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..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..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 0cf1faeb653fe..d2f8d15c763b6 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -218,6 +218,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 8bdd29c5512cc..5738109020d52 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1570,6 +1570,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; @@ -1690,7 +1700,7 @@ static ZEND_COLD void zend_ast_export_name_list_ex(smart_str *str, zend_ast_list 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++; } } @@ -1957,6 +1967,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, zend_ast_list *hook_list, int indent) { smart_str_appends(str, " {"); @@ -2156,10 +2181,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'); @@ -2444,6 +2476,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. */ @@ -2457,7 +2504,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 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..f8733449da2cb 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, 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); @@ -1387,7 +1389,7 @@ static zend_string *add_type_string(zend_string *type, zend_string *new_type, bo return result; } -static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scope) { +static zend_string *resolve_class_name(zend_string *name, const zend_class_entry *scope) { if (scope) { if (zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_SELF))) { name = scope->name; @@ -1407,7 +1409,7 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop } 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; @@ -1432,7 +1434,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 */ @@ -1455,6 +1490,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); } @@ -1524,7 +1561,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) { @@ -1771,6 +1808,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()) { @@ -2072,6 +2121,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 +7015,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 +7048,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"); @@ -7005,14 +7069,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 +7253,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"); } @@ -7271,6 +7338,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)) { @@ -7337,6 +7407,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; @@ -9011,20 +9087,52 @@ 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); + + 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; @@ -9043,7 +9151,7 @@ static zend_string *zend_generate_anon_class_name(zend_ast_decl *decl) prefix = zend_resolve_const_class_name_reference(decl->child[0], "class name"); } else if (decl->child[1]) { 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, @@ -9072,6 +9180,53 @@ 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, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; @@ -9166,6 +9321,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..8e333127ce49a 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 @@ -1013,7 +1014,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 0fbfdfa07ef04..3c0679233556d 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -686,7 +686,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 d27cca5b76187..cf4c7528eb84b 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)); } } @@ -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) { @@ -671,10 +675,60 @@ 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( - 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_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); + +static inheritance_status zend_perform_contravariant_type_check( + 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_generic_type( + zend_class_entry *concrete_scope, + const zend_type *concrete_type_ptr, + zend_class_entry *generic_type_scope, + const zend_type *generic_type_ptr +) { + ZEND_ASSERT(concrete_scope->bound_types); + const zend_type generic_type = *generic_type_ptr; + 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)) { + const zend_type concrete_type = *concrete_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_ptr, generic_type_scope, bound_type_ptr); + const inheritance_status super_type_status = zend_perform_contravariant_type_check( + concrete_scope, concrete_type_ptr, 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_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. @@ -713,7 +767,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 +787,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 +795,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) { @@ -762,9 +816,71 @@ 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_ptr, + const zend_class_entry *proto_scope, const zend_type *proto_type_ptr +) { + if (UNEXPECTED(!ZEND_TYPE_IS_GENERIC_PARAM_NAME(*proto_type_ptr))) { + /* A generic type cannot be a subtype of a concrete one */ + return INHERITANCE_ERROR; + } + ZEND_ASSERT(ZEND_TYPE_IS_GENERIC_PARAM_NAME(*fe_type_ptr)); + 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_ptr->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_ptr->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_ptr, + zend_class_entry *proto_scope, const zend_type *proto_type_ptr +) { + /* If we check for concrete return type */ + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(*proto_type_ptr)) { + return zend_is_type_subtype_of_generic_type( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); + } else if (UNEXPECTED(ZEND_TYPE_IS_GENERIC_PARAM_NAME(*fe_type_ptr))) { + /* 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_ptr, proto_scope, proto_type_ptr); +} + +static inheritance_status zend_perform_contravariant_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; + + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(fe_type)) { + return zend_is_generic_type_subtype_of_generic_type( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); + } + if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(proto_type)) { + return zend_is_type_subtype_of_generic_type( + fe_scope, fe_type_ptr, proto_scope, proto_type_ptr); + } + + /* 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_ptr, fe_scope, fe_type_ptr); +} + 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 +892,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 +946,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) { @@ -881,7 +995,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 @@ -897,10 +1011,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 +1025,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 +1040,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 +1081,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, 0); + zend_append_type_hint(&str, scope, bound_types_to_scope, arg_info, 0); if (ZEND_ARG_SEND_MODE(arg_info)) { smart_str_appendc(&str, '&'); @@ -1039,7 +1178,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, 1); + zend_append_type_hint(&str, scope, bound_types_to_scope, fptr->common.arg_info - 1, 1); } smart_str_0(&str); @@ -1056,11 +1195,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 */ @@ -1297,10 +1440,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); + 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; } @@ -1313,7 +1456,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), @@ -1327,7 +1470,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), @@ -1356,8 +1499,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); } @@ -1581,17 +1724,30 @@ 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 void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_entry *iface) /* {{{ */ +static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry *iface) +{ + 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 */ uint32_t i, ce_num, if_num = iface->num_interfaces; @@ -1609,6 +1765,19 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en 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; } } @@ -1620,14 +1789,14 @@ 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); } } /* }}} */ 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), @@ -1645,7 +1814,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 +1960,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 +1970,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 @@ -2148,6 +2317,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; @@ -2155,6 +2431,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. */ @@ -2168,6 +2448,80 @@ 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]; + 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_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; + } + 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; + } + 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; + } + } + } + } + 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(); @@ -2186,9 +2540,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); - } } /* }}} */ @@ -2777,8 +3128,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_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 08b2ac6b3f39b..48d9b6e928b5e 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); } ; @@ -550,8 +551,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: @@ -640,8 +641,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: @@ -665,19 +666,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: @@ -976,8 +996,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: @@ -1029,7 +1054,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); } ; @@ -1408,7 +1433,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); } @@ -1418,17 +1443,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: @@ -1478,11 +1517,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); } @@ -1550,7 +1589,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); } @@ -1565,7 +1604,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..990cf6c36dfbd 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -118,7 +118,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)); } } @@ -333,6 +333,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 4a6d00b9d73ea..f88cb28e4dac2 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -133,7 +133,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 { @@ -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_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 */ @@ -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 a generic parameter 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_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) @@ -298,10 +307,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))))) \ @@ -309,16 +318,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) @@ -332,6 +342,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__)); +}