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.
        }
    }

Best regards
Tim Düsterhus

Reply via email to