Skip to content

Commit ef95e47

Browse files
committed
Implement readonly properties
1 parent c2b2930 commit ef95e47

32 files changed

+742
-29
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
By-ref foreach over readonly property
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public readonly int $prop;
8+
9+
public function init() {
10+
$this->prop = 1;
11+
}
12+
}
13+
14+
$test = new Test;
15+
16+
// Okay, as foreach skips over uninitialized properties.
17+
foreach ($test as &$prop) {}
18+
19+
$test->init();
20+
21+
try {
22+
foreach ($test as &$prop) {}
23+
} catch (Error $e) {
24+
echo $e->getMessage(), "\n";
25+
}
26+
27+
?>
28+
--EXPECT--
29+
Cannot acquire reference to readonly property Test::$prop
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
--TEST--
2+
Initialization can only happen from private scope
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public readonly int $prop;
8+
9+
public function initPrivate() {
10+
$this->prop = 3;
11+
}
12+
}
13+
class B extends A {
14+
public function initProtected() {
15+
$this->prop = 2;
16+
}
17+
}
18+
19+
$test = new B;
20+
try {
21+
$test->prop = 1;
22+
} catch (Error $e) {
23+
echo $e->getMessage(), "\n";
24+
}
25+
try {
26+
$test->initProtected();
27+
} catch (Error $e) {
28+
echo $e->getMessage(), "\n";
29+
}
30+
31+
$test->initPrivate();
32+
var_dump($test->prop);
33+
34+
// Rebinding bypass works.
35+
$test = new B;
36+
(function() {
37+
$this->prop = 1;
38+
})->bindTo($test, A::class)();
39+
var_dump($test->prop);
40+
41+
class C extends A {
42+
public readonly int $prop;
43+
}
44+
45+
$test = new C;
46+
$test->initPrivate();
47+
var_dump($test->prop);
48+
49+
class X {
50+
public function initFromParent() {
51+
$this->prop = 1;
52+
}
53+
}
54+
class Y extends X {
55+
public readonly int $prop;
56+
}
57+
58+
$test = new Y;
59+
try {
60+
$test->initFromParent();
61+
} catch (Error $e) {
62+
echo $e->getMessage(), "\n";
63+
}
64+
65+
?>
66+
--EXPECT--
67+
Cannot initialize readonly property A::$prop from global scope
68+
Cannot initialize readonly property A::$prop from scope B
69+
int(3)
70+
int(1)
71+
int(3)
72+
Cannot initialize readonly property Y::$prop from scope X
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Promoted readonly property
3+
--FILE--
4+
<?php
5+
6+
class Point {
7+
public function __construct(
8+
public readonly float $x = 0.0,
9+
public readonly float $y = 0.0,
10+
public readonly float $z = 0.0,
11+
) {}
12+
}
13+
14+
var_dump(new Point);
15+
$point = new Point(1.0, 2.0, 3.0);
16+
try {
17+
$point->x = 4.0;
18+
} catch (Error $e) {
19+
echo $e->getMessage(), "\n";
20+
}
21+
var_dump($point);
22+
23+
?>
24+
--EXPECT--
25+
object(Point)#1 (3) {
26+
["x"]=>
27+
float(0)
28+
["y"]=>
29+
float(0)
30+
["z"]=>
31+
float(0)
32+
}
33+
Cannot modify readonly property Point::$x
34+
object(Point)#1 (3) {
35+
["x"]=>
36+
float(1)
37+
["y"]=>
38+
float(2)
39+
["z"]=>
40+
float(3)
41+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Class constants cannot be readonly
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
readonly const X = 1;
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot use 'readonly' as constant modifier in %s on line %d
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--TEST--
2+
Not-modifying a readonly property holding an object
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public readonly object $prop;
8+
9+
public function __construct(object $prop) {
10+
$this->prop = $prop;
11+
}
12+
}
13+
14+
$test = new Test(new stdClass);
15+
$test->prop->foo = 1;
16+
$test->prop->foo += 1;
17+
$test->prop->foo++;
18+
try {
19+
$test->prop += 1;
20+
} catch (Error $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
try {
24+
$test->prop++;
25+
} catch (Error $e) {
26+
echo $e->getMessage(), "\n";
27+
}
28+
try {
29+
--$test->prop;
30+
} catch (Error $e) {
31+
echo $e->getMessage(), "\n";
32+
}
33+
var_dump($test->prop);
34+
35+
// Unfortunately this is allowed, but does not modify $test->prop.
36+
$ref =& $test->prop;
37+
$ref = new stdClass;
38+
var_dump($test->prop);
39+
40+
$test = new Test(new ArrayObject());
41+
$test->prop[] = [];
42+
$test->prop[0][] = 1;
43+
var_dump($test->prop);
44+
45+
?>
46+
--EXPECT--
47+
Unsupported operand types: stdClass + int
48+
Cannot modify readonly property Test::$prop
49+
Cannot modify readonly property Test::$prop
50+
object(stdClass)#2 (1) {
51+
["foo"]=>
52+
int(3)
53+
}
54+
object(stdClass)#5 (0) {
55+
}
56+
object(ArrayObject)#4 (1) {
57+
["storage":"ArrayObject":private]=>
58+
array(1) {
59+
[0]=>
60+
array(1) {
61+
[0]=>
62+
int(1)
63+
}
64+
}
65+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Method cannot be readonly
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
readonly function test() {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot use 'readonly' as method modifier in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Method cannot be readonly in trait alias
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
use T { foo as readonly; }
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot use 'readonly' as method modifier in %s on line %d
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--TEST--
2+
Modifying a readonly property
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
readonly public int $prop;
8+
readonly public array $prop2;
9+
10+
public function __construct() {
11+
// Initializing assignments.
12+
$this->prop = 1;
13+
$this->prop2 = [];
14+
}
15+
}
16+
17+
function byRef(&$ref) {}
18+
19+
$test = new Test;
20+
var_dump($test->prop); // Read.
21+
try {
22+
$test->prop = 2;
23+
} catch (Error $e) {
24+
echo $e->getMessage(), "\n";
25+
}
26+
try {
27+
$test->prop += 1;
28+
} catch (Error $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
try {
32+
$test->prop++;
33+
} catch (Error $e) {
34+
echo $e->getMessage(), "\n";
35+
}
36+
try {
37+
++$test->prop;
38+
} catch (Error $e) {
39+
echo $e->getMessage(), "\n";
40+
}
41+
try {
42+
$ref =& $test->prop;
43+
} catch (Error $e) {
44+
echo $e->getMessage(), "\n";
45+
}
46+
try {
47+
$test->prop =& $ref;
48+
} catch (Error $e) {
49+
echo $e->getMessage(), "\n";
50+
}
51+
try {
52+
byRef($test->prop);
53+
} catch (Error $e) {
54+
echo $e->getMessage(), "\n";
55+
}
56+
57+
var_dump($test->prop2); // Read.
58+
try {
59+
$test->prop2[] = 1;
60+
} catch (Error $e) {
61+
echo $e->getMessage(), "\n";
62+
}
63+
try {
64+
$test->prop2[0][] = 1;
65+
} catch (Error $e) {
66+
echo $e->getMessage(), "\n";
67+
}
68+
69+
?>
70+
--EXPECT--
71+
int(1)
72+
Cannot modify readonly property Test::$prop
73+
Cannot modify readonly property Test::$prop
74+
Cannot modify readonly property Test::$prop
75+
Cannot modify readonly property Test::$prop
76+
Cannot modify readonly property Test::$prop
77+
Cannot modify readonly property Test::$prop
78+
Cannot modify readonly property Test::$prop
79+
array(0) {
80+
}
81+
Cannot modify readonly property Test::$prop2
82+
Cannot modify readonly property Test::$prop2
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Can replace readonly with readwrite
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public readonly int $prop;
8+
}
9+
class B extends A {
10+
public int $prop;
11+
}
12+
13+
?>
14+
===DONE===
15+
--EXPECT--
16+
===DONE===
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Readonly match of imported trait properties (valid)
3+
--FILE--
4+
<?php
5+
6+
trait T1 {
7+
public readonly int $prop;
8+
}
9+
trait T2 {
10+
public readonly int $prop;
11+
}
12+
class C {
13+
use T1, T2;
14+
}
15+
16+
?>
17+
===DONE===
18+
--EXPECT--
19+
===DONE===
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Readonly mismatch of imported trait properties
3+
--FILE--
4+
<?php
5+
6+
trait T1 {
7+
public int $prop;
8+
}
9+
trait T2 {
10+
public readonly int $prop;
11+
}
12+
class C {
13+
use T1, T2;
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d

0 commit comments

Comments
 (0)