-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for readonly properties, for which only a single initializing assignment from the declaring scope is allowed. RFC: https://wiki.php.net/rfc/readonly_properties_v2 Closes GH-7089.
- Loading branch information
Showing
42 changed files
with
1,118 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | ||
} |
Oops, something went wrong.