Skip to content

Implement readonly properties #7089

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 20 commits into from
Closed
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
3 changes: 3 additions & 0 deletions Zend/tests/grammar/semi_reserved_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Obj
function array(){ echo __METHOD__, PHP_EOL; }
function print(){ echo __METHOD__, PHP_EOL; }
function echo(){ echo __METHOD__, PHP_EOL; }
function readonly(){ echo __METHOD__, PHP_EOL; }
function require(){ echo __METHOD__, PHP_EOL; }
function require_once(){ echo __METHOD__, PHP_EOL; }
function return(){ echo __METHOD__, PHP_EOL; }
Expand Down Expand Up @@ -125,6 +126,7 @@ $obj->throw();
$obj->array();
$obj->print();
$obj->echo();
$obj->readonly();
$obj->require();
$obj->require_once();
$obj->return();
Expand Down Expand Up @@ -205,6 +207,7 @@ Obj::throw
Obj::array
Obj::print
Obj::echo
Obj::readonly
Obj::require
Obj::require_once
Obj::return
Expand Down
29 changes: 29 additions & 0 deletions Zend/tests/readonly_props/by_ref_foreach.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
By-ref foreach over readonly property
--FILE--
<?php

class Test {
public readonly int $prop;

public function init() {
$this->prop = 1;
}
}

$test = new Test;

// Okay, as foreach skips over uninitialized properties.
foreach ($test as &$prop) {}

$test->init();

try {
foreach ($test as &$prop) {}
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
Cannot acquire reference to readonly property Test::$prop
122 changes: 122 additions & 0 deletions Zend/tests/readonly_props/cache_slot.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
--TEST--
Test interaction with cache slots
--FILE--
<?php

class Test {
public readonly string $prop;
public readonly array $prop2;
public readonly object $prop3;
public function setProp(string $prop) {
$this->prop = $prop;
}
public function initAndAppendProp2() {
$this->prop2 = [];
$this->prop2[] = 1;
}
public function initProp3() {
$this->prop3 = new stdClass;
$this->prop3->foo = 1;
}
public function replaceProp3() {
$ref =& $this->prop3;
$ref = new stdClass;
}
}

$test = new Test;
$test->setProp("a");
var_dump($test->prop);
try {
$test->setProp("b");
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->prop);
echo "\n";

$test = new Test;
try {
$test->initAndAppendProp2();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
$test->initAndAppendProp2();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->prop2);
echo "\n";

$test = new Test;
$test->initProp3();
$test->replaceProp3();
var_dump($test->prop3);
$test->replaceProp3();
var_dump($test->prop3);
echo "\n";

// Test variations using closure rebinding, so we have unknown property_info in JIT.
$test = new Test;
(function() { $this->prop2 = []; })->call($test);
$appendProp2 = (function() {
$this->prop2[] = 1;
})->bindTo($test, Test::class);
try {
$appendProp2();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
$appendProp2();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->prop2);
echo "\n";

$test = new Test;
$replaceProp3 = (function() {
$ref =& $this->prop3;
$ref = new stdClass;
})->bindTo($test, Test::class);
$test->initProp3();
$replaceProp3();
var_dump($test->prop3);
$replaceProp3();
var_dump($test->prop3);

?>
--EXPECT--
string(1) "a"
Cannot modify readonly property Test::$prop
string(1) "a"

Cannot modify readonly property Test::$prop2
Cannot modify readonly property Test::$prop2
array(0) {
}

object(stdClass)#3 (1) {
["foo"]=>
int(1)
}
object(stdClass)#3 (1) {
["foo"]=>
int(1)
}

Cannot modify readonly property Test::$prop2
Cannot modify readonly property Test::$prop2
array(0) {
}

object(stdClass)#5 (1) {
["foo"]=>
int(1)
}
object(stdClass)#5 (1) {
["foo"]=>
int(1)
}
72 changes: 72 additions & 0 deletions Zend/tests/readonly_props/initialization_scope.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
--TEST--
Initialization can only happen from private scope
--FILE--
<?php

class A {
public readonly int $prop;

public function initPrivate() {
$this->prop = 3;
}
}
class B extends A {
public function initProtected() {
$this->prop = 2;
}
}

$test = new B;
try {
$test->prop = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
$test->initProtected();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

$test->initPrivate();
var_dump($test->prop);

// Rebinding bypass works.
$test = new B;
(function() {
$this->prop = 1;
})->bindTo($test, A::class)();
var_dump($test->prop);

class C extends A {
public readonly int $prop;
}

$test = new C;
$test->initPrivate();
var_dump($test->prop);

class X {
public function initFromParent() {
$this->prop = 1;
}
}
class Y extends X {
public readonly int $prop;
}

$test = new Y;
try {
$test->initFromParent();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
Cannot initialize readonly property A::$prop from global scope
Cannot initialize readonly property A::$prop from scope B
int(3)
int(1)
int(3)
Cannot initialize readonly property Y::$prop from scope X
74 changes: 74 additions & 0 deletions Zend/tests/readonly_props/magic_get_set.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
--TEST--
Interaction with magic get/set
--FILE--
<?php

class Test {
public readonly int $prop;

public function unsetProp() {
unset($this->prop);
}

public function __get($name) {
echo __METHOD__, "($name)\n";
return 1;
}

public function __set($name, $value) {
echo __METHOD__, "($name, $value)\n";
}

public function __unset($name) {
echo __METHOD__, "($name)\n";
}

public function __isset($name) {
echo __METHOD__, "($name)\n";
return true;
}
}

$test = new Test;

// The property is in uninitialized state, no magic methods should be invoked.
var_dump(isset($test->prop));
try {
var_dump($test->prop);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
$test->prop = 1;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
unset($test->prop);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

$test->unsetProp();

var_dump(isset($test->prop));
var_dump($test->prop);
$test->prop = 2;
try {
unset($test->prop);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
bool(false)
Typed property Test::$prop must not be accessed before initialization
Cannot initialize readonly property Test::$prop from global scope
Cannot unset readonly property Test::$prop from global scope
Test::__isset(prop)
bool(true)
Test::__get(prop)
int(1)
Test::__set(prop, 2)
Test::__unset(prop)
26 changes: 26 additions & 0 deletions Zend/tests/readonly_props/override_with_attributes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Can override readonly property with attributes
--FILE--
<?php

#[Attribute]
class FooAttribute {}

class A {
public readonly int $prop;

public function __construct() {
$this->prop = 42;
}
}
class B extends A {
#[FooAttribute]
public readonly int $prop;
}

var_dump((new ReflectionProperty(B::class, 'prop'))->getAttributes()[0]->newInstance());

?>
--EXPECT--
object(FooAttribute)#1 (0) {
}
41 changes: 41 additions & 0 deletions Zend/tests/readonly_props/promotion.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
Promoted readonly property
--FILE--
<?php

class Point {
public function __construct(
public readonly float $x = 0.0,
public readonly float $y = 0.0,
public readonly float $z = 0.0,
) {}
}

var_dump(new Point);
$point = new Point(1.0, 2.0, 3.0);
try {
$point->x = 4.0;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($point);

?>
--EXPECT--
object(Point)#1 (3) {
["x"]=>
float(0)
["y"]=>
float(0)
["z"]=>
float(0)
}
Cannot modify readonly property Point::$x
object(Point)#1 (3) {
["x"]=>
float(1)
["y"]=>
float(2)
["z"]=>
float(3)
}
Loading