parent
a3ea91918d
commit
e5f61eabfe
39 changed files with 689 additions and 3 deletions
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class CloneDirectMethodCall |
||||||
|
{ |
||||||
|
public function __clone(): void |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function invoke(): void |
||||||
|
{ |
||||||
|
$this->__clone(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class CloneDirectStaticCall |
||||||
|
{ |
||||||
|
public function __clone(): void |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public function invoke(): void |
||||||
|
{ |
||||||
|
self::__clone(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
<?php |
||||||
|
class ConstructorDirectMethodCall |
||||||
|
{ |
||||||
|
public function __construct() {} |
||||||
|
} |
||||||
|
|
||||||
|
function invoke_constructor(ConstructorDirectMethodCall $object): void |
||||||
|
{ |
||||||
|
$object->__construct(); |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
<?php |
||||||
|
class ConstructorDirectStaticCall |
||||||
|
{ |
||||||
|
public function __construct() {} |
||||||
|
|
||||||
|
public function invoke(): void |
||||||
|
{ |
||||||
|
self::__construct(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use native_types; |
||||||
|
|
||||||
|
class ReadonlyPropertyNoNativeRef |
||||||
|
{ |
||||||
|
public readonly int $integer; |
||||||
|
public readonly float $floating; |
||||||
|
|
||||||
|
public function __construct(int $integer, float $floating) |
||||||
|
{ |
||||||
|
$this->integer = $integer; |
||||||
|
$this->floating = $floating; |
||||||
|
$this->integer += 1; |
||||||
|
$this->floating += 1.5; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyReferenceAssignment |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct(int &$source) |
||||||
|
{ |
||||||
|
$this->value =& $source; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
function mutate_readonly_argument(int &$value): void |
||||||
|
{ |
||||||
|
$value++; |
||||||
|
} |
||||||
|
|
||||||
|
class ReadonlyReferenceCallArgument |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->value = 1; |
||||||
|
mutate_readonly_argument($this->value); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyReferenceFetch |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->value = 1; |
||||||
|
$reference =& $this->value; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyArrayDimWrite |
||||||
|
{ |
||||||
|
public readonly array $value; |
||||||
|
public function __construct() { $this->value = []; } |
||||||
|
public function change(): void { $this->value[] = 1; } |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class ReadonlyCloneParent |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->value = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ReadonlyCloneChild extends ReadonlyCloneParent |
||||||
|
{ |
||||||
|
public function __clone(): void |
||||||
|
{ |
||||||
|
$this->value = 2; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyParent |
||||||
|
{ |
||||||
|
protected readonly int $value; |
||||||
|
} |
||||||
|
|
||||||
|
class ReadonlyChild extends ReadonlyParent |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->value = 1; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class ReadonlyCloneClosure |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->value = 1; |
||||||
|
} |
||||||
|
|
||||||
|
public function __clone(): void |
||||||
|
{ |
||||||
|
$write = function (): void { |
||||||
|
$this->value = 2; |
||||||
|
}; |
||||||
|
$write(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyCoalesceWrite |
||||||
|
{ |
||||||
|
public readonly ?int $value; |
||||||
|
public function __construct() { $this->value = null; } |
||||||
|
public function change(): void { $this->value ??= 1; } |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyCompoundWrite |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
public function __construct() { $this->value = 1; } |
||||||
|
public function change(): void { $this->value += 1; } |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyConstructorClosure |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$writer = function (): void { |
||||||
|
$this->value = 1; |
||||||
|
}; |
||||||
|
$writer(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyForeachWrite |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
public function __construct() { $this->value = 1; } |
||||||
|
public function change(): void |
||||||
|
{ |
||||||
|
foreach ([2] as $this->value) {} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyIncrementWrite |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
public function __construct() { $this->value = 1; } |
||||||
|
public function change(): void { ++$this->value; } |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyListWrite |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
public function __construct() { $this->value = 1; } |
||||||
|
public function change(): void { [$this->value] = [2]; } |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class ReadonlyCloneOtherInstance |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->value = 1; |
||||||
|
} |
||||||
|
|
||||||
|
public function __clone(): void |
||||||
|
{ |
||||||
|
$other = new self(); |
||||||
|
$other->value = 2; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyOtherInstance |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct(ReadonlyOtherInstance $other) |
||||||
|
{ |
||||||
|
$other->value = 1; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
<?php |
||||||
|
class ReadonlyWriteOutsideConstructor |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->value = 1; |
||||||
|
} |
||||||
|
|
||||||
|
public function change(): void |
||||||
|
{ |
||||||
|
$this->value = 2; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class ReadonlyPropertyTest extends \BaseTest |
||||||
|
{ |
||||||
|
public function testConstructorCannotBeCalledAsOrdinaryMethod(): void |
||||||
|
{ |
||||||
|
$this->exec('Constructor __construct() can only be invoked by new', 'constructor-direct-method-call.php'); |
||||||
|
$this->exec('Constructor __construct() can only be invoked by new', 'constructor-direct-static-call.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testCloneCannotBeCalledAsOrdinaryMethod(): void |
||||||
|
{ |
||||||
|
$this->exec('Clone method __clone() can only be invoked by clone', 'clone-direct-method-call.php'); |
||||||
|
$this->exec('Clone method __clone() can only be invoked by clone', 'clone-direct-static-call.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testWriteOutsideConstructorIsRejected(): void |
||||||
|
{ |
||||||
|
$this->exec('Readonly property `ReadonlyWriteOutsideConstructor::$value` can only be modified in its declaring `__construct` or `__clone` method', 'readonly-write-outside-constructor.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testChildConstructorCannotWriteParentReadonlyProperty(): void |
||||||
|
{ |
||||||
|
$this->exec('Readonly property `ReadonlyParent::$value` can only be modified in its declaring `__construct` or `__clone` method', 'readonly-write-child-constructor.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testConstructorCannotWriteReadonlyPropertyOnAnotherObject(): void |
||||||
|
{ |
||||||
|
$this->exec('Readonly property `ReadonlyOtherInstance::$value` can only be modified on `$this`', 'readonly-write-other-instance-constructor.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testClosureInsideConstructorCannotWriteReadonlyProperty(): void |
||||||
|
{ |
||||||
|
$this->exec('Readonly property `ReadonlyConstructorClosure::$value` can only be modified directly in `__construct` or `__clone`', 'readonly-write-constructor-closure.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testReadonlyCloneWriteRetainsLexicalRestrictions(): void |
||||||
|
{ |
||||||
|
$this->exec( |
||||||
|
'Readonly property `ReadonlyCloneParent::$value` can only be modified in its declaring `__construct` or `__clone` method', |
||||||
|
'readonly-write-child-clone.php' |
||||||
|
); |
||||||
|
$this->exec( |
||||||
|
'Readonly property `ReadonlyCloneClosure::$value` can only be modified directly in `__construct` or `__clone`', |
||||||
|
'readonly-write-clone-closure.php' |
||||||
|
); |
||||||
|
$this->exec( |
||||||
|
'Readonly property `ReadonlyCloneOtherInstance::$value` can only be modified on `$this`', |
||||||
|
'readonly-write-other-instance-clone.php' |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public function testReadonlyPropertyCannotBeAssignedByReference(): void |
||||||
|
{ |
||||||
|
$this->exec('Cannot assign readonly property `ReadonlyReferenceAssignment::$value` by reference', 'readonly-reference-assignment.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testReadonlyPropertyCannotBeTakenByReference(): void |
||||||
|
{ |
||||||
|
$this->exec('Cannot take reference to readonly property `ReadonlyReferenceFetch::$value`', 'readonly-reference-fetch.php'); |
||||||
|
$this->exec('Cannot take reference to readonly property `ReadonlyReferenceCallArgument::$value`', 'readonly-reference-call-argument.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testAllReadonlyWriteFormsOutsideConstructorAreRejected(): void |
||||||
|
{ |
||||||
|
foreach ([ |
||||||
|
'readonly-write-compound.php', |
||||||
|
'readonly-write-increment.php', |
||||||
|
'readonly-write-array-dim.php', |
||||||
|
'readonly-write-coalesce.php', |
||||||
|
'readonly-write-list.php', |
||||||
|
'readonly-write-foreach.php', |
||||||
|
] as $file) { |
||||||
|
$this->exec('can only be modified in its declaring `__construct` or `__clone` method', $file); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,68 @@ |
|||||||
|
--TEST-- |
||||||
|
TypePHP readonly properties may be updated while cloning |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
use native_types; |
||||||
|
|
||||||
|
class ReadonlyCloneBase |
||||||
|
{ |
||||||
|
public readonly int $base; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->base = 1; |
||||||
|
} |
||||||
|
|
||||||
|
public function __clone(): void |
||||||
|
{ |
||||||
|
$this->base++; |
||||||
|
$this->base += 3; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ReadonlyCloneValue extends ReadonlyCloneBase |
||||||
|
{ |
||||||
|
public readonly string $name; |
||||||
|
public readonly array $items; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
parent::__construct(); |
||||||
|
$this->name = 'original'; |
||||||
|
$this->items = [1]; |
||||||
|
} |
||||||
|
|
||||||
|
public function __clone(): void |
||||||
|
{ |
||||||
|
parent::__clone(); |
||||||
|
$this->name = 'clone'; |
||||||
|
$this->name .= 'd'; |
||||||
|
$this->items[] = 2; |
||||||
|
$this->items[0] = 10; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$original = new ReadonlyCloneValue(); |
||||||
|
$copy = clone $original; |
||||||
|
var_dump($original->base, $original->name, $original->items); |
||||||
|
var_dump($copy->base, $copy->name, $copy->items); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(1) |
||||||
|
string(8) "original" |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
int(1) |
||||||
|
} |
||||||
|
int(5) |
||||||
|
string(6) "cloned" |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
int(10) |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
--TEST-- |
||||||
|
TypePHP readonly properties are mutable only during their declaring constructor |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
use native_types; |
||||||
|
|
||||||
|
class ReadonlyConstructorOnly |
||||||
|
{ |
||||||
|
public readonly int $number; |
||||||
|
public readonly string $text; |
||||||
|
public readonly array $items; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->number = 1; |
||||||
|
$this->number = 2; |
||||||
|
$this->number += 3; |
||||||
|
++$this->number; |
||||||
|
|
||||||
|
$this->text = 'a'; |
||||||
|
$this->text .= 'b'; |
||||||
|
|
||||||
|
$this->items = []; |
||||||
|
$this->items[] = 10; |
||||||
|
$this->items[0] = 20; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$value = new ReadonlyConstructorOnly(); |
||||||
|
var_dump($value->number, $value->text, $value->items); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(6) |
||||||
|
string(2) "ab" |
||||||
|
array(1) { |
||||||
|
[0]=> |
||||||
|
int(20) |
||||||
|
} |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
--TEST-- |
||||||
|
Readonly clone method cannot be called dynamically |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class ReadonlyDynamicCloneCall |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->value = 1; |
||||||
|
} |
||||||
|
|
||||||
|
public function __clone(): void |
||||||
|
{ |
||||||
|
$this->value = 2; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$value = new ReadonlyDynamicCloneCall(); |
||||||
|
$method = '__clone'; |
||||||
|
try { |
||||||
|
$value->$method(); |
||||||
|
} catch (Error $error) { |
||||||
|
echo $error->getMessage(), "\n"; |
||||||
|
} |
||||||
|
var_dump($value->value); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
Clone method ReadonlyDynamicCloneCall::__clone() can only be invoked by clone |
||||||
|
int(1) |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
--TEST-- |
||||||
|
Readonly constructor cannot be called dynamically after construction |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class ReadonlyDynamicConstructorCall |
||||||
|
{ |
||||||
|
public readonly int $value; |
||||||
|
|
||||||
|
public function __construct(int $value) |
||||||
|
{ |
||||||
|
$this->value = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$value = new ReadonlyDynamicConstructorCall(1); |
||||||
|
$method = '__construct'; |
||||||
|
try { |
||||||
|
$value->$method(2); |
||||||
|
} catch (Error $error) { |
||||||
|
echo $error->getMessage(), "\n"; |
||||||
|
} |
||||||
|
var_dump($value->value); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
Constructor ReadonlyDynamicConstructorCall::__construct() can only be invoked by new |
||||||
|
int(1) |
||||||
Loading…
Reference in new issue