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/abstract_generic_type_with_constraint.phpt b/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint.phpt new file mode 100644 index 0000000000000..2f1c57492c833 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/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/abstract_generic_type_with_constraint_failed.phpt b/Zend/tests/type_declarations/abstract_generics/abstract_generic_type_with_constraint_failed.phpt new file mode 100644 index 0000000000000..440d1868e6162 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/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/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/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..a037af45bf87d --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/errors/abstract_generic_in_class.phpt @@ -0,0 +1,12 @@ +--TEST-- +Abstract generic type in class is invalid +--FILE-- + { + public function foo(T $param): T; +} + +?> +--EXPECTF-- +Parse error: syntax error, unexpected token "<", expecting "{" 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..b00a523069a6b --- /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-- +Parse error: syntax error, unexpected token "<", expecting "{" 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..aeca6dfc84271 --- /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: Cannot implement I as it has generic parameters which are not specified 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..b74f4593d548a --- /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: Cannot implement I as it has generic parameters which are not specified 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..8e8f6e66e65a8 --- /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: Cannot implement I2 as it has generic parameters which are not specified in %s on line %d diff --git a/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt new file mode 100644 index 0000000000000..da395e28caf5d --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_abstract_generic_types.phpt @@ -0,0 +1,21 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--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/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..2e543aa0ad947 --- /dev/null +++ b/Zend/tests/type_declarations/abstract_generics/extended_interface_new_abstract_generic_type2.phpt @@ -0,0 +1,24 @@ +--TEST-- +Abstract generic type behaviour in extended interface +--XFAIL-- +Generic type is not properly bound (Declaration of C::foo(string $param): string must be compatible with I::foo(T $param): T) +--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/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/zend.c b/Zend/zend.c index 2d8a0f455f8b4..edec7ba675630 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -732,6 +732,7 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{ compiler_globals->script_encoding_list = NULL; compiler_globals->current_linking_class = NULL; + compiler_globals->bound_associated_types = NULL; /* Map region is going to be created and resized at run-time. */ compiler_globals->map_ptr_real_base = NULL; diff --git a/Zend/zend.h b/Zend/zend.h index 0cf1faeb653fe..48cbfe1d950e3 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -218,6 +218,11 @@ struct _zend_class_entry { zend_trait_precedence **trait_precedences; HashTable *attributes; + /* Only for interfaces */ + 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 5437208694d49..3ff697f19c41e 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2724,6 +2724,17 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, ": "); ast = ast->child[1]; goto tail_call; + // TODO Export generic types + //case ZEND_AST_ASSOCIATED_TYPE: + // smart_str_appends(str, "type "); + // zend_ast_export_name(str, ast->child[0], 0, indent); + // if (ast->child[1]) { + // smart_str_appends(str, " : "); + // smart_str_appends(str, " : "); + // zend_ast_export_type(str, ast->child[1], indent); + // } + // smart_str_appendc(str, ';'); + //break; /* 3 child nodes */ case ZEND_AST_METHOD_CALL: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 9348c35f6cc07..3b7d56ad54961 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_MATCH_ARM, ZEND_AST_NAMED_ARG, ZEND_AST_PARENT_PROPERTY_HOOK_CALL, + 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 0669d106f15e9..7eca126973bcd 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -84,6 +84,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 }; + 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); @@ -463,6 +465,7 @@ void init_compiler(void) /* {{{ */ CG(delayed_autoloads) = NULL; CG(unlinked_uses) = NULL; CG(current_linking_class) = NULL; + CG(bound_associated_types) = NULL; } /* }}} */ @@ -491,6 +494,12 @@ void shutdown_compiler(void) /* {{{ */ CG(unlinked_uses) = NULL; } CG(current_linking_class) = NULL; + /* This can happen during a fatal error */ + if (CG(bound_associated_types)) { + zend_hash_destroy(CG(bound_associated_types)); + FREE_HASHTABLE(CG(bound_associated_types)); + CG(bound_associated_types) = NULL; + } } /* }}} */ @@ -1455,6 +1464,9 @@ 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(); + // TODO Is this still required? + //} else if (ZEND_TYPE_IS_ASSOCIATED(type)) { + // str = add_associated_type(ZEND_TYPE_NAME(type), scope); } else if (ZEND_TYPE_HAS_NAME(type)) { str = resolve_class_name(ZEND_TYPE_NAME(type), scope); } @@ -1771,6 +1783,17 @@ 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_string *class_name = zend_ast_get_str(ast->child[0]); + if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type_ast(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, 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()) { @@ -2072,6 +2095,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; @@ -6962,9 +6989,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"); } @@ -6993,8 +7022,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 *genric_param = &ce->generic_parameters[generic_param_index]; + if (zend_string_equals(type_name, genric_param->name)) { + return (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(type_name), /* allow null */ false, _ZEND_TYPE_ASSOCIATED_BIT); + } + } + } + + 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"); @@ -7005,14 +7043,14 @@ static zend_type zend_compile_single_typename(zend_ast *ast) if (fetch_type == ZEND_FETCH_CLASS_SELF) { /* Scope might be unknown for unbound closures and traits */ if (zend_is_scope_known()) { - class_name = CG(active_class_entry)->name; + class_name = ce->name; ZEND_ASSERT(class_name && "must know class name when resolving self type at compile time"); } } else { ZEND_ASSERT(fetch_type == ZEND_FETCH_CLASS_PARENT); /* Scope might be unknown for unbound closures and traits */ if (zend_is_scope_known()) { - class_name = CG(active_class_entry)->parent_name; + class_name = ce->parent_name; ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time"); } } @@ -7189,6 +7227,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_ASSOCIATED(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"); } @@ -7271,6 +7312,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_ASSOCIATED(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)) { @@ -7337,6 +7381,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_ASSOCIATED(type) && is_marked_nullable) { + zend_error_noreturn(E_COMPILE_ERROR, "Generic type cannot be part of a union type"); + } + if (ZEND_TYPE_IS_ASSOCIATED(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; @@ -9011,20 +9061,53 @@ static void zend_compile_use_trait(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) /* {{{ */ { 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); + + // TODO, need the list to a type list + 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 Check that we have the same 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; @@ -9072,6 +9155,45 @@ 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 = emalloc(list->children * sizeof(zend_generic_parameter)); + 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]) { + // TODO Need to free this + constraint_type = zend_compile_typename(param_ast->child[1]); + // TODO Validate that void, static, never are not used in the constraint? + } + + 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, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; @@ -9166,6 +9288,10 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); } + if (ce->ce_flags & ZEND_ACC_INTERFACE && decl->child[4]) { + 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 62d0fbcded2ee..6d8c50e1ea8ba 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -53,6 +53,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 diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 079bfb99caccf..dc1a06f52c539 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -151,6 +151,8 @@ struct _zend_compiler_globals { HashTable *delayed_autoloads; HashTable *unlinked_uses; zend_class_entry *current_linking_class; + /* Those are initialized and destroyed by zend_do_inheritance_ex() */ + HashTable *bound_associated_types; uint32_t rtd_key_counter; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index e718eb5684e65..9fe5091a31658 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -428,16 +428,16 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name /* Check whether any type in the fe_type intersection type is a subtype of the proto class. */ static inheritance_status zend_is_intersection_subtype_of_class( - zend_class_entry *fe_scope, const zend_type fe_type, + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce) { - ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(fe_type)); + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(*fe_type_ptr)); bool have_unresolved = false; const zend_type *single_type; /* Traverse the list of child types and check that at least one is * a subtype of the parent type being checked */ - ZEND_TYPE_FOREACH(fe_type, single_type) { + ZEND_TYPE_FOREACH(*fe_type_ptr, single_type) { zend_class_entry *fe_ce; zend_string *fe_class_name = NULL; if (ZEND_TYPE_HAS_NAME(*single_type)) { @@ -473,7 +473,9 @@ 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_ptr +) { + const zend_type proto_type = *proto_type_ptr; zend_class_entry *fe_ce = NULL; bool have_unresolved = 0; @@ -521,7 +523,7 @@ static inheritance_status zend_is_class_subtype_of_type( ZEND_TYPE_FOREACH(proto_type, single_type) { if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { inheritance_status subtype_status = zend_is_class_subtype_of_type( - fe_scope, fe_class_name, proto_scope, *single_type); + fe_scope, fe_class_name, proto_scope, single_type); switch (subtype_status) { case INHERITANCE_ERROR: @@ -606,9 +608,11 @@ 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_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr +) { + const zend_type fe_type = *fe_type_ptr; + const zend_type proto_type = *proto_type_ptr; bool have_unresolved = false; const zend_type *single_type; uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); @@ -644,7 +648,7 @@ static inheritance_status zend_is_intersection_subtype_of_type( if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { status = zend_is_intersection_subtype_of_type( - fe_scope, fe_type, proto_scope, *single_type); + fe_scope, fe_type_ptr, proto_scope, single_type); } else { zend_string *proto_class_name = get_class_from_type(proto_scope, *single_type); if (!proto_class_name) { @@ -653,7 +657,7 @@ static inheritance_status zend_is_intersection_subtype_of_type( zend_class_entry *proto_ce = NULL; status = zend_is_intersection_subtype_of_class( - fe_scope, fe_type, proto_scope, proto_class_name, proto_ce); + fe_scope, fe_type_ptr, proto_scope, proto_class_name, proto_ce); } if (status == early_exit_status) { @@ -672,9 +676,42 @@ static inheritance_status zend_is_intersection_subtype_of_type( } ZEND_API 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) + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr); + +static inheritance_status zend_is_type_subtype_of_associated_type( + zend_class_entry *concrete_scope, + const zend_type *concrete_type_ptr, + zend_class_entry *associated_type_scope, + const zend_type *associated_type_ptr +) { + const zend_type associated_type = *associated_type_ptr; + + ZEND_ASSERT(CG(bound_associated_types) && "Have associated type"); + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(associated_type)); + + zend_string *associated_type_name = ZEND_TYPE_NAME(associated_type); + const zend_type *bound_type_ptr = zend_hash_find_ptr(CG(bound_associated_types), associated_type_name); + ZEND_ASSERT(bound_type_ptr != NULL); + /* Generic type must be invariant */ + const inheritance_status sub_type_status = zend_perform_covariant_type_check( + concrete_scope, concrete_type_ptr, associated_type_scope, bound_type_ptr); + const inheritance_status super_type_status = zend_perform_covariant_type_check( + associated_type_scope, bound_type_ptr, concrete_scope, concrete_type_ptr); + + if (sub_type_status != super_type_status) { + return INHERITANCE_ERROR; + } else { + return sub_type_status; + } +} + +ZEND_API inheritance_status zend_perform_covariant_type_check( + zend_class_entry *fe_scope, const zend_type *fe_type_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr) { + const zend_type fe_type = *fe_type_ptr; + const zend_type proto_type = *proto_type_ptr; ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type)); /* Apart from void, everything is trivially covariant to the mixed type. @@ -684,6 +721,17 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( return INHERITANCE_SUCCESS; } + /* If we check for concrete return type */ + if (ZEND_TYPE_IS_ASSOCIATED(proto_type)) { + return zend_is_type_subtype_of_associated_type( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); + } + /* If we check for concrete parameter type */ + if (ZEND_TYPE_IS_ASSOCIATED(fe_type)) { + return zend_is_type_subtype_of_associated_type( + proto_scope, proto_type_ptr, fe_scope, fe_type_ptr); + } + /* Builtin types may be removed, but not added */ uint32_t fe_type_mask = ZEND_TYPE_PURE_MASK(fe_type); uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); @@ -713,7 +761,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( early_exit_status = ZEND_TYPE_IS_INTERSECTION(proto_type) ? INHERITANCE_ERROR : INHERITANCE_SUCCESS; inheritance_status status = zend_is_intersection_subtype_of_type( - fe_scope, fe_type, proto_scope, proto_type); + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); if (status == early_exit_status) { return status; @@ -733,7 +781,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( /* Union has an intersection type as it's member */ if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { status = zend_is_intersection_subtype_of_type( - fe_scope, *single_type, proto_scope, proto_type); + fe_scope, single_type, proto_scope, proto_type_ptr); } else { zend_string *fe_class_name = get_class_from_type(fe_scope, *single_type); if (!fe_class_name) { @@ -741,7 +789,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( } status = zend_is_class_subtype_of_type( - fe_scope, fe_class_name, proto_scope, proto_type); + fe_scope, fe_class_name, proto_scope, proto_type_ptr); } if (status == early_exit_status) { @@ -763,7 +811,7 @@ ZEND_API inheritance_status zend_perform_covariant_type_check( } static inheritance_status zend_do_perform_arg_type_hint_check( - zend_class_entry *fe_scope, zend_arg_info *fe_arg_info, + zend_class_entry *fe_scope, const zend_arg_info *fe_arg_info, zend_class_entry *proto_scope, 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) { @@ -779,7 +827,7 @@ static inheritance_status zend_do_perform_arg_type_hint_check( /* 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); + proto_scope, &proto_arg_info->type, fe_scope, &fe_arg_info->type); } /* }}} */ @@ -881,7 +929,7 @@ static inheritance_status zend_do_perform_implementation_check( } local_status = zend_perform_covariant_type_check( - fe_scope, fe->common.arg_info[-1].type, proto_scope, proto->common.arg_info[-1].type); + fe_scope, &fe->common.arg_info[-1].type, proto_scope, &proto->common.arg_info[-1].type); if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { if (local_status == INHERITANCE_ERROR @@ -1297,10 +1345,10 @@ static inheritance_status full_property_types_compatible( /* Perform a covariant type check in both directions to determined invariance. */ inheritance_status status1 = variance == PROP_CONTRAVARIANT ? INHERITANCE_SUCCESS : zend_perform_covariant_type_check( - child_info->ce, child_info->type, parent_info->ce, parent_info->type); + 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); + parent_info->ce, &parent_info->type, child_info->ce, &child_info->type); if (status1 == INHERITANCE_SUCCESS && status2 == INHERITANCE_SUCCESS) { return INHERITANCE_SUCCESS; } @@ -1357,7 +1405,7 @@ static inheritance_status verify_property_type_compatibility( && (!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); + parent_info->ce, &set_type, child_info->ce, &child_info->type); if ((result == INHERITANCE_ERROR && throw_on_error) || (result == INHERITANCE_UNRESOLVED && throw_on_unresolved)) { emit_set_hook_type_error(child_info, parent_info); } @@ -1645,7 +1693,7 @@ static inheritance_status class_constant_types_compatible(const zend_class_const return INHERITANCE_ERROR; } - return zend_perform_covariant_type_check(child->ce, child->type, parent->ce, parent->type); + return zend_perform_covariant_type_check(child->ce, &child->type, parent->ce, &parent->type); } static bool do_inherit_constant_check( @@ -1791,7 +1839,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; } @@ -1801,7 +1849,7 @@ ZEND_API inheritance_status zend_verify_property_hook_variance(const zend_proper } zend_class_entry *ce = prop_info->ce; - return zend_perform_covariant_type_check(ce, prop_info->type, ce, value_arg_info->type); + return zend_perform_covariant_type_check(ce, &prop_info->type, ce, &value_arg_info->type); } #ifdef ZEND_OPCACHE_SHM_REATTACHMENT @@ -2168,6 +2216,90 @@ 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)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot implement %s as it has generic parameters which are not specified", + ZSTR_VAL(iface->name) + ); + } + const HashTable *bound_types = zend_hash_find_ptr_lc(ce->bound_types, iface->name); + if (UNEXPECTED(bound_types == NULL)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot implement %s as it has generic parameters which are not specified", + ZSTR_VAL(iface->name) + ); + } + const uint32_t num_bound_types = zend_hash_num_elements(bound_types); + if (UNEXPECTED(num_bound_types != iface->num_generic_parameters)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot implement %s as the number of generic arguments specified (%" PRIu32 ") does not match the number of generic parameters declared on the interface (%" PRIu32 ")", + ZSTR_VAL(iface->name), + num_bound_types, + iface->num_generic_parameters + ); + } + HashTable *ht = emalloc(sizeof(HashTable)); + zend_hash_init(ht, num_bound_types, NULL, NULL, false); + CG(bound_associated_types) = ht; + for (uint32_t i = 0; i < num_bound_types; i++) { + const zend_generic_parameter *generic_parameter = &iface->generic_parameters[i]; + const zend_type* generic_constraint = &generic_parameter->constraint; + 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_ASSOCIATED(*bound_type_ptr)) { + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_INTERFACE); + ZEND_ASSERT(ce->num_generic_parameters > 0); + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*bound_type_ptr)); + 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; + } + const zend_type *current_ce_generic_type_constraint = ¤t_ce_generic_parameter->constraint; + ZEND_ASSERT(current_ce_generic_type_constraint != NULL); + if (zend_perform_covariant_type_check(ce, current_ce_generic_type_constraint, iface, generic_constraint) != INHERITANCE_SUCCESS) { + zend_string *current_ce_constraint_type_str = zend_type_to_string(*current_ce_generic_type_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; + } + /* Loosing const qualifier here is OK because this hashtable never frees or does anything with the value */ + zend_hash_add_new_ptr(ht, generic_parameter->name, (void*)current_ce_generic_type_constraint); + break; + } + } else { + if (zend_perform_covariant_type_check(ce, bound_type_ptr, iface, generic_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; + } + zend_hash_add_new_ptr(ht, generic_parameter->name, bound_type_ptr); + } + } + } ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { do_inherit_iface_constant(key, c, ce, iface); } ZEND_HASH_FOREACH_END(); @@ -2189,6 +2321,11 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * if (iface->num_interfaces) { zend_do_inherit_interfaces(ce, iface); } + if (CG(bound_associated_types)) { + zend_hash_destroy(CG(bound_associated_types)); + efree(CG(bound_associated_types)); + CG(bound_associated_types) = NULL; + } } /* }}} */ @@ -2777,8 +2914,8 @@ static bool do_trait_constant_check( emit_incompatible_trait_constant_error(ce, existing_constant, trait_constant, name, traits, current_trait); 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 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); 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 0c5bb36501e72..0f90f70a1a551 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -261,7 +261,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 @@ -286,6 +286,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 +%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 @@ -363,9 +364,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); } ; @@ -546,8 +547,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: @@ -636,8 +637,8 @@ trait_declaration_statement: 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: @@ -661,19 +662,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: @@ -972,8 +992,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: @@ -1025,7 +1050,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); } ; @@ -1404,7 +1429,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); } @@ -1414,17 +1439,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: @@ -1474,11 +1513,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); } @@ -1546,7 +1585,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); } @@ -1561,7 +1600,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 6e7d31e15a40f..0f59eb2d1973d 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -351,6 +351,18 @@ ZEND_API void destroy_zend_class(zval *zv) zend_hash_release(ce->attributes); } + 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_ex(generic_param.name, false); + zend_type_release(generic_param.constraint, false); + } + efree(ce->generic_parameters); + } + if (ce->num_interfaces > 0 && !(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)) { uint32_t i; @@ -527,6 +539,17 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->attributes) { zend_hash_release(ce->attributes); } + 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, true); + } + free(ce->generic_parameters); + } free(ce); break; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 4a6d00b9d73ea..ed6d4f1921915 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -141,14 +141,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_ASSOCIATED_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_ASSOCIATED_BIT) /* For BC behaviour with iterable type */ #define _ZEND_TYPE_ITERABLE_BIT (1u << 21) /* Whether the type list is arena allocated */ @@ -166,7 +172,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 an associated type (which implies it is a name) */ #define ZEND_TYPE_IS_COMPLEX(t) \ ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) @@ -179,6 +185,9 @@ typedef struct { #define ZEND_TYPE_HAS_LIST(t) \ ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) +#define ZEND_TYPE_IS_ASSOCIATED(t) \ + ((((t).type_mask) & _ZEND_TYPE_ASSOCIATED_BIT) != 0) + #define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \ ((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0)