Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:130072 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 8F48D1A00BC for ; Mon, 16 Feb 2026 18:20:54 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1771266059; bh=smOirq/LtzU7EG92oU637n3R4dgFb9e0rIixuffQSmo=; h=References:In-Reply-To:From:Date:Subject:To:From; b=MtAK9k1sAcQWJ3ktC+9qOPBSoNXdFHPD6mI7qSlWF0cYjJTuJ/o54pmi+DxCeZKFa yFtd+aOy2CamYi22NB24QDq70BgL88Chkt7bGQzUHi+KGZz9UmGQAbEGrQIwUnPQtm o5sjcXVm344XXs5Snw+1Q6e0OJTOTOBwQBjHRnTQdZhr0uw6U4n/qbi8EPHptOYp8B E90pe+0AzEJp4AlEpkZ9zhroycxsUl2oKC3yPazrSf9CXoZd8J+5hJj1FKkWq1pS6h C+M+dmEXo8nV9FcWYSu00Kap4dcBVmN1S/WY5Bmk+38lffcHEXMbXkdiCed1M4F9Al 1VYwOZwZjvqJA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 696DC1801E8 for ; Mon, 16 Feb 2026 18:20:58 +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=1.6 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_50, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS, FORGED_GMAIL_RCVD,FREEMAIL_FROM,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-qv1-f53.google.com (mail-qv1-f53.google.com [209.85.219.53]) (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 18:20:58 +0000 (UTC) Received: by mail-qv1-f53.google.com with SMTP id 6a1803df08f44-895071c5527so47284146d6.1 for ; Mon, 16 Feb 2026 10:20:53 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1771266052; cv=none; d=google.com; s=arc-20240605; b=Wi/jCnm4JI7qsARWd9WJMDAAeQULPl9TM0NApAyHVvwz9VdWSZQwp2FrTUiZ56cVYE gRUp6OTP+A/gBSMnX7prGy4BcJ/pIDePEOCMq+gD6LN8OEddvtwuLdiF0V/ZyGJCSIBX Hatwv6K7/IeqmfSl4NrXp0XploekZvHsjKgoNvUNPCIj7ip3sp6DfirVCqtPYVh3x6wu H/BfJieO+kMbMdXWPFQdiSa0QIIRaRDHlhFsPWQm0hDzOBquSOXX1veIMyrUl+xSVPbB 6Zdu+4JnImvQgLVs4apM5Lo0Pa7HefdLzc1y5UthSn1jWbjp3M5G71h47lX54B6FOGfn LgOw== 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=qbjKMz8TEe7Gj+BIMoTzv2eI7WiSsF/xImhbuni6PaE=; fh=RFBKMNwdsUBkXmmb8AAjYIg7L3EzMrZygiLbMB7seK8=; b=BlWSsZw6t8B+9ZKqQ52egBbnZTSR+Ibbtg6ouOJxrrqvVWvtbfHSFIhXpyznPw7Aj+ XirISmUYTP+/ziHdYzmiMQjkY/mO8vNmN51Sbz5sXYJQ2mLZR8U0OuUtzb6MiT2x7cRj oUl6pDhexJ1h0QEQOHgNMD+J2MD8+zi/Xp/jI6aHsImhFiMTbUq/2s2HiTPnI8LZFaDB uW3dErIrouKJcwsWKuuY14m9TdLC6iGqPSnctHb6x2juq/i3yTa6J4hlWkI7i2OzwzO2 p3Vfrs4ndZzUNNuyupfgo7ft7EWpzFLVuNc8CmwroAi/f5s3tLSzTb7l8GRIRMB/nYy9 CiDA==; 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=1771266052; x=1771870852; 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=qbjKMz8TEe7Gj+BIMoTzv2eI7WiSsF/xImhbuni6PaE=; b=gEVDKE1AADk8nhuaU+fL+UZraeioqS1YaUOnGbkEWzzS9Ec+Pv1ik2FIr63yq/ukZZ wLbWhXYoSkc6VXn/51z+Lknm9X7jQqOP14yOHmmGsxJuTB+Nbw94CJMVSQ1nokObnnmY iV8w9JGzXN3tX3wTkpRZFzZShTQw8jFmQ80rCSxIEahmJl7FRfCYIRzLHDkERlqflJgp bJYyqIuQMoGNnP9D8x2WhrxCXSpAvuZ6IkhtLll2SRCa13KWqSKb9T8JHxAE97Qfs6RC mFJzzJHN4BQNcNWMznzQkJxANBpbLjSRQvtS1ILnSJ6NhzuuphVchBYYkCuZpbjZCWuM KVCA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771266052; x=1771870852; 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=qbjKMz8TEe7Gj+BIMoTzv2eI7WiSsF/xImhbuni6PaE=; b=TUMXLvvO3A68H/1jHTbYIbRgIA+n5dO/vmfcgeK4mi4do/1MOT45MqNBzCDXWR2ak2 4IniN8PTVy4lhLYfweiMGNFW3m5y+RhwRst3oXvfhW52JZZ3HSv12RVoElqxY1j9ndgv dia6iJBEgM5qCvmzMlqRHnUzkKEckYunllMXk9nCLNwpvYRti07Mcxz6spsXSKCkf9JT RhebgOQa7YYw2TJOIHkbTJyz6n3Gt/XPsbTWGzzwAc64NdATS6qecsgHLA+t7cZjicvu Jj1/TospdBoyDGMaq5TSB+YPmDfyMOWbR5r9aquF3+0kYqd63xOT7R4cteREoZMFPLh0 P13w== X-Gm-Message-State: AOJu0Yx400USr2kFpbUq5BSDHIHjlsog6/II/1vLWnJXoSuoAwnyNPS0 SOv6jkNL6Nr84NurrmgxqPDXxDni3ylTnL+XfpARJXLDkIYDvm0+W1FGHbG3d5XOcXbRejl9BLt 11qg/cK5LtPmd9bLPfJkGGQD+EW7Lb/k7isbwjQA= X-Gm-Gg: AZuq6aJN4fMRAkDB2supDcKo8yOzGRXm4JrEaqL902LY2KU4jgFwH4dxZ1EaBf7PBuN 4l5KIppNJAdEwh+sp8hfM20dNYdMpLau2fcvA0gvP6suP31cDTgJVxel9frIBS6lSYpTgH2Qr8R VhthANTC/T6I8eqK2tkpuG25eFvfN9SV14iXDzbvoDGywY39hDx1EncXxvfPwmViJVymtNIwHyq rhZk56j0s89vEN1UkMUf3ezgmo05sjdno5vFUpdcS8v9eKwEZgaFOB0zcgiphFSQAFS/cPawh9e fkZ19ES3tKANxwyGA+Nl7eWEqqFzP6Oz6r1yb5aO3d4pMUWnXEvU/Tl1r4Gb791+vqw+5X+ylRf L/kB0 X-Received: by 2002:ad4:5ca9:0:b0:896:76ab:5a1 with SMTP id 6a1803df08f44-897404d10dbmr145543026d6.65.1771266052358; Mon, 16 Feb 2026 10:20:52 -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: Mon, 16 Feb 2026 19:20:41 +0100 X-Gm-Features: AaiRm526rUYtK-Y9hTGCyMVfUKmc0rSNk2AonFHgKj4I2aA23w8Uxjl2rdNdNos Message-ID: Subject: Re: [PHP-DEV] [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor To: PHP Internals List Content-Type: multipart/alternative; boundary="000000000000da858d064af50368" From: nicolas.grekas+php@gmail.com (Nicolas Grekas) --000000000000da858d064af50368 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable (Reposting per Tim's email, thanks Derick for working on this!) > 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. > > 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 Re= assign > 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=9Dl= ock=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= 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->p= rop =3D $prop` > assignment happens before the unlock. I believe it would also more > closely match the semantics of `__clone()`. > > Best regards > Tim D=C3=BCsterhus > 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 throws= as expected. - The thrown Error is catchable from the child (around parent::__construct()), but not from inside the parent body before implicit 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 --000000000000da858d064af50368 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
= (Reposting per Tim's email, thanks Derick for working=C2=A0on this!)
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()`.

Best regards
Tim D=C3=BCsterhus

To be sure I understood you well: you are sug= gesting that mutability should be scoped to the constructor that declares t= he property (not any constructor on the object).

This makes sense and I=E2=80=99ve implemented exactly that = model:
-=C2=A0Reassignment is allowed only while the declaring class con= structor 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 p= arent::__construct()=E2=80=9D now throws as expected.
- The thrown Error= is catchable from the child (around parent::__construct()), but not from i= nside the parent body before implicit 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/preem= ption scenarios.

Anything else?

Cheers= ,
Nicolas

=C2=A0
--000000000000da858d064af50368--