Skip to content

Commit 999e32b

Browse files
committed
Implement union types
According to RFC: https://wiki.php.net/rfc/union_types_v2 The type representation now makes use of both the pointer payload and the type mask at the same time. Additionall, zend_type_list is introduced as a new kind of pointer payload, which is used to store multiple class types. Each of the class types is a tagged pointer, which may be either a class name or class entry. The latter is only used for typed properties, while arguments/returns will instead use cache slots. A type list can contain a mix of both names and CEs at the same time, as not all classes may be resolvable. One thing this is missing is support for union types in arginfo and stubs, which I want to handle separately. I've also dropped the special object code from the JIT implementation for now -- I plan to add this back in a different form at a later time. For now I did not want to include non-trivial JIT changes together with large functional changes. Another possible piece of follow-up work is to implement "iterable" as an internal alias for "array|Traversable". I believe this will eliminate quite a few special-cases that had to be implemented. Closes GH-4838.
1 parent ac4e0f0 commit 999e32b

File tree

66 files changed

+2713
-788
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2713
-788
lines changed

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ PHP 8.0 UPGRADE NOTES
318318
========================================
319319

320320
- Core:
321+
. Added support for union types.
322+
RFC: https://wiki.php.net/rfc/union_types_v2
321323
. Added ValueError class.
322324

323325
========================================

Zend/tests/assert/expect_015.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ assert(0 && ($a = function &(array &$a, ?X $b = null) use ($c,&$d) : ?X {
6262
}
6363
}));
6464

65-
assert(0 && ($a = function &(array &$a, X $b = null) use ($c,&$d) : X {
65+
assert(0 && ($a = function &(array &$a, X $b = null, int|float $c) use ($c,&$d) : X {
6666
final class A {
6767
final protected function f2() {
6868
if (!$x) {
@@ -204,7 +204,7 @@ Warning: assert(): assert(0 && ($a = function &(array &$a, ?X $b = null) use($c,
204204

205205
})) failed in %sexpect_015.php on line %d
206206

207-
Warning: assert(): assert(0 && ($a = function &(array &$a, X $b = null) use($c, &$d): X {
207+
Warning: assert(): assert(0 && ($a = function &(array &$a, X $b = null, int|float $c) use($c, &$d): X {
208208
final class A {
209209
protected final function f2() {
210210
if (!$x) {

Zend/tests/return_types/generators002.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ function test1() : StdClass {
66
yield 1;
77
}
88
--EXPECTF--
9-
Fatal error: Generators may only declare a return type of Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d
9+
Fatal error: Generators may only declare a return type containing Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d

Zend/tests/type_declarations/nullable_void.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ function test() : ?void {
88

99
?>
1010
--EXPECTF--
11-
Fatal error: Void type cannot be nullable in %s on line %d
11+
Fatal error: Void can only be used as a standalone type in %s on line %d

Zend/tests/type_declarations/typed_properties_043.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ var_dump(Bar::$selfProp, Bar::$selfNullProp, Bar::$parentProp);
4141

4242
?>
4343
--EXPECT--
44-
Cannot write a value to a 'self' typed static property of a trait
45-
Cannot write a non-null value to a 'self' typed static property of a trait
46-
Cannot access parent:: when current class scope has no parent
44+
Cannot assign stdClass to property Test::$selfProp of type self
45+
Cannot assign stdClass to property Test::$selfNullProp of type ?self
46+
Cannot assign stdClass to property Test::$parentProp of type parent
4747
NULL
4848
object(Bar)#3 (0) {
4949
}

Zend/tests/type_declarations/typed_properties_095.phpt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,26 @@ var_dump(_ZendTestClass::$staticIntProp);
6262
int(123)
6363
Cannot assign string to property _ZendTestClass::$intProp of type int
6464
Cannot assign _ZendTestClass to property _ZendTestClass::$classProp of type ?stdClass
65-
object(_ZendTestClass)#1 (2) {
65+
object(_ZendTestClass)#1 (3) {
6666
["intProp"]=>
6767
int(456)
6868
["classProp"]=>
6969
object(stdClass)#2 (0) {
7070
}
71+
["classUnionProp"]=>
72+
NULL
7173
}
7274
int(123)
7375
Cannot assign string to property _ZendTestClass::$intProp of type int
7476
Cannot assign Test to property _ZendTestClass::$classProp of type ?stdClass
75-
object(Test)#4 (2) {
77+
object(Test)#4 (3) {
7678
["intProp"]=>
7779
int(456)
7880
["classProp"]=>
7981
object(stdClass)#1 (0) {
8082
}
83+
["classUnionProp"]=>
84+
NULL
8185
}
8286
int(123)
8387
Cannot assign string to property _ZendTestClass::$staticIntProp of type int
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Generator return value has to have Traversable-ish, but may also have extra types
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
public function test(): iterable|false;
8+
}
9+
10+
class C implements I {
11+
public function test(): iterable|false {
12+
yield;
13+
}
14+
}
15+
16+
var_dump((new C)->test());
17+
18+
?>
19+
--EXPECT--
20+
object(Generator)#2 (0) {
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Generator return type with multiple classes
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
public function test(): Generator|ArrayAccess|array;
8+
}
9+
class C implements I {
10+
function test(): Generator|ArrayAccess|array {
11+
yield;
12+
}
13+
}
14+
15+
?>
16+
===DONE===
17+
--EXPECT--
18+
===DONE===
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Argument default value not legal for any type in the union
3+
--FILE--
4+
<?php
5+
6+
function test(int|float $arg = "0") {
7+
}
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Cannot use string as default value for parameter $arg of type int|float in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Default value not legal for any type in the union
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public int|float $prop = "0";
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot use string as default value for property Test::$prop of type int|float in %s on line %d
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
--TEST--
2+
Increment/decrement a typed property with int|float type
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public int|float $prop;
8+
public int|bool $prop2;
9+
}
10+
11+
/* Incrementing a int|float property past int min/max is legal */
12+
13+
$test = new Test;
14+
$test->prop = PHP_INT_MAX;
15+
$x = $test->prop++;
16+
var_dump(is_double($test->prop));
17+
18+
$test->prop = PHP_INT_MAX;
19+
$x = ++$test->prop;
20+
var_dump(is_double($test->prop));
21+
22+
$test->prop = PHP_INT_MIN;
23+
$x = $test->prop--;
24+
var_dump(is_double($test->prop));
25+
26+
$test->prop = PHP_INT_MIN;
27+
$x = --$test->prop;
28+
var_dump(is_double($test->prop));
29+
30+
$test = new Test;
31+
$test->prop = PHP_INT_MAX;
32+
$r =& $test->prop;
33+
$x = $test->prop++;
34+
var_dump(is_double($test->prop));
35+
36+
$test->prop = PHP_INT_MAX;
37+
$x = ++$test->prop;
38+
$r =& $test->prop;
39+
var_dump(is_double($test->prop));
40+
41+
$test->prop = PHP_INT_MIN;
42+
$x = $test->prop--;
43+
$r =& $test->prop;
44+
var_dump(is_double($test->prop));
45+
46+
$test->prop = PHP_INT_MIN;
47+
$x = --$test->prop;
48+
$r =& $test->prop;
49+
var_dump(is_double($test->prop));
50+
51+
/* Incrementing a non-int|float property past int min/max is an error,
52+
* even if the result of the overflow (a float) would technically be allowed
53+
* under a type coercion. */
54+
55+
try {
56+
$test->prop2 = PHP_INT_MAX;
57+
$x = $test->prop2++;
58+
} catch (TypeError $e) {
59+
echo $e->getMessage(), "\n";
60+
}
61+
62+
try {
63+
$test->prop2 = PHP_INT_MAX;
64+
$x = ++$test->prop2;
65+
} catch (TypeError $e) {
66+
echo $e->getMessage(), "\n";
67+
}
68+
69+
try {
70+
$test->prop2 = PHP_INT_MIN;
71+
$x = $test->prop2--;
72+
} catch (TypeError $e) {
73+
echo $e->getMessage(), "\n";
74+
}
75+
76+
try {
77+
$test->prop2 = PHP_INT_MIN;
78+
$x = --$test->prop2;
79+
} catch (TypeError $e) {
80+
echo $e->getMessage(), "\n";
81+
}
82+
83+
try {
84+
$test->prop2 = PHP_INT_MAX;
85+
$r =& $test->prop2;
86+
$x = $test->prop2++;
87+
} catch (TypeError $e) {
88+
echo $e->getMessage(), "\n";
89+
}
90+
91+
try {
92+
$test->prop2 = PHP_INT_MAX;
93+
$r =& $test->prop2;
94+
$x = ++$test->prop2;
95+
} catch (TypeError $e) {
96+
echo $e->getMessage(), "\n";
97+
}
98+
99+
try {
100+
$test->prop2 = PHP_INT_MIN;
101+
$r =& $test->prop2;
102+
$x = $test->prop2--;
103+
} catch (TypeError $e) {
104+
echo $e->getMessage(), "\n";
105+
}
106+
107+
try {
108+
$test->prop2 = PHP_INT_MIN;
109+
$r =& $test->prop2;
110+
$x = --$test->prop2;
111+
} catch (TypeError $e) {
112+
echo $e->getMessage(), "\n";
113+
}
114+
115+
?>
116+
--EXPECT--
117+
bool(true)
118+
bool(true)
119+
bool(true)
120+
bool(true)
121+
bool(true)
122+
bool(true)
123+
bool(true)
124+
bool(true)
125+
Cannot increment property Test::$prop2 of type int|bool past its maximal value
126+
Cannot increment property Test::$prop2 of type int|bool past its maximal value
127+
Cannot decrement property Test::$prop2 of type int|bool past its minimal value
128+
Cannot decrement property Test::$prop2 of type int|bool past its minimal value
129+
Cannot increment a reference held by property Test::$prop2 of type int|bool past its maximal value
130+
Cannot increment a reference held by property Test::$prop2 of type int|bool past its maximal value
131+
Cannot decrement a reference held by property Test::$prop2 of type int|bool past its minimal value
132+
Cannot decrement a reference held by property Test::$prop2 of type int|bool past its minimal value
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
Various inheritance scenarios for properties/methods with union types
3+
--FILE--
4+
<?php
5+
6+
class X {
7+
public A|B|int $prop;
8+
public function method(A|B|int $arg): A|B|int { }
9+
10+
private A|B|int $prop2;
11+
private function method2(A|B|int $arg): A|B|int { }
12+
}
13+
14+
class Y extends X {
15+
}
16+
17+
trait T {
18+
public A|B|int $prop;
19+
public function method(A|B|int $arg): A|B|int { }
20+
21+
private A|B|int $prop2;
22+
private function method2(A|B|int $arg): A|B|int { }
23+
}
24+
25+
class Z {
26+
use T;
27+
}
28+
29+
class U extends X {
30+
use T;
31+
}
32+
33+
class V extends X {
34+
use T;
35+
36+
public A|B|int $prop;
37+
public function method(A|B|int $arg): A|B|int { }
38+
39+
private A|B|int $prop2;
40+
private function method2(A|B|int $arg): A|B|int { }
41+
}
42+
43+
?>
44+
===DONE===
45+
--EXPECT--
46+
===DONE===
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Inheritance of union type from internal class
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('zend-test')) die('skip requires zend-test extension');
6+
?>
7+
--FILE--
8+
<?php
9+
10+
class C extends _ZendTestClass {}
11+
12+
$obj = new _ZendTestChildClass;
13+
$obj->classUnionProp = new stdClass;
14+
$obj->classUnionProp = new ArrayIterator;
15+
try {
16+
$obj->classUnionProp = new DateTime;
17+
} catch (TypeError $e) {
18+
echo $e->getMessage(), "\n";
19+
}
20+
21+
$obj = new C;
22+
$obj->classUnionProp = new stdClass;
23+
$obj->classUnionProp = new ArrayIterator;
24+
try {
25+
$obj->classUnionProp = new DateTime;
26+
} catch (TypeError $e) {
27+
echo $e->getMessage(), "\n";
28+
}
29+
30+
?>
31+
--EXPECT--
32+
Cannot assign DateTime to property _ZendTestClass::$classUnionProp of type stdClass|Iterator|null
33+
Cannot assign DateTime to property _ZendTestClass::$classUnionProp of type stdClass|Iterator|null

0 commit comments

Comments
 (0)