diff --git a/Zend/tests/magic_methods_021.phpt b/Zend/tests/magic_methods_021.phpt index fd3e7fa9d0ee5..33a7220e5674e 100644 --- a/Zend/tests/magic_methods_021.phpt +++ b/Zend/tests/magic_methods_021.phpt @@ -12,7 +12,7 @@ class Foo2 { } class Foo3 { - public static function __set_state(array $data): Foo3|self {} + public static function __set_state(array $data): Foo3|Foo2 {} } ?> diff --git a/Zend/tests/bug80055.phpt b/Zend/tests/traits/bug80055.phpt similarity index 100% rename from Zend/tests/bug80055.phpt rename to Zend/tests/traits/bug80055.phpt diff --git a/Zend/tests/traits/trait_parent_type_in_class_no_parent.phpt b/Zend/tests/traits/trait_parent_type_in_class_no_parent.phpt new file mode 100644 index 0000000000000..cd620d81ae991 --- /dev/null +++ b/Zend/tests/traits/trait_parent_type_in_class_no_parent.phpt @@ -0,0 +1,14 @@ +--TEST-- +Cannot use a trait which references parent as a type in a class with no parent, single type +--FILE-- + +--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/traits/trait_parent_type_in_class_no_parent_variation2.phpt b/Zend/tests/traits/trait_parent_type_in_class_no_parent_variation2.phpt new file mode 100644 index 0000000000000..c20dc635167d5 --- /dev/null +++ b/Zend/tests/traits/trait_parent_type_in_class_no_parent_variation2.phpt @@ -0,0 +1,14 @@ +--TEST-- +Cannot use a trait which references parent as a type in a class with no parent, nullable type +--FILE-- + +--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/traits/trait_parent_type_in_class_no_parent_variation3.phpt b/Zend/tests/traits/trait_parent_type_in_class_no_parent_variation3.phpt new file mode 100644 index 0000000000000..e83fb946bca4a --- /dev/null +++ b/Zend/tests/traits/trait_parent_type_in_class_no_parent_variation3.phpt @@ -0,0 +1,14 @@ +--TEST-- +Cannot use a trait which references parent as a type in a class with no parent, union type +--FILE-- + +--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/traits/trait_parent_type_in_class_no_parent_variation4.phpt b/Zend/tests/traits/trait_parent_type_in_class_no_parent_variation4.phpt new file mode 100644 index 0000000000000..d21294427267b --- /dev/null +++ b/Zend/tests/traits/trait_parent_type_in_class_no_parent_variation4.phpt @@ -0,0 +1,14 @@ +--TEST-- +Cannot use a trait which references parent as a type in a class with no parent, DNF type +--FILE-- + +--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/intersection_types/invalid_types/invalid_parent_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_unresolved_relative_type_parent.phpt similarity index 66% rename from Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt rename to Zend/tests/type_declarations/intersection_types/invalid_types/invalid_unresolved_relative_type_parent.phpt index e3a86771a2189..55bfd28e306f6 100644 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_unresolved_relative_type_parent.phpt @@ -1,11 +1,9 @@ --TEST-- -parent type cannot take part in an intersection type +parent type cannot take part in an intersection type when unresolved --FILE-- +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_parent_type_outside_class.phpt b/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_parent_type_outside_class.phpt new file mode 100644 index 0000000000000..1285a710c1d0a --- /dev/null +++ b/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_parent_type_outside_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cannot use parent type outside a class +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "parent" when no class scope is active in %s on line %d diff --git a/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_self_type_outside_class.phpt b/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_self_type_outside_class.phpt new file mode 100644 index 0000000000000..7e1bc4a4f2fdd --- /dev/null +++ b/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_self_type_outside_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cannot use self type outside a class +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "self" when no class scope is active in %s on line %d diff --git a/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_static_type_outside_class.phpt b/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_static_type_outside_class.phpt new file mode 100644 index 0000000000000..4c54db2017708 --- /dev/null +++ b/Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_static_type_outside_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +Cannot use static type outside a class +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "static" when no class scope is active in %s on line %d diff --git a/Zend/tests/type_declarations/relative_class_types/relative_type_in_closures.phpt b/Zend/tests/type_declarations/relative_class_types/relative_type_in_closures.phpt new file mode 100644 index 0000000000000..c729df12bf306 --- /dev/null +++ b/Zend/tests/type_declarations/relative_class_types/relative_type_in_closures.phpt @@ -0,0 +1,13 @@ +--TEST-- +Relative class types can be used for closures as it may be bound to a class +--FILE-- + +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/relative_class_types/relative_type_in_evaled_class_using_trait.phpt b/Zend/tests/type_declarations/relative_class_types/relative_type_in_evaled_class_using_trait.phpt new file mode 100644 index 0000000000000..0c0e64cdec86e --- /dev/null +++ b/Zend/tests/type_declarations/relative_class_types/relative_type_in_evaled_class_using_trait.phpt @@ -0,0 +1,27 @@ +--TEST-- +Eval Class definition should not leak memory when using compiled traits +--FILE-- +bar(); +var_dump($a2); + +?> +DONE +--EXPECT-- +object(A)#2 (0) { +} +DONE diff --git a/Zend/tests/type_declarations/relative_class_types/relative_type_in_evaled_trait.phpt b/Zend/tests/type_declarations/relative_class_types/relative_type_in_evaled_trait.phpt new file mode 100644 index 0000000000000..74e71f6c324e7 --- /dev/null +++ b/Zend/tests/type_declarations/relative_class_types/relative_type_in_evaled_trait.phpt @@ -0,0 +1,27 @@ +--TEST-- +Eval code should not leak memory when using traits +--FILE-- +bar(); +var_dump($a2); + +?> +DONE +--EXPECT-- +object(A)#2 (0) { +} +DONE diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_parent_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_parent_type.phpt new file mode 100644 index 0000000000000..2312fed21e9e6 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_parent_type.phpt @@ -0,0 +1,15 @@ +--TEST-- +Duplicate parent type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type Foo is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_self_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_self_type.phpt new file mode 100644 index 0000000000000..fd3d073f97ba4 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_self_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +Duplicate self type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type Foo is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_static_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_static_type.phpt new file mode 100644 index 0000000000000..9c6e37ca6e093 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_static_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +Duplicate static type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type static is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation1.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation1.phpt new file mode 100644 index 0000000000000..b0eb58bfc30eb --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation1.phpt @@ -0,0 +1,12 @@ +--TEST-- +Relative class type self resolving to an existing entry (after variation) +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type Foo is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation2.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation2.phpt new file mode 100644 index 0000000000000..54342a9915d6d --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation2.phpt @@ -0,0 +1,12 @@ +--TEST-- +Relative class type self resolving to an existing entry (before variation) +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type Foo is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation3.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation3.phpt new file mode 100644 index 0000000000000..e1bcbf8b1e1a5 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation3.phpt @@ -0,0 +1,15 @@ +--TEST-- +Relative class type parent resolving to an existing entry (after variation) +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type Foo is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation4.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation4.phpt new file mode 100644 index 0000000000000..0b5a513a24397 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation4.phpt @@ -0,0 +1,15 @@ +--TEST-- +Relative class type parent resolving to an existing entry (before variation) +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type Foo is redundant in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 3cbb0c4f7a1ed..d779afc116991 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1354,10 +1354,8 @@ static zend_string *add_intersection_type(zend_string *str, ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) { ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*single_type)); ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*single_type)); - zend_string *name = ZEND_TYPE_NAME(*single_type); - zend_string *resolved = resolve_class_name(name, scope); - intersection_str = add_type_string(intersection_str, resolved, /* is_intersection */ true); - zend_string_release(resolved); + + intersection_str = add_type_string(intersection_str, ZEND_TYPE_NAME(*single_type), /* is_intersection */ true); } ZEND_TYPE_LIST_FOREACH_END(); ZEND_ASSERT(intersection_str); @@ -1389,6 +1387,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop } ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type)); + zend_string *name = ZEND_TYPE_NAME(*list_type); zend_string *resolved = resolve_class_name(name, scope); str = add_type_string(str, resolved, /* is_intersection */ false); @@ -6620,14 +6619,14 @@ static zend_type zend_compile_single_typename(zend_ast *ast) return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0); } else { - zend_string *class_name = zend_ast_get_str(ast); - uint8_t type_code = zend_lookup_builtin_type_by_name(class_name); + zend_string *type_name = zend_ast_get_str(ast); + uint8_t type_code = zend_lookup_builtin_type_by_name(type_name); if (type_code != 0) { if ((ast->attr & ZEND_NAME_NOT_FQ) != ZEND_NAME_NOT_FQ) { zend_error_noreturn(E_COMPILE_ERROR, "Type declaration '%s' must be unqualified", - ZSTR_VAL(zend_string_tolower(class_name))); + ZSTR_VAL(zend_string_tolower(type_name))); } /* Transform iterable into a type union alias */ @@ -6641,38 +6640,55 @@ static zend_type zend_compile_single_typename(zend_ast *ast) return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0); } else { const char *correct_name; - zend_string *orig_name = zend_ast_get_str(ast); uint32_t fetch_type = zend_get_class_fetch_type_ast(ast); + zend_string *class_name = type_name; + if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) { class_name = zend_resolve_class_name_ast(ast); zend_assert_valid_class_name(class_name); } else { + ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_SELF || fetch_type == ZEND_FETCH_CLASS_PARENT); + zend_ensure_valid_class_fetch_type(fetch_type); + if (fetch_type == ZEND_FETCH_CLASS_SELF) { + /* Scope might be unknown for unbound closures and traits */ + if (zend_is_scope_known()) { + class_name = CG(active_class_entry)->name; + ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time"); + } + } else { + ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT); + /* Scope might be unknown for unbound closures and traits */ + if (zend_is_scope_known()) { + class_name = CG(active_class_entry)->parent_name; + ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time"); + } + } zend_string_addref(class_name); } if (ast->attr == ZEND_NAME_NOT_FQ - && zend_is_confusable_type(orig_name, &correct_name) - && zend_is_not_imported(orig_name)) { + && zend_is_confusable_type(type_name, &correct_name) + && zend_is_not_imported(type_name)) { const char *extra = FC(current_namespace) ? " or import the class with \"use\"" : ""; if (correct_name) { zend_error(E_COMPILE_WARNING, "\"%s\" will be interpreted as a class name. Did you mean \"%s\"? " "Write \"\\%s\"%s to suppress this warning", - ZSTR_VAL(orig_name), correct_name, ZSTR_VAL(class_name), extra); + ZSTR_VAL(type_name), correct_name, ZSTR_VAL(class_name), extra); } else { zend_error(E_COMPILE_WARNING, "\"%s\" is not a supported builtin type " "and will be interpreted as a class name. " "Write \"\\%s\"%s to suppress this warning", - ZSTR_VAL(orig_name), ZSTR_VAL(class_name), extra); + ZSTR_VAL(type_name), ZSTR_VAL(class_name), extra); } } class_name = zend_new_interned_string(class_name); zend_alloc_ce_cache(class_name); - return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 0, 0); + return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, /* allow null */ false, 0); } } } @@ -6795,6 +6811,7 @@ static zend_type zend_compile_typename_ex( /* Switch from single name to name list. */ type_list->num_types = 1; type_list->types[0] = type; + /* Clear MAY_BE_* type flags */ ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK; } /* Mark type as list type */ @@ -6841,6 +6858,7 @@ static zend_type zend_compile_typename_ex( "Type contains both true and false, bool should be used instead"); } ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type); + /* Clear MAY_BE_* type flags */ ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK; if (ZEND_TYPE_IS_COMPLEX(single_type)) { @@ -6853,6 +6871,7 @@ static zend_type zend_compile_typename_ex( /* Switch from single name to name list. */ type_list->num_types = 1; type_list->types[0] = type; + /* Clear MAY_BE_* type flags */ ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK; ZEND_TYPE_SET_LIST(type, type_list); } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 491d68714b3c2..8102ebd9479ab 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1946,6 +1946,122 @@ 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(zend_type original_type, zend_type resolved_type) +{ + return original_type.ptr != resolved_type.ptr; +} + +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_literal_ci(ZEND_TYPE_NAME(type), "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_literal_ci(ZEND_TYPE_NAME(type), "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 relative class_types */ + if (ZEND_TYPE_IS_INTERSECTION(type)) { + return type; + } + + ZEND_ASSERT(ZEND_TYPE_USES_ARENA(type)); + zend_type_list *union_type_list = ZEND_TYPE_LIST(type); + bool has_resolved_type = false; + /* 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]; + + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(single_type) || (ZEND_TYPE_IS_INTERSECTION(single_type))); + + /* Intersections types cannot have self or parent */ + if (ZEND_TYPE_IS_INTERSECTION(single_type)) { + continue; + } + + zend_type resolved_type = zend_resolve_name_type(single_type, ce); + if (zend_was_type_resolved(single_type, resolved_type)) { + if (!has_resolved_type) { + const zend_type_list *old_union_type_list = ZEND_TYPE_LIST(type); + union_type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(old_union_type_list->num_types)); + memcpy(union_type_list, old_union_type_list, ZEND_TYPE_LIST_SIZE(old_union_type_list->num_types)); + has_resolved_type = true; + } + union_type_list->types[i] = resolved_type; + } + } + + if (has_resolved_type) { + zend_type new_type = (zend_type) ZEND_TYPE_INIT_NONE(0); + ZEND_TYPE_SET_LIST(new_type, union_type_list); + ZEND_TYPE_FULL_MASK(new_type) |= _ZEND_TYPE_ARENA_BIT; + /* Inform that the type list is a union type */ + ZEND_TYPE_FULL_MASK(new_type) |= _ZEND_TYPE_UNION_BIT; + ZEND_TYPE_FULL_MASK(new_type) = ZEND_TYPE_FULL_MASK(type); + return new_type; + } else { + 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)) { + if (!has_resolved_type) { + new_arg_infos = zend_arena_alloc(&CG(arena), 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; @@ -1992,10 +2108,13 @@ 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_TRAIT_CLONE; new_fn->op_array.fn_flags &= ~ZEND_ACC_IMMUTABLE; } new_fn->common.fn_flags |= ZEND_ACC_TRAIT_CLONE; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 8828a03d7b618..403e6587403c3 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -110,6 +110,7 @@ ZEND_API void destroy_zend_function(zend_function *function) ZEND_API void zend_type_release(zend_type type, bool persistent) { if (ZEND_TYPE_HAS_LIST(type)) { + ZEND_ASSERT((!persistent && !ZEND_TYPE_USES_ARENA(type)) && "User defined list types must be arena allocated"); zend_type *list_type; ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) { zend_type_release(*list_type, persistent); diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 314a0359d3fc7..80c48cd256f7b 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -82,6 +82,7 @@ PHPAPI zend_class_entry *reflection_generator_ptr; PHPAPI zend_class_entry *reflection_parameter_ptr; PHPAPI zend_class_entry *reflection_type_ptr; PHPAPI zend_class_entry *reflection_named_type_ptr; +PHPAPI zend_class_entry *reflection_relative_class_type_ptr; PHPAPI zend_class_entry *reflection_intersection_type_ptr; PHPAPI zend_class_entry *reflection_union_type_ptr; PHPAPI zend_class_entry *reflection_class_ptr; @@ -1335,7 +1336,7 @@ static void reflection_extension_factory(zval *object, const char *name_str) /* }}} */ /* {{{ reflection_parameter_factory */ -static void reflection_parameter_factory(zend_function *fptr, zval *closure_object, struct _zend_arg_info *arg_info, uint32_t offset, bool required, zval *object) +static void reflection_parameter_factory(zend_function *fptr, zend_object *closure_object, struct _zend_arg_info *arg_info, uint32_t offset, bool required, zval *object) { reflection_object *intern; parameter_reference *reference; @@ -1352,7 +1353,7 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje intern->ref_type = REF_TYPE_PARAMETER; intern->ce = fptr->common.scope; if (closure_object) { - ZVAL_OBJ_COPY(&intern->obj, Z_OBJ_P(closure_object)); + ZVAL_OBJ_COPY(&intern->obj, closure_object); } prop_name = reflection_prop_name(object); @@ -1367,7 +1368,9 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje typedef enum { NAMED_TYPE = 0, UNION_TYPE = 1, - INTERSECTION_TYPE = 2 + INTERSECTION_TYPE = 2, + SELF_PARENT_TYPE = 3, + STATIC_TYPE = 4 } reflection_type_kind; /* For backwards compatibility reasons, we need to return T|null style unions @@ -1393,6 +1396,14 @@ static reflection_type_kind get_type_kind(zend_type type) { if (type_mask_without_null != 0) { return UNION_TYPE; } + + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(type)); + if ( + zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "self") + || zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "parent") + ) { + return SELF_PARENT_TYPE; + } return NAMED_TYPE; } if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) { @@ -1402,12 +1413,21 @@ static reflection_type_kind get_type_kind(zend_type type) { if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) { return UNION_TYPE; } + + if (type_mask_without_null == MAY_BE_STATIC) { + return STATIC_TYPE; + } return NAMED_TYPE; } -/* {{{ reflection_type_factory */ -static void reflection_type_factory(zend_type type, zval *object, bool legacy_behavior) -{ +/* ReflectionType private constructor + * The object_ce is used to be able to resolve back the "self", "parent", and "static" ReflectionNamedTypes + * This can be NULL, e.g. when constructing types of a free function + */ +static void reflection_type_factory( + zend_type type, zval *object, bool legacy_behavior, + zend_class_entry *object_ce, zend_object *closure_object +) { reflection_object *intern; type_reference *reference; reflection_type_kind type_kind = get_type_kind(type); @@ -1424,6 +1444,10 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be case NAMED_TYPE: reflection_instantiate(reflection_named_type_ptr, object); break; + case SELF_PARENT_TYPE: + case STATIC_TYPE: + reflection_instantiate(reflection_relative_class_type_ptr, object); + break; EMPTY_SWITCH_DEFAULT_CASE(); } @@ -1433,6 +1457,22 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed && !is_only_null; intern->ptr = reference; intern->ref_type = REF_TYPE_TYPE; + intern->ce = object_ce; + ZVAL_UNDEF(&intern->obj); + + if (closure_object) { + zend_function *fptr = NULL; + zend_class_entry *called_scope = NULL; + zend_object *this_obj = NULL; + closure_object->ce->default_object_handlers->get_closure(closure_object, &called_scope, &fptr, &this_obj, /* check_only */ true); + /* it was bound to something, assign ce to be zend_ce_closure as static can be completely different than self/parent + * and needs to be resolved depending on the type. */ + if (type_kind == STATIC_TYPE) { + intern->ce = called_scope; + } else { + intern->ce = fptr->common.scope; + } + } /* Property types may be resolved during the lifetime of the ReflectionType. * If we reference a string, make sure it doesn't get released. However, only @@ -1443,26 +1483,32 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be zend_string_addref(ZEND_TYPE_NAME(type)); } } -/* }}} */ -/* {{{ reflection_function_factory */ -static void reflection_function_factory(zend_function *function, zval *closure_object, zval *object) +static void reflection_function_create_common(zend_function *function, zend_object *closure_object, zval *object) { - reflection_object *intern; - reflection_instantiate(reflection_function_ptr, object); - intern = Z_REFLECTION_P(object); + reflection_object *intern = Z_REFLECTION_P(object); + intern->ptr = function; intern->ref_type = REF_TYPE_FUNCTION; intern->ce = NULL; if (closure_object) { - ZVAL_OBJ_COPY(&intern->obj, Z_OBJ_P(closure_object)); + ZVAL_OBJ_COPY(&intern->obj, closure_object); + } else { + ZVAL_UNDEF(&intern->obj); } ZVAL_STR_COPY(reflection_prop_name(object), function->common.function_name); } + +/* {{{ reflection_function_factory */ +static void reflection_function_factory(zend_function *function, zend_object *closure_object, zval *object) +{ + reflection_instantiate(reflection_function_ptr, object); + reflection_function_create_common(function, closure_object, object); +} /* }}} */ /* {{{ reflection_method_factory */ -static void reflection_method_factory(zend_class_entry *ce, zend_function *method, zval *closure_object, zval *object) +static void reflection_method_factory(zend_class_entry *ce, zend_function *method, zend_object *closure_object, zval *object) { reflection_object *intern; @@ -1472,7 +1518,7 @@ static void reflection_method_factory(zend_class_entry *ce, zend_function *metho intern->ref_type = REF_TYPE_FUNCTION; intern->ce = ce; if (closure_object) { - ZVAL_OBJ_COPY(&intern->obj, Z_OBJ_P(closure_object)); + ZVAL_OBJ_COPY(&intern->obj, closure_object); } ZVAL_STR_COPY(reflection_prop_name(object), method->common.function_name); @@ -1610,14 +1656,9 @@ ZEND_METHOD(Reflection, getModifierNames) /* {{{ Constructor. Throws an Exception in case the given function does not exist */ ZEND_METHOD(ReflectionFunction, __construct) { - zval *object; zend_object *closure_obj = NULL; - reflection_object *intern; + zend_string *fname = NULL; zend_function *fptr; - zend_string *fname, *lcname; - - object = ZEND_THIS; - intern = Z_REFLECTION_P(object); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJ_OF_CLASS_OR_STR(closure_obj, zend_ce_closure, fname) @@ -1626,6 +1667,7 @@ ZEND_METHOD(ReflectionFunction, __construct) if (closure_obj) { fptr = (zend_function*)zend_get_closure_method_def(closure_obj); } else { + zend_string *lcname; if (UNEXPECTED(ZSTR_VAL(fname)[0] == '\\')) { /* Ignore leading "\" */ ALLOCA_FLAG(use_heap) @@ -1646,20 +1688,14 @@ ZEND_METHOD(ReflectionFunction, __construct) } } + /* If the constructor is manually called again we need to free the storage */ + reflection_object *intern = Z_REFLECTION_P(ZEND_THIS); if (intern->ptr) { zval_ptr_dtor(&intern->obj); - zval_ptr_dtor(reflection_prop_name(object)); + zval_ptr_dtor(reflection_prop_name(ZEND_THIS)); } - ZVAL_STR_COPY(reflection_prop_name(object), fptr->common.function_name); - intern->ptr = fptr; - intern->ref_type = REF_TYPE_FUNCTION; - if (closure_obj) { - ZVAL_OBJ_COPY(&intern->obj, closure_obj); - } else { - ZVAL_UNDEF(&intern->obj); - } - intern->ce = NULL; + reflection_function_create_common(fptr, closure_obj, ZEND_THIS); } /* }}} */ @@ -2039,7 +2075,7 @@ ZEND_METHOD(ReflectionFunction, invoke) fcc.object = NULL; if (!Z_ISUNDEF(intern->obj)) { - Z_OBJ_HT(intern->obj)->get_closure( + Z_OBJ_HANDLER(intern->obj, get_closure)( Z_OBJ(intern->obj), &fcc.called_scope, &fcc.function_handler, &fcc.object, 0); } @@ -2078,7 +2114,7 @@ ZEND_METHOD(ReflectionFunction, invokeArgs) fcc.object = NULL; if (!Z_ISUNDEF(intern->obj)) { - Z_OBJ_HT(intern->obj)->get_closure( + Z_OBJ_HANDLER(intern->obj, get_closure)( Z_OBJ(intern->obj), &fcc.called_scope, &fcc.function_handler, &fcc.object, 0); } @@ -2181,7 +2217,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getParameters) reflection_parameter_factory( _copy_function(fptr), - Z_ISUNDEF(intern->obj) ? NULL : &intern->obj, + Z_ISUNDEF(intern->obj) ? NULL : Z_OBJ(intern->obj), arg_info, i, i < fptr->common.required_num_args, @@ -2363,9 +2399,7 @@ ZEND_METHOD(ReflectionGenerator, getFunction) REFLECTION_CHECK_VALID_GENERATOR(ex) if (ex->func->common.fn_flags & ZEND_ACC_CLOSURE) { - zval closure; - ZVAL_OBJ(&closure, ZEND_CLOSURE_OBJECT(ex->func)); - reflection_function_factory(ex->func, &closure, return_value); + reflection_function_factory(ex->func, ZEND_CLOSURE_OBJECT(ex->func), return_value); } else if (ex->func->op_array.scope) { reflection_method_factory(ex->func->op_array.scope, ex->func, NULL, return_value); } else { @@ -2648,9 +2682,9 @@ ZEND_METHOD(ReflectionParameter, getDeclaringFunction) GET_REFLECTION_OBJECT_PTR(param); if (!param->fptr->common.scope) { - reflection_function_factory(_copy_function(param->fptr), Z_ISUNDEF(intern->obj)? NULL : &intern->obj, return_value); + reflection_function_factory(_copy_function(param->fptr), Z_ISUNDEF(intern->obj)? NULL : Z_OBJ(intern->obj), return_value); } else { - reflection_method_factory(param->fptr->common.scope, _copy_function(param->fptr), Z_ISUNDEF(intern->obj)? NULL : &intern->obj, return_value); + reflection_method_factory(param->fptr->common.scope, _copy_function(param->fptr), Z_ISUNDEF(intern->obj)? NULL : Z_OBJ(intern->obj), return_value); } } /* }}} */ @@ -2698,9 +2732,7 @@ ZEND_METHOD(ReflectionParameter, getClass) * TODO: Think about moving these checks to the compiler or some sort of * lint-mode. */ - zend_string *class_name; - - class_name = ZEND_TYPE_NAME(param->arg_info->type); + zend_string *class_name = ZEND_TYPE_NAME(param->arg_info->type); if (zend_string_equals_literal_ci(class_name, "self")) { ce = param->fptr->common.scope; if (!ce) { @@ -2763,7 +2795,13 @@ ZEND_METHOD(ReflectionParameter, getType) if (!ZEND_TYPE_IS_SET(param->arg_info->type)) { RETURN_NULL(); } - reflection_type_factory(param->arg_info->type, return_value, 1); + reflection_type_factory( + param->arg_info->type, + return_value, + /* legacy_behavior */ true, + intern->ce, + Z_ISUNDEF(intern->obj) ? NULL : Z_OBJ(intern->obj) + ); } /* }}} */ @@ -3135,19 +3173,81 @@ ZEND_METHOD(ReflectionNamedType, isBuiltin) } /* }}} */ -static void append_type(zval *return_value, zend_type type) { +/* {{{ Resolve a relative class type to a proper named type */ +ZEND_METHOD(ReflectionRelativeClassType, resolveToNamedType) +{ + reflection_object *intern; + type_reference *param; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(param); + + /* Unbound closures can have relative class types that we cannot resolve */ + if (!intern->ce) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Cannot resolve relative class name for a static closure"); + RETURN_THROWS(); + } + + if (intern->ce->ce_flags & ZEND_ACC_TRAIT) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Cannot resolve relative class name for a trait"); + RETURN_THROWS(); + } + + /* Support for legacy behaviour of nullable types and ReflectionNamedType */ + bool allows_null = ZEND_TYPE_PURE_MASK(param->type) & MAY_BE_NULL; + zend_type resolved_type; + /* For static resolved name is the name of the class */ + if (intern->ce->ce_flags & ZEND_ACC_INTERFACE) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Cannot resolve \"static\" type of an interface"); + RETURN_THROWS(); + } + + /* The only cases where self/parent are not resolved at compile time is in Closures */ + if (ZEND_TYPE_IS_COMPLEX(param->type) && zend_string_equals_literal_ci(ZEND_TYPE_NAME(param->type), "parent")) { + if (!intern->ce->parent) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Cannot resolve \"parent\" type when class has no parent"); + RETURN_THROWS(); + } + resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(intern->ce->parent->name, allows_null, /* extra flags */ 0); + } else { + resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(intern->ce->name, allows_null, /* extra flags */ 0); + } + + reflection_type_factory( + resolved_type, + return_value, + /* legacy_behavior */ true, + intern->ce, + /* closure_object */ NULL + ); +} +/* }}} */ + +static void append_type(zval *return_value, zend_type type, zend_class_entry *object_ce, zend_object *closure_obj) { zval reflection_type; /* Drop iterable BC bit for type list */ if (ZEND_TYPE_IS_ITERABLE_FALLBACK(type)) { ZEND_TYPE_FULL_MASK(type) &= ~_ZEND_TYPE_ITERABLE_BIT; } - reflection_type_factory(type, &reflection_type, 0); + reflection_type_factory( + type, + &reflection_type, + /* legacy_behavior */ false, + object_ce, + closure_obj + ); zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &reflection_type); } -static void append_type_mask(zval *return_value, uint32_t type_mask) { - append_type(return_value, (zend_type) ZEND_TYPE_INIT_MASK(type_mask)); +static void append_type_mask(zval *return_value, uint32_t type_mask, zend_class_entry *object_ce, zend_object *closure_obj) { + append_type(return_value, (zend_type) ZEND_TYPE_INIT_MASK(type_mask), object_ce, closure_obj); } /* {{{ Returns the types that are part of this union type */ @@ -3166,46 +3266,46 @@ ZEND_METHOD(ReflectionUnionType, getTypes) if (ZEND_TYPE_HAS_LIST(param->type)) { zend_type *list_type; ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), list_type) { - append_type(return_value, *list_type); + append_type(return_value, *list_type, /* object_ce */ intern->ce, /* closure_obj */ Z_ISUNDEF(intern->obj) ? NULL : Z_OBJ(intern->obj)); } ZEND_TYPE_LIST_FOREACH_END(); } else if (ZEND_TYPE_HAS_NAME(param->type)) { zend_string *name = ZEND_TYPE_NAME(param->type); - append_type(return_value, (zend_type) ZEND_TYPE_INIT_CLASS(name, 0, 0)); + append_type(return_value, (zend_type) ZEND_TYPE_INIT_CLASS(name, /* allow_null */ false, /* extra flags */ 0), /* object_ce */ intern->ce, /* closure_obj */ Z_ISUNDEF(intern->obj) ? NULL : Z_OBJ(intern->obj)); } type_mask = ZEND_TYPE_PURE_MASK(param->type); ZEND_ASSERT(!(type_mask & MAY_BE_VOID)); ZEND_ASSERT(!(type_mask & MAY_BE_NEVER)); if (type_mask & MAY_BE_STATIC) { - append_type_mask(return_value, MAY_BE_STATIC); + append_type_mask(return_value, MAY_BE_STATIC, /* object_ce */ intern->ce, /* closure_obj */ Z_ISUNDEF(intern->obj) ? NULL : Z_OBJ(intern->obj)); } if (type_mask & MAY_BE_CALLABLE) { - append_type_mask(return_value, MAY_BE_CALLABLE); + append_type_mask(return_value, MAY_BE_CALLABLE, /* object_ce */ NULL, /* closure_obj */ NULL); } if (type_mask & MAY_BE_OBJECT) { - append_type_mask(return_value, MAY_BE_OBJECT); + append_type_mask(return_value, MAY_BE_OBJECT, /* object_ce */ NULL, /* closure_obj */ NULL); } if (type_mask & MAY_BE_ARRAY) { - append_type_mask(return_value, MAY_BE_ARRAY); + append_type_mask(return_value, MAY_BE_ARRAY, /* object_ce */ NULL, /* closure_obj */ NULL); } if (type_mask & MAY_BE_STRING) { - append_type_mask(return_value, MAY_BE_STRING); + append_type_mask(return_value, MAY_BE_STRING, /* object_ce */ NULL, /* closure_obj */ NULL); } if (type_mask & MAY_BE_LONG) { - append_type_mask(return_value, MAY_BE_LONG); + append_type_mask(return_value, MAY_BE_LONG, /* object_ce */ NULL, /* closure_obj */ NULL); } if (type_mask & MAY_BE_DOUBLE) { - append_type_mask(return_value, MAY_BE_DOUBLE); + append_type_mask(return_value, MAY_BE_DOUBLE, /* object_ce */ NULL, /* closure_obj */ NULL); } if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { - append_type_mask(return_value, MAY_BE_BOOL); + append_type_mask(return_value, MAY_BE_BOOL, /* object_ce */ NULL, /* closure_obj */ NULL); } else if (type_mask & MAY_BE_TRUE) { - append_type_mask(return_value, MAY_BE_TRUE); + append_type_mask(return_value, MAY_BE_TRUE, /* object_ce */ NULL, /* closure_obj */ NULL); } else if (type_mask & MAY_BE_FALSE) { - append_type_mask(return_value, MAY_BE_FALSE); + append_type_mask(return_value, MAY_BE_FALSE, /* object_ce */ NULL, /* closure_obj */ NULL); } if (type_mask & MAY_BE_NULL) { - append_type_mask(return_value, MAY_BE_NULL); + append_type_mask(return_value, MAY_BE_NULL, /* object_ce */ NULL, /* closure_obj */ NULL); } } /* }}} */ @@ -3226,7 +3326,8 @@ ZEND_METHOD(ReflectionIntersectionType, getTypes) array_init(return_value); ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), list_type) { - append_type(return_value, *list_type); + /* Intersection types cannot have a relative class type */ + append_type(return_value, *list_type, /* object_ce */ intern->ce, /* closure_obj */ NULL); } ZEND_TYPE_LIST_FOREACH_END(); } /* }}} */ @@ -3649,7 +3750,13 @@ ZEND_METHOD(ReflectionFunctionAbstract, getReturnType) RETURN_NULL(); } - reflection_type_factory(fptr->common.arg_info[-1].type, return_value, 1); + reflection_type_factory( + fptr->common.arg_info[-1].type, + return_value, + /* legacy_behavior */ true, + intern->ce, + Z_ISUNDEF(intern->obj) ? NULL : Z_OBJ(intern->obj) + ); } /* }}} */ @@ -3685,7 +3792,13 @@ ZEND_METHOD(ReflectionFunctionAbstract, getTentativeReturnType) RETURN_NULL(); } - reflection_type_factory(fptr->common.arg_info[-1].type, return_value, 1); + reflection_type_factory( + fptr->common.arg_info[-1].type, + return_value, + /* legacy_behavior */ true, + intern->ce, + Z_ISUNDEF(intern->obj) ? NULL : Z_OBJ(intern->obj) + ); } /* }}} */ @@ -3906,7 +4019,13 @@ ZEND_METHOD(ReflectionClassConstant, getType) RETURN_NULL(); } - reflection_type_factory(ref->type, return_value, 1); + reflection_type_factory( + ref->type, + return_value, + /* legacy_behavior */ true, + intern->ce, + /* closure_object */ NULL + ); } /* Returns whether class constant has a type */ @@ -5926,7 +6045,13 @@ ZEND_METHOD(ReflectionProperty, getType) RETURN_NULL(); } - reflection_type_factory(ref->prop->type, return_value, 1); + reflection_type_factory( + ref->prop->type, + return_value, + /* legacy_behavior */ true, + intern->ce, + /* closure_object */ NULL + ); } /* }}} */ @@ -7015,7 +7140,13 @@ ZEND_METHOD(ReflectionEnum, getBackingType) RETURN_NULL(); } else { zend_type type = ZEND_TYPE_INIT_CODE(ce->enum_backing_type, 0, 0); - reflection_type_factory(type, return_value, 0); + reflection_type_factory( + type, + return_value, + /* legacy_behavior */ false, + /* object_ce */ NULL, + /* closure_object */ NULL + ); } } @@ -7409,6 +7540,10 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_named_type_ptr->create_object = reflection_objects_new; reflection_named_type_ptr->default_object_handlers = &reflection_object_handlers; + reflection_relative_class_type_ptr = register_class_ReflectionRelativeClassType(reflection_named_type_ptr); + reflection_relative_class_type_ptr->create_object = reflection_objects_new; + reflection_relative_class_type_ptr->default_object_handlers = &reflection_object_handlers; + reflection_union_type_ptr = register_class_ReflectionUnionType(reflection_type_ptr); reflection_union_type_ptr->create_object = reflection_objects_new; reflection_union_type_ptr->default_object_handlers = &reflection_object_handlers; diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h index 6420b04520aa6..bb1b6771e94e7 100644 --- a/ext/reflection/php_reflection.h +++ b/ext/reflection/php_reflection.h @@ -35,6 +35,7 @@ extern PHPAPI zend_class_entry *reflection_function_ptr; extern PHPAPI zend_class_entry *reflection_parameter_ptr; extern PHPAPI zend_class_entry *reflection_type_ptr; extern PHPAPI zend_class_entry *reflection_named_type_ptr; +extern PHPAPI zend_class_entry *reflection_relative_class_type_ptr; extern PHPAPI zend_class_entry *reflection_class_ptr; extern PHPAPI zend_class_entry *reflection_object_ptr; extern PHPAPI zend_class_entry *reflection_method_ptr; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index c9ce811bfeeb3..5ffd2b41d0a9b 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -661,6 +661,11 @@ public function getName(): string {} public function isBuiltin(): bool {} } +class ReflectionRelativeClassType extends ReflectionNamedType +{ + public function resolveToNamedType(): ReflectionNamedType {} +} + class ReflectionUnionType extends ReflectionType { public function getTypes(): array {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index e06469db82b3a..17bb35897ae66 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c81572d388f2539d861df717b3cc8c0491fe72a0 */ + * Stub hash: e0524ce7c6db33c9868e9e70cb947b3754c59032 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -482,6 +482,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionNamedType_isBuiltin arginfo_class_ReflectionFunctionAbstract_inNamespace +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionRelativeClassType_resolveToNamedType, 0, 0, ReflectionNamedType, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionUnionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables #define arginfo_class_ReflectionIntersectionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables @@ -811,6 +814,7 @@ ZEND_METHOD(ReflectionType, allowsNull); ZEND_METHOD(ReflectionType, __toString); ZEND_METHOD(ReflectionNamedType, getName); ZEND_METHOD(ReflectionNamedType, isBuiltin); +ZEND_METHOD(ReflectionRelativeClassType, resolveToNamedType); ZEND_METHOD(ReflectionUnionType, getTypes); ZEND_METHOD(ReflectionIntersectionType, getTypes); ZEND_METHOD(ReflectionExtension, __construct); @@ -1113,6 +1117,11 @@ static const zend_function_entry class_ReflectionNamedType_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_ReflectionRelativeClassType_methods[] = { + ZEND_ME(ReflectionRelativeClassType, resolveToNamedType, arginfo_class_ReflectionRelativeClassType_resolveToNamedType, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static const zend_function_entry class_ReflectionUnionType_methods[] = { ZEND_ME(ReflectionUnionType, getTypes, arginfo_class_ReflectionUnionType_getTypes, ZEND_ACC_PUBLIC) ZEND_FE_END @@ -1539,6 +1548,16 @@ static zend_class_entry *register_class_ReflectionNamedType(zend_class_entry *cl return class_entry; } +static zend_class_entry *register_class_ReflectionRelativeClassType(zend_class_entry *class_entry_ReflectionNamedType) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionRelativeClassType", class_ReflectionRelativeClassType_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionNamedType); + + return class_entry; +} + static zend_class_entry *register_class_ReflectionUnionType(zend_class_entry *class_entry_ReflectionType) { zend_class_entry ce, *class_entry; diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index b7537f170a502..1008c9ea9ab0f 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -54,78 +54,83 @@ array(25) { ["name"]=> string(19) "ReflectionNamedType" } - ["ReflectionUnionType"]=> + ["ReflectionRelativeClassType"]=> object(ReflectionClass)#11 (1) { + ["name"]=> + string(27) "ReflectionRelativeClassType" + } + ["ReflectionUnionType"]=> + object(ReflectionClass)#12 (1) { ["name"]=> string(19) "ReflectionUnionType" } ["ReflectionIntersectionType"]=> - object(ReflectionClass)#12 (1) { + object(ReflectionClass)#13 (1) { ["name"]=> string(26) "ReflectionIntersectionType" } ["ReflectionMethod"]=> - object(ReflectionClass)#13 (1) { + object(ReflectionClass)#14 (1) { ["name"]=> string(16) "ReflectionMethod" } ["ReflectionClass"]=> - object(ReflectionClass)#14 (1) { + object(ReflectionClass)#15 (1) { ["name"]=> string(15) "ReflectionClass" } ["ReflectionObject"]=> - object(ReflectionClass)#15 (1) { + object(ReflectionClass)#16 (1) { ["name"]=> string(16) "ReflectionObject" } ["ReflectionProperty"]=> - object(ReflectionClass)#16 (1) { + object(ReflectionClass)#17 (1) { ["name"]=> string(18) "ReflectionProperty" } ["ReflectionClassConstant"]=> - object(ReflectionClass)#17 (1) { + object(ReflectionClass)#18 (1) { ["name"]=> string(23) "ReflectionClassConstant" } ["ReflectionExtension"]=> - object(ReflectionClass)#18 (1) { + object(ReflectionClass)#19 (1) { ["name"]=> string(19) "ReflectionExtension" } ["ReflectionZendExtension"]=> - object(ReflectionClass)#19 (1) { + object(ReflectionClass)#20 (1) { ["name"]=> string(23) "ReflectionZendExtension" } ["ReflectionReference"]=> - object(ReflectionClass)#20 (1) { + object(ReflectionClass)#21 (1) { ["name"]=> string(19) "ReflectionReference" } ["ReflectionAttribute"]=> - object(ReflectionClass)#21 (1) { + object(ReflectionClass)#22 (1) { ["name"]=> string(19) "ReflectionAttribute" } ["ReflectionEnum"]=> - object(ReflectionClass)#22 (1) { + object(ReflectionClass)#23 (1) { ["name"]=> string(14) "ReflectionEnum" } ["ReflectionEnumUnitCase"]=> - object(ReflectionClass)#23 (1) { + object(ReflectionClass)#24 (1) { ["name"]=> string(22) "ReflectionEnumUnitCase" } ["ReflectionEnumBackedCase"]=> - object(ReflectionClass)#24 (1) { + object(ReflectionClass)#25 (1) { ["name"]=> string(24) "ReflectionEnumBackedCase" } ["ReflectionFiber"]=> - object(ReflectionClass)#25 (1) { + object(ReflectionClass)#26 (1) { ["name"]=> string(15) "ReflectionFiber" } diff --git a/ext/reflection/tests/ReflectionFunction_construct_recall.phpt b/ext/reflection/tests/ReflectionFunction_construct_recall.phpt new file mode 100644 index 0000000000000..ed11827a9dd1a --- /dev/null +++ b/ext/reflection/tests/ReflectionFunction_construct_recall.phpt @@ -0,0 +1,22 @@ +--TEST-- +ReflectionFunction constructor called manually after instantiation +--FILE-- + $a . 'hello'; + +$r = new ReflectionFunction($fn1); +var_dump($r); +$r->__construct('strpos'); +var_dump($r); + +?> +--EXPECTF-- +object(ReflectionFunction)#2 (1) { + ["name"]=> + string(%d) "%s" +} +object(ReflectionFunction)#2 (1) { + ["name"]=> + string(6) "strpos" +} diff --git a/ext/reflection/tests/bug80190.phpt b/ext/reflection/tests/bug80190.phpt index 16f2fbba42729..5cf390c3e5266 100644 --- a/ext/reflection/tests/bug80190.phpt +++ b/ext/reflection/tests/bug80190.phpt @@ -31,13 +31,13 @@ foreach ((new ReflectionClass(C::class))->getMethods() as $method) { print ' $method->getReturnType()->getTypes() returns an array with ' . count($method->getReturnType()->getTypes()) . ' element(s)' . PHP_EOL; print ' type(s) in union: '; - + $types = []; foreach ($method->getReturnType()->getTypes() as $type) { $types[] = get_class($type) . "($type)"; } - + print join(', ', $types) . PHP_EOL; } @@ -48,20 +48,20 @@ foreach ((new ReflectionClass(C::class))->getMethods() as $method) { --EXPECT-- C::a() $method->getReturnType() returns ReflectionNamedType - $method->getReturnType()->__toString() returns self + $method->getReturnType()->__toString() returns C C::b() $method->getReturnType() returns ReflectionUnionType - $method->getReturnType()->__toString() returns stdClass|self + $method->getReturnType()->__toString() returns stdClass|C $method->getReturnType()->getTypes() returns an array with 2 element(s) - type(s) in union: ReflectionNamedType(stdClass), ReflectionNamedType(self) + type(s) in union: ReflectionNamedType(stdClass), ReflectionNamedType(C) C::c() - $method->getReturnType() returns ReflectionNamedType + $method->getReturnType() returns ReflectionRelativeClassType $method->getReturnType()->__toString() returns static C::d() $method->getReturnType() returns ReflectionUnionType $method->getReturnType()->__toString() returns stdClass|static $method->getReturnType()->getTypes() returns an array with 2 element(s) - type(s) in union: ReflectionNamedType(stdClass), ReflectionNamedType(static) + type(s) in union: ReflectionNamedType(stdClass), ReflectionRelativeClassType(static) diff --git a/ext/reflection/tests/types/ReflectionType_001.phpt b/ext/reflection/tests/types/ReflectionType_001.phpt index 3441878f155d7..33b3bbffac2a8 100644 --- a/ext/reflection/tests/types/ReflectionType_001.phpt +++ b/ext/reflection/tests/types/ReflectionType_001.phpt @@ -166,12 +166,12 @@ string(10) "SplSubject" bool(true) bool(false) bool(false) -string(4) "self" +string(1) "c" ** Method 2 - parameter 0 bool(true) bool(false) bool(false) -string(6) "parent" +string(8) "stdClass" ** Method 3 - parameter 0 bool(true) bool(false) @@ -195,12 +195,12 @@ string(3) "int" bool(true) bool(false) bool(false) -string(4) "self" +string(1) "c" ** Function/method return type 4 bool(true) bool(false) bool(false) -string(6) "parent" +string(8) "stdClass" ** Function/method return type 5 bool(true) bool(false) diff --git a/ext/reflection/tests/types/relative_class_types/classes_return_type.phpt b/ext/reflection/tests/types/relative_class_types/classes_return_type.phpt new file mode 100644 index 0000000000000..85609a81d53ba --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/classes_return_type.phpt @@ -0,0 +1,104 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent, static) in classes +--FILE-- +getMethods(); + foreach ($methods as $method) { + echo "\tMethod: ", $method->name, PHP_EOL; + $type = $method->getReturnType(); + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + if ($type instanceof ReflectionRelativeClassType) { + $resolvedType = $type->resolveToNamedType(); + echo "\t\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } + + foreach ($instances as $arg) { + try { + $instance->{$method->name}($arg); + } catch (\TypeError $e) { + echo "\t\t\t\t", $e->getMessage(), PHP_EOL; + } + } + } +} + +?> +--EXPECT-- +Class: A +Class: B + Method: foo + Type: B + Instance of: ReflectionNamedType + B::foo(): Return value must be of type B, A returned +Class: C + Method: bar + Type: B + Instance of: ReflectionNamedType + C::bar(): Return value must be of type B, A returned + Method: ping + Type: C + Instance of: ReflectionNamedType + C::ping(): Return value must be of type C, A returned + C::ping(): Return value must be of type C, B returned + Method: pong + Type: static + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::pong(): Return value must be of type C, A returned + C::pong(): Return value must be of type C, B returned + Method: foo + Type: B + Instance of: ReflectionNamedType + B::foo(): Return value must be of type B, A returned +Class: D + Method: bar + Type: B + Instance of: ReflectionNamedType + C::bar(): Return value must be of type B, A returned + Method: ping + Type: C + Instance of: ReflectionNamedType + C::ping(): Return value must be of type C, A returned + C::ping(): Return value must be of type C, B returned + Method: pong + Type: static + Instance of: ReflectionRelativeClassType + Resolved Type: D + Instance of: ReflectionNamedType + C::pong(): Return value must be of type D, A returned + C::pong(): Return value must be of type D, B returned + C::pong(): Return value must be of type D, C returned + Method: foo + Type: B + Instance of: ReflectionNamedType + B::foo(): Return value must be of type B, A returned diff --git a/ext/reflection/tests/types/relative_class_types/closure_automatic_bound_class.phpt b/ext/reflection/tests/types/relative_class_types/closure_automatic_bound_class.phpt new file mode 100644 index 0000000000000..bef1a248bbb10 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/closure_automatic_bound_class.phpt @@ -0,0 +1,54 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) in Closure bound to class automatically +--FILE-- +getClosureCalledClass()->name, PHP_EOL; + $type = $rc->getReturnType(); + echo "Type: ", $type, PHP_EOL; + echo "Instance of: ", $type::class, PHP_EOL; + try { + $resolvedType = $type->resolveToNamedType(); + echo "\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; + } + } +} + +$b = new C(); +$b->test(); + + +?> +--EXPECT-- +Bound to: C +Type: self +Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType +Bound to: C +Type: parent +Instance of: ReflectionRelativeClassType + Resolved Type: A + Instance of: ReflectionNamedType +Bound to: C +Type: static +Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType diff --git a/ext/reflection/tests/types/relative_class_types/closure_bound_resolvable_relative_types.phpt b/ext/reflection/tests/types/relative_class_types/closure_bound_resolvable_relative_types.phpt new file mode 100644 index 0000000000000..75285c0935f77 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/closure_bound_resolvable_relative_types.phpt @@ -0,0 +1,92 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) in Closure bound to class with parent +--FILE-- +bindTo($b, $scope); + $rc = new ReflectionFunction($fn); + echo "Bound to: ", $rc->getClosureCalledClass()->name, PHP_EOL; + $type = $rc->getReturnType(); + echo "Type: ", $type, PHP_EOL; + echo "Instance of: ", $type::class, PHP_EOL; + try { + $resolvedType = $type->resolveToNamedType(); + echo "\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; + } + } +} + +?> +--EXPECT-- +Scope as "static" +Bound to: B +Type: self +Instance of: ReflectionRelativeClassType + Resolved Type: Closure + Instance of: ReflectionNamedType +Bound to: B +Type: parent +Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve "parent" type when class has no parent +Bound to: B +Type: static +Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType +Scope as "B" +Bound to: B +Type: self +Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType +Bound to: B +Type: parent +Instance of: ReflectionRelativeClassType + Resolved Type: A + Instance of: ReflectionNamedType +Bound to: B +Type: static +Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType +Scope as "A" +Bound to: B +Type: self +Instance of: ReflectionRelativeClassType + Resolved Type: A + Instance of: ReflectionNamedType +Bound to: B +Type: parent +Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve "parent" type when class has no parent +Bound to: B +Type: static +Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType diff --git a/ext/reflection/tests/types/relative_class_types/closure_bound_resolvable_relative_types_002.phpt b/ext/reflection/tests/types/relative_class_types/closure_bound_resolvable_relative_types_002.phpt new file mode 100644 index 0000000000000..354d6c93857ae --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/closure_bound_resolvable_relative_types_002.phpt @@ -0,0 +1,22 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) in Closure bound to class with parent +--FILE-- +getReturnType()->resolveToNamedType()->__toString(); + +// Prints B == A +printf("%s == %s ?\n", $f()::class, $resolved); + +?> +--EXPECT-- +B == B ? diff --git a/ext/reflection/tests/types/relative_class_types/closure_bound_unresolvable_parent_type.phpt b/ext/reflection/tests/types/relative_class_types/closure_bound_unresolvable_parent_type.phpt new file mode 100644 index 0000000000000..3d924a075abe6 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/closure_bound_unresolvable_parent_type.phpt @@ -0,0 +1,31 @@ +--TEST-- +ReflectionTypes of relative class type parent in Closure bound to class with no parent +--FILE-- +bindTo($a); +$rc = new ReflectionFunction($fn); +echo "Bound to: ", $rc->getClosureCalledClass()->name, PHP_EOL; +$type = $rc->getReturnType(); +echo "Type: ", $type, PHP_EOL; +echo "Instance of: ", $type::class, PHP_EOL; +try { + $resolvedType = $type->resolveToNamedType(); + echo "\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\tInstance of: ", $resolvedType::class, PHP_EOL; +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Bound to: A +Type: parent +Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve "parent" type when class has no parent diff --git a/ext/reflection/tests/types/relative_class_types/closure_unresolvable_relative_types.phpt b/ext/reflection/tests/types/relative_class_types/closure_unresolvable_relative_types.phpt new file mode 100644 index 0000000000000..64864ff8b5696 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/closure_unresolvable_relative_types.phpt @@ -0,0 +1,40 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) from traits in classes, variadic parameter DNF types +--FILE-- +getReturnType(); + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + try { + $resolvedType = $type->resolveToNamedType(); + echo "\t\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; + } +} + +?> +--EXPECT-- +Type: self + Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve relative class name for a static closure + Type: parent + Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve relative class name for a static closure + Type: static + Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve relative class name for a static closure diff --git a/ext/reflection/tests/types/relative_class_types/closures_bound_getDeclaringFunction_type_resolution.phpt b/ext/reflection/tests/types/relative_class_types/closures_bound_getDeclaringFunction_type_resolution.phpt new file mode 100644 index 0000000000000..dfe1517c7a002 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/closures_bound_getDeclaringFunction_type_resolution.phpt @@ -0,0 +1,58 @@ +--TEST-- +Check that a bound Closure retrieved via getDeclaringFunction() can still resolve types +--FILE-- +bindTo($c, B::class); + $rClosure = new ReflectionFunction($fn); + var_dump($rClosure->name == $name); + + $params = $rClosure->getParameters(); + unset ($rClosure); + + $closureFromParam = $params[0]->getDeclaringFunction(); + var_dump($closureFromParam->name == $name); + var_dump($closureFromParam::class); + $type = $closureFromParam->getReturnType(); + var_dump((string) $type); + $resolvedType = $type->resolveToNamedType(); + var_dump((string) $resolvedType); +} + +?> +--EXPECT-- +bool(true) +bool(true) +string(16) "ReflectionMethod" +string(4) "self" +string(1) "B" +bool(true) +bool(true) +string(16) "ReflectionMethod" +string(6) "parent" +string(1) "A" +bool(true) +bool(true) +string(16) "ReflectionMethod" +string(6) "static" +string(1) "C" diff --git a/ext/reflection/tests/types/relative_class_types/eval_trait_used_in_classes_parameters1_simple.phpt b/ext/reflection/tests/types/relative_class_types/eval_trait_used_in_classes_parameters1_simple.phpt new file mode 100644 index 0000000000000..b63fa6e6600a2 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/eval_trait_used_in_classes_parameters1_simple.phpt @@ -0,0 +1,83 @@ +--TEST-- +eval code: ReflectionTypes of relative class types (self, parent) from traits in classes, parameter types +--FILE-- +getMethods(); + foreach ($methods as $method) { + echo "\tMethod: ", $method->name, PHP_EOL; + $parameters = $method->getParameters(); + foreach ($parameters as $param) { + $type = $param->getType(); + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + + foreach ($instances as $arg) { + try { + $instance->{$method->name}($arg); + } catch (\TypeError $e) { + echo "\t\t\t\t", $e->getMessage(), PHP_EOL; + } + } + } + } +} +CODE; + +eval($code); + +?> +--EXPECTF-- +Class: A +Class: B +Class: C + Method: bar + Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type B, A given, called in %s on line %d + Method: ping + Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type C, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type C, B given, called in %s on line %d +Class: D + Method: bar + Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type B, A given, called in %s on line %d + Method: ping + Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type C, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type C, B given, called in %s on line %d diff --git a/ext/reflection/tests/types/relative_class_types/interfaces_return_type.phpt b/ext/reflection/tests/types/relative_class_types/interfaces_return_type.phpt new file mode 100644 index 0000000000000..d22a12ac45bf3 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/interfaces_return_type.phpt @@ -0,0 +1,78 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent, static) in interfaces +--FILE-- +getMethods(); + foreach ($methods as $method) { + echo "\tMethod: ", $method->name, PHP_EOL; + $type = $method->getReturnType(); + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + if ($type instanceof ReflectionRelativeClassType) { + try { + $resolvedType = $type->resolveToNamedType(); + echo "\t\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (ReflectionException $e) { + echo $e->getMessage(), PHP_EOL; + } + } + } +} + +?> +--EXPECT-- +Interface: A +Interface: B + Method: foo + Type: B + Instance of: ReflectionNamedType +Interface: C + Method: ping + Type: C + Instance of: ReflectionNamedType + Method: pong + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve "static" type of an interface + Method: foo + Type: B + Instance of: ReflectionNamedType +Interface: D + Method: ping + Type: C + Instance of: ReflectionNamedType + Method: pong + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve "static" type of an interface + Method: foo + Type: B + Instance of: ReflectionNamedType diff --git a/ext/reflection/tests/types/relative_class_types/trait_used_in_classes_return_type.phpt b/ext/reflection/tests/types/relative_class_types/trait_used_in_classes_return_type.phpt new file mode 100644 index 0000000000000..b21ceee43ea64 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/trait_used_in_classes_return_type.phpt @@ -0,0 +1,95 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent, static) from traits in classes, return types +--FILE-- +getMethods(); + foreach ($methods as $method) { + echo "\tMethod: ", $method->name, PHP_EOL; + $type = $method->getReturnType(); + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + if ($type instanceof ReflectionRelativeClassType) { + $resolvedType = $type->resolveToNamedType(); + echo "\t\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } + + foreach ($instances as $arg) { + try { + $instance->{$method->name}($arg); + } catch (\TypeError $e) { + echo "\t\t\t\t", $e->getMessage(), PHP_EOL; + } + } + } +} + +?> +--EXPECT-- +Class: A +Class: B +Class: C + Method: bar + Type: B + Instance of: ReflectionNamedType + C::bar(): Return value must be of type B, A returned + Method: ping + Type: C + Instance of: ReflectionNamedType + C::ping(): Return value must be of type C, A returned + C::ping(): Return value must be of type C, B returned + Method: pong + Type: static + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::pong(): Return value must be of type C, A returned + C::pong(): Return value must be of type C, B returned +Class: D + Method: bar + Type: B + Instance of: ReflectionNamedType + C::bar(): Return value must be of type B, A returned + Method: ping + Type: C + Instance of: ReflectionNamedType + C::ping(): Return value must be of type C, A returned + C::ping(): Return value must be of type C, B returned + Method: pong + Type: static + Instance of: ReflectionRelativeClassType + Resolved Type: D + Instance of: ReflectionNamedType + C::pong(): Return value must be of type D, A returned + C::pong(): Return value must be of type D, B returned + C::pong(): Return value must be of type D, C returned diff --git a/ext/reflection/tests/types/relative_class_types/traits_unresolvable_relative_types.phpt b/ext/reflection/tests/types/relative_class_types/traits_unresolvable_relative_types.phpt new file mode 100644 index 0000000000000..0586b8206d9b7 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types/traits_unresolvable_relative_types.phpt @@ -0,0 +1,42 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) from traits in classes, variadic parameter DNF types +--FILE-- +getMethods(); +foreach ($methods as $method) { + echo "Method: ", $method->name, PHP_EOL; + $type = $method->getReturnType(); + echo "\tType: ", $type, PHP_EOL; + echo "\tInstance of: ", $type::class, PHP_EOL; + try { + $resolvedType = $type->resolveToNamedType(); + echo "\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; + } +} + +?> +--EXPECT-- +Method: bar + Type: parent + Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve relative class name for a trait +Method: ping + Type: self + Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve relative class name for a trait +Method: pong + Type: static + Instance of: ReflectionRelativeClassType +ReflectionException: Cannot resolve relative class name for a trait diff --git a/ext/reflection/tests/types/unresolved_relative_class_types.phpt b/ext/reflection/tests/types/unresolved_relative_class_types.phpt new file mode 100644 index 0000000000000..af748494456de --- /dev/null +++ b/ext/reflection/tests/types/unresolved_relative_class_types.phpt @@ -0,0 +1,81 @@ +--TEST-- +ReflectionTypes of unresolved relative class types (self, parent, static) +--FILE-- +getReturnType(); + echo "\tType: ", $type, PHP_EOL; + echo "\tInstance of: ", $type::class, PHP_EOL; + try { + $resolvedType = $type->resolveToNamedType(); + echo "\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (ReflectionException $e) { + echo $e->getMessage(), PHP_EOL; + } +} + +echo PHP_EOL, "Trait:", PHP_EOL; +trait A { + public function bar(object $o): parent { return $o; } + public function ping(object $o): self { return $o; } + public function pong(object $o): static { return $o; } +} + +$rc = new ReflectionClass('A'); +$methods = $rc->getMethods(); +foreach ($methods as $method) { + echo "Method: ", $method->name, PHP_EOL; + $type = $method->getReturnType(); + echo "\tType: ", $type, PHP_EOL; + echo "\tInstance of: ", $type::class, PHP_EOL; + try { + $resolvedType = $type->resolveToNamedType(); + echo "\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (ReflectionException $e) { + echo $e->getMessage(), PHP_EOL; + } +} + +?> +--EXPECT-- +Closure: + Type: self + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a static closure +Closure: + Type: parent + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a static closure +Closure: + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a static closure + +Trait: +Method: bar + Type: parent + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a trait +Method: ping + Type: self + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a trait +Method: pong + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a trait diff --git a/ext/reflection/tests/types/unresolved_relative_class_types_union_type.phpt b/ext/reflection/tests/types/unresolved_relative_class_types_union_type.phpt new file mode 100644 index 0000000000000..e2547b49b495a --- /dev/null +++ b/ext/reflection/tests/types/unresolved_relative_class_types_union_type.phpt @@ -0,0 +1,91 @@ +--TEST-- +ReflectionTypes of unresolved relative class types (self, parent, static) in union types +--FILE-- +getReturnType(); + foreach ($type->getTypes() as $single_type) { + if (!($single_type instanceof ReflectionRelativeClassType)) { + continue; + } + echo "\tType: ", $single_type, PHP_EOL; + echo "\tInstance of: ", $single_type::class, PHP_EOL; + try { + $resolvedType = $single_type->resolveToNamedType(); + echo "\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (ReflectionException $e) { + echo $e->getMessage(), PHP_EOL; + } + } +} + +echo PHP_EOL, "Trait:", PHP_EOL; +trait A { + public function bar(object $o): X|parent { return $o; } + public function ping(object $o): X|self { return $o; } + public function pong(object $o): X|static { return $o; } +} + +$rc = new ReflectionClass('A'); +$methods = $rc->getMethods(); +foreach ($methods as $method) { + echo "Method: ", $method->name, PHP_EOL; + $type = $method->getReturnType(); + foreach ($type->getTypes() as $single_type) { + if (!($single_type instanceof ReflectionRelativeClassType)) { + continue; + } + echo "\tType: ", $single_type, PHP_EOL; + echo "\tInstance of: ", $single_type::class, PHP_EOL; + try { + $resolvedType = $single_type->resolveToNamedType(); + echo "\t\tResolved Type: ", $resolvedType, PHP_EOL; + echo "\t\tInstance of: ", $resolvedType::class, PHP_EOL; + } catch (ReflectionException $e) { + echo $e->getMessage(), PHP_EOL; + } + } +} + +?> +--EXPECT-- +Closure: + Type: self + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a static closure +Closure: + Type: parent + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a static closure +Closure: + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a static closure + +Trait: +Method: bar + Type: parent + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a trait +Method: ping + Type: self + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a trait +Method: pong + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a trait