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 +}