diff --git a/Zend/tests/nullable_types/arraykey.phpt b/Zend/tests/nullable_types/arraykey.phpt new file mode 100644 index 0000000000000..650e0f23ff8c9 --- /dev/null +++ b/Zend/tests/nullable_types/arraykey.phpt @@ -0,0 +1,16 @@ +--TEST-- +Explicitly nullable arraykey type +--FILE-- +baz = 4; + } + + public function bar(string $bar): arraykey { + return $bar; + } + abstract public function baz(string ...$args): void; +} + +class ArrayKeyBar extends ArrayKeyFoo { + public arraykey $bar = 'foo'; + + public function bar(arraykey $bar): string { + return (string) parent::bar((string) $bar); + } + public function baz(arraykey ...$args): void {} +} + +$bar = new ArrayKeyBar(); +$bar->baz = 'f'; +$bar->baz = 4; + +try { + $bar->baz = false; +} catch(\TypeError $e) { + echo "OK\n"; +} + +try { + $bar->baz = 4.3; +} catch(\TypeError $e) { + echo "OK\n"; +} + +$bar->bar('f'); +$bar->bar(4); + +try { + $bar->baz(false); +} catch(\TypeError $e) { + echo "OK\n"; +} + +try { + $bar->baz(4.3); +} catch(\TypeError $e) { + echo "OK\n"; +} + +function _arraykey_nullable(?arraykey $b): void {} + +_arraykey_nullable(44); +_arraykey_nullable('d'); +_arraykey_nullable(null); + +try { + _arraykey_nullable(4.3); +} catch(\TypeError $e) { + echo "OK\n"; +} + + +function _arraykey_nullable_return_null(): ?arraykey { return null; } +function _arraykey_nullable_return_string(): ?arraykey { return 'foo'; } +function _arraykey_nullable_return_int(): ?arraykey { return 'bar'; } +function _arraykey_nullable_return_float(): ?arraykey { return 3.2; } + +_arraykey_nullable_return_null(); +_arraykey_nullable_return_string(); +_arraykey_nullable_return_int(); + +try { + _arraykey_nullable_return_float(); +} catch(\TypeError $e) { + echo "OK\n"; +} + +function _arraykey_return_string(): arraykey { return ''; } +function _arraykey_return_int(): arraykey { return 2; } +function _arraykey_return_float(): arraykey { return 3.3; } + +_arraykey_return_string(); +_arraykey_return_int(); + +try { + _arraykey_return_float(); +} catch(\TypeError $e) { + echo "OK\n"; +} + +--EXPECT-- +OK +OK +OK +OK +OK +OK +OK diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 79a86f9dcd84a..0b4c561c405ad 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -124,6 +124,8 @@ ZEND_API char *zend_get_type_by_const(int type) /* {{{ */ return "void"; case _IS_NUMBER: return "number"; + case IS_ARRAYKEY: + return "arraykey"; default: return "unknown"; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index af331bb3f1ed9..e0c1bd9fd31e9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -172,6 +172,7 @@ static const struct reserved_class_name reserved_class_names[] = { {ZEND_STRL("void")}, {ZEND_STRL("iterable")}, {ZEND_STRL("object")}, + {ZEND_STRL("arraykey")}, {NULL, 0} }; @@ -218,6 +219,7 @@ static const builtin_type_info builtin_types[] = { {ZEND_STRL("void"), IS_VOID}, {ZEND_STRL("iterable"), IS_ITERABLE}, {ZEND_STRL("object"), IS_OBJECT}, + {ZEND_STRL("arraykey"), IS_ARRAYKEY}, {NULL, 0, IS_UNDEF} }; @@ -5839,7 +5841,12 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) / zend_error_noreturn(E_COMPILE_ERROR, "Default value for property of type float can only be float or int"); } - } else if (!ZEND_SAME_FAKE_TYPE(ZEND_TYPE_CODE(type), Z_TYPE(value_zv))) { + } else if (ZEND_TYPE_CODE(type) == IS_ARRAYKEY) { + if (Z_TYPE(value_zv) != IS_STRING && Z_TYPE(value_zv) != IS_LONG) { + zend_error_noreturn(E_COMPILE_ERROR, + "Default value for property of type arraykey can only be string or int"); + } + } else if (!ZEND_SAME_FAKE_TYPE(ZEND_TYPE_CODE(type), Z_TYPE(value_zv))) { zend_error_noreturn(E_COMPILE_ERROR, "Default value for property of type %s can only be %s", zend_get_type_by_const(ZEND_TYPE_CODE(type)), diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 75169cafeba3e..c92ac69fe3b76 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1016,7 +1016,9 @@ static zend_always_inline zend_bool i_zend_check_property_type(zend_property_inf return 1; } else if (ZEND_TYPE_CODE(info->type) == IS_ITERABLE) { return zend_is_iterable(property); - } else { + } else if (ZEND_TYPE_CODE(info->type) == IS_ARRAYKEY && EXPECTED(Z_TYPE_P(property) == IS_STRING || Z_TYPE_P(property) == IS_LONG)) { + return 1; + } else { return zend_verify_scalar_type_hint(ZEND_TYPE_CODE(info->type), property, strict); } } @@ -1098,7 +1100,9 @@ static zend_always_inline zend_bool zend_check_type( } else if (ZEND_TYPE_CODE(type) == _IS_BOOL && EXPECTED(Z_TYPE_P(arg) == IS_FALSE || Z_TYPE_P(arg) == IS_TRUE)) { return 1; - } else if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) { + } else if (ZEND_TYPE_CODE(type) == IS_ARRAYKEY && EXPECTED(Z_TYPE_P(arg) == IS_STRING || Z_TYPE_P(arg) == IS_LONG)) { + return 1; + } else if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) { return 0; /* we cannot have conversions for typed refs */ } else { return zend_verify_scalar_type_hint(ZEND_TYPE_CODE(type), arg, diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index a4e437f271276..1c17896b95d3b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -183,6 +183,20 @@ static zend_always_inline zend_bool zend_iterable_compatibility_check(zend_arg_i } /* }}} */ +static zend_always_inline zend_bool zend_arraykey_compatibility_check(zend_arg_info *arg_info) /* {{{ */ +{ + if (ZEND_TYPE_CODE(arg_info->type) == IS_STRING) { + return 1; + } + + if (ZEND_TYPE_CODE(arg_info->type) == IS_LONG) { + 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) /* {{{ */ { ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_arg_info->type) && ZEND_TYPE_IS_SET(proto_arg_info->type)); @@ -238,7 +252,9 @@ static int zend_do_perform_type_hint_check(const zend_function *fe, zend_arg_inf } zend_string_release(proto_class_name); zend_string_release(fe_class_name); - } else if (ZEND_TYPE_CODE(fe_arg_info->type) != ZEND_TYPE_CODE(proto_arg_info->type)) { + } else if (ZEND_TYPE_CODE(fe_arg_info->type) == IS_ARRAYKEY && (ZEND_TYPE_CODE(proto_arg_info->type) == IS_STRING || ZEND_TYPE_CODE(proto_arg_info->type) == IS_LONG)) { + return 1; + } else if (ZEND_TYPE_CODE(fe_arg_info->type) != ZEND_TYPE_CODE(proto_arg_info->type)) { /* Incompatible built-in types */ return 0; } @@ -338,7 +354,11 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c return 0; } break; - + case IS_ARRAYKEY: + if (!zend_arraykey_compatibility_check(proto_arg_info)) { + return 0; + } + break; default: return 0; } @@ -371,7 +391,11 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c return 0; } break; - + case IS_ARRAYKEY: + if (!zend_arraykey_compatibility_check(fe->common.arg_info - 1)) { + return 0; + } + break; default: return 0; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 6ba488d88b05b..0f04af4f1dff1 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -422,6 +422,7 @@ struct _zend_ast_ref { #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 +#define IS_ARRAYKEY 21 /* constant expressions */ #define IS_CONSTANT_AST 11