From 4bb0ed820301af50eac47c9d2580085ea12cba86 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 16 Jun 2023 01:47:08 +0100 Subject: [PATCH 1/9] Add tests for relative class types --- .../invalid_parent_type_outside_class.phpt | 10 ++++++++++ .../invalid_self_type_outside_class.phpt | 10 ++++++++++ .../invalid_static_type_outside_class.phpt | 10 ++++++++++ .../relative_type_in_closures.phpt | 13 +++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_parent_type_outside_class.phpt create mode 100644 Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_self_type_outside_class.phpt create mode 100644 Zend/tests/type_declarations/relative_class_types/invalid_types/invalid_static_type_outside_class.phpt create mode 100644 Zend/tests/type_declarations/relative_class_types/relative_type_in_closures.phpt 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 From a1474996cec20cc84e79f75cd5a825d06533eecf Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 16 Jun 2023 08:10:42 +0100 Subject: [PATCH 2/9] Resolve relative types at compile time when possible --- Zend/tests/magic_methods_021.phpt | 2 +- .../duplicate_relative_class_parent_type.phpt | 15 +++ .../duplicate_relative_class_self_type.phpt | 12 ++ .../duplicate_relative_class_static_type.phpt | 12 ++ ...solved_relative_class_type_variation1.phpt | 12 ++ ...solved_relative_class_type_variation2.phpt | 12 ++ ...solved_relative_class_type_variation3.phpt | 15 +++ ...solved_relative_class_type_variation4.phpt | 15 +++ Zend/zend_compile.c | 118 ++++++++++++++---- Zend/zend_string.h | 2 + Zend/zend_types.h | 18 ++- 11 files changed, 206 insertions(+), 27 deletions(-) create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_parent_type.phpt create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_self_type.phpt create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/duplicate_relative_class_static_type.phpt create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation1.phpt create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation2.phpt create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation3.phpt create mode 100644 Zend/tests/type_declarations/union_types/redundant_types/duplicate_resolved_relative_class_type_variation4.phpt 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/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..4596785263c70 --- /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 parent 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..4fcdb1f6109e3 --- /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 self 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..03b84709c6de3 --- /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: self resolves to Foo which 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..5a2bc2e3f1942 --- /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: self resolves to Foo which 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..21306e154d87e --- /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: parent resolves to Foo which 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..e77980aba53f4 --- /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: parent resolves to Foo which is redundant in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0b54823d2026c..716687e0d3496 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1316,10 +1316,10 @@ 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); + ZEND_ASSERT(!ZEND_TYPE_IS_RELATIVE_SELF(*single_type) && "Compile time disallowed to have 'self' in intersection"); + ZEND_ASSERT(!ZEND_TYPE_IS_RELATIVE_PARENT(*single_type) && "Compile time disallowed to have 'parent' in intersection"); + + intersection_str = add_type_string(intersection_str, ZEND_TYPE_NAME(*single_type), /* is_intersection */ true); } ZEND_TYPE_LIST_FOREACH_END(); ZEND_ASSERT(intersection_str); @@ -1351,13 +1351,30 @@ 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); - zend_string_release(resolved); + + /* We already have resolved types from compile time + * Mimic unresolved types for BC with "self" and "parent" */ + if (!scope && ZEND_TYPE_IS_RELATIVE_SELF(*list_type)) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_SELF), /* is_intersection */ false); + } else if (!scope && ZEND_TYPE_IS_RELATIVE_PARENT(*list_type)) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_PARENT), /* is_intersection */ false); + } else { + 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); + zend_string_release(resolved); + } } ZEND_TYPE_LIST_FOREACH_END(); } else if (ZEND_TYPE_HAS_NAME(type)) { - str = resolve_class_name(ZEND_TYPE_NAME(type), scope); + /* We already have resolved types from compile time + * Mimic unresolved types for BC with "self" and "parent" */ + if (!scope && ZEND_TYPE_IS_RELATIVE_SELF(type)) { + str = ZSTR_KNOWN(ZEND_STR_SELF); + } else if (!scope && ZEND_TYPE_IS_RELATIVE_PARENT(type)) { + str = ZSTR_KNOWN(ZEND_STR_PARENT); + } else { + str = resolve_class_name(ZEND_TYPE_NAME(type), scope); + } } uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); @@ -6361,14 +6378,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 */ @@ -6382,38 +6399,58 @@ 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); + uint32_t type_flags = 0; + 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) { + type_flags = _ZEND_TYPE_SELF_BIT; + /* 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); + type_flags = _ZEND_TYPE_PARENT_BIT; + /* 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, type_flags); } } } @@ -6495,7 +6532,34 @@ static void zend_is_type_list_redundant_by_single_type(zend_type_list *type_list } if (zend_string_equals_ci(ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(type))) { zend_string *single_type_str = zend_type_to_string(type); - zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str)); + if ( + ZEND_TYPE_IS_RELATIVE_SELF(type) + || ZEND_TYPE_IS_RELATIVE_PARENT(type) + ) { + if ( ( + ZEND_TYPE_FULL_MASK(type) + & ZEND_TYPE_FULL_MASK(type_list->types[i]) + & (_ZEND_TYPE_SELF_BIT|_ZEND_TYPE_PARENT_BIT)) != 0 + ) { + zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str)); + } + /* zend_type_to_string() will return "self" or "parent" where the resolved type is stored in + * ZEND_TYPE_NAME() */ + zend_error_noreturn(E_COMPILE_ERROR, "%s resolves to %s which is redundant", + ZSTR_VAL(single_type_str), ZSTR_VAL(ZEND_TYPE_NAME(type)) + ); + } else if ( + ZEND_TYPE_IS_RELATIVE_SELF(type_list->types[i]) + || ZEND_TYPE_IS_RELATIVE_PARENT(type_list->types[i]) + ) { + /* zend_type_to_string() will return "self" or "parent" where the resolved type is stored in + * ZEND_TYPE_NAME() */ + zend_error_noreturn(E_COMPILE_ERROR, "%s resolves to %s which is redundant", + ZEND_TYPE_IS_RELATIVE_SELF(type_list->types[i]) ? "self" : "parent", ZSTR_VAL(ZEND_TYPE_NAME(type)) + ); + } else { + zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str)); + } } } } @@ -6534,6 +6598,7 @@ static zend_type zend_compile_typename( /* 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 */ @@ -6580,6 +6645,7 @@ static zend_type zend_compile_typename( "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)) { @@ -6587,13 +6653,18 @@ static zend_type zend_compile_typename( /* The first class type can be stored directly as the type ptr payload. */ ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type)); ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT; + /* Add flags indicating the named type is self/parent */ + ZEND_TYPE_FULL_MASK(type) |= (ZEND_TYPE_FULL_MASK(single_type) & _ZEND_TYPE_RELATIVE_TYPE_MASK); } else { if (type_list->num_types == 0) { /* 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); + /* Clear flags indicating the named type is self/parent */ + ZEND_TYPE_FULL_MASK(type) &= ~_ZEND_TYPE_RELATIVE_TYPE_MASK; } type_list->types[type_list->num_types++] = single_type; @@ -6655,10 +6726,11 @@ static zend_type zend_compile_typename( zend_string_release_ex(standard_type_str, false); } /* Check for "self" and "parent" too */ - if (zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "self") - || zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "parent")) { - zend_error_noreturn(E_COMPILE_ERROR, - "Type %s cannot be part of an intersection type", ZSTR_VAL(ZEND_TYPE_NAME(single_type))); + if (ZEND_TYPE_IS_RELATIVE_SELF(single_type)) { + zend_error_noreturn(E_COMPILE_ERROR, "Type self cannot be part of an intersection type"); + } + if (ZEND_TYPE_IS_RELATIVE_PARENT(single_type)) { + zend_error_noreturn(E_COMPILE_ERROR, "Type parent cannot be part of an intersection type"); } /* Add type to the type list */ diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 1513a19c36070..a0283aab59ee7 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -619,6 +619,8 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_NULL_LOWERCASE, "null") \ _(ZEND_STR_MIXED, "mixed") \ _(ZEND_STR_TRAVERSABLE, "Traversable") \ + _(ZEND_STR_SELF, "self") \ + _(ZEND_STR_PARENT, "parent") \ _(ZEND_STR_SLEEP, "__sleep") \ _(ZEND_STR_WAKEUP, "__wakeup") \ _(ZEND_STR_CASES, "cases") \ diff --git a/Zend/zend_types.h b/Zend/zend_types.h index af5f3821723fa..7fd81ed290485 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -141,10 +141,14 @@ typedef struct { zend_type types[1]; } zend_type_list; -#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25 -#define _ZEND_TYPE_MASK ((1u << 25) - 1) +#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 26 +#define _ZEND_TYPE_MASK ((1u << 26) - 1) /* Only one of these bits may be set. */ -#define _ZEND_TYPE_NAME_BIT (1u << 24) +#define _ZEND_TYPE_PARENT_BIT (1u << 25) +#define _ZEND_TYPE_SELF_BIT (1u << 24) +#define _ZEND_TYPE_RELATIVE_TYPE_MASK (_ZEND_TYPE_SELF_BIT|_ZEND_TYPE_PARENT_BIT) +/* Only one of these bits may be set. */ +#define _ZEND_TYPE_NAME_BIT (1u << 23) #define _ZEND_TYPE_LIST_BIT (1u << 22) #define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT) /* For BC behaviour with iterable type */ @@ -163,6 +167,14 @@ typedef struct { #define ZEND_TYPE_IS_SET(t) \ (((t).type_mask & _ZEND_TYPE_MASK) != 0) + +/* To determine if the type resolved type was written with "self" */ +#define ZEND_TYPE_IS_RELATIVE_SELF(t) \ + ((((t).type_mask) & _ZEND_TYPE_SELF_BIT) != 0) +/* To determine if the type resolved type was written with "parent" */ +#define ZEND_TYPE_IS_RELATIVE_PARENT(t) \ + ((((t).type_mask) & _ZEND_TYPE_PARENT_BIT) != 0) + /* If a type is complex it means it's either a list with a union or intersection, * or the void pointer is a class name */ #define ZEND_TYPE_IS_COMPLEX(t) \ From e538d879a52353e7bcfd3830287b28c168b1166c Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 14 Jun 2023 11:48:52 +0100 Subject: [PATCH 3/9] Support for resolving self/parent/static in ReflectionType This is only possible if the type is resolvable. --- ext/reflection/php_reflection.c | 140 +++++++++++++----- ext/reflection/php_reflection.h | 1 + ext/reflection/php_reflection.stub.php | 5 + ext/reflection/php_reflection_arginfo.h | 22 ++- .../ReflectionExtension_getClasses_basic.phpt | 37 +++-- ext/reflection/tests/bug80190.phpt | 12 +- .../relative_class_types_in_classes.phpt | 116 +++++++++++++++ ...ve_class_types_in_classes_parameters1.phpt | 103 +++++++++++++ ...ve_class_types_in_classes_parameters2.phpt | 103 +++++++++++++ ...ve_class_types_in_classes_parameters3.phpt | 110 ++++++++++++++ ...ve_class_types_in_classes_parameters4.phpt | 110 ++++++++++++++ .../relative_class_types_in_interfaces.phpt | 86 +++++++++++ .../unresolved_relative_class_types.phpt | 81 ++++++++++ ...olved_relative_class_types_union_type.phpt | 91 ++++++++++++ 14 files changed, 961 insertions(+), 56 deletions(-) create mode 100644 ext/reflection/tests/types/relative_class_types_in_classes.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_classes_parameters1.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_classes_parameters2.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_classes_parameters3.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_classes_parameters4.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_interfaces.phpt create mode 100644 ext/reflection/tests/types/unresolved_relative_class_types.phpt create mode 100644 ext/reflection/tests/types/unresolved_relative_class_types_union_type.phpt diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 7d24f5b4b2291..119c8be54ce47 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; @@ -1328,6 +1329,7 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje typedef enum { NAMED_TYPE = 0, + RELATIVE_TYPE = 3, UNION_TYPE = 1, INTERSECTION_TYPE = 2 } reflection_type_kind; @@ -1355,6 +1357,11 @@ 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_TYPE_IS_RELATIVE_SELF(type) || ZEND_TYPE_IS_RELATIVE_PARENT(type)) { + return RELATIVE_TYPE; + } return NAMED_TYPE; } if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) { @@ -1364,12 +1371,22 @@ static reflection_type_kind get_type_kind(zend_type type) { if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) { return UNION_TYPE; } + + /* "static" is a relative type */ + if (type_mask_without_null == MAY_BE_STATIC) { + return RELATIVE_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 +) { reflection_object *intern; type_reference *reference; reflection_type_kind type_kind = get_type_kind(type); @@ -1386,6 +1403,9 @@ 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 RELATIVE_TYPE: + reflection_instantiate(reflection_relative_class_type_ptr, object); + break; EMPTY_SWITCH_DEFAULT_CASE(); } @@ -1395,6 +1415,7 @@ 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; /* 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 @@ -1405,7 +1426,6 @@ 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) @@ -2653,17 +2673,14 @@ 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); - if (zend_string_equals_literal_ci(class_name, "self")) { + if (ZEND_TYPE_IS_RELATIVE_SELF(param->arg_info->type)) { ce = param->fptr->common.scope; if (!ce) { zend_throw_exception_ex(reflection_exception_ptr, 0, "Parameter uses \"self\" as type but function is not a class member"); RETURN_THROWS(); } - } else if (zend_string_equals_literal_ci(class_name, "parent")) { + } else if (ZEND_TYPE_IS_RELATIVE_PARENT(param->arg_info->type)) { ce = param->fptr->common.scope; if (!ce) { zend_throw_exception_ex(reflection_exception_ptr, 0, @@ -2677,6 +2694,7 @@ ZEND_METHOD(ReflectionParameter, getClass) } ce = ce->parent; } else { + zend_string *class_name = ZEND_TYPE_NAME(param->arg_info->type); ce = zend_lookup_class(class_name); if (!ce) { zend_throw_exception_ex(reflection_exception_ptr, 0, @@ -2718,7 +2736,7 @@ 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); } /* }}} */ @@ -3090,19 +3108,64 @@ ZEND_METHOD(ReflectionNamedType, isBuiltin) } /* }}} */ -static void append_type(zval *return_value, zend_type type) { +/* {{{ Returns whether type is a builtin 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 use relative class types */ + if (!intern->ce) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Cannot resolve relative class name for a 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 (ZEND_TYPE_PURE_MASK(param->type) & MAY_BE_STATIC) { + 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(); + } + resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(intern->ce->name, allows_null, /*extra flags */ 0); + } else { + ZEND_ASSERT(ZEND_TYPE_IS_RELATIVE_SELF(param->type) || ZEND_TYPE_IS_RELATIVE_PARENT(param->type)); + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(param->type)); + resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_NAME(param->type), allows_null, /*extra flags */ 0); + } + + reflection_type_factory(resolved_type, return_value, /* legacy_behavior */ true, intern->ce); +} +/* }}} */ + +static void append_type(zval *return_value, zend_type type, zend_class_entry *object_ce) { 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); 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) { + append_type(return_value, (zend_type) ZEND_TYPE_INIT_MASK(type_mask), object_ce); } /* {{{ Returns the types that are part of this union type */ @@ -3121,46 +3184,53 @@ 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); } 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)); + uint32_t type_flags = 0; + if (ZEND_TYPE_IS_RELATIVE_SELF(param->type)) { + type_flags = _ZEND_TYPE_SELF_BIT; + } + if (ZEND_TYPE_IS_RELATIVE_PARENT(param->type)) { + type_flags = _ZEND_TYPE_PARENT_BIT; + } + append_type(return_value, (zend_type) ZEND_TYPE_INIT_CLASS(name, /* allow_null */ false, /* extra flags */ type_flags), /* object_ce */ intern->ce); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } 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); } } /* }}} */ @@ -3181,7 +3251,7 @@ 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); + append_type(return_value, *list_type, /* object_ce */ intern->ce); } ZEND_TYPE_LIST_FOREACH_END(); } /* }}} */ @@ -3578,7 +3648,7 @@ 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); } /* }}} */ @@ -3614,7 +3684,7 @@ 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); } /* }}} */ @@ -3835,7 +3905,7 @@ 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); } /* Returns whether class constant has a type */ @@ -5839,7 +5909,7 @@ 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); } /* }}} */ @@ -6929,7 +6999,7 @@ 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); } } @@ -7189,6 +7259,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 de0d83d244a85..f05b5e44b5e55 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -717,6 +717,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 ddc445f5206d3..ec41449d767be 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: 75d10a475cce503d94bd8471764adf495f0ddd34 */ + * Stub hash: 47f97c2523215f74452afc1ffa5775f9ac49cfda */ 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) @@ -478,6 +478,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 @@ -792,6 +795,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); @@ -1101,6 +1105,12 @@ static const zend_function_entry class_ReflectionNamedType_methods[] = { }; +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 @@ -1525,6 +1535,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 b7c10079d4dd3..910216e48f9ba 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection'); var_dump($ext->getClasses()); ?> --EXPECT-- -array(24) { +array(25) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -54,78 +54,83 @@ array(24) { ["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/bug80190.phpt b/ext/reflection/tests/bug80190.phpt index 16f2fbba42729..6e2908f9c435f 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; } @@ -47,21 +47,21 @@ foreach ((new ReflectionClass(C::class))->getMethods() as $method) { ?> --EXPECT-- C::a() - $method->getReturnType() returns ReflectionNamedType + $method->getReturnType() returns ReflectionRelativeClassType $method->getReturnType()->__toString() returns self C::b() $method->getReturnType() returns ReflectionUnionType $method->getReturnType()->__toString() returns stdClass|self $method->getReturnType()->getTypes() returns an array with 2 element(s) - type(s) in union: ReflectionNamedType(stdClass), ReflectionNamedType(self) + type(s) in union: ReflectionNamedType(stdClass), ReflectionRelativeClassType(self) 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/relative_class_types_in_classes.phpt b/ext/reflection/tests/types/relative_class_types_in_classes.phpt new file mode 100644 index 0000000000000..5adb2508cfcc7 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_classes.phpt @@ -0,0 +1,116 @@ +--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; + $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: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Return value must be of type B, A returned +Class: C + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Return value must be of type B, A returned + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved 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: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Return value must be of type B, A returned +Class: D + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Return value must be of type B, A returned + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved 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: self + Instance of: ReflectionRelativeClassType + Resolved 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_in_classes_parameters1.phpt b/ext/reflection/tests/types/relative_class_types_in_classes_parameters1.phpt new file mode 100644 index 0000000000000..602f992951034 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_classes_parameters1.phpt @@ -0,0 +1,103 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) 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; + $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; + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type B, A given, called in %s on line %d +Class: C + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved 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: self + Instance of: ReflectionRelativeClassType + Resolved 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 + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type B, A given, called in %s on line %d +Class: D + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved 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: self + Instance of: ReflectionRelativeClassType + Resolved 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 + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type B, A given, called in %s on line %d diff --git a/ext/reflection/tests/types/relative_class_types_in_classes_parameters2.phpt b/ext/reflection/tests/types/relative_class_types_in_classes_parameters2.phpt new file mode 100644 index 0000000000000..274c791851893 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_classes_parameters2.phpt @@ -0,0 +1,103 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) in classes, parameter nullable 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; + $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; + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B + Method: foo + Type: ?self + Instance of: ReflectionRelativeClassType + Resolved Type: ?B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type ?B, A given, called in %s on line %d +Class: C + Method: bar + Type: ?parent + Instance of: ReflectionRelativeClassType + Resolved 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: ?self + Instance of: ReflectionRelativeClassType + Resolved 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 + Method: foo + Type: ?self + Instance of: ReflectionRelativeClassType + Resolved Type: ?B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type ?B, A given, called in %s on line %d +Class: D + Method: bar + Type: ?parent + Instance of: ReflectionRelativeClassType + Resolved 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: ?self + Instance of: ReflectionRelativeClassType + Resolved 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 + Method: foo + Type: ?self + Instance of: ReflectionRelativeClassType + Resolved Type: ?B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type ?B, A given, called in %s on line %d diff --git a/ext/reflection/tests/types/relative_class_types_in_classes_parameters3.phpt b/ext/reflection/tests/types/relative_class_types_in_classes_parameters3.phpt new file mode 100644 index 0000000000000..bea4bcfe6217f --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_classes_parameters3.phpt @@ -0,0 +1,110 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) in classes, parameter union types +--FILE-- +getMethods(); + foreach ($methods as $method) { + echo "\tMethod: ", $method->name, PHP_EOL; + $parameters = $method->getParameters(); + foreach ($parameters as $param) { + $unionType = $param->getType(); + $types = $unionType->getTypes(); + foreach ($types as $type) { + if (!($type instanceof ReflectionRelativeClassType)) { + // Do not care about "int" type here; + continue; + } + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + $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; + } + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type B|int, A given, called in %s on line %d +Class: C + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type B|int, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type C|int, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type C|int, B given, called in %s on line %d + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type B|int, A given, called in %s on line %d +Class: D + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type B|int, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type C|int, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type C|int, B given, called in %s on line %d + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type B|int, A given, called in %s on line %d diff --git a/ext/reflection/tests/types/relative_class_types_in_classes_parameters4.phpt b/ext/reflection/tests/types/relative_class_types_in_classes_parameters4.phpt new file mode 100644 index 0000000000000..3410a10a2c7e7 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_classes_parameters4.phpt @@ -0,0 +1,110 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) in classes, parameter DNF types +--FILE-- +getMethods(); + foreach ($methods as $method) { + echo "\tMethod: ", $method->name, PHP_EOL; + $parameters = $method->getParameters(); + foreach ($parameters as $param) { + $unionType = $param->getType(); + $types = $unionType->getTypes(); + foreach ($types as $type) { + if (!($type instanceof ReflectionRelativeClassType)) { + // Do not care about "(X&Y)" type here; + continue; + } + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + $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; + } + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type (X&Y)|B, A given, called in %s on line %d +Class: C + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type (X&Y)|B, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type (X&Y)|C, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type (X&Y)|C, B given, called in %s on line %d + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type (X&Y)|B, A given, called in %s on line %d +Class: D + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type (X&Y)|B, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type (X&Y)|C, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type (X&Y)|C, B given, called in %s on line %d + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 ($o) must be of type (X&Y)|B, A given, called in %s on line %d diff --git a/ext/reflection/tests/types/relative_class_types_in_interfaces.phpt b/ext/reflection/tests/types/relative_class_types_in_interfaces.phpt new file mode 100644 index 0000000000000..450947fbb84ce --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_interfaces.phpt @@ -0,0 +1,86 @@ +--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; + 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: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType +Interface: C + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + Method: pong + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve "static" type of an interface + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType +Interface: D + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + Method: pong + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve "static" type of an interface + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType 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..169f3d2ec3e94 --- /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 closure +Closure: + Type: parent + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a closure +Closure: + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a 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..3886a4f0c5ddb --- /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 closure +Closure: + Type: parent + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a closure +Closure: + Type: static + Instance of: ReflectionRelativeClassType +Cannot resolve relative class name for a 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 From 186a4b31ffcb34214a0f55796723e1ab207f7980 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 18 Jun 2023 15:55:42 +0100 Subject: [PATCH 4/9] Resolve trait relative class types when added to a class Also throw a compile error if trying to use a trait using the parent type in a class not having a parent --- .../trait_parent_type_in_class_no_parent.phpt | 14 +++ ...nt_type_in_class_no_parent_variation2.phpt | 14 +++ ...nt_type_in_class_no_parent_variation3.phpt | 14 +++ ...nt_type_in_class_no_parent_variation4.phpt | 14 +++ Zend/zend_inheritance.c | 58 ++++++++++ ..._types_in_classes_variadic_parameter1.phpt | 103 ++++++++++++++++++ ..._class_types_in_trait_used_in_classes.phpt | 101 +++++++++++++++++ ...s_in_trait_used_in_classes_parameters.phpt | 90 +++++++++++++++ ..._in_trait_used_in_classes_parameters2.phpt | 88 +++++++++++++++ ..._in_trait_used_in_classes_parameters3.phpt | 95 ++++++++++++++++ ..._in_trait_used_in_classes_parameters4.phpt | 95 ++++++++++++++++ 11 files changed, 686 insertions(+) create mode 100644 Zend/tests/traits/trait_parent_type_in_class_no_parent.phpt create mode 100644 Zend/tests/traits/trait_parent_type_in_class_no_parent_variation2.phpt create mode 100644 Zend/tests/traits/trait_parent_type_in_class_no_parent_variation3.phpt create mode 100644 Zend/tests/traits/trait_parent_type_in_class_no_parent_variation4.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_classes_variadic_parameter1.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters2.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters3.phpt create mode 100644 ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters4.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/zend_inheritance.c b/Zend/zend_inheritance.c index 17bbae8335445..9cd2239228f9d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1898,6 +1898,40 @@ 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; } +static void zend_resolve_trait_relative_class_types(zend_type *type, const zend_class_entry *ce) +{ + /* Only built-in types */ + if (!ZEND_TYPE_IS_COMPLEX(*type)) { + return; + } + /* We are adding trait methods to another trait, delay resolution */ + if (ce->ce_flags & ZEND_ACC_TRAIT) { + return; + } + zend_type *single_type; + ZEND_TYPE_FOREACH(*type, single_type) { + 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; + } + if (ZEND_TYPE_IS_RELATIVE_SELF(*single_type)) { + zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(*single_type)); + memcpy(single_type, &resolved_type, sizeof(zend_type)); + } + if (ZEND_TYPE_IS_RELATIVE_PARENT(*single_type)) { + 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"); + return; + } + 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(*single_type)); + memcpy(single_type, &resolved_type, sizeof(zend_type)); + } + } ZEND_TYPE_FOREACH_END(); +} + static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_string *key, zend_function *fn) /* {{{ */ { zend_function *existing_fn = NULL; @@ -1947,10 +1981,34 @@ 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)); + if (new_fn->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_type *return_type = &new_fn->common.arg_info[-1].type; + zend_resolve_trait_relative_class_types(return_type, ce); + } + + uint32_t num_args = new_fn->common.num_args; + for (uint32_t i = 0; i < num_args; i++) { + // TODO Handle variadic parameter types? + zend_arg_info *arg_info = &new_fn->common.arg_info[i]; + zend_type *type = &arg_info->type; + zend_resolve_trait_relative_class_types(type, 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)); + if (new_fn->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_type *return_type = &new_fn->common.arg_info[-1].type; + zend_resolve_trait_relative_class_types(return_type, ce); + } + + uint32_t num_args = new_fn->common.num_args; + for (uint32_t i = 0; i < num_args; i++) { + // TODO Handle variadic parameter types? + zend_arg_info *arg_info = &new_fn->common.arg_info[i]; + zend_type *type = &arg_info->type; + zend_resolve_trait_relative_class_types(type, ce); + } new_fn->op_array.fn_flags |= ZEND_ACC_TRAIT_CLONE; new_fn->op_array.fn_flags &= ~ZEND_ACC_IMMUTABLE; } diff --git a/ext/reflection/tests/types/relative_class_types_in_classes_variadic_parameter1.phpt b/ext/reflection/tests/types/relative_class_types_in_classes_variadic_parameter1.phpt new file mode 100644 index 0000000000000..74b94fbfe918c --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_classes_variadic_parameter1.phpt @@ -0,0 +1,103 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) 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; + $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; + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 must be of type B, A given, called in %s on line %d +Class: C + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 must be of type B, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 must be of type C, A given, called in %s on line %d + C::ping(): Argument #1 must be of type C, B given, called in %s on line %d + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 must be of type B, A given, called in %s on line %d +Class: D + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 must be of type B, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 must be of type C, A given, called in %s on line %d + C::ping(): Argument #1 must be of type C, B given, called in %s on line %d + Method: foo + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + B::foo(): Argument #1 must be of type B, A given, called in %s on line %d diff --git a/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes.phpt b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes.phpt new file mode 100644 index 0000000000000..e3cf26aeb72aa --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes.phpt @@ -0,0 +1,101 @@ +--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; + $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: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Return value must be of type B, A returned + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved 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: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Return value must be of type B, A returned + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved 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_in_trait_used_in_classes_parameters.phpt b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters.phpt new file mode 100644 index 0000000000000..ab50cfb212d84 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters.phpt @@ -0,0 +1,90 @@ +--TEST-- +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; + $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; + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B +Class: C + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved 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: self + Instance of: ReflectionRelativeClassType + Resolved 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: parent + Instance of: ReflectionRelativeClassType + Resolved 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: self + Instance of: ReflectionRelativeClassType + Resolved 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_in_trait_used_in_classes_parameters2.phpt b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters2.phpt new file mode 100644 index 0000000000000..4142be2590af1 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters2.phpt @@ -0,0 +1,88 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) from traits in classes, parameter nullable 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; + $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; + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B +Class: C + Method: bar + Type: ?parent + Instance of: ReflectionRelativeClassType + Resolved 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: ?self + Instance of: ReflectionRelativeClassType + Resolved 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: ?parent + Instance of: ReflectionRelativeClassType + Resolved 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: ?self + Instance of: ReflectionRelativeClassType + Resolved 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_in_trait_used_in_classes_parameters3.phpt b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters3.phpt new file mode 100644 index 0000000000000..2e7cc819f95ed --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters3.phpt @@ -0,0 +1,95 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) from traits in classes, parameter union types +--FILE-- +getMethods(); + foreach ($methods as $method) { + echo "\tMethod: ", $method->name, PHP_EOL; + $parameters = $method->getParameters(); + foreach ($parameters as $param) { + $unionType = $param->getType(); + $types = $unionType->getTypes(); + foreach ($types as $type) { + if (!($type instanceof ReflectionRelativeClassType)) { + // Do not care about "int" type here; + continue; + } + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + $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; + } + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B +Class: C + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type B|int, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type C|int, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type C|int, B given, called in %s on line %d +Class: D + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type B|int, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type C|int, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type C|int, B given, called in %s on line %d diff --git a/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters4.phpt b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters4.phpt new file mode 100644 index 0000000000000..17144bfdce977 --- /dev/null +++ b/ext/reflection/tests/types/relative_class_types_in_trait_used_in_classes_parameters4.phpt @@ -0,0 +1,95 @@ +--TEST-- +ReflectionTypes of relative class types (self, parent) from traits in classes, parameter DNF types +--FILE-- +getMethods(); + foreach ($methods as $method) { + echo "\tMethod: ", $method->name, PHP_EOL; + $parameters = $method->getParameters(); + foreach ($parameters as $param) { + $unionType = $param->getType(); + $types = $unionType->getTypes(); + foreach ($types as $type) { + if (!($type instanceof ReflectionRelativeClassType)) { + // Do not care about "(X&Y)" type here; + continue; + } + echo "\t\tType: ", $type, PHP_EOL; + echo "\t\tInstance of: ", $type::class, PHP_EOL; + $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; + } + } + } + } + } +} + +?> +--EXPECTF-- +Class: A +Class: B +Class: C + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type (X&Y)|B, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type (X&Y)|C, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type (X&Y)|C, B given, called in %s on line %d +Class: D + Method: bar + Type: parent + Instance of: ReflectionRelativeClassType + Resolved Type: B + Instance of: ReflectionNamedType + C::bar(): Argument #1 ($o) must be of type (X&Y)|B, A given, called in %s on line %d + Method: ping + Type: self + Instance of: ReflectionRelativeClassType + Resolved Type: C + Instance of: ReflectionNamedType + C::ping(): Argument #1 ($o) must be of type (X&Y)|C, A given, called in %s on line %d + C::ping(): Argument #1 ($o) must be of type (X&Y)|C, B given, called in %s on line %d From 2aac9773fe4b5163bfb8fab8b8cd83ef4bbbd72f Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 19 Jun 2023 01:20:20 +0100 Subject: [PATCH 5/9] drop const qualifier --- Zend/zend_inheritance.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 9cd2239228f9d..c7b13e0ee2fc9 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1898,7 +1898,7 @@ 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; } -static void zend_resolve_trait_relative_class_types(zend_type *type, const zend_class_entry *ce) +static void zend_resolve_trait_relative_class_types(zend_type *type, /* const */ zend_class_entry *ce) { /* Only built-in types */ if (!ZEND_TYPE_IS_COMPLEX(*type)) { From eda873f9ee5fc0983771df56bd21938aee27ec06 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 19 Jun 2023 14:37:49 +0100 Subject: [PATCH 6/9] Use memmove as we are pointing to the same memory I think --- Zend/tests/{ => traits}/bug80055.phpt | 0 Zend/zend_inheritance.c | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) rename Zend/tests/{ => traits}/bug80055.phpt (100%) 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/zend_inheritance.c b/Zend/zend_inheritance.c index c7b13e0ee2fc9..9f4ab029caa44 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1918,7 +1918,8 @@ static void zend_resolve_trait_relative_class_types(zend_type *type, /* const */ } if (ZEND_TYPE_IS_RELATIVE_SELF(*single_type)) { zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(*single_type)); - memcpy(single_type, &resolved_type, sizeof(zend_type)); + /* Cannot use memcpy as we violate the restrict constraint of src and dest */ + memmove(single_type, &resolved_type, sizeof(zend_type)); } if (ZEND_TYPE_IS_RELATIVE_PARENT(*single_type)) { if (!ce->parent) { @@ -1927,7 +1928,8 @@ static void zend_resolve_trait_relative_class_types(zend_type *type, /* const */ return; } 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(*single_type)); - memcpy(single_type, &resolved_type, sizeof(zend_type)); + /* Cannot use memcpy as we violate the restrict constraint of src and dest */ + memmove(single_type, &resolved_type, sizeof(zend_type)); } } ZEND_TYPE_FOREACH_END(); } From 1c1642dfcc060f1c9bb44c25484a4d0fc786f593 Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 Jun 2023 20:27:32 +0200 Subject: [PATCH 7/9] test --- Zend/zend_inheritance.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 9f4ab029caa44..f840343297cdc 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1918,8 +1918,7 @@ static void zend_resolve_trait_relative_class_types(zend_type *type, /* const */ } if (ZEND_TYPE_IS_RELATIVE_SELF(*single_type)) { zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(*single_type)); - /* Cannot use memcpy as we violate the restrict constraint of src and dest */ - memmove(single_type, &resolved_type, sizeof(zend_type)); + *single_type = resolved_type; } if (ZEND_TYPE_IS_RELATIVE_PARENT(*single_type)) { if (!ce->parent) { @@ -1928,8 +1927,7 @@ static void zend_resolve_trait_relative_class_types(zend_type *type, /* const */ return; } 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(*single_type)); - /* Cannot use memcpy as we violate the restrict constraint of src and dest */ - memmove(single_type, &resolved_type, sizeof(zend_type)); + *single_type = resolved_type; } } ZEND_TYPE_FOREACH_END(); } From 6928cd81e061541e741cde88d81bc3f40a72afd7 Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 Jun 2023 21:45:34 +0200 Subject: [PATCH 8/9] test --- .github/actions/configure-x32/action.yml | 2 ++ .github/actions/test-linux/action.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/actions/configure-x32/action.yml b/.github/actions/configure-x32/action.yml index cc8fe6538400e..f7e263d9d1216 100644 --- a/.github/actions/configure-x32/action.yml +++ b/.github/actions/configure-x32/action.yml @@ -18,6 +18,8 @@ runs: --enable-option-checking=fatal \ --build=i686-pc-linux-gnu \ --prefix=/usr \ + --enable-address-sanitizer \ + --enable-undefined-sanitizer \ --enable-phpdbg \ --enable-fpm \ --enable-intl \ diff --git a/.github/actions/test-linux/action.yml b/.github/actions/test-linux/action.yml index 9b7d0d100608f..e93634ab41527 100644 --- a/.github/actions/test-linux/action.yml +++ b/.github/actions/test-linux/action.yml @@ -41,6 +41,7 @@ runs: --show-diff \ --show-slow 1000 \ --set-timeout 120 + ext/reflection Zend/tests - uses: actions/upload-artifact@v3 if: always() && inputs.testArtifacts != null with: From 2e32b7ba9b09466920f8ceb539d38fb70d0a4916 Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Mon, 19 Jun 2023 21:45:41 +0200 Subject: [PATCH 9/9] test --- .github/actions/test-linux/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/test-linux/action.yml b/.github/actions/test-linux/action.yml index e93634ab41527..353d7ef82fdff 100644 --- a/.github/actions/test-linux/action.yml +++ b/.github/actions/test-linux/action.yml @@ -40,7 +40,7 @@ runs: --offline \ --show-diff \ --show-slow 1000 \ - --set-timeout 120 + --set-timeout 120 \ ext/reflection Zend/tests - uses: actions/upload-artifact@v3 if: always() && inputs.testArtifacts != null