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/type_declarations/list_type_nullable.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
nullable list<T> parameter accepts null and matching object list
--FILE--
<?php
class User {}

function f(?list<User> $users): void {
echo $users === null ? "null\n" : count($users) . "\n";
}

f(null);
f([new User]);
?>
--EXPECT--
null
1
14 changes: 14 additions & 0 deletions Zend/tests/type_declarations/list_type_param_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
list<T> parameter accepts matching object list
--FILE--
<?php
class User {}

function f(list<User> $users): void {
echo count($users), "\n";
}

f([new User, new User]);
?>
--EXPECT--
2
22 changes: 22 additions & 0 deletions Zend/tests/type_declarations/list_type_param_contravariance.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
array parameter type is contravariant with list<T>
--FILE--
<?php
class User {}

class A {
public function setItems(list<User> $items): void {
echo "A\n";
}
}

class B extends A {
public function setItems(array $items): void {
echo count($items), "\n";
}
}

(new B)->setItems([new User]);
?>
--EXPECT--
1
18 changes: 18 additions & 0 deletions Zend/tests/type_declarations/list_type_param_rejects_assoc.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
list<T> parameter rejects associative array
--FILE--
<?php
class User {}

function f(list<User> $users): void {
echo "inside\n";
}

try {
f(['x' => new User]);
} catch (TypeError $e) {
echo "TypeError\n";
}
?>
--EXPECT--
TypeError
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
list<T> parameter rejects non-matching object element
--FILE--
<?php
class User {}

function f(list<User> $users): void {
echo "inside\n";
}

try {
f([new stdClass]);
} catch (TypeError $e) {
echo "TypeError\n";
}
?>
--EXPECT--
TypeError
23 changes: 23 additions & 0 deletions Zend/tests/type_declarations/list_type_property_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
list<T> property accepts matching object list and rejects bad element
--FILE--
<?php
class User {}

class Box {
public list<User> $users;
}

$box = new Box;
$box->users = [new User, new User];
echo count($box->users), "\n";

try {
$box->users = [new stdClass];
} catch (TypeError $e) {
echo "TypeError\n";
}
?>
--EXPECT--
2
TypeError
18 changes: 18 additions & 0 deletions Zend/tests/type_declarations/list_type_property_nullable.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
nullable list<T> property accepts null and matching object list
--FILE--
<?php
class User {}

class Box {
public ?list<User> $users = null;
}

$box = new Box;
var_dump($box->users);
$box->users = [new User];
echo count($box->users), "\n";
?>
--EXPECT--
NULL
1
15 changes: 15 additions & 0 deletions Zend/tests/type_declarations/list_type_property_reflection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
list<T> property reflection string
--FILE--
<?php
class User {}

class Box {
public list<User> $users;
}

$prop = new ReflectionProperty(Box::class, 'users');
echo (string) $prop->getType(), "\n";
?>
--EXPECT--
list<User>
13 changes: 13 additions & 0 deletions Zend/tests/type_declarations/list_type_reflection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
list<T> parameter reflection string
--FILE--
<?php
class User {}

function f(list<User> $users): void {}

$param = (new ReflectionFunction('f'))->getParameters()[0];
echo (string) $param->getType(), "\n";
?>
--EXPECT--
list<User>
14 changes: 14 additions & 0 deletions Zend/tests/type_declarations/list_type_return_basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
list<T> return type accepts matching object list
--FILE--
<?php
class User {}

function f(): list<User> {
return [new User, new User];
}

echo count(f()), "\n";
?>
--EXPECT--
2
22 changes: 22 additions & 0 deletions Zend/tests/type_declarations/list_type_return_covariance.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
list<T> return type is covariant with array
--FILE--
<?php
class User {}

class A {
public function getItems(): array {
return [];
}
}

class B extends A {
public function getItems(): list<User> {
return [new User];
}
}

echo count((new B)->getItems()), "\n";
?>
--EXPECT--
1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
list<T> return type rejects non-matching object element
--FILE--
<?php
class User {}

function f(): list<User> {
return [new stdClass];
}

try {
f();
} catch (TypeError $e) {
echo "TypeError\n";
}
?>
--EXPECT--
TypeError
20 changes: 20 additions & 0 deletions Zend/tests/type_declarations/list_type_scalar_string.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
list<string> parameter accepts strings and rejects non-string elements
--FILE--
<?php
declare(strict_types=1);
function f(list<string> $items): void {
echo count($items), "\n";
}

f(["a", "b"]);

try {
f(["a", 123]);
} catch (TypeError $e) {
echo "TypeError\n";
}
?>
--EXPECT--
2
TypeError
10 changes: 10 additions & 0 deletions Zend/tests/type_declarations/list_type_union_null.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
list<T> cannot be part of a union type
--FILE--
<?php
class User {}

function f(list<User>|null $users): void {}
?>
--EXPECTF--
Fatal error: Type list<T> cannot be part of a union type in %s on line %d
5 changes: 5 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -2398,6 +2398,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
default: ZEND_UNREACHABLE();
}
break;
case ZEND_AST_LIST_TYPE:
APPEND_STR("list<");
zend_ast_export_ex(str, ast->child[0], priority, indent);
APPEND_STR(">");
break;
case ZEND_AST_TYPE:
switch (ast->attr & ~ZEND_TYPE_NULLABLE) {
case IS_ARRAY: APPEND_STR("array");
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ enum _zend_ast_kind {

/* 1 child node */
ZEND_AST_VAR = 1 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_LIST_TYPE,
ZEND_AST_CONST,
ZEND_AST_UNPACK,
ZEND_AST_UNARY_PLUS,
Expand Down
22 changes: 21 additions & 1 deletion Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,13 @@ static zend_string *add_intersection_type(zend_string *str,
zend_string *zend_type_to_string_resolved(const zend_type type, const zend_class_entry *scope) {
zend_string *str = NULL;

if (ZEND_TYPE_IS_TYPED_LIST(type)) {
zend_string *inner = zend_type_to_string_resolved(ZEND_TYPE_LIST(type)->types[0], scope);
zend_string *result = zend_string_concat3("list<", 5, ZSTR_VAL(inner), ZSTR_LEN(inner), ">", 1);
zend_string_release(inner);
return result;
}

/* Pure intersection type */
if (ZEND_TYPE_IS_INTERSECTION(type)) {
ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type));
Expand Down Expand Up @@ -7364,6 +7371,16 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */
static zend_type zend_compile_single_typename(zend_ast *ast)
{
ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE));
if (ast->kind == ZEND_AST_LIST_TYPE) {
zend_ast *inner_ast = ast->child[0];
zend_type inner_type = zend_compile_single_typename(inner_ast);
zend_type_list *type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(1));
type_list->num_types = 1;
type_list->types[0] = inner_type;
zend_type type = ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY | _ZEND_TYPE_TYPED_LIST_BIT | _ZEND_TYPE_ARENA_BIT);
ZEND_TYPE_SET_LIST(type, type_list);
return type;
}
if (ast->kind == ZEND_AST_TYPE) {
if (ast->attr == IS_STATIC && !CG(active_class_entry) && zend_is_scope_known()) {
zend_error_noreturn(E_COMPILE_ERROR,
Expand Down Expand Up @@ -7592,6 +7609,9 @@ static zend_type zend_compile_typename_ex(
}

single_type = zend_compile_single_typename(type_ast);
if (ZEND_TYPE_IS_TYPED_LIST(single_type)) {
zend_error_noreturn(E_COMPILE_ERROR, "Type list<T> cannot be part of a union type");
}
uint32_t single_type_mask = ZEND_TYPE_PURE_MASK(single_type);

if (single_type_mask == MAY_BE_ANY) {
Expand Down Expand Up @@ -8197,7 +8217,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
ZEND_TYPE_FULL_MASK(arg_info->type) |= arg_info_flags;
if (opcode == ZEND_RECV) {
opline->op2.num = type_ast ?
ZEND_TYPE_FULL_MASK(arg_info->type) : MAY_BE_ANY;
(ZEND_TYPE_IS_TYPED_LIST(arg_info->type) ? 0 : ZEND_TYPE_FULL_MASK(arg_info->type)) : MAY_BE_ANY;
}

if (is_promoted) {
Expand Down
45 changes: 44 additions & 1 deletion Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -1036,9 +1036,16 @@ static bool zend_check_and_resolve_property_or_class_constant_class_type(
return false;
}

static zend_always_inline bool zend_check_typed_list_type(
const zend_type *type, zval *arg, bool is_return_type, bool is_internal);

static zend_always_inline bool i_zend_check_property_type(const zend_property_info *info, zval *property, bool strict)
{
ZEND_ASSERT(!Z_ISREF_P(property));
if (UNEXPECTED(ZEND_TYPE_IS_TYPED_LIST(info->type))) {
return zend_check_typed_list_type(&info->type, property, false, false);
}

if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) {
return 1;
}
Expand Down Expand Up @@ -1148,10 +1155,45 @@ static bool zend_check_intersection_type_from_list(
return true;
}

static zend_always_inline bool zend_check_type(
const zend_type *type, zval *arg, bool is_return_type, bool is_internal);

static zend_always_inline bool zend_check_typed_list_type(
const zend_type *type, zval *arg, bool is_return_type, bool is_internal)
{
const zend_type *inner_type;
zval *element;

if (UNEXPECTED(Z_TYPE_P(arg) != IS_ARRAY)) {
return false;
}

if (UNEXPECTED(!zend_array_is_list(Z_ARRVAL_P(arg)))) {
return false;
}

inner_type = &ZEND_TYPE_LIST(*type)->types[0];

ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arg), element) {
if (UNEXPECTED(!zend_check_type(inner_type, element, is_return_type, is_internal))) {
return false;
}
} ZEND_HASH_FOREACH_END();

return true;
}

static zend_always_inline bool zend_check_type_slow(
const zend_type *type, zval *arg, const zend_reference *ref,
bool is_return_type, bool is_internal)
{
if (UNEXPECTED(ZEND_TYPE_IS_TYPED_LIST(*type))) {
if (Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(*type)) {
return true;
}
return zend_check_typed_list_type(type, arg, is_return_type, is_internal);
}

if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
zend_class_entry *ce;
if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) {
Expand Down Expand Up @@ -1222,7 +1264,8 @@ static zend_always_inline bool zend_check_type(
arg = Z_REFVAL_P(arg);
}

if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(*type, Z_TYPE_P(arg)))) {
if (EXPECTED(!ZEND_TYPE_IS_TYPED_LIST(*type))
&& EXPECTED(ZEND_TYPE_CONTAINS_CODE(*type, Z_TYPE_P(arg)))) {
return 1;
}

Expand Down
Loading
Loading