Skip to content

Commit 95446c7

Browse files
committed
Handle iterable in complex union types
1 parent 3444bd8 commit 95446c7

File tree

5 files changed

+28
-41
lines changed

5 files changed

+28
-41
lines changed

Zend/tests/typehints/or_null.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ TypeError: callableF(): Argument #1 ($param) must be of type ?callable, int give
247247
Stack trace:
248248
#0 %s(52): callableF(1)
249249
#1 {main}
250-
TypeError: iterableF(): Argument #1 ($param) must be of type Traversable|array|null, int given, called in %s:%d
250+
TypeError: iterableF(): Argument #1 ($param) must be of type ?iterable, int given, called in %s:%d
251251
Stack trace:
252252
#0 %s(60): iterableF(1)
253253
#1 {main}
@@ -283,7 +283,7 @@ TypeError: returnCallable(): Return value must be of type ?callable, int returne
283283
Stack trace:
284284
#0 %s(138): returnCallable()
285285
#1 {main}
286-
TypeError: returnIterable(): Return value must be of type Traversable|array|null, int returned in %s:%d
286+
TypeError: returnIterable(): Return value must be of type ?iterable, int returned in %s:%d
287287
Stack trace:
288288
#0 %s(148): returnIterable()
289289
#1 {main}
@@ -307,7 +307,7 @@ TypeError: returnMissingCallable(): Return value must be of type ?callable, none
307307
Stack trace:
308308
#0 %s(194): returnMissingCallable()
309309
#1 {main}
310-
TypeError: returnMissingIterable(): Return value must be of type Traversable|array|null, none returned in %s:%d
310+
TypeError: returnMissingIterable(): Return value must be of type ?iterable, none returned in %s:%d
311311
Stack trace:
312312
#0 %s(203): returnMissingIterable()
313313
#1 {main}

Zend/zend_compile.c

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,41 +1189,40 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop
11891189

11901190
zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) {
11911191
zend_string *str = NULL;
1192+
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
1193+
bool has_name = ZEND_TYPE_HAS_NAME(type);
11921194

1193-
// TODO also handle case when iterable is in a union type?
1194-
// This would allow to remove the zend_type_to_string_ex() shim in Reflection
1195-
/* BC for iterable type */
1196-
if (UNEXPECTED(ZEND_TYPE_IS_ITERABLE_FALLBACK(type)) && ZEND_TYPE_HAS_NAME(type)) {
1197-
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
1198-
if (type_mask != MAY_BE_ARRAY && type_mask != MAY_BE_ARRAY|MAY_BE_NULL) {
1199-
goto standard_resolve;
1200-
}
1195+
/* BC for iterable type as a single type */
1196+
if (UNEXPECTED(ZEND_TYPE_IS_ITERABLE_FALLBACK(type))) {
12011197
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE), /* is_intersection */ false);
1202-
1203-
if (type_mask == MAY_BE_NULL) {
1204-
zend_string *nullable_str = zend_string_concat2("?", 1, ZSTR_VAL(str), ZSTR_LEN(str));
1205-
zend_string_release(str);
1206-
return nullable_str;
1198+
/* Remove array bit */
1199+
type_mask &= ~MAY_BE_ARRAY;
1200+
/* If it has a type name it must be Traversable, ignore it */
1201+
if (has_name) {
1202+
has_name = false;
12071203
}
1208-
return str;
12091204
}
12101205

1211-
standard_resolve:
12121206
if (ZEND_TYPE_HAS_LIST(type)) {
12131207
zend_type *list_type;
12141208
bool is_intersection = ZEND_TYPE_IS_INTERSECTION(type);
12151209
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
12161210
zend_string *name = ZEND_TYPE_NAME(*list_type);
1211+
/* BC for iterable type in a union type */
1212+
if (UNEXPECTED(ZEND_TYPE_IS_ITERABLE_FALLBACK(*list_type) && zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_TRAVERSABLE)))) {
1213+
/* Remove array bit */
1214+
type_mask &= ~MAY_BE_ARRAY;
1215+
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE), /* is_intersection */ false);
1216+
continue;
1217+
}
12171218
zend_string *resolved = resolve_class_name(name, scope);
12181219
str = add_type_string(str, resolved, is_intersection);
12191220
zend_string_release(resolved);
12201221
} ZEND_TYPE_LIST_FOREACH_END();
1221-
} else if (ZEND_TYPE_HAS_NAME(type)) {
1222+
} else if (has_name) {
12221223
str = resolve_class_name(ZEND_TYPE_NAME(type), scope);
12231224
}
12241225

1225-
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
1226-
12271226
if (type_mask == MAY_BE_ANY) {
12281227
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_MIXED), /* is_intersection */ false);
12291228

@@ -6155,7 +6154,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
61556154
zend_type iterable = (zend_type) ZEND_TYPE_INIT_CLASS(ZSTR_KNOWN(ZEND_STR_TRAVERSABLE), 0, 0);
61566155
ZEND_TYPE_FULL_MASK(iterable) |= _ZEND_TYPE_NAME_BIT;
61576156
ZEND_TYPE_FULL_MASK(iterable) |= MAY_BE_ARRAY;
6158-
/* Set iterable bit for BC compat during Reflection */
6157+
/* Set iterable bit for BC compat during Reflection and string representation of type */
61596158
ZEND_TYPE_FULL_MASK(iterable) |= _ZEND_TYPE_ITERABLE_BIT;
61606159
/* Inform that the type list is a union type */
61616160
ZEND_TYPE_FULL_MASK(iterable) |= _ZEND_TYPE_UNION_BIT;

ext/reflection/php_reflection.c

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2993,17 +2993,9 @@ ZEND_METHOD(ReflectionType, allowsNull)
29932993
}
29942994
/* }}} */
29952995

2996-
/* BC for iterable */
2997-
static zend_string *zend_type_to_string_ex(zend_type type) {
2998-
if (UNEXPECTED(ZEND_TYPE_IS_ITERABLE_FALLBACK(type))) {
2999-
return ZSTR_KNOWN(ZEND_STR_ITERABLE);
3000-
}
3001-
return zend_type_to_string(type);
3002-
}
3003-
30042996
static zend_string *zend_type_to_string_without_null(zend_type type) {
30052997
ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL;
3006-
return zend_type_to_string_ex(type);
2998+
return zend_type_to_string(type);
30072999
}
30083000

30093001
/* {{{ Return the text of the type hint */
@@ -3017,7 +3009,7 @@ ZEND_METHOD(ReflectionType, __toString)
30173009
}
30183010
GET_REFLECTION_OBJECT_PTR(param);
30193011

3020-
RETURN_STR(zend_type_to_string_ex(param->type));
3012+
RETURN_STR(zend_type_to_string(param->type));
30213013
}
30223014
/* }}} */
30233015

@@ -3035,7 +3027,7 @@ ZEND_METHOD(ReflectionNamedType, getName)
30353027
if (param->legacy_behavior) {
30363028
RETURN_STR(zend_type_to_string_without_null(param->type));
30373029
}
3038-
RETURN_STR(zend_type_to_string_ex(param->type));
3030+
RETURN_STR(zend_type_to_string(param->type));
30393031
}
30403032
/* }}} */
30413033

@@ -3083,6 +3075,7 @@ ZEND_METHOD(ReflectionUnionType, getTypes)
30833075
if (ZEND_TYPE_HAS_LIST(param->type)) {
30843076
zend_type *list_type;
30853077
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), list_type) {
3078+
/* BC for iterable type */
30863079
if (UNEXPECTED(ZEND_TYPE_IS_ITERABLE_FALLBACK(*list_type))) {
30873080
has_iterable = true;
30883081
}

ext/reflection/tests/ReflectionType_001.phpt

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,8 @@ $reflector = new ReflectionClass(PropTypeTest::class);
9090

9191
foreach ($reflector->getProperties() as $name => $property) {
9292
if ($property->hasType()) {
93-
$type = $property->getType();
94-
if ($type instanceof ReflectionNamedType) {
95-
printf("public %s $%s;\n", $type->getName(), $property->getName());
96-
} else {
97-
echo 'public ', implode('|', $type->getTypes()),
98-
' $', $property->getName(), ";\n";
99-
}
93+
printf("public %s $%s;\n",
94+
$property->getType()->getName(), $property->getName());
10095
} else printf("public $%s;\n", $property->getName());
10196
}
10297

ext/reflection/tests/union_types.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ Allows null: true
7575
Name: null
7676
String: null
7777
Allows Null: true
78-
Type X|Traversable|array|bool:
78+
Type X|iterable|bool:
7979
Allows null: false
8080
Name: X
8181
String: X

0 commit comments

Comments
 (0)