Re: [RFC] Allow Reassignment of Promoted Readonly Properties in Constructor
| From: | Nicolas Grekas | 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
>