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

From: Date: Sun, 15 Feb 2026 18:20:06 +0000
Subject: Re: [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor
References: 1 2 3 4 5 6 7 8 9  Groups: php.internals 
Request: Send a blank email to internals+get-130075@lists.php.net to get a copy of this message
Reposting as previous delivery apparently failed. Le ven. 13 févr. 2026, 15:52, Nicolas Grekas <nicolas.grekas+php@gmail..com> a écrit : > Hi Tim, > > Le jeu. 5 févr. 2026 à 20:29, Tim Düsterhus <tim@bastelstu..be> a écrit : > >> 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(). >> >> > 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 (#130075) next »