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