]> granicus.if.org Git - php/commitdiff
Show "or null" in TypeErrors for nullable arg_infos
authorAndrea Faulds <ajf@ajf.me>
Mon, 8 Aug 2016 15:15:59 +0000 (16:15 +0100)
committerNikita Popov <nikic@php.net>
Thu, 11 Aug 2016 16:44:43 +0000 (18:44 +0200)
NEWS
UPGRADING
Zend/tests/ns_071.phpt
Zend/tests/ns_072.phpt
Zend/tests/return_types/030.phpt
Zend/tests/typehints/or_null.phpt [new file with mode: 0644]
Zend/zend_execute.c
ext/spl/tests/spl_004.phpt
tests/classes/type_hinting_004.phpt

diff --git a/NEWS b/NEWS
index 126f9ea372245eb9c3aad8639144b1b405386d5a..55bf0bcd6ab88258c7f1125d72191831a58f33ba 100644 (file)
--- 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)
index 8db36e1fc03ef9faf77c6d4052fc7396410a44c9..6ec40e7619f23d06bb22292a864e7de543495239 100644 (file)
--- 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
index 2f2fcfad1a3fe80c25cec14069edc896d81f5781..d7f1592b3865f6bd1690e6e47d9f8689c899430c 100644 (file)
@@ -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}
index 6375682890240a956eb3a3519a620139ea831319..34a9bbf6ad441247c22cbb8e90f134a9997b1e6b 100644 (file)
@@ -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}
index d1ceac8329903fffbec4aaeecccd64647f529dca..288137f05cc014aadd25a40f27a363a4071b5b83 100644 (file)
@@ -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 (file)
index 0000000..8c20052
--- /dev/null
@@ -0,0 +1,317 @@
+--TEST--
+Test "or null"/"or be null" in type-checking errors for userland functions
+--FILE--
+<?php
+
+// This should test every branch in zend_execute.c's `zend_verify_arg_type`, `zend_verify_return_type` and `zend_verify_missing_return_type` functions which produces an "or null"/"or be null" part in its error message
+
+function unloadedClass(?I\Dont\Exist $param) {}
+
+try {
+    unloadedClass(new \StdClass);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+class RealClass {}
+interface RealInterface {}
+
+function loadedClass(?RealClass $param) {}
+function loadedInterface(?RealInterface $param) {}
+
+try {
+    loadedClass(new \StdClass);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+try {
+    loadedInterface(new \StdClass);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+try {
+    unloadedClass(1);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+try {
+    loadedClass(1);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+try {
+    loadedInterface(1);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function callableF(?callable $param) {}
+
+try {
+    callableF(1);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function iterableF(?iterable $param) {}
+
+try {
+    iterableF(1);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function intF(?int $param) {}
+
+try {
+    intF(new StdClass);
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnUnloadedClass(): ?I\Dont\Exist {
+    return new \StdClass;
+}
+
+try {
+    returnUnloadedClass();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnLoadedClass(): ?RealClass {
+    return new \StdClass;
+}
+
+try {
+    returnLoadedClass();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnLoadedInterface(): ?RealInterface {
+    return new \StdClass;
+}
+
+try {
+    returnLoadedInterface();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnUnloadedClassScalar(): ?I\Dont\Exist {
+    return 1;
+}
+
+try {
+    returnUnloadedClassScalar();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnLoadedClassScalar(): ?RealClass {
+    return 1;
+}
+
+try {
+    returnLoadedClassScalar();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnLoadedInterfaceScalar(): ?RealInterface {
+    return 1;
+}
+
+try {
+    returnLoadedInterfaceScalar();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnCallable(): ?callable {
+    return 1;
+}
+
+try {
+    returnCallable();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnIterable(): ?iterable {
+    return 1;
+}
+
+try {
+    returnIterable();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnInt(): ?int {
+    return new \StdClass;
+}
+
+try {
+    returnInt();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnMissingUnloadedClass(): ?I\Dont\Exist {
+}
+
+try {
+    returnMissingUnloadedClass();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnMissingLoadedClass(): ?RealClass {
+}
+
+try {
+    returnMissingLoadedClass();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnMissingLoadedInterface(): ?RealInterface {
+}
+
+try {
+    returnMissingLoadedInterface();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnMissingCallable(): ?callable {
+}
+
+try {
+    returnMissingCallable();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnMissingIterable(): ?iterable {
+}
+
+try {
+    returnMissingIterable();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+function returnMissingInt(): ?int {
+}
+
+try {
+    returnMissingInt();
+} catch (\TypeError $e) {
+    echo $e, PHP_EOL;
+}
+
+?>
+--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}
index 234f62a592a66d517c522eccbb9a8a05b2222054..dee1d91f691485db8f57fbc33da5e9404f28f604 100644 (file)
@@ -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;
        }
index ac44b9d684600317b8c3d23ebe0993274a286c91..be0cb804c623370c971f08f2343a1e60bd9696c7 100644 (file)
@@ -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
index 8883f2633684093dfdcae1a879a9e5a5d9f85ca3..95df6264dda440d17d45ed9fb05596878282540f 100644 (file)
@@ -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);