Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:130105 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 9D0ED1A00BC for ; Fri, 20 Feb 2026 11:40:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1771587646; bh=EYwgf/qWJ9HjBid+V563CzcrTyXSiEUFZS7hjkGE/Mw=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=TXLWNCHbwgQTgK0WYX/7879eHUqUgtwybtHrXsMrIe3/YXkNpLpcaCK3i2zf45VY3 W11bpUPefeq51+MHOL3BvBZF5g6WT/urz2Z43oDC7A8Q9JQFamLeB2uqzeKkQf4UKJ ipFg4YLpuUyhyEYRsEAIKFzBrERKyA2MydZ6iQ4nbCqEG58fhWMLG4KWr2BXQxDckj BxtorE7YiBf8KeTqQ/TOGX51TI+v2/mivdpm9KrLMBstS2jBLFxRP2EHycyoQ2MOak u4J+dpOheR1k0QOjJILEPeSrsG5xyKdBWrNxAF35OZXeWRPLKyYwCVmgglNvrD+YV3 eMIxqexYVNRXg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 06A2E1805E4 for ; Fri, 20 Feb 2026 11:40:45 +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, 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-qv1-f50.google.com (mail-qv1-f50.google.com [209.85.219.50]) (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 ; Fri, 20 Feb 2026 11:40:44 +0000 (UTC) Received: by mail-qv1-f50.google.com with SMTP id 6a1803df08f44-89549b2f538so51890236d6.2 for ; Fri, 20 Feb 2026 03:40:39 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1771587639; cv=none; d=google.com; s=arc-20240605; b=Z0RU6Z9s5yv21U4QUPXPk3bOpF77ToFq5VdN7MnTnCoZP8upfwcKbDXpr1pssBDNXI BKudxdPjJNlTGUqMPRa7CJP9ssDTKyEFkZdruyQ+pcpyYmxA7weh2v4oILCzvZS4IfYJ X9oJ5uABKPM5DTuqydLiMRgfWqckp+hQazvyirhZ/NRAVP1kt8RN36vImoUgbPBJ+Awc GCYdk5Ge5Q1tzLPbzFZTBdI+wOQpjq3Vp1otBn3nEVihzdsSLspMWFB8Hma/oDkpnMKT UOVLRaBeMsEscKUYps5jQ00Hu2Su5PJvpa7su8Y0Zsuz2NQadm1lLj6jJ6pCdx/gVihg lHrw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=LzFlihfv9qIZKDnbFeviEJSCN+8goFaXvK2UCcsWmok=; fh=Xr/ogOWGTRFA1d+q7p9CE+6uwwIHZm+lXGw7EES2NUs=; b=DyMVQf/UB88aMbD0AsG/c3g2uvnA8DYfjpeBWrmNnb6pRsg7HKLI0ttLuVrQQxHv7y azfnMWwTqpLHL6LB35Gk/YvtUBf+a4Jnidh5g2yAmkjcOqgL4iwvYdMu8ZDaQbX5Z4TT yrc6u25kU+Nxzqv7ckVXdJGjldWe1ySGm7rBrBeaR8JDnW0x+gEsswpKAYcT+jVhzy67 A8z6KmL4F4O+PidQiueFE1Y5zvb18MooHhWUjnrkx2uksN/OCHOlWnjTEB6gf4z6hOCe aubuS6EHq3A1egSFj++ZBRN7Nlcw3GRNdYr95viN0ft8iGsISHVLm1Mid6aJjlzAIFKZ 6+Bg==; 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=1771587639; x=1772192439; darn=lists.php.net; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=LzFlihfv9qIZKDnbFeviEJSCN+8goFaXvK2UCcsWmok=; b=FbPGel8QTDGqHNJYE8oUbUpBjMT65yCPU36+BfJCTZPrNYdDj3XEyUxBRs07ckfwzi ktLdHWd7s6Md+68Z9TfmDbojW7YvebbFsFV+5cKymYBXcTzCP5uFkBYowfOAWMeGfwy6 W24/ZgsdKVljHHA/52Zcr8C/mI9hq+EzI8dRL1NZxQveQaeWURCxUZZZa+cKc3aco2SV LUKeCc7dddJUt12xIFvxhPoh8mGOOZu29EKyB/Vk2o9bG5OYNXbJfGCDl0isRoLeFg/q F4AhU7UmeCV3VRvjHVSkw/0RhTrCuMLNH0AmKd8GyPhhGV1pEYxFUfu44c4puX0+Wya6 3QKg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771587639; x=1772192439; h=cc: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=LzFlihfv9qIZKDnbFeviEJSCN+8goFaXvK2UCcsWmok=; b=c3fJFYf5rIrJnXSK3r8FYiR5wiXqq2jFGkyAkkEtyOIkUiXcem2VK6ia7aQ27U2sv2 yrGcpll0KGsSKtNDtYPXWxGMV166KYHVyssXhrNU6UEdFIdDYHHjWRGUh1KUYyBWW0/K zqz7wJ51XNSJ/X49PkSkCiD98z/48tZDv9jH6TF/a2Y2yUCXXG4UDR/Eqmpukq74BlBm Nbvu5/Z+7FlCCLlalpInL7ZG5Q+1wvwhVC7pf7qkRfmz3chNFd5DmWC8PB7I+NY52VU2 klys2M+pcI5X6g7//r+jTxz2ZjdzGVGKLLpu3aeZj1mH4TMS7r/L8WHpDV/OOOoNsVkI 1S+g== X-Gm-Message-State: AOJu0Yy/G6+yveRYwnA0MX+iRKxbTUJRePVprZAodOjnTaTjaaXJcz1o QiCsOMftgXkJ0iZUs3n+ovGEFX8gdk8vMo9scYonR40zU9BgSykDn5j0Kx2n4uZK3lNXDQkEFAN QjIRBVm3iArtX+8ia+pzvdYo7/5aVmOcZqjCuXX4= X-Gm-Gg: AZuq6aIefExKx6bspNaEySgjWF+i7aSFHIVQyAJnC/29hZIDeBmF11xptBMdEP3mJUt haBuqBQEp1j5rwiznKkwYXvE625BeqAIjRzY7+t4KveEKrct/mBqY/7ZU9LaSKhTTKya1ode2fP LflFqq6fxMH9i2+gGVLPsSV8seql4O5oxSblAZvLfw9dgwhl6mzYlAecp0aXtgY/ddIGPQ3bt5X lbi8PsD69ow/bjsvwBf+Wgykyy8rp9SGj5nfqVi/Di9Vw1pxnamZKAXqju0VrDslvNsm1aYsBVr Mgy/Ad21IgWza1e1FAtDduB3XLykboPnbyuO5T9PvvsULSCUsWsfZUV9eWC8nYlBXei63w== X-Received: by 2002:a05:6214:248f:b0:895:1e32:d2c6 with SMTP id 6a1803df08f44-8974049c50dmr309152456d6.50.1771587638849; Fri, 20 Feb 2026 03:40:38 -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> <3cd0d869-5a5b-467c-8eb4-3f8647ba7aa7@bastelstu.be> In-Reply-To: Date: Fri, 20 Feb 2026 12:40:28 +0100 X-Gm-Features: AaiRm50KFqpqnpkJGyGDogxOFA37Te1aYRyV2Pl4_MVIIu0_iu1zuxDKtBI9MBQ Message-ID: Subject: Re: [PHP-DEV] [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor To: =?UTF-8?Q?Tim_D=C3=BCsterhus?= Cc: PHP Internals List Content-Type: multipart/alternative; boundary="000000000000e6f351064b3fe34f" From: nicolas.grekas+php@gmail.com (Nicolas Grekas) --000000000000e6f351064b3fe34f Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi everyone, Le jeu. 19 f=C3=A9vr. 2026 =C3=A0 10:49, Nicolas Grekas a =C3=A9crit : > Hi Tim, > > Le mer. 18 f=C3=A9vr. 2026 =C3=A0 22:29, Tim D=C3=BCsterhus a =C3=A9crit : > >> Hi >> >> On 2/16/26 19:20, Nicolas Grekas wrote: >> > 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). >> >> Yes, because otherwise you might rely on implementation details of the >> parent constructor: Depending on whether the parent constructor >> reassigns internally, your reassignment in the child constructor either >> succeeds or fails. >> >> > This makes sense and I=E2=80=99ve implemented exactly that model: >> > - Reassignment is allowed only while the declaring class constructor i= s >> > active (including methods/closures called from it). >> > - A child constructor can no longer reassign a parent-declared promote= d >> > readonly property. >> > - =E2=80=9CChild sets first, then parent::__construct()=E2=80=9D now t= hrows 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. >> >> Thank you. I've checked the RFC and the explanation and semantics make >> sense to me. I've also reviewed (parts) of the tests and provided some >> feedback there. I'll take another look at the tests when you made the >> adjustments to make sure that everything in the RFC is properly tested >> to make sure we didn't miss and edge case. >> >> > Anything else? >> >> Yes, there is one edge case related to inheritance that isn't mentioned >> in the RFC and from what I see it's not tested either. >> >> Child classes can redefine readonly properties and they are then =E2=80= =9Cowned=E2=80=9D >> by the child class. Thus we need to explain what happens in that case. >> I've prepared example for the three relevant cases I can think of. The >> follow from the existing semantics in a straight-forward fashion, but >> it's good to spell them out explicitly (and particularly test them). >> >> 1. Parent uses CPP, child redefines and reassigns. >> >> class P1 { >> public function __construct( >> public readonly string $x =3D 'P', >> ) { } >> } >> >> class C1 extends P1 { >> public readonly string $x; >> >> public function __construct() { >> parent::__construct(); >> >> $this->x =3D 'C'; // This should fail. >> } >> } >> >> 2. Parent uses CPP and reassigns, child redefines. >> >> class P2 { >> public function __construct( >> public readonly string $x =3D 'P1', >> ) { >> $this->x =3D 'P2'; // This should be legal. >> } >> } >> >> class C2 extends P2 { >> public readonly string $x; >> >> public function __construct() { >> parent::__construct(); >> } >> } >> >> 3. Parent uses CPP, child uses CPP redefinition. >> >> class P3 { >> public function __construct( >> public readonly string $x =3D 'P', >> ) { } >> } >> >> class C3 extends P3 { >> public function __construct( >> public readonly string $x =3D 'C1', >> ) { >> parent::__construct(); // This should fail. >> } >> } >> >> > > Thanks, I've added new test cases to cover this. > I've also improved tests as suggested on the PR. > And finally I updated the implementation to reuse IS_PROP_REINITABLE > instead of adding new flags + use an approach that doesn't require walkin= g > the call stack. > PR and RFC updated accordingly, all green. > One last update following more review comments by Tim: the reassign window is now scoped to the CPP-owning constructor. See updated wording at https://wiki.php.net/rfc/promoted_readonly_constructor_reassign Cheers, Nicolas --000000000000e6f351064b3fe34f Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi everyone,

Le=C2= =A0jeu. 19 f=C3=A9vr. 2026 =C3=A0=C2=A010:49, Nicolas Grekas <nicolas.grekas+php@gmail.com>= ; a =C3=A9crit=C2=A0:
Hi Tim,

Le=C2=A0mer. 18 f=C3=A9vr. 2= 026 =C3=A0=C2=A022:29, Tim D=C3=BCsterhus <tim@bastelstu.be> a =C3=A9crit=C2=A0:
Hi

On 2/16/26 19:20, Nicolas Grekas wrote:
> To be sure I understood you well: you are suggesting that mutability s= hould
> be scoped to the constructor that declares the property (not any
> constructor on the object).

Yes, because otherwise you might rely on implementation details of the
parent constructor: Depending on whether the parent constructor
reassigns internally, your reassignment in the child constructor either succeeds or fails.

> This makes sense and I=E2=80=99ve implemented exactly that model:
> - Reassignment is allowed only while the declaring class constructor i= s
> active (including methods/closures called from it).
> - A child constructor can no longer reassign a parent-declared promote= d
> readonly property.
> - =E2=80=9CChild sets first, then parent::__construct()=E2=80=9D now t= hrows as expected.
> - The thrown Error is catchable from the child (around
> parent::__construct()), but not from inside the parent body before imp= licit
> CPP init.
> - Calling __construct() on an already-constructed object still cannot<= br> > mutate readonly state.
>
> I also updated the RFC text and examples to state this explicitly, and=
> added/updated tests for the inheritance/preemption scenarios.

Thank you. I've checked the RFC and the explanation and semantics make =
sense to me. I've also reviewed (parts) of the tests and provided some =
feedback there. I'll take another look at the tests when you made the <= br> adjustments to make sure that everything in the RFC is properly tested
to make sure we didn't miss and edge case.

> Anything else?

Yes, there is one edge case related to inheritance that isn't mentioned=
in the RFC and from what I see it's not tested either.

Child classes can redefine readonly properties and they are then =E2=80=9Co= wned=E2=80=9D
by the child class. Thus we need to explain what happens in that case.
I've prepared example for the three relevant cases I can think of. The =
follow from the existing semantics in a straight-forward fashion, but
it's good to spell them out explicitly (and particularly test them).
1. Parent uses CPP, child redefines and reassigns.

=C2=A0 =C2=A0 =C2=A0class P1 {
=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 C1 extends P1 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0public readonly string $x;

=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=A0parent::__construct();

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$this->x =3D 'C'= ; // This should fail.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0}

2. Parent uses CPP and reassigns, child redefines.

=C2=A0 =C2=A0 =C2=A0class P2 {
=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 'P1',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0$this->x =3D 'P2'= ;; // This should be legal.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0}

=C2=A0 =C2=A0 =C2=A0class C2 extends P2 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0public readonly string $x;

=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=A0parent::__construct();
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0}

3. Parent uses CPP, child uses CPP redefinition.

=C2=A0 =C2=A0 =C2=A0class P3 {
=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 C3 extends P3 {
=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 'C1',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0parent::__construct(); // T= his should fail.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0}



Thanks, I've added = new test cases to cover this.
I've also improved tests as sug= gested on the PR.
And finally I updated the implementation to reu= se IS_PROP_REINITABLE instead of adding new flags=C2=A0+ use an approach th= at doesn't require walking the call stack.
PR and RFC updated= accordingly, all green.

= One last update following more review comments by Tim:=C2=A0 the reassign w= indow is now scoped to the CPP-owning constructor.

Cheers,
Nicolas
--000000000000e6f351064b3fe34f--