From 2b09b775c5c3e7d4da5af69b251a9e24ee1989a1 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 22 May 2025 14:13:11 +0100 Subject: [PATCH 1/3] Zend: Inherit interfaces early --- Zend/zend_inheritance.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 3c3931cdca164..922d244aec2f8 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1580,17 +1580,21 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } /* }}} */ -static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ +static inline void do_implement_interface_ex(zend_class_entry *ce, zend_class_entry *inherited_face, zend_class_entry *base_iface) { - if (!(ce->ce_flags & ZEND_ACC_INTERFACE) && iface->interface_gets_implemented && iface->interface_gets_implemented(iface, ce) == FAILURE) { - zend_error_noreturn(E_CORE_ERROR, "%s %s could not implement interface %s", zend_get_object_type_uc(ce), ZSTR_VAL(ce->name), ZSTR_VAL(iface->name)); + if (!(ce->ce_flags & ZEND_ACC_INTERFACE) && inherited_face->interface_gets_implemented && inherited_face->interface_gets_implemented(base_iface, ce) == FAILURE) { + zend_error_noreturn(E_CORE_ERROR, "%s %s could not implement interface %s", zend_get_object_type_uc(ce), ZSTR_VAL(ce->name), ZSTR_VAL(base_iface->name)); } /* This should be prevented by the class lookup logic. */ - ZEND_ASSERT(ce != iface); + ZEND_ASSERT(ce != base_iface); +} + +static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry *iface) +{ + do_implement_interface_ex(ce, iface, iface); } -/* }}} */ -static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_entry *iface) /* {{{ */ +static void zend_do_inherit_interfaces(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ { /* expects interface to be contained in ce's interface list already */ uint32_t i, ce_num, if_num = iface->num_interfaces; @@ -1619,7 +1623,7 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en /* and now call the implementing handlers */ while (ce_num < ce->num_interfaces) { - do_implement_interface(ce, ce->interfaces[ce_num++]); + do_implement_interface_ex(ce, ce->interfaces[ce_num++], iface); } } /* }}} */ @@ -2169,6 +2173,10 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_class_constant *c; uint32_t flags = ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY; + if (iface->num_interfaces) { + zend_do_inherit_interfaces(ce, iface); + } + if (!(ce->ce_flags & ZEND_ACC_INTERFACE)) { /* We are not setting the prototype of overridden interface methods because of abstract * constructors. See Zend/tests/interface_constructor_prototype_001.phpt. */ @@ -2200,9 +2208,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * } ZEND_HASH_FOREACH_END(); do_implement_interface(ce, iface); - if (iface->num_interfaces) { - zend_do_inherit_interfaces(ce, iface); - } } /* }}} */ From ed33d34aec4cf86b7393b7dff5c6af649070515d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 5 Apr 2025 17:07:11 +0100 Subject: [PATCH 2/3] Compile time generics W.I.P. --- .../abstract_generic_001.phpt | 31 ++ .../abstract_generics/big_example.phpt | 100 +++++ ...licit_interface_different_bound_types.phpt | 21 + ...icit_interface_different_bound_types2.phpt | 21 + ...icit_interface_different_bound_types3.phpt | 21 + ...icit_interface_different_bound_types4.phpt | 21 + .../implicit_interface_no_bound_types.phpt | 21 + .../implicit_interface_no_bound_types2.phpt | 21 + .../implicit_interface_no_bound_types3.phpt | 21 + .../implicit_interface_no_bound_types4.phpt | 21 + .../implicit_interface_same_bound_types.phpt | 22 + .../implicit_interface_same_bound_types2.phpt | 22 + ...abstract_generic_type_with_constraint.phpt | 31 ++ ...t_generic_type_with_constraint_failed.phpt | 16 + .../using_generic_type_as_constraint.phpt | 12 + .../errors/using_never_as_constraint.phpt | 12 + .../errors/using_static_as_constraint.phpt | 12 + .../errors/using_void_as_constraint.phpt | 12 + ...e_abstract_generic_constraint_failure.phpt | 21 + ...ct_generic_cannot_be_in_intersection1.phpt | 12 + ...ct_generic_cannot_be_in_intersection2.phpt | 12 + .../abstract_generic_cannot_be_in_union1.phpt | 12 + .../abstract_generic_cannot_be_in_union2.phpt | 12 + .../abstract_generic_cannot_be_in_union3.phpt | 12 + .../abstract_generic_cannot_be_in_union4.phpt | 12 + .../abstract_generic_cannot_be_in_union5.phpt | 12 + .../errors/abstract_generic_in_class.phpt | 13 + ..._generic_in_class_with_class_modifier.phpt | 12 + .../errors/abstract_generic_in_trait.phpt | 12 + .../errors/abstract_generic_redeclared.phpt | 12 + .../no_bound_abstract_generic_type.phpt | 16 + ...abstract_generic_type_with_constraint.phpt | 16 + ...t_generic_type_with_prior_bound_types.phpt | 20 + ...terface_abstract_generic_types_1_to_2.phpt | 22 + ...nterface_abstract_generic_types_basic.phpt | 22 + ...d_interface_new_abstract_generic_type.phpt | 22 + ..._interface_new_abstract_generic_type2.phpt | 22 + .../extended_interface_redeclares_method.phpt | 18 + ...erics_redeclares_method_contravariant.phpt | 17 + ..._generics_redeclares_method_covariant.phpt | 18 + ...nterface_abstract_generic_types_basic.phpt | 27 ++ ...twice_interface_generic_into_concrete.phpt | 27 ++ .../multiple_abstract_generic_type.phpt | 72 ++++ ...ce_concrete_to_generic_contravariance.phpt | 17 + ...erface_concrete_to_generic_covariance.phpt | 17 + ...eclares_method_invalid_contravariance.phpt | 17 + ..._redeclares_method_invalid_covariance.phpt | 17 + .../multiple_abstract_generic_type-error.phpt | 23 + Zend/zend.h | 6 + Zend/zend_ast.c | 59 ++- Zend/zend_ast.h | 4 + Zend/zend_compile.c | 186 +++++++- Zend/zend_compile.h | 3 +- Zend/zend_execute.c | 2 +- Zend/zend_inheritance.c | 401 ++++++++++++++++-- Zend/zend_language_parser.y | 93 ++-- Zend/zend_opcode.c | 16 +- Zend/zend_types.h | 35 +- .../interface_with_generic_types.phpt | 58 +++ 59 files changed, 1774 insertions(+), 91 deletions(-) create mode 100644 Zend/tests/type_declarations/abstract_generics/abstract_generic_001.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/big_example.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types3.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types4.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types3.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types4.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint_failed.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/errors/using_generic_type_as_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/errors/using_never_as_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/errors/using_static_as_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/errors/using_void_as_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_constraint_failure.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class_with_class_modifier.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_trait.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_redeclared.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_1_to_2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_contravariant.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_covariant.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/extended_twice_interface_generic_into_concrete.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_contravariance.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_covariance.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_contravariance.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_covariance.phpt create mode 100644 Zend/tests/type_declarations/abstract_generics/variance/multiple_abstract_generic_type-error.phpt create mode 100644 ext/zend_test/tests/compile_to_ast/interface_with_generic_types.phpt diff --git a/Zend/tests/type_declarations/abstract_generics/abstract_generic_001.phpt b/Zend/tests/type_declarations/abstract_generics/abstract_generic_001.phpt new file mode 100644 index 0000000000000..251724b6dd840 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/abstract_generic_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +Abstract generic types basic +--FILE-- + { + public function foo(T $param): T; +} + +class CS implements I { + public function foo(string $param): string { + return $param . '!'; + } +} + +class CI implements I { + public function foo(int $param): int { + return $param + 42; + } +} + +$cs = new CS(); +var_dump($cs->foo("Hello")); + +$ci = new CI(); +var_dump($ci->foo(5)); + +?> +--EXPECT-- +string(6) "Hello!" +int(47) diff --git a/Zend/tests/type_declarations/abstract_generics/big_example.phpt b/Zend/tests/type_declarations/abstract_generics/big_example.phpt new file mode 100644 index 0000000000000..0beea672751eb --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/big_example.phpt @@ -0,0 +1,100 @@ +--TEST-- +Concrete example of using AT +--CREDITS-- +Levi Morrison +--FILE-- + +{ + function next(): ?Item; + + /** + * @param callable(Item, Item): Item $f + * @return ?Item + */ + function reduce(callable $f): ?Item; +} + +final class StringTablePair +{ + public function __construct( + public readonly string $string, + public readonly int $id, + ) {} +} + +final class StringTableSequence implements Sequence +{ + private array $strings; + + public function __construct(StringTable $string_table) { + $this->strings = $string_table->to_assoc_array(); + } + + function next(): ?StringTablePair + { + $key = \array_key_first($this->strings); + if (!isset($key)) { + return null; + } + $value = \array_shift($this->strings); + return new StringTablePair($key, $value); + } + + /** + * @param callable(Item, Item): Item $f + * @return ?Item + */ + function reduce(callable $f): ?StringTablePair + { + $reduction = $this->next(); + if (!isset($reduction)) { + return null; + } + + while (($next = $this->next()) !== null) { + $reduction = $f($reduction, $next); + } + return $reduction; + } +} + +final class StringTable +{ + private array $strings = ["" => 0]; + + public function __construct() {} + + public function offsetGet(string $offset): int + { + return $this->strings[$offset] ?? throw new \Exception(); + } + + public function offsetExists(string $offset): bool + { + return \isset($this->strings[$offset]); + } + + public function intern(string $str): int + { + return $this->strings[$str] + ?? ($this->strings[$str] = \count($this->strings)); + } + + public function to_sequence(): StringTableSequence + { + return new StringTableSequence($this); + } + + public function to_assoc_array(): array { + return $this->strings; + } +} + +?> +--EXPECTF-- +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types.phpt new file mode 100644 index 0000000000000..c8dd8d7eda0f0 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance with different bound types +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I2, I1 { + public function foo(float $param): float {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types2.phpt new file mode 100644 index 0000000000000..eda45f32ef6d0 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance with different bound types 2 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I1, I2 { + public function foo(string $param): string {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types3.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types3.phpt new file mode 100644 index 0000000000000..6a37e34bbc184 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types3.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance with different bound types 3 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, float $param): float; +} + +class C implements I2, I1 { + public function foo(float $param): float {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types4.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types4.phpt new file mode 100644 index 0000000000000..49029e3904d00 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_different_bound_types4.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance with different bound types 4 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, float $param): float; +} + +class C implements I1, I2 { + public function foo(string $param): string {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types.phpt new file mode 100644 index 0000000000000..3595c2b650298 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance missing bound types +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I2, I1 { + public function foo(float $param): float {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types2.phpt new file mode 100644 index 0000000000000..4f505ebb0a9e4 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance missing bound types 2 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I1, I2 { + public function foo(float $param): float {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types3.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types3.phpt new file mode 100644 index 0000000000000..fc8fe27cf2420 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types3.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance missing bound types 3 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, string $param): string; +} + +class C implements I1, I2 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +--EXPECTF-- +Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types4.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types4.phpt new file mode 100644 index 0000000000000..22ae5a730a95b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_no_bound_types4.phpt @@ -0,0 +1,21 @@ +--TEST-- +Implicit interface inheritance missing bound types 4 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, string $param): string; +} + +class C implements I2, I1 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +--EXPECTF-- +Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types.phpt new file mode 100644 index 0000000000000..4e3e09e9a872b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types.phpt @@ -0,0 +1,22 @@ +--TEST-- +Implicit interface inheritance with same bound types +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I2, I1 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types2.phpt b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types2.phpt new file mode 100644 index 0000000000000..d6ca51381c9df --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/bound_types/implicit_interface_same_bound_types2.phpt @@ -0,0 +1,22 @@ +--TEST-- +Implicit interface inheritance with same bound types 2 +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I1 { + public function bar(int $o, T $param): T; +} + +class C implements I1, I2 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint.phpt new file mode 100644 index 0000000000000..2f1c57492c833 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint.phpt @@ -0,0 +1,31 @@ +--TEST-- +Abstract generic type with a constraint +--FILE-- + { + public function foo(T $param): T; +} + +class CS implements I { + public function foo(string $param): string { + return $param . '!'; + } +} + +class CI implements I { + public function foo(int $param): int { + return $param + 42; + } +} + +$cs = new CS(); +var_dump($cs->foo("Hello")); + +$ci = new CI(); +var_dump($ci->foo(5)); + +?> +--EXPECT-- +string(6) "Hello!" +int(47) diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint_failed.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint_failed.phpt new file mode 100644 index 0000000000000..440d1868e6162 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/abstract_generic_type_with_constraint_failed.phpt @@ -0,0 +1,16 @@ +--TEST-- +Abstract generic type with a constraint that is not satisfied +--FILE-- + { + public function foo(T $param): T; +} + +class C implements I { + public function foo(float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Bound type float is not a subtype of the constraint type string|int of generic type T of interface I in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_generic_type_as_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_generic_type_as_constraint.phpt new file mode 100644 index 0000000000000..7a964dd0f60a5 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_generic_type_as_constraint.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use generic type as a constraint +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot use generic parameter T1 to constrain generic parameter T2 in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_never_as_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_never_as_constraint.phpt new file mode 100644 index 0000000000000..485841f50ee59 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_never_as_constraint.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use never as a constraint +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot use static, void, or never to constrain generic parameter T in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_static_as_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_static_as_constraint.phpt new file mode 100644 index 0000000000000..86bcc1d2b7613 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_static_as_constraint.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use void as a constraint +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot use static, void, or never to constrain generic parameter T in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_void_as_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_void_as_constraint.phpt new file mode 100644 index 0000000000000..1f727f33665ae --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/errors/using_void_as_constraint.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use void as a constraint +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot use static, void, or never to constrain generic parameter T in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_constraint_failure.phpt b/Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_constraint_failure.phpt new file mode 100644 index 0000000000000..4203cc15b3ec9 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/constraints/extended_interface_abstract_generic_constraint_failure.phpt @@ -0,0 +1,21 @@ +--TEST-- +Abstract generic type behaviour in extended interface violates type constraint +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I { + public function bar(int $o, T $param): T; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(int $o, float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Constraint type mixed of generic type T of interface I2 is not a subtype of the constraint type (Traversable&Countable)|string|int of generic type T of interface I in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt new file mode 100644 index 0000000000000..a4f2defb5d0d5 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection1.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in intersection (simple intersection with class type) +--FILE-- + { + public function foo(T&Traversable $param): T&Traversable; +} + +?> +--EXPECTF-- +Fatal error: Generic type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt new file mode 100644 index 0000000000000..bbffdc731ae4f --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_intersection2.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in intersection (DNF type) +--FILE-- + { + public function foo(stdClass|(T&Traversable) $param): stdClass|(T&Traversable); +} + +?> +--EXPECTF-- +Fatal error: Generic type cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt new file mode 100644 index 0000000000000..2880ff7007ff3 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union1.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (simple union with built-in type) +--FILE-- + { + public function foo(T|int $param): T|int; +} + +?> +--EXPECTF-- +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt new file mode 100644 index 0000000000000..a1b9d7e2f0fd4 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union2.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (simple union with class type) +--FILE-- + { + public function foo(T|stdClass $param): T|stdClass; +} + +?> +--EXPECTF-- +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt new file mode 100644 index 0000000000000..3f93897a21655 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union3.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (DNF type) +--FILE-- + { + public function foo(T|(Traversable&Countable) $param): T|(Traversable&Countable); +} + +?> +--EXPECTF-- +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt new file mode 100644 index 0000000000000..d69a58e155643 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union4.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (nullable type union with ?) +--FILE-- + { + public function foo(?T $param): ?T; +} + +?> +--EXPECTF-- +Fatal error: Generic type cannot be part of a union type in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt new file mode 100644 index 0000000000000..26287188e31cc --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_cannot_be_in_union5.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type cannot be in union (forced allowed null) +--FILE-- + { + public function foo(T $param = null): T; +} + +?> +--EXPECTF-- +Fatal error: Generic type cannot be part of a union type (implicitly nullable due to default null value) in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt new file mode 100644 index 0000000000000..2b3af5523774f --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt @@ -0,0 +1,13 @@ +--TEST-- +Abstract generic type in class is invalid +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +--EXPECTF-- +Fatal error: Cannot declare generic type on a class in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class_with_class_modifier.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class_with_class_modifier.phpt new file mode 100644 index 0000000000000..38929dc423776 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class_with_class_modifier.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type in class is invalid (with class modifier) +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot declare generic type on a class in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_trait.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_trait.phpt new file mode 100644 index 0000000000000..c3d1f12ca2f2a --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_trait.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type in trait is invalid +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Fatal error: Cannot declare generic type on a trait in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_redeclared.phpt b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_redeclared.phpt new file mode 100644 index 0000000000000..148baa42f3b02 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_redeclared.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type that is redeclared +--FILE-- + { + public function foo(T&Traversable $param): T&Traversable; +} + +?> +--EXPECTF-- +Fatal error: Duplicate generic parameter T in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt new file mode 100644 index 0000000000000..932ff702388e2 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type.phpt @@ -0,0 +1,16 @@ +--TEST-- +Implementing class does not bind any abstract generic type +--FILE-- + { + public function foo(T $param): T; +} + +class C implements I { + public function foo(float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Interface I expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt new file mode 100644 index 0000000000000..bce328f805b1d --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_constraint.phpt @@ -0,0 +1,16 @@ +--TEST-- +Implementing class does not bind any abstract generic type +--FILE-- + { + public function foo(T $param): T; +} + +class C implements I { + public function foo(float $param): float {} +} + +?> +--EXPECTF-- +Fatal error: Interface I expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt new file mode 100644 index 0000000000000..417c90a8d2aae --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/no_bound_abstract_generic_type_with_prior_bound_types.phpt @@ -0,0 +1,20 @@ +--TEST-- +Implementing class does not bind any abstract generic type +--FILE-- + { + public function foo(T $param): T; +} +interface I2 { + public function bar(T $param): T; +} + +class C implements I1, I2 { + public function foo(float $param): float {} + public function bar(string $param): string {} +} + +?> +--EXPECTF-- +Fatal error: Interface I2 expects 1 generic parameters, 0 given in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_1_to_2.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_1_to_2.phpt new file mode 100644 index 0000000000000..3b5ebbde839ef --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_1_to_2.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract generic type behaviour in extended interface 2 generic type to 1 +--FILE-- + { + public function foo(T1 $param): T2; +} + +interface I2 extends I { + public function bar(int $o, S $param): S; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt new file mode 100644 index 0000000000000..038e66ef63e3c --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types_basic.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T1 $param): T1; +} + +interface I2 extends I { + public function bar(int $o, T2 $param): T2; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type.phpt new file mode 100644 index 0000000000000..8fa85be75ca2c --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I { + public function bar(T2 $o, T $param): T2; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(float $o, string $param): float {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt new file mode 100644 index 0000000000000..784fd9a0aa33b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt @@ -0,0 +1,22 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T $param): T; +} + +interface I2 extends I { + public function bar(T $o, T2 $param): T; +} + +class C implements I2 { + public function foo(string $param): string {} + public function bar(float $o, string $param): float {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt new file mode 100644 index 0000000000000..359d94d594374 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_redeclares_method.phpt @@ -0,0 +1,18 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method +--FILE-- + { + public function foo(T $param): int; +} + +interface J extends I { + public function foo(float $param): int; +} + +?> +DONE +--EXPECT-- +DONE + diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_contravariant.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_contravariant.phpt new file mode 100644 index 0000000000000..44ca267727701 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_contravariant.phpt @@ -0,0 +1,17 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method generic type contravariant +--FILE-- + { + public function foo(T $param): int; +} + +interface J extends I { + public function foo(S $param): int; +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_covariant.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_covariant.phpt new file mode 100644 index 0000000000000..b73f71c68a14b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_with_generics_redeclares_method_covariant.phpt @@ -0,0 +1,18 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method generic type covariant +--FILE-- + { + public function foo(int $param): T; +} + +interface J extends I { + public function foo(int $param): S; +} + +?> +DONE +--EXPECT-- +DONE + diff --git a/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt new file mode 100644 index 0000000000000..14ce05d3e248b --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_abstract_generic_types_basic.phpt @@ -0,0 +1,27 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T1 $param): T1; +} + +interface I2 extends I1 { + public function bar(int $o, T2 $param): T2; +} + +interface I3 extends I2 { + public function foobar(T3 $a, float $b): float; +} + +class C implements I3 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} + public function foobar(string $a, float $b): float {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_generic_into_concrete.phpt b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_generic_into_concrete.phpt new file mode 100644 index 0000000000000..f42fb17c691b0 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_twice_interface_generic_into_concrete.phpt @@ -0,0 +1,27 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--FILE-- + { + public function foo(T1 $param): T1; +} + +interface I2 extends I1 { + public function bar(int $o, T2 $param): T2; +} + +interface I3 extends I2 { + public function foobar(string $a, float $b): float; +} + +class C implements I3 { + public function foo(string $param): string {} + public function bar(int $o, string $param): string {} + public function foobar(string $a, float $b): float {} +} + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type.phpt b/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type.phpt new file mode 100644 index 0000000000000..63b3bf6f202f7 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/multiple_abstract_generic_type.phpt @@ -0,0 +1,72 @@ +--TEST-- +Multiple abstract generic type +--FILE-- + { + public function set(K $key, V $value): void; + public function get(K $key): V; +} + +class C1 implements I { + public array $a = []; + public function set(int $key, string $value): void { + $this->a[$key] = $value . '!'; + } + public function get(int $key): string { + return $this->a[$key]; + } +} + +class C2 implements I { + public array $a = []; + public function set(string $key, object $value): void { + $this->a[$key] = $value; + } + public function get(string $key): object { + return $this->a[$key]; + } +} + +$c1 = new C1(); +$c1->set(5, "Hello"); +var_dump($c1->a); +var_dump($c1->get(5)); + +$c2 = new C2(); +$c2->set('C1', $c1); +var_dump($c2->a); +var_dump($c2->get('C1')); + +try { + $c1->set('blah', "Hello"); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + + +?> +--EXPECTF-- +array(1) { + [5]=> + string(6) "Hello!" +} +string(6) "Hello!" +array(1) { + ["C1"]=> + object(C1)#1 (1) { + ["a"]=> + array(1) { + [5]=> + string(6) "Hello!" + } + } +} +object(C1)#1 (1) { + ["a"]=> + array(1) { + [5]=> + string(6) "Hello!" + } +} +TypeError: C1::set(): Argument #1 ($key) must be of type int, string given, called in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_contravariance.phpt b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_contravariance.phpt new file mode 100644 index 0000000000000..602b917ea5fc0 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_contravariance.phpt @@ -0,0 +1,17 @@ +--TEST-- +Redeclaring a method that has a concrete type into a generic type (contravariance) +--FILE-- + extends I { + public function foo(S $param): int; +} + +?> +DONE +--EXPECTF-- +Fatal error: Declaration of J::foo( $param): int must be compatible with I::foo(int $param): int %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_covariance.phpt b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_covariance.phpt new file mode 100644 index 0000000000000..005a055e3f0fd --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_concrete_to_generic_covariance.phpt @@ -0,0 +1,17 @@ +--TEST-- +Redeclaring a method that has a concrete type into a generic type (covariance) +--FILE-- + extends I { + public function foo(int $param): S; +} + +?> +DONE +--EXPECTF-- +Fatal error: Declaration of J::foo(int $param): must be compatible with I::foo(int $param): int %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_contravariance.phpt b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_contravariance.phpt new file mode 100644 index 0000000000000..f5360557bb6a3 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_contravariance.phpt @@ -0,0 +1,17 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method but does not use bound generic type (contravariance) +--FILE-- + { + public function foo(T $param): int; +} + +interface J extends I { + public function foo(int $param): int; +} + +?> +DONE +--EXPECTF-- +Fatal error: Declaration of J::foo(int $param): int must be compatible with I>::foo(> $param): int %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_covariance.phpt b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_covariance.phpt new file mode 100644 index 0000000000000..beb571606cf0f --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/extended_interface_with_generics_redeclares_method_invalid_covariance.phpt @@ -0,0 +1,17 @@ +--TEST-- +Abstract generic type behaviour in extended interface which redeclares method but does not use bound generic type (covariance) +--FILE-- + { + public function foo(int $param): T; +} + +interface J extends I { + public function foo(int $param): int; +} + +?> +DONE +--EXPECTF-- +Fatal error: Declaration of J::foo(int $param): int must be compatible with I>::foo(int $param): > in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/variance/multiple_abstract_generic_type-error.phpt b/Zend/tests/type_declarations/abstract_generics/variance/multiple_abstract_generic_type-error.phpt new file mode 100644 index 0000000000000..9112b8fd11aaa --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/variance/multiple_abstract_generic_type-error.phpt @@ -0,0 +1,23 @@ +--TEST-- +Multiple abstract generic type incorrect bound types in implementation +--FILE-- + { + public function set(K $key, V $value): void; + public function get(K $key): V; +} + +class C implements I { + public array $a = []; + public function set(int $key, string $value): void { + $this->a[$key] = $value . '!'; + } + public function get(int $key): string { + return $this->a[$key]; + } +} + +?> +--EXPECTF-- +Fatal error: Declaration of C::set(int $key, string $value): void must be compatible with I::set( $key, $value): void in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 8957241ccfb37..f3fd58ee8661a 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -219,6 +219,12 @@ struct _zend_class_entry { zend_trait_precedence **trait_precedences; HashTable *attributes; + /* The bound_types HashTable is a map: "lower_case_interface_names" => map + * Where an integer index refers to the position, and the string to the name of the generic parameter */ + HashTable *bound_types; + zend_generic_parameter *generic_parameters; + uint32_t num_generic_parameters; + uint32_t enum_backing_type; HashTable *backed_enum_table; diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 30bd4a9c05dd0..57de0927062a4 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1567,6 +1567,16 @@ static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int zend_ast_export_ex(str, ast, priority, indent); } +static ZEND_COLD void zend_ast_export_class_name(smart_str *str, zend_ast *ast, int priority, int indent) +{ + if (ast->kind == ZEND_AST_CLASS_REF) { + ZEND_ASSERT(ast->child[1] == NULL && "Generic params not supported yet"); + zend_ast_export_ns_name(str, ast->child[0], priority, indent); + return; + } + zend_ast_export_ex(str, ast, priority, indent); +} + static ZEND_COLD bool zend_ast_valid_var_char(char ch) { unsigned char c = (unsigned char)ch; @@ -1687,7 +1697,7 @@ static ZEND_COLD void zend_ast_export_name_list_ex(smart_str *str, const zend_as if (i != 0) { smart_str_appends(str, separator); } - zend_ast_export_name(str, list->child[i], 0, indent); + zend_ast_export_ns_name(str, list->child[i], 0, indent); i++; } } @@ -1954,6 +1964,21 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in zend_ast_export_ns_name(str, ast, 0, indent); } +static ZEND_COLD void zend_ast_export_generic_arg_list(smart_str *str, const zend_ast_list *list, int indent) { + // TODO Why cannot I just use + // zend_ast_export_list(str, list, true, 0, indent); + // ? + + uint32_t i = 0; + while (i < list->children) { + if (i != 0) { + smart_str_appends(str, ", "); + } + zend_ast_export_type(str, list->child[i], indent); + i++; + } +} + static ZEND_COLD void zend_ast_export_hook_list(smart_str *str, const zend_ast_list *hook_list, int indent) { smart_str_appends(str, " {"); @@ -2159,10 +2184,17 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } smart_str_appends(str, "class "); } - smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name)); - if (decl->flags & ZEND_ACC_ENUM && decl->child[4]) { - smart_str_appends(str, ": "); - zend_ast_export_type(str, decl->child[4], indent); + smart_str_append(str, decl->name); + if (decl->child[4]) { + if (decl->flags & ZEND_ACC_ENUM) { + smart_str_appends(str, ": "); + zend_ast_export_type(str, decl->child[4], indent); + } else { + ZEND_ASSERT(decl->flags & ZEND_ACC_INTERFACE); + smart_str_appendc(str, '<'); + zend_ast_export_list(str, zend_ast_get_list(decl->child[4]), true, 0, indent); + smart_str_appendc(str, '>'); + } } zend_ast_export_class_no_header(str, decl, indent); smart_str_appendc(str, '\n'); @@ -2453,6 +2485,21 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, "::"); zend_ast_export_name(str, ast->child[1], 0, indent); break; + case ZEND_AST_GENERIC_PARAM: + zend_ast_export_name(str, ast->child[0], 0, indent); + if (ast->child[1]) { + smart_str_appendl(str, ZEND_STRL(" : ")); + zend_ast_export_type(str, ast->child[1], indent); + } + break; + case ZEND_AST_CLASS_REF: + zend_ast_export_ns_name(str, ast->child[0], 0, indent); + if (ast->child[1]) { + smart_str_appendc(str, '<'); + zend_ast_export_generic_arg_list(str, zend_ast_get_list(ast->child[1]), indent); + smart_str_appendc(str, '>'); + } + break; case ZEND_AST_CLASS_NAME: if (ast->child[0] == NULL) { /* The const expr representation stores the fetch type instead. */ @@ -2466,7 +2513,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio EMPTY_SWITCH_DEFAULT_CASE() } } else { - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + zend_ast_export_class_name(str, ast->child[0], 0, indent); } smart_str_appends(str, "::class"); break; diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index fb48b187252b3..e7299d834db8e 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -70,6 +70,8 @@ enum _zend_ast_kind { ZEND_AST_ATTRIBUTE_GROUP, ZEND_AST_MATCH_ARM_LIST, ZEND_AST_MODIFIER_LIST, + ZEND_AST_GENERIC_PARAM_LIST, + ZEND_AST_GENERIC_ARG_LIST, /* 0 child nodes */ ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT, @@ -154,6 +156,8 @@ enum _zend_ast_kind { ZEND_AST_NAMED_ARG, ZEND_AST_PARENT_PROPERTY_HOOK_CALL, ZEND_AST_PIPE, + ZEND_AST_GENERIC_PARAM, + ZEND_AST_CLASS_REF, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0cb4ee3740f1b..4922531e450c4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -85,6 +85,8 @@ static inline uint32_t zend_alloc_cache_slot(void) { return zend_alloc_cache_slots(1); } +const zend_type zend_mixed_type = { NULL, MAY_BE_ANY, 0 }; + ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type); ZEND_API zend_op_array *(*zend_compile_string)(zend_string *source_string, const char *filename, zend_compile_position position); @@ -1397,7 +1399,7 @@ static zend_string *resolve_class_name(zend_string *name, const zend_class_entry } static zend_string *add_intersection_type(zend_string *str, - const zend_type_list *intersection_type_list, zend_class_entry *scope, + const zend_type_list *intersection_type_list, const zend_class_entry *scope, bool is_bracketed) { const zend_type *single_type; @@ -1422,7 +1424,40 @@ static zend_string *add_intersection_type(zend_string *str, return str; } -zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope) { +static zend_string* resolve_bound_generic_type(const zend_type *type, const zend_class_entry *scope, const HashTable *bound_types) { + const zend_string *type_name = ZEND_TYPE_NAME(*type); + if (bound_types == NULL) { + const size_t len = ZSTR_LEN(type_name) + strlen("<>"); + zend_string *result = zend_string_alloc(len, 0); + ZSTR_VAL(result)[0] = '<'; + memcpy(ZSTR_VAL(result) + strlen("<"), ZSTR_VAL(type_name), ZSTR_LEN(type_name)); + ZSTR_VAL(result)[len-1] = '>'; + ZSTR_VAL(result)[len] = '\0'; + return result; + } + + const zend_type *constraint = zend_hash_index_find_ptr(bound_types, type->generic_param_index); + ZEND_ASSERT(constraint != NULL); + + zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope, NULL); + + size_t len = ZSTR_LEN(type_name) + ZSTR_LEN(constraint_type_str) + strlen("< : >"); + zend_string *result = zend_string_alloc(len, 0); + + ZSTR_VAL(result)[0] = '<'; + memcpy(ZSTR_VAL(result) + strlen("<"), ZSTR_VAL(type_name), ZSTR_LEN(type_name)); + ZSTR_VAL(result)[ZSTR_LEN(type_name) + 1] = ' '; + ZSTR_VAL(result)[ZSTR_LEN(type_name) + 2] = ':'; + ZSTR_VAL(result)[ZSTR_LEN(type_name) + 3] = ' '; + memcpy(ZSTR_VAL(result) + ZSTR_LEN(type_name) + strlen("< : "), ZSTR_VAL(constraint_type_str), ZSTR_LEN(constraint_type_str)); + ZSTR_VAL(result)[len-1] = '>'; + ZSTR_VAL(result)[len] = '\0'; + + zend_string_release(constraint_type_str); + return result; +} + +zend_string *zend_type_to_string_resolved(const zend_type type, const zend_class_entry *scope, const HashTable *bound_types_to_scope) { zend_string *str = NULL; /* Pure intersection type */ @@ -1445,6 +1480,8 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry str = add_type_string(str, resolved, /* is_intersection */ false); zend_string_release(resolved); } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(type)) { + str = resolve_bound_generic_type(&type, scope, bound_types_to_scope); } else if (ZEND_TYPE_HAS_NAME(type)) { str = resolve_class_name(ZEND_TYPE_NAME(type), scope); } @@ -1514,7 +1551,7 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry } ZEND_API zend_string *zend_type_to_string(zend_type type) { - return zend_type_to_string_resolved(type, NULL); + return zend_type_to_string_resolved(type, NULL, NULL); } static bool is_generator_compatible_class_type(const zend_string *name) { @@ -1761,6 +1798,18 @@ static zend_string *zend_resolve_const_class_name_reference(zend_ast *ast, const return zend_resolve_class_name(class_name, ast->attr); } +static zend_string *zend_resolve_const_class_name_reference_with_generics(zend_ast *ast, const char *type) +{ + zend_ast *name_ast = ast->child[0]; + zend_string *class_name = zend_ast_get_str(name_ast); + if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type_ast(name_ast)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use \"%s\" as %s, as it is reserved", + ZSTR_VAL(class_name), type); + } + return zend_resolve_class_name(class_name, name_ast->attr); +} + static void zend_ensure_valid_class_fetch_type(uint32_t fetch_type) /* {{{ */ { if (fetch_type != ZEND_FETCH_CLASS_DEFAULT && zend_is_scope_known()) { @@ -2066,6 +2115,10 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->default_static_members_count = 0; ce->properties_info_table = NULL; ce->attributes = NULL; + ce->bound_types = NULL; + // TODO Should these be inside nullify_handlers? + ce->generic_parameters = NULL; + ce->num_generic_parameters = 0; ce->enum_backing_type = IS_UNDEF; ce->backed_enum_table = NULL; @@ -7159,9 +7212,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */ static zend_type zend_compile_single_typename(zend_ast *ast) { + zend_class_entry *ce = CG(active_class_entry); + ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE)); if (ast->kind == ZEND_AST_TYPE) { - if (ast->attr == IS_STATIC && !CG(active_class_entry) && zend_is_scope_known()) { + if (ast->attr == IS_STATIC && !ce && zend_is_scope_known()) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use \"static\" when no class scope is active"); } @@ -7190,8 +7245,17 @@ static zend_type zend_compile_single_typename(zend_ast *ast) } else { const char *correct_name; uint32_t fetch_type = zend_get_class_fetch_type_ast(ast); - zend_string *class_name = type_name; + if (ce && ce->num_generic_parameters > 0) { + for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { + const zend_generic_parameter *generic_param = &ce->generic_parameters[generic_param_index]; + if (zend_string_equals(type_name, generic_param->name)) { + return (zend_type) ZEND_TYPE_INIT_GENERIC_PARAM(zend_string_copy(type_name), generic_param_index); + } + } + } + + 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, "a type name"); @@ -7390,6 +7454,9 @@ static zend_type zend_compile_typename_ex( single_type = zend_compile_single_typename(type_ast); uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type); + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(single_type)) { + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type"); + } if (single_type_mask == MAY_BE_ANY) { zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type"); } @@ -7472,6 +7539,9 @@ static zend_type zend_compile_typename_ex( zend_ast *type_ast = list->child[i]; zend_type single_type = zend_compile_single_typename(type_ast); + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(single_type)) { + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of an intersection type"); + } /* An intersection of union types cannot exist so invalidate it * Currently only can happen with iterable getting canonicalized to Traversable|array */ if (ZEND_TYPE_IS_ITERABLE_FALLBACK(single_type)) { @@ -7538,6 +7608,12 @@ static zend_type zend_compile_typename_ex( if ((type_mask & MAY_BE_NULL) && is_marked_nullable) { zend_error_noreturn(E_COMPILE_ERROR, "null cannot be marked as nullable"); } + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(type) && is_marked_nullable) { + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type"); + } + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(type) && force_allow_null) { + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type (implicitly nullable due to default null value)"); + } if (force_allow_null && !is_marked_nullable && !(type_mask & MAY_BE_NULL)) { *forced_allow_null = true; @@ -9266,20 +9342,52 @@ static void zend_compile_use_trait(const zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_bound_types_ht_dtor(zval *ptr) { + HashTable *interface_bound_types = Z_PTR_P(ptr); + zend_hash_destroy(interface_bound_types); + efree(interface_bound_types); +} + +static void zend_types_ht_dtor(zval *ptr) { + zend_type *type = Z_PTR_P(ptr); + // TODO Figure out persistency? + zend_type_release(*type, false); + efree(type); +} + static void zend_compile_implements(zend_ast *ast) /* {{{ */ { const zend_ast_list *list = zend_ast_get_list(ast); zend_class_entry *ce = CG(active_class_entry); zend_class_name *interface_names; - uint32_t i; interface_names = emalloc(sizeof(zend_class_name) * list->children); - for (i = 0; i < list->children; ++i) { - zend_ast *class_ast = list->child[i]; + for (uint32_t i = 0; i < list->children; ++i) { + zend_ast *interface_ast = list->child[i]; interface_names[i].name = - zend_resolve_const_class_name_reference(class_ast, "interface name"); + zend_resolve_const_class_name_reference_with_generics(interface_ast, "interface name"); interface_names[i].lc_name = zend_string_tolower(interface_names[i].name); + + if (interface_ast->child[1]) { + const zend_ast_list *generics_list = zend_ast_get_list(interface_ast->child[1]); + const uint32_t num_generic_args = generics_list->children; + + // TODO Can we already check that we have correct number of generic args? + if (ce->bound_types == NULL) { + ALLOC_HASHTABLE(ce->bound_types); + zend_hash_init(ce->bound_types, list->children-i, NULL, zend_bound_types_ht_dtor, false /* todo depend on internal or not? */); + } + + HashTable *bound_interface_types; + ALLOC_HASHTABLE(bound_interface_types); + zend_hash_init(bound_interface_types, num_generic_args, NULL, zend_types_ht_dtor, false /* todo depend on internal or not? */); + for (uint32_t generic_param = 0; generic_param < num_generic_args; ++generic_param) { + zend_type bound_type = zend_compile_typename(generics_list->child[generic_param]); + zend_hash_index_add_mem(bound_interface_types, generic_param, &bound_type, sizeof(bound_type)); + } + zend_hash_add_new_ptr(ce->bound_types, interface_names[i].lc_name, bound_interface_types); + } } ce->num_interfaces = list->children; @@ -9298,7 +9406,7 @@ static zend_string *zend_generate_anon_class_name(const zend_ast_decl *decl) prefix = zend_resolve_const_class_name_reference(decl->child[0], "class name"); } else if (decl->child[1]) { const zend_ast_list *list = zend_ast_get_list(decl->child[1]); - prefix = zend_resolve_const_class_name_reference(list->child[0], "interface name"); + prefix = zend_resolve_const_class_name_reference_with_generics(list->child[0], "interface name"); } zend_string *result = zend_strpprintf(0, "%s@anonymous%c%s:%" PRIu32 "$%" PRIx32, @@ -9327,6 +9435,52 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ zend_type_release(type, 0); } +static void zend_compile_generic_params(zend_ast *params_ast) +{ + const zend_ast_list *list = zend_ast_get_list(params_ast); + zend_generic_parameter *generic_params = safe_pemalloc(list->children, sizeof(zend_generic_parameter), 0, CG(active_class_entry)->type & ZEND_INTERNAL_CLASS); + CG(active_class_entry)->generic_parameters = generic_params; + + for (uint32_t i = 0; i < list->children; i++) { + const zend_ast *param_ast = list->child[i]; + zend_string *name = zend_ast_get_str(param_ast->child[0]); + zend_type constraint_type = zend_mixed_type; + + if (zend_string_equals(name, CG(active_class_entry)->name)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Generic parameter %s has same name as class", ZSTR_VAL(name)); + } + + for (uint32_t j = 0; j < i; j++) { + if (zend_string_equals(name, generic_params[j].name)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Duplicate generic parameter %s", ZSTR_VAL(name)); + } + } + + if (param_ast->child[1]) { + constraint_type = zend_compile_typename(param_ast->child[1]); + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(constraint_type)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use generic parameter %s to constrain generic parameter %s", + ZSTR_VAL(ZEND_TYPE_NAME(constraint_type)), ZSTR_VAL(name)); + } + if (ZEND_TYPE_FULL_MASK(constraint_type) & (MAY_BE_STATIC|MAY_BE_VOID|MAY_BE_NEVER)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use static, void, or never to constrain generic parameter %s", + ZSTR_VAL(name)); + } + } + + generic_params[i].name = zend_string_copy(name); + generic_params[i].constraint = constraint_type; + + /* Update number of parameters on the fly, so that previous parameters can be + * referenced in the type constraint of following parameters. */ + CG(active_class_entry)->num_generic_parameters = i + 1; + } +} + static void zend_compile_class_decl(znode *result, const zend_ast *ast, bool toplevel) /* {{{ */ { const zend_ast_decl *decl = (const zend_ast_decl *) ast; @@ -9421,6 +9575,18 @@ static void zend_compile_class_decl(znode *result, const zend_ast *ast, bool top zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); } + /* Enums use the 4th node to store the backing type */ + if (decl->child[4] && (ce->ce_flags & ZEND_ACC_ENUM) == 0) { + if (UNEXPECTED((ce->ce_flags & ZEND_ACC_INTERFACE) == 0)) { + const char *type = "a class"; + if (decl->flags & ZEND_ACC_TRAIT) { + type = "a trait"; + } + zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare generic type on %s", type); + } + zend_compile_generic_params(decl->child[4]); + } + if (implements_ast) { zend_compile_implements(implements_ast); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index d2a3b47bf92f4..b41c47d236d43 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -54,6 +54,7 @@ typedef struct _zend_op_array zend_op_array; typedef struct _zend_op zend_op; +extern const zend_type zend_mixed_type; /* On 64-bit systems less optimal, but more compact VM code leads to better * performance. So on 32-bit systems we use absolute addresses for jump @@ -1028,7 +1029,7 @@ int ZEND_FASTCALL zendlex(zend_parser_stack_elem *elem); void zend_assert_valid_class_name(const zend_string *const_name, const char *type); -zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope); +zend_string *zend_type_to_string_resolved(zend_type type, const zend_class_entry *scope, const HashTable *bound_types_to_scope); ZEND_API zend_string *zend_type_to_string(zend_type type); /* BEGIN: OPCODES */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 518cbb98fc0f8..c88401c725678 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -695,7 +695,7 @@ static ZEND_COLD void zend_verify_type_error_common( *fclass = ""; } - *need_msg = zend_type_to_string_resolved(arg_info->type, zf->common.scope); + *need_msg = zend_type_to_string_resolved(arg_info->type, zf->common.scope, /* TODO? */ NULL); if (value) { *given_kind = zend_zval_value_name(value); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 922d244aec2f8..9e5740d905bf8 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -60,8 +60,8 @@ static void add_property_hook_obligation( zend_class_entry *ce, const zend_property_info *hooked_prop, const zend_function *hook_func); static void ZEND_COLD emit_incompatible_method_error( - const zend_function *child, zend_class_entry *child_scope, - const zend_function *parent, zend_class_entry *parent_scope, + const zend_function *child, const zend_class_entry *child_scope, + const zend_function *parent, const zend_class_entry *parent_scope, inheritance_status status); static void zend_type_copy_ctor(zend_type *const type, bool use_arena, bool persistent); @@ -91,7 +91,7 @@ static void zend_type_list_copy_ctor( static void zend_type_copy_ctor(zend_type *const type, bool use_arena, bool persistent) { if (ZEND_TYPE_HAS_LIST(*type)) { zend_type_list_copy_ctor(type, use_arena, persistent); - } else if (ZEND_TYPE_HAS_NAME(*type)) { + } else if (ZEND_TYPE_HAS_NAME(*type) || ZEND_TYPE_IS_GENERIC_PARAM_NAME(*type)) { zend_string_addref(ZEND_TYPE_NAME(*type)); } } @@ -473,7 +473,8 @@ static inheritance_status zend_is_intersection_subtype_of_class( /* Check whether a single class proto type is a subtype of a potentially complex fe_type. */ static inheritance_status zend_is_class_subtype_of_type( zend_class_entry *fe_scope, zend_string *fe_class_name, - zend_class_entry *proto_scope, const zend_type proto_type) { + zend_class_entry *proto_scope, const zend_type proto_type +) { zend_class_entry *fe_ce = NULL; bool have_unresolved = false; @@ -606,9 +607,9 @@ static void register_unresolved_classes(zend_class_entry *scope, const zend_type } static inheritance_status zend_is_intersection_subtype_of_type( - zend_class_entry *fe_scope, const zend_type fe_type, - zend_class_entry *proto_scope, const zend_type proto_type) -{ + zend_class_entry *fe_scope, const zend_type fe_type, + zend_class_entry *proto_scope, const zend_type proto_type +) { bool have_unresolved = false; const zend_type *single_type; uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); @@ -671,7 +672,53 @@ static inheritance_status zend_is_intersection_subtype_of_type( return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } -ZEND_API inheritance_status zend_perform_covariant_type_check( +static inheritance_status zend_perform_covariant_type_check( + zend_class_entry *fe_scope, const zend_type fe_type, + zend_class_entry *proto_scope, const zend_type proto_type); + +static inheritance_status zend_perform_contravariant_type_check( + zend_class_entry *fe_scope, const zend_type fe_type, + zend_class_entry *proto_scope, const zend_type proto_type); + +static inheritance_status zend_is_type_subtype_of_generic_type( + zend_class_entry *concrete_scope, + const zend_type concrete_type, + zend_class_entry *generic_type_scope, + const zend_type generic_type +) { + ZEND_ASSERT(concrete_scope->bound_types); + const HashTable *bound_generic_types = zend_hash_find_ptr_lc(concrete_scope->bound_types, generic_type_scope->name); + + ZEND_ASSERT(bound_generic_types && "Have generic type"); + ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(generic_type)); + + const zend_type *bound_type_ptr = zend_hash_index_find_ptr(bound_generic_types, generic_type.generic_param_index); + ZEND_ASSERT(bound_type_ptr != NULL); + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*bound_type_ptr)) { + if ( + ZEND_TYPE_IS_GENERIC_PARAM_NAME(concrete_type) + && concrete_type.generic_param_index == bound_type_ptr->generic_param_index + ) { + return INHERITANCE_SUCCESS; + } else { + return INHERITANCE_ERROR; + } + } else { + /* Generic type must be invariant */ + const inheritance_status sub_type_status = zend_perform_covariant_type_check( + concrete_scope, concrete_type, generic_type_scope, *bound_type_ptr); + const inheritance_status super_type_status = zend_perform_contravariant_type_check( + concrete_scope, concrete_type, generic_type_scope, *bound_type_ptr); + + if (sub_type_status != super_type_status) { + return INHERITANCE_ERROR; + } else { + return sub_type_status; + } + } +} + +static inheritance_status zend_perform_covariant_type_check_no_generics( zend_class_entry *fe_scope, const zend_type fe_type, zend_class_entry *proto_scope, const zend_type proto_type) { @@ -762,9 +809,68 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( return INHERITANCE_UNRESOLVED; } +static inheritance_status zend_is_generic_type_subtype_of_generic_type( + const zend_class_entry *fe_scope, const zend_type fe_type, + const zend_class_entry *proto_scope, const zend_type proto_type +) { + if (UNEXPECTED(!ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type))) { + /* A generic type cannot be a subtype of a concrete one */ + return INHERITANCE_ERROR; + } + ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type)); + ZEND_ASSERT(fe_scope->bound_types); + + const HashTable *bound_generic_types = zend_hash_find_ptr_lc(fe_scope->bound_types, proto_scope->name); + ZEND_ASSERT(bound_generic_types && "Must have bound generic type"); + + const zend_type *bound_type_ptr = zend_hash_index_find_ptr(bound_generic_types, proto_type.generic_param_index); + ZEND_ASSERT(bound_type_ptr != NULL); + + const zend_type bound_type = *bound_type_ptr; + ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(bound_type)); + + return bound_type_ptr->generic_param_index == fe_type.generic_param_index ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; +} + +static inheritance_status zend_perform_covariant_type_check( + zend_class_entry *fe_scope, const zend_type fe_type, + zend_class_entry *proto_scope, const zend_type proto_type +) { + /* If we check for concrete return type */ + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type)) { + return zend_is_type_subtype_of_generic_type( + fe_scope, fe_type, proto_scope, proto_type); + } else if (UNEXPECTED(ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type))) { + /* A generic type cannot be a subtype of a concrete one */ + return INHERITANCE_ERROR; + } + + return zend_perform_covariant_type_check_no_generics( + fe_scope, fe_type, proto_scope, proto_type); +} + +static inheritance_status zend_perform_contravariant_type_check( + zend_class_entry *fe_scope, const zend_type fe_type, + zend_class_entry *proto_scope, const zend_type proto_type +) { + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type)) { + return zend_is_generic_type_subtype_of_generic_type( + fe_scope, fe_type, proto_scope, proto_type); + } + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type)) { + return zend_is_type_subtype_of_generic_type( + fe_scope, fe_type, proto_scope, proto_type); + } + + /* Contravariant type check is performed as a covariant type check with swapped + * argument order. */ + return zend_perform_covariant_type_check_no_generics( + proto_scope, proto_type, fe_scope, fe_type); +} + static inheritance_status zend_do_perform_arg_type_hint_check( - zend_class_entry *fe_scope, zend_arg_info *fe_arg_info, - zend_class_entry *proto_scope, zend_arg_info *proto_arg_info) /* {{{ */ + zend_class_entry *fe_scope, const zend_arg_info *fe_arg_info, + zend_class_entry *proto_scope, const zend_arg_info *proto_arg_info) /* {{{ */ { if (!ZEND_TYPE_IS_SET(fe_arg_info->type) || ZEND_TYPE_PURE_MASK(fe_arg_info->type) == MAY_BE_ANY) { /* Child with no type or mixed type is always compatible */ @@ -776,10 +882,8 @@ static inheritance_status zend_do_perform_arg_type_hint_check( return INHERITANCE_ERROR; } - /* Contravariant type check is performed as a covariant type check with swapped - * argument order. */ - return zend_perform_covariant_type_check( - proto_scope, proto_arg_info->type, fe_scope, fe_arg_info->type); + return zend_perform_contravariant_type_check( + fe_scope, fe_arg_info->type, proto_scope, proto_arg_info->type); } /* }}} */ @@ -832,10 +936,10 @@ static inheritance_status zend_do_perform_implementation_check( status = INHERITANCE_SUCCESS; for (uint32_t i = 0; i < num_args; i++) { - zend_arg_info *proto_arg_info = + const zend_arg_info *proto_arg_info = i < proto_num_args ? &proto->common.arg_info[i] : proto_is_variadic ? &proto->common.arg_info[proto_num_args - 1] : NULL; - zend_arg_info *fe_arg_info = + const zend_arg_info *fe_arg_info = i < fe_num_args ? &fe->common.arg_info[i] : fe_is_variadic ? &fe->common.arg_info[fe_num_args - 1] : NULL; if (!proto_arg_info) { @@ -897,10 +1001,10 @@ static inheritance_status zend_do_perform_implementation_check( /* }}} */ static ZEND_COLD void zend_append_type_hint( - smart_str *str, zend_class_entry *scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */ + smart_str *str, const zend_class_entry *scope, const HashTable *bound_types_to_scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */ { if (ZEND_TYPE_IS_SET(arg_info->type)) { - zend_string *type_str = zend_type_to_string_resolved(arg_info->type, scope); + zend_string *type_str = zend_type_to_string_resolved(arg_info->type, scope, bound_types_to_scope); smart_str_append(str, type_str); zend_string_release(type_str); if (!return_hint) { @@ -911,7 +1015,7 @@ static ZEND_COLD void zend_append_type_hint( /* }}} */ static ZEND_COLD zend_string *zend_get_function_declaration( - const zend_function *fptr, zend_class_entry *scope) /* {{{ */ + const zend_function *fptr, const zend_class_entry *scope, const HashTable *bound_types_to_scope) /* {{{ */ { smart_str str = {0}; @@ -926,6 +1030,31 @@ static ZEND_COLD zend_string *zend_get_function_declaration( } else { smart_str_appendl(&str, ZSTR_VAL(fptr->common.scope->name), ZSTR_LEN(fptr->common.scope->name)); } + if (scope->num_generic_parameters) { + bool is_first = true; + smart_str_appendc(&str, '<'); + for (uint32_t i = 0; i < scope->num_generic_parameters; i++) { + const zend_generic_parameter param = scope->generic_parameters[i]; + zend_string *constraint_type_str; + if (bound_types_to_scope) { + const zend_type *constraint = zend_hash_index_find_ptr(bound_types_to_scope, i); + ZEND_ASSERT(constraint != NULL); + constraint_type_str = zend_type_to_string_resolved(*constraint, scope, NULL); + } else { + constraint_type_str = zend_type_to_string_resolved(param.constraint, scope, NULL); + } + + if (!is_first) { + smart_str_appends(&str, ", "); + } + smart_str_append(&str, param.name); + smart_str_appends(&str, " : "); + smart_str_append(&str, constraint_type_str); + zend_string_release(constraint_type_str); + is_first = false; + } + smart_str_appendc(&str, '>'); + } smart_str_appends(&str, "::"); } @@ -942,7 +1071,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration( num_args++; } for (uint32_t i = 0; i < num_args;) { - zend_append_type_hint(&str, scope, arg_info, false); + zend_append_type_hint(&str, scope, bound_types_to_scope, arg_info, false); if (ZEND_ARG_SEND_MODE(arg_info)) { smart_str_appendc(&str, '&'); @@ -1037,7 +1166,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration( if (fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { smart_str_appends(&str, ": "); - zend_append_type_hint(&str, scope, fptr->common.arg_info - 1, true); + zend_append_type_hint(&str, scope, bound_types_to_scope, fptr->common.arg_info - 1, true); } smart_str_0(&str); @@ -1054,11 +1183,15 @@ static zend_always_inline uint32_t func_lineno(const zend_function *fn) { } static void ZEND_COLD emit_incompatible_method_error( - const zend_function *child, zend_class_entry *child_scope, - const zend_function *parent, zend_class_entry *parent_scope, + const zend_function *child, const zend_class_entry *child_scope, + const zend_function *parent, const zend_class_entry *parent_scope, inheritance_status status) { - zend_string *parent_prototype = zend_get_function_declaration(parent, parent_scope); - zend_string *child_prototype = zend_get_function_declaration(child, child_scope); + const HashTable *bound_types_to_parent = NULL; + if (child_scope->bound_types) { + bound_types_to_parent = zend_hash_find_ptr_lc(child_scope->bound_types, parent_scope->name); + } + zend_string *parent_prototype = zend_get_function_declaration(parent, parent_scope, bound_types_to_parent); + zend_string *child_prototype = zend_get_function_declaration(child, child_scope, NULL); if (status == INHERITANCE_UNRESOLVED) { // TODO Improve error message if first unresolved class is present in child and parent? /* Fetch the first unresolved class from registered autoloads */ @@ -1294,8 +1427,8 @@ static inheritance_status full_property_types_compatible( zend_perform_covariant_type_check( child_info->ce, child_info->type, parent_info->ce, parent_info->type); inheritance_status status2 = variance == PROP_COVARIANT ? INHERITANCE_SUCCESS : - zend_perform_covariant_type_check( - parent_info->ce, parent_info->type, child_info->ce, child_info->type); + zend_perform_contravariant_type_check( + child_info->ce, child_info->type, parent_info->ce, parent_info->type); if (status1 == INHERITANCE_SUCCESS && status2 == INHERITANCE_SUCCESS) { return INHERITANCE_SUCCESS; } @@ -1308,7 +1441,7 @@ static inheritance_status full_property_types_compatible( static ZEND_COLD void emit_incompatible_property_error( const zend_property_info *child, const zend_property_info *parent, prop_variance variance) { - zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce); + zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce, /* TODO? */ NULL); zend_error_noreturn(E_COMPILE_ERROR, "Type of %s::$%s must be %s%s (as in class %s)", ZSTR_VAL(child->ce->name), @@ -1322,7 +1455,7 @@ static ZEND_COLD void emit_incompatible_property_error( static ZEND_COLD void emit_set_hook_type_error(const zend_property_info *child, const zend_property_info *parent) { zend_type set_type = parent->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type; - zend_string *type_str = zend_type_to_string_resolved(set_type, parent->ce); + zend_string *type_str = zend_type_to_string_resolved(set_type, parent->ce, /* TODO? */ NULL); zend_error_noreturn(E_COMPILE_ERROR, "Set type of %s::$%s must be supertype of %s (as in %s %s)", ZSTR_VAL(child->ce->name), @@ -1351,8 +1484,8 @@ static inheritance_status verify_property_type_compatibility( if (parent_info->hooks[ZEND_PROPERTY_HOOK_SET] && (!child_info->hooks || !child_info->hooks[ZEND_PROPERTY_HOOK_SET])) { zend_type set_type = parent_info->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type; - inheritance_status result = zend_perform_covariant_type_check( - parent_info->ce, set_type, child_info->ce, child_info->type); + inheritance_status result = zend_perform_contravariant_type_check( + child_info->ce, child_info->type, parent_info->ce, set_type); if ((result == INHERITANCE_ERROR && throw_on_error) || (result == INHERITANCE_UNRESOLVED && throw_on_unresolved)) { emit_set_hook_type_error(child_info, parent_info); } @@ -1594,6 +1727,15 @@ static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry do_implement_interface_ex(ce, iface, iface); } +static ZEND_COLD void emit_incompatible_generic_arg_count_error(const zend_class_entry *iface, uint32_t given_args) { + zend_error_noreturn(E_COMPILE_ERROR, + "Interface %s expects %" PRIu32 " generic parameters, %" PRIu32 " given", + ZSTR_VAL(iface->name), + iface->num_generic_parameters, + given_args + ); +} + static void zend_do_inherit_interfaces(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ { /* expects interface to be contained in ce's interface list already */ @@ -1612,6 +1754,19 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, zend_class_entry *i zend_class_entry *entry = iface->interfaces[if_num]; for (i = 0; i < ce_num; i++) { if (ce->interfaces[i] == entry) { + if (entry->num_generic_parameters) { + if (UNEXPECTED(ce->bound_types == NULL)) { + emit_incompatible_generic_arg_count_error(entry, 0); + } + const HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, entry->name); + if (UNEXPECTED(bound_types == NULL)) { + emit_incompatible_generic_arg_count_error(entry, 0); + } + const uint32_t num_bound_types = zend_hash_num_elements(bound_types); + if (UNEXPECTED(num_bound_types != entry->num_generic_parameters)) { + emit_incompatible_generic_arg_count_error(entry, num_bound_types); + } + } break; } } @@ -1630,7 +1785,7 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, zend_class_entry *i static void emit_incompatible_class_constant_error( const zend_class_constant *child, const zend_class_constant *parent, const zend_string *const_name) { - zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce); + zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce, NULL); zend_error_noreturn(E_COMPILE_ERROR, "Type of %s::%s must be compatible with %s::%s of type %s", ZSTR_VAL(child->ce->name), @@ -1809,7 +1964,7 @@ ZEND_API inheritance_status zend_verify_property_hook_variance(const zend_proper { ZEND_ASSERT(prop_info->hooks && prop_info->hooks[ZEND_PROPERTY_HOOK_SET] == func); - zend_arg_info *value_arg_info = &func->op_array.arg_info[0]; + const zend_arg_info *value_arg_info = &func->op_array.arg_info[0]; if (!ZEND_TYPE_IS_SET(value_arg_info->type)) { return INHERITANCE_SUCCESS; } @@ -2166,6 +2321,113 @@ static void do_inherit_iface_constant(zend_string *name, zend_class_constant *c, } /* }}} */ +// TODO Merge with the ones in zend_compile +static void zend_bound_types_ht_dtor(zval *ptr) { + HashTable *interface_bound_types = Z_PTR_P(ptr); + zend_hash_destroy(interface_bound_types); + efree(interface_bound_types); +} +static void zend_types_ht_dtor(zval *ptr) { + zend_type *type = Z_PTR_P(ptr); + // TODO Figure out persistency? + zend_type_release(*type, false); + efree(type); +} + +ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(zend_class_entry *ce, const zend_class_entry *iface) { + const HashTable *iface_bound_types = iface->bound_types; + if (iface_bound_types == NULL) { +#ifdef ZEND_DEBUG + for (uint32_t i = 0; i < iface->num_interfaces; i++) { + const zend_class_entry *inherited_iface = iface->interfaces[i]; + ZEND_ASSERT(inherited_iface->num_generic_parameters == 0); + } +#endif + return; + } + + if (ce->bound_types == NULL) { + ALLOC_HASHTABLE(ce->bound_types); + zend_hash_init(ce->bound_types, zend_hash_num_elements(iface_bound_types), NULL, zend_bound_types_ht_dtor, false /* todo depend on internal or not */); + } + const HashTable *ce_bound_types_for_direct_iface = zend_hash_find_ptr_lc(ce->bound_types, iface->name); + + zend_string *lc_inherited_iface_name = NULL; + const HashTable *interface_bound_types_for_inherited_iface = NULL; + ZEND_HASH_FOREACH_STR_KEY_PTR(iface_bound_types, lc_inherited_iface_name, interface_bound_types_for_inherited_iface) { + ZEND_ASSERT(lc_inherited_iface_name != NULL); + + const HashTable *existing_bound_types_for_inherited_iface = zend_hash_find_ptr(ce->bound_types, lc_inherited_iface_name); + if (EXPECTED(existing_bound_types_for_inherited_iface == NULL)) { + HashTable *ce_bound_types_for_inherited_iface = NULL; + ALLOC_HASHTABLE(ce_bound_types_for_inherited_iface); + zend_hash_init( + ce_bound_types_for_inherited_iface, + zend_hash_num_elements(interface_bound_types_for_inherited_iface), + NULL, + zend_types_ht_dtor, + false /* TODO depends on internals */ + ); + + zend_ulong generic_param_index = 0; + const zend_type *bound_type_ptr = NULL; + ZEND_HASH_FOREACH_NUM_KEY_PTR(interface_bound_types_for_inherited_iface, generic_param_index, bound_type_ptr) { + zend_type bound_type = *bound_type_ptr; + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(bound_type)) { + ZEND_ASSERT(ce_bound_types_for_direct_iface != NULL && + "If a bound type is generic then we must have bound types for the current interface"); + const zend_type *ce_bound_type_ptr = zend_hash_index_find_ptr(ce_bound_types_for_direct_iface, bound_type_ptr->generic_param_index); + ZEND_ASSERT(ce_bound_type_ptr != NULL); + bound_type = *ce_bound_type_ptr; + } + + zend_type_copy_ctor(&bound_type, true, false /* TODO Depends on internal or not? */); + zend_hash_index_add_mem(ce_bound_types_for_inherited_iface, generic_param_index, + &bound_type, sizeof(bound_type)); + } ZEND_HASH_FOREACH_END(); + zend_hash_add_new_ptr(ce->bound_types, lc_inherited_iface_name, ce_bound_types_for_inherited_iface); + } else { + const uint32_t num_generic_types = zend_hash_num_elements(interface_bound_types_for_inherited_iface); + ZEND_ASSERT(zend_hash_num_elements(existing_bound_types_for_inherited_iface) == num_generic_types && "Existing bound types should have errored before"); + + for (zend_ulong bound_type_index = 0; bound_type_index < num_generic_types; bound_type_index++) { + const zend_type *iface_bound_type_ptr = zend_hash_index_find_ptr(interface_bound_types_for_inherited_iface, bound_type_index); + const zend_type *ce_bound_type_ptr = zend_hash_index_find_ptr(existing_bound_types_for_inherited_iface, bound_type_index); + ZEND_ASSERT(iface_bound_type_ptr != NULL && ce_bound_type_ptr != NULL); + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*iface_bound_type_ptr)) { + iface_bound_type_ptr = zend_hash_index_find_ptr(ce_bound_types_for_direct_iface, iface_bound_type_ptr->generic_param_index); + ZEND_ASSERT(iface_bound_type_ptr != NULL); + } + const zend_type t1 = *iface_bound_type_ptr; + const zend_type t2 = *ce_bound_type_ptr; + if ( + ZEND_TYPE_FULL_MASK(t1) != ZEND_TYPE_FULL_MASK(t2) + || (ZEND_TYPE_HAS_NAME(t1) && !zend_string_equals(ZEND_TYPE_NAME(t1), ZEND_TYPE_NAME(t2))) + // || ZEND_TYPE_HAS_LIST(t1) && TODO Check list types are equal + ) { + const zend_class_entry *inherited_iface = zend_hash_find_ptr(CG(class_table), lc_inherited_iface_name); + ZEND_ASSERT(inherited_iface != NULL); + const zend_generic_parameter param = inherited_iface->generic_parameters[bound_type_index]; + + zend_string *ce_bound_type_str = zend_type_to_string_resolved(t2, ce, NULL); + zend_string *iface_bound_type_str = zend_type_to_string_resolved(t1, iface, NULL); + zend_error_noreturn(E_COMPILE_ERROR, + "Bound type %s for interface %s implemented explicitly in %s with type %s must match the implicitly bound type %s from interface %s", + ZSTR_VAL(param.name), + ZSTR_VAL(inherited_iface->name), + ZSTR_VAL(ce->name), + ZSTR_VAL(ce_bound_type_str), + ZSTR_VAL(iface_bound_type_str), + ZSTR_VAL(iface->name) + ); + zend_string_release_ex(ce_bound_type_str, false); + zend_string_release_ex(iface_bound_type_str, false); + } + } + } + } ZEND_HASH_FOREACH_END(); +} + static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ { zend_function *func; @@ -2190,6 +2452,77 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * ZEND_INHERITANCE_RESET_CHILD_OVERRIDE; } + if (iface->num_generic_parameters > 0) { + if (UNEXPECTED(ce->bound_types == NULL)) { + emit_incompatible_generic_arg_count_error(iface, 0); + } + HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, iface->name); + if (UNEXPECTED(bound_types == NULL)) { + emit_incompatible_generic_arg_count_error(iface, 0); + } + const uint32_t num_bound_types = zend_hash_num_elements(bound_types); + if (UNEXPECTED(num_bound_types != iface->num_generic_parameters)) { + emit_incompatible_generic_arg_count_error(iface, num_bound_types); + } + for (uint32_t i = 0; i < num_bound_types; i++) { + const zend_generic_parameter *generic_parameter = &iface->generic_parameters[i]; + zend_type *bound_type_ptr = zend_hash_index_find_ptr(bound_types, i); + ZEND_ASSERT(bound_type_ptr != NULL); + + /* We are currently extending another interface */ + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*bound_type_ptr)) { + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_INTERFACE); + ZEND_ASSERT(ce->num_generic_parameters > 0); + const zend_string *current_generic_param_name = ZEND_TYPE_NAME(*bound_type_ptr); + for (uint32_t j = 0; j < ce->num_generic_parameters; j++) { + const zend_generic_parameter *current_ce_generic_parameter = &ce->generic_parameters[j]; + if (!zend_string_equals(current_ce_generic_parameter->name, current_generic_param_name)) { + continue; + } + if ( + zend_perform_covariant_type_check( + ce, + current_ce_generic_parameter->constraint, + iface, + generic_parameter->constraint + ) != INHERITANCE_SUCCESS + ) { + zend_string *current_ce_constraint_type_str = zend_type_to_string(current_ce_generic_parameter->constraint); + zend_string *constraint_type_str = zend_type_to_string(generic_parameter->constraint); + zend_error_noreturn(E_COMPILE_ERROR, + "Constraint type %s of generic type %s of interface %s is not a subtype of the constraint type %s of generic type %s of interface %s", + ZSTR_VAL(current_ce_constraint_type_str), + ZSTR_VAL(current_ce_generic_parameter->name), + ZSTR_VAL(ce->name), + ZSTR_VAL(constraint_type_str), + ZSTR_VAL(generic_parameter->name), + ZSTR_VAL(iface->name) + ); + zend_string_release(current_ce_constraint_type_str); + zend_string_release(constraint_type_str); + return; + } + break; + } + } else { + if (zend_perform_covariant_type_check(ce, *bound_type_ptr, iface, generic_parameter->constraint) != INHERITANCE_SUCCESS) { + zend_string *bound_type_str = zend_type_to_string(*bound_type_ptr); + zend_string *constraint_type_str = zend_type_to_string(generic_parameter->constraint); + zend_error_noreturn(E_COMPILE_ERROR, + "Bound type %s is not a subtype of the constraint type %s of generic type %s of interface %s", + ZSTR_VAL(bound_type_str), + ZSTR_VAL(constraint_type_str), + ZSTR_VAL(generic_parameter->name), + ZSTR_VAL(iface->name) + ); + zend_string_release(bound_type_str); + zend_string_release(constraint_type_str); + return; + } + } + } + } + bind_generic_types_for_inherited_interfaces(ce, iface); ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { do_inherit_iface_constant(key, c, ce, iface); } ZEND_HASH_FOREACH_END(); @@ -2798,7 +3131,7 @@ static bool do_trait_constant_check( return false; } else if (ZEND_TYPE_IS_SET(trait_constant->type)) { inheritance_status status1 = zend_perform_covariant_type_check(ce, existing_constant->type, traits[current_trait], trait_constant->type); - inheritance_status status2 = zend_perform_covariant_type_check(traits[current_trait], trait_constant->type, ce, existing_constant->type); + inheritance_status status2 = zend_perform_contravariant_type_check(ce, existing_constant->type, traits[current_trait], trait_constant->type); if (status1 == INHERITANCE_ERROR || status2 == INHERITANCE_ERROR) { emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); return false; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e4d61006fe12f..33bbaf00f75f5 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -263,7 +263,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type static_var class_statement trait_adaptation trait_precedence trait_alias %type absolute_trait_method_reference trait_method_reference property echo_expr %type new_dereferenceable new_non_dereferenceable anonymous_class class_name class_name_reference simple_variable -%type internal_functions_in_yacc +%type internal_functions_in_yacc simple_class_name generic_arg_list %type scalar backticks_expr lexical_var function_call member_name property_name %type variable_class_name dereferenceable_scalar constant class_constant %type fully_dereferenceable array_object_dereferenceable @@ -288,6 +288,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type function_name non_empty_member_modifiers %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body %type optional_parameter_list clone_argument_list non_empty_clone_argument_list +%type optional_generic_params generic_params generic_param class_name_with_generics_list %type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers @@ -365,9 +366,9 @@ name: ; attribute_decl: - class_name + simple_class_name { $$ = zend_ast_create(ZEND_AST_ATTRIBUTE, $1, NULL); } - | class_name argument_list + | simple_class_name argument_list { $$ = zend_ast_create(ZEND_AST_ATTRIBUTE, $1, $2); } ; @@ -552,8 +553,8 @@ catch_list: ; catch_name_list: - class_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } - | catch_name_list '|' class_name { $$ = zend_ast_list_add($1, $3); } + simple_class_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } + | catch_name_list '|' simple_class_name { $$ = zend_ast_list_add($1, $3); } ; optional_variable: @@ -603,11 +604,11 @@ is_variadic: class_declaration_statement: class_modifiers T_CLASS { $$ = CG(zend_lineno); } - T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $3, $7, zend_ast_get_str($4), $5, $6, $9, NULL, NULL); } + T_STRING optional_generic_params extends_from implements_list backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $3, $8, zend_ast_get_str($4), $6, $7, $10, NULL, $5); } | T_CLASS { $$ = CG(zend_lineno); } - T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $2, $6, zend_ast_get_str($3), $4, $5, $8, NULL, NULL); } + T_STRING optional_generic_params extends_from implements_list backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $2, $7, zend_ast_get_str($3), $5, $6, $9, NULL, $4); } ; class_modifiers: @@ -636,14 +637,14 @@ class_modifier: trait_declaration_statement: T_TRAIT { $$ = CG(zend_lineno); } - T_STRING backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_TRAIT, $2, $4, zend_ast_get_str($3), NULL, NULL, $6, NULL, NULL); } + T_STRING optional_generic_params backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_TRAIT, $2, $5, zend_ast_get_str($3), NULL, NULL, $7, NULL, $4); } ; interface_declaration_statement: T_INTERFACE { $$ = CG(zend_lineno); } - T_STRING interface_extends_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $2, $5, zend_ast_get_str($3), NULL, $4, $7, NULL, NULL); } + T_STRING optional_generic_params interface_extends_list backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4); } ; enum_declaration_statement: @@ -667,19 +668,38 @@ enum_case_expr: | '=' expr { $$ = $2; } ; +optional_generic_params: + %empty { $$ = NULL; } + | '<' generic_params '>' { $$ = $2; } +; + +generic_params: + generic_param + { $$ = zend_ast_create_list(1, ZEND_AST_GENERIC_PARAM_LIST, $1); } + | generic_params ',' generic_param + { $$ = zend_ast_list_add($1, $3); } +; + +generic_param: + T_STRING + { $$ = zend_ast_create(ZEND_AST_GENERIC_PARAM, $1, NULL); } + | T_STRING ':' type_expr + { $$ = zend_ast_create(ZEND_AST_GENERIC_PARAM, $1, $3); } +; + extends_from: %empty { $$ = NULL; } - | T_EXTENDS class_name { $$ = $2; } + | T_EXTENDS simple_class_name { $$ = $2; } ; interface_extends_list: %empty { $$ = NULL; } - | T_EXTENDS class_name_list { $$ = $2; } + | T_EXTENDS class_name_with_generics_list { $$ = $2; } ; implements_list: %empty { $$ = NULL; } - | T_IMPLEMENTS class_name_list { $$ = $2; } + | T_IMPLEMENTS class_name_with_generics_list { $$ = $2; } ; foreach_variable: @@ -1006,8 +1026,13 @@ class_statement: ; class_name_list: + simple_class_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } + | class_name_list ',' simple_class_name { $$ = zend_ast_list_add($1, $3); } +; + +class_name_with_generics_list: class_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } - | class_name_list ',' class_name { $$ = zend_ast_list_add($1, $3); } + | class_name_with_generics_list ',' class_name { $$ = zend_ast_list_add($1, $3); } ; trait_adaptations: @@ -1059,7 +1084,7 @@ trait_method_reference: ; absolute_trait_method_reference: - class_name T_PAAMAYIM_NEKUDOTAYIM identifier + simple_class_name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = zend_ast_create(ZEND_AST_METHOD_REFERENCE, $1, $3); } ; @@ -1450,7 +1475,7 @@ function_call: if (zend_lex_tstring(&zv, $1) == FAILURE) { YYABORT; } $$ = zend_ast_create(ZEND_AST_CALL, zend_ast_create_zval(&zv), $2); } - | class_name T_PAAMAYIM_NEKUDOTAYIM member_name argument_list + | simple_class_name T_PAAMAYIM_NEKUDOTAYIM member_name argument_list { $$ = zend_ast_create(ZEND_AST_STATIC_CALL, $1, $3, $4); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM member_name argument_list { $$ = zend_ast_create(ZEND_AST_STATIC_CALL, $1, $3, $4); } @@ -1460,17 +1485,31 @@ function_call: } ; -class_name: +simple_class_name: T_STATIC { zval zv; ZVAL_INTERNED_STR(&zv, ZSTR_KNOWN(ZEND_STR_STATIC)); $$ = zend_ast_create_zval_ex(&zv, ZEND_NAME_NOT_FQ); } | name { $$ = $1; } ; +class_name: + simple_class_name + { $$ = zend_ast_create(ZEND_AST_CLASS_REF, $1, NULL); } + | simple_class_name '<' generic_arg_list '>' + { $$ = zend_ast_create(ZEND_AST_CLASS_REF, $1, $3); } +; + +generic_arg_list: + type_expr + { $$ = zend_ast_create_list(1, ZEND_AST_GENERIC_ARG_LIST, $1); } + | generic_arg_list ',' type_expr + { $$ = zend_ast_list_add($1, $3); } +; + class_name_reference: - class_name { $$ = $1; } - | new_variable { $$ = $1; } - | '(' expr ')' { $$ = $2; } + simple_class_name { $$ = $1; } + | new_variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } ; backticks_expr: @@ -1520,11 +1559,11 @@ constant: ; class_constant: - class_name T_PAAMAYIM_NEKUDOTAYIM identifier + simple_class_name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = zend_ast_create_class_const_or_name($1, $3); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = zend_ast_create_class_const_or_name($1, $3); } - | class_name T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' + | simple_class_name T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' { $$ = zend_ast_create(ZEND_AST_CLASS_CONST, $1, $4); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' { $$ = zend_ast_create(ZEND_AST_CLASS_CONST, $1, $4); } @@ -1592,7 +1631,7 @@ simple_variable: ; static_member: - class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable + simple_class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = zend_ast_create(ZEND_AST_STATIC_PROP, $1, $3); } | variable_class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = zend_ast_create(ZEND_AST_STATIC_PROP, $1, $3); } @@ -1607,7 +1646,7 @@ new_variable: { $$ = zend_ast_create(ZEND_AST_PROP, $1, $3); } | new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = zend_ast_create(ZEND_AST_NULLSAFE_PROP, $1, $3); } - | class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable + | simple_class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = zend_ast_create(ZEND_AST_STATIC_PROP, $1, $3); } | new_variable T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = zend_ast_create(ZEND_AST_STATIC_PROP, $1, $3); } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 5e9e7b20d869b..b5a0e867f9b22 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -119,7 +119,7 @@ ZEND_API void zend_type_release(zend_type type, bool persistent) { if (!ZEND_TYPE_USES_ARENA(type)) { pefree(ZEND_TYPE_LIST(type), persistent); } - } else if (ZEND_TYPE_HAS_NAME(type)) { + } else if (ZEND_TYPE_HAS_NAME(type) || ZEND_TYPE_IS_GENERIC_PARAM_NAME(type)) { zend_string_release(ZEND_TYPE_NAME(type)); } } @@ -345,6 +345,20 @@ ZEND_API void destroy_zend_class(zval *zv) return; } + bool persistent = ce->type == ZEND_INTERNAL_CLASS; + /* Common to internal and user classes */ + if (ce->bound_types) { + zend_hash_release(ce->bound_types); + } + if (ce->num_generic_parameters > 0) { + for (uint32_t generic_param_index = 0; generic_param_index < ce->num_generic_parameters; generic_param_index++) { + const zend_generic_parameter generic_param = ce->generic_parameters[generic_param_index]; + zend_string_release(generic_param.name); + zend_type_release(generic_param.constraint, persistent); + } + pefree(ce->generic_parameters, persistent); + } + switch (ce->type) { case ZEND_USER_CLASS: if (!(ce->ce_flags & ZEND_ACC_CACHED)) { diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 43aa2aa86a00e..23ef81c8168c0 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -122,7 +122,7 @@ typedef struct { * are only supported since C++20). */ void *ptr; uint32_t type_mask; - /* TODO: We could use the extra 32-bit of padding on 64-bit systems. */ + uint32_t generic_param_index; } zend_type; typedef struct { @@ -130,14 +130,20 @@ typedef struct { zend_type types[1]; } zend_type_list; -#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25 -#define _ZEND_TYPE_MASK ((1u << 25) - 1) +typedef struct { + zend_string *name; + zend_type constraint; +} zend_generic_parameter; + +#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_GENERIC_PARAM_NAME_BIT (1u << 25) #define _ZEND_TYPE_NAME_BIT (1u << 24) // Used to signify that type.ptr is not a `zend_string*` but a `const char*`, #define _ZEND_TYPE_LITERAL_NAME_BIT (1u << 23) #define _ZEND_TYPE_LIST_BIT (1u << 22) -#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT) +#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT|_ZEND_TYPE_GENERIC_PARAM_NAME_BIT) /* For BC behaviour with iterable type */ #define _ZEND_TYPE_ITERABLE_BIT (1u << 21) /* Whether the type list is arena allocated */ @@ -155,7 +161,7 @@ typedef struct { (((t).type_mask & _ZEND_TYPE_MASK) != 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 */ + * the void pointer is a class name, or the type is a generic parameter name */ #define ZEND_TYPE_IS_COMPLEX(t) \ ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) @@ -168,6 +174,9 @@ typedef struct { #define ZEND_TYPE_HAS_LIST(t) \ ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) +#define ZEND_TYPE_IS_GENERIC_PARAM_NAME(t) \ + ((((t).type_mask) & _ZEND_TYPE_GENERIC_PARAM_NAME_BIT) != 0) + #define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \ ((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0) @@ -287,10 +296,10 @@ typedef struct { #endif #define ZEND_TYPE_INIT_NONE(extra_flags) \ - _ZEND_TYPE_PREFIX { NULL, (extra_flags) } + _ZEND_TYPE_PREFIX { NULL, (extra_flags), 0 } #define ZEND_TYPE_INIT_MASK(_type_mask) \ - _ZEND_TYPE_PREFIX { NULL, (_type_mask) } + _ZEND_TYPE_PREFIX { NULL, (_type_mask), 0 } #define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ( (code) == IS_ITERABLE ? _ZEND_TYPE_ITERABLE_BIT : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code))))) \ @@ -298,16 +307,17 @@ typedef struct { #define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \ _ZEND_TYPE_PREFIX { (void *) (ptr), \ - (type_kind) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } + (type_kind) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags), \ + 0 } #define ZEND_TYPE_INIT_PTR_MASK(ptr, type_mask) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask), 0 } #define ZEND_TYPE_INIT_UNION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_UNION_BIT) | (extra_flags) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_UNION_BIT) | (extra_flags), 0 } #define ZEND_TYPE_INIT_INTERSECTION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags), 0 } #define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \ ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags) @@ -321,6 +331,9 @@ typedef struct { #define ZEND_TYPE_INIT_CLASS_CONST_MASK(class_name, type_mask) \ ZEND_TYPE_INIT_PTR_MASK(class_name, (_ZEND_TYPE_LITERAL_NAME_BIT | (type_mask))) +#define ZEND_TYPE_INIT_GENERIC_PARAM(generic_name, index) \ + _ZEND_TYPE_PREFIX { (void *) (generic_name), _ZEND_TYPE_GENERIC_PARAM_NAME_BIT, index } + typedef union _zend_value { zend_long lval; /* long value */ double dval; /* double value */ diff --git a/ext/zend_test/tests/compile_to_ast/interface_with_generic_types.phpt b/ext/zend_test/tests/compile_to_ast/interface_with_generic_types.phpt new file mode 100644 index 0000000000000..d57bce88a1f70 --- /dev/null +++ b/ext/zend_test/tests/compile_to_ast/interface_with_generic_types.phpt @@ -0,0 +1,58 @@ +--TEST-- +AST can be recreated (interface with generic types) +--EXTENSIONS-- +zend_test +--FILE-- + { + public function bar(T1 $v): T2; + } +} + +namespace Foo { + interface MyInterface2 extends \MyInterface1 { + public function foobar(S $v): int; + } + + class MyClass implements MyInterface2 { + public function bar(string $v): string {} + public function foobar(string $v): int {} + } +} + +namespace { + echo zend_test_compile_to_ast( file_get_contents( __FILE__ ) ); +} + +?> +--EXPECT-- +namespace { + interface MyInterface1 { + public function bar(T1 $v): T2; + + } + +} + +namespace Foo { + interface MyInterface2 implements \MyInterface1 { + public function foobar(S $v): int; + + } + + class MyClass implements MyInterface2 { + public function bar(string $v): string { + } + + public function foobar(string $v): int { + } + + } + +} + +namespace { + echo zend_test_compile_to_ast(file_get_contents(__FILE__)); +} From 55c7f9e4b5a5745415f61030fc36724bd4d47b27 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 3 Jan 2026 18:56:27 +0100 Subject: [PATCH 3/3] [skip ci] Rebase and squash on top of current master