diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index 09c63c992a1ee..4cdd585e47825 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -74,7 +74,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 90bd2aa59a2f8..f43cd81c46151 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -313,7 +313,7 @@ static inline bool can_elide_return_type_check( return 0; } - if (ZEND_TYPE_HAS_CLASS(info->type)) { + if (ZEND_TYPE_IS_COMPLEX(info->type)) { if (!use_info->ce || !def_info->ce || !safe_instanceof(use_info->ce, def_info->ce)) { return 0; } diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 323cb06fa7ec5..f3bee2c82618e 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2208,7 +2208,7 @@ ZEND_API uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_i } tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type)); - if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { + if (ZEND_TYPE_IS_COMPLEX(arg_info->type)) { tmp |= MAY_BE_OBJECT; /* As we only have space to store one CE, we use a plain object type for class unions. */ if (ZEND_TYPE_HAS_NAME(arg_info->type)) { @@ -2316,7 +2316,7 @@ static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_in } if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { uint32_t type = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type)); - if (ZEND_TYPE_HAS_CLASS(prop_info->type)) { + if (ZEND_TYPE_IS_COMPLEX(prop_info->type)) { type |= MAY_BE_OBJECT; if (pce) { if (ZEND_TYPE_HAS_CE(prop_info->type)) { diff --git a/Zend/tests/type_declarations/composite_types/intersection_and_union_type_combined.phpt b/Zend/tests/type_declarations/composite_types/intersection_and_union_type_combined.phpt new file mode 100644 index 0000000000000..fc5a8e848b1c1 --- /dev/null +++ b/Zend/tests/type_declarations/composite_types/intersection_and_union_type_combined.phpt @@ -0,0 +1,127 @@ +--TEST-- +Test for a complex type with an intersection type and union of a simple type +--FILE-- +foo1($a); +$test->foo2($a); +$test->foo1($i); +$test->foo2($i); +$test->prop1 = $a; +$test->prop1 = $i; +$test->prop2 = $a; +$test->prop2 = $i; + +$test->bar1($a); +$test->bar2($a); +$test->bar1($b); +$test->bar2($b); +$test->prop3 = $a; +$test->prop4 = $b; +$test->prop3 = $a; +$test->prop4 = $b; + +$c = new C(); +try { + $test->foo1($c); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + $test->foo2($c); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + $test->bar1($c); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + $test->bar2($c); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + $test->prop1 = $c; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + $test->prop2 = $c; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + $test->prop3 = $c; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + $test->prop4 = $c; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +===DONE=== +--EXPECTF-- +object(A)#2 (0) { +} +object(A)#2 (0) { +} +int(10) +int(10) +object(A)#2 (0) { +} +object(A)#2 (0) { +} +object(B)#3 (0) { +} +object(B)#3 (0) { +} +Test::foo1(): Argument #1 ($v) must be of type X&Y|int, C given, called in %s on line %d +Test::foo2(): Argument #1 ($v) must be of type X&Y|int, C given, called in %s on line %d +Test::bar1(): Argument #1 ($v) must be of type B|X&Y, C given, called in %s on line %d +Test::bar2(): Argument #1 ($v) must be of type X&Y|B, C given, called in %s on line %d +Cannot assign C to property Test::$prop1 of type X&Y|int +Cannot assign C to property Test::$prop2 of type X&Y|int +Cannot assign C to property Test::$prop3 of type X&Y|B +Cannot assign C to property Test::$prop4 of type B|X&Y +===DONE=== diff --git a/Zend/tests/type_declarations/composite_types/union_of_intersection_types.phpt b/Zend/tests/type_declarations/composite_types/union_of_intersection_types.phpt new file mode 100644 index 0000000000000..5ae25528bc96f --- /dev/null +++ b/Zend/tests/type_declarations/composite_types/union_of_intersection_types.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test for a complex type with an intersection type and union with another intersection +--FILE-- +getMessage(), \PHP_EOL; +} +try { + bar2(); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECTF-- +object(A)#%d (0) { +} +object(A)#%d (0) { +} +object(B)#%d (0) { +} +object(B)#%d (0) { +} +bar1(): Return value must be of type X&Y|W&Z, C returned +bar2(): Return value must be of type W&Z|X&Y, C returned 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..3d7334ccd44ad --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt @@ -0,0 +1,53 @@ +--TEST-- +Added element of intersection type +--FILE-- +getMessage(), "\n"; +} + +$c = new Collection(); +$a = new A(); + +try { + $c->intersect = $a; + echo 'OK', \PHP_EOL; +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + bar($a); +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--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_array_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_array_type.phpt new file mode 100644 index 0000000000000..cd276dfb27b3e --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_bool_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_bool_type.phpt new file mode 100644 index 0000000000000..345793b4d5be8 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_callable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_callable_type.phpt new file mode 100644 index 0000000000000..c78e5245a88bf --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_false_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_false_type.phpt new file mode 100644 index 0000000000000..52e9159681670 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_float_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_float_type.phpt new file mode 100644 index 0000000000000..3a61181767621 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_int_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_int_type.phpt new file mode 100644 index 0000000000000..9e4dc56a5be8a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_iterable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_iterable_type.phpt new file mode 100644 index 0000000000000..fc4ee2d5607d0 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_mixed_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_mixed_type.phpt new file mode 100644 index 0000000000000..b980b8436e79a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_null_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_null_type.phpt new file mode 100644 index 0000000000000..c062bc323bf19 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_nullable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_nullable_type.phpt new file mode 100644 index 0000000000000..1c35dfdf91c70 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_parent_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_parent_type.phpt new file mode 100644 index 0000000000000..e3a86771a2189 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_self_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_self_type.phpt new file mode 100644 index 0000000000000..66c7ec79325dc --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_static_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_static_type.phpt new file mode 100644 index 0000000000000..7d2f44f572ee1 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_string_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_string_type.phpt new file mode 100644 index 0000000000000..406a34510ae9a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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_void_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_void_type.phpt new file mode 100644 index 0000000000000..9fb9325972d63 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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/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..d34c022175649 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid3.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/invalid4.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt new file mode 100644 index 0000000000000..cff3674ddf485 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid4.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/invalid5.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt new file mode 100644 index 0000000000000..892a8f9bf803c --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt @@ -0,0 +1,18 @@ +--TEST-- +Property types must be identical +--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/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..7055512680c16 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid4.phpt @@ -0,0 +1,19 @@ +--TEST-- +Intersection type reduction 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/zend_API.c b/Zend/zend_API.c index 810337e5a9895..4bef1f7e4f49c 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2402,14 +2402,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)))); @@ -2746,7 +2746,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 9deb6bcef980e..b657339718dae 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1444,6 +1444,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 fb6587b48cd31..488b1f288c36a 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 8aeca71cd1054..0576ba6b5fbe7 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1157,15 +1157,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; } @@ -1188,14 +1194,62 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop return zend_string_copy(name); } +static zend_string *resolve_intersection_type(zend_string *str, + zend_type_list *intersection_type_list, zend_class_entry *scope) +{ + zend_type *single_type; + /* First type is not part of an intersection with the previous type */ + bool is_intersection = false; + + ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) { + if (ZEND_TYPE_HAS_CE(*single_type)) { + str = add_type_string(str, ZEND_TYPE_CE(*single_type)->name, is_intersection); + } else { + zend_string *name = ZEND_TYPE_NAME(*single_type); + + if (ZSTR_HAS_CE_CACHE(name) + && ZSTR_GET_CE_CACHE(name)) { + zend_class_entry *ce = ZSTR_GET_CE_CACHE(name); + /* Can this happen? */ + 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, is_intersection); + } else { + str = add_type_string(str, ce->name, is_intersection); + } + } else { + zend_string *resolved = resolve_class_name(name, scope); + str = add_type_string(str, resolved, is_intersection); + zend_string_release(resolved); + } + } + /* Following types are part of the intersection */ + is_intersection = true; + } ZEND_TYPE_LIST_FOREACH_END(); + + return str; +} + zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) { zend_string *str = NULL; - if (ZEND_TYPE_HAS_LIST(type)) { + /* Pure intersection type */ + if (ZEND_TYPE_IS_INTERSECTION(type)) { + ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type)); + str = resolve_intersection_type(str, ZEND_TYPE_LIST(type), scope); + } + + if (ZEND_TYPE_IS_UNION(type)) { zend_type *list_type; ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) { + /* Type within the union is an intersection */ + if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { + str = resolve_intersection_type(str, ZEND_TYPE_LIST(*list_type), scope); + continue; + } + 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 */ false); } else { zend_string *name = ZEND_TYPE_NAME(*list_type); @@ -1204,13 +1258,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 */ false); } else { - str = add_type_string(str, ce->name); + str = add_type_string(str, ce->name, /* is_intersection */ false); } } else { zend_string *resolved = resolve_class_name(name, scope); - str = add_type_string(str, resolved); + str = add_type_string(str, resolved, /* is_intersection */ false); zend_string_release(resolved); } } @@ -1236,7 +1290,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; } @@ -1248,36 +1302,36 @@ 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_NULL) { @@ -1288,7 +1342,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; } @@ -2416,7 +2470,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)) { @@ -6222,6 +6276,7 @@ static zend_type zend_compile_typename( if (ast->kind == ZEND_AST_TYPE_UNION) { zend_ast_list *list = zend_ast_get_list(ast); zend_type_list *type_list; + bool is_composite = false; ALLOCA_FLAG(use_heap) type_list = do_alloca(ZEND_TYPE_LIST_SIZE(list->children), use_heap); @@ -6229,8 +6284,32 @@ static zend_type zend_compile_typename( 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); - uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type); + zend_type single_type; + uint32_t single_type_mask; + + if (type_ast->kind == ZEND_AST_TYPE_INTERSECTION) { + is_composite = true; + /* The first class type can be stored directly as the type ptr payload. */ + if (ZEND_TYPE_IS_COMPLEX(type) && !ZEND_TYPE_HAS_LIST(type)) { + /* Switch from single name to name list. */ + type_list->num_types = 1; + type_list->types[0] = type; + /* Reset flags for first type */ + ZEND_TYPE_FULL_MASK(type_list->types[0]) &= ~_ZEND_TYPE_MAY_BE_MASK; + ZEND_TYPE_SET_LIST(type, type_list); + } + + single_type = zend_compile_typename(type_ast, false); + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(single_type)); + + type_list->types[type_list->num_types++] = single_type; + + /* TODO Check for trivially redundant class types? */ + continue; + } + + single_type = zend_compile_single_typename(type_ast); + single_type_mask = ZEND_TYPE_PURE_MASK(single_type); if (single_type_mask == MAY_BE_ANY) { zend_error_noreturn(E_COMPILE_ERROR, "Type mixed can only be used as a standalone type"); @@ -6246,8 +6325,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) && !is_composite) { /* 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; @@ -6264,6 +6343,9 @@ static zend_type zend_compile_typename( /* Check for trivially redundant class types */ for (size_t i = 0; i < type_list->num_types - 1; i++) { + if (ZEND_TYPE_IS_INTERSECTION(type_list->types[i])) { + continue; + } 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); @@ -6281,9 +6363,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); + zend_string *standard_type_str = zend_type_to_string(single_type); + + /* An intersection of standard types cannot exist so invalidate it */ + if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + } + /* Check for "self" and "parent" too */ + if (zend_string_equals_literal_ci(standard_type_str, "self") + || zend_string_equals_literal_ci(standard_type_str, "parent")) { + 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); + + /* 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); } @@ -6310,19 +6443,19 @@ 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_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 { @@ -7438,7 +7571,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", @@ -7695,7 +7828,7 @@ static void zend_compile_enum_case(zend_ast *ast) } if (enum_class->enum_backing_type != Z_TYPE(case_value_zv)) { - zend_error_noreturn(E_COMPILE_ERROR, "Enum case type %s does not match enum backing type %s", + zend_error_noreturn(E_COMPILE_ERROR, "Enum case type %s does not match enum backing type %s", zend_get_type_by_const(Z_TYPE(case_value_zv)), zend_get_type_by_const(enum_class->enum_backing_type)); } diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index ee4ee4df45def..b07a99c95c3de 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -845,11 +845,65 @@ static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class } } +static bool zend_check_and_resolve_property_intersection_class_type( + zend_type_list *intersection_list_type, zend_property_info *info, + zend_class_entry *object_ce) +{ + zend_class_entry *ce; + zend_type *individual_type; + + ZEND_TYPE_LIST_FOREACH(intersection_list_type, individual_type) { + if (ZEND_TYPE_HAS_NAME(*individual_type)) { + zend_string *name = ZEND_TYPE_NAME(*individual_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)) { + /* Cannot resolve */ + return false; + } + } + } else { + ce = resolve_single_class_type(name, info->ce); + if (!ce) { + /* Cannot resolve */ + return false; + } + if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + zend_string_release(name); + ZEND_TYPE_SET_CE(*individual_type, ce); + } + } + } else { + ce = ZEND_TYPE_CE(*individual_type); + } + if (!instanceof_function(object_ce, ce)) { + return false; + } + } ZEND_TYPE_LIST_FOREACH_END(); + + /* If type is an intersection reaching the end of the loop + * means the type has validated */ + return true; +} + +// TODO Handle complex types when added 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; + + /* Type is only an intersection type */ + if (ZEND_TYPE_IS_INTERSECTION(info->type)) { + ZEND_ASSERT(!ZEND_TYPE_IS_UNION(info->type)); + return zend_check_and_resolve_property_intersection_class_type( + ZEND_TYPE_LIST(info->type), info, object_ce); + } + + ZEND_ASSERT(ZEND_TYPE_IS_UNION(info->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); @@ -875,11 +929,17 @@ static bool zend_check_and_resolve_property_class_type( } else { ce = ZEND_TYPE_CE(*list_type); } - if (instanceof_function(object_ce, ce)) { - return 1; + if (UNEXPECTED(ZEND_TYPE_IS_INTERSECTION(*list_type))) { + if (zend_check_and_resolve_property_intersection_class_type( + ZEND_TYPE_LIST(*list_type), info, object_ce)) { + return true; + } + } else if (instanceof_function(object_ce, ce)) { + return true; } } ZEND_TYPE_LIST_FOREACH_END(); - return 0; + /* For union types it means it has failed */ + return false; } else { if (UNEXPECTED(ZEND_TYPE_HAS_NAME(info->type))) { zend_string *name = ZEND_TYPE_NAME(info->type); @@ -916,7 +976,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; } @@ -978,16 +1038,91 @@ ZEND_API bool zend_value_instanceof_static(zval *zv) { # define HAVE_CACHE_SLOT 1 #endif +static bool zend_check_intersection_type(zend_type_list *intersection_type_list, + zend_object *arg, void **cache_slot, zend_class_entry *scope) +{ + zend_type *individual_type; + zend_class_entry *ce; + + ZEND_TYPE_LIST_FOREACH(intersection_type_list, individual_type) { + if (HAVE_CACHE_SLOT && *cache_slot) { + ce = *cache_slot; + } else { + zend_string *name = ZEND_TYPE_NAME(*individual_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++; + } + + /* Cannot resolve */ + return false; + } + } + } 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++; + } + + /* Cannot resolve */ + return false; + } + } + } + + /* Perform actual type check */ + if (!instanceof_function(arg->ce, ce)) { + return false; + } + if (HAVE_CACHE_SLOT) { + cache_slot++; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return true; +} + +/* TODO Need to rewind cache slot pointer? */ static zend_always_inline bool zend_check_type_slow( zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope, 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; + + /* Type is only an intersection type */ + if (ZEND_TYPE_IS_INTERSECTION(*type)) { + ZEND_ASSERT(!ZEND_TYPE_IS_UNION(*type)); + ZEND_ASSERT(Z_TYPE_P(arg) == IS_OBJECT); + return zend_check_intersection_type(ZEND_TYPE_LIST(*type), Z_OBJ_P(arg), cache_slot, scope); + } + + ZEND_ASSERT(ZEND_TYPE_IS_UNION(*type)); + /* Type is a union of types (single and/or intersection) */ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { + /* Type is an intersection */ + if (UNEXPECTED(ZEND_TYPE_IS_INTERSECTION(*list_type))) { + ZEND_ASSERT(Z_TYPE_P(arg) == IS_OBJECT); + if (zend_check_intersection_type(ZEND_TYPE_LIST(*list_type), Z_OBJ_P(arg), cache_slot, scope)) { + return true; + } + /* Increase cache slot */ + if (HAVE_CACHE_SLOT) { + cache_slot++; + } + /* Go to the next type */ + continue; + } + if (HAVE_CACHE_SLOT && *cache_slot) { ce = *cache_slot; } else { @@ -1014,12 +1149,12 @@ static zend_always_inline bool zend_check_type_slow( continue; } } - if (HAVE_CACHE_SLOT) { - *cache_slot = ce; - } } + + /* Perform actual type check */ + /* Instance of a single type part of a union is sufficient to pass the type check */ if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; + return true; } if (HAVE_CACHE_SLOT) { cache_slot++; @@ -3109,7 +3244,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_inheritance.c b/Zend/zend_inheritance.c index dff25db31d685..0a8c713b1c790 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -395,11 +395,59 @@ 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_intersection_covariant_class_type_check( + zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce, + zend_class_entry *fe_scope, zend_type fe_type, + bool register_unresolved) +{ + bool have_unresolved = false; + zend_type *single_type; + + /* Traverse the list of child types and check if it is either a subtype + * of one of the parent types OR is not in the parent type list */ + 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, register_unresolved); + fe_ce = + lookup_class(fe_scope, fe_class_name, register_unresolved); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved); + fe_ce = ZEND_TYPE_CE(*single_type); + } else { + /* standard type */ + ZEND_ASSERT(0 && "This shouldn't happen yet"); + 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; +} + static inheritance_status zend_perform_covariant_class_type_check( 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 register_unresolved) { 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 @@ -423,6 +471,9 @@ 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 */ ZEND_TYPE_FOREACH(proto_type, single_type) { zend_class_entry *proto_ce; zend_string *proto_class_name = NULL; @@ -440,6 +491,7 @@ static inheritance_status zend_perform_covariant_class_type_check( if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); proto_ce = ZEND_TYPE_CE(*single_type); } else { + /* standard type */ continue; } @@ -490,33 +542,69 @@ static inheritance_status zend_perform_covariant_type_check( } zend_type *single_type; - bool all_success = 1; + bool all_success = true; + + /* For intersection types loop over the parent types first as a child + * can add them */ + if (ZEND_TYPE_IS_INTERSECTION(proto_type) || ZEND_TYPE_IS_INTERSECTION(fe_type)) { + /* First try to check whether we can succeed without resolving anything */ + ZEND_TYPE_FOREACH(proto_type, single_type) { + inheritance_status status; + zend_string *proto_class_name; + zend_class_entry *proto_ce = NULL; + + if (ZEND_TYPE_HAS_NAME(*single_type)) { + proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + proto_ce = ZEND_TYPE_CE(*single_type); + proto_class_name = proto_ce->name; + } else { + /* standard type */ + ZEND_ASSERT(0 && "This shouldn't happen yet"); + 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, /* register_unresolved */ 0); - } 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, /* register_unresolved */ 0); - } else { - continue; - } + status = zend_perform_intersection_covariant_class_type_check( + proto_scope, proto_class_name, proto_ce, + fe_scope, fe_type, /* register_unresolved */ false); - if (status == INHERITANCE_ERROR) { - return INHERITANCE_ERROR; - } - if (status != INHERITANCE_SUCCESS) { - all_success = 0; - } - } ZEND_TYPE_FOREACH_END(); + if (status == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; + } + if (status != INHERITANCE_SUCCESS) { + ZEND_ASSERT(status == INHERITANCE_UNRESOLVED); + all_success = false; + } + } ZEND_TYPE_FOREACH_END(); + } + /* if (ZEND_TYPE_IS_UNION(fe_type) || ZEND_TYPE_IS_UNION(proto_type)) */ + else { + /* 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, /* register_unresolved */ 0); + } 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, /* register_unresolved */ 0); + } else { + continue; + } + + if (status == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; + } + if (status != INHERITANCE_SUCCESS) { + all_success = 0; + } + } ZEND_TYPE_FOREACH_END(); + } /* All individual checks succeeded, overall success */ if (all_success) { @@ -524,20 +612,24 @@ static inheritance_status zend_perform_covariant_type_check( } /* Register all classes that may have to be resolved */ - ZEND_TYPE_FOREACH(fe_type, single_type) { - if (ZEND_TYPE_HAS_NAME(*single_type)) { - zend_string *fe_class_name = - resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); - zend_perform_covariant_class_type_check( - fe_scope, fe_class_name, NULL, - proto_scope, proto_type, /* register_unresolved */ 1); - } else if (ZEND_TYPE_HAS_CE(*single_type)) { - zend_class_entry *fe_ce = ZEND_TYPE_CE(*single_type); - zend_perform_covariant_class_type_check( - fe_scope, fe_ce->name, fe_ce, - proto_scope, proto_type, /* register_unresolved */ 1); - } - } ZEND_TYPE_FOREACH_END(); + if (ZEND_TYPE_IS_INTERSECTION(proto_type)) { + // TODO Register intersection type classes + } else { + ZEND_TYPE_FOREACH(fe_type, single_type) { + if (ZEND_TYPE_HAS_NAME(*single_type)) { + zend_string *fe_class_name = + resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); + zend_perform_covariant_class_type_check( + fe_scope, fe_class_name, NULL, + proto_scope, proto_type, /* register_unresolved */ 1); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + zend_class_entry *fe_ce = ZEND_TYPE_CE(*single_type); + zend_perform_covariant_class_type_check( + fe_scope, fe_ce->name, fe_ce, + proto_scope, proto_type, /* register_unresolved */ 1); + } + } ZEND_TYPE_FOREACH_END(); + } return INHERITANCE_UNRESOLVED; } /* }}} */ diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 54ec8eab25490..51b6fb46bd602 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,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_COALESCE "'??'" %token T_POW "'**'" %token T_POW_EQUAL "'**='" +%token T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG "'&'" +%token T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG "'&''" %token T_BAD_CHARACTER "invalid character" /* Token used to force a parse error from the lexer */ @@ -264,8 +266,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 +303,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 +562,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 +640,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 +792,7 @@ type_expr: type { $$ = $1; } | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type { $$ = $1; } + | intersection_type { $$ = $1; } ; type: @@ -794,7 +802,16 @@ type: union_type: type '|' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | intersection_type '|' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | type '|' intersection_type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | intersection_type '|' intersection_type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } | union_type '|' type { $$ = zend_ast_list_add($1, $3); } + | union_type '|' intersection_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", @@ -804,6 +821,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: @@ -815,8 +833,23 @@ type_without_static: union_type_without_static: type_without_static '|' type_without_static { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | intersection_type_without_static '|' type_without_static + { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | type_without_static '|' intersection_type_without_static + { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | intersection_type_without_static '|' intersection_type_without_static + { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } | union_type_without_static '|' type_without_static { $$ = zend_ast_list_add($1, $3); } + | union_type_without_static '|' intersection_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: @@ -1047,7 +1080,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 +1124,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 +1235,7 @@ backup_lex_pos: returns_ref: %empty { $$ = 0; } - | '&' { $$ = ZEND_ACC_RETURN_REFERENCE; } + | ampersand { $$ = ZEND_ACC_RETURN_REFERENCE; } ; lexical_vars: @@ -1216,7 +1250,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 +1450,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); } diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index c8417ecee3217..824b75e8dfbb2 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1865,6 +1865,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 958eaa31dd4eb..a8694c4c35033 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/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 9ed28ed3e2b93..e7cb9349c3756 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1342,40 +1342,74 @@ static zend_always_inline bool zend_jit_verify_type_common(zval *arg, zend_arg_i { uint32_t type_mask; - if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) { + if (ZEND_TYPE_IS_COMPLEX(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 (ZEND_TYPE_IS_INTERSECTION(arg_info->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 (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); if (!ce) { - cache_slot++; - continue; + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (!ce) { + /* Cannot resolve */ + return false; + } + } + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + if (!ce) { + /* Cannot resolve */ + return false; } } + *cache_slot = ce; + } + if (!instanceof_function(Z_OBJCE_P(arg), ce)) { + return false; + } + cache_slot++; + } ZEND_TYPE_LIST_FOREACH_END(); + return true; + } else { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { + if (*cache_slot) { + ce = *cache_slot; } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - cache_slot++; - continue; + 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; } - *cache_slot = ce; - } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; - } - cache_slot++; - } ZEND_TYPE_LIST_FOREACH_END(); + 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; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 0e14f2257086e..090f02a60c37d 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -78,6 +78,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; @@ -1325,22 +1326,40 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje } /* }}} */ +typedef enum reflection_type_kind { + 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 */ @@ -1348,14 +1367,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; @@ -3044,6 +3075,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) { @@ -6796,6 +6848,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 e9815558d61b9..f873f168a97dc 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -591,6 +591,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 36e6dc3cec004..be9cb92bc3702 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: 3594ec0b0c3ed7266223be9c6b426aac56e3aabe */ + * Stub hash: 55c35924ce3b13fb3a6199a565e02ddf5f1606f0 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -422,6 +422,8 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionUnionType_getTypes, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionIntersectionType_getTypes arginfo_class_ReflectionUnionType_getTypes + #define arginfo_class_ReflectionExtension___clone arginfo_class_ReflectionFunctionAbstract___clone #define arginfo_class_ReflectionExtension___construct arginfo_class_ReflectionClass_hasMethod @@ -696,6 +698,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); @@ -991,6 +994,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_ACC_FINAL) ZEND_ME(ReflectionExtension, __construct, arginfo_class_ReflectionExtension___construct, ZEND_ACC_PUBLIC) @@ -1277,6 +1286,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 084e05e118d8a..99c9e51013f1b 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(22) { +array(23) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -59,63 +59,68 @@ array(22) { ["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" } diff --git a/ext/reflection/tests/intersection_types.phpt b/ext/reflection/tests/intersection_types.phpt new file mode 100644 index 0000000000000..39557aaf1d796 --- /dev/null +++ b/ext/reflection/tests/intersection_types.phpt @@ -0,0 +1,80 @@ +--TEST-- +Intersection types in reflection +--FILE-- +allowsNull() ? "true" : "false") . "\n"; + foreach ($rt->getTypes() as $type) { + echo " Name: " . $type->getName() . "\n"; + echo " String: " . (string) $type . "\n"; + echo " Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\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 e711023eeffd9..2330244b61583 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 96571bb1d7798..fba343bdf7df2 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -53,6 +53,8 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BOOLEAN_OR", T_BOOLEAN_OR, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BOOLEAN_AND", T_BOOLEAN_AND, 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_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IS_EQUAL", T_IS_EQUAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IS_NOT_EQUAL", T_IS_NOT_EQUAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IS_IDENTICAL", T_IS_IDENTICAL, CONST_CS | CONST_PERSISTENT); @@ -203,6 +205,8 @@ char *get_token_type_name(int token_type) case T_COALESCE: return "T_COALESCE"; case T_BOOLEAN_OR: return "T_BOOLEAN_OR"; case T_BOOLEAN_AND: return "T_BOOLEAN_AND"; + case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; + case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG"; case T_IS_EQUAL: return "T_IS_EQUAL"; case T_IS_NOT_EQUAL: return "T_IS_NOT_EQUAL"; case T_IS_IDENTICAL: return "T_IS_IDENTICAL";