From b40ae808042d56e006303a7fc9e9d4b4d79a11fb Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 7 Jun 2022 13:35:34 +0100 Subject: [PATCH] Convert iterable into an internal alias for Traversable|array (#7309) This does a compile time transformation of ``iterable`` into ``Traversable|array`` which simplifies some of the LSP variance handling. The arginfo generation script from stubs is updated to produce a union type when it encounters the type ``iterable`` Extension functions which do not regenerate the arginfo, or write them manually are still supported by mimicking the compile time transformation while registering the function. Type Reflection is preserved for single ``iterable`` (and ``?iterable``) to produce a ReflectionNamedType with name ``iterable``, however usage of ``iterable`` in union types will be converted to ``array|Traversable`` --- Zend/Optimizer/zend_inference.c | 3 - Zend/tests/return_types/generators001.phpt | 7 ++ .../invalid_types/invalid_iterable_type.phpt | 2 +- .../intersection_types/variance/invalid5.phpt | 2 +- .../{ => iterable}/iterable_001.phpt | 2 +- .../{ => iterable}/iterable_002.phpt | 2 +- .../{ => iterable}/iterable_003.phpt | 2 +- .../{ => iterable}/iterable_004.phpt | 2 +- .../{ => iterable}/iterable_005.phpt | 2 +- .../type_declarations/iterable/or_null.phpt | 46 +++++++++ .../iterable_and_Traversable.phpt | 2 +- .../iterable_and_Traversable_2.phpt | 2 +- .../redundant_types/iterable_and_array.phpt | 2 +- Zend/tests/typehints/or_null.phpt | 81 ++++----------- Zend/zend_API.c | 10 ++ Zend/zend_compile.c | 51 ++++------ Zend/zend_execute.c | 11 +-- Zend/zend_inheritance.c | 45 +-------- Zend/zend_string.h | 1 + Zend/zend_type_info.h | 1 - Zend/zend_types.h | 8 +- build/gen_stub.php | 25 +++-- ext/opcache/jit/zend_jit_arm64.dasc | 2 +- ext/opcache/jit/zend_jit_helpers.c | 4 +- ext/opcache/jit/zend_jit_x86.dasc | 2 +- .../tests/iterable_type_optimization.phpt | 2 +- ext/reflection/php_reflection.c | 40 ++++++-- ext/reflection/tests/ReflectionType_001.phpt | 2 - .../tests/ReflectionType_possible_types.phpt | 2 - ext/reflection/tests/bug72661.phpt | 10 -- ext/reflection/tests/iterable_Reflection.phpt | 98 +++++++++++++++++++ ext/reflection/tests/union_types.phpt | 9 +- ext/zend_test/test.c | 25 +++++ ext/zend_test/test_arginfo.h | 4 +- ext/zend_test/tests/zend_legacy_iterable.phpt | 28 ++++++ ext/zend_test/tests/zend_weakmap.phpt | 2 +- 36 files changed, 341 insertions(+), 198 deletions(-) rename Zend/tests/type_declarations/{ => iterable}/iterable_001.phpt (84%) rename Zend/tests/type_declarations/{ => iterable}/iterable_002.phpt (89%) rename Zend/tests/type_declarations/{ => iterable}/iterable_003.phpt (84%) rename Zend/tests/type_declarations/{ => iterable}/iterable_004.phpt (74%) rename Zend/tests/type_declarations/{ => iterable}/iterable_005.phpt (88%) create mode 100644 Zend/tests/type_declarations/iterable/or_null.phpt delete mode 100644 ext/reflection/tests/bug72661.phpt create mode 100644 ext/reflection/tests/iterable_Reflection.phpt create mode 100644 ext/zend_test/tests/zend_legacy_iterable.phpt diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 95c3516b408..c17c73ad0ef 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2114,9 +2114,6 @@ static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) { if (type_mask & MAY_BE_CALLABLE) { result_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } - if (type_mask & MAY_BE_ITERABLE) { - result_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; - } if (type_mask & MAY_BE_STATIC) { result_mask |= MAY_BE_OBJECT; } diff --git a/Zend/tests/return_types/generators001.phpt b/Zend/tests/return_types/generators001.phpt index 64793eaa00f..615dabc240b 100644 --- a/Zend/tests/return_types/generators001.phpt +++ b/Zend/tests/return_types/generators001.phpt @@ -26,6 +26,10 @@ function test6() : object|callable { yield 6; } +function test7() : iterable { + yield 7; +} + var_dump( test1(), test2(), @@ -33,6 +37,7 @@ var_dump( test4(), test5(), test6(), + test7(), ); ?> --EXPECTF-- @@ -48,3 +53,5 @@ object(Generator)#%d (%d) { } object(Generator)#%d (%d) { } +object(Generator)#%d (%d) { +} diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt index fc4ee2d5607..3fd321d394b 100644 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt @@ -7,4 +7,4 @@ function foo(): iterable&Iterator {} ?> --EXPECTF-- -Fatal error: Type iterable cannot be part of an intersection type in %s on line %d +Fatal error: Type Traversable|array cannot be part of an intersection type 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 index b704f89b909..ce3a20fb03a 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt @@ -15,4 +15,4 @@ class Test2 extends Test { ?> --EXPECTF-- -Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): iterable in %s on line %d +Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): Traversable|array in %s on line %d diff --git a/Zend/tests/type_declarations/iterable_001.phpt b/Zend/tests/type_declarations/iterable/iterable_001.phpt similarity index 84% rename from Zend/tests/type_declarations/iterable_001.phpt rename to Zend/tests/type_declarations/iterable/iterable_001.phpt index 10a001dea02..f7541800908 100644 --- a/Zend/tests/type_declarations/iterable_001.phpt +++ b/Zend/tests/type_declarations/iterable/iterable_001.phpt @@ -45,4 +45,4 @@ object(ArrayIterator)#1 (1) { int(3) } } -test(): Argument #1 ($iterable) must be of type iterable, int given, called in %s on line %d +test(): Argument #1 ($iterable) must be of type Traversable|array, int given, called in %s on line %d diff --git a/Zend/tests/type_declarations/iterable_002.phpt b/Zend/tests/type_declarations/iterable/iterable_002.phpt similarity index 89% rename from Zend/tests/type_declarations/iterable_002.phpt rename to Zend/tests/type_declarations/iterable/iterable_002.phpt index fdc3b20df58..d1e1b09d187 100644 --- a/Zend/tests/type_declarations/iterable_002.phpt +++ b/Zend/tests/type_declarations/iterable/iterable_002.phpt @@ -17,4 +17,4 @@ function baz(iterable $iterable = 1) { ?> --EXPECTF-- -Fatal error: Cannot use int as default value for parameter $iterable of type iterable in %s on line %d +Fatal error: Cannot use int as default value for parameter $iterable of type Traversable|array in %s on line %d diff --git a/Zend/tests/type_declarations/iterable_003.phpt b/Zend/tests/type_declarations/iterable/iterable_003.phpt similarity index 84% rename from Zend/tests/type_declarations/iterable_003.phpt rename to Zend/tests/type_declarations/iterable/iterable_003.phpt index d2a3bcb368c..d7c5b206eba 100644 --- a/Zend/tests/type_declarations/iterable_003.phpt +++ b/Zend/tests/type_declarations/iterable/iterable_003.phpt @@ -29,4 +29,4 @@ array(0) { } object(Generator)#2 (0) { } -baz(): Return value must be of type iterable, int returned +baz(): Return value must be of type Traversable|array, int returned diff --git a/Zend/tests/type_declarations/iterable_004.phpt b/Zend/tests/type_declarations/iterable/iterable_004.phpt similarity index 74% rename from Zend/tests/type_declarations/iterable_004.phpt rename to Zend/tests/type_declarations/iterable/iterable_004.phpt index fe9d4461b98..8b54482625a 100644 --- a/Zend/tests/type_declarations/iterable_004.phpt +++ b/Zend/tests/type_declarations/iterable/iterable_004.phpt @@ -21,4 +21,4 @@ class Bar extends Foo { ?> --EXPECTF-- -Fatal error: Declaration of Bar::testScalar(iterable $iterable) must be compatible with Foo::testScalar(int $int) in %s on line %d +Fatal error: Declaration of Bar::testScalar(Traversable|array $iterable) must be compatible with Foo::testScalar(int $int) in %s on line %d diff --git a/Zend/tests/type_declarations/iterable_005.phpt b/Zend/tests/type_declarations/iterable/iterable_005.phpt similarity index 88% rename from Zend/tests/type_declarations/iterable_005.phpt rename to Zend/tests/type_declarations/iterable/iterable_005.phpt index 39dede3b5c3..2f5fb83f07d 100644 --- a/Zend/tests/type_declarations/iterable_005.phpt +++ b/Zend/tests/type_declarations/iterable/iterable_005.phpt @@ -29,4 +29,4 @@ class TestScalar extends Test { ?> --EXPECTF-- -Fatal error: Declaration of TestScalar::method(): int must be compatible with Test::method(): iterable in %s on line %d +Fatal error: Declaration of TestScalar::method(): int must be compatible with Test::method(): Traversable|array in %s on line %d diff --git a/Zend/tests/type_declarations/iterable/or_null.phpt b/Zend/tests/type_declarations/iterable/or_null.phpt new file mode 100644 index 00000000000..286f1e291db --- /dev/null +++ b/Zend/tests/type_declarations/iterable/or_null.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test "or null"/"or be null" in type-checking errors for userland functions with iterable +--FILE-- + +--EXPECTF-- +TypeError: iterableF(): Argument #1 ($param) must be of type Traversable|array|null, int given, called in %s on line %d and defined in %s:%d +Stack trace: +#0 %s(%d): iterableF(1) +#1 {main} +TypeError: returnIterable(): Return value must be of type Traversable|array|null, int returned in %s:%d +Stack trace: +#0 %s(%d): returnIterable() +#1 {main} +TypeError: returnMissingIterable(): Return value must be of type Traversable|array|null, none returned in %s:%d +Stack trace: +#0 %s(%d): returnMissingIterable() +#1 {main} diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt index 5b65a33de1a..f8d9287be6b 100644 --- a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt +++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt @@ -8,4 +8,4 @@ function test(): iterable|Traversable { ?> --EXPECTF-- -Fatal error: Type Traversable|iterable contains both iterable and Traversable, which is redundant in %s on line %d +Fatal error: Duplicate type Traversable is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt index e3f7c5858b4..a36d73e3f30 100644 --- a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt +++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt @@ -8,4 +8,4 @@ function test(): iterable|Traversable|ArrayAccess { ?> --EXPECTF-- -Fatal error: Type Traversable|ArrayAccess|iterable contains both iterable and Traversable, which is redundant in %s on line %d +Fatal error: Duplicate type Traversable is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt index c6b09494188..ca75ff705ed 100644 --- a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt +++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt @@ -8,4 +8,4 @@ function test(): iterable|array { ?> --EXPECTF-- -Fatal error: Type iterable|array contains both iterable and array, which is redundant in %s on line %d +Fatal error: Duplicate type array is redundant in %s on line %d diff --git a/Zend/tests/typehints/or_null.phpt b/Zend/tests/typehints/or_null.phpt index d7a2e230026..279a04aaff1 100644 --- a/Zend/tests/typehints/or_null.phpt +++ b/Zend/tests/typehints/or_null.phpt @@ -57,14 +57,6 @@ try { echo $e, PHP_EOL; } -function iterableF(?iterable $param) {} - -try { - iterableF(1); -} catch (\TypeError $e) { - echo $e, PHP_EOL; -} - function intF(?int $param) {} try { @@ -143,16 +135,6 @@ try { echo $e, PHP_EOL; } -function returnIterable(): ?iterable { - return 1; -} - -try { - returnIterable(); -} catch (\TypeError $e) { - echo $e, PHP_EOL; -} - function returnInt(): ?int { return new \StdClass; } @@ -199,15 +181,6 @@ try { echo $e, PHP_EOL; } -function returnMissingIterable(): ?iterable { -} - -try { - returnMissingIterable(); -} catch (\TypeError $e) { - echo $e, PHP_EOL; -} - function returnMissingInt(): ?int { } @@ -221,97 +194,85 @@ try { --EXPECTF-- TypeError: unloadedClass(): Argument #1 ($param) must be of type ?I\Dont\Exist, stdClass given, called in %s:%d Stack trace: -#0 %s(8): unloadedClass(Object(stdClass)) +#0 %s(%d): unloadedClass(Object(stdClass)) #1 {main} TypeError: loadedClass(): Argument #1 ($param) must be of type ?RealClass, stdClass given, called in %s:%d Stack trace: -#0 %s(20): loadedClass(Object(stdClass)) +#0 %s(%d): loadedClass(Object(stdClass)) #1 {main} TypeError: loadedInterface(): Argument #1 ($param) must be of type ?RealInterface, stdClass given, called in %s:%d Stack trace: -#0 %s(26): loadedInterface(Object(stdClass)) +#0 %s(%d): loadedInterface(Object(stdClass)) #1 {main} TypeError: unloadedClass(): Argument #1 ($param) must be of type ?I\Dont\Exist, int given, called in %s:%d Stack trace: -#0 %s(32): unloadedClass(1) +#0 %s(%d): unloadedClass(1) #1 {main} TypeError: loadedClass(): Argument #1 ($param) must be of type ?RealClass, int given, called in %s:%d Stack trace: -#0 %s(38): loadedClass(1) +#0 %s(%d): loadedClass(1) #1 {main} TypeError: loadedInterface(): Argument #1 ($param) must be of type ?RealInterface, int given, called in %s:%d Stack trace: -#0 %s(44): loadedInterface(1) +#0 %s(%d): loadedInterface(1) #1 {main} TypeError: callableF(): Argument #1 ($param) must be of type ?callable, int given, called in %s:%d Stack trace: -#0 %s(52): callableF(1) -#1 {main} -TypeError: iterableF(): Argument #1 ($param) must be of type ?iterable, int given, called in %s:%d -Stack trace: -#0 %s(60): iterableF(1) +#0 %s(%d): callableF(1) #1 {main} TypeError: intF(): Argument #1 ($param) must be of type ?int, stdClass given, called in %s:%d Stack trace: -#0 %s(68): intF(Object(stdClass)) +#0 %s(%d): intF(Object(stdClass)) #1 {main} TypeError: returnUnloadedClass(): Return value must be of type ?I\Dont\Exist, stdClass returned in %s:%d Stack trace: -#0 %s(78): returnUnloadedClass() +#0 %s(%d): returnUnloadedClass() #1 {main} TypeError: returnLoadedClass(): Return value must be of type ?RealClass, stdClass returned in %s:%d Stack trace: -#0 %s(88): returnLoadedClass() +#0 %s(%d): returnLoadedClass() #1 {main} TypeError: returnLoadedInterface(): Return value must be of type ?RealInterface, stdClass returned in %s:%d Stack trace: -#0 %s(98): returnLoadedInterface() +#0 %s(%d): returnLoadedInterface() #1 {main} TypeError: returnUnloadedClassScalar(): Return value must be of type ?I\Dont\Exist, int returned in %s:%d Stack trace: -#0 %s(108): returnUnloadedClassScalar() +#0 %s(%d): returnUnloadedClassScalar() #1 {main} TypeError: returnLoadedClassScalar(): Return value must be of type ?RealClass, int returned in %s:%d Stack trace: -#0 %s(118): returnLoadedClassScalar() +#0 %s(%d): returnLoadedClassScalar() #1 {main} TypeError: returnLoadedInterfaceScalar(): Return value must be of type ?RealInterface, int returned in %s:%d Stack trace: -#0 %s(128): returnLoadedInterfaceScalar() +#0 %s(%d): returnLoadedInterfaceScalar() #1 {main} TypeError: returnCallable(): Return value must be of type ?callable, int returned in %s:%d Stack trace: -#0 %s(138): returnCallable() -#1 {main} -TypeError: returnIterable(): Return value must be of type ?iterable, int returned in %s:%d -Stack trace: -#0 %s(148): returnIterable() +#0 %s(%d): returnCallable() #1 {main} TypeError: returnInt(): Return value must be of type ?int, stdClass returned in %s:%d Stack trace: -#0 %s(158): returnInt() +#0 %s(%d): returnInt() #1 {main} TypeError: returnMissingUnloadedClass(): Return value must be of type ?I\Dont\Exist, none returned in %s:%d Stack trace: -#0 %s(167): returnMissingUnloadedClass() +#0 %s(%d): returnMissingUnloadedClass() #1 {main} TypeError: returnMissingLoadedClass(): Return value must be of type ?RealClass, none returned in %s:%d Stack trace: -#0 %s(176): returnMissingLoadedClass() +#0 %s(%d): returnMissingLoadedClass() #1 {main} TypeError: returnMissingLoadedInterface(): Return value must be of type ?RealInterface, none returned in %s:%d Stack trace: -#0 %s(185): returnMissingLoadedInterface() +#0 %s(%d): returnMissingLoadedInterface() #1 {main} TypeError: returnMissingCallable(): Return value must be of type ?callable, none returned in %s:%d Stack trace: -#0 %s(194): returnMissingCallable() -#1 {main} -TypeError: returnMissingIterable(): Return value must be of type ?iterable, none returned in %s:%d -Stack trace: -#0 %s(203): returnMissingIterable() +#0 %s(%d): returnMissingCallable() #1 {main} TypeError: returnMissingInt(): Return value must be of type ?int, none returned in %s:%d Stack trace: -#0 %s(212): returnMissingInt() +#0 %s(%d): returnMissingInt() #1 {main} diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 9e903f16ce1..6d027037362 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2805,6 +2805,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend } } + /* Rebuild arginfos if parameter/property types and/or a return type are used */ if (reg_function->common.arg_info && (reg_function->common.fn_flags & (ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_HAS_TYPE_HINTS))) { /* convert "const char*" class type names into "zend_string*" */ @@ -2856,6 +2857,15 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend } } } + if (ZEND_TYPE_IS_ITERABLE_FALLBACK(new_arg_info[i].type)) { + /* Warning generated an extension load warning which is emitted for every test + zend_error(E_CORE_WARNING, "iterable type is now a compile time alias for array|Traversable," + " regenerate the argument info via the php-src gen_stub build script"); + */ + zend_type legacy_iterable = ZEND_TYPE_INIT_CLASS_CONST_MASK(ZSTR_KNOWN(ZEND_STR_TRAVERSABLE), + (new_arg_info[i].type.type_mask|MAY_BE_ARRAY)); + new_arg_info[i].type = legacy_iterable; + } } } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 6c06a0a01b9..0ec50dc5892 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1227,9 +1227,6 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop if (type_mask & MAY_BE_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), /* is_intersection */ false); - } if (type_mask & MAY_BE_OBJECT) { str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT), /* is_intersection */ false); } @@ -1289,7 +1286,7 @@ static void zend_mark_function_as_generator(void) /* {{{ */ if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { zend_type return_type = CG(active_op_array)->arg_info[-1].type; - bool valid_type = (ZEND_TYPE_FULL_MASK(return_type) & (MAY_BE_ITERABLE | MAY_BE_OBJECT)) != 0; + bool valid_type = (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_OBJECT) != 0; if (!valid_type) { zend_type *single_type; ZEND_TYPE_FOREACH(return_type, single_type) { @@ -6134,6 +6131,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast) zend_error_noreturn(E_COMPILE_ERROR, "Cannot use \"static\" when no class scope is active"); } + return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0); } else { zend_string *class_name = zend_ast_get_str(ast); @@ -6145,6 +6143,15 @@ static zend_type zend_compile_single_typename(zend_ast *ast) "Type declaration '%s' must be unqualified", ZSTR_VAL(zend_string_tolower(class_name))); } + + /* Transform iterable into a type union alias */ + if (type_code == IS_ITERABLE) { + /* Set iterable bit for BC compat during Reflection and string representation of type */ + zend_type iterable = (zend_type) ZEND_TYPE_INIT_CLASS_CONST_MASK(ZSTR_KNOWN(ZEND_STR_TRAVERSABLE), + (MAY_BE_ARRAY|_ZEND_TYPE_ITERABLE_BIT)); + return iterable; + } + return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0); } else { const char *correct_name; @@ -6184,19 +6191,6 @@ static zend_type zend_compile_single_typename(zend_ast *ast) } } -static bool zend_type_contains_traversable(zend_type type) { - zend_type *single_type; - ZEND_TYPE_FOREACH(type, single_type) { - if (ZEND_TYPE_HAS_NAME(*single_type) - && zend_string_equals_literal_ci(ZEND_TYPE_NAME(*single_type), "Traversable")) { - return 1; - } - } ZEND_TYPE_FOREACH_END(); - return 0; -} - -// TODO: Ideally we'd canonicalize "iterable" into "array|Traversable" and essentially -// treat it as a built-in type alias. static zend_type zend_compile_typename( zend_ast *ast, bool force_allow_null) /* {{{ */ { @@ -6290,6 +6284,14 @@ static zend_type zend_compile_typename( zend_ast *type_ast = list->child[i]; zend_type single_type = zend_compile_single_typename(type_ast); + /* An intersection of union types cannot exist so invalidate it + * Currently only can happen with iterable getting canonicalized to Traversable|array */ + if (ZEND_TYPE_IS_ITERABLE_FALLBACK(single_type)) { + zend_string *standard_type_str = zend_type_to_string(single_type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + zend_string_release_ex(standard_type_str, false); + } /* An intersection of standard types cannot exist so invalidate it */ if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { zend_string *standard_type_str = zend_type_to_string(single_type); @@ -6329,18 +6331,6 @@ static zend_type zend_compile_typename( } uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); - if ((type_mask & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) { - zend_string *type_str = zend_type_to_string(type); - zend_error_noreturn(E_COMPILE_ERROR, - "Type %s contains both iterable and array, which is redundant", ZSTR_VAL(type_str)); - } - - if ((type_mask & MAY_BE_ITERABLE) && zend_type_contains_traversable(type)) { - zend_string *type_str = zend_type_to_string(type); - zend_error_noreturn(E_COMPILE_ERROR, - "Type %s contains both iterable and Traversable, which is redundant", - ZSTR_VAL(type_str)); - } if (type_mask == MAY_BE_ANY && is_marked_nullable) { zend_error_noreturn(E_COMPILE_ERROR, "Type mixed cannot be marked as nullable since mixed already includes null"); @@ -6387,9 +6377,6 @@ static bool zend_is_valid_default_value(zend_type type, zval *value) convert_to_double(value); return 1; } - if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_ITERABLE) && Z_TYPE_P(value) == IS_ARRAY) { - return 1; - } return 0; } diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 7b55fa5e8ac..7e52348cb2f 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -909,9 +909,6 @@ static zend_always_inline bool i_zend_check_property_type(zend_property_info *in uint32_t type_mask = ZEND_TYPE_FULL_MASK(info->type); ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC))); - if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(property)) { - return 1; - } return zend_verify_scalar_type_hint(type_mask, property, strict, 0); } @@ -1048,9 +1045,6 @@ static zend_always_inline bool zend_check_type_slow( if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) { return 1; } - if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { - return 1; - } if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) { return 1; } @@ -2929,7 +2923,7 @@ static zend_always_inline bool check_type_array_assignable(zend_type type) { if (!ZEND_TYPE_IS_SET(type)) { return 1; } - return (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0; + return (ZEND_TYPE_FULL_MASK(type) & MAY_BE_ARRAY) != 0; } /* Checks whether an array can be assigned to the reference. Throws error if not assignable. */ @@ -3378,9 +3372,6 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( type_mask = ZEND_TYPE_FULL_MASK(type); ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC))); - if (type_mask & MAY_BE_ITERABLE) { - return zend_is_iterable(zv); - } /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */ if (strict) { diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 3921ee35fe0..1b0d57b2e7c 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -328,21 +328,6 @@ static bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) { return 0; } -static bool zend_type_contains_traversable(zend_type type) { - zend_type *single_type; - if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) { - return 1; - } - - ZEND_TYPE_FOREACH(type, single_type) { - if (ZEND_TYPE_HAS_NAME(*single_type) - && zend_string_equals_literal_ci(ZEND_TYPE_NAME(*single_type), "Traversable")) { - return 1; - } - } ZEND_TYPE_FOREACH_END(); - return 0; -} - static bool zend_type_permits_self( zend_type type, zend_class_entry *scope, zend_class_entry *self) { if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) { @@ -473,15 +458,6 @@ static inheritance_status zend_is_class_subtype_of_type( return INHERITANCE_SUCCESS; } } - if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) { - if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name); - if (!fe_ce) { - have_unresolved = 1; - } else if (unlinked_instanceof(fe_ce, zend_ce_traversable)) { - track_class_dependency(fe_ce, fe_class_name); - return INHERITANCE_SUCCESS; - } - } zend_type *single_type; @@ -567,18 +543,6 @@ static inheritance_status zend_perform_covariant_type_check( uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); uint32_t added_types = fe_type_mask & ~proto_type_mask; if (added_types) { - // TODO: Make "iterable" an alias of "array|Traversable" instead, - // so these special cases will be handled automatically. - if ((added_types & MAY_BE_ITERABLE) - && (proto_type_mask & MAY_BE_ARRAY) - && zend_type_contains_traversable(proto_type)) { - /* Replacing array|Traversable with iterable is okay */ - added_types &= ~MAY_BE_ITERABLE; - } - if ((added_types & MAY_BE_ARRAY) && (proto_type_mask & MAY_BE_ITERABLE)) { - /* Replacing iterable with array is okay */ - added_types &= ~MAY_BE_ARRAY; - } if ((added_types & MAY_BE_STATIC) && zend_type_permits_self(proto_type, proto_scope, fe_scope)) { /* Replacing type that accepts self with static is okay */ @@ -605,8 +569,7 @@ static inheritance_status zend_perform_covariant_type_check( * We still perform a class lookup for forward-compatibility reasons, * as we may have named types in the future that are not classes * (such as typedefs). */ - if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) { - bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0; + 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) { @@ -614,10 +577,8 @@ static inheritance_status zend_perform_covariant_type_check( } zend_class_entry *fe_ce = lookup_class(fe_scope, fe_class_name); if (fe_ce) { - if (any_class || unlinked_instanceof(fe_ce, zend_ce_traversable)) { - track_class_dependency(fe_ce, fe_class_name); - return INHERITANCE_SUCCESS; - } + track_class_dependency(fe_ce, fe_class_name); + return INHERITANCE_SUCCESS; } else { have_unresolved = true; } diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 1a1c0cd7a67..7c346077a3a 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -580,6 +580,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_FALSE, "false") \ _(ZEND_STR_NULL_LOWERCASE, "null") \ _(ZEND_STR_MIXED, "mixed") \ + _(ZEND_STR_TRAVERSABLE, "Traversable") \ _(ZEND_STR_SLEEP, "__sleep") \ _(ZEND_STR_WAKEUP, "__wakeup") \ _(ZEND_STR_CASES, "cases") \ diff --git a/Zend/zend_type_info.h b/Zend/zend_type_info.h index 93048a777bd..f9780181a4c 100644 --- a/Zend/zend_type_info.h +++ b/Zend/zend_type_info.h @@ -38,7 +38,6 @@ /* These are used in zend_type, but not for type inference. * They are allowed to overlap with types used during inference. */ #define MAY_BE_CALLABLE (1 << IS_CALLABLE) -#define MAY_BE_ITERABLE (1 << IS_ITERABLE) #define MAY_BE_VOID (1 << IS_VOID) #define MAY_BE_NEVER (1 << IS_NEVER) #define MAY_BE_STATIC (1 << IS_STATIC) diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 151afadec22..908a2c769a9 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -144,7 +144,8 @@ typedef struct { #define _ZEND_TYPE_NAME_BIT (1u << 24) #define _ZEND_TYPE_LIST_BIT (1u << 22) #define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT) -/* TODO: bit 21 is not used */ +/* For BC behaviour with iterable type */ +#define _ZEND_TYPE_ITERABLE_BIT (1u << 21) /* Whether the type list is arena allocated */ #define _ZEND_TYPE_ARENA_BIT (1u << 20) /* Whether the type list is an intersection type */ @@ -170,6 +171,9 @@ typedef struct { #define ZEND_TYPE_HAS_LIST(t) \ ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) +#define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \ + ((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0) + #define ZEND_TYPE_IS_INTERSECTION(t) \ ((((t).type_mask) & _ZEND_TYPE_INTERSECTION_BIT) != 0) @@ -263,7 +267,7 @@ typedef struct { { NULL, (_type_mask) } #define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ - ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code)))) \ + ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ( (code) == IS_ITERABLE ? _ZEND_TYPE_ITERABLE_BIT : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code))))) \ | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags)) #define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \ diff --git a/build/gen_stub.php b/build/gen_stub.php index 320fe087ab8..647426a2c65 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -219,7 +219,6 @@ class SimpleType { case "float": case "string": case "callable": - case "iterable": case "object": case "resource": case "mixed": @@ -231,6 +230,8 @@ class SimpleType { return ArrayType::createGenericArray(); case "self": throw new Exception('The exact class name must be used instead of "self"'); + case "iterable": + throw new Exception('This should not happen'); } $matches = []; @@ -369,8 +370,6 @@ class SimpleType { return "IS_VOID"; case "callable": return "IS_CALLABLE"; - case "iterable": - return "IS_ITERABLE"; case "mixed": return "IS_MIXED"; case "static": @@ -408,8 +407,6 @@ class SimpleType { return "MAY_BE_OBJECT"; case "callable": return "MAY_BE_CALLABLE"; - case "iterable": - return "MAY_BE_ITERABLE"; case "mixed": return "MAY_BE_ANY"; case "void": @@ -509,18 +506,32 @@ class Type { public static function fromNode(Node $node): Type { if ($node instanceof Node\UnionType) { - return new Type(array_map(['SimpleType', 'fromNode'], $node->types)); + $nestedTypeObjects = array_map(['Type', 'fromNode'], $node->types); + $types = []; + foreach ($nestedTypeObjects as $typeObject) { + array_push($types, ...$typeObject->types); + } + return new Type($types); } if ($node instanceof Node\NullableType) { return new Type( [ - SimpleType::fromNode($node->type), + ...Type::fromNode($node->type)->types, SimpleType::null(), ] ); } + if ($node instanceof Node\Identifier && $node->toLowerString() === "iterable") { + return new Type( + [ + SimpleType::fromString("Traversable"), + ArrayType::createGenericArray(), + ] + ); + } + return new Type([SimpleType::fromNode($node)]); } diff --git a/ext/opcache/jit/zend_jit_arm64.dasc b/ext/opcache/jit/zend_jit_arm64.dasc index ec50f6edd8d..112f007f3d1 100644 --- a/ext/opcache/jit/zend_jit_arm64.dasc +++ b/ext/opcache/jit/zend_jit_arm64.dasc @@ -12232,7 +12232,7 @@ static int zend_jit_fetch_obj(dasm_State **Dst, uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS; if (flags == ZEND_FETCH_DIM_WRITE) { - if ((ZEND_TYPE_FULL_MASK(prop_info->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) { + if ((ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_ARRAY) == 0) { if (!type_loaded) { type_loaded = 1; | MEM_ACCESS_32_WITH_UOFFSET ldr, REG2w, FCARG1x, (prop_info->offset + offsetof(zval,u1.type_info)), TMP1 diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 9ec73892d54..955d8fc1cfc 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1949,7 +1949,7 @@ static zend_always_inline bool check_type_array_assignable(zend_type type) { if (!ZEND_TYPE_IS_SET(type)) { return 1; } - return (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0; + return (ZEND_TYPE_FULL_MASK(type) & MAY_BE_ARRAY) != 0; } static zend_property_info *zend_object_fetch_property_type_info( @@ -2094,7 +2094,7 @@ static void ZEND_FASTCALL zend_jit_check_array_promotion(zval *val, zend_propert if ((Z_TYPE_P(val) <= IS_FALSE || (Z_ISREF_P(val) && Z_TYPE_P(Z_REFVAL_P(val)) <= IS_FALSE)) && ZEND_TYPE_IS_SET(prop->type) - && (ZEND_TYPE_FULL_MASK(prop->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) { + && (ZEND_TYPE_FULL_MASK(prop->type) & MAY_BE_ARRAY) == 0) { zend_string *type_str = zend_type_to_string(prop->type); zend_type_error( "Cannot auto-initialize an array inside property %s::$%s of type %s", diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index c4a715b306e..c33a4912d7b 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -12962,7 +12962,7 @@ static int zend_jit_fetch_obj(dasm_State **Dst, uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS; if (flags == ZEND_FETCH_DIM_WRITE) { - if ((ZEND_TYPE_FULL_MASK(prop_info->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) { + if ((ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_ARRAY) == 0) { if (!type_loaded) { type_loaded = 1; | mov edx, dword [FCARG1a + prop_info->offset + 8] diff --git a/ext/opcache/tests/iterable_type_optimization.phpt b/ext/opcache/tests/iterable_type_optimization.phpt index 277df8f374c..340fcdea228 100644 --- a/ext/opcache/tests/iterable_type_optimization.phpt +++ b/ext/opcache/tests/iterable_type_optimization.phpt @@ -12,7 +12,7 @@ test(new stdClass); ?> --EXPECTF-- -Fatal error: Uncaught TypeError: test(): Return value must be of type iterable, stdClass returned in %s:%d +Fatal error: Uncaught TypeError: test(): Return value must be of type Traversable|array, stdClass returned in %s:%d Stack trace: #0 %s(%d): test(Object(stdClass)) #1 {main} diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8c7118bda6d..5c9bcc731b4 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1330,6 +1330,7 @@ typedef enum { } reflection_type_kind; /* For backwards compatibility reasons, we need to return T|null style unions + * and transformation from iterable to Traversable|array * as a ReflectionNamedType. Here we determine what counts as a union type and * what doesn't. */ static reflection_type_kind get_type_kind(zend_type type) { @@ -1344,6 +1345,10 @@ static reflection_type_kind get_type_kind(zend_type type) { } if (ZEND_TYPE_IS_COMPLEX(type)) { + /* BC support for 'iterable' type */ + if (UNEXPECTED(ZEND_TYPE_IS_ITERABLE_FALLBACK(type))) { + return NAMED_TYPE; + } if (type_mask_without_null != 0) { return UNION_TYPE; } @@ -2726,6 +2731,11 @@ ZEND_METHOD(ReflectionParameter, isArray) } GET_REFLECTION_OBJECT_PTR(param); + /* BC For iterable */ + if (ZEND_TYPE_IS_ITERABLE_FALLBACK(param->arg_info->type)) { + RETURN_FALSE; + } + type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(param->arg_info->type); RETVAL_BOOL(type_mask == MAY_BE_ARRAY); } @@ -3006,9 +3016,21 @@ ZEND_METHOD(ReflectionType, allowsNull) } /* }}} */ +/* For BC with iterable for named types */ +static zend_string *zend_named_reflection_type_to_string(zend_type type) { + if (ZEND_TYPE_IS_ITERABLE_FALLBACK(type)) { + zend_string *iterable = ZSTR_KNOWN(ZEND_STR_ITERABLE); + if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_NULL) { + return zend_string_concat2("?", strlen("?"), ZSTR_VAL(iterable), ZSTR_LEN(iterable)); + } + return iterable; + } + return zend_type_to_string(type); +} + static zend_string *zend_type_to_string_without_null(zend_type type) { ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; - return zend_type_to_string(type); + return zend_named_reflection_type_to_string(type); } /* {{{ Return the text of the type hint */ @@ -3022,7 +3044,7 @@ ZEND_METHOD(ReflectionType, __toString) } GET_REFLECTION_OBJECT_PTR(param); - RETURN_STR(zend_type_to_string(param->type)); + RETURN_STR(zend_named_reflection_type_to_string(param->type)); } /* }}} */ @@ -3040,7 +3062,7 @@ ZEND_METHOD(ReflectionNamedType, getName) if (param->legacy_behavior) { RETURN_STR(zend_type_to_string_without_null(param->type)); } - RETURN_STR(zend_type_to_string(param->type)); + RETURN_STR(zend_named_reflection_type_to_string(param->type)); } /* }}} */ @@ -3055,6 +3077,10 @@ ZEND_METHOD(ReflectionNamedType, isBuiltin) } GET_REFLECTION_OBJECT_PTR(param); + if (ZEND_TYPE_IS_ITERABLE_FALLBACK(param->type)) { + RETURN_TRUE; + } + /* Treat "static" as a class type for the purposes of reflection. */ RETVAL_BOOL(ZEND_TYPE_IS_ONLY_MASK(param->type) && !(ZEND_TYPE_FULL_MASK(param->type) & MAY_BE_STATIC)); @@ -3063,6 +3089,11 @@ ZEND_METHOD(ReflectionNamedType, isBuiltin) static void append_type(zval *return_value, zend_type type) { zval reflection_type; + /* Drop iterable BC bit for type list */ + if (ZEND_TYPE_IS_ITERABLE_FALLBACK(type)) { + ZEND_TYPE_FULL_MASK(type) &= ~_ZEND_TYPE_ITERABLE_BIT; + } + reflection_type_factory(type, &reflection_type, 0); zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &reflection_type); } @@ -3103,9 +3134,6 @@ ZEND_METHOD(ReflectionUnionType, getTypes) if (type_mask & MAY_BE_CALLABLE) { append_type_mask(return_value, MAY_BE_CALLABLE); } - if (type_mask & MAY_BE_ITERABLE) { - append_type_mask(return_value, MAY_BE_ITERABLE); - } if (type_mask & MAY_BE_OBJECT) { append_type_mask(return_value, MAY_BE_OBJECT); } diff --git a/ext/reflection/tests/ReflectionType_001.phpt b/ext/reflection/tests/ReflectionType_001.phpt index d0f327d0466..b4baf5355c0 100644 --- a/ext/reflection/tests/ReflectionType_001.phpt +++ b/ext/reflection/tests/ReflectionType_001.phpt @@ -80,7 +80,6 @@ class PropTypeTest { public int $int; public string $string; public array $arr; - public iterable $iterable; public stdClass $std; public OtherThing $other; public $mixed; @@ -216,7 +215,6 @@ string(4) "Test" public int $int; public string $string; public array $arr; -public iterable $iterable; public stdClass $std; public OtherThing $other; public $mixed; diff --git a/ext/reflection/tests/ReflectionType_possible_types.phpt b/ext/reflection/tests/ReflectionType_possible_types.phpt index dd6d39300b5..9162b71df98 100644 --- a/ext/reflection/tests/ReflectionType_possible_types.phpt +++ b/ext/reflection/tests/ReflectionType_possible_types.phpt @@ -11,7 +11,6 @@ $functions = [ function(): bool {}, function(): array {}, function(): callable {}, - function(): iterable {}, function(): null {}, function(): false {}, function(): StdClass {} @@ -31,7 +30,6 @@ string(6) "string" string(4) "bool" string(5) "array" string(8) "callable" -string(8) "iterable" string(4) "null" string(5) "false" string(8) "StdClass" diff --git a/ext/reflection/tests/bug72661.phpt b/ext/reflection/tests/bug72661.phpt deleted file mode 100644 index b1cb764beb6..00000000000 --- a/ext/reflection/tests/bug72661.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -Bug #72661 (ReflectionType::__toString crashes with iterable) ---FILE-- -getType()); -?> ---EXPECT-- -string(8) "iterable" diff --git a/ext/reflection/tests/iterable_Reflection.phpt b/ext/reflection/tests/iterable_Reflection.phpt new file mode 100644 index 00000000000..0a8a30a6dd0 --- /dev/null +++ b/ext/reflection/tests/iterable_Reflection.phpt @@ -0,0 +1,98 @@ +--TEST-- +iterable Type in Reflection +--FILE-- +getType(); +var_dump($paramType::class); +var_dump($paramType); +var_dump($paramType->getName()); +var_dump((string) $paramType); +var_dump($paramType->isBuiltin()); + +$reflectionFunc = new ReflectionFunction($function); +$returnType = $reflectionFunc->getReturnType(); +var_dump($returnType::class); +var_dump($returnType); +var_dump($returnType->getName()); +var_dump((string) $returnType); +var_dump($returnType->isBuiltin()); + +class PropIterableTypeTest { + public iterable $iterable; + public ?iterable $nullableIterable; + public array $control; + public ?array $nullableControl; +} + +$reflector = new ReflectionClass(PropIterableTypeTest::class); + +[$property, $nullable, $control, $nullableControl] = $reflector->getProperties(); +$iterableType = $property->getType(); +var_dump($iterableType::class); +var_dump($iterableType); +var_dump($iterableType->getName()); +var_dump((string) $iterableType); +var_dump($iterableType->isBuiltin()); + +$nullableIterableType = $nullable->getType(); +var_dump($nullableIterableType::class); +var_dump($nullableIterableType); +var_dump($nullableIterableType->getName()); +var_dump((string) $nullableIterableType); +var_dump($nullableIterableType->isBuiltin()); + +$controlType = $control->getType(); +var_dump($controlType::class); +var_dump($controlType); +var_dump($controlType->getName()); +var_dump((string) $controlType); +var_dump($controlType->isBuiltin()); + +$nullableControlType = $nullableControl->getType(); +var_dump($nullableControlType::class); +var_dump($nullableControlType); +var_dump($nullableControlType->getName()); +var_dump((string) $nullableControlType); +var_dump($nullableControlType->isBuiltin()); + +?> +--EXPECTF-- +string(19) "ReflectionNamedType" +object(ReflectionNamedType)#%d (0) { +} +string(8) "iterable" +string(8) "iterable" +bool(true) +string(19) "ReflectionNamedType" +object(ReflectionNamedType)#%d (0) { +} +string(8) "iterable" +string(8) "iterable" +bool(true) +string(19) "ReflectionNamedType" +object(ReflectionNamedType)#%d (0) { +} +string(8) "iterable" +string(8) "iterable" +bool(true) +string(19) "ReflectionNamedType" +object(ReflectionNamedType)#%d (0) { +} +string(8) "iterable" +string(9) "?iterable" +bool(true) +string(19) "ReflectionNamedType" +object(ReflectionNamedType)#%d (0) { +} +string(5) "array" +string(5) "array" +bool(true) +string(19) "ReflectionNamedType" +object(ReflectionNamedType)#%d (0) { +} +string(5) "array" +string(6) "?array" +bool(true) diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/union_types.phpt index a3ac53b54ab..e670567712f 100644 --- a/ext/reflection/tests/union_types.phpt +++ b/ext/reflection/tests/union_types.phpt @@ -75,13 +75,16 @@ Allows null: true Name: null String: null Allows Null: true -Type X|iterable|bool: +Type X|Traversable|array|bool: Allows null: false Name: X String: X Allows Null: false - Name: iterable - String: iterable + Name: Traversable + String: Traversable + Allows Null: false + Name: array + String: array Allows Null: false Name: bool String: bool diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 37bbc4547f8..2fbabe7ef64 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -314,6 +314,29 @@ static ZEND_FUNCTION(zend_iterable) ZEND_PARSE_PARAMETERS_END(); } +static ZEND_FUNCTION(zend_iterable_legacy) +{ + zval *arg1, *arg2; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ITERABLE(arg1) + Z_PARAM_OPTIONAL + Z_PARAM_ITERABLE_OR_NULL(arg2) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_COPY(arg1); +} + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_iterable_legacy, 0, 1, IS_ITERABLE, 0) + ZEND_ARG_TYPE_INFO(0, arg1, IS_ITERABLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg2, IS_ITERABLE, 1, "null") +ZEND_END_ARG_INFO() + +static const zend_function_entry ext_function_legacy[] = { + ZEND_FE(zend_iterable_legacy, arginfo_zend_iterable_legacy) + ZEND_FE_END +}; + /* Call a method on a class or object using zend_call_method() */ static ZEND_FUNCTION(zend_call_method) { @@ -628,6 +651,8 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_string_enum = register_class_ZendTestStringEnum(); zend_test_int_enum = register_class_ZendTestIntEnum(); + zend_register_functions(NULL, ext_function_legacy, NULL, EG(current_module)->type); + // Loading via dl() not supported with the observer API if (type != MODULE_TEMPORARY) { REGISTER_INI_ENTRIES(); diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 0c575cf002a..d682b537550 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -53,8 +53,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_zend_string_or_stdclass_or_n ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_iterable, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, arg1, IS_ITERABLE, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg2, IS_ITERABLE, 1, "null") + ZEND_ARG_OBJ_TYPE_MASK(0, arg1, Traversable, MAY_BE_ARRAY, NULL) + ZEND_ARG_OBJ_TYPE_MASK(0, arg2, Traversable, MAY_BE_ARRAY|MAY_BE_NULL, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_weakmap_attach, 0, 2, _IS_BOOL, 0) diff --git a/ext/zend_test/tests/zend_legacy_iterable.phpt b/ext/zend_test/tests/zend_legacy_iterable.phpt new file mode 100644 index 00000000000..73bbb269c26 --- /dev/null +++ b/ext/zend_test/tests/zend_legacy_iterable.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test that legacy IS_ITERABLE arg info type generates a notice +--EXTENSIONS-- +zend_test +--FILE-- + +==DONE== +--EXPECT-- +array(0) { +} +array(0) { +} +object(Generator)#1 (0) { +} +object(Generator)#1 (0) { +} +==DONE== diff --git a/ext/zend_test/tests/zend_weakmap.phpt b/ext/zend_test/tests/zend_weakmap.phpt index 99a2075c047..209b8bc0e09 100644 --- a/ext/zend_test/tests/zend_weakmap.phpt +++ b/ext/zend_test/tests/zend_weakmap.phpt @@ -51,4 +51,4 @@ array(1) { [%s]=> object(stdClass)#2 (0) { } -} \ No newline at end of file +}