diff --git a/Zend/tests/return_types/030.phpt b/Zend/tests/return_types/030.phpt new file mode 100644 index 0000000000000..d1ceac8329903 --- /dev/null +++ b/Zend/tests/return_types/030.phpt @@ -0,0 +1,23 @@ +--TEST-- +Nullable return value +--FILE-- + +--EXPECTF-- +ok +ok + +Fatal error: Uncaught TypeError: Return value of foo() must be of the type array, integer returned in %s030.php:3 +Stack trace: +#0 %s030.php(10): foo(0) +#1 {main} + thrown in %s030.php on line 3 diff --git a/Zend/tests/return_types/031.phpt b/Zend/tests/return_types/031.phpt new file mode 100644 index 0000000000000..91ee2f8ce4b2b --- /dev/null +++ b/Zend/tests/return_types/031.phpt @@ -0,0 +1,14 @@ +--TEST-- +Nullable return type inheritance rules (non-nullable and nullable) +--FILE-- + +DONE +--EXPECTF-- +Fatal error: Declaration of B::foo(): ?int must be compatible with A::foo(): int in %s031.php on line 7 \ No newline at end of file diff --git a/Zend/tests/return_types/032.phpt b/Zend/tests/return_types/032.phpt new file mode 100644 index 0000000000000..00790b5d608ef --- /dev/null +++ b/Zend/tests/return_types/032.phpt @@ -0,0 +1,14 @@ +--TEST-- +Nullable return type inheritance rules (nullable and non-nullable) +--FILE-- + +DONE +--EXPECT-- +DONE diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5f3114d10e1ae..61234419e9634 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -4844,6 +4844,11 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ arg_infos->allow_null = 0; arg_infos->class_name = NULL; + if (return_type_ast->attr & ZEND_TYPE_NULLABLE) { + arg_infos->allow_null = 1; + return_type_ast->attr &= ~ZEND_TYPE_NULLABLE; + } + zend_compile_typename(return_type_ast, arg_infos); arg_infos++; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 833aeab2972ed..404517943e756 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -835,6 +835,8 @@ ZEND_API void zend_assert_valid_class_name(const zend_string *const_name); #define ZEND_NAME_NOT_FQ 1 #define ZEND_NAME_RELATIVE 2 +#define ZEND_TYPE_NULLABLE (1<<8) + /* var status for backpatching */ #define BP_VAR_R 0 #define BP_VAR_W 1 diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index c4dbc879e8e70..29e8d15b98f8b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -341,6 +341,10 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c 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) { + return 0; + } } return 1; } @@ -503,6 +507,9 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function if (fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { smart_str_appends(&str, ": "); + if (fptr->common.arg_info[-1].allow_null) { + smart_str_appendc(&str, '?'); + } zend_append_type_hint(&str, fptr, fptr->common.arg_info - 1, 1); } smart_str_0(&str); @@ -585,7 +592,8 @@ static void do_inheritance_check_on_method(zend_function *child, zend_function * error_verb = "must"; } else if ((parent->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) && (!(child->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) || - !zend_do_perform_type_hint_check(child, child->common.arg_info - 1, parent, parent->common.arg_info - 1))) { + !zend_do_perform_type_hint_check(child, child->common.arg_info - 1, parent, parent->common.arg_info - 1) || + (child->common.arg_info[-1].allow_null && !parent->common.arg_info[-1].allow_null))) { error_level = E_COMPILE_ERROR; error_verb = "must"; } else { diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 0ec991e9086fa..0f70d1ea29415 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -651,6 +651,7 @@ type: return_type: /* empty */ { $$ = NULL; } | ':' type { $$ = $2; } + | ':' '?' type { $$ = $3; $$->attr |= ZEND_TYPE_NULLABLE; } ; argument_list: