Re: [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor

From: Date: Mon, 16 Feb 2026 18:20:41 +0000
Subject: Re: [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor
References: 1 2 3 4 5 6 7 8  Groups: php.internals 
Request: Send a blank email to internals+get-130072@lists.php.net to get a copy of this message
(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 = 'P', > ) { } > } > > class C extends P { > public function __construct() { > $this->x = 'C'; > > parent::__construct(); // Will this throw or not? > } > } > > More generally, as Rob, I stumbled upon the “Child Classes Can Reassign > Parent Properties” 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 “post-processing” on a > promoted readonly property that it owns. Specifically, the property > becomes locked once the constructor completes. > > For the example in “Child Classes Can Reassign Parent Properties” 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 ”lock” > $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 “unlock” the property after the “auto-generated > promotion logic” finished and should “relock” the property when the > method with the auto-generated promotion logic finishes. > > In other words: > > public function __construct( > public readonly string $prop = 'parent default', > ) { > // Parent does NOT reassign here > } > > should be equivalent to: > > public function __construct( > string $prop = 'parent default', > ) { > $this->prop = $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 “will this throw” question of > this email would be “yes”, because the implicit $this->prop = > $prop > assignment happens before the unlock. I believe it would also more > closely match the semantics of __clone(). > > Best regards > Tim Düsterhus > 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’ve 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. - “Child sets first, then parent::__construct()” 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

« previous php.internals (#130072) next »