Implement typed properties

RFC: https://wiki.php.net/rfc/typed_properties_v2

This is a squash of PR #3734, which is a squash of PR #3313.

Co-authored-by: Bob Weinand <bobwei9@hotmail.com>
Co-authored-by: Joe Watkins <krakjoe@php.net>
Co-authored-by: Dmitry Stogov <dmitry@zend.com>
This commit is contained in:
Nikita Popov 2019-01-07 12:28:51 +01:00
parent fe8fdfa3bd
commit e219ec144e
210 changed files with 22678 additions and 5594 deletions

View File

@ -78,8 +78,20 @@ PHP 7.4 UPGRADE NOTES
2. New Features
========================================
- Core:
. Added support for typed properties. For example:
class User {
public int $id;
public string $name;
}
This will enforce that $user->id can only be assigned integer and
$user->name can only be assigned strings. For more information see the
RFC: https://wiki.php.net/rfc/typed_properties_v2
- PDO_OCI:
. PDOStatement::getColumnMeta is now available
. PDOStatement::getColumnMeta() is now available
- PDO_SQLite:
. PDOStatement::getAttribute(PDO::SQLITE_ATTR_READONLY_STATEMENT) allows to

View File

@ -13,6 +13,8 @@ PHP 7.4 INTERNALS UPGRADE NOTES
j. Removed add_get_assoc_*() and add_get_index_*()
k. Class declaration opcodes
l. HASH_FLAG_INITIALIZED
m. write_property return value
n. Assignments to references
2. Build system changes
a. Abstract
@ -158,6 +160,17 @@ PHP 7.4 INTERNALS UPGRADE NOTES
Special HT_IS_INITIALIZED() and HT_INVALIDATE() macro were introduced
to hide implementation details.
m. The write_property() object handler now returns the assigned value (after
possible type coercions) rather than void. For extensions, it should
usually be sufficient to return whatever was passed as the argument.
n. Assignments to references now need to ensure that they respect property
types that affect the reference. This means that references should no
longer be directly assigned to, and instead a set of specialized macros
of the form ZEND_TRY_ASSIGN* needs to be used. You can find detailed
porting instructions as well as a compatibility shim in the wiki:
https://wiki.php.net/rfc/typed_properties_v2#assignments_to_references
========================
2. Build system changes
========================

View File

@ -0,0 +1,44 @@
--TEST--
Test typed properties basic operation
--FILE--
<?php
var_dump(new class(1, 2.2, true, ["four"], new stdClass) {
public int $int;
public float $float;
public bool $bool;
public array $array;
public stdClass $std;
public iterable $it;
public function __construct(int $int, float $float, bool $bool, array $array, stdClass $std) {
$this->int = $int;
$this->float = $float;
$this->bool = $bool;
$this->array = $array;
$this->std = $std;
$this->it = $array;
}
});
?>
--EXPECTF--
object(class@anonymous)#%d (6) {
["int"]=>
int(1)
["float"]=>
float(2.2)
["bool"]=>
bool(true)
["array"]=>
array(1) {
[0]=>
string(4) "four"
}
["std"]=>
object(stdClass)#%d (0) {
}
["it"]=>
array(1) {
[0]=>
string(4) "four"
}
}

View File

@ -0,0 +1,15 @@
--TEST--
Test typed properties error condition (read uninitialized)
--FILE--
<?php
$thing = new class() {
public int $int;
};
var_dump($thing->int);
?>
--EXPECTF--
Fatal error: Uncaught Error: Typed property class@anonymous::$int must not be accessed before initialization in %s:6
Stack trace:
#0 {main}
thrown in %s on line 6

View File

@ -0,0 +1,15 @@
--TEST--
Test typed properties error condition (fetch uninitialized by reference)
--FILE--
<?php
$thing = new class() {
public int $int;
};
$var = &$thing->int;
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot access uninitialized non-nullable property class@anonymous::$int by reference in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View File

@ -0,0 +1,18 @@
--TEST--
Test typed properties error condition (type mismatch)
--FILE--
<?php
new class("PHP 7 is better than you, and it knows it ...") {
public int $int;
public function __construct(string $string) {
$this->int = $string;
}
};
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Typed property class@anonymous::$int must be int, string used in %s:6
Stack trace:
#0 %s(2): class@anonymous->__construct('PHP 7 is better...')
#1 {main}
thrown in %s on line 6

View File

@ -0,0 +1,20 @@
--TEST--
Test typed properties error condition (type mismatch object)
--FILE--
<?php
class Dummy {}
new class(new Dummy) {
public stdClass $std;
public function __construct(Dummy $dummy) {
$this->std = $dummy;
}
};
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Typed property class@anonymous::$std must be an instance of stdClass, Dummy used in %s:8
Stack trace:
#0 %s(4): class@anonymous->__construct(Object(Dummy))
#1 {main}
thrown in %s on line 8

View File

@ -0,0 +1,14 @@
--TEST--
Test typed properties inheritance (scalar)
--FILE--
<?php
class Foo {
public int $qux;
}
class Bar extends Foo {
public string $qux;
}
?>
--EXPECTF--
Fatal error: Type of Bar::$qux must be int (as in class Foo) in %s on line 8

View File

@ -0,0 +1,17 @@
--TEST--
Test typed properties inheritance
--FILE--
<?php
class Whatever {}
class Thing extends Whatever {}
class Foo {
public Whatever $qux;
}
class Bar extends Foo {
public Thing $qux;
}
?>
--EXPECTF--
Fatal error: Type of Bar::$qux must be Whatever (as in class Foo) in %s on line 11

View File

@ -0,0 +1,14 @@
--TEST--
Test typed properties inheritance (missing info)
--FILE--
<?php
class Foo {
public int $qux;
}
class Bar extends Foo {
public $qux;
}
?>
--EXPECTF--
Fatal error: Type of Bar::$qux must be int (as in class Foo) in %s on line 8

View File

@ -0,0 +1,23 @@
--TEST--
Test typed properties unset leaves properties in an uninitialized state
--FILE--
<?php
class Foo {
public int $bar;
public function __get($name) {
var_dump($name);
/* return value has to be compatible with int */
return 0;
}
}
$foo = new Foo();
unset($foo->bar);
var_dump($foo->bar);
?>
--EXPECT--
string(3) "bar"
int(0)

View File

@ -0,0 +1,17 @@
--TEST--
Test typed properties allow fetch reference
--FILE--
<?php
class Foo {
public int $bar = 1;
}
$cb = function(int &$bar) {
var_dump($bar);
};
$foo = new Foo();
$cb($foo->bar);
?>
--EXPECT--
int(1)

View File

@ -0,0 +1,18 @@
--TEST--
Test typed properties allow fetch reference for init array
--FILE--
<?php
class Foo {
public int $bar = 1;
}
$foo = new Foo();
$array = [&$foo->bar];
var_dump($array);
?>
--EXPECT--
array(1) {
[0]=>
&int(1)
}

View File

@ -0,0 +1,19 @@
--TEST--
Test typed properties allow fetch reference for foreach
--FILE--
<?php
class Foo {
public int $bar = 1;
}
$foo = new Foo();
foreach ($foo as &$prop) {
$prop++;
}
var_dump($foo);
?>
--EXPECT--
object(Foo)#1 (1) {
["bar"]=>
&int(2)
}

View File

@ -0,0 +1,10 @@
--TEST--
Test typed properties disallow incorrect type initial value (scalar)
--FILE--
<?php
class Foo {
public int $bar = "string";
}
?>
--EXPECTF--
Fatal error: Default value for property of type int can only be int in %s on line 3

View File

@ -0,0 +1,10 @@
--TEST--
Test typed properties disallow incorrect type initial value (array)
--FILE--
<?php
class Foo {
public array $bar = 32;
}
?>
--EXPECTF--
Fatal error: Default value for property of type array can only be an array in %s on line 3

View File

@ -0,0 +1,10 @@
--TEST--
Test typed properties disallow incorrect type initial value (object)
--FILE--
<?php
class Foo {
public stdClass $bar = null;
}
?>
--EXPECTF--
Fatal error: Default value for property of type stdClass may not be null. Use the nullable type ?stdClass to allow null default value in %s on line %d

View File

@ -0,0 +1,16 @@
--TEST--
Test typed properties initial values
--FILE--
<?php
class Foo {
public int $int = 1;
public float $flt = 2.2;
public float $flt2 = 2;
public array $arr = [];
public bool $bool = false;
public iterable $iter = [];
}
echo "ok\n";
?>
--EXPECT--
ok

View File

@ -0,0 +1,12 @@
--TEST--
Test typed properties disallow void
--FILE--
<?php
class Foo {
public void $int;
}
$foo = new Foo();
?>
--EXPECTF--
Fatal error: Property Foo::$int cannot have type void in %s on line 3

View File

@ -0,0 +1,17 @@
--TEST--
Test typed properties type applies to all props in group
--FILE--
<?php
class Foo {
public int $bar,
$qux;
}
$reflector = new ReflectionClass(Foo::class);
$prop = $reflector->getProperty("qux");
var_dump((string) $prop->getType());
?>
--EXPECT--
string(3) "int"

View File

@ -0,0 +1,22 @@
--TEST--
Test typed properties int must not be allowed to overflow
--FILE--
<?php
class Foo {
public int $bar = PHP_INT_MAX;
public function inc() {
return ++$this->bar;
}
}
$foo = new Foo();
try {
$foo->inc();
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Cannot increment property Foo::$bar of type int past its maximal value

View File

@ -0,0 +1,26 @@
--TEST--
Test typed properties binary assign op helper test
--FILE--
<?php
declare(strict_types=1);
class Foo {
public int $bar = 0;
public function __construct() {
$this->bar += 2;
try {
$this->bar += 1.5;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
}
}
$foo = new Foo();
var_dump($foo->bar);
?>
--EXPECT--
Typed property Foo::$bar must be int, float used
int(2)

View File

@ -0,0 +1,15 @@
--TEST--
Test typed properties delay type check on constant
--FILE--
<?php
class Foo {
public int $bar = BAR::BAZ;
}
$foo = new Foo();
?>
--EXPECTF--
Fatal error: Uncaught Error: Class 'BAR' not found in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View File

@ -0,0 +1,15 @@
--TEST--
Test typed properties delay type check on ast
--FILE--
<?php
class Foo {
public int $bar = BAR::BAZ * 2;
}
$foo = new Foo();
?>
--EXPECTF--
Fatal error: Uncaught Error: Class 'BAR' not found in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View File

@ -0,0 +1,52 @@
--TEST--
Test typed static property
--FILE--
<?php
function &ref() {
static $a = 5;
return $a;
}
class Foo {
public static int $i;
public static string $s = "x";
}
var_dump(Foo::$i = 1);
var_dump(Foo::$i);
var_dump(Foo::$i = "1");
var_dump(Foo::$i);
var_dump(Foo::$s);
var_dump(Foo::$s = Foo::$i++);
var_dump(Foo::$s, Foo::$i);
$a = 3;
var_dump(Foo::$s = $a);
var_dump(Foo::$s);
var_dump(Foo::$i = "4");
var_dump(Foo::$i);
var_dump(Foo::$i = ref());
var_dump(Foo::$i);
var_dump(Foo::$s = ref());
var_dump(Foo::$s);
var_dump(ref());
?>
--EXPECT--
int(1)
int(1)
int(1)
int(1)
string(1) "x"
string(1) "1"
string(1) "1"
int(2)
string(1) "3"
string(1) "3"
int(4)
int(4)
int(5)
int(5)
string(1) "5"
string(1) "5"
int(5)

View File

@ -0,0 +1,16 @@
--TEST--
Test typed properties ignore private props during inheritance
--FILE--
<?php
class Foo {
private int $thing;
}
class Bar extends Foo {
public string $thing; // No conflict
}
echo "ok";
?>
--EXPECT--
ok

View File

@ -0,0 +1,11 @@
--TEST--
Test typed properties type must preceed first declaration in group
--FILE--
<?php
class Foo {
public $bar,
int $qux;
}
?>
--EXPECTF--
Parse error: syntax error, unexpected 'int' (T_STRING), expecting variable (T_VARIABLE) in %s on line 4

View File

@ -0,0 +1,23 @@
--TEST--
Test typed properties inherit traits with typed properties
--FILE--
<?php
trait Foo{
private int $baz;
}
class Baz{
use Foo;
function get(){
return $this->baz;
}
}
var_dump((new Baz)->get());
--EXPECTF--
Fatal error: Uncaught Error: Typed property Baz::$baz must not be accessed before initialization in %s:10
Stack trace:
#0 %s(14): Baz->get()
#1 {main}
thrown in %s on line 10

View File

@ -0,0 +1,16 @@
--TEST--
Test typed properties float widen at runtime
--FILE--
<?php
class Foo {
public float $bar = 1.1;
}
$foo = new Foo;
$foo->bar = 10;
var_dump($foo->bar);
?>
--EXPECT--
float(10)

View File

@ -0,0 +1,15 @@
--TEST--
Test typed properties respect strict types (off)
--FILE--
<?php
class Foo {
public int $bar;
}
$foo = new Foo;
$foo->bar = "1";
var_dump($foo->bar);
?>
--EXPECT--
int(1)

View File

@ -0,0 +1,18 @@
--TEST--
Test typed properties respect strict types (on)
--FILE--
<?php
declare(strict_types=1);
class Foo {
public int $bar;
}
$foo = new Foo;
$foo->bar = "1";
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Typed property Foo::$bar must be int, string used in %s:9
Stack trace:
#0 {main}
thrown in %s on line 9

View File

@ -0,0 +1,25 @@
--TEST--
Test typed properties unset __get magical magic
--FILE--
<?php
class Foo {
public int $bar;
public function __get($name) {
return "violate";
}
}
$foo = new Foo;
$foo->bar = "1"; # ok
unset($foo->bar); # ok
var_dump($foo->bar); # not okay, __get is nasty
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Typed property Foo::$bar must be int, string used in %s:16
Stack trace:
#0 {main}
thrown in %s on line 16

View File

@ -0,0 +1,22 @@
--TEST--
Test typed properties coerce int to float even in strict mode
--FILE--
<?php
declare(strict_types=1);
class Bar
{
public float $bar;
public function setBar($value) {
$this->bar = $value;
}
}
$bar = new Bar();
$bar->setBar(100);
var_dump($bar->bar);
--EXPECT--
float(100)

View File

@ -0,0 +1,15 @@
--TEST--
Test typed properties return by ref is allowed
--FILE--
<?php
$foo = new class {
public int $bar = 15;
public function &method() {
return $this->bar;
}
};
var_dump($foo->method());
--EXPECT--
int(15)

View File

@ -0,0 +1,38 @@
--TEST--
Test typed properties yield reference guard
--FILE--
<?php
$foo = new class {
public int $foo = 1;
public int $bar = 3;
public int $baz = 5;
public int $qux = PHP_INT_MAX;
public function &fetch() {
yield $this->foo;
yield $this->bar;
yield $this->baz;
yield $this->qux;
}
};
try {
foreach ($foo->fetch() as &$prop) {
$prop += 1;
}
} catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($foo);
?>
--EXPECTF--
Cannot assign float to reference held by property class@anonymous::$qux of type int
object(class@anonymous)#1 (4) {
["foo"]=>
int(2)
["bar"]=>
int(4)
["baz"]=>
int(6)
["qux"]=>
&int(%d)
}

View File

@ -0,0 +1,51 @@
--TEST--
Test typed properties passed to typed function
--FILE--
<?php
$foo = new class {
public ?int $bar = 42;
public int $baz;
public function &getIterator() {
foreach (['1', &$this->bar] as &$item) {
yield $item;
}
}
};
function foo(?int &$a) {
var_dump($a);
$a = null;
}
foo($foo->bar);
try {
$foo->baz = &$foo->bar;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
$foo->bar = 10;
foreach ($foo->getIterator() as &$item) {
$foo->baz = &$item;
var_dump($foo->baz);
}
try {
foo($foo->bar);
} catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($foo);
?>
--EXPECT--
int(42)
Typed property class@anonymous::$baz must be int, null used
int(1)
int(10)
int(10)
Cannot assign null to reference held by property class@anonymous::$baz of type int
object(class@anonymous)#1 (2) {
["bar"]=>
&int(10)
["baz"]=>
&int(10)
}

View File

@ -0,0 +1,13 @@
--TEST--
Test typed properties inheritance must not change type
--FILE--
<?php
class Foo{
public $bar = 42;
}
class Baz extends Foo{
public int $bar = 33;
}
--EXPECTF--
Fatal error: Type of Baz::$bar must not be defined (as in class Foo) in %s on line 8

View File

@ -0,0 +1,15 @@
--TEST--
Test unitialized typed properties normal foreach must not be yielded
--FILE--
<?php
$foo = new class {
public int $bar = 10, $qux;
};
foreach ($foo as $key => $bar) {
var_dump($key, $bar);
}
?>
--EXPECT--
string(3) "bar"
int(10)

View File

@ -0,0 +1,16 @@
--TEST--
Test typed properties var_dump uninitialized
--FILE--
<?php
$foo = new class {
public int $bar = 10, $qux;
};
var_dump($foo);
--EXPECTF--
object(class@anonymous)#%d (1) {
["bar"]=>
int(10)
["qux"]=>
uninitialized(int)
}

View File

@ -0,0 +1,61 @@
--TEST--
Test typed properties overflowing
--FILE--
<?php
$foo = new class {
public int $bar = PHP_INT_MAX;
};
try {
$foo->bar++;
} catch(TypeError $t) {
var_dump($t->getMessage());
}
var_dump($foo);
try {
$foo->bar += 1;
} catch(TypeError $t) {
var_dump($t->getMessage());
}
var_dump($foo);
try {
++$foo->bar;
} catch(TypeError $t) {
var_dump($t->getMessage());
}
var_dump($foo);
try {
$foo->bar = $foo->bar + 1;
} catch(TypeError $t) {
var_dump($t->getMessage());
}
var_dump($foo);
--EXPECTF--
string(82) "Cannot increment property class@anonymous::$bar of type int past its maximal value"
object(class@anonymous)#1 (1) {
["bar"]=>
int(%d)
}
string(60) "Typed property class@anonymous::$bar must be int, float used"
object(class@anonymous)#1 (1) {
["bar"]=>
int(%d)
}
string(82) "Cannot increment property class@anonymous::$bar of type int past its maximal value"
object(class@anonymous)#1 (1) {
["bar"]=>
int(%d)
}
string(60) "Typed property class@anonymous::$bar must be int, float used"
object(class@anonymous)#1 (1) {
["bar"]=>
int(%d)
}

View File

@ -0,0 +1,31 @@
--TEST--
Repeated assign of a variable to mismatched property type must not succeed
--FILE--
<?php
class A {
public int $foo;
}
class B {
public A $foo;
}
$objs = [new A, new A];
$v = 1;
foreach ($objs as $obj) {
$obj->foo = $v;
$v = new A;
$obj = new B;
$obj->foo = $v;
}
var_dump($objs);
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Typed property A::$foo must be int, A used in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View File

@ -0,0 +1,25 @@
--TEST--
Test __get on unset typed property must fail properly
--FILE--
<?php
declare(strict_types=1);
class Foo {
public int $bar;
public function __get($name) {
var_dump($name);
}
}
$foo = new Foo();
var_dump($foo->bar);
?>
--EXPECTF--
string(3) "bar"
Fatal error: Uncaught TypeError: Typed property Foo::$bar must be int, null used in %s:14
Stack trace:
#0 {main}
thrown in %s on line 14

View File

@ -0,0 +1,16 @@
--TEST--
Test typed properties weak conversion of strings
--FILE--
<?php
class Foo {
public int $bar = 1;
}
$foo = new Foo;
$foo->bar = "10";
var_dump($foo->bar);
?>
--EXPECT--
int(10)

View File

@ -0,0 +1,21 @@
--TEST--
Proper source duplication on assignment to typed property
--FILE--
<?php
class Foo {
public int $bar;
}
$foo = new Foo();
for ($i = 0; $i < 5; $i++) {
$foo->bar = "5";
var_dump($foo->bar);
}
?>
--EXPECT--
int(5)
int(5)
int(5)
int(5)
int(5)

View File

@ -0,0 +1,53 @@
--TEST--
Trying to assign to a static 'self' typed property on a trait must not fixate the type to the trait
--FILE--
<?php
trait Test {
public static self $selfProp;
public static ?self $selfNullProp;
public static parent $parentProp;
}
try {
Test::$selfProp = new stdClass;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
Test::$selfNullProp = new stdClass;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
Test::$parentProp = new stdClass;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
Test::$selfNullProp = null;
var_dump(Test::$selfNullProp);
class Foo {}
class Bar extends Foo {
use Test;
}
Bar::$selfProp = new Bar;
Bar::$selfNullProp = new Bar;
Bar::$parentProp = new Foo;
var_dump(Bar::$selfProp, Bar::$selfNullProp, Bar::$parentProp);
?>
--EXPECT--
Cannot write a value to a 'self' typed static property of a trait
Cannot write a non-null value to a 'self' typed static property of a trait
Cannot access parent:: when current class scope has no parent
NULL
object(Bar)#3 (0) {
}
object(Bar)#2 (0) {
}
object(Foo)#4 (0) {
}

View File

@ -0,0 +1,58 @@
--TEST--
Test increment functions on typed property references
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip this test is for 64 bit platform only"); ?>
--FILE--
<?php
$foo = new class {
public ?int $bar;
};
$bar = &$foo->bar;
$bar *= 1;
var_dump($bar--);
var_dump(--$bar);
var_dump(++$bar);
var_dump($bar++);
$bar = PHP_INT_MAX;
try {
var_dump($bar++);
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
try {
var_dump(++$bar);
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
$bar = PHP_INT_MIN;
try {
var_dump($bar--);
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
try {
var_dump(--$bar);
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
int(0)
int(-2)
int(-1)
int(-1)
Cannot increment a reference held by property class@anonymous::$bar of type ?int past its maximal value
Cannot increment a reference held by property class@anonymous::$bar of type ?int past its maximal value
Cannot decrement a reference held by property class@anonymous::$bar of type ?int past its minimal value
Cannot decrement a reference held by property class@anonymous::$bar of type ?int past its minimal value

View File

@ -0,0 +1,51 @@
--TEST--
foreach() must return properly typed references
--FILE--
<?php
class Foo {
public int $bar = 0;
public float $baz = 0.5;
private float $privateProp = 0.5;
public function test() {
foreach ($this as $k => &$val) {
if ($k == 'privateProp') {
var_dump($val);
$val = 20;
var_dump($val);
try {
$val = [];
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
}
}
}
}
$foo = new Foo;
foreach ($foo as $k => &$val) {
var_dump($val);
$val = 20;
var_dump($foo->$k);
try {
$val = [];
var_dump($foo->$k);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
}
$foo->test();
?>
--EXPECT--
int(0)
int(20)
Cannot assign array to reference held by property Foo::$bar of type int
float(0.5)
float(20)
Cannot assign array to reference held by property Foo::$baz of type float
float(0.5)
float(20)
Cannot assign array to reference held by property Foo::$privateProp of type float

View File

@ -0,0 +1,29 @@
--TEST--
Memory leaks on wrong assignment to typed property
--FILE--
<?php
class Foo {
public int $bbb;
}
function foo() {
return new Foo();
}
function bar() {
return str_repeat("b", 3);
}
for ($i = 0; $i < 5; $i++) {
try {
foo()->{bar()} = str_repeat("a", 3);
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
}
--EXPECT--
Typed property Foo::$bbb must be int, string used
Typed property Foo::$bbb must be int, string used
Typed property Foo::$bbb must be int, string used
Typed property Foo::$bbb must be int, string used
Typed property Foo::$bbb must be int, string used

View File

@ -0,0 +1,40 @@
--TEST--
Nullable typed property
--FILE--
<?php
class Foo {
public ?int $foo = null;
}
$x = new Foo();
var_dump($x);
var_dump($x->foo);
$x->foo = 5;
var_dump($x->foo);
$x->foo = null;
var_dump($x->foo);
unset($x->foo);
try {
var_dump($x->foo);
} catch (Throwable $e) {
echo $e->getMessage()."\n";
}
try {
$x->foo = "ops";
} catch (Throwable $e) {
echo $e->getMessage()."\n";
}
?>
--EXPECTF--
object(Foo)#1 (1) {
["foo"]=>
NULL
}
NULL
int(5)
NULL
Typed property Foo::$foo must not be accessed before initialization
Typed property Foo::$foo must be int or null, string used

View File

@ -0,0 +1,17 @@
--TEST--
Parent private property types must be ignored
--FILE--
<?php
class A {
private $prop = "1";
}
class B extends A {
private int $prop = 2;
}
var_dump((function () { return $this->prop; })->call(new B));
?>
--EXPECT--
int(2)

View File

@ -0,0 +1,10 @@
--TEST--
Nullable typed property
--FILE--
<?php
class Foo {
public int $foo = null;
}
?>
--EXPECTF--
Fatal error: Default value for property of type int may not be null. Use the nullable type ?int to allow null default value in %s on line %d

View File

@ -0,0 +1,19 @@
--TEST--
Weak casts must not overwrite source variables
--FILE--
<?php
$b = 1;
$a = "$b";
class A { public int $a; }
$o = new A;
$o->a = $b;
var_dump($o, $a);
?>
--EXPECT--
object(A)#1 (1) {
["a"]=>
int(1)
}
string(1) "1"

View File

@ -0,0 +1,27 @@
--TEST--
Weak casts must not leak
--FILE--
<?php
class A {
public string $a;
}
class B {
function __toString() {
return str_repeat("ok", 2);
}
}
class C {
}
$o = new A;
$o->a = new B;
var_dump($o->a);
try {
$o->a = new C;
} catch (Throwable $e) {
echo $e->getMessage()."\n";
}
?>
--EXPECT--
string(4) "okok"
Typed property A::$a must be string, C used

View File

@ -0,0 +1,34 @@
--TEST--
Class properties declared in eval() must not leak
--FILE--
<?php
eval(<<<'EOF'
class A {
public A $a1;
public \B $b1;
public Foo\C $c1;
public ?A $a2;
public ?\B $b2;
public ?Foo\C $c2;
}
EOF
);
$obj = new A;
var_dump($obj);
?>
--EXPECT--
object(A)#1 (0) {
["a1"]=>
uninitialized(A)
["b1"]=>
uninitialized(B)
["c1"]=>
uninitialized(Foo\C)
["a2"]=>
uninitialized(?A)
["b2"]=>
uninitialized(?B)
["c2"]=>
uninitialized(?Foo\C)
}

View File

@ -0,0 +1,12 @@
--TEST--
Typed properties disallow callable
--FILE--
<?php
class A {
public callable $a;
}
$obj = new A;
var_dump($obj);
?>
--EXPECTF--
Fatal error: Property A::$a cannot have type callable in %s on line %d

View File

@ -0,0 +1,12 @@
--TEST--
Typed properties disallow callable (nullable variant)
--FILE--
<?php
class A {
public ?callable $a;
}
$obj = new A;
var_dump($obj);
?>
--EXPECTF--
Fatal error: Property A::$a cannot have type callable in %s on line %d

View File

@ -0,0 +1,29 @@
--TEST--
Test assign to typed property taken by reference
--FILE--
<?php
class A {
public $foo = 1;
public int $bar = 2;
}
class B {
public A $a;
}
$f = function (&$n) {
var_dump($n);
$n = "ops";
};
$o = new B;
$o->a = new A;
$f($o->a->foo);
$f($o->a->bar);
?>
--EXPECTF--
int(1)
int(2)
Fatal error: Uncaught TypeError: Cannot assign string to reference held by property A::$bar of type int in %s:%d
Stack trace:
#0 %s(%d): {closure}(2)
#1 {main}
thrown in %s on line %d

View File

@ -0,0 +1,23 @@
--TEST--
Type change in assign_op (use-after-free)
--FILE--
<?php
declare(strict_types=1);
class A {
public string $foo;
}
$o = new A;
$o->foo = "1" . str_repeat("0", 2);
try {
$o->foo += 5;
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
var_dump($o->foo);
unset($o);
?>
--EXPECT--
Typed property A::$foo must be string, int used
string(3) "100"

View File

@ -0,0 +1,31 @@
--TEST--
Type change in pre/post-increment (use-after-free)
--FILE--
<?php
declare(strict_types=1);
class A {
public string $foo;
}
$o = new A;
$o->foo = "1" . str_repeat("0", 2);
try {
$x = ++$o->foo;
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
var_dump($o->foo);
try {
$x = $o->foo++;
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
var_dump($o->foo);
unset($o);
?>
--EXPECT--
Typed property A::$foo must be string, int used
string(3) "100"
Typed property A::$foo must be string, int used
string(3) "100"

View File

@ -0,0 +1,32 @@
--TEST--
Constants in default values of properties
--FILE--
<?php
declare(strict_types=1);
define("FOO", 5);
class A {
public int $foo = FOO;
}
class B {
public string $foo = FOO;
}
$o = new A();
var_dump($o->foo);
for ($i = 0; $i < 2; $i++) {
try {
$o = new B();
var_dump($o->foo);
} catch (Throwable $e) {
echo $e->getMessage() . "\n";
}
}
?>
--EXPECT--
int(5)
Typed property B::$foo must be string, int used
Typed property B::$foo must be string, int used

View File

@ -0,0 +1,29 @@
--TEST--
Nullable typed properties in traits
--FILE--
<?php
trait T {
public int $a1;
public ?int $b1;
}
class A {
use T;
public int $a2;
public ?int $b2;
}
$x = new A;
var_dump($x);
?>
--EXPECT--
object(A)#1 (0) {
["a2"]=>
uninitialized(int)
["b2"]=>
uninitialized(?int)
["a1"]=>
uninitialized(int)
["b1"]=>
uninitialized(?int)
}

View File

@ -0,0 +1,22 @@
--TEST--
Test typed properties work fine with simple inheritance
--FILE--
<?php
class A {
public int $a = 1;
}
class B extends A {}
$o = new B;
var_dump($o->a);
$o->a = "a";
?>
--EXPECTF--
int(1)
Fatal error: Uncaught TypeError: Typed property A::$a must be int, string used in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d

View File

@ -0,0 +1,58 @@
--TEST--
Typed property on overloaded by-ref property
--SKIPIF--
<?php if (PHP_INT_SIZE == 4) die("SKIP: 64 bit test"); ?>
--FILE--
<?php
$a = new class {
public int $foo = 1;
function &__get($x) {
return $this->foo;
}
function __set($x, $y) {
echo "set($y)\n";
}
};
$a->_ += 1;
var_dump($a->foo);
$a->_ .= "1";
var_dump($a->foo);
$a->_ .= "e50";
var_dump($a->foo);
$a->_--;
var_dump($a->foo);
--$a->_;
var_dump($a->foo);
$a->foo = PHP_INT_MAX;
$a->_++;
var_dump($a->foo);
++$a->_;
var_dump($a->foo);
?>
--EXPECT--
set(2)
int(1)
set(11)
int(1)
set(1e50)
int(1)
set(0)
int(1)
set(0)
int(1)
set(9.2233720368548E+18)
int(9223372036854775807)
set(9.2233720368548E+18)
int(9223372036854775807)

View File

@ -0,0 +1,81 @@
--TEST--
Typed property on by-ref property
--FILE--
<?php
$a = new class {
public int $foo = 1;
public $_;
};
$a->_ = &$a->foo;
$a->_ += 1;
var_dump($a->foo);
$a->_ .= "1";
var_dump($a->foo);
try {
$a->_ .= "e50";
} catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($a->foo);
$a->_--;
var_dump($a->foo);
--$a->_;
var_dump($a->foo);
$a->foo = PHP_INT_MIN;
try {
$a->_--;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
try {
--$a->_;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
$a->foo = PHP_INT_MAX;
try {
$a->_++;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
try {
++$a->_;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
$a->_ = 0;
try {
$a->_ = [];
} catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($a->foo);
$a->_ = 1;
var_dump($a->foo);
?>
--EXPECT--
int(2)
int(21)
Cannot assign string to reference held by property class@anonymous::$foo of type int
int(21)
int(20)
int(19)
Cannot decrement a reference held by property class@anonymous::$foo of type int past its minimal value
integer
Cannot decrement a reference held by property class@anonymous::$foo of type int past its minimal value
integer
Cannot increment a reference held by property class@anonymous::$foo of type int past its maximal value
integer
Cannot increment a reference held by property class@anonymous::$foo of type int past its maximal value
integer
Cannot assign array to reference held by property class@anonymous::$foo of type int
int(0)
int(1)

View File

@ -0,0 +1,80 @@
--TEST--
Typed property on by-ref variable
--FILE--
<?php
$a = new class {
public int $foo = 1;
};
$_ = &$a->foo;
$_ += 1;
var_dump($a->foo);
$_ .= "1";
var_dump($a->foo);
try {
$_ .= "e50";
} catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($a->foo);
$_--;
var_dump($a->foo);
--$_;
var_dump($a->foo);
$a->foo = PHP_INT_MIN;
try {
$_--;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
try {
--$_;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
$a->foo = PHP_INT_MAX;
try {
$_++;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
try {
++$_;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
$_ = 0;
try {
$_ = [];
} catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($a->foo);
$_ = 1;
var_dump($a->foo);
?>
--EXPECT--
int(2)
int(21)
Cannot assign string to reference held by property class@anonymous::$foo of type int
int(21)
int(20)
int(19)
Cannot decrement a reference held by property class@anonymous::$foo of type int past its minimal value
integer
Cannot decrement a reference held by property class@anonymous::$foo of type int past its minimal value
integer
Cannot increment a reference held by property class@anonymous::$foo of type int past its maximal value
integer
Cannot increment a reference held by property class@anonymous::$foo of type int past its maximal value
integer
Cannot assign array to reference held by property class@anonymous::$foo of type int
int(0)
int(1)

View File

@ -0,0 +1,80 @@
--TEST--
Typed property on by-ref array value
--FILE--
<?php
$a = new class {
public int $foo = 1;
};
$_ = [&$a->foo];
$_[0] += 1;
var_dump($a->foo);
$_[0] .= "1";
var_dump($a->foo);
try {
$_[0] .= "e50";
} catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($a->foo);
$_[0]--;
var_dump($a->foo);
--$_[0];
var_dump($a->foo);
$a->foo = PHP_INT_MIN;
try {
$_[0]--;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
try {
--$_[0];
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
$a->foo = PHP_INT_MAX;
try {
$_[0]++;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
try {
++$_[0];
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
$_[0] = 0;
try {
$_[0] = [];
} catch (Error $e) { echo $e->getMessage(), "\n"; }
var_dump($a->foo);
$_[0] = 1;
var_dump($a->foo);
?>
--EXPECT--
int(2)
int(21)
Cannot assign string to reference held by property class@anonymous::$foo of type int
int(21)
int(20)
int(19)
Cannot decrement a reference held by property class@anonymous::$foo of type int past its minimal value
integer
Cannot decrement a reference held by property class@anonymous::$foo of type int past its minimal value
integer
Cannot increment a reference held by property class@anonymous::$foo of type int past its maximal value
integer
Cannot increment a reference held by property class@anonymous::$foo of type int past its maximal value
integer
Cannot assign array to reference held by property class@anonymous::$foo of type int
int(0)
int(1)

View File

@ -0,0 +1,71 @@
--TEST--
Typed property on by-ref array dimension
--FILE--
<?php
$a = new class implements ArrayAccess {
public int $foo = 1;
function offsetExists($o) { return 1; }
function &offsetGet($o) { return $this->foo; }
function offsetSet($o, $v) { print "offsetSet($v)\n"; }
function offsetUnset($o) { print "offsetUnset() ?!?"; }
};
$a[0] += 1;
var_dump($a->foo);
$a[0] .= "1";
var_dump($a->foo);
$a[0] .= "e50";
var_dump($a->foo);
$a[0]--;
var_dump($a->foo);
--$a[0];
var_dump($a->foo);
$a->foo = PHP_INT_MIN;
try {
$a[0]--;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
try {
--$a[0];
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
$a->foo = PHP_INT_MAX;
try {
$a[0]++;
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
try {
++$a[0];
} catch (Error $e) { echo $e->getMessage(), "\n"; }
echo gettype($a->foo),"\n";
?>
--EXPECT--
offsetSet(2)
int(1)
offsetSet(11)
int(1)
offsetSet(1e50)
int(1)
int(0)
int(-1)
Cannot decrement a reference held by property class@anonymous::$foo of type int past its minimal value
integer
Cannot decrement a reference held by property class@anonymous::$foo of type int past its minimal value
integer
Cannot increment a reference held by property class@anonymous::$foo of type int past its maximal value
integer
Cannot increment a reference held by property class@anonymous::$foo of type int past its maximal value
integer

View File

@ -0,0 +1,21 @@
--TEST--
Typed property assignment must not overwrite constants
--FILE--
<?php
class Foo {
public float $x = 0.0;
};
$x = new Foo;
$y =& $x->x;
$y = 4;
var_dump($x, 4); /* Optimizer will merge both "4" constants, making it immediately visible */
?>
--EXPECT--
object(Foo)#1 (1) {
["x"]=>
&float(4)
}
int(4)

View File

@ -0,0 +1,36 @@
--TEST--
Iterable typed properties must be accepted to by-ref array arguments
--FILE--
<?php
$obj = new class {
public ?iterable $it = null;
};
function arr(?array &$arr) {
$arr = [1];
}
arr($obj->it);
var_dump($obj->it);
array_shift($obj->it);
var_dump($obj->it);
parse_str("foo=bar", $obj->it);
var_dump($obj->it);
$obj->it = [];
var_dump($obj->it);
?>
--EXPECT--
array(1) {
[0]=>
int(1)
}
array(0) {
}
array(1) {
["foo"]=>
string(3) "bar"
}
array(0) {
}

View File

@ -0,0 +1,87 @@
--TEST--
Test typed static property by ref
--FILE--
<?php
function &ref($a = null) {
static $f;
if ($a !== null) $f = function &() use (&$a) { return $a; };
return $f();
}
class Foo {
public static int $i;
public static string $s = "x";
}
Foo::$i = &ref(5);
var_dump(Foo::$i);
$i = &Foo::$i;
$i = 2;
var_dump($i, Foo::$i);
$i = "3";
var_dump($i, Foo::$i);
Foo::$i = "4";
var_dump($i, Foo::$i);
try {
$i = null;
} catch (TypeError $e) { print $e->getMessage()."\n"; }
var_dump($i, Foo::$i);
try {
Foo::$i = null;
} catch (TypeError $e) { print $e->getMessage()."\n"; }
var_dump($i, Foo::$i);
Foo::$s = &ref(5);
var_dump(Foo::$s, ref());
Foo::$i = &ref("0");
var_dump(Foo::$i, ref());
try {
Foo::$i = &ref("x");
} catch (TypeError $e) { print $e->getMessage()."\n"; }
var_dump(Foo::$i, ref());
try {
Foo::$i = &Foo::$s;
} catch (TypeError $e) { print $e->getMessage()."\n"; }
var_dump(Foo::$i, Foo::$s);
try {
Foo::$s = &Foo::$i;
} catch (TypeError $e) { print $e->getMessage()."\n"; }
var_dump(Foo::$i, Foo::$s);
?>
--EXPECT--
int(5)
int(2)
int(2)
int(3)
int(3)
int(4)
int(4)
Cannot assign null to reference held by property Foo::$i of type int
int(4)
int(4)
Typed property Foo::$i must be int, null used
int(4)
int(4)
string(1) "5"
string(1) "5"
int(0)
int(0)
Typed property Foo::$i must be int, string used
int(0)
string(1) "x"
Reference with value of type string held by property Foo::$s of type string is not compatible with property Foo::$i of type int
int(0)
string(1) "5"
Reference with value of type int held by property Foo::$i of type int is not compatible with property Foo::$s of type string
int(0)
string(1) "5"

View File

@ -0,0 +1,27 @@
--TEST--
Test assign of invalid string to typed static int property
--FILE--
<?php
function &nonNumericStringRef() {
static $a = "x";
return $a;
}
class Foo {
public static int $i;
}
try {
Foo::$i = &nonNumericStringRef();
} catch (TypeError $e) { print $e->getMessage()."\n"; }
try {
var_dump(Foo::$i);
} catch (Error $e) { print $e->getMessage()."\n"; }
var_dump(nonNumericStringRef());
?>
--EXPECT--
Typed property Foo::$i must be int, string used
Typed static property Foo::$i must not be accessed before initialization
string(1) "x"

View File

@ -0,0 +1,49 @@
--TEST--
Test typed static property with assign op operators
--FILE--
<?php
function &stringRef() {
static $a = "1";
$b = $a;
$a = &$b;
return $a;
}
class Foo {
public static int $i = 0;
public static string $s = "1";
}
Foo::$s .= "1";
var_dump(Foo::$s);
Foo::$s += 2;
var_dump(Foo::$s);
Foo::$s = &stringRef();
Foo::$s .= 2;
var_dump(Foo::$s);
Foo::$i += stringRef();
var_dump(Foo::$i);
try {
Foo::$i += PHP_INT_MAX;
} catch (TypeError $e) { print $e->getMessage()."\n"; }
var_dump(Foo::$i);
try {
Foo::$i .= PHP_INT_MAX;
} catch (TypeError $e) { print $e->getMessage()."\n"; }
var_dump(Foo::$i);
?>
--EXPECT--
string(2) "11"
string(2) "13"
string(2) "12"
int(1)
Typed property Foo::$i must be int, float used
int(1)
Typed property Foo::$i must be int, string used
int(1)

View File

@ -0,0 +1,36 @@
--TEST--
Test assignment to typed reference with weak type conversion
--FILE--
<?php
class Test {
public string $x = "x";
}
$test = new Test;
var_dump($test);
$y = "y";
$test->x = &$y;
var_dump($y, $test);
$z = 42;
$y = $z;
var_dump($y, $z, $test);
?>
--EXPECT--
object(Test)#1 (1) {
["x"]=>
string(1) "x"
}
string(1) "y"
object(Test)#1 (1) {
["x"]=>
&string(1) "y"
}
string(2) "42"
int(42)
object(Test)#1 (1) {
["x"]=>
&string(2) "42"
}

View File

@ -0,0 +1,24 @@
--TEST--
Typed property must cast when used with __get()
--FILE--
<?php
class Test {
public int $val;
public function __get($name) {
return "42";
}
}
$test = new Test;
var_dump($test);
var_dump($test->val);
?>
--EXPECT--
object(Test)#1 (0) {
["val"]=>
uninitialized(int)
}
int(42)

View File

@ -0,0 +1,44 @@
--TEST--
Typed property must cast when used with &__get()
--FILE--
<?php
class Test {
public $prop = "42";
public int $val;
public function &__get($name) {
return $this->prop;
}
}
$test = new Test;
var_dump($test);
var_dump($val = &$test->val);
var_dump($test);
$test->prop = "x";
var_dump($test, $val);
?>
--EXPECT--
object(Test)#1 (1) {
["prop"]=>
string(2) "42"
["val"]=>
uninitialized(int)
}
int(42)
object(Test)#1 (1) {
["prop"]=>
&int(42)
["val"]=>
uninitialized(int)
}
object(Test)#1 (1) {
["prop"]=>
&string(1) "x"
["val"]=>
uninitialized(int)
}
string(1) "x"

View File

@ -0,0 +1,41 @@
--TEST--
Typed property must be compatible when returned via &__get()
--FILE--
<?php
class Test {
public $prop = "x";
public int $val;
public function &__get($name) {
return $this->prop;
}
}
$test = new Test;
$dummyRef = &$test->prop;
var_dump($test);
try {
var_dump($test->val);
} catch (TypeError $e) { print $e->getMessage()."\n"; }
var_dump($test);
$test->prop = "y";
var_dump($test->prop);
?>
--EXPECT--
object(Test)#1 (1) {
["prop"]=>
&string(1) "x"
["val"]=>
uninitialized(int)
}
Typed property Test::$val must be int, string used
object(Test)#1 (1) {
["prop"]=>
&string(1) "x"
["val"]=>
uninitialized(int)
}
string(1) "y"

View File

@ -0,0 +1,53 @@
--TEST--
Test typed properties overflowing
--SKIPIF--
<?php if (PHP_INT_SIZE == 4) die("SKIP: 64 bit test"); ?>
--FILE--
<?php
class Foo {
public static int $bar = PHP_INT_MAX;
};
try {
Foo::$bar++;
} catch(TypeError $t) {
var_dump($t->getMessage());
}
var_dump(Foo::$bar);
try {
Foo::$bar += 1;
} catch(TypeError $t) {
var_dump($t->getMessage());
}
var_dump(Foo::$bar);
try {
++Foo::$bar;
} catch(TypeError $t) {
var_dump($t->getMessage());
}
var_dump(Foo::$bar);
try {
Foo::$bar = Foo::$bar + 1;
} catch(TypeError $t) {
var_dump($t->getMessage());
}
var_dump(Foo::$bar);
?>
--EXPECT--
string(70) "Cannot increment property Foo::$bar of type int past its maximal value"
int(9223372036854775807)
string(48) "Typed property Foo::$bar must be int, float used"
int(9223372036854775807)
string(70) "Cannot increment property Foo::$bar of type int past its maximal value"
int(9223372036854775807)
string(48) "Typed property Foo::$bar must be int, float used"
int(9223372036854775807)

View File

@ -0,0 +1,71 @@
--TEST--
Computation of intersection types for typed reference to typed property assignments
--FILE--
<?php
class A {}
class B extends A {}
class Test {
public int $int;
public float $float;
public ?int $nint;
public ?string $nstring;
public array $array;
public object $object;
public iterable $iterable;
public Iterator $Iterator;
public A $A;
public B $B;
}
function invalid(Test $test, string $prop1, string $prop2, $value) {
try {
$test->$prop2 = $value;
$test->$prop1 =& $test->$prop2;
echo "Invalid assignment $prop1 =& $prop2 did not error\n";
} catch (TypeError $e) {}
try {
$test->$prop1 = $value;
$test->$prop2 =& $test->$prop1;
echo "Invalid assignment $prop2 =& $prop1 did not error\n";
} catch (TypeError $e) {}
}
function valid(Test $test, string $prop1, string $prop2, $value) {
try {
$test->$prop2 = $value;
$test->$prop1 =& $test->$prop2;
} catch (TypeError $e) {
echo "Valid assignment $prop1 =& $prop2 threw {$e->getMessage()}\n";
}
try {
$test->$prop1 = $value;
$test->$prop2 =& $test->$prop1;
} catch (TypeError $e) {
echo "Valid assignment $prop2 =& $prop1 threw {$e->getMessage()}\n";
}
}
$test = new Test;
invalid($test, 'int', 'float', 42.0);
valid($test, 'int', 'nint', 42);
invalid($test, 'int', 'nint', null);
valid($test, 'nint', 'nstring', null);
invalid($test, 'nint', 'nstring', '42');
valid($test, 'A', 'A', new A);
valid($test, 'A', 'B', new B);
invalid($test, 'A', 'B', new A);
valid($test, 'iterable', 'array', [1, 2, 3]);
valid($test, 'A', 'object', new A);
invalid($test, 'A', 'object', new Test);
valid($test, 'iterable', 'Iterator', new ArrayIterator);
invalid($test, 'Iterator', 'iterable', [1, 2, 3]);
valid($test, 'object', 'iterable', new ArrayIterator);
invalid($test, 'iterable', 'object', new stdClass);
echo "Done\n";
?>
--EXPECT--
Done

View File

@ -0,0 +1,18 @@
--TEST--
Converted values shall be returned and not the original value upon property assignment
--FILE--
<?php
class Test {
public int $i;
public string $s;
}
$test = new Test;
var_dump($test->i = "42");
var_dump($test->s = 42);
?>
--EXPECT--
int(42)
string(2) "42"

View File

@ -0,0 +1,59 @@
--TEST--
Typed references must be kept track of and always be only the intersection of the types currently holding that reference
--FILE--
<?php
$a = new class {
public ?iterable $it = [];
public ?array $a;
public ?Traversable $t;
};
$ref = &$a->it;
$a->a = &$ref;
var_dump($ref);
try {
$a->t = &$ref;
} catch (TypeError $e) { var_dump($e->getMessage()); }
var_dump($ref);
$a->it = [1]; // type is still assignable
var_dump($ref);
try {
$ref = new ArrayIterator();
} catch (TypeError $e) { var_dump($e->getMessage()); }
var_dump($ref instanceof ArrayIterator);
unset($a->a);
$ref = null;
$a->t = &$ref;
try {
$ref = [];
} catch (TypeError $e) { var_dump($e->getMessage()); }
var_dump($ref instanceof ArrayIterator);
$ref = new ArrayIterator();
var_dump($ref instanceof ArrayIterator);
?>
--EXPECT--
array(0) {
}
string(89) "Typed property class@anonymous::$t must be an instance of Traversable or null, array used"
array(0) {
}
array(1) {
[0]=>
int(1)
}
string(92) "Cannot assign ArrayIterator to reference held by property class@anonymous::$a of type ?array"
bool(false)
string(90) "Cannot assign array to reference held by property class@anonymous::$t of type ?Traversable"
bool(false)
bool(true)

View File

@ -0,0 +1,33 @@
--TEST--
Test static typed properties with references
--FILE--
<?php
class A {
static iterable $it = [];
static ?array $a;
}
A::$a = &A::$it;
try {
A::$it = new ArrayIterator();
} catch (TypeError $e) { var_dump($e->getMessage()); }
var_dump(A::$it);
A::$a = &$a;
A::$it = new ArrayIterator();
try {
$a = 1;
} catch (TypeError $e) { var_dump($e->getMessage()); }
var_dump($a);
?>
--EXPECT--
string(78) "Cannot assign ArrayIterator to reference held by property A::$a of type ?array"
array(0) {
}
string(68) "Cannot assign int to reference held by property A::$a of type ?array"
NULL

View File

@ -0,0 +1,36 @@
--TEST--
Access to typed static properties before initialization
--FILE--
<?php
class Test {
public static int $a;
protected static int $b;
private static int $c;
static function run() {
try {
self::$a;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
self::$b;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
self::$c;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
}
}
Test::run();
?>
--EXPECT--
Typed static property Test::$a must not be accessed before initialization
Typed static property Test::$b must not be accessed before initialization
Typed static property Test::$c must not be accessed before initialization

View File

@ -0,0 +1,22 @@
--TEST--
Clone must inherit typed references
--FILE--
<?php
class Test {
public int $x = 42;
}
$test = new Test;
$x =& $test->x;
$test2 = clone $test;
unset($test);
try {
$x = "foo";
} catch (TypeError $e) { echo $e->getMessage(), "\n"; }
var_dump($test2->x);
?>
--EXPECT--
Cannot assign string to reference held by property Test::$x of type int
int(42)

View File

@ -0,0 +1,30 @@
--TEST--
Test typed references to static properties
--FILE--
<?php
class Test {
public static int $x = 0;
}
class Test2 extends Test {
public static $y = 1;
}
$x =& Test::$x;
try {
$x = "foo";
} catch (TypeError $e) { echo $e->getMessage(), "\n"; }
var_dump($x, Test::$x);
Test::$x =& Test2::$y; // remove the typed ref from $x
$x = "foo";
var_dump($x, Test::$x);
?>
--EXPECT--
Cannot assign string to reference held by property Test::$x of type int
int(0)
int(0)
string(3) "foo"
int(1)

View File

@ -0,0 +1,80 @@
--TEST--
Test array promotion does not violate type restrictions
--FILE--
<?php
class Foo {
public ?string $p;
public ?iterable $i;
public static ?string $s;
public static ?array $a;
}
$a = new Foo;
$a->i[] = 1;
var_dump($a->i);
try {
$a->p[] = "test";
} catch (TypeError $e) { var_dump($e->getMessage()); }
try { // must be uninit
var_dump($a->p); // WRONG!
} catch (Error $e) { var_dump($e->getMessage()); }
$a->p = null;
try {
$a->p[] = "test";
} catch (TypeError $e) { var_dump($e->getMessage()); }
var_dump($a->p);
Foo::$a["bar"] = 2;
var_dump(Foo::$a);
try {
Foo::$s["baz"][] = "baz";
} catch (TypeError $e) { var_dump($e->getMessage()); }
try { // must be uninit
var_dump(Foo::$s);
} catch (Error $e) { var_dump($e->getMessage()); }
Foo::$a = null;
$ref = &Foo::$a;
$ref[] = 3;
var_dump($ref);
$ref = &$a->p;
try {
$ref[] = "bar";
} catch (TypeError $e) { var_dump($e->getMessage()); }
var_dump($ref);
try {
$ref["baz"][] = "bar"; // indirect assign
} catch (TypeError $e) { var_dump($e->getMessage()); }
var_dump($ref);
?>
--EXPECT--
array(1) {
[0]=>
int(1)
}
string(71) "Cannot auto-initialize an array inside property Foo::$p of type ?string"
string(65) "Typed property Foo::$p must not be accessed before initialization"
string(71) "Cannot auto-initialize an array inside property Foo::$p of type ?string"
NULL
array(1) {
["bar"]=>
int(2)
}
string(71) "Cannot auto-initialize an array inside property Foo::$s of type ?string"
string(72) "Typed static property Foo::$s must not be accessed before initialization"
array(1) {
[0]=>
int(3)
}
string(91) "Cannot auto-initialize an array inside a reference held by property Foo::$p of type ?string"
NULL
string(91) "Cannot auto-initialize an array inside a reference held by property Foo::$p of type ?string"
NULL

View File

@ -0,0 +1,25 @@
--TEST--
Typed properties and class aliases
--FILE--
<?php
eval(<<<'PHP'
class Foo {}
class_alias('Foo', 'Bar');
PHP);
eval(<<<'PHP'
class A {
public Foo $prop;
}
PHP);
eval(<<<'PHP'
class B extends A {
public Bar $prop;
}
PHP);
?>
===DONE===
--EXPECT--
===DONE===

View File

@ -0,0 +1,17 @@
--TEST--
Important properties with different types from traits
--FILE--
<?php
trait T1 {
public int $prop;
}
trait T2 {
public string $prop;
}
class C {
use T1, T2;
}
?>
--EXPECTF--
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

View File

@ -0,0 +1,27 @@
--TEST--
Test typed properties with integer keys
--FILE--
<?php
class T {
// Class must have at least one property. Property must have a type.
// Empty class or untyped property removes segfault
public int $i;
}
$t = new T;
// $x must be undefined or a non-string type
$x = 1;
$t->$x = 2;
$t->$x--;
var_dump($t);
?>
--EXPECT--
object(T)#1 (1) {
["i"]=>
uninitialized(int)
["1"]=>
int(1)
}

View File

@ -0,0 +1,15 @@
--TEST--
Ensure null-initialization of nullable typed static property taken by reference
--FILE--
<?php
class A {
public static ?int $a;
}
$x =& A::$a;
var_dump($x);
?>
--EXPECT--
NULL

View File

@ -0,0 +1,30 @@
--TEST--
Check for correct invalidation of prop_info cache slots
--FILE--
<?php
class A {
public int $prop;
}
class B {
public $prop;
}
function test($obj) {
$obj->prop = "42";
var_dump($obj);
}
test(new A);
test(new B);
?>
--EXPECT--
object(A)#1 (1) {
["prop"]=>
int(42)
}
object(B)#1 (1) {
["prop"]=>
string(2) "42"
}

View File

@ -0,0 +1,38 @@
--TEST--
Modification of typed property during assignment must not leak
--FILE--
<?php
class A {
public string $prop = "";
}
class B {
public function __toString() {
global $a;
$a->prop = "dont ";
$a->prop .= "leak ";
$a->prop .= "me!";
return "test";
}
}
$a = new A;
$a->prop = new B;
var_dump($a);
$a = new A;
$prop = &$a->prop;
$a->prop = new B;
var_dump($a);
?>
--EXPECTF--
object(A)#1 (1) {
["prop"]=>
string(4) "test"
}
object(A)#%d (1) {
["prop"]=>
&string(4) "test"
}

View File

@ -0,0 +1,25 @@
--TEST--
Unsetting typed properties containing a reference must respect shadowing
--FILE--
<?php
class A {
private int $prop = 42;
public function test() {
$x =& $this->prop;
unset($this->prop);
$x = "foo";
var_dump($x);
}
}
class B extends A {
private $prop;
}
$b = new B;
$b->test();
?>
--EXPECT--
string(3) "foo"

View File

@ -0,0 +1,199 @@
--TEST--
Automatic promotion of falsy to object
--FILE--
<?php
class Test {
public ?Test $prop;
public ?stdClass $stdProp;
public ?object $objectProp;
public static ?Test $staticProp = null;
public static ?stdClass $staticStdProp = null;
public static ?object $staticObjectProp = null;
}
// Object properties
$test = new Test;
try {
$test->prop->wat = 123;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
$test->stdProp->wat = 123;
$test->objectProp->wat = 123;
var_dump($test);
// Object properties via reference
$test = new Test;
$prop =& $test->prop;
$stdProp =& $test->stdProp;
$objectProp =& $test->objectProp;
try {
$prop->wat = 123;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
$stdProp->wat = 123;
$objectProp->wat = 123;
var_dump($test);
// Object properties via reference rw
$test = new Test;
$prop =& $test->prop;
$stdProp =& $test->stdProp;
$objectProp =& $test->objectProp;
try {
$prop->wat->wat = 123;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
$stdProp->wat->wat = 123;
$objectProp->wat->wat = 123;
var_dump($test);
// Static properties
try {
Test::$staticProp->wat = 123;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
Test::$staticStdProp->wat = 123;
Test::$staticObjectProp->wat = 123;
var_dump(Test::$staticProp, Test::$staticStdProp, Test::$staticObjectProp);
// Non-string property name
$test = new Test;
$propName = new class {
public function __toString() {
return 'prop';
}
};
try {
$test->$propName->wat = 123;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test);
// Initially null
$test = new Test;
$test->prop = NULL;
$test->stdProp = NULL;
$test->objectProp = NULL;
try {
$test->prop->wat = 123;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
$test->stdProp->wat = 123;
$test->objectProp->wat = 123;
var_dump($test);
?>
--EXPECTF--
Cannot auto-initialize an stdClass inside property Test::$prop of type ?Test
Warning: Creating default object from empty value in %s on line %d
Warning: Creating default object from empty value in %s on line %d
object(Test)#1 (2) {
["prop"]=>
uninitialized(?Test)
["stdProp"]=>
object(stdClass)#3 (1) {
["wat"]=>
int(123)
}
["objectProp"]=>
object(stdClass)#4 (1) {
["wat"]=>
int(123)
}
}
Cannot auto-initialize an stdClass inside a reference held by property Test::$prop of type ?Test
Warning: Creating default object from empty value in %s on line %d
Warning: Creating default object from empty value in %s on line %d
object(Test)#5 (3) {
["prop"]=>
&NULL
["stdProp"]=>
&object(stdClass)#2 (1) {
["wat"]=>
int(123)
}
["objectProp"]=>
&object(stdClass)#4 (1) {
["wat"]=>
int(123)
}
}
Cannot auto-initialize an stdClass inside a reference held by property Test::$prop of type ?Test
Warning: Creating default object from empty value in %s on line %d
Warning: Creating default object from empty value in %s on line %d
object(Test)#3 (3) {
["prop"]=>
&NULL
["stdProp"]=>
&object(stdClass)#1 (1) {
["wat"]=>
object(stdClass)#2 (1) {
["wat"]=>
int(123)
}
}
["objectProp"]=>
&object(stdClass)#5 (1) {
["wat"]=>
object(stdClass)#6 (1) {
["wat"]=>
int(123)
}
}
}
Cannot auto-initialize an stdClass inside property Test::$staticProp of type ?Test
Warning: Creating default object from empty value in %s on line %d
Warning: Creating default object from empty value in %s on line %d
NULL
object(stdClass)#4 (1) {
["wat"]=>
int(123)
}
object(stdClass)#8 (1) {
["wat"]=>
int(123)
}
Cannot auto-initialize an stdClass inside property Test::$prop of type ?Test
object(Test)#9 (0) {
["prop"]=>
uninitialized(?Test)
["stdProp"]=>
uninitialized(?stdClass)
["objectProp"]=>
uninitialized(?object)
}
Cannot auto-initialize an stdClass inside property Test::$prop of type ?Test
Warning: Creating default object from empty value in %s on line %d
Warning: Creating default object from empty value in %s on line %d
object(Test)#7 (3) {
["prop"]=>
NULL
["stdProp"]=>
object(stdClass)#10 (1) {
["wat"]=>
int(123)
}
["objectProp"]=>
object(stdClass)#11 (1) {
["wat"]=>
int(123)
}
}

View File

@ -0,0 +1,41 @@
--TEST--
Refs on ASSIGN_OBJ fast-path
--FILE--
<?php
function &ref(&$foo) {
return $foo;
}
class Test {
public array $prop;
public int $prop2;
public function foo() {
$array = [];
$ref =& $array;
$this->prop = $array;
}
public function bar() {
$str = "123";
$this->prop2 = ref($str);
}
}
$test = new Test;
$test->foo();
$test->foo();
$test->bar();
$test->bar();
var_dump($test);
?>
--EXPECT--
object(Test)#1 (2) {
["prop"]=>
array(0) {
}
["prop2"]=>
int(123)
}

View File

@ -0,0 +1,31 @@
--TEST--
Typed property assignment by ref with variable name
--FILE--
<?php
class Test {
public int $prop;
}
$name = new class {
public function __toString() {
return 'prop';
}
};
$test = new Test;
$ref = "foobar";
try {
$test->$name =& $ref;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test);
?>
--EXPECT--
Typed property Test::$prop must be int, string used
object(Test)#2 (0) {
["prop"]=>
uninitialized(int)
}

View File

@ -0,0 +1,36 @@
--TEST--
Edge cases relating to reference source tracking
--FILE--
<?php
class A {
public int $prop = 42;
}
class B extends A {
}
$b = new B;
$r =& $b->prop;
unset($b);
$r = "foo"; // ok
class A2 {
private int $prop = 42;
public function &getRef() {
return $this->prop;
}
}
class B2 extends A2 {
public $prop;
}
$b2 = new B2;
$r2 =& $b2->getRef();
unset($b2);
$r2 = "foo"; // ok
?>
===DONE===
--EXPECT--
===DONE===

View File

@ -0,0 +1,84 @@
--TEST--
Typed properties in internal classes
--SKIPIF--
<?php if (!extension_loaded('zend-test')) die('skip requires zend-test'); ?>
--FILE--
<?php
// Internal typed properties
$obj = new _ZendTestClass;
var_dump($obj->intProp);
try {
$obj->intProp = "foobar";
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
$obj->intProp = 456;
try {
$obj->classProp = $obj;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
$obj->classProp = new stdClass;
var_dump($obj);
// Inherit from internal class
class Test extends _ZendTestClass {
}
$obj = new Test;
var_dump($obj->intProp);
try {
$obj->intProp = "foobar";
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
$obj->intProp = 456;
try {
$obj->classProp = $obj;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
$obj->classProp = new stdClass;
var_dump($obj);
// Static internal typed properties
var_dump(_ZendTestClass::$staticIntProp);
try {
_ZendTestClass::$staticIntProp = "foobar";
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
_ZendTestClass::$staticIntProp = 456;
var_dump(_ZendTestClass::$staticIntProp);
?>
--EXPECT--
int(123)
Typed property _ZendTestClass::$intProp must be int, string used
Typed property _ZendTestClass::$classProp must be an instance of stdClass or null, _ZendTestClass used
object(_ZendTestClass)#1 (2) {
["intProp"]=>
int(456)
["classProp"]=>
object(stdClass)#2 (0) {
}
}
int(123)
Typed property _ZendTestClass::$intProp must be int, string used
Typed property _ZendTestClass::$classProp must be an instance of stdClass or null, Test used
object(Test)#4 (2) {
["intProp"]=>
int(456)
["classProp"]=>
object(stdClass)#1 (0) {
}
}
int(123)
Typed property _ZendTestClass::$staticIntProp must be int, string used
int(456)

View File

@ -0,0 +1,46 @@
--TEST--
References to typed properties with undefined classes
--FILE--
<?php
class Test1 {
public Foobar $prop;
public int $prop2;
}
$test = new Test1;
$test->prop2 = 123;
$ref =& $test->prop2;
try {
$test->prop =& $ref;
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
var_dump($test);
class Test2 {
public ?Foobar $prop;
public ?int $prop2;
}
$test = new Test2;
$test->prop2 = null;
$ref =& $test->prop2;
$test->prop =& $ref;
var_dump($test);
?>
--EXPECT--
Typed property Test1::$prop must be an instance of Foobar, int used
object(Test1)#1 (1) {
["prop"]=>
uninitialized(Foobar)
["prop2"]=>
&int(123)
}
object(Test2)#3 (2) {
["prop"]=>
&NULL
["prop2"]=>
&NULL
}

View File

@ -0,0 +1,90 @@
--TEST--
Incrementing/decrementing past max/min value (additional cases)
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die('skip 64 bit test'); ?>
--FILE--
<?php
class Test {
public int $foo;
}
$test = new Test;
$test->foo = PHP_INT_MIN;
try {
--$test->foo;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->foo);
try {
$test->foo--;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->foo);
$test->foo = PHP_INT_MAX;
try {
++$test->foo;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->foo);
try {
$test->foo++;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->foo);
// Do the same things again, but with the property being a reference.
$ref =& $test->foo;
$test->foo = PHP_INT_MIN;
try {
--$test->foo;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->foo);
try {
$test->foo--;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->foo);
$test->foo = PHP_INT_MAX;
try {
++$test->foo;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->foo);
try {
$test->foo++;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
var_dump($test->foo);
?>
--EXPECT--
Cannot decrement property Test::$foo of type int past its minimal value
int(-9223372036854775808)
Cannot decrement property Test::$foo of type int past its minimal value
int(-9223372036854775808)
Cannot increment property Test::$foo of type int past its maximal value
int(9223372036854775807)
Cannot increment property Test::$foo of type int past its maximal value
int(9223372036854775807)
Cannot decrement property Test::$foo of type int past its minimal value
int(-9223372036854775808)
Cannot decrement property Test::$foo of type int past its minimal value
int(-9223372036854775808)
Cannot increment property Test::$foo of type int past its maximal value
int(9223372036854775807)
Cannot increment property Test::$foo of type int past its maximal value
int(9223372036854775807)

View File

@ -0,0 +1,17 @@
--TEST--
Make sure uninitialized property is initialized to null when taken by reference
--FILE--
<?php
class Test {
public $prop;
}
$test = new Test;
unset($test->prop);
$ref =& $test->prop;
var_dump($ref);
?>
--EXPECT--
NULL

Some files were not shown because too many files have changed in this diff Show More