Skip to content

Commit b45f962

Browse files
committed
Disjoin union types
Haven't done variance yet
1 parent a70db6b commit b45f962

File tree

5 files changed

+326
-18
lines changed

5 files changed

+326
-18
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
--TEST--
2+
Union of two intersection type
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
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
--TEST--
2+
Union of a simple and intersection type
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+
class Test {
14+
public (X&Y)|int $prop1;
15+
public int|(X&Y) $prop2;
16+
public (X&Y)|B $prop3;
17+
public B|(X&Y) $prop4;
18+
19+
public function foo1((X&Y)|int $v): (X&Y)|int {
20+
var_dump($v);
21+
return $v;
22+
}
23+
public function foo2(int|(X&Y) $v): int|(X&Y) {
24+
var_dump($v);
25+
return $v;
26+
}
27+
public function bar1(B|(X&Y) $v): B|(X&Y) {
28+
var_dump($v);
29+
return $v;
30+
}
31+
public function bar2((X&Y)|B $v): (X&Y)|B {
32+
var_dump($v);
33+
return $v;
34+
}
35+
}
36+
37+
$test = new Test();
38+
$a = new A();
39+
$b = new B();
40+
$i = 10;
41+
42+
$test->foo1($a);
43+
$test->foo2($a);
44+
$test->foo1($i);
45+
$test->foo2($i);
46+
$test->prop1 = $a;
47+
$test->prop1 = $i;
48+
$test->prop2 = $a;
49+
$test->prop2 = $i;
50+
51+
$test->bar1($a);
52+
$test->bar2($a);
53+
$test->bar1($b);
54+
$test->bar2($b);
55+
$test->prop3 = $a;
56+
$test->prop4 = $b;
57+
$test->prop3 = $a;
58+
$test->prop4 = $b;
59+
60+
$c = new C();
61+
try {
62+
$test->foo1($c);
63+
} catch (\TypeError $e) {
64+
echo $e->getMessage(), \PHP_EOL;
65+
}
66+
try {
67+
$test->foo2($c);
68+
} catch (\TypeError $e) {
69+
echo $e->getMessage(), \PHP_EOL;
70+
}
71+
try {
72+
$test->bar1($c);
73+
} catch (\TypeError $e) {
74+
echo $e->getMessage(), \PHP_EOL;
75+
}
76+
try {
77+
$test->bar2($c);
78+
} catch (\TypeError $e) {
79+
echo $e->getMessage(), \PHP_EOL;
80+
}
81+
try {
82+
$test->prop1 = $c;
83+
} catch (\TypeError $e) {
84+
echo $e->getMessage(), \PHP_EOL;
85+
}
86+
try {
87+
$test->prop2 = $c;
88+
} catch (\TypeError $e) {
89+
echo $e->getMessage(), \PHP_EOL;
90+
}
91+
try {
92+
$test->prop3 = $c;
93+
} catch (\TypeError $e) {
94+
echo $e->getMessage(), \PHP_EOL;
95+
}
96+
try {
97+
$test->prop4 = $c;
98+
} catch (\TypeError $e) {
99+
echo $e->getMessage(), \PHP_EOL;
100+
}
101+
102+
?>
103+
===DONE===
104+
--EXPECTF--
105+
object(A)#2 (0) {
106+
}
107+
object(A)#2 (0) {
108+
}
109+
int(10)
110+
int(10)
111+
object(A)#2 (0) {
112+
}
113+
object(A)#2 (0) {
114+
}
115+
object(B)#3 (0) {
116+
}
117+
object(B)#3 (0) {
118+
}
119+
Test::foo1(): Argument #1 ($v) must be of type (X&Y)|int, C given, called in %s on line %d
120+
Test::foo2(): Argument #1 ($v) must be of type (X&Y)|int, C given, called in %s on line %d
121+
Test::bar1(): Argument #1 ($v) must be of type B|(X&Y), C given, called in %s on line %d
122+
Test::bar2(): Argument #1 ($v) must be of type (X&Y)|B, C given, called in %s on line %d
123+
Cannot assign C to property Test::$prop1 of type (X&Y)|int
124+
Cannot assign C to property Test::$prop2 of type (X&Y)|int
125+
Cannot assign C to property Test::$prop3 of type (X&Y)|B
126+
Cannot assign C to property Test::$prop4 of type B|(X&Y)
127+
===DONE===

Zend/zend_compile.c

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,16 +1195,47 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop
11951195
return zend_string_copy(name);
11961196
}
11971197

1198+
static zend_string *add_intersection_type(zend_string *str,
1199+
zend_type_list *intersection_type_list, zend_class_entry *scope,
1200+
bool is_bracketed)
1201+
{
1202+
zend_type *single_type;
1203+
zend_string *intersection_str = NULL;
1204+
1205+
ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) {
1206+
zend_string *name = ZEND_TYPE_NAME(*single_type);
1207+
zend_string *resolved = resolve_class_name(name, scope);
1208+
intersection_str = add_type_string(intersection_str, resolved, /* is_intersection */ true);
1209+
zend_string_release(resolved);
1210+
} ZEND_TYPE_LIST_FOREACH_END();
1211+
1212+
if (is_bracketed) {
1213+
zend_string *result = zend_string_concat3("(", 1, ZSTR_VAL(intersection_str), ZSTR_LEN(intersection_str), ")", 1);
1214+
zend_string_release(intersection_str);
1215+
intersection_str = result;
1216+
}
1217+
str = add_type_string(str, intersection_str, /* is_intersection */ false);
1218+
zend_string_release(intersection_str);
1219+
return str;
1220+
}
1221+
11981222
zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) {
11991223
zend_string *str = NULL;
12001224

1201-
if (ZEND_TYPE_HAS_LIST(type)) {
1225+
/* Pure intersection type */
1226+
if (ZEND_TYPE_IS_INTERSECTION(type)) {
1227+
ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type));
1228+
str = add_intersection_type(str, ZEND_TYPE_LIST(type), scope, /* is_bracketed */ false);
1229+
} else if (ZEND_TYPE_IS_UNION(type)) {
12021230
zend_type *list_type;
1203-
bool is_intersection = ZEND_TYPE_IS_INTERSECTION(type);
12041231
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
1232+
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
1233+
str = add_intersection_type(str, ZEND_TYPE_LIST(*list_type), scope, /* is_bracketed */ true);
1234+
continue;
1235+
}
12051236
zend_string *name = ZEND_TYPE_NAME(*list_type);
12061237
zend_string *resolved = resolve_class_name(name, scope);
1207-
str = add_type_string(str, resolved, is_intersection);
1238+
str = add_type_string(str, resolved, /* is_intersection */ false);
12081239
zend_string_release(resolved);
12091240
} ZEND_TYPE_LIST_FOREACH_END();
12101241
} else if (ZEND_TYPE_HAS_NAME(type)) {
@@ -6234,14 +6265,38 @@ static zend_type zend_compile_typename(
62346265
if (ast->kind == ZEND_AST_TYPE_UNION) {
62356266
zend_ast_list *list = zend_ast_get_list(ast);
62366267
zend_type_list *type_list;
6268+
bool is_composite = false;
62376269
ALLOCA_FLAG(use_heap)
62386270

62396271
type_list = do_alloca(ZEND_TYPE_LIST_SIZE(list->children), use_heap);
62406272
type_list->num_types = 0;
62416273

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

62476302
if (single_type_mask == MAY_BE_ANY) {
@@ -6259,7 +6314,7 @@ static zend_type zend_compile_typename(
62596314
ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK;
62606315

62616316
if (ZEND_TYPE_IS_COMPLEX(single_type)) {
6262-
if (!ZEND_TYPE_IS_COMPLEX(type)) {
6317+
if (!ZEND_TYPE_IS_COMPLEX(type) && !is_composite) {
62636318
/* The first class type can be stored directly as the type ptr payload. */
62646319
ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type));
62656320
ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT;
@@ -6276,6 +6331,9 @@ static zend_type zend_compile_typename(
62766331

62776332
/* Check for trivially redundant class types */
62786333
for (size_t i = 0; i < type_list->num_types - 1; i++) {
6334+
if (ZEND_TYPE_IS_INTERSECTION(type_list->types[i])) {
6335+
continue;
6336+
}
62796337
if (zend_string_equals_ci(
62806338
ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(single_type))) {
62816339
zend_string *single_type_str = zend_type_to_string(single_type);

Zend/zend_execute.c

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -873,20 +873,38 @@ static zend_always_inline zend_class_entry *zend_ce_from_type(
873873
return resolve_single_class_type(name, info->ce);
874874
}
875875

876+
static bool zend_check_intersection_for_property_class_type(zend_type_list *intersection_type_list,
877+
zend_property_info *info, zend_class_entry *object_ce)
878+
{
879+
zend_type *list_type;
880+
881+
ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) {
882+
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
883+
zend_class_entry *ce = zend_ce_from_type(info, list_type);
884+
if (!ce || !instanceof_function(object_ce, ce)) {
885+
return false;
886+
}
887+
} ZEND_TYPE_LIST_FOREACH_END();
888+
return true;
889+
}
890+
876891
static bool zend_check_and_resolve_property_class_type(
877892
zend_property_info *info, zend_class_entry *object_ce) {
878893
if (ZEND_TYPE_HAS_LIST(info->type)) {
879894
zend_type *list_type;
880895
if (ZEND_TYPE_IS_INTERSECTION(info->type)) {
881-
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) {
882-
zend_class_entry *ce = zend_ce_from_type(info, list_type);
883-
if (!ce || !instanceof_function(object_ce, ce)) {
884-
return false;
885-
}
886-
} ZEND_TYPE_LIST_FOREACH_END();
887-
return true;
896+
return zend_check_intersection_for_property_class_type(
897+
ZEND_TYPE_LIST(info->type), info, object_ce);
888898
} else {
889899
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) {
900+
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
901+
if (zend_check_intersection_for_property_class_type(
902+
ZEND_TYPE_LIST(*list_type), info, object_ce)) {
903+
return true;
904+
}
905+
continue;
906+
}
907+
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
890908
zend_class_entry *ce = zend_ce_from_type(info, list_type);
891909
if (ce && instanceof_function(object_ce, ce)) {
892910
return true;
@@ -1002,6 +1020,22 @@ static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot(
10021020
return ce;
10031021
}
10041022

1023+
static bool zend_check_intersection_type_from_cache_slot(zend_type_list *intersection_type_list,
1024+
zend_class_entry *arg_ce, void **cache_slot)
1025+
{
1026+
zend_class_entry *ce;
1027+
zend_type *list_type;
1028+
ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) {
1029+
ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
1030+
/* If type is not an instance of one of the types taking part in the
1031+
* intersection it cannot be a valid instance of the whole intersection type. */
1032+
if (!ce || !instanceof_function(arg_ce, ce)) {
1033+
return false;
1034+
}
1035+
} ZEND_TYPE_LIST_FOREACH_END();
1036+
return true;
1037+
}
1038+
10051039
static zend_always_inline bool zend_check_type_slow(
10061040
zend_type *type, zval *arg, zend_reference *ref, void **cache_slot,
10071041
bool is_return_type, bool is_internal)
@@ -1026,11 +1060,19 @@ static zend_always_inline bool zend_check_type_slow(
10261060
return true;
10271061
} else {
10281062
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) {
1029-
ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
1030-
/* Instance of a single type part of a union is sufficient to pass the type check */
1031-
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
1032-
return true;
1063+
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
1064+
if (zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg), cache_slot)) {
1065+
return true;
1066+
}
1067+
} else {
1068+
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
1069+
ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
1070+
/* Instance of a single type part of a union is sufficient to pass the type check */
1071+
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
1072+
return true;
1073+
}
10331074
}
1075+
10341076
if (HAVE_CACHE_SLOT) {
10351077
cache_slot++;
10361078
}

0 commit comments

Comments
 (0)