Skip to content

Commit 7f3a6dd

Browse files
committed
Convert iterable into an internal alias for Traversable|array
1 parent ff8e04a commit 7f3a6dd

19 files changed

+50
-98
lines changed

Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ function foo(): iterable&Iterator {}
77

88
?>
99
--EXPECTF--
10-
Fatal error: Type iterable cannot be part of an intersection type in %s on line %d
10+
Fatal error: Type Traversable|array cannot be part of an intersection type in %s on line %d

Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ class Test2 extends Test {
1515

1616
?>
1717
--EXPECTF--
18-
Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): iterable in %s on line %d
18+
Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): Traversable|array in %s on line %d

Zend/tests/type_declarations/iterable_001.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ object(ArrayIterator)#1 (1) {
4545
int(3)
4646
}
4747
}
48-
test(): Argument #1 ($iterable) must be of type iterable, int given, called in %s on line %d
48+
test(): Argument #1 ($iterable) must be of type Traversable|array, int given, called in %s on line %d

Zend/tests/type_declarations/iterable_002.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ function baz(iterable $iterable = 1) {
1717

1818
?>
1919
--EXPECTF--
20-
Fatal error: Cannot use int as default value for parameter $iterable of type iterable in %s on line %d
20+
Fatal error: Cannot use int as default value for parameter $iterable of type Traversable|array in %s on line %d

Zend/tests/type_declarations/iterable_003.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ array(0) {
2929
}
3030
object(Generator)#2 (0) {
3131
}
32-
baz(): Return value must be of type iterable, int returned
32+
baz(): Return value must be of type Traversable|array, int returned

Zend/tests/type_declarations/iterable_004.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ class Bar extends Foo {
2121

2222
?>
2323
--EXPECTF--
24-
Fatal error: Declaration of Bar::testScalar(iterable $iterable) must be compatible with Foo::testScalar(int $int) in %s on line %d
24+
Fatal error: Declaration of Bar::testScalar(Traversable|array $iterable) must be compatible with Foo::testScalar(int $int) in %s on line %d

Zend/tests/type_declarations/iterable_005.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ class TestScalar extends Test {
2929

3030
?>
3131
--EXPECTF--
32-
Fatal error: Declaration of TestScalar::method(): int must be compatible with Test::method(): iterable in %s on line %d
32+
Fatal error: Declaration of TestScalar::method(): int must be compatible with Test::method(): Traversable|array in %s on line %d

Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ function test(): iterable|Traversable {
88

99
?>
1010
--EXPECTF--
11-
Fatal error: Type Traversable|iterable contains both iterable and Traversable, which is redundant in %s on line %d
11+
Fatal error: Duplicate type Traversable is redundant in %s on line %d

Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ function test(): iterable|Traversable|ArrayAccess {
88

99
?>
1010
--EXPECTF--
11-
Fatal error: Type Traversable|ArrayAccess|iterable contains both iterable and Traversable, which is redundant in %s on line %d
11+
Fatal error: Duplicate type Traversable is redundant in %s on line %d

Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ function test(): iterable|array {
88

99
?>
1010
--EXPECTF--
11-
Fatal error: Type iterable|array contains both iterable and array, which is redundant in %s on line %d
11+
Fatal error: Duplicate type array is redundant in %s on line %d

Zend/zend_compile.c

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,9 +1257,6 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
12571257
if (type_mask & MAY_BE_CALLABLE) {
12581258
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE), /* is_intersection */ false);
12591259
}
1260-
if (type_mask & MAY_BE_ITERABLE) {
1261-
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE), /* is_intersection */ false);
1262-
}
12631260
if (type_mask & MAY_BE_OBJECT) {
12641261
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT), /* is_intersection */ false);
12651262
}
@@ -1319,7 +1316,7 @@ static void zend_mark_function_as_generator(void) /* {{{ */
13191316

13201317
if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
13211318
zend_type return_type = CG(active_op_array)->arg_info[-1].type;
1322-
bool valid_type = (ZEND_TYPE_FULL_MASK(return_type) & (MAY_BE_ITERABLE | MAY_BE_OBJECT)) != 0;
1319+
bool valid_type = (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_OBJECT) != 0;
13231320
if (!valid_type) {
13241321
zend_type *single_type;
13251322
ZEND_TYPE_FOREACH(return_type, single_type) {
@@ -6185,6 +6182,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
61856182
zend_error_noreturn(E_COMPILE_ERROR,
61866183
"Cannot use \"static\" when no class scope is active");
61876184
}
6185+
61886186
return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
61896187
} else {
61906188
zend_string *class_name = zend_ast_get_str(ast);
@@ -6196,6 +6194,17 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
61966194
"Type declaration '%s' must be unqualified",
61976195
ZSTR_VAL(zend_string_tolower(class_name)));
61986196
}
6197+
6198+
/* Transform iterable into a type union alias */
6199+
if (type_code == IS_ITERABLE) {
6200+
zend_type iterable = (zend_type) ZEND_TYPE_INIT_CLASS(ZSTR_KNOWN(ZEND_STR_TRAVERSABLE), 0, 0);
6201+
ZEND_TYPE_FULL_MASK(iterable) |= MAY_BE_ARRAY;
6202+
/* Inform that the type list is a union type */
6203+
ZEND_TYPE_FULL_MASK(iterable) |= _ZEND_TYPE_NAME_BIT;
6204+
ZEND_TYPE_FULL_MASK(iterable) |= _ZEND_TYPE_UNION_BIT;
6205+
return iterable;
6206+
}
6207+
61996208
return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0);
62006209
} else {
62016210
const char *correct_name;
@@ -6233,19 +6242,6 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
62336242
}
62346243
}
62356244

6236-
static bool zend_type_contains_traversable(zend_type type) {
6237-
zend_type *single_type;
6238-
ZEND_TYPE_FOREACH(type, single_type) {
6239-
if (ZEND_TYPE_HAS_NAME(*single_type)
6240-
&& zend_string_equals_literal_ci(ZEND_TYPE_NAME(*single_type), "Traversable")) {
6241-
return 1;
6242-
}
6243-
} ZEND_TYPE_FOREACH_END();
6244-
return 0;
6245-
}
6246-
6247-
// TODO: Ideally we'd canonicalize "iterable" into "array|Traversable" and essentially
6248-
// treat it as a built-in type alias.
62496245
static zend_type zend_compile_typename(
62506246
zend_ast *ast, bool force_allow_null) /* {{{ */
62516247
{
@@ -6340,7 +6336,7 @@ static zend_type zend_compile_typename(
63406336
zend_type single_type = zend_compile_single_typename(type_ast);
63416337

63426338
/* An intersection of standard types cannot exist so invalidate it */
6343-
if (ZEND_TYPE_IS_ONLY_MASK(single_type)) {
6339+
if (ZEND_TYPE_PURE_MASK(single_type)) {
63446340
zend_string *standard_type_str = zend_type_to_string(single_type);
63456341
zend_error_noreturn(E_COMPILE_ERROR,
63466342
"Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str));
@@ -6382,18 +6378,6 @@ static zend_type zend_compile_typename(
63826378
}
63836379

63846380
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
6385-
if ((type_mask & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) {
6386-
zend_string *type_str = zend_type_to_string(type);
6387-
zend_error_noreturn(E_COMPILE_ERROR,
6388-
"Type %s contains both iterable and array, which is redundant", ZSTR_VAL(type_str));
6389-
}
6390-
6391-
if ((type_mask & MAY_BE_ITERABLE) && zend_type_contains_traversable(type)) {
6392-
zend_string *type_str = zend_type_to_string(type);
6393-
zend_error_noreturn(E_COMPILE_ERROR,
6394-
"Type %s contains both iterable and Traversable, which is redundant",
6395-
ZSTR_VAL(type_str));
6396-
}
63976381

63986382
if (type_mask == MAY_BE_ANY && (orig_ast_attr & ZEND_TYPE_NULLABLE)) {
63996383
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed cannot be marked as nullable since mixed already includes null");
@@ -6440,9 +6424,6 @@ static bool zend_is_valid_default_value(zend_type type, zval *value)
64406424
convert_to_double(value);
64416425
return 1;
64426426
}
6443-
if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_ITERABLE) && Z_TYPE_P(value) == IS_ARRAY) {
6444-
return 1;
6445-
}
64466427
return 0;
64476428
}
64486429

Zend/zend_inheritance.c

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -326,21 +326,6 @@ static bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) {
326326
return 0;
327327
}
328328

329-
static bool zend_type_contains_traversable(zend_type type) {
330-
zend_type *single_type;
331-
if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) {
332-
return 1;
333-
}
334-
335-
ZEND_TYPE_FOREACH(type, single_type) {
336-
if (ZEND_TYPE_HAS_NAME(*single_type)
337-
&& zend_string_equals_literal_ci(ZEND_TYPE_NAME(*single_type), "Traversable")) {
338-
return 1;
339-
}
340-
} ZEND_TYPE_FOREACH_END();
341-
return 0;
342-
}
343-
344329
static bool zend_type_permits_self(
345330
zend_type type, zend_class_entry *scope, zend_class_entry *self) {
346331
if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) {
@@ -473,15 +458,6 @@ static inheritance_status zend_is_class_subtype_of_type(
473458
return INHERITANCE_SUCCESS;
474459
}
475460
}
476-
if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) {
477-
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name);
478-
if (!fe_ce) {
479-
have_unresolved = 1;
480-
} else if (unlinked_instanceof(fe_ce, zend_ce_traversable)) {
481-
track_class_dependency(fe_ce, fe_class_name);
482-
return INHERITANCE_SUCCESS;
483-
}
484-
}
485461

486462
zend_type *single_type;
487463

@@ -576,18 +552,6 @@ static inheritance_status zend_perform_covariant_type_check(
576552
uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type);
577553
uint32_t added_types = fe_type_mask & ~proto_type_mask;
578554
if (added_types) {
579-
// TODO: Make "iterable" an alias of "array|Traversable" instead,
580-
// so these special cases will be handled automatically.
581-
if ((added_types & MAY_BE_ITERABLE)
582-
&& (proto_type_mask & MAY_BE_ARRAY)
583-
&& zend_type_contains_traversable(proto_type)) {
584-
/* Replacing array|Traversable with iterable is okay */
585-
added_types &= ~MAY_BE_ITERABLE;
586-
}
587-
if ((added_types & MAY_BE_ARRAY) && (proto_type_mask & MAY_BE_ITERABLE)) {
588-
/* Replacing iterable with array is okay */
589-
added_types &= ~MAY_BE_ARRAY;
590-
}
591555
if ((added_types & MAY_BE_STATIC)
592556
&& zend_type_permits_self(proto_type, proto_scope, fe_scope)) {
593557
/* Replacing type that accepts self with static is okay */
@@ -614,8 +578,7 @@ static inheritance_status zend_perform_covariant_type_check(
614578
* We still perform a class lookup for forward-compatibility reasons,
615579
* as we may have named types in the future that are not classes
616580
* (such as typedefs). */
617-
if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) {
618-
bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0;
581+
if (proto_type_mask & MAY_BE_OBJECT) {
619582
ZEND_TYPE_FOREACH(fe_type, single_type) {
620583
zend_class_entry *fe_ce;
621584
zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type);
@@ -626,10 +589,8 @@ static inheritance_status zend_perform_covariant_type_check(
626589
fe_ce = lookup_class(fe_scope, fe_class_name);
627590
}
628591
if (fe_ce) {
629-
if (any_class || unlinked_instanceof(fe_ce, zend_ce_traversable)) {
630-
track_class_dependency(fe_ce, fe_class_name);
631-
return INHERITANCE_SUCCESS;
632-
}
592+
track_class_dependency(fe_ce, fe_class_name);
593+
return INHERITANCE_SUCCESS;
633594
} else {
634595
have_unresolved = true;
635596
}

Zend/zend_string.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ EMPTY_SWITCH_DEFAULT_CASE()
555555
_(ZEND_STR_FALSE, "false") \
556556
_(ZEND_STR_NULL_LOWERCASE, "null") \
557557
_(ZEND_STR_MIXED, "mixed") \
558+
_(ZEND_STR_TRAVERSABLE, "Traversable") \
558559
_(ZEND_STR_SLEEP, "__sleep") \
559560
_(ZEND_STR_WAKEUP, "__wakeup") \
560561
_(ZEND_STR_CASES, "cases") \

ext/reflection/php_reflection.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3094,9 +3094,6 @@ ZEND_METHOD(ReflectionUnionType, getTypes)
30943094
if (type_mask & MAY_BE_CALLABLE) {
30953095
append_type_mask(return_value, MAY_BE_CALLABLE);
30963096
}
3097-
if (type_mask & MAY_BE_ITERABLE) {
3098-
append_type_mask(return_value, MAY_BE_ITERABLE);
3099-
}
31003097
if (type_mask & MAY_BE_OBJECT) {
31013098
append_type_mask(return_value, MAY_BE_OBJECT);
31023099
}

ext/reflection/tests/ReflectionClass_isArray.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Deprecated: Method ReflectionParameter::isArray() is deprecated in %s on line %d
2121
bool(true)
2222

2323
Deprecated: Method ReflectionParameter::isArray() is deprecated in %s on line %d
24-
bool(false)
24+
bool(true)
2525

2626
Deprecated: Method ReflectionParameter::isArray() is deprecated in %s on line %d
2727
bool(false)

ext/reflection/tests/ReflectionType_001.phpt

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

9191
foreach ($reflector->getProperties() as $name => $property) {
9292
if ($property->hasType()) {
93-
printf("public %s $%s;\n",
94-
$property->getType()->getName(), $property->getName());
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+
}
95100
} else printf("public $%s;\n", $property->getName());
96101
}
97102

@@ -216,7 +221,7 @@ string(4) "Test"
216221
public int $int;
217222
public string $string;
218223
public array $arr;
219-
public iterable $iterable;
224+
public Traversable|array $iterable
220225
public stdClass $std;
221226
public OtherThing $other;
222227
public $mixed;

ext/reflection/tests/ReflectionType_possible_types.phpt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ $functions = [
1818
foreach ($functions as $function) {
1919
$reflectionFunc = new ReflectionFunction($function);
2020
$returnType = $reflectionFunc->getReturnType();
21-
var_dump($returnType->getName());
21+
if ($returnType instanceof ReflectionNamedType) {
22+
var_dump($returnType->getName());
23+
} else {
24+
var_dump(implode('|', $returnType->getTypes()));
25+
}
2226
}
2327
?>
2428
--EXPECT--
@@ -29,5 +33,5 @@ string(6) "string"
2933
string(4) "bool"
3034
string(5) "array"
3135
string(8) "callable"
32-
string(8) "iterable"
36+
string(17) "Traversable|array"
3337
string(8) "StdClass"

ext/reflection/tests/bug72661.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ function test(iterable $arg) { }
77
var_dump((string)(new ReflectionParameter("test", 0))->getType());
88
?>
99
--EXPECT--
10-
string(8) "iterable"
10+
string(17) "Traversable|array"

ext/reflection/tests/union_types.phpt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,16 @@ Allows null: true
6666
Name: null
6767
String: null
6868
Allows Null: true
69-
Type X|iterable|bool:
69+
Type X|Traversable|array|bool:
7070
Allows null: false
7171
Name: X
7272
String: X
7373
Allows Null: false
74-
Name: iterable
75-
String: iterable
74+
Name: Traversable
75+
String: Traversable
76+
Allows Null: false
77+
Name: array
78+
String: array
7679
Allows Null: false
7780
Name: bool
7881
String: bool

0 commit comments

Comments
 (0)