Skip to content

Commit 82cc480

Browse files
committed
Prevent associated type from being in a union or intersection type
1 parent 7751731 commit 82cc480

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
@@ -6963,9 +6963,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */
69636963

69646964
static zend_type zend_compile_single_typename(zend_ast *ast)
69656965
{
6966+
zend_class_entry *ce = CG(active_class_entry);
6967+
69666968
ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE));
69676969
if (ast->kind == ZEND_AST_TYPE) {
6968-
if (ast->attr == IS_STATIC && !CG(active_class_entry) && zend_is_scope_known()) {
6970+
if (ast->attr == IS_STATIC && !ce && zend_is_scope_known()) {
69696971
zend_error_noreturn(E_COMPILE_ERROR,
69706972
"Cannot use \"static\" when no class scope is active");
69716973
}
@@ -6995,25 +6997,29 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
69956997
const char *correct_name;
69966998
uint32_t fetch_type = zend_get_class_fetch_type_ast(ast);
69976999
zend_string *class_name = type_name;
7000+
uint32_t flags = 0;
69987001

69997002
if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) {
70007003
class_name = zend_resolve_class_name_ast(ast);
70017004
zend_assert_valid_class_name(class_name, "a type name");
7005+
if (ce && ce->associated_types && zend_hash_exists(ce->associated_types, class_name)) {
7006+
flags = _ZEND_TYPE_ASSOCIATED_BIT;
7007+
}
70027008
} else {
70037009
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT);
70047010

70057011
zend_ensure_valid_class_fetch_type(fetch_type);
70067012
if (fetch_type == ZEND_FETCH_CLASS_SELF) {
70077013
/* Scope might be unknown for unbound closures and traits */
70087014
if (zend_is_scope_known()) {
7009-
class_name = CG(active_class_entry)->name;
7015+
class_name = ce->name;
70107016
ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time");
70117017
}
70127018
} else {
70137019
ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT);
70147020
/* Scope might be unknown for unbound closures and traits */
70157021
if (zend_is_scope_known()) {
7016-
class_name = CG(active_class_entry)->parent_name;
7022+
class_name = ce->parent_name;
70177023
ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time");
70187024
}
70197025
}
@@ -7041,7 +7047,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
70417047

70427048
class_name = zend_new_interned_string(class_name);
70437049
zend_alloc_ce_cache(class_name);
7044-
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0);
7050+
return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, flags);
70457051
}
70467052
}
70477053
}
@@ -7190,6 +7196,9 @@ static zend_type zend_compile_typename_ex(
71907196
single_type = zend_compile_single_typename(type_ast);
71917197
uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type);
71927198

7199+
if (ZEND_TYPE_IS_ASSOCIATED(single_type)) {
7200+
zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of a union type");
7201+
}
71937202
if (single_type_mask == MAY_BE_ANY) {
71947203
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type");
71957204
}
@@ -7272,6 +7281,9 @@ static zend_type zend_compile_typename_ex(
72727281
zend_ast *type_ast = list->child[i];
72737282
zend_type single_type = zend_compile_single_typename(type_ast);
72747283

7284+
if (ZEND_TYPE_IS_ASSOCIATED(single_type)) {
7285+
zend_error_noreturn(E_COMPILE_ERROR, "Associated type cannot be part of an intersection type");
7286+
}
72757287
/* An intersection of union types cannot exist so invalidate it
72767288
* Currently only can happen with iterable getting canonicalized to Traversable|array */
72777289
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
@@ -141,14 +141,15 @@ typedef struct {
141141
zend_type types[1];
142142
} zend_type_list;
143143

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

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

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

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

0 commit comments

Comments
 (0)