Hi Tim,
Le mer. 18 févr. 2026 à 22:29, Tim Düsterhus <[email protected]> a écrit :
> 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’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.
>
> 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 “owned”
> 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 = 'P',
> ) { }
> }
>
> class C1 extends P1 {
> public readonly string $x;
>
> public function __construct() {
> parent::__construct();
>
> $this->x = 'C'; // This should fail.
> }
> }
>
> 2. Parent uses CPP and reassigns, child redefines.
>
> class P2 {
> public function __construct(
> public readonly string $x = 'P1',
> ) {
> $this->x = '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 = 'P',
> ) { }
> }
>
> class C3 extends P3 {
> public function __construct(
> public readonly string $x = '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 walking
the call stack.
PR and RFC updated accordingly, all green.
Nicolas