summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Zend/tests/return_types/generators002.phpt2
-rw-r--r--Zend/tests/type_declarations/iterable_001.phpt48
-rw-r--r--Zend/tests/type_declarations/iterable_002.phpt21
-rw-r--r--Zend/tests/type_declarations/iterable_003.phpt32
-rw-r--r--Zend/tests/type_declarations/iterable_004.phpt25
-rw-r--r--Zend/tests/type_declarations/iterable_005.phpt33
-rw-r--r--Zend/zend_API.c16
-rw-r--r--Zend/zend_API.h2
-rw-r--r--Zend/zend_compile.c28
-rw-r--r--Zend/zend_execute.c21
-rw-r--r--Zend/zend_inheritance.c40
-rw-r--r--Zend/zend_types.h3
-rw-r--r--Zend/zend_vm_def.h1
-rw-r--r--Zend/zend_vm_execute.h5
-rw-r--r--ext/opcache/Optimizer/zend_inference.c2
-rw-r--r--ext/standard/basic_functions.c5
-rw-r--r--ext/standard/php_type.h1
-rw-r--r--ext/standard/tests/general_functions/is_iterable.phpt24
-rw-r--r--ext/standard/type.c14
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