Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Zend/tests/readonly_props/cpp_reassign_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
Promoted readonly property reassignment in constructor - basic
--FILE--
<?php

class Point {
public function __construct(
public readonly int $x = 0,
public readonly int $y = 0,
) {
// Reassign promoted readonly properties - allowed once
$this->x = abs($x);
$this->y = abs($y);
}
}

$point = new Point();
var_dump($point->x, $point->y);

$point2 = new Point(-5, -3);
var_dump($point2->x, $point2->y);

?>
--EXPECT--
int(0)
int(0)
int(5)
int(3)
89 changes: 89 additions & 0 deletions Zend/tests/readonly_props/cpp_reassign_child_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
--TEST--
Promoted readonly property reassignment in constructor - child cannot reassign parent's property
--FILE--
<?php

// Case 1: Parent does NOT use reassignment, child still cannot reassign
class Parent1 {
public function __construct(
public readonly string $prop = 'parent default',
) {
// Parent does NOT reassign here
}
}

class Child1 extends Parent1 {
public function __construct() {
parent::__construct();
// Child cannot reassign parent-owned promoted property
try {
$this->prop = 'child override';
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}
}

$child = new Child1();
var_dump($child->prop);

// Case 2: Parent USES reassignment, child cannot
class Parent2 {
public function __construct(
public readonly string $prop = 'parent default',
) {
$this->prop = 'parent set'; // Uses the one reassignment
}
}

class Child2 extends Parent2 {
public function __construct() {
parent::__construct();
// Child cannot reassign parent-owned promoted property
try {
$this->prop = 'child override';
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}
}

$child2 = new Child2();
var_dump($child2->prop);

// Case 3: Child with its own promoted property
class Parent3 {
public function __construct(
public readonly string $parentProp = 'parent default',
) {
// Parent does NOT reassign here
}
}

class Child3 extends Parent3 {
public function __construct(
public readonly string $childProp = 'child default',
) {
parent::__construct();
// Child cannot reassign parent's property, but can reassign its own
try {
$this->parentProp = 'child set parent';
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
$this->childProp = 'child set own';
}
}

$child3 = new Child3();
var_dump($child3->parentProp, $child3->childProp);

?>
--EXPECT--
Error: Cannot modify readonly property Parent1::$prop
string(14) "parent default"
Error: Cannot modify readonly property Parent2::$prop
string(10) "parent set"
Error: Cannot modify readonly property Parent3::$parentProp
string(14) "parent default"
string(13) "child set own"
31 changes: 31 additions & 0 deletions Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Promoted readonly property reassignment in constructor - child preempt then parent ctor throws
--FILE--
<?php

class ParentCPP {
public function __construct(
public readonly string $prop = 'parent default',
) {
$this->prop = 'parent set';
}
}

class ChildCPP extends ParentCPP {
public function __construct() {
$this->prop = 'child set';
try {
parent::__construct();
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}
}

$c = new ChildCPP();
var_dump($c->prop);

?>
--EXPECT--
Error: Cannot modify readonly property ParentCPP::$prop
string(9) "child set"
84 changes: 84 additions & 0 deletions Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
--TEST--
Promoted readonly property reassignment in constructor - child redefines parent property
--FILE--
<?php

// Case 1: Parent uses CPP, child redefines as non-promoted, child tries to reassign.
// P1 owns the CPP reassignment window; it is cleared when P1's constructor exits,
// before C1's body runs. So C1's write attempt fails.
class P1 {
public function __construct(
public readonly string $x = 'P',
) {}
}

class C1 extends P1 {
public readonly string $x;

public function __construct() {
parent::__construct();
try {
$this->x = 'C';
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}
}

$c1 = new C1();
var_dump($c1->x);

// Case 2: Parent uses CPP and reassigns; child redefines as non-promoted.
// The child does not use CPP, so it does not claim CPP ownership of the property.
// P2's CPP "owns" the reassignment window: P2's body write succeeds.
class P2 {
public function __construct(
public readonly string $x = 'P1',
) {
$this->x = 'P2';
}
}

class C2 extends P2 {
public readonly string $x;

public function __construct() {
parent::__construct();
}
}

$c2 = new C2();
var_dump($c2->x);

// Case 3: Parent uses CPP, child uses CPP redefinition.
// Child's CPP opens the reassignment window for C3::$x. When parent::__construct()
// runs, P3's CPP tries to initialize C3::$x again, which must fail since C3
// owns the property and has already initialized it.
class P3 {
public function __construct(
public readonly string $x = 'P',
) {}
}

class C3 extends P3 {
public function __construct(
public readonly string $x = 'C1',
) {
try {
parent::__construct();
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}
}

$c3 = new C3();
var_dump($c3->x);

?>
--EXPECT--
Error: Cannot modify readonly property C1::$x
string(1) "P"
string(2) "P2"
Error: Cannot modify readonly property C3::$x
string(2) "C1"
23 changes: 23 additions & 0 deletions Zend/tests/readonly_props/cpp_reassign_conditional.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Promoted readonly property reassignment in constructor - conditional initialization
--FILE--
<?php

class Config {
public function __construct(
public readonly ?string $cacheDir = null,
) {
$this->cacheDir ??= '/tmp/app_cache';
}
}

$config1 = new Config();
var_dump($config1->cacheDir);

$config2 = new Config('/custom/cache');
var_dump($config2->cacheDir);

?>
--EXPECT--
string(14) "/tmp/app_cache"
string(13) "/custom/cache"
34 changes: 34 additions & 0 deletions Zend/tests/readonly_props/cpp_reassign_different_object.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
Promoted readonly property reassignment in constructor - different object fails
--FILE--
<?php

// Constructor can modify its own promoted property, but not another object's
class Foo {
public function __construct(
public readonly int $x = 0,
?Foo $other = null,
) {
$this->x = $x * 2;
if ($other !== null) {
try {
$other->x = 999;
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}
}
}

$a = new Foo(5);
var_dump($a->x);

$b = new Foo(3, $a);
var_dump($a->x, $b->x); // $a unchanged

?>
--EXPECT--
int(10)
Error: Cannot modify readonly property Foo::$x
int(10)
int(6)
29 changes: 29 additions & 0 deletions Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
Promoted readonly properties cannot be reassigned when __construct() is called directly
--FILE--
<?php

class Foo {
public function __construct(
public readonly string $value = 'default',
) {
$this->value = strtoupper($this->value);
}
}

$obj = new Foo('hello');
var_dump($obj->value);

// Direct call fails: CPP assignment cannot reinitialize an already-set property
try {
$obj->__construct('world');
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
var_dump($obj->value);

?>
--EXPECT--
string(5) "HELLO"
Error: Cannot modify readonly property Foo::$value
string(5) "HELLO"
47 changes: 47 additions & 0 deletions Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
Promoted readonly property reassignment in constructor - indirect reassignment allowed
--FILE--
<?php

// Reassignment IS allowed in methods called by the constructor
class CalledMethod {
public function __construct(
public readonly string $prop = 'default',
) {
$this->initProp();
}

private function initProp(): void {
$this->prop = 'from method';
}
}

$cm = new CalledMethod();
var_dump($cm->prop);

// But second reassignment still fails
class MultipleReassign {
public function __construct(
public readonly string $prop = 'default',
) {
$this->initProp("first from method");
try {
$this->initProp("second from method"); // Second call - should fail
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}

private function initProp(string $v): void {
$this->prop = $v;
}
}

$mr = new MultipleReassign();
var_dump($mr->prop);

?>
--EXPECT--
string(11) "from method"
Error: Cannot modify readonly property MultipleReassign::$prop
string(17) "first from method"
Loading
Loading