diff --git a/UPGRADING b/UPGRADING index 12e4b86539cab..de86fc31793e8 100644 --- a/UPGRADING +++ b/UPGRADING @@ -64,6 +64,9 @@ PHP 8.1 UPGRADE NOTES PHP cross-version compatibility concerns, a `#[ReturnTypeWillChange]` attribute can be added to silence the deprecation notice. RFC: https://wiki.php.net/rfc/internal_method_return_types + . Added support for intersection types. + They cannot be combined with union types. + RFC: https://wiki.php.net/rfc/pure-intersection-types - Fileinfo: . The fileinfo functions now accept and return, respectively, finfo objects @@ -307,7 +310,7 @@ PHP 8.1 UPGRADE NOTES Previously, -a without readline had the same behavior as calling php without any arguments, apart from printing an additional "Interactive mode enabled" message. This mode was not, in fact, interactive. - + - phpdbg: . Remote functionality from phpdbg has been removed. @@ -453,7 +456,7 @@ PHP 8.1 UPGRADE NOTES MYSQLI_REFRESH_SLAVE, in line with an upstream change in MySQL. The old constant is still available for backwards-compatibility reasons, but may be deprecated/removed in the future. - + - Sockets: . TCP_DEFER_ACCEPT socket option added where available. diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index bda09ded025e4..6e935bc60c8c1 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -73,7 +73,7 @@ static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num) arg_info = op_array->arg_info - 1; } - if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { + if (ZEND_TYPE_IS_COMPLEX(arg_info->type)) { if (ZEND_TYPE_HAS_LIST(arg_info->type)) { return ZEND_TYPE_LIST(arg_info->type)->num_types; } diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index ff9335dec8a6a..2967addd2f0bf 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -310,19 +310,23 @@ static inline bool can_elide_return_type_check( return true; } - if (disallowed_types == MAY_BE_OBJECT && use_info->ce && ZEND_TYPE_HAS_CLASS(arg_info->type)) { + if (disallowed_types == MAY_BE_OBJECT && use_info->ce && ZEND_TYPE_IS_COMPLEX(arg_info->type)) { zend_type *single_type; + /* For intersection: result==false is failure, default is success. + * For union: result==true is success, default is failure. */ + bool is_intersection = ZEND_TYPE_IS_INTERSECTION(arg_info->type); ZEND_TYPE_FOREACH(arg_info->type, single_type) { if (ZEND_TYPE_HAS_NAME(*single_type)) { zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(*single_type)); zend_class_entry *ce = zend_optimizer_get_class_entry(script, lcname); zend_string_release(lcname); - if (ce && safe_instanceof(use_info->ce, ce)) { - /* One of the class union types matched. */ - return true; + bool result = ce && safe_instanceof(use_info->ce, ce); + if (result == !is_intersection) { + return result; } } } ZEND_TYPE_FOREACH_END(); + return is_intersection; } return false; diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index b4efbeabb050d..32b6414d7985f 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2221,7 +2221,7 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen } uint32_t tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(type)); - if (ZEND_TYPE_HAS_CLASS(type)) { + if (ZEND_TYPE_IS_COMPLEX(type)) { tmp |= MAY_BE_OBJECT; if (pce) { /* As we only have space to store one CE, diff --git a/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt b/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt new file mode 100644 index 0000000000000..19ec4dae50919 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt @@ -0,0 +1,39 @@ +--TEST-- +Added element of intersection type +--FILE-- +intersect = $a; +echo 'OK', \PHP_EOL; +bar($a); +?> +--EXPECT-- +object(A)#1 (0) { +} +OK +object(A)#3 (0) { +} diff --git a/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt b/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt new file mode 100644 index 0000000000000..568e6e25afade --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt @@ -0,0 +1,57 @@ +--TEST-- +Assigning values to intersection types +--FILE-- +prop = $tp; +} catch (TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +$o->prop = $tc; + +$r = $o->method1($tp); +var_dump($r); +$r = $o->method2($tp); +var_dump($r); +$r = $o->method1($tc); +var_dump($r); +$r = $o->method2($tc); +var_dump($r); + + +?> +--EXPECTF-- +Cannot assign TestParent to property A::$prop of type X&Y&Z +object(TestChild)#%d (0) { +} +object(TestParent)#%d (0) { +} +object(TestChild)#%d (0) { +} +object(TestParent)#%d (0) { +} diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_array_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_array_type.phpt new file mode 100644 index 0000000000000..cd276dfb27b3e --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_array_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +array type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type array cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_bool_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_bool_type.phpt new file mode 100644 index 0000000000000..345793b4d5be8 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_bool_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +bool type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type bool cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_callable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_callable_type.phpt new file mode 100644 index 0000000000000..c78e5245a88bf --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_callable_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +callable type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type callable cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_false_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_false_type.phpt new file mode 100644 index 0000000000000..52e9159681670 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_false_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +false type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type false cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_float_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_float_type.phpt new file mode 100644 index 0000000000000..3a61181767621 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_float_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +float type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type float cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_int_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_int_type.phpt new file mode 100644 index 0000000000000..9e4dc56a5be8a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_int_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +int type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type int cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt new file mode 100644 index 0000000000000..fc4ee2d5607d0 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +iterable type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type iterable cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_mixed_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_mixed_type.phpt new file mode 100644 index 0000000000000..b980b8436e79a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_mixed_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +mixed type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type mixed cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_never_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_never_type.phpt new file mode 100644 index 0000000000000..2bd8fd356b2d1 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_never_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +never type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type never cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_null_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_null_type.phpt new file mode 100644 index 0000000000000..c062bc323bf19 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_null_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +null type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type null cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt new file mode 100644 index 0000000000000..1c35dfdf91c70 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +Intersection type cannot be nullable +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "&", expecting "{" in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_object_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_object_type.phpt new file mode 100644 index 0000000000000..0f6b4d0304e2e --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_object_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +object type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type object cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt new file mode 100644 index 0000000000000..e3a86771a2189 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt @@ -0,0 +1,14 @@ +--TEST-- +parent type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type parent cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt new file mode 100644 index 0000000000000..66c7ec79325dc --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +self type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type self cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt new file mode 100644 index 0000000000000..7d2f44f572ee1 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +static type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type static cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_string_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_string_type.phpt new file mode 100644 index 0000000000000..406a34510ae9a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_string_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +string type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type string cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_void_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_void_type.phpt new file mode 100644 index 0000000000000..9fb9325972d63 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_void_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +void type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type void cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/missing_interface_intersection_type.phpt b/Zend/tests/type_declarations/intersection_types/missing_interface_intersection_type.phpt new file mode 100644 index 0000000000000..e724e1b15f50a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/missing_interface_intersection_type.phpt @@ -0,0 +1,50 @@ +--TEST-- +Missing one element of intersection type +--FILE-- +getMessage(), "\n"; +} + +$c = new Collection(); +$a = new A(); + +try { + $c->intersect = $a; +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + bar($a); +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +foo(): Return value must be of type X&Y, A returned +Cannot assign A to property Collection::$intersect of type X&Y +bar(): Argument #1 ($o) must be of type X&Y, A given, called in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/parameter.phpt b/Zend/tests/type_declarations/intersection_types/parameter.phpt new file mode 100644 index 0000000000000..d1c7de2243654 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/parameter.phpt @@ -0,0 +1,28 @@ +--TEST-- +Intersection types in parameters +--FILE-- +getMessage(), \PHP_EOL; +} + +?> +--EXPECTF-- +object(Foo)#1 (0) { +} +foo(): Argument #1 ($bar) must be of type A&B, Bar given, called in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/parse_error.phpt b/Zend/tests/type_declarations/intersection_types/parse_error.phpt new file mode 100644 index 0000000000000..2711754532d65 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/parse_error.phpt @@ -0,0 +1,8 @@ +--TEST-- +Parse error for & not followed by var or vararg +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "&" in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type.phpt new file mode 100644 index 0000000000000..ff53b9f593014 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Duplicate class alias type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type A is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type_runtime.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type_runtime.phpt new file mode 100644 index 0000000000000..b2866fb8b8b5d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type_runtime.phpt @@ -0,0 +1,14 @@ +--TEST-- +Duplicate class alias type at runtime +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_type.phpt new file mode 100644 index 0000000000000..41d59c7a8dcb9 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Duplicate class type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type FOO is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/inheritence.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/inheritence.phpt new file mode 100644 index 0000000000000..f31c8d39dc0e6 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/inheritence.phpt @@ -0,0 +1,15 @@ +--TEST-- +Intersection with child class +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/typed_reference.phpt b/Zend/tests/type_declarations/intersection_types/typed_reference.phpt new file mode 100644 index 0000000000000..2ceb34f256ffd --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/typed_reference.phpt @@ -0,0 +1,30 @@ +--TEST-- +Intersection types and typed reference +--FILE-- +y =& $r; +$test->z =& $r; + +try { + $r = new B; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECT-- +Cannot assign B to reference held by property Test::$z of type X&Z diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid1.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid1.phpt new file mode 100644 index 0000000000000..f4b214fb90256 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid1.phpt @@ -0,0 +1,27 @@ +--TEST-- +Co-variance check failure for intersection type where child replace one of intersection type members with a supertype +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(): A&C must be compatible with Foo::foo(): B&C in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid2.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid2.phpt new file mode 100644 index 0000000000000..e69028882688e --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid2.phpt @@ -0,0 +1,26 @@ +--TEST-- +Co-variance check failure for intersection type where child replaces it with standard type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(): array must be compatible with Foo::foo(): A&B in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid3.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid3.phpt new file mode 100644 index 0000000000000..118393e8931cf --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid3.phpt @@ -0,0 +1,18 @@ +--TEST-- +Replacing int type with intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): int in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt new file mode 100644 index 0000000000000..be94fd2a1729a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt @@ -0,0 +1,17 @@ +--TEST-- +Replacing object type with not-loadable intersection type +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Could not check compatibility between Test2::method(): X&Y and Test::method(): object, because class X is not available in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt new file mode 100644 index 0000000000000..b704f89b909f2 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt @@ -0,0 +1,18 @@ +--TEST-- +Replacing iterable type with non-Traversable intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): iterable in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid6.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid6.phpt new file mode 100644 index 0000000000000..b99b707862bf6 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid6.phpt @@ -0,0 +1,20 @@ +--TEST-- +Replacing not-loadable parent intersection type with loadable child intersection type +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Could not check compatibility between Test2::method(): Y&Z and Test::method(): X&Y, because class X is not available in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type1.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type1.phpt new file mode 100644 index 0000000000000..d34c022175649 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type1.phpt @@ -0,0 +1,26 @@ +--TEST-- +Co-variance check failure for intersection type where child removes one of intersection type members +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(): A must be compatible with Foo::foo(): A&B in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type2.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type2.phpt new file mode 100644 index 0000000000000..cff3674ddf485 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type2.phpt @@ -0,0 +1,27 @@ +--TEST-- +Co-variance check failure for intersection type where child removes one of intersection type members +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(): A&B must be compatible with Foo::foo(): A&B&C in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union1.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union1.phpt new file mode 100644 index 0000000000000..bf5c45c2dd985 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union1.phpt @@ -0,0 +1,24 @@ +--TEST-- +Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 1 +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): X&Y in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union2.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union2.phpt new file mode 100644 index 0000000000000..1eafcb5d58718 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union2.phpt @@ -0,0 +1,23 @@ +--TEST-- +Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 2 +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::foo(): TestOne|int must be compatible with A::foo(): X&Y in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union3.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union3.phpt new file mode 100644 index 0000000000000..c4dd3449f976d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union3.phpt @@ -0,0 +1,25 @@ +--TEST-- +Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 2 +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): X&Z in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance1.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance1.phpt new file mode 100644 index 0000000000000..6f81d10b72f23 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance1.phpt @@ -0,0 +1,18 @@ +--TEST-- +Property types must be invariant +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::$prop must be X&Y (as in class A) in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance2.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance2.phpt new file mode 100644 index 0000000000000..0c48e42790037 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance2.phpt @@ -0,0 +1,20 @@ +--TEST-- +Intersection type reduction invalid invariant type check +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Type of Test2::$prop must be X&Y (as in class Test) in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid1.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid1.phpt new file mode 100644 index 0000000000000..3282b0f9dc04f --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid1.phpt @@ -0,0 +1,40 @@ +--TEST-- +Valid inheritence - co-variance +--FILE-- +foo()); +$o = new FooSecondChild(); +var_dump($o->foo()); + +?> +--EXPECTF-- +object(Test)#%d (0) { +} +object(Test)#%d (0) { +} diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid2.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid2.phpt new file mode 100644 index 0000000000000..e8db85eb5523d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid2.phpt @@ -0,0 +1,22 @@ +--TEST-- +Commutative intersection types +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid3.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid3.phpt new file mode 100644 index 0000000000000..d8166b49ec42b --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid3.phpt @@ -0,0 +1,29 @@ +--TEST-- +Valid intersection type variance +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid4.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid4.phpt new file mode 100644 index 0000000000000..4761628e5b97f --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid4.phpt @@ -0,0 +1,19 @@ +--TEST-- +Intersection type reduction valid invariant type check +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid5.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid5.phpt new file mode 100644 index 0000000000000..de79f0586157d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid5.phpt @@ -0,0 +1,45 @@ +--TEST-- +Replacing union of classes respecting intersection type by intersection type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid6.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid6.phpt new file mode 100644 index 0000000000000..bb6b50badcc62 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid6.phpt @@ -0,0 +1,20 @@ +--TEST-- +Replacing union type by intersection type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt new file mode 100644 index 0000000000000..bc2f28eeff189 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt @@ -0,0 +1,23 @@ +--TEST-- +Replacing object type with intersection type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid8.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid8.phpt new file mode 100644 index 0000000000000..a3b553b23e824 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid8.phpt @@ -0,0 +1,22 @@ +--TEST-- +Replacing iterable type with intersection type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_API.c b/Zend/zend_API.c index b726412f3a051..0df51b2f62413 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2424,14 +2424,14 @@ static void zend_check_magic_method_return_type(const zend_class_entry *ce, cons return; } - bool has_class_type = ZEND_TYPE_HAS_CLASS(fptr->common.arg_info[-1].type); + bool is_complex_type = ZEND_TYPE_IS_COMPLEX(fptr->common.arg_info[-1].type); uint32_t extra_types = ZEND_TYPE_PURE_MASK(fptr->common.arg_info[-1].type) & ~return_type; if (extra_types & MAY_BE_STATIC) { extra_types &= ~MAY_BE_STATIC; - has_class_type = 1; + is_complex_type = true; } - if (extra_types || (has_class_type && return_type != MAY_BE_OBJECT)) { + if (extra_types || (is_complex_type && return_type != MAY_BE_OBJECT)) { zend_error(error_type, "%s::%s(): Return type must be %s when declared", ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name), ZSTR_VAL(zend_type_to_string((zend_type) ZEND_TYPE_INIT_MASK(return_type)))); @@ -2768,7 +2768,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend memcpy(new_arg_info, arg_info, sizeof(zend_arg_info) * num_args); reg_function->common.arg_info = new_arg_info + 1; for (i = 0; i < num_args; i++) { - if (ZEND_TYPE_HAS_CLASS(new_arg_info[i].type)) { + if (ZEND_TYPE_IS_COMPLEX(new_arg_info[i].type)) { ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type) && "Should be stored as simple name"); const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type); diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 927fc23d95004..ba46b7feb6cba 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1457,6 +1457,16 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in } return; } + if (ast->kind == ZEND_AST_TYPE_INTERSECTION) { + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + if (i != 0) { + smart_str_appendc(str, '&'); + } + zend_ast_export_type(str, list->child[i], indent); + } + return; + } if (ast->attr & ZEND_TYPE_NULLABLE) { smart_str_appendc(str, '?'); } diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 0e3468ebde110..1bf8f263fa45e 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -62,6 +62,7 @@ enum _zend_ast_kind { ZEND_AST_TRAIT_ADAPTATIONS, ZEND_AST_USE, ZEND_AST_TYPE_UNION, + ZEND_AST_TYPE_INTERSECTION, ZEND_AST_ATTRIBUTE_LIST, ZEND_AST_ATTRIBUTE_GROUP, ZEND_AST_MATCH_ARM_LIST, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a762fe98f1b10..78a39fbc6cb02 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1149,15 +1149,21 @@ ZEND_API zend_result do_bind_class(zval *lcname, zend_string *lc_parent_name) /* } /* }}} */ -static zend_string *add_type_string(zend_string *type, zend_string *new_type) { +static zend_string *add_type_string(zend_string *type, zend_string *new_type, bool is_intersection) { zend_string *result; if (type == NULL) { return zend_string_copy(new_type); } - result = zend_string_concat3( - ZSTR_VAL(type), ZSTR_LEN(type), "|", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type)); - zend_string_release(type); + if (is_intersection) { + result = zend_string_concat3(ZSTR_VAL(type), ZSTR_LEN(type), + "&", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type)); + zend_string_release(type); + } else { + result = zend_string_concat3( + ZSTR_VAL(type), ZSTR_LEN(type), "|", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type)); + zend_string_release(type); + } return result; } @@ -1185,9 +1191,10 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop if (ZEND_TYPE_HAS_LIST(type)) { zend_type *list_type; + bool is_intersection = ZEND_TYPE_IS_INTERSECTION(type); ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) { if (ZEND_TYPE_HAS_CE(*list_type)) { - str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name); + str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, is_intersection); } else { zend_string *name = ZEND_TYPE_NAME(*list_type); @@ -1196,13 +1203,13 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop zend_class_entry *ce = ZSTR_GET_CE_CACHE(name); if (ce->ce_flags & ZEND_ACC_ANON_CLASS) { zend_string *tmp = zend_string_init(ZSTR_VAL(ce->name), strlen(ZSTR_VAL(ce->name)), 0); - str = add_type_string(str, tmp); + str = add_type_string(str, tmp, is_intersection); } else { - str = add_type_string(str, ce->name); + str = add_type_string(str, ce->name, is_intersection); } } else { zend_string *resolved = resolve_class_name(name, scope); - str = add_type_string(str, resolved); + str = add_type_string(str, resolved, is_intersection); zend_string_release(resolved); } } @@ -1228,7 +1235,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); if (type_mask == MAY_BE_ANY) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_MIXED)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_MIXED), /* is_intersection */ false); return str; } @@ -1240,39 +1247,39 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop name = called_scope->name; } } - str = add_type_string(str, name); + str = add_type_string(str, name, /* is_intersection */ false); } if (type_mask & MAY_BE_CALLABLE) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE), /* is_intersection */ false); } if (type_mask & MAY_BE_ITERABLE) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE), /* is_intersection */ false); } if (type_mask & MAY_BE_OBJECT) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT), /* is_intersection */ false); } if (type_mask & MAY_BE_ARRAY) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY), /* is_intersection */ false); } if (type_mask & MAY_BE_STRING) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING), /* is_intersection */ false); } if (type_mask & MAY_BE_LONG) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT), /* is_intersection */ false); } if (type_mask & MAY_BE_DOUBLE) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT), /* is_intersection */ false); } if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL), /* is_intersection */ false); } else if (type_mask & MAY_BE_FALSE) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE), /* is_intersection */ false); } if (type_mask & MAY_BE_VOID) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID), /* is_intersection */ false); } if (type_mask & MAY_BE_NEVER) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NEVER)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NEVER), /* is_intersection */ false); } if (type_mask & MAY_BE_NULL) { @@ -1283,7 +1290,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop return nullable_str; } - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE), /* is_intersection */ false); } return str; } @@ -2411,7 +2418,7 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr) /* {{{ */ /* }}} */ static size_t zend_type_get_num_classes(zend_type type) { - if (!ZEND_TYPE_HAS_CLASS(type)) { + if (!ZEND_TYPE_IS_COMPLEX(type)) { return 0; } if (ZEND_TYPE_HAS_LIST(type)) { @@ -6249,8 +6256,8 @@ static zend_type zend_compile_typename( ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type); ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK; - if (ZEND_TYPE_HAS_CLASS(single_type)) { - if (!ZEND_TYPE_HAS_CLASS(type)) { + if (ZEND_TYPE_IS_COMPLEX(single_type)) { + if (!ZEND_TYPE_IS_COMPLEX(type)) { /* The first class type can be stored directly as the type ptr payload. */ ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type)); ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT; @@ -6284,9 +6291,60 @@ static zend_type zend_compile_typename( memcpy(list, type_list, ZEND_TYPE_LIST_SIZE(type_list->num_types)); ZEND_TYPE_SET_LIST(type, list); ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; + /* Inform that the type list is a union type */ + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_UNION_BIT; } free_alloca(type_list, use_heap); + } else if (ast->kind == ZEND_AST_TYPE_INTERSECTION) { + zend_ast_list *list = zend_ast_get_list(ast); + zend_type_list *type_list; + + /* Allocate the type list directly on the arena as it must be a type + * list of the same number of elements as the AST list has children */ + type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children)); + type_list->num_types = 0; + + ZEND_ASSERT(list->children > 1); + + for (uint32_t i = 0; i < list->children; i++) { + zend_ast *type_ast = list->child[i]; + zend_type single_type = zend_compile_single_typename(type_ast); + + /* An intersection of standard types cannot exist so invalidate it */ + if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { + zend_string *standard_type_str = zend_type_to_string(single_type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + zend_string_release_ex(standard_type_str, false); + } + /* Check for "self" and "parent" too */ + if (zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "self") + || zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "parent")) { + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s cannot be part of an intersection type", ZSTR_VAL(ZEND_TYPE_NAME(single_type))); + } + + /* Add type to the type list */ + type_list->types[type_list->num_types++] = single_type; + + /* Check for trivially redundant class types */ + for (size_t i = 0; i < type_list->num_types - 1; i++) { + if (zend_string_equals_ci( + ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(single_type))) { + zend_string *single_type_str = zend_type_to_string(single_type); + zend_error_noreturn(E_COMPILE_ERROR, + "Duplicate type %s is redundant", ZSTR_VAL(single_type_str)); + } + } + } + + ZEND_ASSERT(list->children == type_list->num_types); + + ZEND_TYPE_SET_LIST(type, type_list); + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; + /* Inform that the type list is an intersection type */ + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT; } else { type = zend_compile_single_typename(ast); } @@ -6313,23 +6371,23 @@ static zend_type zend_compile_typename( zend_error_noreturn(E_COMPILE_ERROR, "Type mixed cannot be marked as nullable since mixed already includes null"); } - if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_HAS_CLASS(type) || (type_mask & MAY_BE_STATIC))) { + if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_IS_COMPLEX(type) || (type_mask & MAY_BE_STATIC))) { zend_string *type_str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s contains both object and a class type, which is redundant", ZSTR_VAL(type_str)); } - if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_HAS_CLASS(type) || type_mask != MAY_BE_VOID)) { + if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_IS_COMPLEX(type) || type_mask != MAY_BE_VOID)) { zend_error_noreturn(E_COMPILE_ERROR, "Void can only be used as a standalone type"); } - if ((type_mask & MAY_BE_NEVER) && (ZEND_TYPE_HAS_CLASS(type) || type_mask != MAY_BE_NEVER)) { + if ((type_mask & MAY_BE_NEVER) && (ZEND_TYPE_IS_COMPLEX(type) || type_mask != MAY_BE_NEVER)) { zend_error_noreturn(E_COMPILE_ERROR, "never can only be used as a standalone type"); } if ((type_mask & (MAY_BE_NULL|MAY_BE_FALSE)) - && !ZEND_TYPE_HAS_CLASS(type) && !(type_mask & ~(MAY_BE_NULL|MAY_BE_FALSE))) { + && !ZEND_TYPE_IS_COMPLEX(type) && !(type_mask & ~(MAY_BE_NULL|MAY_BE_FALSE))) { if (type_mask == MAY_BE_NULL) { zend_error_noreturn(E_COMPILE_ERROR, "Null can not be used as a standalone type"); } else { @@ -7472,7 +7530,7 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ ZEND_ASSERT(ce->ce_flags & ZEND_ACC_ENUM); zend_type type = zend_compile_typename(enum_backing_type_ast, 0); uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); - if (ZEND_TYPE_HAS_CLASS(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) { + if (ZEND_TYPE_IS_COMPLEX(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) { zend_string *type_string = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Enum backing type must be int or string, %s given", diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index aa07f9b41b036..60eb75347393f 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -836,67 +836,54 @@ static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class } } +static zend_always_inline zend_class_entry *zend_ce_from_type( + zend_property_info *info, zend_type *type) { + if (UNEXPECTED(!ZEND_TYPE_HAS_NAME(*type))) { + ZEND_ASSERT(ZEND_TYPE_HAS_CE(*type)); + return ZEND_TYPE_CE(*type); + } + + zend_string *name = ZEND_TYPE_NAME(*type); + zend_class_entry *ce; + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + } + } else { + ce = resolve_single_class_type(name, info->ce); + if (ce && !(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + zend_string_release(name); + ZEND_TYPE_SET_CE(*type, ce); + } + } + return ce; +} + static bool zend_check_and_resolve_property_class_type( zend_property_info *info, zend_class_entry *object_ce) { - zend_class_entry *ce; if (ZEND_TYPE_HAS_LIST(info->type)) { zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { - if (ZEND_TYPE_HAS_NAME(*list_type)) { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - continue; - } - } - } else { - ce = resolve_single_class_type(name, info->ce); - if (!ce) { - continue; - } - if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { - zend_string_release(name); - ZEND_TYPE_SET_CE(*list_type, ce); - } - } - } else { - ce = ZEND_TYPE_CE(*list_type); - } - if (instanceof_function(object_ce, ce)) { - return 1; - } - } ZEND_TYPE_LIST_FOREACH_END(); - return 0; - } else { - if (UNEXPECTED(ZEND_TYPE_HAS_NAME(info->type))) { - zend_string *name = ZEND_TYPE_NAME(info->type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - return 0; - } + if (ZEND_TYPE_IS_INTERSECTION(info->type)) { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { + zend_class_entry *ce = zend_ce_from_type(info, list_type); + if (!ce || !instanceof_function(object_ce, ce)) { + return false; } - } else { - ce = resolve_single_class_type(name, info->ce); - if (UNEXPECTED(!ce)) { - return 0; - } - if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { - zend_string_release(name); - ZEND_TYPE_SET_CE(info->type, ce); - } - } + } ZEND_TYPE_LIST_FOREACH_END(); + return true; } else { - ce = ZEND_TYPE_CE(info->type); + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { + zend_class_entry *ce = zend_ce_from_type(info, list_type); + if (ce && instanceof_function(object_ce, ce)) { + return true; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return false; } - return instanceof_function(object_ce, ce); + } else { + zend_class_entry *ce = zend_ce_from_type(info, &info->type); + return ce && instanceof_function(object_ce, ce); } } @@ -907,7 +894,7 @@ static zend_always_inline bool i_zend_check_property_type(zend_property_info *in return 1; } - if (ZEND_TYPE_HAS_CLASS(info->type) && Z_TYPE_P(property) == IS_OBJECT + if (ZEND_TYPE_IS_COMPLEX(info->type) && Z_TYPE_P(property) == IS_OBJECT && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) { return 1; } @@ -949,7 +936,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf return zend_assign_to_variable(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES()); } -ZEND_API bool zend_value_instanceof_static(zval *zv) { +static zend_always_inline bool zend_value_instanceof_static(zval *zv) { if (Z_TYPE_P(zv) != IS_OBJECT) { return 0; } @@ -969,85 +956,81 @@ ZEND_API bool zend_value_instanceof_static(zval *zv) { # define HAVE_CACHE_SLOT 1 #endif +static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot( + void **cache_slot, zend_type *type) +{ + if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { + return (zend_class_entry *) *cache_slot; + } + + zend_string *name = ZEND_TYPE_NAME(*type); + zend_class_entry *ce; + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (UNEXPECTED(!ce)) { + /* Cannot resolve */ + return NULL; + } + } + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + if (UNEXPECTED(!ce)) { + return NULL; + } + } + if (HAVE_CACHE_SLOT) { + *cache_slot = (void *) ce; + } + return ce; +} + static zend_always_inline bool zend_check_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope, + zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type, bool is_internal) { uint32_t type_mask; - if (ZEND_TYPE_HAS_CLASS(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { + if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { zend_class_entry *ce; if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) { zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { - if (HAVE_CACHE_SLOT && *cache_slot) { - ce = *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (!ce) { - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - continue; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - continue; - } + if (ZEND_TYPE_IS_INTERSECTION(*type)) { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { + ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); + /* If type is not an instance of one of the types taking part in the + * intersection it cannot be a valid instance of the whole intersection type. */ + if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) { + return false; } if (HAVE_CACHE_SLOT) { - *cache_slot = ce; + cache_slot++; } - } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; - } - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - } ZEND_TYPE_LIST_FOREACH_END(); - } else { - if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { - ce = (zend_class_entry *) *cache_slot; + } ZEND_TYPE_LIST_FOREACH_END(); + return true; } else { - zend_string *name = ZEND_TYPE_NAME(*type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - goto builtin_types; - } + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { + ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); + /* Instance of a single type part of a union is sufficient to pass the type check */ + if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { + return true; } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (UNEXPECTED(!ce)) { - goto builtin_types; + if (HAVE_CACHE_SLOT) { + cache_slot++; } - } - if (HAVE_CACHE_SLOT) { - *cache_slot = (void *) ce; - } + } ZEND_TYPE_LIST_FOREACH_END(); } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; + } else { + ce = zend_fetch_ce_from_cache_slot(cache_slot, type); + /* If we have a CE we check if it satisfies the type constraint, + * otherwise it will check if a standard type satisfies it. */ + if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { + return true; } } } -builtin_types: type_mask = ZEND_TYPE_FULL_MASK(*type); if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) { return 1; @@ -1093,7 +1076,14 @@ static zend_always_inline bool zend_check_type( return 1; } - return zend_check_type_slow(type, arg, ref, cache_slot, scope, is_return_type, is_internal); + return zend_check_type_slow(type, arg, ref, cache_slot, is_return_type, is_internal); +} + +ZEND_API bool zend_check_user_type_slow( + zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type) +{ + return zend_check_type_slow( + type, arg, ref, cache_slot, is_return_type, /* is_internal */ false); } static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot) @@ -3129,7 +3119,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( return 1; } - if (ZEND_TYPE_HAS_CLASS(type) && zv_type == IS_OBJECT + if (ZEND_TYPE_IS_COMPLEX(type) && zv_type == IS_OBJECT && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) { return 1; } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 13ef03d92fc0b..2bef567390725 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -76,7 +76,8 @@ ZEND_API ZEND_COLD void zend_verify_return_error( ZEND_API ZEND_COLD void zend_verify_never_error( const zend_function *zf); ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref); -ZEND_API bool zend_value_instanceof_static(zval *zv); +ZEND_API bool zend_check_user_type_slow( + zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type); #define ZEND_REF_TYPE_SOURCES(ref) \ @@ -459,7 +460,6 @@ ZEND_COLD void zend_verify_property_type_error(zend_property_info *info, zval *p } \ } while (0) - END_EXTERN_C() #endif /* ZEND_EXECUTE_H */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 055f5fbc6d6bc..f7ad6c5a33532 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -403,14 +403,64 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name zend_hash_add_ptr(ht, class_name, ce); } -static inheritance_status zend_perform_covariant_class_type_check( +/* 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, zend_type fe_type, + zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce) +{ + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(fe_type)); + bool have_unresolved = false; + 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_class_entry *fe_ce; + zend_string *fe_class_name = NULL; + if (ZEND_TYPE_HAS_NAME(*single_type)) { + fe_class_name = + resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); + if (zend_string_equals_ci(fe_class_name, proto_class_name)) { + return INHERITANCE_SUCCESS; + } + + if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name); + fe_ce = lookup_class(fe_scope, fe_class_name); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name); + fe_ce = ZEND_TYPE_CE(*single_type); + } else { + /* standard type in an intersection type is impossible, + * because it would be a fatal compile error */ + ZEND_UNREACHABLE(); + continue; + } + + if (!fe_ce || !proto_ce) { + have_unresolved = true; + continue; + } + if (unlinked_instanceof(fe_ce, proto_ce)) { + track_class_dependency(fe_ce, fe_class_name); + track_class_dependency(proto_ce, proto_class_name); + return INHERITANCE_SUCCESS; + } + } ZEND_TYPE_FOREACH_END(); + + return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR; +} + +/* 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 *fe_ce, zend_class_entry *proto_scope, zend_type proto_type) { bool have_unresolved = 0; + + /* If the parent has 'object' as a return type, any class satisfies the co-variant check */ if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_OBJECT) { /* Currently, any class name would be allowed here. We still perform a class lookup * for forward-compatibility reasons, as we may have named types in the future that - * are not classes (such as enums or typedefs). */ + * are not classes (such as typedefs). */ if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name); if (!fe_ce) { have_unresolved = 1; @@ -430,6 +480,10 @@ static inheritance_status zend_perform_covariant_class_type_check( } zend_type *single_type; + + /* Traverse the list of parent types and check if the current child (FE) + * class is the subtype of at least one of them (union) or all of them (intersection). */ + bool is_intersection = ZEND_TYPE_IS_INTERSECTION(proto_type); ZEND_TYPE_FOREACH(proto_type, single_type) { zend_class_entry *proto_ce; zend_string *proto_class_name = NULL; @@ -437,7 +491,10 @@ static inheritance_status zend_perform_covariant_class_type_check( proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); if (zend_string_equals_ci(fe_class_name, proto_class_name)) { - return INHERITANCE_SUCCESS; + if (!is_intersection) { + return INHERITANCE_SUCCESS; + } + continue; } if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name); @@ -446,19 +503,45 @@ static inheritance_status zend_perform_covariant_class_type_check( if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name); proto_ce = ZEND_TYPE_CE(*single_type); } else { + /* standard type */ + ZEND_ASSERT(!is_intersection); continue; } if (!fe_ce || !proto_ce) { have_unresolved = 1; - } else if (unlinked_instanceof(fe_ce, proto_ce)) { + continue; + } + if (unlinked_instanceof(fe_ce, proto_ce)) { track_class_dependency(fe_ce, fe_class_name); track_class_dependency(proto_ce, proto_class_name); - return INHERITANCE_SUCCESS; + if (!is_intersection) { + return INHERITANCE_SUCCESS; + } + } else { + if (is_intersection) { + return INHERITANCE_ERROR; + } } } ZEND_TYPE_FOREACH_END(); - return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR; + if (have_unresolved) { + return INHERITANCE_UNRESOLVED; + } + return is_intersection ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; +} + +static zend_string *get_class_from_type( + zend_class_entry **ce, zend_class_entry *scope, zend_type single_type) { + if (ZEND_TYPE_HAS_NAME(single_type)) { + *ce = NULL; + return resolve_class_name(scope, ZEND_TYPE_NAME(single_type)); + } + if (ZEND_TYPE_HAS_CE(single_type)) { + *ce = ZEND_TYPE_CE(single_type); + return (*ce)->name; + } + return NULL; } static void register_unresolved_classes(zend_class_entry *scope, zend_type type) { @@ -519,35 +602,85 @@ static inheritance_status zend_perform_covariant_type_check( } zend_type *single_type; - bool all_success = 1; + inheritance_status early_exit_status; + bool have_unresolved = false; + + if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { + /* Currently, for object type any class name would be allowed here. + * We still perform a class lookup for forward-compatibility reasons, + * as we may have named types in the future that are not classes + * (such as typedefs). */ + if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) { + bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0; + ZEND_TYPE_FOREACH(fe_type, single_type) { + zend_class_entry *fe_ce; + zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type); + if (!fe_class_name) { + continue; + } + if (!fe_ce) { + fe_ce = lookup_class(fe_scope, fe_class_name); + } + if (fe_ce) { + if (any_class || unlinked_instanceof(fe_ce, zend_ce_traversable)) { + track_class_dependency(fe_ce, fe_class_name); + return INHERITANCE_SUCCESS; + } + } else { + have_unresolved = true; + } + } ZEND_TYPE_FOREACH_END(); + } + + /* U_1&...&U_n < V_1&...&V_m if forall V_j. exists U_i. U_i < V_j. + * U_1&...&U_n < V_1|...|V_m if exists V_j. exists U_i. U_i < V_j. + * As such, we need to iterate over proto_type (V_j) first and use a different + * quantifier depending on whether fe_type is a union or an intersection. */ + early_exit_status = + ZEND_TYPE_IS_INTERSECTION(proto_type) ? INHERITANCE_ERROR : INHERITANCE_SUCCESS; + ZEND_TYPE_FOREACH(proto_type, single_type) { + zend_class_entry *proto_ce; + zend_string *proto_class_name = + get_class_from_type(&proto_ce, proto_scope, *single_type); + if (!proto_class_name) { + continue; + } - /* First try to check whether we can succeed without resolving anything */ - ZEND_TYPE_FOREACH(fe_type, single_type) { - inheritance_status status; - if (ZEND_TYPE_HAS_NAME(*single_type)) { - zend_string *fe_class_name = - resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); - status = zend_perform_covariant_class_type_check( - fe_scope, fe_class_name, NULL, proto_scope, proto_type); - } else if (ZEND_TYPE_HAS_CE(*single_type)) { - zend_class_entry *fe_ce = ZEND_TYPE_CE(*single_type); - status = zend_perform_covariant_class_type_check( - fe_scope, fe_ce->name, fe_ce, proto_scope, proto_type); - } else { - continue; - } + inheritance_status status = zend_is_intersection_subtype_of_class( + fe_scope, fe_type, proto_scope, proto_class_name, proto_ce); + if (status == early_exit_status) { + return status; + } + if (status == INHERITANCE_UNRESOLVED) { + have_unresolved = true; + } + } ZEND_TYPE_FOREACH_END(); + } else { + /* U_1|...|U_n < V_1|...|V_m if forall U_i. exists V_j. U_i < V_j. + * U_1|...|U_n < V_1&...&V_m if forall U_i. forall V_j. U_i < V_j. + * We need to iterate over fe_type (U_i) first and the logic is independent of + * whether proto_type is a union or intersection (only the inner check differs). */ + early_exit_status = INHERITANCE_ERROR; + ZEND_TYPE_FOREACH(fe_type, single_type) { + zend_class_entry *fe_ce; + zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type); + if (!fe_class_name) { + continue; + } - if (status == INHERITANCE_ERROR) { - return INHERITANCE_ERROR; - } - if (status != INHERITANCE_SUCCESS) { - all_success = 0; - } - } ZEND_TYPE_FOREACH_END(); + inheritance_status status = zend_is_class_subtype_of_type( + fe_scope, fe_class_name, fe_ce, proto_scope, proto_type); + if (status == early_exit_status) { + return status; + } + if (status == INHERITANCE_UNRESOLVED) { + have_unresolved = true; + } + } ZEND_TYPE_FOREACH_END(); + } - /* All individual checks succeeded, overall success */ - if (all_success) { - return INHERITANCE_SUCCESS; + if (!have_unresolved) { + return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } register_unresolved_classes(fe_scope, fe_type); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e783a7d9aa1e7..67a972c53bb9c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -68,7 +68,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %left T_BOOLEAN_AND %left '|' %left '^' -%left '&' +%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG %nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP %nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL %left '.' @@ -231,6 +231,13 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_COALESCE "'??'" %token T_POW "'**'" %token T_POW_EQUAL "'**='" +/* We need to split the & token in two to avoid a shift/reduce conflict. For T1&$v and T1&T2, + * with only one token lookahead, bison does not know whether to reduce T1 as a complete type, + * or shift to continue parsing an intersection type. */ +%token T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG "'&'" +/* Bison warns on duplicate token literals, so use a different dummy value here. + * It will be fixed up by zend_yytnamerr() later. */ +%token T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG "amp" %token T_BAD_CHARACTER "invalid character" /* Token used to force a parse error from the lexer */ @@ -264,8 +271,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type lexical_var_list encaps_list %type array_pair non_empty_array_pair_list array_pair_list possible_array_pair %type isset_variable type return_type type_expr type_without_static -%type identifier type_expr_without_static union_type_without_static -%type inline_function union_type +%type identifier type_expr_without_static union_type_without_static intersection_type_without_static +%type inline_function union_type intersection_type %type attributed_statement attributed_class_statement attributed_parameter %type attribute_decl attribute attributes attribute_group namespace_declaration_name %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list @@ -301,6 +308,11 @@ semi_reserved: | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC ; +ampersand: + T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + | T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG +; + identifier: T_STRING { $$ = $1; } | semi_reserved { @@ -555,7 +567,7 @@ function_declaration_statement: is_reference: %empty { $$ = 0; } - | '&' { $$ = ZEND_PARAM_REF; } + | T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = ZEND_PARAM_REF; } ; is_variadic: @@ -633,7 +645,7 @@ implements_list: foreach_variable: variable { $$ = $1; } - | '&' variable { $$ = zend_ast_create(ZEND_AST_REF, $2); } + | ampersand variable { $$ = zend_ast_create(ZEND_AST_REF, $2); } | T_LIST '(' array_pair_list ')' { $$ = $3; $$->attr = ZEND_ARRAY_SYNTAX_LIST; } | '[' array_pair_list ']' { $$ = $2; $$->attr = ZEND_ARRAY_SYNTAX_SHORT; } ; @@ -785,6 +797,7 @@ type_expr: type { $$ = $1; } | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type { $$ = $1; } + | intersection_type { $$ = $1; } ; type: @@ -797,6 +810,11 @@ union_type: | union_type '|' type { $$ = zend_ast_list_add($1, $3); } ; +intersection_type: + type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_INTERSECTION, $1, $3); } + | intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { $$ = zend_ast_list_add($1, $3); } +; + /* Duplicate the type rules without "static", * to avoid conflicts with "static" modifier for properties. */ @@ -804,6 +822,7 @@ type_expr_without_static: type_without_static { $$ = $1; } | '?' type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type_without_static { $$ = $1; } + | intersection_type_without_static { $$ = $1; } ; type_without_static: @@ -819,6 +838,13 @@ union_type_without_static: { $$ = zend_ast_list_add($1, $3); } ; +intersection_type_without_static: + type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static + { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_INTERSECTION, $1, $3); } + | intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static + { $$ = zend_ast_list_add($1, $3); } +; + return_type: %empty { $$ = NULL; } | ':' type_expr { $$ = $2; } @@ -1047,7 +1073,7 @@ expr: { $2->attr = ZEND_ARRAY_SYNTAX_SHORT; $$ = zend_ast_create(ZEND_AST_ASSIGN, $2, $5); } | variable '=' expr { $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); } - | variable '=' '&' variable + | variable '=' ampersand variable { $$ = zend_ast_create(ZEND_AST_ASSIGN_REF, $1, $4); } | T_CLONE expr { $$ = zend_ast_create(ZEND_AST_CLONE, $2); } | variable T_PLUS_EQUAL expr @@ -1091,7 +1117,8 @@ expr: | expr T_LOGICAL_XOR expr { $$ = zend_ast_create_binary_op(ZEND_BOOL_XOR, $1, $3); } | expr '|' expr { $$ = zend_ast_create_binary_op(ZEND_BW_OR, $1, $3); } - | expr '&' expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); } + | expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); } + | expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); } | expr '^' expr { $$ = zend_ast_create_binary_op(ZEND_BW_XOR, $1, $3); } | expr '.' expr { $$ = zend_ast_create_binary_op(ZEND_CONCAT, $1, $3); } | expr '+' expr { $$ = zend_ast_create_binary_op(ZEND_ADD, $1, $3); } @@ -1201,7 +1228,7 @@ backup_lex_pos: returns_ref: %empty { $$ = 0; } - | '&' { $$ = ZEND_ACC_RETURN_REFERENCE; } + | ampersand { $$ = ZEND_ACC_RETURN_REFERENCE; } ; lexical_vars: @@ -1216,7 +1243,7 @@ lexical_var_list: lexical_var: T_VARIABLE { $$ = $1; } - | '&' T_VARIABLE { $$ = $2; $$->attr = ZEND_BIND_REF; } + | ampersand T_VARIABLE { $$ = $2; $$->attr = ZEND_BIND_REF; } ; function_call: @@ -1416,9 +1443,9 @@ array_pair: { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); } | expr { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); } - | expr T_DOUBLE_ARROW '&' variable + | expr T_DOUBLE_ARROW ampersand variable { $$ = zend_ast_create_ex(ZEND_AST_ARRAY_ELEM, 1, $4, $1); } - | '&' variable + | ampersand variable { $$ = zend_ast_create_ex(ZEND_AST_ARRAY_ELEM, 1, $2, NULL); } | T_ELLIPSIS expr { $$ = zend_ast_create(ZEND_AST_UNPACK, $2); } @@ -1543,6 +1570,14 @@ static YYSIZE_T zend_yytnamerr(char *yyres, const char *yystr) return sizeof("token \"\\\"")-1; } + /* We used "amp" as a dummy label to avoid a duplicate token literal warning. */ + if (strcmp(toktype, "\"amp\"") == 0) { + if (yyres) { + yystpcpy(yyres, "token \"&\""); + } + return sizeof("token \"&\"")-1; + } + /* Avoid unreadable """ */ /* "'" would theoretically be just as bad, but is never currently parsed as a separate token */ if (strcmp(toktype, "'\"'") == 0) { diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index b127c3bc6b80c..1155f97d759c2 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1854,6 +1854,15 @@ NEWLINE ("\r"|"\n"|"\r\n") RETURN_TOKEN(T_SR); } +"&"{TABS_AND_SPACES}("$"|"...") { + yyless(1); + RETURN_TOKEN(T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG); +} + +"&" { + RETURN_TOKEN(T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG); +} + "]"|")" { /* Check that ] and ) match up properly with a preceding [ or ( */ RETURN_EXIT_NESTING_TOKEN(yytext[0]); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 963362222d0eb..b9d013fbc1979 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -109,7 +109,7 @@ typedef void (*copy_ctor_func_t)(zval *pElement); * It shouldn't be used directly. Only through ZEND_TYPE_* macros. * * ZEND_TYPE_IS_SET() - checks if there is a type-hint - * ZEND_TYPE_HAS_ONLY_MASK() - checks if type-hint refer to standard type only + * ZEND_TYPE_IS_ONLY_MASK() - checks if type-hint refer to standard type only * ZEND_TYPE_HAS_CLASS() - checks if type-hint contains some class * ZEND_TYPE_HAS_CE() - checks if type-hint contains some class as zend_class_entry * * ZEND_TYPE_HAS_NAME() - checks if type-hint contains some class as zend_string * @@ -148,15 +148,21 @@ typedef struct { /* TODO: bit 21 is not used */ /* Whether the type list is arena allocated */ #define _ZEND_TYPE_ARENA_BIT (1u << 20) +/* Whether the type list is an intersection type */ +#define _ZEND_TYPE_INTERSECTION_BIT (1u << 19) +/* Whether the type is a union type */ +#define _ZEND_TYPE_UNION_BIT (1u << 18) /* Type mask excluding the flags above. */ -#define _ZEND_TYPE_MAY_BE_MASK ((1u << 20) - 1) +#define _ZEND_TYPE_MAY_BE_MASK ((1u << 18) - 1) /* Must have same value as MAY_BE_NULL */ #define _ZEND_TYPE_NULLABLE_BIT 0x2u #define ZEND_TYPE_IS_SET(t) \ (((t).type_mask & _ZEND_TYPE_MASK) != 0) -#define ZEND_TYPE_HAS_CLASS(t) \ +/* If a type is complex it means it's either a list with a union or intersection, + * or the void pointer is a CE/Name */ +#define ZEND_TYPE_IS_COMPLEX(t) \ ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) #define ZEND_TYPE_HAS_CE(t) \ @@ -168,6 +174,12 @@ typedef struct { #define ZEND_TYPE_HAS_LIST(t) \ ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) +#define ZEND_TYPE_IS_INTERSECTION(t) \ + ((((t).type_mask) & _ZEND_TYPE_INTERSECTION_BIT) != 0) + +#define ZEND_TYPE_IS_UNION(t) \ + ((((t).type_mask) & _ZEND_TYPE_UNION_BIT) != 0) + #define ZEND_TYPE_USES_ARENA(t) \ ((((t).type_mask) & _ZEND_TYPE_ARENA_BIT) != 0) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 29991429abe91..7b65efe579135 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4224,7 +4224,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index c698ad44967a4..6847f58f3c575 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -9995,7 +9995,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -20370,7 +20370,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -27901,7 +27901,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -35050,7 +35050,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -46789,7 +46789,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 5f1bf7b901bcb..2c133a751e7f0 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1320,98 +1320,13 @@ static zend_reference* ZEND_FASTCALL zend_jit_fetch_global_helper(zend_string *v return ref; } -static zend_always_inline bool zend_jit_verify_type_common(zval *arg, zend_arg_info *arg_info, void **cache_slot) -{ - uint32_t type_mask; - - if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) { - zend_class_entry *ce; - if (ZEND_TYPE_HAS_LIST(arg_info->type)) { - zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { - if (*cache_slot) { - ce = *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (!ce) { - cache_slot++; - continue; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - cache_slot++; - continue; - } - } - *cache_slot = ce; - } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; - } - cache_slot++; - } ZEND_TYPE_LIST_FOREACH_END(); - } else { - if (EXPECTED(*cache_slot)) { - ce = (zend_class_entry *) *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(arg_info->type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - goto builtin_types; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (UNEXPECTED(!ce)) { - goto builtin_types; - } - } - *cache_slot = (void *) ce; - } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; - } - } - } - -builtin_types: - type_mask = ZEND_TYPE_FULL_MASK(arg_info->type); - if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) { - return 1; - } - if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { - return 1; - } - if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) { - return 1; - } - if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) { - return 1; - } - return 0; -} - static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg_info) { zend_execute_data *execute_data = EG(current_execute_data); const zend_op *opline = EX(opline); void **cache_slot = CACHE_ADDR(opline->extended_value); - bool ret; - - ret = zend_jit_verify_type_common(arg, arg_info, cache_slot); + bool ret = zend_check_user_type_slow( + &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false); if (UNEXPECTED(!ret)) { zend_verify_arg_error(EX(func), arg_info, opline->op1.num, arg); return 0; @@ -1421,7 +1336,8 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_array *op_array, zend_arg_info *arg_info, void **cache_slot) { - if (UNEXPECTED(!zend_jit_verify_type_common(arg, arg_info, cache_slot))) { + if (UNEXPECTED(!zend_check_user_type_slow( + &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) { zend_verify_return_error((zend_function*)op_array, arg); } } diff --git a/ext/opcache/tests/opt/verify_return_type.phpt b/ext/opcache/tests/opt/verify_return_type.phpt index e69fea2183f83..ef14d7630fe80 100644 --- a/ext/opcache/tests/opt/verify_return_type.phpt +++ b/ext/opcache/tests/opt/verify_return_type.phpt @@ -51,6 +51,10 @@ function getClassUnion(): stdClass|FooBar { return new stdClass; } +function getClassIntersection(): Traversable&Countable { + return new ArrayObject; +} + ?> --EXPECTF-- $_main: @@ -69,6 +73,16 @@ getClassUnion: LIVE RANGES: 0: 0001 - 0002 (new) +getClassIntersection: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s +0000 V0 = NEW 0 string("ArrayObject") +0001 DO_FCALL +0002 RETURN V0 +LIVE RANGES: + 0: 0001 - 0002 (new) + Test1::getIntOrFloat: ; (lines=2, args=1, vars=1, tmps=0) ; (after optimizer) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index ff8b53cf8befe..f76b926f1941a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -79,6 +79,7 @@ PHPAPI zend_class_entry *reflection_generator_ptr; PHPAPI zend_class_entry *reflection_parameter_ptr; PHPAPI zend_class_entry *reflection_type_ptr; PHPAPI zend_class_entry *reflection_named_type_ptr; +PHPAPI zend_class_entry *reflection_intersection_type_ptr; PHPAPI zend_class_entry *reflection_union_type_ptr; PHPAPI zend_class_entry *reflection_class_ptr; PHPAPI zend_class_entry *reflection_object_ptr; @@ -1319,22 +1320,40 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje } /* }}} */ +typedef enum { + NAMED_TYPE = 0, + UNION_TYPE = 1, + INTERSECTION_TYPE = 2 +} reflection_type_kind; + /* For backwards compatibility reasons, we need to return T|null style unions * as a ReflectionNamedType. Here we determine what counts as a union type and * what doesn't. */ -static bool is_union_type(zend_type type) { +static reflection_type_kind get_type_kind(zend_type type) { + uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); + if (ZEND_TYPE_HAS_LIST(type)) { - return 1; + if (ZEND_TYPE_IS_INTERSECTION(type)) { + return INTERSECTION_TYPE; + } + ZEND_ASSERT(ZEND_TYPE_IS_UNION(type)); + return UNION_TYPE; } - uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); - if (ZEND_TYPE_HAS_CLASS(type)) { - return type_mask_without_null != 0; + + if (ZEND_TYPE_IS_COMPLEX(type)) { + if (type_mask_without_null != 0) { + return UNION_TYPE; + } + return NAMED_TYPE; } - if (type_mask_without_null == MAY_BE_BOOL) { - return 0; + if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) { + return NAMED_TYPE; } /* Check that only one bit is set. */ - return (type_mask_without_null & (type_mask_without_null - 1)) != 0; + if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) { + return UNION_TYPE; + } + return NAMED_TYPE; } /* {{{ reflection_type_factory */ @@ -1342,14 +1361,26 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be { reflection_object *intern; type_reference *reference; - bool is_union = is_union_type(type); + reflection_type_kind type_kind = get_type_kind(type); bool is_mixed = ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY; - reflection_instantiate(is_union && !is_mixed ? reflection_union_type_ptr : reflection_named_type_ptr, object); + switch (type_kind) { + case INTERSECTION_TYPE: + reflection_instantiate(reflection_intersection_type_ptr, object); + break; + case UNION_TYPE: + reflection_instantiate(reflection_union_type_ptr, object); + break; + case NAMED_TYPE: + reflection_instantiate(reflection_named_type_ptr, object); + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } + intern = Z_REFLECTION_P(object); reference = (type_reference*) emalloc(sizeof(type_reference)); reference->type = type; - reference->legacy_behavior = legacy_behavior && !is_union && !is_mixed; + reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed; intern->ptr = reference; intern->ref_type = REF_TYPE_TYPE; @@ -3097,6 +3128,27 @@ ZEND_METHOD(ReflectionUnionType, getTypes) } /* }}} */ +/* {{{ Returns the types that are part of this intersection type */ +ZEND_METHOD(ReflectionIntersectionType, getTypes) +{ + reflection_object *intern; + type_reference *param; + zend_type *list_type; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(param); + + ZEND_ASSERT(ZEND_TYPE_HAS_LIST(param->type)); + + array_init(return_value); + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), list_type) { + append_type(return_value, *list_type); + } ZEND_TYPE_LIST_FOREACH_END(); +} +/* }}} */ + /* {{{ Constructor. Throws an Exception in case the given method does not exist */ ZEND_METHOD(ReflectionMethod, __construct) { @@ -7017,6 +7069,9 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_union_type_ptr = register_class_ReflectionUnionType(reflection_type_ptr); reflection_init_class_handlers(reflection_union_type_ptr); + reflection_intersection_type_ptr = register_class_ReflectionIntersectionType(reflection_type_ptr); + reflection_init_class_handlers(reflection_intersection_type_ptr); + reflection_method_ptr = register_class_ReflectionMethod(reflection_function_abstract_ptr); reflection_init_class_handlers(reflection_method_ptr); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index fb2b2675ef2fa..3b883bb7e32ad 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -596,6 +596,11 @@ class ReflectionUnionType extends ReflectionType public function getTypes(): array {} } +class ReflectionIntersectionType extends ReflectionType +{ + public function getTypes(): array {} +} + class ReflectionExtension implements Reflector { public string $name; diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index fa0a532a24806..dc39e428719ff 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e66c459f457f71cb677a93652364ab7e81be8b0e */ + * Stub hash: d8e686125cf213e019c1d706867e3c178fa057d2 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -468,6 +468,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionUnionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables +#define arginfo_class_ReflectionIntersectionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables + #define arginfo_class_ReflectionExtension___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionExtension___construct, 0, 0, 1) @@ -766,6 +768,7 @@ ZEND_METHOD(ReflectionType, __toString); ZEND_METHOD(ReflectionNamedType, getName); ZEND_METHOD(ReflectionNamedType, isBuiltin); ZEND_METHOD(ReflectionUnionType, getTypes); +ZEND_METHOD(ReflectionIntersectionType, getTypes); ZEND_METHOD(ReflectionExtension, __construct); ZEND_METHOD(ReflectionExtension, __toString); ZEND_METHOD(ReflectionExtension, getName); @@ -1070,6 +1073,12 @@ static const zend_function_entry class_ReflectionUnionType_methods[] = { }; +static const zend_function_entry class_ReflectionIntersectionType_methods[] = { + ZEND_ME(ReflectionIntersectionType, getTypes, arginfo_class_ReflectionIntersectionType_getTypes, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + static const zend_function_entry class_ReflectionExtension_methods[] = { ZEND_MALIAS(ReflectionClass, __clone, __clone, arginfo_class_ReflectionExtension___clone, ZEND_ACC_PRIVATE) ZEND_ME(ReflectionExtension, __construct, arginfo_class_ReflectionExtension___construct, ZEND_ACC_PUBLIC) @@ -1367,6 +1376,16 @@ static zend_class_entry *register_class_ReflectionUnionType(zend_class_entry *cl return class_entry; } +static zend_class_entry *register_class_ReflectionIntersectionType(zend_class_entry *class_entry_ReflectionType) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionIntersectionType", class_ReflectionIntersectionType_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionType); + + return class_entry; +} + static zend_class_entry *register_class_ReflectionExtension(zend_class_entry *class_entry_Reflector) { zend_class_entry ce, *class_entry; diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 88551fcd7932b..b7c10079d4dd3 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection'); var_dump($ext->getClasses()); ?> --EXPECT-- -array(23) { +array(24) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -59,68 +59,73 @@ array(23) { ["name"]=> string(19) "ReflectionUnionType" } - ["ReflectionMethod"]=> + ["ReflectionIntersectionType"]=> object(ReflectionClass)#12 (1) { + ["name"]=> + string(26) "ReflectionIntersectionType" + } + ["ReflectionMethod"]=> + object(ReflectionClass)#13 (1) { ["name"]=> string(16) "ReflectionMethod" } ["ReflectionClass"]=> - object(ReflectionClass)#13 (1) { + object(ReflectionClass)#14 (1) { ["name"]=> string(15) "ReflectionClass" } ["ReflectionObject"]=> - object(ReflectionClass)#14 (1) { + object(ReflectionClass)#15 (1) { ["name"]=> string(16) "ReflectionObject" } ["ReflectionProperty"]=> - object(ReflectionClass)#15 (1) { + object(ReflectionClass)#16 (1) { ["name"]=> string(18) "ReflectionProperty" } ["ReflectionClassConstant"]=> - object(ReflectionClass)#16 (1) { + object(ReflectionClass)#17 (1) { ["name"]=> string(23) "ReflectionClassConstant" } ["ReflectionExtension"]=> - object(ReflectionClass)#17 (1) { + object(ReflectionClass)#18 (1) { ["name"]=> string(19) "ReflectionExtension" } ["ReflectionZendExtension"]=> - object(ReflectionClass)#18 (1) { + object(ReflectionClass)#19 (1) { ["name"]=> string(23) "ReflectionZendExtension" } ["ReflectionReference"]=> - object(ReflectionClass)#19 (1) { + object(ReflectionClass)#20 (1) { ["name"]=> string(19) "ReflectionReference" } ["ReflectionAttribute"]=> - object(ReflectionClass)#20 (1) { + object(ReflectionClass)#21 (1) { ["name"]=> string(19) "ReflectionAttribute" } ["ReflectionEnum"]=> - object(ReflectionClass)#21 (1) { + object(ReflectionClass)#22 (1) { ["name"]=> string(14) "ReflectionEnum" } ["ReflectionEnumUnitCase"]=> - object(ReflectionClass)#22 (1) { + object(ReflectionClass)#23 (1) { ["name"]=> string(22) "ReflectionEnumUnitCase" } ["ReflectionEnumBackedCase"]=> - object(ReflectionClass)#23 (1) { + object(ReflectionClass)#24 (1) { ["name"]=> string(24) "ReflectionEnumBackedCase" } ["ReflectionFiber"]=> - object(ReflectionClass)#24 (1) { + object(ReflectionClass)#25 (1) { ["name"]=> string(15) "ReflectionFiber" } diff --git a/ext/reflection/tests/intersection_types.phpt b/ext/reflection/tests/intersection_types.phpt new file mode 100644 index 0000000000000..9f091a8bc93fc --- /dev/null +++ b/ext/reflection/tests/intersection_types.phpt @@ -0,0 +1,80 @@ +--TEST-- +Intersection types in reflection +--FILE-- +allowsNull()) . "\n"; + foreach ($rt->getTypes() as $type) { + echo " Name: " . $type->getName() . "\n"; + echo " String: " . (string) $type . "\n"; + echo " Allows Null: " . json_encode($type->allowsNull()) . "\n"; + } +} + +function test1(): X&Y&Z&Traversable&Countable { } + +class Test { + public X&Y&Countable $prop; +} + +dumpType((new ReflectionFunction('test1'))->getReturnType()); + +$rc = new ReflectionClass(Test::class); +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +/* Force CE resolution of the property type */ + +interface y {} +class x implements Y, Countable { + public function count() {} +} +$test = new Test; +$test->prop = new x; + +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +?> +--EXPECT-- +Type X&Y&Z&Traversable&Countable: +Allows null: false + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: Z + String: Z + Allows Null: false + Name: Traversable + String: Traversable + Allows Null: false + Name: Countable + String: Countable + Allows Null: false +Type X&Y&Countable: +Allows null: false + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: Countable + String: Countable + Allows Null: false +Type x&y&Countable: +Allows null: false + Name: x + String: x + Allows Null: false + Name: y + String: y + Allows Null: false + Name: Countable + String: Countable + Allows Null: false diff --git a/ext/tokenizer/tests/002.phpt b/ext/tokenizer/tests/002.phpt index 7cd65af19319f..4478d5c55c65d 100644 --- a/ext/tokenizer/tests/002.phpt +++ b/ext/tokenizer/tests/002.phpt @@ -917,7 +917,14 @@ array(47) { int(1) } [42]=> - string(1) "&" + array(3) { + [0]=> + int(%d) + [1]=> + string(1) "&" + [2]=> + int(1) + } [43]=> array(3) { [0]=> diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index db5a425406ac6..271184fb107d5 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -167,6 +167,8 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT); } @@ -317,6 +319,8 @@ char *get_token_type_name(int token_type) case T_COALESCE: return "T_COALESCE"; case T_POW: return "T_POW"; case T_POW_EQUAL: return "T_POW_EQUAL"; + case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG"; + case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; case T_BAD_CHARACTER: return "T_BAD_CHARACTER"; }