Skip to content

Commit 766bdfc

Browse files
committed
Prevent associated type from being in a union or intersection type
1 parent 6194356 commit 766bdfc

7 files changed

+89
-8
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Associated type cannot be in intersection (simple intersection with class type)
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
type T;
8+
public function foo(T&Traversable $param): T&Traversable;
9+
}
10+
11+
?>
12+
--EXPECTF--
13+
Fatal error: Associated type cannot be part of an intersection type 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+
Associated type cannot be in intersection (DNF type)
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
type T;
8+
public function foo(stdClass|(T&Traversable) $param): stdClass|(T&Traversable);
9+
}
10+
11+
?>
12+
--EXPECTF--
13+
Fatal error: Associated type cannot be part of an intersection type 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+
Associated type cannot be in union (simple union with built-in type)
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
type T;
8+
public function foo(T|int $param): T|int;
9+
}
10+
11+
?>
12+
--EXPECTF--
13+
Fatal error: Associated type cannot be part of a union type 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+
Associated type cannot be in union (simple union with class type)
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
type T;
8+
public function foo(T|stdClass $param): T|stdClass;
9+
}
10+
11+
?>
12+
--EXPECTF--
13+
Fatal error: Associated type cannot be part of a union type 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+
Associated type cannot be in union (DNF type)
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
type T;
8+
public function foo(T|(Traversable&Countable) $param): T|(Traversable&Countable);
9+
}
10+
11+
?>
12+
--EXPECTF--
13+
Fatal error: Associated type cannot be part of a union type in %s on line %d

Zend/zend_compile.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6966,9 +6966,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */
69666966

69676967
static zend_type zend_compile_single_typename(zend_ast *ast)
69686968
{
6969+
zend_class_entry *ce = CG(active_class_entry);
6970+
69696971
ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE));
69706972
if (ast->kind == ZEND_AST_TYPE) {
6971-
if (ast->attr == IS_STATIC && !CG(active_class_entry) && zend_is_scope_known()) {
6973+
if (ast->attr == IS_STATIC && !ce && zend_is_scope_known()) {
69726974
zend_error_noreturn(E_COMPILE_ERROR,
69736975
"Cannot use \"static\" when no class scope is active");
69746976
}
@@ -6998,25 +7000,29 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
69987000
const char *correct_name;
69997001
uint32_t fetch_type = zend_get_class_fetch_type_ast(ast);
70007002
zend_string *class_name = type_name;
7003+
uint32_t flags = 0;
70017004

70027005
if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) {
70037006
class_name = zend_resolve_class_name_ast(ast);
70047007
zend_assert_valid_class_name(class_name, "a type name");
7008+
if (ce && ce->associated_types && zend_hash_exists(ce->associated_types, class_name)) {
7009+
flags = _ZEND_TYPE_ASSOCIATED_BIT;
7010+
}
70057011
} else {
70067012
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT);
70077013

70087014
zend_ensure_valid_class_fetch_type(fetch_type);
70097015
if (fetch_type == ZEND_FETCH_CLASS_SELF) {
70107016
/* Scope might be unknown for unbound closures and traits */
70117017
if (zend_is_scope_known()) {
7012-
class_name = CG(active_class_entry)->name;
7018+
class_name = ce->name;
70137019
ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time");
70147020
}
70157021
} else {
70167022
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT);
70177023
/* Scope might be unknown for unbound closures and traits */
70187024
if (zend_is_scope_known()) {
7019-
class_name = CG(active_class_entry)->parent_name;
7025+
class_name = ce->parent_name;
70207026
ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time");
70217027
}
70227028
}
@@ -7044,7 +7050,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
70447050

70457051
class_name = zend_new_interned_string(class_name);
70467052
zend_alloc_ce_cache(class_name);
7047-
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0);
7053+
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, flags);
70487054
}
70497055
}
70507056
}
@@ -7193,6 +7199,9 @@ static zend_type zend_compile_typename_ex(
71937199
single_type = zend_compile_single_typename(type_ast);
71947200
uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type);
71957201

7202+
if (ZEND_TYPE_IS_ASSOCIATED(single_type)) {
7203+
zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of a union type");
7204+
}
71967205
if (single_type_mask == MAY_BE_ANY) {
71977206
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type");
71987207
}
@@ -7275,6 +7284,9 @@ static zend_type zend_compile_typename_ex(
72757284
zend_ast *type_ast = list->child[i];
72767285
zend_type single_type = zend_compile_single_typename(type_ast);
72777286

7287+
if (ZEND_TYPE_IS_ASSOCIATED(single_type)) {
7288+
zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of an intersection type");
7289+
}
72787290
/* An intersection of union types cannot exist so invalidate it
72797291
* Currently only can happen with iterable getting canonicalized to Traversable|array */
72807292
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(single_type)) {

Zend/zend_types.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,15 @@ typedef struct {
142142
zend_type types[1];
143143
} zend_type_list;
144144

145-
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25
146-
#define _ZEND_TYPE_MASK ((1u << 25) - 1)
145+
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 26
146+
#define _ZEND_TYPE_MASK ((1u << 26) - 1)
147147
/* Only one of these bits may be set. */
148+
#define _ZEND_TYPE_ASSOCIATED_BIT (1u << 25)
148149
#define _ZEND_TYPE_NAME_BIT (1u << 24)
149150
// Used to signify that type.ptr is not a `zend_string*` but a `const char*`,
150151
#define _ZEND_TYPE_LITERAL_NAME_BIT (1u << 23)
151152
#define _ZEND_TYPE_LIST_BIT (1u << 22)
152-
#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT)
153+
#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT|_ZEND_TYPE_ASSOCIATED_BIT)
153154
/* For BC behaviour with iterable type */
154155
#define _ZEND_TYPE_ITERABLE_BIT (1u << 21)
155156
/* Whether the type list is arena allocated */
@@ -167,7 +168,7 @@ typedef struct {
167168
(((t).type_mask & _ZEND_TYPE_MASK) != 0)
168169

169170
/* If a type is complex it means it's either a list with a union or intersection,
170-
* or the void pointer is a class name */
171+
* the void pointer is a class name, or the type is an associated type (which implies it is a name) */
171172
#define ZEND_TYPE_IS_COMPLEX(t) \
172173
((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0)
173174

@@ -180,6 +181,9 @@ typedef struct {
180181
#define ZEND_TYPE_HAS_LIST(t) \
181182
((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0)
182183

184+
#define ZEND_TYPE_IS_ASSOCIATED(t) \
185+
((((t).type_mask) & _ZEND_TYPE_ASSOCIATED_BIT) != 0)
186+
183187
#define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \
184188
((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0)
185189

0 commit comments

Comments
 (0)