diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt index 1959ab7f802cd..17088824f4249 100644 --- a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent1.phpt @@ -11,5 +11,5 @@ class A { } ?> DONE ---EXPECT-- -DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt index 0d5b9eefc4414..e8cf4c0d4171e 100644 --- a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent2.phpt @@ -11,5 +11,5 @@ class A { } ?> DONE ---EXPECT-- -DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt index eeb50a2abef46..27936c8eae905 100644 --- a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent3.phpt @@ -11,5 +11,5 @@ class A { } ?> DONE ---EXPECT-- -DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent4.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent4.phpt index 02605b28bdcc3..24681b6600666 100644 --- a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent4.phpt +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent4.phpt @@ -1,9 +1,12 @@ --TEST-- -Cannot use a trait which references parent as a type in a class with no parent, DNF type +Cannot use a trait which references parent as a type in a class with no parent, non-simple union type --FILE-- DONE ---EXPECT-- -DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent5.phpt b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent5.phpt new file mode 100644 index 0000000000000..29d885a2792b7 --- /dev/null +++ b/Zend/tests/type_declarations/relative_types/traits/trait_parent_type_in_class_no_parent5.phpt @@ -0,0 +1,15 @@ +--TEST-- +Cannot use a trait which references parent as a type in a class with no parent, DNF type +--FILE-- + +DONE +--EXPECTF-- +Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 5b051af9bb830..573b6dd121965 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2303,6 +2303,120 @@ static zend_class_entry *fixup_trait_scope(const zend_function *fn, zend_class_e return fn->common.scope->ce_flags & ZEND_ACC_TRAIT ? ce : fn->common.scope; } +/* If the type was resolved then either the zend_string pointer is different, or the zend_type_list is */ +static inline bool zend_was_type_resolved(const zend_type *original_type, const zend_type *resolved_type) +{ + return original_type->ptr != resolved_type->ptr; +} + +// TODO Use resolve_class_name() somehow? +static zend_type zend_resolve_name_type(zend_type type, const zend_class_entry *const ce) +{ + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(type)); + if (zend_string_equals_ci(ZEND_TYPE_NAME(type), ZSTR_KNOWN(ZEND_STR_SELF))) { + zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(type)); + return resolved_type; + } else if (zend_string_equals_ci(ZEND_TYPE_NAME(type), ZSTR_KNOWN(ZEND_STR_PARENT))) { + if (!ce->parent) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use trait which has \"parent\" as a type when current class scope has no parent"); + } + zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->parent->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(type)); + return resolved_type; + } else { + return type; + } +} + +/* We cannot modify the type in-place (e.g. via a pointer) as it is written to SHM */ +static zend_type zend_resolve_type(zend_type type, const zend_class_entry *const ce) +{ + /* Only built-in types + static */ + if (!ZEND_TYPE_IS_COMPLEX(type)) { + return type; + } + + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(type) || (ZEND_TYPE_HAS_LIST(type))); + if (ZEND_TYPE_HAS_NAME(type)) { + return zend_resolve_name_type(type, ce); + } + + /* Intersection types cannot have un-resolved relative class types */ + if (ZEND_TYPE_IS_INTERSECTION(type)) { + return type; + } + + zend_type_list *union_type_list = ZEND_TYPE_LIST(type); + bool has_resolved_type = false; + zend_type *list_type; + ZEND_TYPE_LIST_FOREACH(union_type_list, list_type) { + /* Intersections types cannot have self or parent */ + if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { + continue; + } + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type)); + const zend_string *name = ZEND_TYPE_NAME(*list_type); + if (zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_SELF)) || zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_PARENT))) { + has_resolved_type = true; + break; + } + } ZEND_TYPE_LIST_FOREACH_END(); + + if (has_resolved_type) { + zend_type_copy_ctor(&type, true, false); + zend_type_list *new_union_type_list = ZEND_TYPE_LIST(type); + + /* We don't use ZEND_TYPE_LIST_FOREACH() as we need to keep track of the array index */ + for (uint32_t i = 0; i < union_type_list->num_types; i++) { + zend_type single_type = union_type_list->types[i]; + + /* Intersections types cannot have self or parent */ + if (ZEND_TYPE_IS_INTERSECTION(single_type)) { + continue; + } + + new_union_type_list->types[i] = zend_resolve_name_type(single_type, ce); + } + } + return type; +} + +static void zend_resolve_trait_relative_class_types(zend_function *const fn, const zend_class_entry *const ce) +{ + /* We are adding trait methods to another trait, delay resolution */ + if (ce->ce_flags & ZEND_ACC_TRAIT) { + return; + } + /* No type info */ + if (!fn->common.arg_info) { + return; + } + + bool has_return_type = fn->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE; + /* Variadic parameters are not counted as part of the standard number of arguments */ + bool has_variadic_type = fn->common.fn_flags & ZEND_ACC_VARIADIC; + uint32_t num_args = fn->common.num_args + has_variadic_type; + size_t allocated_size = sizeof(zend_arg_info) * (has_return_type + num_args); + + zend_arg_info *new_arg_infos = fn->common.arg_info - has_return_type; + bool has_resolved_type = false; + + for (uint32_t i = 0; i < num_args + has_return_type; i++) { + zend_type type = new_arg_infos[i].type; + zend_type resolved_type = zend_resolve_type(type, ce); + if (zend_was_type_resolved(&type, &resolved_type)) { + //ZEND_ASSERT(ZEND_TYPE_USES_ARENA(resolved_type)); + if (!has_resolved_type) { + new_arg_infos = emalloc(allocated_size); + memcpy(new_arg_infos, fn->common.arg_info - has_return_type, allocated_size); + fn->common.arg_info = new_arg_infos + has_return_type; + has_resolved_type = true; + } + new_arg_infos[i].type = resolved_type; + } + } +} + static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_string *key, zend_function *fn) /* {{{ */ { zend_function *existing_fn = NULL; @@ -2349,10 +2463,12 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_ if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) { new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); memcpy(new_fn, fn, sizeof(zend_internal_function)); + zend_resolve_trait_relative_class_types(new_fn, ce); new_fn->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED; } else { new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); memcpy(new_fn, fn, sizeof(zend_op_array)); + zend_resolve_trait_relative_class_types(new_fn, ce); new_fn->op_array.fn_flags &= ~ZEND_ACC_IMMUTABLE; } new_fn->common.fn_flags |= ZEND_ACC_TRAIT_CLONE;