diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index a103527613137..f71b148620c5b 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -265,6 +265,32 @@ static bool safe_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) { return instanceof_function(ce1, ce2); } +static inline bool can_elide_list_type( + const zend_script *script, const zend_op_array *op_array, + const zend_ssa_var_info *use_info, zend_type 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(type); + ZEND_TYPE_FOREACH(type, single_type) { + if (ZEND_TYPE_HAS_LIST(*single_type)) { + ZEND_ASSERT(!is_intersection); + return can_elide_list_type(script, op_array, use_info, *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, op_array, lcname); + zend_string_release(lcname); + bool result = ce && safe_instanceof(use_info->ce, ce); + if (result == !is_intersection) { + return result; + } + } + } ZEND_TYPE_FOREACH_END(); + return is_intersection; +} + static inline bool can_elide_return_type_check( const zend_script *script, zend_op_array *op_array, zend_ssa *ssa, zend_ssa_op *ssa_op) { zend_arg_info *arg_info = &op_array->arg_info[-1]; @@ -286,22 +312,7 @@ static inline bool can_elide_return_type_check( } 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, op_array, lcname); - zend_string_release(lcname); - bool result = ce && safe_instanceof(use_info->ce, ce); - if (result == !is_intersection) { - return result; - } - } - } ZEND_TYPE_FOREACH_END(); - return is_intersection; + return can_elide_list_type(script, op_array, use_info, arg_info->type); } return false; diff --git a/Zend/tests/type_declarations/dnf_types/dnf_2_intersection.phpt b/Zend/tests/type_declarations/dnf_types/dnf_2_intersection.phpt new file mode 100644 index 0000000000000..6375c60ab71bb --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/dnf_2_intersection.phpt @@ -0,0 +1,63 @@ +--TEST-- +Union of two intersection type +--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/dnf_types/dnf_intersection_and_null.phpt b/Zend/tests/type_declarations/dnf_types/dnf_intersection_and_null.phpt new file mode 100644 index 0000000000000..e9089c130b492 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/dnf_intersection_and_null.phpt @@ -0,0 +1,74 @@ +--TEST-- +Union of null and intersection type +--FILE-- +foo1($a); +$test->foo2($a); +$test->foo1($n); +$test->foo2($n); +$test->prop1 = $a; +$test->prop1 = $n; +$test->prop2 = $a; +$test->prop2 = $n; + +$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->prop1 = $c; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + $test->prop2 = $c; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +===DONE=== +--EXPECTF-- +object(A)#2 (0) { +} +object(A)#2 (0) { +} +NULL +NULL +Test::foo1(): Argument #1 ($v) must be of type (X&Y)|null, C given, called in %s on line %d +Test::foo2(): Argument #1 ($v) must be of type (X&Y)|null, C given, called in %s on line %d +Cannot assign C to property Test::$prop1 of type (X&Y)|null +Cannot assign C to property Test::$prop2 of type (X&Y)|null +===DONE=== diff --git a/Zend/tests/type_declarations/dnf_types/dnf_intersection_and_single.phpt b/Zend/tests/type_declarations/dnf_types/dnf_intersection_and_single.phpt new file mode 100644 index 0000000000000..4c9a5f95e30d0 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/dnf_intersection_and_single.phpt @@ -0,0 +1,127 @@ +--TEST-- +Union of a simple and intersection 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/dnf_types/redundant_types/duplicate_class_alias_type.phpt b/Zend/tests/type_declarations/dnf_types/redundant_types/duplicate_class_alias_type.phpt new file mode 100644 index 0000000000000..93ff63856c0b2 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/redundant_types/duplicate_class_alias_type.phpt @@ -0,0 +1,13 @@ +--TEST-- +Duplicate class alias type +--FILE-- + +--EXPECTF-- +Fatal error: Type X&A is redundant with type X&A in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/redundant_types/duplicate_class_alias_type_runtime.phpt b/Zend/tests/type_declarations/dnf_types/redundant_types/duplicate_class_alias_type_runtime.phpt new file mode 100644 index 0000000000000..0b2e2a13d176c --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/redundant_types/duplicate_class_alias_type_runtime.phpt @@ -0,0 +1,15 @@ +--TEST-- +Duplicate class alias type at runtime +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/dnf_types/redundant_types/inheritence.phpt b/Zend/tests/type_declarations/dnf_types/redundant_types/inheritence.phpt new file mode 100644 index 0000000000000..ef57adf09c0e4 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_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/dnf_types/redundant_types/less_restrive_type_constraint_already_present001.phpt b/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present001.phpt new file mode 100644 index 0000000000000..b0753313a99ea --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present001.phpt @@ -0,0 +1,14 @@ +--TEST-- +A less restrictive type constrain is part of the DNF type 001 +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Type A&B is redundant as it is more restrictive than type A in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present002.phpt b/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present002.phpt new file mode 100644 index 0000000000000..6aa7ce16a4808 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present002.phpt @@ -0,0 +1,14 @@ +--TEST-- +A less restrictive type constrain is part of the DNF type 002 +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Type A&B is redundant as it is more restrictive than type A in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present003.phpt b/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present003.phpt new file mode 100644 index 0000000000000..55f248bc05e4f --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present003.phpt @@ -0,0 +1,15 @@ +--TEST-- +A less restrictive type constrain is part of the DNF type 003 +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Type A&B&C is redundant as it is more restrictive than type A&B in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present004.phpt b/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present004.phpt new file mode 100644 index 0000000000000..ab69ebe9b5f64 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/redundant_types/less_restrive_type_constraint_already_present004.phpt @@ -0,0 +1,15 @@ +--TEST-- +A less restrictive type constrain is part of the DNF type 004 +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Type A&B&C is redundant as it is more restrictive than type A&B in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/variance/invalid1.phpt b/Zend/tests/type_declarations/dnf_types/variance/invalid1.phpt new file mode 100644 index 0000000000000..5e90cc7a79801 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/invalid1.phpt @@ -0,0 +1,28 @@ +--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)|X must be compatible with Foo::foo(): (B&C)|X in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_drop_type1.phpt b/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_drop_type1.phpt new file mode 100644 index 0000000000000..9aaf44cad636c --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_drop_type1.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|X must be compatible with Foo::foo(): (A&B)|X in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_drop_type2.phpt b/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_drop_type2.phpt new file mode 100644 index 0000000000000..67cd813007cb0 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_drop_type2.phpt @@ -0,0 +1,28 @@ +--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)|X must be compatible with Foo::foo(): (A&B&C)|X in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_intersection_to_union1.phpt b/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_intersection_to_union1.phpt new file mode 100644 index 0000000000000..273bbcf1ac500 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_intersection_to_union1.phpt @@ -0,0 +1,26 @@ +--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)|L in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_intersection_to_union2.phpt b/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_intersection_to_union2.phpt new file mode 100644 index 0000000000000..1eafcb5d58718 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_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/dnf_types/variance/invalid_covariance_intersection_to_union3.phpt b/Zend/tests/type_declarations/dnf_types/variance/invalid_covariance_intersection_to_union3.phpt new file mode 100644 index 0000000000000..c4dd3449f976d --- /dev/null +++ b/Zend/tests/type_declarations/dnf_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/dnf_types/variance/invalid_invariance1.phpt b/Zend/tests/type_declarations/dnf_types/variance/invalid_invariance1.phpt new file mode 100644 index 0000000000000..65d3d2033bbc1 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_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)|L (as in class A) in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/variance/invalid_invariance2.phpt b/Zend/tests/type_declarations/dnf_types/variance/invalid_invariance2.phpt new file mode 100644 index 0000000000000..3b82afa89355e --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/invalid_invariance2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Intersection type reduction invalid invariant type check +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Type of Test2::$prop must be (X&Y)|B (as in class Test) in %s on line %d diff --git a/Zend/tests/type_declarations/dnf_types/variance/valid1.phpt b/Zend/tests/type_declarations/dnf_types/variance/valid1.phpt new file mode 100644 index 0000000000000..3282b0f9dc04f --- /dev/null +++ b/Zend/tests/type_declarations/dnf_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/dnf_types/variance/valid2.phpt b/Zend/tests/type_declarations/dnf_types/variance/valid2.phpt new file mode 100644 index 0000000000000..f77380326db96 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/valid2.phpt @@ -0,0 +1,23 @@ +--TEST-- +Commutative intersection types +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/dnf_types/variance/valid4.phpt b/Zend/tests/type_declarations/dnf_types/variance/valid4.phpt new file mode 100644 index 0000000000000..78691715c8e59 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/valid4.phpt @@ -0,0 +1,20 @@ +--TEST-- +Intersection type reduction valid invariant type check +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/dnf_types/variance/valid5.phpt b/Zend/tests/type_declarations/dnf_types/variance/valid5.phpt new file mode 100644 index 0000000000000..ca75aeb584f00 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/valid5.phpt @@ -0,0 +1,46 @@ +--TEST-- +Replacing union of classes respecting intersection type by intersection type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/dnf_types/variance/valid6.phpt b/Zend/tests/type_declarations/dnf_types/variance/valid6.phpt new file mode 100644 index 0000000000000..faf63748aec34 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_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/dnf_types/variance/valid7.phpt b/Zend/tests/type_declarations/dnf_types/variance/valid7.phpt new file mode 100644 index 0000000000000..3d9a58638e2f4 --- /dev/null +++ b/Zend/tests/type_declarations/dnf_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/dnf_types/variance/valid8.phpt b/Zend/tests/type_declarations/dnf_types/variance/valid8.phpt new file mode 100644 index 0000000000000..dda2fa94494ec --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/valid8.phpt @@ -0,0 +1,20 @@ +--TEST-- +Covariant replacement of iterable type with intersection type in DNF type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/dnf_types/variance/valid9.phpt b/Zend/tests/type_declarations/dnf_types/variance/valid9.phpt new file mode 100644 index 0000000000000..1061ca211954d --- /dev/null +++ b/Zend/tests/type_declarations/dnf_types/variance/valid9.phpt @@ -0,0 +1,18 @@ +--TEST-- +Covariant replacement of iterable type with unregistered intersection type in DNF type +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Could not check compatibility between Test2::method2(): (X&MyIterator)|int and Test::method2(): Traversable|array|int, because class X is not available in %s diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 220b76efca6ff..b75cf2a6de376 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4211,6 +4211,8 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z if (is_persistent_class(ce)) { zend_type *single_type; ZEND_TYPE_FOREACH(property_info->type, single_type) { + // TODO Add support and test cases when gen_stub support added + ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*single_type)); if (ZEND_TYPE_HAS_NAME(*single_type)) { zend_string *name = zend_new_interned_string(ZEND_TYPE_NAME(*single_type)); ZEND_TYPE_SET_PTR(*single_type, name); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 728feded60896..522ba6329b139 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1192,16 +1192,54 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop return zend_string_copy(name); } +static zend_string *add_intersection_type(zend_string *str, + zend_type_list *intersection_type_list, zend_class_entry *scope, + bool is_bracketed) +{ + zend_type *single_type; + zend_string *intersection_str = NULL; + + ZEND_TYPE_LIST_FOREACH(intersection_type_list, single_type) { + ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*single_type)); + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*single_type)); + zend_string *name = ZEND_TYPE_NAME(*single_type); + zend_string *resolved = resolve_class_name(name, scope); + intersection_str = add_type_string(intersection_str, resolved, /* is_intersection */ true); + zend_string_release(resolved); + } ZEND_TYPE_LIST_FOREACH_END(); + + ZEND_ASSERT(intersection_str); + + if (is_bracketed) { + zend_string *result = zend_string_concat3("(", 1, ZSTR_VAL(intersection_str), ZSTR_LEN(intersection_str), ")", 1); + zend_string_release(intersection_str); + intersection_str = result; + } + str = add_type_string(str, intersection_str, /* is_intersection */ false); + zend_string_release(intersection_str); + 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 = add_intersection_type(str, ZEND_TYPE_LIST(type), scope, /* is_bracketed */ false); + } else if (ZEND_TYPE_HAS_LIST(type)) { + /* A union type might not be a list */ 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_IS_INTERSECTION(*list_type)) { + str = add_intersection_type(str, ZEND_TYPE_LIST(*list_type), scope, /* is_bracketed */ true); + continue; + } + ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type)); zend_string *name = ZEND_TYPE_NAME(*list_type); zend_string *resolved = resolve_class_name(name, scope); - str = add_type_string(str, resolved, is_intersection); + str = add_type_string(str, resolved, /* is_intersection */ false); zend_string_release(resolved); } ZEND_TYPE_LIST_FOREACH_END(); } else if (ZEND_TYPE_HAS_NAME(type)) { @@ -1259,7 +1297,8 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop if (type_mask & MAY_BE_NULL) { bool is_union = !str || memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL; - if (!is_union) { + bool has_intersection = !str || memchr(ZSTR_VAL(str), '&', ZSTR_LEN(str)) != NULL; + if (!is_union && !has_intersection) { zend_string *nullable_str = zend_string_concat2("?", 1, ZSTR_VAL(str), ZSTR_LEN(str)); zend_string_release(str); return nullable_str; @@ -6197,6 +6236,88 @@ static zend_type zend_compile_single_typename(zend_ast *ast) } } +static void zend_are_intersection_types_redundant(zend_type left_type, zend_type right_type) +{ + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(left_type)); + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(right_type)); + zend_type_list *l_type_list = ZEND_TYPE_LIST(left_type); + zend_type_list *r_type_list = ZEND_TYPE_LIST(right_type); + zend_type_list *smaller_type_list, *larger_type_list; + bool flipped = false; + + if (r_type_list->num_types < l_type_list->num_types) { + smaller_type_list = r_type_list; + larger_type_list = l_type_list; + flipped = true; + } else { + smaller_type_list = l_type_list; + larger_type_list = r_type_list; + } + + unsigned int sum = 0; + zend_type *outer_type; + ZEND_TYPE_LIST_FOREACH(smaller_type_list, outer_type) + zend_type *inner_type; + ZEND_TYPE_LIST_FOREACH(larger_type_list, inner_type) + if (zend_string_equals_ci(ZEND_TYPE_NAME(*inner_type), ZEND_TYPE_NAME(*outer_type))) { + sum++; + break; + } + ZEND_TYPE_LIST_FOREACH_END(); + ZEND_TYPE_LIST_FOREACH_END(); + + if (sum == smaller_type_list->num_types) { + zend_string *smaller_type_str; + zend_string *larger_type_str; + if (flipped) { + smaller_type_str = zend_type_to_string(right_type); + larger_type_str = zend_type_to_string(left_type); + } else { + smaller_type_str = zend_type_to_string(left_type); + larger_type_str = zend_type_to_string(right_type); + } + if (smaller_type_list->num_types == larger_type_list->num_types) { + zend_error_noreturn(E_COMPILE_ERROR, "Type %s is redundant with type %s", + ZSTR_VAL(smaller_type_str), ZSTR_VAL(larger_type_str)); + } else { + zend_error_noreturn(E_COMPILE_ERROR, "Type %s is redundant as it is more restrictive than type %s", + ZSTR_VAL(larger_type_str), ZSTR_VAL(smaller_type_str)); + } + } +} + +static void zend_is_intersection_type_redundant_by_single_type(zend_type intersection_type, zend_type single_type) +{ + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(intersection_type)); + ZEND_ASSERT(!ZEND_TYPE_IS_INTERSECTION(single_type)); + + zend_type *single_intersection_type = NULL; + ZEND_TYPE_FOREACH(intersection_type, single_intersection_type) + if (zend_string_equals_ci(ZEND_TYPE_NAME(*single_intersection_type), ZEND_TYPE_NAME(single_type))) { + zend_string *single_type_str = zend_type_to_string(single_type); + zend_string *complete_type = zend_type_to_string(intersection_type); + zend_error_noreturn(E_COMPILE_ERROR, "Type %s is redundant as it is more restrictive than type %s", + ZSTR_VAL(complete_type), ZSTR_VAL(single_type_str)); + } + ZEND_TYPE_FOREACH_END(); +} + +/* Used by both intersection and union types prior to transforming the type list to a full zend_type */ +static void zend_is_type_list_redundant_by_single_type(zend_type_list *type_list, zend_type type) +{ + ZEND_ASSERT(!ZEND_TYPE_IS_INTERSECTION(type)); + for (size_t i = 0; i < type_list->num_types - 1; i++) { + if (ZEND_TYPE_IS_INTERSECTION(type_list->types[i])) { + zend_is_intersection_type_redundant_by_single_type(type_list->types[i], type); + continue; + } + if (zend_string_equals_ci(ZEND_TYPE_NAME(type_list->types[i]), ZEND_TYPE_NAME(type))) { + zend_string *single_type_str = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type %s is redundant", ZSTR_VAL(single_type_str)); + } + } +} + static zend_type zend_compile_typename( zend_ast *ast, bool force_allow_null) /* {{{ */ { @@ -6211,6 +6332,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); @@ -6218,7 +6340,37 @@ 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); + zend_type single_type; + + 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; + 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; + + /* 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])) { + zend_are_intersection_types_redundant(single_type, type_list->types[i]); + continue; + } + /* Type from type list is a simple type */ + zend_is_intersection_type_redundant_by_single_type(single_type, type_list->types[i]); + } + continue; + } + + single_type = zend_compile_single_typename(type_ast); uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type); if (single_type_mask == MAY_BE_ANY) { @@ -6241,7 +6393,7 @@ static zend_type zend_compile_typename( ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK; if (ZEND_TYPE_IS_COMPLEX(single_type)) { - if (!ZEND_TYPE_IS_COMPLEX(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; @@ -6257,14 +6409,7 @@ static zend_type zend_compile_typename( 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_is_type_list_redundant_by_single_type(type_list, single_type); } } } @@ -6321,14 +6466,7 @@ static zend_type zend_compile_typename( 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_is_type_list_redundant_by_single_type(type_list, single_type); } ZEND_ASSERT(list->children == type_list->num_types); @@ -6642,12 +6780,12 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 } if (force_nullable && ZEND_TYPE_IS_INTERSECTION(arg_info->type)) { + /* We drop the nullable type flag to generate the correct type string */ + ZEND_TYPE_FULL_MASK(arg_info->type) = ZEND_TYPE_FULL_MASK(arg_info->type) & ~MAY_BE_NULL; zend_string *type_str = zend_type_to_string(arg_info->type); zend_error_noreturn(E_COMPILE_ERROR, "Cannot use null as default value for parameter $%s of type %s", - /* We move type_str pointer one char forward to skip the '?' generated by - * the call to zend_compile_typename() */ - ZSTR_VAL(name), ZSTR_VAL(type_str)+1); + ZSTR_VAL(name), ZSTR_VAL(type_str)); } if (default_type != IS_UNDEF && default_type != IS_CONSTANT_AST && !force_nullable diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index c8888a4baf6f5..016de3c6c6518 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -877,20 +877,38 @@ static zend_always_inline zend_class_entry *zend_ce_from_type( return resolve_single_class_type(name, info->ce); } +static bool zend_check_intersection_for_property_class_type(zend_type_list *intersection_type_list, + zend_property_info *info, zend_class_entry *object_ce) +{ + zend_type *list_type; + + ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) { + ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); + zend_class_entry *ce = zend_ce_from_type(info, list_type); + if (!ce || !instanceof_function(object_ce, ce)) { + return false; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return true; +} + static bool zend_check_and_resolve_property_class_type( zend_property_info *info, zend_class_entry *object_ce) { if (ZEND_TYPE_HAS_LIST(info->type)) { zend_type *list_type; 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; - } - } ZEND_TYPE_LIST_FOREACH_END(); - return true; + return zend_check_intersection_for_property_class_type( + ZEND_TYPE_LIST(info->type), info, object_ce); } else { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { + if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { + if (zend_check_intersection_for_property_class_type( + ZEND_TYPE_LIST(*list_type), info, object_ce)) { + return true; + } + continue; + } + ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); zend_class_entry *ce = zend_ce_from_type(info, list_type); if (ce && instanceof_function(object_ce, ce)) { return true; @@ -1006,6 +1024,22 @@ static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot( return ce; } +static bool zend_check_intersection_type_from_cache_slot(zend_type_list *intersection_type_list, + zend_class_entry *arg_ce, void **cache_slot) +{ + zend_class_entry *ce; + zend_type *list_type; + ZEND_TYPE_LIST_FOREACH(intersection_type_list, 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(arg_ce, ce)) { + return false; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return true; +} + static zend_always_inline bool zend_check_type_slow( zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type, bool is_internal) @@ -1030,11 +1064,19 @@ static zend_always_inline bool zend_check_type_slow( return true; } else { 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; + if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { + if (zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg), cache_slot)) { + return true; + } + } else { + ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*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; + } } + if (HAVE_CACHE_SLOT) { cache_slot++; } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 1b0d57b2e7cae..b0008e889b5ac 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -354,10 +354,9 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name { HashTable *ht; + ZEND_ASSERT(class_name); if (!CG(current_linking_class) || ce == CG(current_linking_class)) { return; - } else if (!class_name) { - class_name = ce->name; } else if (zend_string_equals_literal_ci(class_name, "self") || zend_string_equals_literal_ci(class_name, "parent")) { return; @@ -465,6 +464,28 @@ static inheritance_status zend_is_class_subtype_of_type( * 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) { + if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { + inheritance_status subtype_status = zend_is_class_subtype_of_type( + fe_scope, fe_class_name, proto_scope, *single_type); + + switch (subtype_status) { + case INHERITANCE_ERROR: + if (is_intersection) { + return INHERITANCE_ERROR; + } + continue; + case INHERITANCE_UNRESOLVED: + have_unresolved = 1; + continue; + case INHERITANCE_SUCCESS: + if (!is_intersection) { + return INHERITANCE_SUCCESS; + } + continue; + EMPTY_SWITCH_DEFAULT_CASE(); + } + } + zend_class_entry *proto_ce; zend_string *proto_class_name = NULL; if (ZEND_TYPE_HAS_NAME(*single_type)) { @@ -518,6 +539,10 @@ static zend_string *get_class_from_type(zend_class_entry *scope, zend_type singl static void register_unresolved_classes(zend_class_entry *scope, zend_type type) { zend_type *single_type; ZEND_TYPE_FOREACH(type, single_type) { + if (ZEND_TYPE_HAS_LIST(*single_type)) { + register_unresolved_classes(scope, *single_type); + continue; + } if (ZEND_TYPE_HAS_NAME(*single_type)) { zend_string *class_name = resolve_class_name(scope, ZEND_TYPE_NAME(*single_type)); lookup_class_ex(scope, class_name, /* register_unresolved */ true); @@ -525,6 +550,72 @@ static void register_unresolved_classes(zend_class_entry *scope, zend_type type) } ZEND_TYPE_FOREACH_END(); } +static inheritance_status zend_is_intersection_subtype_of_type( + zend_class_entry *fe_scope, zend_type fe_type, + zend_class_entry *proto_scope, zend_type proto_type) +{ + bool have_unresolved = false; + zend_type *single_type; + uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_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) { + ZEND_TYPE_FOREACH(fe_type, single_type) { + zend_string *fe_class_name = get_class_from_type(fe_scope, *single_type); + if (!fe_class_name) { + continue; + } + zend_class_entry *fe_ce = lookup_class(fe_scope, fe_class_name); + if (fe_ce) { + 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. */ + inheritance_status early_exit_status = + ZEND_TYPE_IS_INTERSECTION(proto_type) ? INHERITANCE_ERROR : INHERITANCE_SUCCESS; + ZEND_TYPE_FOREACH(proto_type, single_type) { + inheritance_status status; + + if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { + status = zend_is_intersection_subtype_of_type( + fe_scope, fe_type, proto_scope, *single_type); + } else { + zend_string *proto_class_name = get_class_from_type(proto_scope, *single_type); + if (!proto_class_name) { + continue; + } + + zend_class_entry *proto_ce = NULL; + 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(); + + if (have_unresolved) { + return INHERITANCE_UNRESOLVED; + } + + return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; +} + static inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, zend_type fe_type, zend_class_entry *proto_scope, zend_type proto_type) @@ -565,48 +656,17 @@ static inheritance_status zend_perform_covariant_type_check( 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) { - ZEND_TYPE_FOREACH(fe_type, single_type) { - zend_string *fe_class_name = get_class_from_type(fe_scope, *single_type); - if (!fe_class_name) { - continue; - } - zend_class_entry *fe_ce = lookup_class(fe_scope, fe_class_name); - if (fe_ce) { - 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_string *proto_class_name = get_class_from_type(proto_scope, *single_type); - if (!proto_class_name) { - continue; - } + inheritance_status status = zend_is_intersection_subtype_of_type( + fe_scope, fe_type, proto_scope, proto_type); - zend_class_entry *proto_ce = NULL; - 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(); + if (status == early_exit_status) { + return status; + } + if (status == INHERITANCE_UNRESOLVED) { + have_unresolved = true; + } } 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. @@ -614,13 +674,21 @@ static inheritance_status zend_perform_covariant_type_check( * 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_string *fe_class_name = get_class_from_type(fe_scope, *single_type); - if (!fe_class_name) { - continue; + inheritance_status status; + /* Union has an intersection type as it's member */ + if (ZEND_TYPE_IS_INTERSECTION(*single_type)) { + status = zend_is_intersection_subtype_of_type( + fe_scope, *single_type, proto_scope, proto_type); + } else { + zend_string *fe_class_name = get_class_from_type(fe_scope, *single_type); + if (!fe_class_name) { + continue; + } + + status = zend_is_class_subtype_of_type( + fe_scope, fe_class_name, proto_scope, proto_type); } - inheritance_status status = zend_is_class_subtype_of_type( - fe_scope, fe_class_name, proto_scope, proto_type); if (status == early_exit_status) { return status; } @@ -939,6 +1007,7 @@ static void ZEND_COLD emit_incompatible_method_error( zend_string *parent_prototype = zend_get_function_declaration(parent, parent_scope); zend_string *child_prototype = zend_get_function_declaration(child, child_scope); if (status == INHERITANCE_UNRESOLVED) { + // TODO Improve error message if first unresolved class is present in child and parent? /* Fetch the first unresolved class from registered autoloads */ zend_string *unresolved_class = NULL; ZEND_HASH_MAP_FOREACH_STR_KEY(CG(delayed_autoloads), unresolved_class) { diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index a333bf52a8dad..d85577d9fd7b6 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -272,8 +272,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 intersection_type_without_static -%type inline_function union_type intersection_type +%type identifier type_expr_without_static union_type_without_static_element union_type_without_static intersection_type_without_static +%type inline_function union_type_element 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 @@ -813,9 +813,16 @@ type: | T_STATIC { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_STATIC); } ; +union_type_element: + type { $$ = $1; } + | '(' intersection_type ')' { $$ = $2; } +; + union_type: - type '|' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } - | union_type '|' type { $$ = zend_ast_list_add($1, $3); } + union_type_element '|' union_type_element + { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | union_type '|' union_type_element + { $$ = zend_ast_list_add($1, $3); } ; intersection_type: @@ -839,10 +846,15 @@ type_without_static: | name { $$ = $1; } ; +union_type_without_static_element: + type_without_static { $$ = $1; } + | '(' intersection_type_without_static ')' { $$ = $2; } +; + union_type_without_static: - type_without_static '|' type_without_static + union_type_without_static_element '|' union_type_without_static_element { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } - | union_type_without_static '|' type_without_static + | union_type_without_static '|' union_type_without_static_element { $$ = zend_ast_list_add($1, $3); } ; diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 8ed0caae85ee7..4546f1e1ddf31 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -616,6 +616,21 @@ static zend_string* ZEND_FASTCALL accel_init_interned_string_for_php(const char return zend_string_init(str, size, permanent); } +static inline void accel_copy_permanent_list_types( + zend_new_interned_string_func_t new_interned_string, zend_type type) +{ + zend_type *single_type; + ZEND_TYPE_FOREACH(type, single_type) { + if (ZEND_TYPE_HAS_LIST(*single_type)) { + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(*single_type)); + accel_copy_permanent_list_types(new_interned_string, *single_type); + } + if (ZEND_TYPE_HAS_NAME(*single_type)) { + ZEND_TYPE_SET_PTR(*single_type, new_interned_string(ZEND_TYPE_NAME(*single_type))); + } + } ZEND_TYPE_FOREACH_END(); +} + /* Copy PHP interned strings from PHP process memory into the shared memory */ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_interned_string) { @@ -650,13 +665,7 @@ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_int num_args++; } for (i = 0 ; i < num_args; i++) { - zend_type *single_type; - ZEND_TYPE_FOREACH(arg_info[i].type, single_type) { - if (ZEND_TYPE_HAS_NAME(*single_type)) { - ZEND_TYPE_SET_PTR(*single_type, - new_interned_string(ZEND_TYPE_NAME(*single_type))); - } - } ZEND_TYPE_FOREACH_END(); + accel_copy_permanent_list_types(new_interned_string, arg_info[i].type); } } } ZEND_HASH_FOREACH_END(); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index cc0572f011edc..8913b68d7f48b 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -350,6 +350,10 @@ static void zend_persist_type(zend_type *type) { zend_type *single_type; ZEND_TYPE_FOREACH(*type, single_type) { + if (ZEND_TYPE_HAS_LIST(*single_type)) { + zend_persist_type(single_type); + continue; + } if (ZEND_TYPE_HAS_NAME(*single_type)) { zend_string *type_name = ZEND_TYPE_NAME(*single_type); zend_accel_store_interned_string(type_name); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index c2aa9800f9f89..4e3af0d68c653 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -187,6 +187,10 @@ static void zend_persist_type_calc(zend_type *type) zend_type *single_type; ZEND_TYPE_FOREACH(*type, single_type) { + if (ZEND_TYPE_HAS_LIST(*single_type)) { + zend_persist_type_calc(single_type); + continue; + } if (ZEND_TYPE_HAS_NAME(*single_type)) { zend_string *type_name = ZEND_TYPE_NAME(*single_type); ADD_INTERNED_STRING(type_name); diff --git a/ext/reflection/tests/ReflectionType_001.phpt b/ext/reflection/tests/types/ReflectionType_001.phpt similarity index 100% rename from ext/reflection/tests/ReflectionType_001.phpt rename to ext/reflection/tests/types/ReflectionType_001.phpt diff --git a/ext/reflection/tests/ReflectionType_002.phpt b/ext/reflection/tests/types/ReflectionType_002.phpt similarity index 100% rename from ext/reflection/tests/ReflectionType_002.phpt rename to ext/reflection/tests/types/ReflectionType_002.phpt diff --git a/ext/reflection/tests/ReflectionType_possible_types.phpt b/ext/reflection/tests/types/ReflectionType_possible_types.phpt similarity index 100% rename from ext/reflection/tests/ReflectionType_possible_types.phpt rename to ext/reflection/tests/types/ReflectionType_possible_types.phpt diff --git a/ext/reflection/tests/types/dnf_types.phpt b/ext/reflection/tests/types/dnf_types.phpt new file mode 100644 index 0000000000000..46f3ab0f55f14 --- /dev/null +++ b/ext/reflection/tests/types/dnf_types.phpt @@ -0,0 +1,85 @@ +--TEST-- +Disjunctive Normal Form types in reflection +--FILE-- +getTypes() as $type) { + if ($type instanceof ReflectionNamedType) { + echo $str_indent . " Name: " . $type->getName() . "\n"; + echo $str_indent . " String: " . (string) $type . "\n"; + echo $str_indent . " Allows Null: " . json_encode($type->allowsNull()) . "\n"; + } else { + dumpType($type, $indent+1); + } + } +} + +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(): int { return 0; } +} +$test = new Test; +$test->prop = new x; + +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +?> +--EXPECT-- +Type (X&Y)|(Z&Traversable)|Countable is ReflectionUnionType: + Type X&Y is ReflectionIntersectionType: + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Type Z&Traversable is ReflectionIntersectionType: + 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 is ReflectionUnionType: + Type X&Y is ReflectionIntersectionType: + 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 is ReflectionUnionType: + Type X&Y is ReflectionIntersectionType: + 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/reflection/tests/types/dnf_types_with_null.phpt b/ext/reflection/tests/types/dnf_types_with_null.phpt new file mode 100644 index 0000000000000..ade2199399587 --- /dev/null +++ b/ext/reflection/tests/types/dnf_types_with_null.phpt @@ -0,0 +1,81 @@ +--TEST-- +Disjunctive Normal Form types in reflection +--FILE-- +allowsNull()) . "\n"; + foreach ($rt->getTypes() as $type) { + if ($type instanceof ReflectionNamedType) { + echo $str_indent . " Name: " . $type->getName() . "\n"; + echo $str_indent . " String: " . (string) $type . "\n"; + } else { + dumpType($type, $indent+1); + } + } +} + +function test1(): (X&Y)|(Z&Traversable)|null { } + +class Test { + public (X&Y)|null $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(): int { return 0; } +} +$test = new Test; +$test->prop = new x; + +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +?> +--EXPECT-- +Type (X&Y)|(Z&Traversable)|null is ReflectionUnionType: +Allows Null: true + Type X&Y is ReflectionIntersectionType: + Allows Null: false + Name: X + String: X + Name: Y + String: Y + Type Z&Traversable is ReflectionIntersectionType: + Allows Null: false + Name: Z + String: Z + Name: Traversable + String: Traversable + Name: null + String: null +Type (X&Y)|null is ReflectionUnionType: +Allows Null: true + Type X&Y is ReflectionIntersectionType: + Allows Null: false + Name: X + String: X + Name: Y + String: Y + Name: null + String: null +Type (X&Y)|null is ReflectionUnionType: +Allows Null: true + Type X&Y is ReflectionIntersectionType: + Allows Null: false + Name: X + String: X + Name: Y + String: Y + Name: null + String: null diff --git a/ext/reflection/tests/intersection_types.phpt b/ext/reflection/tests/types/intersection_types.phpt similarity index 100% rename from ext/reflection/tests/intersection_types.phpt rename to ext/reflection/tests/types/intersection_types.phpt diff --git a/ext/reflection/tests/mixed_type.phpt b/ext/reflection/tests/types/mixed_type.phpt similarity index 100% rename from ext/reflection/tests/mixed_type.phpt rename to ext/reflection/tests/types/mixed_type.phpt diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/types/union_types.phpt similarity index 100% rename from ext/reflection/tests/union_types.phpt rename to ext/reflection/tests/types/union_types.phpt