Skip to content

Commit e4bd466

Browse files
committed
Start implementing support for mixing union and intersection types
Parameter composite types are currently broken
1 parent 29637ec commit e4bd466

File tree

5 files changed

+353
-77
lines changed

5 files changed

+353
-77
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
Basic tests for a complex type with an intersection type and union
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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
Basic tests for a complex type with an intersection type and union
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 {
16+
return new A();
17+
}
18+
function foo2(): W&Z|X&Y {
19+
return new A();
20+
}
21+
function foo3(): X&Y|W&Z {
22+
return new B();
23+
}
24+
function foo4(): W&Z|X&Y {
25+
return new B();
26+
}
27+
28+
function bar1(): X&Y|W&Z {
29+
return new C();
30+
}
31+
function bar2(): W&Z|X&Y {
32+
return new C();
33+
}
34+
35+
$o = foo1();
36+
var_dump($o);
37+
$o = foo2();
38+
var_dump($o);
39+
$o = foo3();
40+
var_dump($o);
41+
$o = foo4();
42+
var_dump($o);
43+
44+
try {
45+
bar1();
46+
} catch (\TypeError $e) {
47+
echo $e->getMessage(), \PHP_EOL;
48+
}
49+
try {
50+
bar2();
51+
} catch (\TypeError $e) {
52+
echo $e->getMessage(), \PHP_EOL;
53+
}
54+
55+
?>
56+
--EXPECTF--
57+
object(A)#%d (0) {
58+
}
59+
object(A)#%d (0) {
60+
}
61+
object(B)#%d (0) {
62+
}
63+
object(B)#%d (0) {
64+
}
65+
bar1(): Return value must be of type X&Y|W&Z, C returned
66+
bar2(): Return value must be of type W&Z|X&Y, C returned

Zend/zend_compile.c

Lines changed: 74 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,29 @@ 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+
if (type_list->num_types == 0) {
6285+
/* The first class type can be stored directly as the type ptr payload. */
6286+
if (ZEND_TYPE_IS_COMPLEX(type)) {
6287+
/* Switch from single name to name list. */
6288+
type_list->num_types = 1;
6289+
type_list->types[0] = type;
6290+
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
6291+
}
6292+
/* TODO Check for trivially redundant class types? */
6293+
ZEND_TYPE_SET_LIST(type, type_list);
6294+
}
6295+
6296+
single_type = zend_compile_typename(type_ast, false);
6297+
type_list->types[type_list->num_types++] = single_type;
6298+
continue;
6299+
}
6300+
6301+
single_type = zend_compile_single_typename(type_ast);
6302+
single_type_mask = ZEND_TYPE_PURE_MASK(single_type);
62376303

62386304
if (single_type_mask == MAY_BE_ANY) {
62396305
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type");

0 commit comments

Comments
 (0)