Skip to content

Commit 06c290e

Browse files
committed
Start implementing support for mixing union and intersection types
Parameter composite types are currently broken
1 parent d2e85d9 commit 06c290e

File tree

5 files changed

+358
-77
lines changed

5 files changed

+358
-77
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
Test for a complex type with an intersection type and union of a simple type
3+
--FILE--
4+
<?php
5+
6+
interface X {}
7+
interface Y {}
8+
9+
class A implements X, Y {}
10+
class B {}
11+
12+
class Test {
13+
public X&Y|int $prop1;
14+
public int|X&Y $prop2;
15+
public X&Y|B $prop3;
16+
public B|X&Y $prop4;
17+
18+
public function foo1(X&Y|int $v): X&Y|int {
19+
var_dump($v);
20+
return $v;
21+
}
22+
public function foo2(int|X&Y $v): int|X&Y {
23+
var_dump($v);
24+
return $v;
25+
}
26+
public function bar1(B|X&Y $v): B|X&Y {
27+
var_dump($v);
28+
return $v;
29+
}
30+
public function bar2(X&Y|B $v): X&Y|B {
31+
var_dump($v);
32+
return $v;
33+
}
34+
}
35+
36+
$test = new Test();
37+
$a = new A();
38+
$b = new B();
39+
$i = 10;
40+
41+
$test->foo1($a);
42+
$test->foo2($a);
43+
$test->foo1($i);
44+
$test->foo2($i);
45+
$test->prop1 = $a;
46+
$test->prop1 = $i;
47+
$test->prop2 = $a;
48+
$test->prop2 = $i;
49+
50+
$test->bar1($a);
51+
$test->bar2($a);
52+
$test->bar1($b);
53+
$test->bar2($b);
54+
$test->prop3 = $a;
55+
$test->prop4 = $b;
56+
$test->prop3 = $a;
57+
$test->prop4 = $b;
58+
59+
?>
60+
===DONE===
61+
--EXPECT--
62+
===DONE===
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
--TEST--
2+
Test for a complex type with an intersection type and union with another intersection
3+
--FILE--
4+
<?php
5+
6+
interface W {}
7+
interface X {}
8+
interface Y {}
9+
interface Z {}
10+
11+
class A implements X, Y {}
12+
class B implements W, Z {}
13+
class C {}
14+
15+
function foo1(X&Y|W&Z $v): X&Y|W&Z {
16+
return $v;
17+
}
18+
function foo2(W&Z|X&Y $v): W&Z|X&Y {
19+
return $v;
20+
}
21+
22+
function bar1(): X&Y|W&Z {
23+
return new C();
24+
}
25+
function bar2(): W&Z|X&Y {
26+
return new C();
27+
}
28+
29+
$a = new A();
30+
$b = new B();
31+
32+
$o = foo1($a);
33+
var_dump($o);
34+
$o = foo2($a);
35+
var_dump($o);
36+
$o = foo1($b);
37+
var_dump($o);
38+
$o = foo2($b);
39+
var_dump($o);
40+
41+
try {
42+
bar1();
43+
} catch (\TypeError $e) {
44+
echo $e->getMessage(), \PHP_EOL;
45+
}
46+
try {
47+
bar2();
48+
} catch (\TypeError $e) {
49+
echo $e->getMessage(), \PHP_EOL;
50+
}
51+
52+
?>
53+
--EXPECTF--
54+
object(A)#%d (0) {
55+
}
56+
object(A)#%d (0) {
57+
}
58+
object(B)#%d (0) {
59+
}
60+
object(B)#%d (0) {
61+
}
62+
bar1(): Return value must be of type X&Y|W&Z, C returned
63+
bar2(): Return value must be of type W&Z|X&Y, C returned

Zend/zend_compile.c

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,28 +1194,73 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop
11941194
return zend_string_copy(name);
11951195
}
11961196

1197+
static zend_string *resolve_intersection_type(zend_string *str,
1198+
zend_type_list *intersection_type_list, zend_class_entry *scope)
1199+
{
1200+
zend_type *single_type;
1201+
/* First type is not part of an intersection with the previous type */
1202+
bool is_intersection = false;
1203+
1204+
ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) {
1205+
if (ZEND_TYPE_HAS_CE(*single_type)) {
1206+
str = add_type_string(str, ZEND_TYPE_CE(*single_type)->name, is_intersection);
1207+
} else {
1208+
if (ZEND_TYPE_HAS_CE_CACHE(*single_type)
1209+
&& ZEND_TYPE_CE_CACHE(*single_type)) {
1210+
zend_class_entry *ce = ZEND_TYPE_CE_CACHE(*single_type);
1211+
1212+
// TODO Can this happen?
1213+
if (ce->ce_flags & ZEND_ACC_ANON_CLASS) {
1214+
zend_string *tmp = zend_string_init(ZSTR_VAL(ce->name), strlen(ZSTR_VAL(ce->name)), 0);
1215+
str = add_type_string(str, tmp, is_intersection);
1216+
} else {
1217+
str = add_type_string(str, ce->name, is_intersection);
1218+
}
1219+
} else {
1220+
zend_string *resolved = resolve_class_name(ZEND_TYPE_NAME(*single_type), scope);
1221+
str = add_type_string(str, resolved, is_intersection);
1222+
zend_string_release(resolved);
1223+
}
1224+
}
1225+
is_intersection = true;
1226+
} ZEND_TYPE_LIST_FOREACH_END();
1227+
1228+
return str;
1229+
}
1230+
11971231
zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) {
11981232
zend_string *str = NULL;
11991233

1200-
if (ZEND_TYPE_HAS_LIST(type)) {
1234+
/* Pure intersection type */
1235+
if (ZEND_TYPE_IS_INTERSECTION(type)) {
1236+
ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type));
1237+
str = resolve_intersection_type(str, ZEND_TYPE_LIST(type), scope);
1238+
}
1239+
1240+
if (ZEND_TYPE_IS_UNION(type)) {
12011241
zend_type *list_type;
1202-
bool is_intersection = ZEND_TYPE_IS_INTERSECTION(type);
12031242
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
1243+
/* Type within the union is an intersection */
1244+
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
1245+
str = resolve_intersection_type(str, ZEND_TYPE_LIST(*list_type), scope);
1246+
continue;
1247+
}
1248+
12041249
if (ZEND_TYPE_HAS_CE(*list_type)) {
1205-
str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, is_intersection);
1250+
str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, /* is_intersection */ false);
12061251
} else {
12071252
if (ZEND_TYPE_HAS_CE_CACHE(*list_type)
12081253
&& ZEND_TYPE_CE_CACHE(*list_type)) {
12091254
zend_class_entry *ce = ZEND_TYPE_CE_CACHE(*list_type);
12101255
if (ce->ce_flags & ZEND_ACC_ANON_CLASS) {
12111256
zend_string *tmp = zend_string_init(ZSTR_VAL(ce->name), strlen(ZSTR_VAL(ce->name)), 0);
1212-
str = add_type_string(str, tmp, is_intersection);
1257+
str = add_type_string(str, tmp, /* is_intersection */ false);
12131258
} else {
1214-
str = add_type_string(str, ce->name, is_intersection);
1259+
str = add_type_string(str, ce->name, /* is_intersection */ false);
12151260
}
12161261
} else {
12171262
zend_string *resolved = resolve_class_name(ZEND_TYPE_NAME(*list_type), scope);
1218-
str = add_type_string(str, resolved, is_intersection);
1263+
str = add_type_string(str, resolved, /* is_intersection */ false);
12191264
zend_string_release(resolved);
12201265
}
12211266
}
@@ -6232,8 +6277,31 @@ static zend_type zend_compile_typename(
62326277

62336278
for (uint32_t i = 0; i < list->children; i++) {
62346279
zend_ast *type_ast = list->child[i];
6235-
zend_type single_type = zend_compile_single_typename(type_ast);
6236-
uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type);
6280+
zend_type single_type;
6281+
uint32_t single_type_mask;
6282+
6283+
if (type_ast->kind == ZEND_AST_TYPE_INTERSECTION) {
6284+
/* The first class type can be stored directly as the type ptr payload. */
6285+
if (ZEND_TYPE_IS_COMPLEX(type) && !ZEND_TYPE_HAS_LIST(type)) {
6286+
/* Switch from single name to name list. */
6287+
type_list->num_types = 1;
6288+
type_list->types[0] = type;
6289+
/* Reset flags for first type */
6290+
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
6291+
ZEND_TYPE_SET_LIST(type, type_list);
6292+
}
6293+
6294+
single_type = zend_compile_typename(type_ast, false);
6295+
ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(single_type));
6296+
6297+
type_list->types[type_list->num_types++] = single_type;
6298+
6299+
/* TODO Check for trivially redundant class types? */
6300+
continue;
6301+
}
6302+
6303+
single_type = zend_compile_single_typename(type_ast);
6304+
single_type_mask = ZEND_TYPE_PURE_MASK(single_type);
62376305

62386306
if (single_type_mask == MAY_BE_ANY) {
62396307
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type");
@@ -6267,6 +6335,9 @@ static zend_type zend_compile_typename(
62676335

62686336
/* Check for trivially redundant class types */
62696337
for (size_t i = 0; i < type_list->num_types - 1; i++) {
6338+
if (ZEND_TYPE_IS_INTERSECTION(type_list->types[i])) {
6339+
continue;
6340+
}
62706341
if (zend_string_equals_ci(
62716342
ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(single_type))) {
62726343
zend_string *single_type_str = zend_type_to_string(single_type);

0 commit comments

Comments
 (0)