diff options
-rw-r--r-- | Zend/tests/return_types/generators002.phpt | 2 | ||||
-rw-r--r-- | Zend/tests/type_declarations/iterable_001.phpt | 48 | ||||
-rw-r--r-- | Zend/tests/type_declarations/iterable_002.phpt | 21 | ||||
-rw-r--r-- | Zend/tests/type_declarations/iterable_003.phpt | 32 | ||||
-rw-r--r-- | Zend/tests/type_declarations/iterable_004.phpt | 25 | ||||
-rw-r--r-- | Zend/tests/type_declarations/iterable_005.phpt | 33 | ||||
-rw-r--r-- | Zend/zend_API.c | 16 | ||||
-rw-r--r-- | Zend/zend_API.h | 2 | ||||
-rw-r--r-- | Zend/zend_compile.c | 28 | ||||
-rw-r--r-- | Zend/zend_execute.c | 21 | ||||
-rw-r--r-- | Zend/zend_inheritance.c | 40 | ||||
-rw-r--r-- | Zend/zend_types.h | 3 | ||||
-rw-r--r-- | Zend/zend_vm_def.h | 1 | ||||
-rw-r--r-- | Zend/zend_vm_execute.h | 5 | ||||
-rw-r--r-- | ext/opcache/Optimizer/zend_inference.c | 2 | ||||
-rw-r--r-- | ext/standard/basic_functions.c | 5 | ||||
-rw-r--r-- | ext/standard/php_type.h | 1 | ||||
-rw-r--r-- | ext/standard/tests/general_functions/is_iterable.phpt | 24 | ||||
-rw-r--r-- | ext/standard/type.c | 14 |
19 files changed, 309 insertions, 14 deletions
diff --git a/Zend/tests/return_types/generators002.phpt b/Zend/tests/return_types/generators002.phpt index f7dbfda69b..519c97a962 100644 --- a/Zend/tests/return_types/generators002.phpt +++ b/Zend/tests/return_types/generators002.phpt @@ -8,4 +8,4 @@ function test1() : StdClass { } --EXPECTF-- -Fatal error: Generators may only declare a return type of Generator, Iterator or Traversable, StdClass is not permitted in %s on line %d +Fatal error: Generators may only declare a return type of Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d diff --git a/Zend/tests/type_declarations/iterable_001.phpt b/Zend/tests/type_declarations/iterable_001.phpt new file mode 100644 index 0000000000..f0c8bba610 --- /dev/null +++ b/Zend/tests/type_declarations/iterable_001.phpt @@ -0,0 +1,48 @@ +--TEST-- +iterable type#001 +--FILE-- +<?php + +function test(iterable $iterable) { + var_dump($iterable); +} + +function gen() { + yield 1; + yield 2; + yield 3; +}; + +test([1, 2, 3]); +test(gen()); +test(new ArrayIterator([1, 2, 3])); + +try { + test(1); +} catch (Throwable $e) { + echo $e->getMessage(); +} + +--EXPECTF-- +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} +object(Generator)#1 (0) { +} +object(ArrayIterator)#1 (1) { + ["storage":"ArrayIterator":private]=> + array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + } +} +Argument 1 passed to test() must be iterable, integer given, called in %s on line %d diff --git a/Zend/tests/type_declarations/iterable_002.phpt b/Zend/tests/type_declarations/iterable_002.phpt new file mode 100644 index 0000000000..74f6d83f1e --- /dev/null +++ b/Zend/tests/type_declarations/iterable_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +iterable type#002 - Default values +--FILE-- +<?php + +function foo(iterable $iterable = null) { + // Null should be allowed as a default value +} + +function bar(iterable $iterable = []) { + // Array should be allowed as a default value +} + +function baz(iterable $iterable = 1) { + // No other values should be allowed as defaults +} + +?> +--EXPECTF-- + +Fatal error: Default value for parameters with iterable type can only be an array or NULL in %s on line %d diff --git a/Zend/tests/type_declarations/iterable_003.phpt b/Zend/tests/type_declarations/iterable_003.phpt new file mode 100644 index 0000000000..8c91c993d0 --- /dev/null +++ b/Zend/tests/type_declarations/iterable_003.phpt @@ -0,0 +1,32 @@ +--TEST-- +iterable type#003 - Return types +--FILE-- +<?php + +function foo(): iterable { + return []; +} +function bar(): iterable { + return (function () { yield; })(); +} + +function baz(): iterable { + return 1; +} + +var_dump(foo()); +var_dump(bar()); + +try { + baz(); +} catch (Throwable $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +array(0) { +} +object(Generator)#2 (0) { +} +Return value of baz() must be iterable, integer returned diff --git a/Zend/tests/type_declarations/iterable_004.phpt b/Zend/tests/type_declarations/iterable_004.phpt new file mode 100644 index 0000000000..47e79fa6b3 --- /dev/null +++ b/Zend/tests/type_declarations/iterable_004.phpt @@ -0,0 +1,25 @@ +--TEST-- +iterable type#004 - Parameter covariance +--FILE-- +<?php + +class Foo { + function testArray(array $array) {} + + function testTraversable(Traversable $traversable) {} + + function testScalar(int $int) {} +} + +class Bar extends Foo { + function testArray(iterable $iterable) {} + + function testTraversable(iterable $iterable) {} + + function testScalar(iterable $iterable) {} +} + +?> +--EXPECTF-- + +Warning: Declaration of Bar::testScalar(iterable $iterable) should 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_005.phpt new file mode 100644 index 0000000000..9c0584b51a --- /dev/null +++ b/Zend/tests/type_declarations/iterable_005.phpt @@ -0,0 +1,33 @@ +--TEST-- +iterable type#005 - Return type covariance +--FILE-- +<?php + +class Test { + function method(): iterable { + return []; + } +} + +class TestArray extends Test { + function method(): array { + return []; + } +} + +class TestTraversable extends Test { + function method(): Traversable { + return new ArrayIterator([]); + } +} + +class TestScalar extends Test { + function method(): int { + return 1; + } +} + +?> +--EXPECTF-- + +Fatal error: Declaration of TestScalar::method(): int must be compatible with Test::method(): iterable in %s on line %d diff --git a/Zend/zend_API.c b/Zend/zend_API.c index ae52921ba6..096b494e30 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -27,6 +27,7 @@ #include "zend_modules.h" #include "zend_extensions.h" #include "zend_constants.h" +#include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_closures.h" #include "zend_inheritance.h" @@ -182,6 +183,8 @@ ZEND_API char *zend_get_type_by_const(int type) /* {{{ */ return "null"; case IS_CALLABLE: return "callable"; + case IS_ITERABLE: + return "iterable"; case IS_ARRAY: return "array"; case IS_VOID: @@ -4203,6 +4206,19 @@ ZEND_API const char *zend_get_object_type(const zend_class_entry *ce) /* {{{ */ } /* }}} */ +ZEND_API zend_bool zend_is_iterable(zval *iterable) /* {{{ */ +{ + switch (Z_TYPE_P(iterable)) { + case IS_ARRAY: + return 1; + case IS_OBJECT: + return instanceof_function(Z_OBJCE_P(iterable), zend_ce_traversable); + default: + return 0; + } +} +/* }}} */ + /* * Local variables: * tab-width: 4 diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 77127afa4b..5f68b680bc 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -554,6 +554,8 @@ ZEND_API zend_string *zend_resolve_method_name(zend_class_entry *ce, zend_functi ZEND_API const char *zend_get_object_type(const zend_class_entry *ce); +ZEND_API zend_bool zend_is_iterable(zval *iterable); + #define add_method(arg, key, method) add_assoc_function((arg), (key), (method)) ZEND_API ZEND_FUNCTION(display_disabled_function); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 46d27f2ad4..03ab7d177e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -160,6 +160,7 @@ static const struct reserved_class_name reserved_class_names[] = { {ZEND_STRL("string")}, {ZEND_STRL("true")}, {ZEND_STRL("void")}, + {ZEND_STRL("iterable")}, {NULL, 0} }; @@ -204,6 +205,7 @@ static const builtin_type_info builtin_types[] = { {ZEND_STRL("string"), IS_STRING}, {ZEND_STRL("bool"), _IS_BOOL}, {ZEND_STRL("void"), IS_VOID}, + {ZEND_STRL("iterable"), IS_ITERABLE}, {NULL, 0, IS_UNDEF} }; @@ -1254,17 +1256,20 @@ static void zend_mark_function_as_generator() /* {{{ */ } if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { - const char *msg = "Generators may only declare a return type of Generator, Iterator or Traversable, %s is not permitted"; zend_arg_info return_info = CG(active_op_array)->arg_info[-1]; - if (!return_info.class_name) { - zend_error_noreturn(E_COMPILE_ERROR, msg, zend_get_type_by_const(return_info.type_hint)); - } + if (return_info.type_hint != IS_ITERABLE) { + const char *msg = "Generators may only declare a return type of Generator, Iterator, Traversable, or iterable, %s is not permitted"; + + if (!return_info.class_name) { + zend_error_noreturn(E_COMPILE_ERROR, msg, zend_get_type_by_const(return_info.type_hint)); + } - if (!zend_string_equals_literal_ci(return_info.class_name, "Traversable") - && !zend_string_equals_literal_ci(return_info.class_name, "Iterator") - && !zend_string_equals_literal_ci(return_info.class_name, "Generator")) { - zend_error_noreturn(E_COMPILE_ERROR, msg, ZSTR_VAL(return_info.class_name)); + if (!zend_string_equals_literal_ci(return_info.class_name, "Traversable") + && !zend_string_equals_literal_ci(return_info.class_name, "Iterator") + && !zend_string_equals_literal_ci(return_info.class_name, "Generator")) { + zend_error_noreturn(E_COMPILE_ERROR, msg, ZSTR_VAL(return_info.class_name)); + } } } @@ -5117,6 +5122,13 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ "with a float type can only be float, integer, or NULL"); } break; + + case IS_ITERABLE: + if (Z_TYPE(default_node.u.constant) != IS_ARRAY) { + zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters " + "with iterable type can only be an array or NULL"); + } + break; default: if (!ZEND_SAME_FAKE_TYPE(arg_info->type_hint, Z_TYPE(default_node.u.constant))) { diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 4a69cabded..7c8621fe64 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -758,6 +758,11 @@ static int zend_verify_internal_arg_type(zend_function *zf, uint32_t arg_num, zv zend_verify_arg_error(zf, arg_num, "be callable", "", zend_zval_type_name(arg), ""); return 0; } + } else if (cur_arg_info->type_hint == IS_ITERABLE) { + if (!zend_is_iterable(arg)) { + zend_verify_arg_error(zf, arg_num, "be iterable", "", zend_zval_type_name(arg), ""); + return 0; + } } else if (cur_arg_info->type_hint == _IS_BOOL && EXPECTED(Z_TYPE_P(arg) == IS_FALSE || Z_TYPE_P(arg) == IS_TRUE)) { /* pass */ @@ -849,6 +854,11 @@ static zend_always_inline int zend_verify_arg_type(zend_function *zf, uint32_t a zend_verify_arg_error(zf, arg_num, "be callable", "", zend_zval_type_name(arg), ""); return 0; } + } else if (cur_arg_info->type_hint == IS_ITERABLE) { + if (!zend_is_iterable(arg)) { + zend_verify_arg_error(zf, arg_num, "be iterable", "", zend_zval_type_name(arg), ""); + return 0; + } } else if (cur_arg_info->type_hint == _IS_BOOL && EXPECTED(Z_TYPE_P(arg) == IS_FALSE || Z_TYPE_P(arg) == IS_TRUE)) { /* pass */ @@ -966,6 +976,11 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret) zend_verify_internal_return_error(zf, "be callable", "", zend_zval_type_name(ret), ""); return 0; } + } else if (ret_info->type_hint == IS_ITERABLE) { + if (!zend_is_iterable(ret) && (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null)) { + zend_verify_internal_return_error(zf, "be iterable", "", zend_zval_type_name(ret), ""); + return 0; + } } else if (ret_info->type_hint == _IS_BOOL && EXPECTED(Z_TYPE_P(ret) == IS_FALSE || Z_TYPE_P(ret) == IS_TRUE)) { /* pass */ @@ -1028,6 +1043,10 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * if (!zend_is_callable(ret, IS_CALLABLE_CHECK_SILENT, NULL)) { zend_verify_return_error(zf, "be callable", "", zend_zval_type_name(ret), ""); } + } else if (ret_info->type_hint == IS_ITERABLE) { + if (!zend_is_iterable(ret)) { + zend_verify_return_error(zf, "be iterable", "", zend_zval_type_name(ret), ""); + } } else if (ret_info->type_hint == _IS_BOOL && EXPECTED(Z_TYPE_P(ret) == IS_FALSE || Z_TYPE_P(ret) == IS_TRUE)) { /* pass */ @@ -1069,6 +1088,8 @@ static ZEND_COLD int zend_verify_missing_return_type(zend_function *zf, void **c return 0; } else if (ret_info->type_hint == IS_CALLABLE) { zend_verify_return_error(zf, "be callable", "", "none", ""); + } else if (ret_info->type_hint == IS_ITERABLE) { + zend_verify_return_error(zf, "be iterable", "", "none", ""); } else { zend_verify_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), "none", ""); } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 44abfb6ffb..7dc6bfc1f7 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -167,6 +167,20 @@ char *zend_visibility_string(uint32_t fn_flags) /* {{{ */ } /* }}} */ +static zend_always_inline zend_bool zend_iterable_compatibility_check(zend_arg_info *arg_info) /* {{{ */ +{ + if (arg_info->type_hint == IS_ARRAY) { + return 1; + } + + if (arg_info->class_name && zend_string_equals_literal_ci(arg_info->class_name, "Traversable")) { + return 1; + } + + return 0; +} +/* }}} */ + static int zend_do_perform_type_hint_check(const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ { if (ZEND_LOG_XOR(fe_arg_info->class_name, proto_arg_info->class_name)) { @@ -314,9 +328,18 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c } else { proto_arg_info = &proto->common.arg_info[proto->common.num_args]; } - + if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) { - return 0; + switch (fe_arg_info->type_hint) { + case IS_ITERABLE: + if (!zend_iterable_compatibility_check(proto_arg_info)) { + return 0; + } + break; + + default: + return 0; + } } // This introduces BC break described at https://bugs.php.net/bug.php?id=72119 @@ -338,9 +361,18 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c if (!(fe->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { return 0; } - + if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) { - return 0; + switch (proto->common.arg_info[-1].type_hint) { + case IS_ITERABLE: + if (!zend_iterable_compatibility_check(fe->common.arg_info - 1)) { + return 0; + } + break; + + default: + return 0; + } } if (fe->common.arg_info[-1].allow_null && !proto->common.arg_info[-1].allow_null) { diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 45c2ebdf70..95c4ef6e54 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -319,12 +319,13 @@ struct _zend_ast_ref { /* fake types */ #define _IS_BOOL 13 #define IS_CALLABLE 14 +#define IS_ITERABLE 19 #define IS_VOID 18 /* internal types */ #define IS_INDIRECT 15 #define IS_PTR 17 -#define _IS_ERROR 19 +#define _IS_ERROR 20 static zend_always_inline zend_uchar zval_get_type(const zval* pz) { return pz->u1.v.type; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 927b372d48..f8082f91cf 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3859,6 +3859,7 @@ ZEND_VM_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV, UNUSED) if (UNEXPECTED(!ret_info->class_name && ret_info->type_hint != IS_CALLABLE + && ret_info->type_hint != IS_ITERABLE && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index f2ae082f91..a495b59000 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7510,6 +7510,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CONST_ if (UNEXPECTED(!ret_info->class_name && ret_info->type_hint != IS_CALLABLE + && ret_info->type_hint != IS_ITERABLE && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -14014,6 +14015,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN if (UNEXPECTED(!ret_info->class_name && ret_info->type_hint != IS_CALLABLE + && ret_info->type_hint != IS_ITERABLE && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -21555,6 +21557,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN if (UNEXPECTED(!ret_info->class_name && ret_info->type_hint != IS_CALLABLE + && ret_info->type_hint != IS_ITERABLE && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -30171,6 +30174,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED if (UNEXPECTED(!ret_info->class_name && ret_info->type_hint != IS_CALLABLE + && ret_info->type_hint != IS_ITERABLE && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -42242,6 +42246,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU if (UNEXPECTED(!ret_info->class_name && ret_info->type_hint != IS_CALLABLE + && ret_info->type_hint != IS_ITERABLE && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index c33b5459f5..4760076f05 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -2378,6 +2378,8 @@ static uint32_t zend_fetch_arg_info(const zend_script *script, zend_arg_info *ar tmp |= MAY_BE_NULL; } else if (arg_info->type_hint == IS_CALLABLE) { tmp |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + } else if (arg_info->type_hint == IS_ITERABLE) { + tmp |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } else if (arg_info->type_hint == IS_ARRAY) { tmp |= MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } else if (arg_info->type_hint == _IS_BOOL) { diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 22bc77f77f..55e1f7e2eb 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -2563,6 +2563,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_is_callable, 0, 0, 1) ZEND_ARG_INFO(0, syntax_only) ZEND_ARG_INFO(1, callable_name) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_is_iterable, 0, 0, 1) + ZEND_ARG_INFO(0, var) +ZEND_END_ARG_INFO() /* }}} */ /* {{{ uniqid.c */ #ifdef HAVE_GETTIMEOFDAY @@ -3079,6 +3083,7 @@ const zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(is_object, arginfo_is_object) PHP_FE(is_scalar, arginfo_is_scalar) PHP_FE(is_callable, arginfo_is_callable) + PHP_FE(is_iterable, arginfo_is_iterable) /* functions from file.c */ PHP_FE(pclose, arginfo_pclose) diff --git a/ext/standard/php_type.h b/ext/standard/php_type.h index e9a3155572..c58718d90f 100644 --- a/ext/standard/php_type.h +++ b/ext/standard/php_type.h @@ -38,5 +38,6 @@ PHP_FUNCTION(is_array); PHP_FUNCTION(is_object); PHP_FUNCTION(is_scalar); PHP_FUNCTION(is_callable); +PHP_FUNCTION(is_iterable); #endif diff --git a/ext/standard/tests/general_functions/is_iterable.phpt b/ext/standard/tests/general_functions/is_iterable.phpt new file mode 100644 index 0000000000..e0822d7eb6 --- /dev/null +++ b/ext/standard/tests/general_functions/is_iterable.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test is_iterable() function +--FILE-- +<?php + +function gen() { + yield; +} + +var_dump(is_iterable([1, 2, 3])); +var_dump(is_iterable(new ArrayIterator([1, 2, 3]))); +var_dump(is_iterable(gen())); +var_dump(is_iterable(1)); +var_dump(is_iterable(3.14)); +var_dump(is_iterable(new stdClass())); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) diff --git a/ext/standard/type.c b/ext/standard/type.c index 5350e13ec5..3f97bc8688 100644 --- a/ext/standard/type.c +++ b/ext/standard/type.c @@ -434,6 +434,20 @@ PHP_FUNCTION(is_callable) } /* }}} */ +/* {{{ proto bool is_iterable(mixed var) + Returns true if var is iterable (array or instance of Traversable). */ +PHP_FUNCTION(is_iterable) +{ + zval *var; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &var) == FAILURE) { + return; + } + + RETURN_BOOL(zend_is_iterable(var)); +} +/* }}} */ + /* * Local variables: * tab-width: 4 |