From: Andrea Faulds Date: Mon, 8 Aug 2016 15:15:59 +0000 (+0100) Subject: Show "or null" in TypeErrors for nullable arg_infos X-Git-Tag: php-7.1.0beta3~66 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1dab96c1db2d7c5663569d9c55f63d8e25a0dddc;p=php Show "or null" in TypeErrors for nullable arg_infos --- diff --git a/NEWS b/NEWS index 126f9ea372..55bf0bcd6a 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ PHP NEWS - Core: . Fixed bug #72767 (PHP Segfaults when trying to expand an infinite operator). (Nikita) + . TypeError messages for arg_info type checks will now say "must be ... + or null" where the parameter or return type accepts null. (Andrea) - EXIF: . Fixed bug #72735 (Samsung picture thumb not read (zero size)). (Kalle, Remi) diff --git a/UPGRADING b/UPGRADING index 8db36e1fc0..6ec40e7619 100644 --- a/UPGRADING +++ b/UPGRADING @@ -127,6 +127,12 @@ PHP 7.1 UPGRADE NOTES . Closure::fromCallable (RFC: https://wiki.php.net/rfc/closurefromcallable) . Added support for class constant visibility modifiers. (RFC: https://wiki.php.net/rfc/class_const_visibility) + . TypeError messages for arg_info type checks will now say "must be ... + or null", or "must ... or be null" where the parameter or return type + accepts null. arg_info type checks are used by all userland functions with + type declarations, and some internal functions. Both nullable type + declarations (?int) and parameters with default values of null + (int $foo = NULL) are considered to "accept null" for this purpose. ======================================== 3. Changes in SAPI modules diff --git a/Zend/tests/ns_071.phpt b/Zend/tests/ns_071.phpt index 2f2fcfad1a..d7f1592b38 100644 --- a/Zend/tests/ns_071.phpt +++ b/Zend/tests/ns_071.phpt @@ -18,7 +18,7 @@ new bar(new \stdclass); --EXPECTF-- NULL -Fatal error: Uncaught TypeError: Argument 1 passed to foo\bar::__construct() must be of the type array, object given, called in %s on line %d and defined in %s:%d +Fatal error: Uncaught TypeError: Argument 1 passed to foo\bar::__construct() must be of the type array or null, object given, called in %s on line %d and defined in %s:%d Stack trace: #0 %s(%d): foo\bar->__construct(Object(stdClass)) #1 {main} diff --git a/Zend/tests/ns_072.phpt b/Zend/tests/ns_072.phpt index 6375682890..34a9bbf6ad 100644 --- a/Zend/tests/ns_072.phpt +++ b/Zend/tests/ns_072.phpt @@ -30,7 +30,7 @@ object(foo\test)#%d (0) { } NULL -Fatal error: Uncaught TypeError: Argument 1 passed to foo\bar::__construct() must implement interface foo\foo, instance of stdClass given, called in %s on line %d and defined in %s:%d +Fatal error: Uncaught TypeError: Argument 1 passed to foo\bar::__construct() must implement interface foo\foo or be null, instance of stdClass given, called in %s on line %d and defined in %s:%d Stack trace: #0 %s(%d): foo\bar->__construct(Object(stdClass)) #1 {main} diff --git a/Zend/tests/return_types/030.phpt b/Zend/tests/return_types/030.phpt index d1ceac8329..288137f05c 100644 --- a/Zend/tests/return_types/030.phpt +++ b/Zend/tests/return_types/030.phpt @@ -16,7 +16,7 @@ foo(0); ok ok -Fatal error: Uncaught TypeError: Return value of foo() must be of the type array, integer returned in %s030.php:3 +Fatal error: Uncaught TypeError: Return value of foo() must be of the type array or null, integer returned in %s030.php:3 Stack trace: #0 %s030.php(10): foo(0) #1 {main} diff --git a/Zend/tests/typehints/or_null.phpt b/Zend/tests/typehints/or_null.phpt new file mode 100644 index 0000000000..8c20052165 --- /dev/null +++ b/Zend/tests/typehints/or_null.phpt @@ -0,0 +1,317 @@ +--TEST-- +Test "or null"/"or be null" in type-checking errors for userland functions +--FILE-- + +--EXPECTF-- +TypeError: Argument 1 passed to unloadedClass() must be an instance of I\Dont\Exist or null, instance of stdClass given, called in %s on line 8 and defined in %s:5 +Stack trace: +#0 %s(8): unloadedClass(Object(stdClass)) +#1 {main} +TypeError: Argument 1 passed to loadedClass() must be an instance of RealClass or null, instance of stdClass given, called in %s on line 20 and defined in %s:16 +Stack trace: +#0 %s(20): loadedClass(Object(stdClass)) +#1 {main} +TypeError: Argument 1 passed to loadedInterface() must implement interface RealInterface or be null, instance of stdClass given, called in %s on line 26 and defined in %s:17 +Stack trace: +#0 %s(26): loadedInterface(Object(stdClass)) +#1 {main} +TypeError: Argument 1 passed to unloadedClass() must be an instance of I\Dont\Exist or null, integer given, called in %s on line 32 and defined in %s:5 +Stack trace: +#0 %s(32): unloadedClass(1) +#1 {main} +TypeError: Argument 1 passed to loadedClass() must be an instance of RealClass or null, integer given, called in %s on line 38 and defined in %s:16 +Stack trace: +#0 %s(38): loadedClass(1) +#1 {main} +TypeError: Argument 1 passed to loadedInterface() must implement interface RealInterface or be null, integer given, called in %s on line 44 and defined in %s:17 +Stack trace: +#0 %s(44): loadedInterface(1) +#1 {main} +TypeError: Argument 1 passed to callableF() must be callable or null, integer given, called in %s on line 52 and defined in %s:49 +Stack trace: +#0 %s(52): callableF(1) +#1 {main} +TypeError: Argument 1 passed to iterableF() must be iterable or null, integer given, called in %s on line 60 and defined in %s:57 +Stack trace: +#0 %s(60): iterableF(1) +#1 {main} +TypeError: Argument 1 passed to intF() must be of the type integer or null, object given, called in %s on line 68 and defined in %s:65 +Stack trace: +#0 %s(68): intF(Object(stdClass)) +#1 {main} +TypeError: Return value of returnUnloadedClass() must be an instance of I\Dont\Exist or null, instance of stdClass returned in %s:74 +Stack trace: +#0 %s(78): returnUnloadedClass() +#1 {main} +TypeError: Return value of returnLoadedClass() must be an instance of RealClass or null, instance of stdClass returned in %s:84 +Stack trace: +#0 %s(88): returnLoadedClass() +#1 {main} +TypeError: Return value of returnLoadedInterface() must implement interface RealInterface or be null, instance of stdClass returned in %s:94 +Stack trace: +#0 %s(98): returnLoadedInterface() +#1 {main} +TypeError: Return value of returnUnloadedClassScalar() must be an instance of I\Dont\Exist or null, integer returned in %s:104 +Stack trace: +#0 %s(108): returnUnloadedClassScalar() +#1 {main} +TypeError: Return value of returnLoadedClassScalar() must be an instance of RealClass or null, integer returned in %s:114 +Stack trace: +#0 %s(118): returnLoadedClassScalar() +#1 {main} +TypeError: Return value of returnLoadedInterfaceScalar() must implement interface RealInterface or be null, integer returned in %s:124 +Stack trace: +#0 %s(128): returnLoadedInterfaceScalar() +#1 {main} +TypeError: Return value of returnCallable() must be callable or null, integer returned in %s:134 +Stack trace: +#0 %s(138): returnCallable() +#1 {main} +TypeError: Return value of returnIterable() must be iterable or null, integer returned in %s:144 +Stack trace: +#0 %s(148): returnIterable() +#1 {main} +TypeError: Return value of returnInt() must be of the type integer or null, object returned in %s:154 +Stack trace: +#0 %s(158): returnInt() +#1 {main} +TypeError: Return value of returnMissingUnloadedClass() must be an instance of I\Dont\Exist or null, none returned in %s:164 +Stack trace: +#0 %s(167): returnMissingUnloadedClass() +#1 {main} +TypeError: Return value of returnMissingLoadedClass() must be an instance of RealClass or null, none returned in %s:173 +Stack trace: +#0 %s(176): returnMissingLoadedClass() +#1 {main} +TypeError: Return value of returnMissingLoadedInterface() must implement interface RealInterface or be null, none returned in %s:182 +Stack trace: +#0 %s(185): returnMissingLoadedInterface() +#1 {main} +TypeError: Return value of returnMissingCallable() must be callable or null, none returned in %s:191 +Stack trace: +#0 %s(194): returnMissingCallable() +#1 {main} +TypeError: Return value of returnMissingIterable() must be iterable or null, none returned in %s:200 +Stack trace: +#0 %s(203): returnMissingIterable() +#1 {main} +TypeError: Return value of returnMissingInt() must be of the type integer or null, none returned in %s:209 +Stack trace: +#0 %s(212): returnMissingInt() +#1 {main} diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 234f62a592..dee1d91f69 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -597,7 +597,7 @@ static inline int make_real_object(zval *object) return 1; } -static char * zend_verify_internal_arg_class_kind(const zend_internal_arg_info *cur_arg_info, char **class_name, zend_class_entry **pce) +static char * zend_verify_internal_arg_class_kind(const zend_internal_arg_info *cur_arg_info, char **class_name, zend_class_entry **pce, char **need_or_null) { zend_string *key; ALLOCA_FLAG(use_heap); @@ -608,8 +608,10 @@ static char * zend_verify_internal_arg_class_kind(const zend_internal_arg_info * *class_name = (*pce) ? ZSTR_VAL((*pce)->name) : (char*)cur_arg_info->class_name; if (*pce && (*pce)->ce_flags & ZEND_ACC_INTERFACE) { + *need_or_null = " or be null"; return "implement interface "; } else { + *need_or_null = " or null"; return "be an instance of "; } } @@ -619,7 +621,7 @@ static zend_always_inline zend_class_entry* zend_verify_arg_class_kind(const zen return zend_fetch_class(cur_arg_info->class_name, (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); } -static ZEND_COLD void zend_verify_arg_error(const zend_function *zf, uint32_t arg_num, const char *need_msg, const char *need_kind, const char *given_msg, const char *given_kind) +static ZEND_COLD void zend_verify_arg_error(const zend_function *zf, uint32_t arg_num, const char *need_msg, const char *need_kind, const char *need_or_null, const char *given_msg, const char *given_kind) { zend_execute_data *ptr = EG(current_execute_data)->prev_execute_data; const char *fname = ZSTR_VAL(zf->common.function_name); @@ -636,14 +638,14 @@ static ZEND_COLD void zend_verify_arg_error(const zend_function *zf, uint32_t ar if (zf->common.type == ZEND_USER_FUNCTION) { if (ptr && ptr->func && ZEND_USER_CODE(ptr->func->common.type)) { - zend_type_error("Argument %d passed to %s%s%s() must %s%s, %s%s given, called in %s on line %d", - arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind, + zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given, called in %s on line %d", + arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind, ZSTR_VAL(ptr->func->op_array.filename), ptr->opline->lineno); } else { - zend_type_error("Argument %d passed to %s%s%s() must %s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind); + zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); } } else { - zend_type_error("Argument %d passed to %s%s%s() must %s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind); + zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); } } @@ -728,7 +730,7 @@ static zend_bool zend_verify_scalar_type_hint(zend_uchar type_hint, zval *arg, z static int zend_verify_internal_arg_type(zend_function *zf, uint32_t arg_num, zval *arg) { zend_internal_arg_info *cur_arg_info; - char *need_msg, *class_name; + char *need_msg, *need_or_null, *class_name; zend_class_entry *ce; if (EXPECTED(arg_num <= zf->internal_function.num_args)) { @@ -743,32 +745,32 @@ static int zend_verify_internal_arg_type(zend_function *zf, uint32_t arg_num, zv ZVAL_DEREF(arg); if (EXPECTED(cur_arg_info->type_hint == Z_TYPE_P(arg))) { if (cur_arg_info->class_name) { - need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info*)cur_arg_info, &class_name, &ce); + need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info*)cur_arg_info, &class_name, &ce, &need_or_null); if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) { - zend_verify_arg_error(zf, arg_num, need_msg, class_name, "instance of ", ZSTR_VAL(Z_OBJCE_P(arg)->name)); + zend_verify_arg_error(zf, arg_num, need_msg, class_name, (cur_arg_info->allow_null ? need_or_null : ""), "instance of ", ZSTR_VAL(Z_OBJCE_P(arg)->name)); return 0; } } } else if (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null) { if (cur_arg_info->class_name) { - need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info*)cur_arg_info, &class_name, &ce); - zend_verify_arg_error(zf, arg_num, need_msg, class_name, zend_zval_type_name(arg), ""); + need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info*)cur_arg_info, &class_name, &ce, &need_or_null); + zend_verify_arg_error(zf, arg_num, need_msg, class_name, (cur_arg_info->allow_null ? need_or_null : ""), zend_zval_type_name(arg), ""); return 0; } else if (cur_arg_info->type_hint == IS_CALLABLE) { if (!zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { - zend_verify_arg_error(zf, arg_num, "be callable", "", zend_zval_type_name(arg), ""); + zend_verify_arg_error(zf, arg_num, "be callable", "", (cur_arg_info->allow_null ? " or null" : ""), 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), ""); + zend_verify_arg_error(zf, arg_num, "be iterable", "", (cur_arg_info->allow_null ? " or null" : ""), 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 */ } else if (UNEXPECTED(!zend_verify_scalar_type_hint(cur_arg_info->type_hint, arg, ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data))))) { - zend_verify_arg_error(zf, arg_num, "be of the type ", zend_get_type_by_const(cur_arg_info->type_hint), zend_zval_type_name(arg), ""); + zend_verify_arg_error(zf, arg_num, "be of the type ", zend_get_type_by_const(cur_arg_info->type_hint), (cur_arg_info->allow_null ? " or null" : ""), zend_zval_type_name(arg), ""); return 0; } } @@ -796,7 +798,7 @@ static zend_never_inline int zend_verify_internal_arg_types(zend_function *fbc, static zend_always_inline int zend_verify_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, zval *default_value, void **cache_slot) { zend_arg_info *cur_arg_info; - char *need_msg; + char *need_msg, *need_or_null; zend_class_entry *ce; if (EXPECTED(arg_num <= zf->common.num_args)) { @@ -816,7 +818,7 @@ static zend_always_inline int zend_verify_arg_type(zend_function *zf, uint32_t a } else { ce = zend_verify_arg_class_kind(cur_arg_info); if (UNEXPECTED(!ce)) { - zend_verify_arg_error(zf, arg_num, "be an instance of ", ZSTR_VAL(cur_arg_info->class_name), "instance of ", ZSTR_VAL(Z_OBJCE_P(arg)->name)); + zend_verify_arg_error(zf, arg_num, "be an instance of ", ZSTR_VAL(cur_arg_info->class_name), (cur_arg_info->allow_null ? " or null" : ""), "instance of ", ZSTR_VAL(Z_OBJCE_P(arg)->name)); return 0; } *cache_slot = (void*)ce; @@ -825,7 +827,10 @@ static zend_always_inline int zend_verify_arg_type(zend_function *zf, uint32_t a need_msg = (ce->ce_flags & ZEND_ACC_INTERFACE) ? "implement interface " : "be an instance of "; - zend_verify_arg_error(zf, arg_num, need_msg, ZSTR_VAL(ce->name), "instance of ", ZSTR_VAL(Z_OBJCE_P(arg)->name)); + need_or_null = + (ce->ce_flags & ZEND_ACC_INTERFACE) ? + " or be null" : " or null"; + zend_verify_arg_error(zf, arg_num, need_msg, ZSTR_VAL(ce->name), (cur_arg_info->allow_null ? need_or_null : ""), "instance of ", ZSTR_VAL(Z_OBJCE_P(arg)->name)); return 0; } } @@ -837,7 +842,7 @@ static zend_always_inline int zend_verify_arg_type(zend_function *zf, uint32_t a ce = zend_verify_arg_class_kind(cur_arg_info); if (UNEXPECTED(!ce)) { ZEND_ASSERT(Z_TYPE_P(arg) != IS_OBJECT); - zend_verify_arg_error(zf, arg_num, "be an instance of ", ZSTR_VAL(cur_arg_info->class_name), "", zend_zval_type_name(arg)); + zend_verify_arg_error(zf, arg_num, "be an instance of ", ZSTR_VAL(cur_arg_info->class_name), (cur_arg_info->allow_null ? " or null" : ""), "", zend_zval_type_name(arg)); return 0; } *cache_slot = (void*)ce; @@ -845,23 +850,26 @@ static zend_always_inline int zend_verify_arg_type(zend_function *zf, uint32_t a need_msg = (ce->ce_flags & ZEND_ACC_INTERFACE) ? "implement interface " : "be an instance of "; - zend_verify_arg_error(zf, arg_num, need_msg, ZSTR_VAL(ce->name), zend_zval_type_name(arg), ""); + need_or_null = + (ce->ce_flags & ZEND_ACC_INTERFACE) ? + " or be null" : " or null"; + zend_verify_arg_error(zf, arg_num, need_msg, ZSTR_VAL(ce->name), (cur_arg_info->allow_null ? need_or_null : ""), zend_zval_type_name(arg), ""); return 0; } else if (cur_arg_info->type_hint == IS_CALLABLE) { if (!zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { - zend_verify_arg_error(zf, arg_num, "be callable", "", zend_zval_type_name(arg), ""); + zend_verify_arg_error(zf, arg_num, "be callable", "", (cur_arg_info->allow_null ? " or null" : ""), 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), ""); + zend_verify_arg_error(zf, arg_num, "be iterable", "", (cur_arg_info->allow_null ? " or null" : ""), 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 */ } else if (UNEXPECTED(!zend_verify_scalar_type_hint(cur_arg_info->type_hint, arg, ZEND_ARG_USES_STRICT_TYPES()))) { - zend_verify_arg_error(zf, arg_num, "be of the type ", zend_get_type_by_const(cur_arg_info->type_hint), zend_zval_type_name(arg), ""); + zend_verify_arg_error(zf, arg_num, "be of the type ", zend_get_type_by_const(cur_arg_info->type_hint), (cur_arg_info->allow_null ? " or null" : ""), zend_zval_type_name(arg), ""); return 0; } } @@ -894,7 +902,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_missing_arg_error(zend_execute_data * } } -static ZEND_COLD void zend_verify_return_error(const zend_function *zf, const char *need_msg, const char *need_kind, const char *returned_msg, const char *returned_kind) +static ZEND_COLD void zend_verify_return_error(const zend_function *zf, const char *need_msg, const char *need_kind, const char *need_or_null, const char *returned_msg, const char *returned_kind) { const char *fname = ZSTR_VAL(zf->common.function_name); const char *fsep; @@ -908,12 +916,12 @@ static ZEND_COLD void zend_verify_return_error(const zend_function *zf, const ch fclass = ""; } - zend_type_error("Return value of %s%s%s() must %s%s, %s%s returned", - fclass, fsep, fname, need_msg, need_kind, returned_msg, returned_kind); + zend_type_error("Return value of %s%s%s() must %s%s%s, %s%s returned", + fclass, fsep, fname, need_msg, need_kind, need_or_null, returned_msg, returned_kind); } #if ZEND_DEBUG -static ZEND_COLD void zend_verify_internal_return_error(const zend_function *zf, const char *need_msg, const char *need_kind, const char *returned_msg, const char *returned_kind) +static ZEND_COLD void zend_verify_internal_return_error(const zend_function *zf, const char *need_msg, const char *need_kind, const char *need_or_null, const char *returned_msg, const char *returned_kind) { const char *fname = ZSTR_VAL(zf->common.function_name); const char *fsep; @@ -927,8 +935,8 @@ static ZEND_COLD void zend_verify_internal_return_error(const zend_function *zf, fclass = ""; } - zend_error_noreturn(E_CORE_ERROR, "Return value of %s%s%s() must %s%s, %s%s returned", - fclass, fsep, fname, need_msg, need_kind, returned_msg, returned_kind); + zend_error_noreturn(E_CORE_ERROR, "Return value of %s%s%s() must %s%s%s, %s%s returned", + fclass, fsep, fname, need_msg, need_kind, need_or_null, returned_msg, returned_kind); } static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, const char *returned_msg, const char *returned_kind) @@ -952,31 +960,31 @@ static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, con static int zend_verify_internal_return_type(zend_function *zf, zval *ret) { zend_arg_info *ret_info = zf->common.arg_info - 1; - char *need_msg, *class_name; + char *need_msg, *need_or_null, *class_name; zend_class_entry *ce; if (ret_info->type_hint) { if (EXPECTED(ret_info->type_hint == Z_TYPE_P(ret))) { if (ret_info->class_name) { - need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info *)ret_info, &class_name, &ce); + need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info *)ret_info, &class_name, &ce, &need_or_null); if (!ce || !instanceof_function(Z_OBJCE_P(ret), ce)) { - zend_verify_internal_return_error(zf, need_msg, class_name, "instance of ", ZSTR_VAL(Z_OBJCE_P(ret)->name)); + zend_verify_internal_return_error(zf, need_msg, class_name, (ret_info->allow_null ? need_or_null : ""), "instance of ", ZSTR_VAL(Z_OBJCE_P(ret)->name)); return 0; } } } else if (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null) { if (ret_info->class_name) { - need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info *)ret_info, &class_name, &ce); - zend_verify_internal_return_error(zf, need_msg, class_name, zend_zval_type_name(ret), ""); + need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info *)ret_info, &class_name, &ce, &need_or_null); + zend_verify_internal_return_error(zf, need_msg, class_name, (ret_info->allow_null ? need_or_null : ""), zend_zval_type_name(ret), ""); } else if (ret_info->type_hint == IS_CALLABLE) { if (!zend_is_callable(ret, IS_CALLABLE_CHECK_SILENT, NULL) && (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null)) { - zend_verify_internal_return_error(zf, "be callable", "", zend_zval_type_name(ret), ""); + zend_verify_internal_return_error(zf, "be callable", "", (ret_info->allow_null ? " or null" : ""), 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), ""); + zend_verify_internal_return_error(zf, "be iterable", "", (ret_info->allow_null ? " or null" : ""), zend_zval_type_name(ret), ""); return 0; } } else if (ret_info->type_hint == _IS_BOOL && @@ -986,7 +994,7 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret) zend_verify_void_return_error(zf, zend_zval_type_name(ret), ""); } else { /* Use strict check to verify return value of internal function */ - zend_verify_internal_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), zend_zval_type_name(ret), ""); + zend_verify_internal_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), (ret_info->allow_null ? " or null" : ""), zend_zval_type_name(ret), ""); return 0; } } @@ -998,7 +1006,7 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret) static zend_always_inline void zend_verify_return_type(zend_function *zf, zval *ret, void **cache_slot) { zend_arg_info *ret_info = zf->common.arg_info - 1; - char *need_msg; + char *need_msg, *need_or_null; zend_class_entry *ce; if (ret_info->type_hint) { @@ -1009,7 +1017,7 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * } else { ce = zend_verify_arg_class_kind(ret_info); if (UNEXPECTED(!ce)) { - zend_verify_return_error(zf, "be an instance of ", ZSTR_VAL(ret_info->class_name), "instance of ", ZSTR_VAL(Z_OBJCE_P(ret)->name)); + zend_verify_return_error(zf, "be an instance of ", ZSTR_VAL(ret_info->class_name), (ret_info->allow_null ? " or null" : ""), "instance of ", ZSTR_VAL(Z_OBJCE_P(ret)->name)); return; } *cache_slot = (void*)ce; @@ -1018,7 +1026,10 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * need_msg = (ce->ce_flags & ZEND_ACC_INTERFACE) ? "implement interface " : "be an instance of "; - zend_verify_return_error(zf, need_msg, ZSTR_VAL(ce->name), "instance of ", ZSTR_VAL(Z_OBJCE_P(ret)->name)); + need_or_null = + (ce->ce_flags & ZEND_ACC_INTERFACE) ? + " or be null" : " or null"; + zend_verify_return_error(zf, need_msg, ZSTR_VAL(ce->name), (ret_info->allow_null ? need_or_null : ""), "instance of ", ZSTR_VAL(Z_OBJCE_P(ret)->name)); } } } else if (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null) { @@ -1028,7 +1039,7 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * } else { ce = zend_verify_arg_class_kind(ret_info); if (UNEXPECTED(!ce)) { - zend_verify_return_error(zf, "be an instance of ", ZSTR_VAL(ret_info->class_name), zend_zval_type_name(ret), ""); + zend_verify_return_error(zf, "be an instance of ", ZSTR_VAL(ret_info->class_name), (ret_info->allow_null ? " or null" : ""), zend_zval_type_name(ret), ""); return; } *cache_slot = (void*)ce; @@ -1036,14 +1047,17 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * need_msg = (ce->ce_flags & ZEND_ACC_INTERFACE) ? "implement interface " : "be an instance of "; - zend_verify_return_error(zf, need_msg, ZSTR_VAL(ce->name), zend_zval_type_name(ret), ""); + need_or_null = + (ce->ce_flags & ZEND_ACC_INTERFACE) ? + " or be null" : " or null"; + zend_verify_return_error(zf, need_msg, ZSTR_VAL(ce->name), (ret_info->allow_null ? need_or_null : ""), zend_zval_type_name(ret), ""); } else if (ret_info->type_hint == IS_CALLABLE) { if (!zend_is_callable(ret, IS_CALLABLE_CHECK_SILENT, NULL)) { - zend_verify_return_error(zf, "be callable", "", zend_zval_type_name(ret), ""); + zend_verify_return_error(zf, "be callable", "", (ret_info->allow_null ? " or null" : ""), 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), ""); + zend_verify_return_error(zf, "be iterable", "", (ret_info->allow_null ? " or null" : ""), 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)) { @@ -1055,7 +1069,7 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * * this part of the runtime check for non-internal functions. */ } else if (UNEXPECTED(!zend_verify_scalar_type_hint(ret_info->type_hint, ret, ZEND_RET_USES_STRICT_TYPES()))) { - zend_verify_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), zend_zval_type_name(ret), ""); + zend_verify_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), (ret_info->allow_null ? " or null" : ""), zend_zval_type_name(ret), ""); } } } @@ -1064,7 +1078,7 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * static ZEND_COLD int zend_verify_missing_return_type(zend_function *zf, void **cache_slot) { zend_arg_info *ret_info = zf->common.arg_info - 1; - char *need_msg; + char *need_msg, *need_or_null; zend_class_entry *ce; if (ret_info->type_hint && EXPECTED(ret_info->type_hint != IS_VOID)) { @@ -1074,7 +1088,7 @@ static ZEND_COLD int zend_verify_missing_return_type(zend_function *zf, void **c } else { ce = zend_verify_arg_class_kind(ret_info); if (UNEXPECTED(!ce)) { - zend_verify_return_error(zf, "be an instance of ", ZSTR_VAL(ret_info->class_name), "none", ""); + zend_verify_return_error(zf, "be an instance of ", ZSTR_VAL(ret_info->class_name), (ret_info->allow_null ? " or null" : ""), "none", ""); return 0; } *cache_slot = (void*)ce; @@ -1082,14 +1096,17 @@ static ZEND_COLD int zend_verify_missing_return_type(zend_function *zf, void **c need_msg = (ce->ce_flags & ZEND_ACC_INTERFACE) ? "implement interface " : "be an instance of "; - zend_verify_return_error(zf, need_msg, ZSTR_VAL(ce->name), "none", ""); + need_or_null = + (ce->ce_flags & ZEND_ACC_INTERFACE) ? + " or be null" : " or null"; + zend_verify_return_error(zf, need_msg, ZSTR_VAL(ce->name), (ret_info->allow_null ? need_or_null : ""), "none", ""); return 0; } else if (ret_info->type_hint == IS_CALLABLE) { - zend_verify_return_error(zf, "be callable", "", "none", ""); + zend_verify_return_error(zf, "be callable", "", (ret_info->allow_null ? " or null" : ""), "none", ""); } else if (ret_info->type_hint == IS_ITERABLE) { - zend_verify_return_error(zf, "be iterable", "", "none", ""); + zend_verify_return_error(zf, "be iterable", "", (ret_info->allow_null ? " or null" : ""), "none", ""); } else { - zend_verify_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), "none", ""); + zend_verify_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), (ret_info->allow_null ? " or null" : ""), "none", ""); } return 0; } diff --git a/ext/spl/tests/spl_004.phpt b/ext/spl/tests/spl_004.phpt index ac44b9d684..be0cb804c6 100644 --- a/ext/spl/tests/spl_004.phpt +++ b/ext/spl/tests/spl_004.phpt @@ -78,7 +78,7 @@ int(5) int(6) int(4) ===ERRORS=== -Error: Argument 3 passed to iterator_apply() must be of the type array, integer given +Error: Argument 3 passed to iterator_apply() must be of the type array or null, integer given Error: iterator_apply() expects parameter 2 to be a valid callback, function 'non_existing_function' not found or invalid function name NULL Error: iterator_apply() expects at most 3 parameters, 4 given diff --git a/tests/classes/type_hinting_004.phpt b/tests/classes/type_hinting_004.phpt index 8883f26336..95df6264dd 100644 --- a/tests/classes/type_hinting_004.phpt +++ b/tests/classes/type_hinting_004.phpt @@ -152,7 +152,7 @@ Ensure type hints are enforced for functions invoked as callbacks. 0: Argument 1 passed to f1() must be an instance of A, integer given%s(%d) in f1; -0: Argument 1 passed to f2() must be an instance of A, integer given%s(%d) +0: Argument 1 passed to f2() must be an instance of A or null, integer given%s(%d) in f2; in f2; @@ -163,7 +163,7 @@ in f2; 0: Argument 1 passed to C::f1() must be an instance of A, integer given%s(%d) in C::f1 (static); -0: Argument 1 passed to C::f2() must be an instance of A, integer given%s(%d) +0: Argument 1 passed to C::f2() must be an instance of A or null, integer given%s(%d) in C::f2 (static); in C::f2 (static); @@ -174,7 +174,7 @@ in C::f2 (static); 0: Argument 1 passed to D::f1() must be an instance of A, integer given%s(%d) in C::f1 (instance); -0: Argument 1 passed to D::f2() must be an instance of A, integer given%s(%d) +0: Argument 1 passed to D::f2() must be an instance of A or null, integer given%s(%d) in C::f2 (instance); in C::f2 (instance);