Skip to content

Commit 91b25fd

Browse files
committed
Fix GH-9556 "iterable" alias "array|Traversable" breaks PHP 8.1 code
1 parent 9e7d610 commit 91b25fd

15 files changed

+192
-4
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
A DNF type which contains object is redundant
3+
--FILE--
4+
<?php
5+
6+
interface A {}
7+
interface B {}
8+
9+
function test(): (A&B)|object {}
10+
11+
?>
12+
===DONE===
13+
--EXPECTF--
14+
Fatal error: Type (A&B)|object contains both object and a class type, which is redundant in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
A DNF type which contains object is redundant 2
3+
--FILE--
4+
<?php
5+
6+
interface A {}
7+
interface B {}
8+
9+
function test(): object|(A&B) {}
10+
11+
?>
12+
===DONE===
13+
--EXPECTF--
14+
Fatal error: Type (A&B)|object contains both object and a class type, which is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
iterable type with array should be redundant
3+
--FILE--
4+
<?php
5+
6+
function bar(): array|iterable|null {
7+
return null;
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Duplicate type array is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
iterable type with array should be redundant
3+
--FILE--
4+
<?php
5+
6+
function bar(): iterable|array|null {
7+
return null;
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Duplicate type array is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
iterable type with second iterable should be redundant
3+
--FILE--
4+
<?php
5+
6+
function bar(): iterable|iterable|null {
7+
return null;
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Duplicate type array is redundant in %s on line %d
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
iterable type with object should NOT be redundant 1
3+
--FILE--
4+
<?php
5+
6+
function bar(): iterable|object|null {
7+
return null;
8+
}
9+
10+
?>
11+
===DONE===
12+
--EXPECT--
13+
===DONE===
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
iterable type with object should NOT be redundant 2
3+
--FILE--
4+
<?php
5+
6+
function bar(): object|iterable|null {
7+
return null;
8+
}
9+
10+
?>
11+
===DONE===
12+
--EXPECT--
13+
===DONE===
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
iterable type with object and class T should be redundant
3+
--FILE--
4+
<?php
5+
6+
function bar(): object|iterable|T|null {
7+
return null;
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Type Traversable|T|object|array|null contains both object and a class type, which is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
iterable type with object and class T should be redundant
3+
--FILE--
4+
<?php
5+
6+
function bar(): iterable|object|T|null {
7+
return null;
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Type Traversable|T|object|array|null contains both object and a class type, which is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
iterable type with object and class T should be redundant
3+
--FILE--
4+
<?php
5+
6+
function bar(): T|object|iterable|null {
7+
return null;
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Type T|Traversable|object|array|null contains both object and a class type, which is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
iterable type with object and class T should be redundant
3+
--FILE--
4+
<?php
5+
6+
function bar(): T|iterable|object|null {
7+
return null;
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Type T|Traversable|object|array|null contains both object and a class type, which is redundant in %s on line %d
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
iterable type with object should be allowed in variance checks
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public object|iterable $x;
8+
public object|array $y;
9+
}
10+
class B extends A {
11+
public object|array $x;
12+
public object|iterable $y;
13+
}
14+
15+
?>
16+
===DONE===
17+
--EXPECT--
18+
===DONE===
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Using both object and a class type 2
3+
--FILE--
4+
<?php
5+
6+
function test(): Test|object {
7+
}
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Type Test|object contains both object and a class type, which is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
object and static are redundant 2
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function foo(): object|static {}
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Type static|object contains both object and a class type, which is redundant in %s on line %d

Zend/zend_compile.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6363,6 +6363,7 @@ static zend_type zend_compile_typename(
63636363
zend_ast_list *list = zend_ast_get_list(ast);
63646364
zend_type_list *type_list;
63656365
bool is_composite = false;
6366+
bool has_only_iterable_class = true;
63666367
ALLOCA_FLAG(use_heap)
63676368

63686369
type_list = do_alloca(ZEND_TYPE_LIST_SIZE(list->children), use_heap);
@@ -6371,17 +6372,20 @@ static zend_type zend_compile_typename(
63716372
for (uint32_t i = 0; i < list->children; i++) {
63726373
zend_ast *type_ast = list->child[i];
63736374
zend_type single_type;
6375+
uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
63746376

63756377
if (type_ast->kind == ZEND_AST_TYPE_INTERSECTION) {
6378+
has_only_iterable_class = false;
63766379
is_composite = true;
63776380
/* The first class type can be stored directly as the type ptr payload. */
63786381
if (ZEND_TYPE_IS_COMPLEX(type) && !ZEND_TYPE_HAS_LIST(type)) {
63796382
/* Switch from single name to name list. */
63806383
type_list->num_types = 1;
63816384
type_list->types[0] = type;
63826385
ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK;
6383-
ZEND_TYPE_SET_LIST(type, type_list);
63846386
}
6387+
/* Mark type as list type */
6388+
ZEND_TYPE_SET_LIST(type, type_list);
63856389

63866390
single_type = zend_compile_typename(type_ast, false);
63876391
ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(single_type));
@@ -6406,6 +6410,9 @@ static zend_type zend_compile_typename(
64066410
if (single_type_mask == MAY_BE_ANY) {
64076411
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type");
64086412
}
6413+
if (ZEND_TYPE_IS_COMPLEX(single_type) && !ZEND_TYPE_IS_ITERABLE_FALLBACK(single_type)) {
6414+
has_only_iterable_class = false;
6415+
}
64096416

64106417
uint32_t type_mask_overlap = ZEND_TYPE_PURE_MASK(type) & single_type_mask;
64116418
if (type_mask_overlap) {
@@ -6414,8 +6421,9 @@ static zend_type zend_compile_typename(
64146421
zend_error_noreturn(E_COMPILE_ERROR,
64156422
"Duplicate type %s is redundant", ZSTR_VAL(overlap_type_str));
64166423
}
6417-
if ( ((ZEND_TYPE_PURE_MASK(type) & MAY_BE_TRUE) && (single_type_mask == MAY_BE_FALSE))
6418-
|| ((ZEND_TYPE_PURE_MASK(type) & MAY_BE_FALSE) && (single_type_mask == MAY_BE_TRUE)) ) {
6424+
6425+
if ( ((type_mask & MAY_BE_TRUE) && (single_type_mask == MAY_BE_FALSE))
6426+
|| ((type_mask & MAY_BE_FALSE) && (single_type_mask == MAY_BE_TRUE)) ) {
64196427
zend_error_noreturn(E_COMPILE_ERROR,
64206428
"Type contains both true and false, bool should be used instead");
64216429
}
@@ -6457,7 +6465,8 @@ static zend_type zend_compile_typename(
64576465
free_alloca(type_list, use_heap);
64586466

64596467
uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
6460-
if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_IS_COMPLEX(type) || (type_mask & MAY_BE_STATIC))) {
6468+
if ((type_mask & MAY_BE_OBJECT) &&
6469+
((!has_only_iterable_class && ZEND_TYPE_IS_COMPLEX(type)) || (type_mask & MAY_BE_STATIC))) {
64616470
zend_string *type_str = zend_type_to_string(type);
64626471
zend_error_noreturn(E_COMPILE_ERROR,
64636472
"Type %s contains both object and a class type, which is redundant",

0 commit comments

Comments
 (0)