Skip to content

Commit 05ab9ac

Browse files
committed
Start implementing support for mixing union and intersection types
1 parent 9d65106 commit 05ab9ac

File tree

5 files changed

+339
-81
lines changed

5 files changed

+339
-81
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
class C {}
12+
13+
function foo1(): X&Y|array {
14+
return new A();
15+
}
16+
function foo2(): array|X&Y {
17+
return new A();
18+
}
19+
function foo3(): X&Y|array {
20+
return [];
21+
}
22+
function foo4(): array|X&Y {
23+
return [];
24+
}
25+
function bar1(): B|X&Y {
26+
return new A();
27+
}
28+
function bar2(): X&Y|B {
29+
return new A();
30+
}
31+
function bar3(): B|X&Y {
32+
return new B();
33+
}
34+
function bar4(): X&Y|C|B {
35+
return new B();
36+
}
37+
38+
$o = foo1();
39+
var_dump($o);
40+
$o = foo2();
41+
var_dump($o);
42+
$o = foo3();
43+
var_dump($o);
44+
$o = foo4();
45+
var_dump($o);
46+
$o = bar1();
47+
var_dump($o);
48+
$o = bar2();
49+
var_dump($o);
50+
$o = bar3();
51+
var_dump($o);
52+
$o = bar4();
53+
var_dump($o);
54+
55+
?>
56+
--EXPECTF--
57+
object(A)#%d (0) {
58+
}
59+
object(A)#%d (0) {
60+
}
61+
array(0) {
62+
}
63+
array(0) {
64+
}
65+
object(A)#%d (0) {
66+
}
67+
object(A)#%d (0) {
68+
}
69+
object(B)#%d (0) {
70+
}
71+
object(B)#%d (0) {
72+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
$o = foo1();
29+
var_dump($o);
30+
$o = foo2();
31+
var_dump($o);
32+
$o = foo3();
33+
var_dump($o);
34+
$o = foo4();
35+
var_dump($o);
36+
37+
?>
38+
--EXPECTF--
39+
object(A)#%d (0) {
40+
}
41+
object(A)#%d (0) {
42+
}
43+
array(0) {
44+
}
45+
array(0) {
46+
}
47+
object(A)#%d (0) {
48+
}
49+
object(A)#%d (0) {
50+
}
51+
object(B)#%d (0) {
52+
}
53+
object(B)#%d (0) {
54+
}

Zend/zend_compile.c

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,28 +1194,69 @@ 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_type_list *intersection_type_list, zend_class_entry *scope)
1198+
{
1199+
zend_type *single_type;
1200+
zend_string *str = NULL;
1201+
1202+
ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) {
1203+
if (ZEND_TYPE_HAS_CE(*single_type)) {
1204+
str = add_type_string(str, ZEND_TYPE_CE(*single_type)->name, /* is_intersection */ true);
1205+
} else {
1206+
if (ZEND_TYPE_HAS_CE_CACHE(*single_type)
1207+
&& ZEND_TYPE_CE_CACHE(*single_type)) {
1208+
zend_class_entry *ce = ZEND_TYPE_CE_CACHE(*single_type);
1209+
1210+
// TODO Can this happen?
1211+
if (ce->ce_flags & ZEND_ACC_ANON_CLASS) {
1212+
zend_string *tmp = zend_string_init(ZSTR_VAL(ce->name), strlen(ZSTR_VAL(ce->name)), 0);
1213+
str = add_type_string(str, tmp, /* is_intersection */ true);
1214+
} else {
1215+
str = add_type_string(str, ce->name, /* is_intersection */ true);
1216+
}
1217+
} else {
1218+
zend_string *resolved = resolve_class_name(ZEND_TYPE_NAME(*single_type), scope);
1219+
str = add_type_string(str, resolved, /* is_intersection */ true);
1220+
zend_string_release(resolved);
1221+
}
1222+
}
1223+
} ZEND_TYPE_LIST_FOREACH_END();
1224+
1225+
return str;
1226+
}
1227+
11971228
zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) {
11981229
zend_string *str = NULL;
11991230

1200-
if (ZEND_TYPE_HAS_LIST(type)) {
1231+
/* Pure intersection type */
1232+
if (ZEND_TYPE_HAS_INTERSECTION(type)) {
1233+
str = resolve_intersection_type(ZEND_TYPE_LIST(type), scope);
1234+
}
1235+
1236+
if (ZEND_TYPE_HAS_UNION(type)) {
12011237
zend_type *list_type;
1202-
bool is_intersection = ZEND_TYPE_HAS_INTERSECTION(type);
12031238
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
1239+
/* Type within the union is an intersection */
1240+
if (ZEND_TYPE_HAS_INTERSECTION(*list_type)) {
1241+
str = resolve_intersection_type(ZEND_TYPE_LIST(*list_type), scope);
1242+
continue;
1243+
}
1244+
12041245
if (ZEND_TYPE_HAS_CE(*list_type)) {
1205-
str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, is_intersection);
1246+
str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, /* is_intersection */ false);
12061247
} else {
12071248
if (ZEND_TYPE_HAS_CE_CACHE(*list_type)
12081249
&& ZEND_TYPE_CE_CACHE(*list_type)) {
12091250
zend_class_entry *ce = ZEND_TYPE_CE_CACHE(*list_type);
12101251
if (ce->ce_flags & ZEND_ACC_ANON_CLASS) {
12111252
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);
1253+
str = add_type_string(str, tmp, /* is_intersection */ false);
12131254
} else {
1214-
str = add_type_string(str, ce->name, is_intersection);
1255+
str = add_type_string(str, ce->name, /* is_intersection */ false);
12151256
}
12161257
} else {
12171258
zend_string *resolved = resolve_class_name(ZEND_TYPE_NAME(*list_type), scope);
1218-
str = add_type_string(str, resolved, is_intersection);
1259+
str = add_type_string(str, resolved, /* is_intersection */ false);
12191260
zend_string_release(resolved);
12201261
}
12211262
}
@@ -6232,8 +6273,17 @@ static zend_type zend_compile_typename(
62326273

62336274
for (uint32_t i = 0; i < list->children; i++) {
62346275
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);
6276+
zend_type single_type;
6277+
uint32_t single_type_mask;
6278+
6279+
if (type_ast->kind == ZEND_AST_TYPE_INTERSECTION) {
6280+
single_type = zend_compile_typename(type_ast, false);
6281+
type_list->types[type_list->num_types++] = single_type;
6282+
continue;
6283+
}
6284+
6285+
single_type = zend_compile_single_typename(type_ast);
6286+
single_type_mask = ZEND_TYPE_PURE_MASK(single_type);
62376287

62386288
if (single_type_mask == MAY_BE_ANY) {
62396289
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type");
@@ -6320,13 +6370,12 @@ static zend_type zend_compile_typename(
63206370
}
63216371
zend_string_release_ex(standard_type_str, false);
63226372

6323-
/* Inform that the type is part of an intersection type */
6324-
ZEND_TYPE_FULL_MASK(single_type) |= _ZEND_TYPE_INTERSECTION_BIT;
6325-
63266373
if (!ZEND_TYPE_HAS_CLASS(type)) {
63276374
/* The first class type can be stored directly as the type ptr payload. */
63286375
ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type));
63296376
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT;
6377+
/* Inform that the type list is an intersection type */
6378+
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT;
63306379
} else {
63316380
if (type_list->num_types == 0) {
63326381
/* Switch from single name to name list. */
@@ -6356,8 +6405,6 @@ static zend_type zend_compile_typename(
63566405
memcpy(list, type_list, ZEND_TYPE_LIST_SIZE(type_list->num_types));
63576406
ZEND_TYPE_SET_LIST(type, list);
63586407
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT;
6359-
/* Inform that the type list contains an intersection type */
6360-
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT;
63616408
}
63626409

63636410
free_alloca(type_list, use_heap);

0 commit comments

Comments
 (0)