Skip to content

Commit 8fe285e

Browse files
committed
Start implementing support for mixing union and intersection types
Parameter composite types are currently broken for A&B|C where C is a class
1 parent 8a0d29a commit 8fe285e

File tree

5 files changed

+393
-134
lines changed

5 files changed

+393
-134
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); // This currently fails
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: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,15 +1194,62 @@ 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+
zend_string *name = ZEND_TYPE_NAME(*single_type);
1209+
1210+
if (ZSTR_HAS_CE_CACHE(name)
1211+
&& ZSTR_GET_CE_CACHE(name)) {
1212+
zend_class_entry *ce = ZSTR_GET_CE_CACHE(name);
1213+
/* Can this happen? */
1214+
if (ce->ce_flags & ZEND_ACC_ANON_CLASS) {
1215+
zend_string *tmp = zend_string_init(ZSTR_VAL(ce->name), strlen(ZSTR_VAL(ce->name)), 0);
1216+
str = add_type_string(str, tmp, is_intersection);
1217+
} else {
1218+
str = add_type_string(str, ce->name, is_intersection);
1219+
}
1220+
} else {
1221+
zend_string *resolved = resolve_class_name(name, scope);
1222+
str = add_type_string(str, resolved, is_intersection);
1223+
zend_string_release(resolved);
1224+
}
1225+
}
1226+
/* Following types are part of the intersection */
1227+
is_intersection = true;
1228+
} ZEND_TYPE_LIST_FOREACH_END();
1229+
1230+
return str;
1231+
}
1232+
11971233
zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) {
11981234
zend_string *str = NULL;
11991235

1200-
if (ZEND_TYPE_HAS_LIST(type)) {
1236+
/* Pure intersection type */
1237+
if (ZEND_TYPE_IS_INTERSECTION(type)) {
1238+
ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type));
1239+
str = resolve_intersection_type(str, ZEND_TYPE_LIST(type), scope);
1240+
}
1241+
1242+
if (ZEND_TYPE_IS_UNION(type)) {
12011243
zend_type *list_type;
1202-
bool is_intersection = ZEND_TYPE_IS_INTERSECTION(type);
12031244
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
1245+
/* Type within the union is an intersection */
1246+
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
1247+
str = resolve_intersection_type(str, ZEND_TYPE_LIST(*list_type), scope);
1248+
continue;
1249+
}
1250+
12041251
if (ZEND_TYPE_HAS_CE(*list_type)) {
1205-
str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, is_intersection);
1252+
str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, /* is_intersection */ false);
12061253
} else {
12071254
zend_string *name = ZEND_TYPE_NAME(*list_type);
12081255

@@ -1211,13 +1258,13 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
12111258
zend_class_entry *ce = ZSTR_GET_CE_CACHE(name);
12121259
if (ce->ce_flags & ZEND_ACC_ANON_CLASS) {
12131260
zend_string *tmp = zend_string_init(ZSTR_VAL(ce->name), strlen(ZSTR_VAL(ce->name)), 0);
1214-
str = add_type_string(str, tmp, is_intersection);
1261+
str = add_type_string(str, tmp, /* is_intersection */ false);
12151262
} else {
1216-
str = add_type_string(str, ce->name, is_intersection);
1263+
str = add_type_string(str, ce->name, /* is_intersection */ false);
12171264
}
12181265
} else {
12191266
zend_string *resolved = resolve_class_name(name, scope);
1220-
str = add_type_string(str, resolved, is_intersection);
1267+
str = add_type_string(str, resolved, /* is_intersection */ false);
12211268
zend_string_release(resolved);
12221269
}
12231270
}
@@ -6236,8 +6283,31 @@ static zend_type zend_compile_typename(
62366283

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

62426312
if (single_type_mask == MAY_BE_ANY) {
62436313
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type");
@@ -6271,6 +6341,9 @@ static zend_type zend_compile_typename(
62716341

62726342
/* Check for trivially redundant class types */
62736343
for (size_t i = 0; i < type_list->num_types - 1; i++) {
6344+
if (ZEND_TYPE_IS_INTERSECTION(type_list->types[i])) {
6345+
continue;
6346+
}
62746347
if (zend_string_equals_ci(
62756348
ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(single_type))) {
62766349
zend_string *single_type_str = zend_type_to_string(single_type);

0 commit comments

Comments
 (0)