Skip to content

Commit 2e3ed49

Browse files
committed
Zend: Resolve "self" and "parent" types when using traits
1 parent a5ffcf3 commit 2e3ed49

9 files changed

+130
-30
lines changed

Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ class A {
1111
}
1212
?>
1313
DONE
14-
--EXPECT--
15-
DONE
14+
--EXPECTF--
15+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d

Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ class A {
1111
}
1212
?>
1313
DONE
14-
--EXPECT--
15-
DONE
14+
--EXPECTF--
15+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d

Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ class A {
1111
}
1212
?>
1313
DONE
14-
--EXPECT--
15-
DONE
14+
--EXPECTF--
15+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
--TEST--
2-
Cannot use a trait which references parent as a type in a class with no parent, DNF type
2+
Cannot use a trait which references parent as a type in a class with no parent, non-simple union type
33
--FILE--
44
<?php
5+
6+
class T {}
7+
58
trait TraitExample {
6-
public function bar(): (X&Y)|parent { return parent::class; }
9+
public function bar(): T|parent { return parent::class; }
710
}
811

912
class A {
1013
use TraitExample;
1114
}
1215
?>
1316
DONE
14-
--EXPECT--
15-
DONE
17+
--EXPECTF--
18+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Cannot use a trait which references parent as a type in a class with no parent, DNF type
3+
--FILE--
4+
<?php
5+
trait TraitExample {
6+
public function bar(): (X&Y)|parent { return parent::class; }
7+
}
8+
9+
class A {
10+
use TraitExample;
11+
}
12+
?>
13+
DONE
14+
--EXPECTF--
15+
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d

Zend/zend_compile.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7420,6 +7420,10 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
74207420
ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time");
74217421
}
74227422
}
7423+
if (CG(active_class_entry) && CG(active_class_entry)->ce_flags & ZEND_ACC_TRAIT) {
7424+
zend_op_array *op_array = CG(active_op_array);
7425+
op_array->fn_flags2 = ZEND_ACC_RESOLVE_RELATIVE_TYPE;
7426+
}
74237427
zend_string_addref(class_name);
74247428
}
74257429

Zend/zend_compile.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,8 @@ typedef struct _zend_oparray_context {
416416
/* Function Flags 2 (fn_flags2) (unused: 0-31) | | | */
417417
/* ============================ | | | */
418418
/* | | | */
419-
/* #define ZEND_ACC2_EXAMPLE (1 << 0) | X | | */
419+
/* method had self or parent type from trait | | | */
420+
#define ZEND_ACC_RESOLVE_RELATIVE_TYPE (1 << 1) /* | X | | */
420421

421422
#define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
422423
#define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET)

Zend/zend_inheritance.c

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2361,6 +2361,67 @@ static zend_class_entry *fixup_trait_scope(const zend_function *fn, zend_class_e
23612361
return fn->common.scope->ce_flags & ZEND_ACC_TRAIT ? ce : fn->common.scope;
23622362
}
23632363

2364+
static void zend_resolve_type(zend_type *type, const zend_class_entry *const ce)
2365+
{
2366+
/* Only built-in types + static */
2367+
if (!ZEND_TYPE_IS_COMPLEX(*type)) {
2368+
return;
2369+
}
2370+
/* Intersection types cannot have un-resolved relative class types */
2371+
if (ZEND_TYPE_IS_INTERSECTION(*type)) {
2372+
return;
2373+
}
2374+
2375+
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*type) || (ZEND_TYPE_HAS_LIST(*type)));
2376+
if (ZEND_TYPE_HAS_NAME(*type)) {
2377+
if (zend_string_equals_ci(ZEND_TYPE_NAME(*type), ZSTR_KNOWN(ZEND_STR_SELF))) {
2378+
ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->name));
2379+
} else if (zend_string_equals_ci(ZEND_TYPE_NAME(*type), ZSTR_KNOWN(ZEND_STR_PARENT))) {
2380+
if (!ce->parent) {
2381+
zend_error_noreturn(E_COMPILE_ERROR,
2382+
"Cannot use trait which has \"parent\" as a type when current class scope has no parent");
2383+
}
2384+
ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->parent->name));
2385+
}
2386+
return;
2387+
}
2388+
2389+
zend_type_list *union_type_list = ZEND_TYPE_LIST(*type);
2390+
zend_type *list_type;
2391+
ZEND_TYPE_LIST_FOREACH_MUTABLE(union_type_list, list_type) {
2392+
zend_resolve_type(list_type, ce);
2393+
} ZEND_TYPE_LIST_FOREACH_END();
2394+
}
2395+
2396+
static void zend_resolve_trait_relative_class_types(zend_function *const fn, const zend_class_entry *const ce)
2397+
{
2398+
/* No type info */
2399+
if (!fn->common.arg_info) {
2400+
return;
2401+
}
2402+
2403+
bool has_return_type = fn->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE;
2404+
/* Variadic parameters are not counted as part of the standard number of arguments */
2405+
bool has_variadic_type = fn->common.fn_flags & ZEND_ACC_VARIADIC;
2406+
uint32_t num_args = fn->common.num_args + has_variadic_type;
2407+
size_t allocated_size = sizeof(zend_arg_info) * (has_return_type + num_args);
2408+
2409+
zend_arg_info *new_arg_infos = fn->common.arg_info - has_return_type;
2410+
2411+
new_arg_infos = safe_emalloc(num_args + has_return_type, sizeof(zend_arg_info), 0);
2412+
memcpy(new_arg_infos, fn->common.arg_info - has_return_type, allocated_size);
2413+
fn->common.arg_info = new_arg_infos + has_return_type;
2414+
2415+
for (uint32_t i = 0; i < num_args + has_return_type; i++) {
2416+
zend_type type = new_arg_infos[i].type;
2417+
zend_type_copy_ctor(&type, true, ce->type == ZEND_INTERNAL_CLASS);
2418+
/* New CE is a concrete class, resolve */
2419+
if (!(ce->ce_flags & ZEND_ACC_TRAIT)) {
2420+
zend_resolve_type(&type, ce);
2421+
}
2422+
}
2423+
}
2424+
23642425
static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_string *key, zend_function *fn) /* {{{ */
23652426
{
23662427
zend_function *existing_fn = NULL;
@@ -2415,6 +2476,9 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
24152476
/* Reassign method name, in case it is an alias. */
24162477
new_fn->common.function_name = name;
24172478
function_add_ref(new_fn);
2479+
if (new_fn->op_array.fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) {
2480+
zend_resolve_trait_relative_class_types(new_fn, ce);
2481+
}
24182482
fn = zend_hash_update_ptr(&ce->function_table, key, new_fn);
24192483
zend_add_magic_method(ce, fn, key);
24202484
}
@@ -2957,8 +3021,8 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
29573021
zend_string *doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL;
29583022

29593023
zend_type type = property_info->type;
2960-
/* Assumption: only userland classes can use traits, as such the type must be arena allocated */
2961-
zend_type_copy_ctor(&type, /* use arena */ true, /* persistent */ false);
3024+
/* Resolve possible self/parent types, copy otherwise */
3025+
zend_resolve_type(&type, ce);
29623026
zend_property_info *new_prop = zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, type);
29633027

29643028
if (property_info->attributes) {
@@ -2989,6 +3053,10 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
29893053

29903054
zend_fixup_trait_method(new_fn, ce);
29913055

3056+
if (new_fn->op_array.fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) {
3057+
zend_resolve_trait_relative_class_types(new_fn, ce);
3058+
}
3059+
29923060
hooks[j] = new_fn;
29933061
}
29943062
}

Zend/zend_opcode.c

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,28 @@ ZEND_API void zend_destroy_static_vars(zend_op_array *op_array)
561561
}
562562
}
563563

564+
static void zend_destroy_arg_infos_from_op_array(zend_op_array *op_array) {
565+
566+
uint32_t num_args = op_array->num_args;
567+
zend_arg_info *arg_info = op_array->arg_info;
568+
569+
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
570+
arg_info--;
571+
num_args++;
572+
}
573+
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
574+
num_args++;
575+
}
576+
for (uint32_t i = 0 ; i < num_args; i++) {
577+
if (arg_info[i].name) {
578+
zend_string_release_ex(arg_info[i].name, 0);
579+
}
580+
zend_type_release(arg_info[i].type, /* persistent */ false);
581+
}
582+
efree(arg_info);
583+
op_array->arg_info = NULL;
584+
}
585+
564586
ZEND_API void destroy_op_array(zend_op_array *op_array)
565587
{
566588
uint32_t i;
@@ -574,6 +596,9 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
574596
zend_string_release_ex(op_array->function_name, 0);
575597
}
576598

599+
if (op_array->fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) {
600+
zend_destroy_arg_infos_from_op_array(op_array);
601+
}
577602
if (!op_array->refcount || --(*op_array->refcount) > 0) {
578603
return;
579604
}
@@ -634,23 +659,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
634659
}
635660
}
636661
if (op_array->arg_info) {
637-
uint32_t num_args = op_array->num_args;
638-
zend_arg_info *arg_info = op_array->arg_info;
639-
640-
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
641-
arg_info--;
642-
num_args++;
643-
}
644-
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
645-
num_args++;
646-
}
647-
for (i = 0 ; i < num_args; i++) {
648-
if (arg_info[i].name) {
649-
zend_string_release_ex(arg_info[i].name, 0);
650-
}
651-
zend_type_release(arg_info[i].type, /* persistent */ false);
652-
}
653-
efree(arg_info);
662+
zend_destroy_arg_infos_from_op_array(op_array);
654663
}
655664
if (op_array->static_variables) {
656665
zend_array_destroy(op_array->static_variables);

0 commit comments

Comments
 (0)