Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Zend/tests/nullable_types/arraykey.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Explicitly nullable arraykey type
--FILE--
<?php

function _arraykey_(?arraykey $v): ?arraykey {
return $v;
}

var_dump(_arraykey_(null));
var_dump(_arraykey_("php"));
var_dump(_arraykey_(1));
--EXPECT--
NULL
string(3) "php"
int(1)
107 changes: 107 additions & 0 deletions Zend/tests/typehints/arraykey.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
--TEST--
Arraykey parameter, return, and property type hints
--FILE--
<?php

abstract class ArrayKeyFoo {
public arraykey $baz;

public function __construct() {
$this->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
2 changes: 2 additions & 0 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
9 changes: 8 additions & 1 deletion Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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}
};

Expand Down Expand Up @@ -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}
};

Expand Down Expand Up @@ -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)),
Expand Down
8 changes: 6 additions & 2 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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,
Expand Down
30 changes: 27 additions & 3 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down