From 72692e9a9c3439be3083135eec3b174410ef08e4 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 3 Jun 2016 17:42:04 -0500 Subject: [PATCH 01/12] Add iterable pseudo-type --- Zend/zend_API.c | 14 ++++++++++++++ Zend/zend_API.h | 2 ++ Zend/zend_compile.c | 10 ++++++++++ Zend/zend_execute.c | 23 +++++++++++++++++++++++ Zend/zend_types.h | 3 ++- Zend/zend_vm_def.h | 1 + Zend/zend_vm_execute.h | 5 +++++ 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index eb76c671fc0f5..0ecbe0e278def 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" @@ -4201,6 +4202,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 ff93a53139ffe..5b2b95a819793 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -550,6 +550,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 4bb712678f964..b6627b21c1fcf 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} }; @@ -5060,6 +5062,14 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters " "with callable type can only be NULL"); } + } else if (arg_info->type_hint == IS_ITERABLE && default_ast) { + if (default_ast && !has_null_default + && Z_TYPE(default_node.u.constant) != IS_ARRAY + && !Z_CONSTANT(default_node.u.constant) + ) { + zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters " + "with iterable type can only be an array or NULL"); + } } } else { if (default_ast && !has_null_default && !Z_CONSTANT(default_node.u.constant)) { diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 8d97845bad579..f7b92e7539375 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 */ @@ -893,6 +903,8 @@ static zend_always_inline int zend_verify_missing_arg_type(zend_function *zf, ui zend_verify_arg_error(zf, arg_num, need_msg, ZSTR_VAL(ce->name), "none", ""); } else if (cur_arg_info->type_hint == IS_CALLABLE) { zend_verify_arg_error(zf, arg_num, "be callable", "", "none", ""); + } else if (cur_arg_info->type_hint == IS_ITERABLE) { + zend_verify_arg_error(zf, arg_num, "be iterable", "", "none", ""); } else { zend_verify_arg_error(zf, arg_num, "be of the type ", zend_get_type_by_const(cur_arg_info->type_hint), "none", ""); } @@ -998,6 +1010,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 */ @@ -1060,6 +1077,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 callable", "", 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 */ @@ -1101,6 +1122,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_types.h b/Zend/zend_types.h index 45c2ebdf70a69..95c4ef6e549fb 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 44fb7292b872e..f2eba7d820263 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3861,6 +3861,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 67def7658e05b..e337d7c2d6ad1 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7419,6 +7419,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) @@ -13925,6 +13926,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) @@ -21654,6 +21656,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) @@ -31610,6 +31613,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) @@ -45100,6 +45104,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) From 0e221cb3ebea3ebe4e3ec6cd824b22748efc36b8 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 3 Jun 2016 17:42:12 -0500 Subject: [PATCH 02/12] Add is_iterable() function --- ext/standard/basic_functions.c | 5 +++++ ext/standard/php_type.h | 1 + ext/standard/type.c | 14 ++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 5fb5246d92a08..1ac31200b07b5 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 @@ -3060,6 +3064,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 e9a315557216f..c58718d90f7d8 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/type.c b/ext/standard/type.c index 529d666d02db5..3cd234d38b82c 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 From 340a00008d05044fa0cc9a7948cf752cad08cb85 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 3 Jun 2016 17:56:13 -0500 Subject: [PATCH 03/12] Add iterable to zend_get_type_by_const() --- Zend/zend_API.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 0ecbe0e278def..d6350502ff2b2 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -183,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: From bf6a65984ba00ed4bea7dc7968e597c2f02fce0d Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 3 Jun 2016 18:01:56 -0500 Subject: [PATCH 04/12] Fix default argument check --- Zend/zend_compile.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b6627b21c1fcf..bd4dba88ff593 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5062,14 +5062,6 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters " "with callable type can only be NULL"); } - } else if (arg_info->type_hint == IS_ITERABLE && default_ast) { - if (default_ast && !has_null_default - && Z_TYPE(default_node.u.constant) != IS_ARRAY - && !Z_CONSTANT(default_node.u.constant) - ) { - zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters " - "with iterable type can only be an array or NULL"); - } } } else { if (default_ast && !has_null_default && !Z_CONSTANT(default_node.u.constant)) { @@ -5083,6 +5075,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))) { From 8146c47d85dad9883d9153d221dc415b0ee3c12d Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 3 Jun 2016 18:29:24 -0500 Subject: [PATCH 05/12] Fix error message --- Zend/zend_execute.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index f7b92e7539375..d3ca248de803a 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1079,7 +1079,7 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * } } else if (ret_info->type_hint == IS_ITERABLE) { if (!zend_is_iterable(ret)) { - zend_verify_return_error(zf, "be callable", "", zend_zval_type_name(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)) { From bea9df52811d1b5f99fd59dfd4d8232d8448b538 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 3 Jun 2016 18:41:03 -0500 Subject: [PATCH 06/12] Add iterable tests --- .../tests/type_declarations/iterable_001.phpt | 48 +++++++++++++++++++ .../tests/type_declarations/iterable_002.phpt | 21 ++++++++ .../tests/type_declarations/iterable_003.phpt | 32 +++++++++++++ .../tests/general_functions/is_iterable.phpt | 24 ++++++++++ 4 files changed, 125 insertions(+) create mode 100644 Zend/tests/type_declarations/iterable_001.phpt create mode 100644 Zend/tests/type_declarations/iterable_002.phpt create mode 100644 Zend/tests/type_declarations/iterable_003.phpt create mode 100644 ext/standard/tests/general_functions/is_iterable.phpt diff --git a/Zend/tests/type_declarations/iterable_001.phpt b/Zend/tests/type_declarations/iterable_001.phpt new file mode 100644 index 0000000000000..f0c8bba610448 --- /dev/null +++ b/Zend/tests/type_declarations/iterable_001.phpt @@ -0,0 +1,48 @@ +--TEST-- +iterable type#001 +--FILE-- +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 0000000000000..74f6d83f1e6d5 --- /dev/null +++ b/Zend/tests/type_declarations/iterable_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +iterable type#002 - Default values +--FILE-- + +--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 0000000000000..8c91c993d0a82 --- /dev/null +++ b/Zend/tests/type_declarations/iterable_003.phpt @@ -0,0 +1,32 @@ +--TEST-- +iterable type#003 - Return types +--FILE-- +getMessage(); +} + +?> +--EXPECT-- +array(0) { +} +object(Generator)#2 (0) { +} +Return value of baz() must be iterable, integer returned 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 0000000000000..e0822d7eb6d5c --- /dev/null +++ b/ext/standard/tests/general_functions/is_iterable.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test is_iterable() function +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) From 4da3e77b4ce4beb1735a679c58cb2bce3fdfece1 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 4 Jun 2016 09:44:49 -0500 Subject: [PATCH 07/12] Covariance on inheriting classes with iterable --- Zend/zend_compile.c | 19 +++++++++++-------- Zend/zend_inheritance.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index bd4dba88ff593..62fa76b5b1d37 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1253,17 +1253,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)); + } } } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 44abfb6ffba07..dd1442e828688 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -22,6 +22,7 @@ #include "zend_compile.h" #include "zend_execute.h" #include "zend_inheritance.h" +#include "zend_interfaces.h" #include "zend_smart_str.h" #include "zend_inheritance.h" @@ -167,6 +168,26 @@ char *zend_visibility_string(uint32_t fn_flags) /* {{{ */ } /* }}} */ +static zend_bool zend_iterable_type_check(zend_arg_info *arg_info) /* {{{ */ +{ + if (arg_info->class_name) { + zend_class_entry *ce; + + ce = zend_lookup_class(arg_info->class_name); + + if (ce && instanceof_function(ce, zend_ce_traversable)) { + return 1; + } + } + + if (arg_info->type_hint == IS_ITERABLE || arg_info->type_hint == IS_ARRAY) { + 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,6 +335,10 @@ 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 (fe_arg_info->type_hint == IS_ITERABLE && zend_iterable_type_check(proto_arg_info)) { + continue; + } if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) { return 0; @@ -338,6 +363,10 @@ 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 (proto->common.arg_info[-1].type_hint == IS_ITERABLE && zend_iterable_type_check(fe->common.arg_info - 1)) { + return 1; + } if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) { return 0; From c2266c382acf8a16c18bd7df013fe40348cd478d Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sat, 4 Jun 2016 10:08:44 -0500 Subject: [PATCH 08/12] Fix generator test; add covariance tests --- Zend/tests/return_types/generators002.phpt | 2 +- .../tests/type_declarations/iterable_004.phpt | 29 ++++++++++++ .../tests/type_declarations/iterable_005.phpt | 45 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/iterable_004.phpt create mode 100644 Zend/tests/type_declarations/iterable_005.phpt diff --git a/Zend/tests/return_types/generators002.phpt b/Zend/tests/return_types/generators002.phpt index f7dbfda69bd9e..519c97a962bf7 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_004.phpt b/Zend/tests/type_declarations/iterable_004.phpt new file mode 100644 index 0000000000000..8215dd686b873 --- /dev/null +++ b/Zend/tests/type_declarations/iterable_004.phpt @@ -0,0 +1,29 @@ +--TEST-- +iterable type#004 - Parameter covariance +--FILE-- + +--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 0000000000000..cfb55517e0443 --- /dev/null +++ b/Zend/tests/type_declarations/iterable_005.phpt @@ -0,0 +1,45 @@ +--TEST-- +iterable type#005 - Return type covariance +--FILE-- + +--EXPECTF-- + +Fatal error: Declaration of TestScalar::method(): int must be compatible with Test::method(): iterable in %s on line %d From cf0290c27380e17d2407828c974882d39ed976f4 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sun, 5 Jun 2016 02:01:01 -0500 Subject: [PATCH 09/12] Fix abort too early --- Zend/zend_inheritance.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index dd1442e828688..1785f453729a1 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -178,6 +178,8 @@ static zend_bool zend_iterable_type_check(zend_arg_info *arg_info) /* {{{ */ if (ce && instanceof_function(ce, zend_ce_traversable)) { return 1; } + + return 0; } if (arg_info->type_hint == IS_ITERABLE || arg_info->type_hint == IS_ARRAY) { @@ -336,11 +338,11 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c proto_arg_info = &proto->common.arg_info[proto->common.num_args]; } - if (fe_arg_info->type_hint == IS_ITERABLE && zend_iterable_type_check(proto_arg_info)) { - continue; - } - - if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) { + if (fe_arg_info->type_hint == IS_ITERABLE) { + if (!zend_iterable_type_check(proto_arg_info)) { + return 0; + } + } else if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) { return 0; } @@ -364,11 +366,11 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c return 0; } - if (proto->common.arg_info[-1].type_hint == IS_ITERABLE && zend_iterable_type_check(fe->common.arg_info - 1)) { - return 1; - } - - if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) { + if (proto->common.arg_info[-1].type_hint == IS_ITERABLE) { + if (!zend_iterable_type_check(fe->common.arg_info - 1)) { + return 0; + } + } else if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) { return 0; } From 277d89cde8ed19c0e1f9f51e14bcb993f80e1d22 Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Sun, 5 Jun 2016 09:49:29 -0500 Subject: [PATCH 10/12] Limit covariance to array and Traversable --- Zend/tests/type_declarations/iterable_004.phpt | 4 ---- Zend/tests/type_declarations/iterable_005.phpt | 12 ------------ Zend/zend_inheritance.c | 15 +++------------ 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/Zend/tests/type_declarations/iterable_004.phpt b/Zend/tests/type_declarations/iterable_004.phpt index 8215dd686b873..47e79fa6b37b6 100644 --- a/Zend/tests/type_declarations/iterable_004.phpt +++ b/Zend/tests/type_declarations/iterable_004.phpt @@ -8,8 +8,6 @@ class Foo { function testTraversable(Traversable $traversable) {} - function testGenerator(Generator $generator) {} - function testScalar(int $int) {} } @@ -18,8 +16,6 @@ class Bar extends Foo { function testTraversable(iterable $iterable) {} - function testGenerator(iterable $iterable) {} - function testScalar(iterable $iterable) {} } diff --git a/Zend/tests/type_declarations/iterable_005.phpt b/Zend/tests/type_declarations/iterable_005.phpt index cfb55517e0443..9c0584b51a600 100644 --- a/Zend/tests/type_declarations/iterable_005.phpt +++ b/Zend/tests/type_declarations/iterable_005.phpt @@ -21,18 +21,6 @@ class TestTraversable extends Test { } } -class TestIterator extends Test { - function method(): Iterator { - return new ArrayIterator([]); - } -} - -class TestGenerator extends Test { - function method(): Generator { - return (function () { yield; })(); - } -} - class TestScalar extends Test { function method(): int { return 1; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 1785f453729a1..6c3dc743fbc80 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -22,7 +22,6 @@ #include "zend_compile.h" #include "zend_execute.h" #include "zend_inheritance.h" -#include "zend_interfaces.h" #include "zend_smart_str.h" #include "zend_inheritance.h" @@ -170,19 +169,11 @@ char *zend_visibility_string(uint32_t fn_flags) /* {{{ */ static zend_bool zend_iterable_type_check(zend_arg_info *arg_info) /* {{{ */ { - if (arg_info->class_name) { - zend_class_entry *ce; - - ce = zend_lookup_class(arg_info->class_name); - - if (ce && instanceof_function(ce, zend_ce_traversable)) { - return 1; - } - - return 0; + if (arg_info->type_hint == IS_ITERABLE || arg_info->type_hint == IS_ARRAY) { + return 1; } - if (arg_info->type_hint == IS_ITERABLE || arg_info->type_hint == IS_ARRAY) { + if (arg_info->class_name && zend_string_equals_literal_ci(arg_info->class_name, "Traversable")) { return 1; } From 0dc772c9b4466de45c47c6a5222b7d3464dcfa2c Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Fri, 10 Jun 2016 19:01:27 -0500 Subject: [PATCH 11/12] Add iterable type to opcache --- ext/opcache/Optimizer/zend_inference.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index 8ea4347d50cd7..795ad79230ca8 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -2375,6 +2375,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) { From 583386d59e6b362fe49e51594718a109d0c0cc2f Mon Sep 17 00:00:00 2001 From: Aaron Piotrowski Date: Wed, 29 Jun 2016 09:36:33 -0500 Subject: [PATCH 12/12] Swap type check order --- Zend/zend_inheritance.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 6c3dc743fbc80..7dc6bfc1f7adb 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -167,9 +167,9 @@ char *zend_visibility_string(uint32_t fn_flags) /* {{{ */ } /* }}} */ -static zend_bool zend_iterable_type_check(zend_arg_info *arg_info) /* {{{ */ +static zend_always_inline zend_bool zend_iterable_compatibility_check(zend_arg_info *arg_info) /* {{{ */ { - if (arg_info->type_hint == IS_ITERABLE || arg_info->type_hint == IS_ARRAY) { + if (arg_info->type_hint == IS_ARRAY) { return 1; } @@ -329,12 +329,17 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c proto_arg_info = &proto->common.arg_info[proto->common.num_args]; } - if (fe_arg_info->type_hint == IS_ITERABLE) { - if (!zend_iterable_type_check(proto_arg_info)) { - return 0; + if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) { + switch (fe_arg_info->type_hint) { + case IS_ITERABLE: + if (!zend_iterable_compatibility_check(proto_arg_info)) { + return 0; + } + break; + + default: + return 0; } - } else if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) { - return 0; } // This introduces BC break described at https://bugs.php.net/bug.php?id=72119 @@ -357,12 +362,17 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c return 0; } - if (proto->common.arg_info[-1].type_hint == IS_ITERABLE) { - if (!zend_iterable_type_check(fe->common.arg_info - 1)) { - return 0; + if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) { + 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; } - } else if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) { - return 0; } if (fe->common.arg_info[-1].allow_null && !proto->common.arg_info[-1].allow_null) {