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