diff --git a/Zend/tests/nullable_types/001.phpt b/Zend/tests/nullable_types/001.phpt new file mode 100644 index 0000000000000..41b4fd5b4612f --- /dev/null +++ b/Zend/tests/nullable_types/001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Nullable argument +--FILE-- + +--EXPECTF-- +ok +ok + +Catchable fatal error: Argument 1 passed to foo() must be of the type array, integer given, called in %s001.php on line 8 and defined in %s001.php on line 2 diff --git a/Zend/tests/nullable_types/002.phpt b/Zend/tests/nullable_types/002.phpt new file mode 100644 index 0000000000000..9c831165dd8b8 --- /dev/null +++ b/Zend/tests/nullable_types/002.phpt @@ -0,0 +1,17 @@ +--TEST-- +Nullable argument with default NULL value +--FILE-- + +--EXPECTF-- +ok +ok + +Catchable fatal error: Argument 1 passed to foo() must be of the type array, integer given, called in %s002.php on line 8 and defined in %s002.php on line 2 diff --git a/Zend/tests/nullable_types/003.phpt b/Zend/tests/nullable_types/003.phpt new file mode 100644 index 0000000000000..f45ca2acf4ac0 --- /dev/null +++ b/Zend/tests/nullable_types/003.phpt @@ -0,0 +1,19 @@ +--TEST-- +Nullable return value +--FILE-- + +--EXPECTF-- +ok +ok + +Catchable fatal error: Return value of foo() must be of the type array, integer returned in %s003.php on line 3 diff --git a/Zend/tests/nullable_types/inheritance001.phpt b/Zend/tests/nullable_types/inheritance001.phpt new file mode 100644 index 0000000000000..242208438eb93 --- /dev/null +++ b/Zend/tests/nullable_types/inheritance001.phpt @@ -0,0 +1,15 @@ +--TEST-- +Nullable parameter type inheritance rules (nullable and nullable) +--FILE-- + +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/nullable_types/inheritance002.phpt b/Zend/tests/nullable_types/inheritance002.phpt new file mode 100644 index 0000000000000..13361db90cbde --- /dev/null +++ b/Zend/tests/nullable_types/inheritance002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Nullable parameter type inheritance rules (nullable and not-nullable) +--FILE-- + +DONE +--EXPECTF-- +Strict Standards: Declaration of B::foo() should be compatible with A::foo(?array $a) in %sinheritance002.php on line 8 +DONE diff --git a/Zend/tests/nullable_types/inheritance003.phpt b/Zend/tests/nullable_types/inheritance003.phpt new file mode 100644 index 0000000000000..3bec9e2d153f1 --- /dev/null +++ b/Zend/tests/nullable_types/inheritance003.phpt @@ -0,0 +1,15 @@ +--TEST-- +Nullable parameter type inheritance rules (not-nullable and nullable) +--FILE-- + +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/nullable_types/inheritance004.phpt b/Zend/tests/nullable_types/inheritance004.phpt new file mode 100644 index 0000000000000..eed37e9609fcf --- /dev/null +++ b/Zend/tests/nullable_types/inheritance004.phpt @@ -0,0 +1,15 @@ +--TEST-- +Nullable return type inheritance rules (nullable and nullable) +--FILE-- + +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/nullable_types/inheritance005.phpt b/Zend/tests/nullable_types/inheritance005.phpt new file mode 100644 index 0000000000000..77d98f66fb8bf --- /dev/null +++ b/Zend/tests/nullable_types/inheritance005.phpt @@ -0,0 +1,15 @@ +--TEST-- +Nullable return type inheritance rules (nullable and not-nullable) +--FILE-- + +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/nullable_types/inheritance006.phpt b/Zend/tests/nullable_types/inheritance006.phpt new file mode 100644 index 0000000000000..47ba8d183327c --- /dev/null +++ b/Zend/tests/nullable_types/inheritance006.phpt @@ -0,0 +1,15 @@ +--TEST-- +Nullable return type inheritance rules (not-nullable and nullable) +--FILE-- + +DONE +--EXPECTF-- +Fatal error: Declaration of B::foo() must be compatible with A::foo(): array in %sinheritance006.php on line 8 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index bdd0c17583b9c..1c47189000721 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -621,12 +621,12 @@ zend_string *zend_resolve_non_class_name( return zend_string_init(name->val + 1, name->len - 1, 0); } - if (type == ZEND_NAME_FQ) { + if (type & ZEND_NAME_FQ) { *is_fully_qualified = 1; return zend_string_copy(name); } - if (type == ZEND_NAME_RELATIVE) { + if (type & ZEND_NAME_RELATIVE) { *is_fully_qualified = 1; return zend_prefix_with_ns(name); } @@ -683,11 +683,11 @@ zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* {{{ */ { char *compound; - if (type == ZEND_NAME_RELATIVE) { + if (type & ZEND_NAME_RELATIVE) { return zend_prefix_with_ns(name); } - if (type == ZEND_NAME_FQ || name->val[0] == '\\') { + if ((type & ZEND_NAME_FQ) || name->val[0] == '\\') { /* Remove \ prefix (only relevant if this is a string rather than a label) */ if (name->val[0] == '\\') { name = zend_string_init(name->val + 1, name->len - 1, 0); @@ -1902,7 +1902,7 @@ static inline zend_bool zend_is_const_default_class_ref(zend_ast *name_ast) /* { } /* Fully qualified names are always default refs */ - if (!name_ast->attr) { + if (name_ast->attr & (ZEND_NAME_FQ | ZEND_NAME_RELATIVE)) { return 1; } @@ -3824,11 +3824,11 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, zend_bool is_ arg_infos->pass_by_reference = (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0; arg_infos->is_variadic = 0; arg_infos->type_hint = 0; - arg_infos->allow_null = 0; + arg_infos->allow_null = (return_type_ast->attr & ZEND_TYPE_NULLABLE) != 0; arg_infos->class_name = NULL; if (return_type_ast->kind == ZEND_AST_TYPE) { - arg_infos->type_hint = return_type_ast->attr; + arg_infos->type_hint = return_type_ast->attr & ZEND_TYPE_MASK; } else { zend_string *class_name = zend_ast_get_str(return_type_ast); @@ -3929,10 +3929,10 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, zend_bool is_ && strcasecmp(Z_STRVAL(default_node.u.constant), "NULL") == 0)); op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS; - arg_info->allow_null = has_null_default; + arg_info->allow_null = (type_ast->attr & ZEND_TYPE_NULLABLE) || has_null_default; if (type_ast->kind == ZEND_AST_TYPE) { - arg_info->type_hint = type_ast->attr; + arg_info->type_hint = type_ast->attr & ZEND_TYPE_MASK; if (arg_info->type_hint == IS_ARRAY) { if (default_ast && !has_null_default && Z_TYPE(default_node.u.constant) != IS_ARRAY diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 8a9b37cc6c6f8..33b6086a15a14 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -770,9 +770,12 @@ int zend_add_literal(zend_op_array *op_array, zval *zv); #define ZEND_PARAM_REF (1<<0) #define ZEND_PARAM_VARIADIC (1<<1) -#define ZEND_NAME_FQ 0 -#define ZEND_NAME_NOT_FQ 1 -#define ZEND_NAME_RELATIVE 2 +#define ZEND_NAME_FQ (1<<0) +#define ZEND_NAME_NOT_FQ (1<<1) +#define ZEND_NAME_RELATIVE (1<<2) + +#define ZEND_TYPE_MASK 0xff +#define ZEND_TYPE_NULLABLE (1<<8) /* unset types */ #define ZEND_UNSET_REG 0 diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index bcbf3c7b758a4..2e194451bbbd2 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -372,6 +372,10 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) { return 0; } + + if (proto_arg_info->allow_null && !fe_arg_info->allow_null) { + return 0; + } } /* check return type compataibility */ @@ -382,6 +386,9 @@ 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; } @@ -423,6 +430,28 @@ static void zend_append_type_hint(smart_str *str, zend_function *fptr, zend_arg_ } /* }}} */ +static zval *zend_get_param_default_value(zend_op_array *op_array, int num) /* {{{ */ +{ + zend_op *op = op_array->opcodes; + zend_op *end = op + op_array->last; + + while (op < end) { + if (op->opcode == ZEND_RECV) { + /* skip */ + } else if (op->opcode == ZEND_RECV_INIT) { + if (op->op1.num == num + 1) { + return RT_CONSTANT(op_array, op->op2); + } + } else { + break; + } + op++; + } + return NULL; +} +/* }}} */ + + static zend_string *zend_get_function_declaration(zend_function *fptr) /* {{{ */ { smart_str str = {0}; @@ -449,6 +478,18 @@ static zend_string *zend_get_function_declaration(zend_function *fptr) /* {{{ */ num_args++; } for (i = 0; i < num_args;) { + if (arg_info->allow_null && arg_info->type_hint) { + if (fptr->type == ZEND_USER_FUNCTION) { + zval *zv = zend_get_param_default_value(&fptr->op_array, i); + + if (i < required || (zv && Z_TYPE_P(zv) != IS_NULL)) { + smart_str_appendc(&str, '?'); + } + } else if (i < required) { + smart_str_appendc(&str, '?'); + } + } + zend_append_type_hint(&str, fptr, arg_info, 0); if (arg_info->pass_by_reference) { @@ -475,25 +516,9 @@ static zend_string *zend_get_function_declaration(zend_function *fptr) /* {{{ */ if (i >= required && !arg_info->is_variadic) { smart_str_appends(&str, " = "); if (fptr->type == ZEND_USER_FUNCTION) { - zend_op *precv = NULL; - { - uint32_t idx = i; - zend_op *op = fptr->op_array.opcodes; - zend_op *end = op + fptr->op_array.last; - - ++idx; - while (op < end) { - if ((op->opcode == ZEND_RECV || op->opcode == ZEND_RECV_INIT) - && op->op1.num == (zend_ulong)idx) - { - precv = op; - } - ++op; - } - } - if (precv && precv->opcode == ZEND_RECV_INIT && precv->op2_type != IS_UNUSED) { - zval *zv = RT_CONSTANT(&fptr->op_array, precv->op2); + zval *zv = zend_get_param_default_value(&fptr->op_array, i); + if (zv) { if (Z_TYPE_P(zv) == IS_CONSTANT) { smart_str_append(&str, Z_STR_P(zv)); } else if (Z_TYPE_P(zv) == IS_FALSE) { @@ -535,6 +560,9 @@ static zend_string *zend_get_function_declaration(zend_function *fptr) /* {{{ */ 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); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 7acec68b47ffc..964e78f4268f6 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -572,6 +572,7 @@ parameter: optional_type: /* empty */ { $$ = NULL; } | type { $$ = $1; } + | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } ; type: @@ -581,8 +582,9 @@ type: ; return_type: - /* empty */ { $$ = NULL; } - | ':' type { $$ = $2; } + /* empty */ { $$ = NULL; } + | ':' type { $$ = $2; } + | ':' '?' type { $$ = $3; $$->attr |= ZEND_TYPE_NULLABLE; } ; argument_list: