Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:130075 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id E5B561A00BC for ; Mon, 16 Feb 2026 20:11:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1771272720; bh=uHxKuNymDWnbgmAmWMel0opn5Bbn1SER18SjmjAuTRc=; h=References:In-Reply-To:From:Date:Subject:To:From; b=iebdAQ1HNhcwH7cRzVs6Od+6inyIBphJ45P9JvAJdi7oaCJGr2LUA3gtVPaSb822g QWNvg6+JamFwJGOwtT75Hmm+OGJCGR0/kar90KVNtD2xDyxPOACQt1+coRIM9hrU29 BRJS00LYZyrZQaFXxEvzBGG6t5j+MR7lzzewv3XOdGZK6RQre0Bcf8hc/tzQaZFVbE f3QKxlxXU0eupoq3eOkclqNGHWQwIcpHDIg4N6qvlWUL3W11voptEDgP7bRBBVSpHB P+e15XOocncum1x4CwRoLJw3p6PlJh+8utdsQ3c7Rwp/r8Eh+8mW0n+p8GzZFhhILo AUXGYegmcQ+Gw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id CA2DD180059 for ; Mon, 16 Feb 2026 20:11:59 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: ** X-Spam-Status: No, score=2.9 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_50, DATE_IN_PAST_24_48,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF, DMARC_PASS,FREEMAIL_FROM,FREEMAIL_REPLY,HTML_MESSAGE, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from mail-vk1-f169.google.com (mail-vk1-f169.google.com [209.85.221.169]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Mon, 16 Feb 2026 20:11:49 +0000 (UTC) Received: by mail-vk1-f169.google.com with SMTP id 71dfb90a1353d-5663724e4daso2613638e0c.3 for ; Mon, 16 Feb 2026 12:11:44 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1771272704; cv=none; d=google.com; s=arc-20240605; b=fPLnSL0q3PCPyIpg63STSb17unCU9M+CNgxuZnMhUF2xt5gmSS/CXhBIIYGaa1imov B5koqlgVRWLmuo6eBddJFTDfIgUQgpSAlFbBE2lsl5U0btNsJrFyC3sggBoos1Va9/NQ LywkLFddzmWcoswGjdiVhvg+9rQ/atCcwkjk9/wBjV8CcBrPHgukKzEfwlOXuUnAFirI QEJEzXOcxI+0CkDzhuL50EbKPkxGu+7OcmooM82/JjJY/I/KbIxdxQAUxHGGaa0szlDS LEnDljlwQIJ/rlht2FvDAxBxuCsGJkDlLYHXP8YAfqOnxr+l63eGpSC26hK6TZUMmz38 HlvQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :dkim-signature; bh=Q13CuFKdmMhu9T1HeXl5AW6xcA311KeEqvkXsDOSIm4=; fh=RFBKMNwdsUBkXmmb8AAjYIg7L3EzMrZygiLbMB7seK8=; b=NFHV//rZOVqXLk19w/TGmotKYRRys7mhH+jiTCb3MKedUYOmrgj7vz90Bohugqwla0 g7FBfzX5/ju17ASBOInhB7F5m4uXlZBqz+XQb1NSZeSxXs67C6Ygo7oV6IFdZ7JjFFuj sTBTgFXRfOU6eZ7znHN+mwth/I3oPKtlSYkLowJlew1vh/vD9CNVOS+2OJKmDChns9dj maSd8NvY6Bqc1s5fRMcQ8gDkl6KjOAEb3exM3ZudX6nITPYG5Z4apNeY5gnQgL5WPFZX O/lD4s6ieUv1mAAeKOwa6O2daLdiMPc5lGWsVjrYL1L6iVG50wooI2rdfQqB/Wntr2lV EBLw==; darn=lists.php.net ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771272704; x=1771877504; darn=lists.php.net; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :from:to:cc:subject:date:message-id:reply-to; bh=Q13CuFKdmMhu9T1HeXl5AW6xcA311KeEqvkXsDOSIm4=; b=Ix/w5BoByPgk5O0WtOTpFEvAC+WB1w+BaQIpc8Lu+mauDIJhHGTB0FquebvCXEUcR+ BqV30hCJ3TgtP3ahHcOoMnQN8stq1NN50c5sJm71laUQKAhAxwa4MnppKkT/Q5dIHepv pewxLHOUdgrMFgv84jCiY8R3KWh8NfOLAp7UK5yHUrQoCnKC/LVa/+H1dLtHg/zcl41X o8PD4GTcLUFdWQrCpTsXXY0NCRxHQPSSJpURbezsG8jODKwwK5np50eIhmZdZ9EpAzHf icZShSYQVx00XeIFyOescNXk51vD5dcLoPHUwjVtay00GeeiLlZhLrnK5Dg1tT/35lDO svOQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771272704; x=1771877504; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=Q13CuFKdmMhu9T1HeXl5AW6xcA311KeEqvkXsDOSIm4=; b=gawLZGost7gUO0KN7kx2eyEBgkzW4Xgf41eNOP/QKvAH66Ox0Vf/gb/FJuRarOsw8X Mpu5hamNESLwzZyBs6ziZBKk2xdgqQ2rra0xg29lHyZgHiOLGIiCt8ZWrTIV0pnzYhNf wF/00YmIkGo0GlhaNxXhaVQdD2MHpRFYpH3Bejd58C/WCjX/3lohZc39A2ROMAv3U5Z7 WXGA9gjoNY/jZz2TPz4Ak0MCe5f9JDVGxFfOWdwBRbmG91lSaBx1KgeH44aWFC/0AjzW /3xZZbpysXbXjDcY9pWuNobO7XG5G/AAmO5x9tas3Wa/pWNjT5UPejsYZBuzDu+yBB08 BISg== X-Gm-Message-State: AOJu0Ywe+lOdzUB46oqdTkC70Jlmtd99WDpSPUavk2hA/Jr6sJSfM9dJ tvqKomUmIdk6f6BNMEpQTFekN+dl3qdxrPj10mVM/Y+BC446WS/Yvl8BGxJK8wCzRTEvVYCrFMj vDz3HdA8woejqVrUNcfbUJ5TtUQrm0W+pm1nI34A= X-Gm-Gg: AZuq6aKH74RdXINJZprNmCOgSU7azGwAxMfTl+wVYC1l9q4oFF3uRXWNViPApCsDEVz aP/LH0J47AJNEU/lwJ0zsBlEW4Q182bDBaw3rRpnhhhbjjjgY3L6JcHoJ76NR8Zd+EjUqsdZtRQ wHQov2M1TDZLmM+NQcmQQD7L5mINREVG+Ubv8nWJRjs5a9Jvi5QQgGE7QBeCww1kd8Y85ZKkTiS eiTjBYnKczRNrfKGsmYTu08ZbALzYUMrrdb4KU2vgxaOpf9yietsAyIXus6lwFakc1RxxNz3DW4 3L/bldGpc8e+lUmzZuSxCI2BXdKCWmAmFcWTTOOiP3Ns7igD2kY4/bAbNjl9ac/SDHRl5HfkUaa gFAJhaIlaphffSxkOboXcHvTNllQ= X-Received: by 2002:a05:620a:46a1:b0:8ca:4545:aedc with SMTP id af79cd13be357-8cb422b3a9dmr935647085a.36.1771179617838; Sun, 15 Feb 2026 10:20:17 -0800 (PST) Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 References: <4b74f9a1-96d2-4104-abdd-fe56c5e7016c@app.fastmail.com> <585bfc82-a522-43ec-be42-b8945952fe8b@app.fastmail.com> In-Reply-To: Date: Sun, 15 Feb 2026 19:20:06 +0100 X-Gm-Features: AaiRm52IaYDSC8AKFjfW2Mnb-dfbimBrkh40l85oTM-Sgj5iZ45aPkIam2g7QUE Message-ID: Subject: Re: [PHP-DEV] [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor To: PHP Internals List Content-Type: multipart/alternative; boundary="000000000000f469af064ae0e3f1" From: nicolas.grekas+php@gmail.com (Nicolas Grekas) --000000000000f469af064ae0e3f1 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Reposting as previous delivery apparently failed. Le ven. 13 f=C3=A9vr. 2026, 15:52, Nicolas Grekas a =C3=A9crit : > Hi Tim, > > Le jeu. 5 f=C3=A9vr. 2026 =C3=A0 20:29, Tim D=C3=BCsterhus a =C3=A9crit : > >> Hi >> >> On 2/3/26 10:22, Nicolas Grekas wrote: >> > The existing behavior is preserved: if a reassignment fails, it throws= a >> > catchable Error. The implicit CPP assignment in a parent constructor >> > happens before the parent body, so a child cannot "set first" and then >> call >> > ''parent::__construct()'' to avoid it; a try/catch in the parent canno= t >> >> This is a good example that as far as I can tell is not explicitly >> spelled out in the RFC: Please include an example where the *child* sets >> a readonly property defined in the parent before calling the parent >> constructor. >> >> class P { >> public function __construct( >> public readonly string $x =3D 'P', >> ) { } >> } >> >> class C extends P { >> public function __construct() { >> $this->x =3D 'C'; >> >> parent::__construct(); // Will this throw or not? >> } >> } >> >> More generally, as Rob, I stumbled upon the =E2=80=9CChild Classes Can R= eassign >> Parent Properties=E2=80=9D section, because it's least obviously correct >> behavior to me. >> >> My understanding of this RFC is that it is intended to solve the case >> where the class itself needs to perform some =E2=80=9Cpost-processing=E2= =80=9D on a >> promoted readonly property that it owns. Specifically, the property >> becomes locked once the constructor completes. >> >> For the example in =E2=80=9CChild Classes Can Reassign Parent Properties= =E2=80=9D my >> mental model says that `$prop` is owned by `Parent_`, since `Parent_` is >> the class that declared it. Thus it would be natural for me to =E2=80=9D= lock=E2=80=9D >> `$prop` once the `parent::__construct()` call completes. If the child >> class needs to do special processing on the property, it has two options= : >> >> 1. Not call the parent constructor. If the parent constructor logic is >> unfit, then not calling the constructor is the right thing rather than >> trying to revert part of what it did to a readonly property. >> >> 2. Call `parent::__construct()` with an appropriately modified value: >> parent::__construct('child override'); >> >> So to describe my expected semantics in more technical terms: The >> implementation should =E2=80=9Cunlock=E2=80=9D the property after the = =E2=80=9Cauto-generated >> promotion logic=E2=80=9D finished and should =E2=80=9Crelock=E2=80=9D th= e property when the >> method with the auto-generated promotion logic finishes. >> >> In other words: >> >> public function __construct( >> public readonly string $prop =3D 'parent default', >> ) { >> // Parent does NOT reassign here >> } >> >> should be equivalent to: >> >> public function __construct( >> string $prop =3D 'parent default', >> ) { >> $this->prop =3D $prop; >> // unlock $this->prop (set IS_PROP_REINITABLE) >> try { >> // Parent does NOT reassign here >> } finally { >> // lock $this->prop (unset IS_PROP_REINITABLE) >> } >> } >> >> With this logic, the answer to initial =E2=80=9Cwill this throw=E2=80=9D= question of >> this email would be =E2=80=9Cyes=E2=80=9D, because the implicit `$this->= prop =3D $prop` >> assignment happens before the unlock. I believe it would also more >> closely match the semantics of `__clone()`. >> >> > To be sure I understood you well: you are suggesting that mutability > should be scoped to the constructor that declares the property (not any > constructor on the object). > > This makes sense and I=E2=80=99ve implemented exactly that model: > - Reassignment is allowed only while the declaring class constructor is > active (including methods/closures called from it). > - A child constructor can no longer reassign a parent-declared promoted > readonly property. > - =E2=80=9CChild sets first, then parent::__construct()=E2=80=9D now thro= ws as expected. > - The thrown Error is catchable from the child (around > parent::__construct()), but not from inside the parent body before implic= it > CPP init. > - Calling __construct() on an already-constructed object still cannot > mutate readonly state. > > I also updated the RFC text and examples to state this explicitly, and > added/updated tests for the inheritance/preemption scenarios. > > Anything else? > > Cheers, > Nicolas > --000000000000f469af064ae0e3f1 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Reposting as previous delivery apparently failed.
Le ven. 13 f=C3=A9vr. 2026, 15:52, Nicolas Grekas <nicolas.grekas+php@gmail.com> a =C3=A9crit=C2=A0:

Hi

On 2/3/26 10:22, Nicolas Grekas wrote:
> The existing behavior is preserved: if a reassignment fails, it throws= a
> catchable Error. The implicit CPP assignment in a parent constructor > happens before the parent body, so a child cannot "set first"= ; and then call
> ''parent::__construct()'' to avoid it; a try/catch in = the parent cannot

This is a good example that as far as I can tell is not explicitly
spelled out in the RFC: Please include an example where the *child* sets a readonly property defined in the parent before calling the parent
constructor.

=C2=A0 =C2=A0 =C2=A0class P {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0public function __construct(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0public readonly string $x = =3D 'P',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0) { }
=C2=A0 =C2=A0 =C2=A0}

=C2=A0 =C2=A0 =C2=A0class C extends P {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0public function __construct() {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$this->x =3D 'C'= ;

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0parent::__construct(); // W= ill this throw or not?
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0}

More generally, as Rob, I stumbled upon the =E2=80=9CChild Classes Can Reas= sign
Parent Properties=E2=80=9D section, because it's least obviously correc= t
behavior to me.

My understanding of this RFC is that it is intended to solve the case
where the class itself needs to perform some =E2=80=9Cpost-processing=E2=80= =9D on a
promoted readonly property that it owns. Specifically, the property
becomes locked once the constructor completes.

For the example in =E2=80=9CChild Classes Can Reassign Parent Properties=E2= =80=9D my
mental model says that `$prop` is owned by `Parent_`, since `Parent_` is the class that declared it. Thus it would be natural for me to =E2=80=9Dloc= k=E2=80=9D
`$prop` once the `parent::__construct()` call completes. If the child
class needs to do special processing on the property, it has two options:
1. Not call the parent constructor. If the parent constructor logic is
unfit, then not calling the constructor is the right thing rather than
trying to revert part of what it did to a readonly property.

2. Call `parent::__construct()` with an appropriately modified value:
parent::__construct('child override');

So to describe my expected semantics in more technical terms: The
implementation should =E2=80=9Cunlock=E2=80=9D the property after the =E2= =80=9Cauto-generated
promotion logic=E2=80=9D finished and should =E2=80=9Crelock=E2=80=9D the p= roperty when the
method with the auto-generated promotion logic finishes.

In other words:

=C2=A0 =C2=A0 =C2=A0public function __construct(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0public readonly string $prop =3D 'par= ent default',
=C2=A0 =C2=A0 =C2=A0) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0// Parent does NOT reassign here
=C2=A0 =C2=A0 =C2=A0}

should be equivalent to:

=C2=A0 =C2=A0 =C2=A0public function __construct(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0string $prop =3D 'parent default'= ,
=C2=A0 =C2=A0 =C2=A0) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$this->prop =3D $prop;
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0// unlock $this->prop (set IS_PROP_REI= NITABLE)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0try {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0// Parent does NOT reassign= here
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0} finally {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0// lock $this->prop (uns= et IS_PROP_REINITABLE)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0}

With this logic, the answer to initial =E2=80=9Cwill this throw=E2=80=9D qu= estion of
this email would be =E2=80=9Cyes=E2=80=9D, because the implicit `$this->= prop =3D $prop`
assignment happens before the unlock. I believe it would also more
closely match the semantics of `__clone()`.


To be sure I und= erstood you well: you are suggesting that mutability should be scoped to th= e constructor that declares the property (not any constructor on the object= ).

This makes sense and I=E2=80= =99ve implemented exactly that model:
-=C2=A0Reassignment is allowed onl= y while the declaring class constructor is active (including methods/closur= es called from it).
- A child constructor c= an no longer reassign a parent-declared promoted readonly property.
- = =E2=80=9CChild sets first, then parent::__construct()=E2=80=9D now throws a= s expected.
- The thrown Error is catchable from the child (around paren= t::__construct()), but not from inside the parent body before implicit CPP = init.
- Calling __construct() on an already-constructed object still can= not mutate readonly state.

I also u= pdated the RFC text and examples to state this explicitly, and added/update= d tests for the inheritance/preemption scenarios.

Anything else= ?

Cheers,
Nicolas
--000000000000f469af064ae0e3f1--