Skip to content

[WIP][RFC]Spread Operator in Array #3640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
41 changes: 41 additions & 0 deletions Zend/tests/array_unpack/already_occupied.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
Appending to an array via unpack may fail
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip 64bit only"); ?>
--FILE--
<?php

$arr = [1, 2, 3];
var_dump([PHP_INT_MAX-1 => 0, ...$arr]);

var_dump([PHP_INT_MAX-1 => 0, ...[1, 2, 3]]);

const ARR = [1, 2, 3];
const ARR2 = [PHP_INT_MAX-1 => 0, ...ARR];
var_dump(ARR2);

?>
--EXPECTF--
Warning: Cannot add element to the array as the next element is already occupied in %s on line %d
array(2) {
[9223372036854775806]=>
int(0)
[9223372036854775807]=>
int(1)
}

Warning: Cannot add element to the array as the next element is already occupied in %s on line %d
array(2) {
[9223372036854775806]=>
int(0)
[9223372036854775807]=>
int(1)
}

Warning: Cannot add element to the array as the next element is already occupied in %s on line %d
array(2) {
[9223372036854775806]=>
int(0)
[9223372036854775807]=>
int(1)
}
119 changes: 119 additions & 0 deletions Zend/tests/array_unpack/basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
--TEST--
Basic array unpacking
--FILE--
<?php
$array = [1, 2, 3];

function getArr() {
return [4, 5];
}

function arrGen() {
for($i = 11; $i < 15; $i++) {
yield $i;
}
}

var_dump([...[]]);
var_dump([...[1, 2, 3]]);
var_dump([...$array]);
var_dump([...getArr()]);
var_dump([...arrGen()]);
var_dump([...new ArrayIterator(['a', 'b', 'c'])]);

var_dump([0, ...$array, ...getArr(), 6, 7, 8, 9, 10, ...arrGen()]);
var_dump([0, ...$array, ...$array, 'end']);

--EXPECT--
array(0) {
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(2) {
[0]=>
int(4)
[1]=>
int(5)
}
array(4) {
[0]=>
int(11)
[1]=>
int(12)
[2]=>
int(13)
[3]=>
int(14)
}
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
}
array(15) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
[4]=>
int(4)
[5]=>
int(5)
[6]=>
int(6)
[7]=>
int(7)
[8]=>
int(8)
[9]=>
int(9)
[10]=>
int(10)
[11]=>
int(11)
[12]=>
int(12)
[13]=>
int(13)
[14]=>
int(14)
}
array(8) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
[4]=>
int(1)
[5]=>
int(2)
[6]=>
int(3)
[7]=>
string(3) "end"
}
46 changes: 46 additions & 0 deletions Zend/tests/array_unpack/classes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
Array unpacking with classes
--FILE--
<?php

class C {
public const FOO = [0, ...self::ARR, 4];
public const ARR = [1, 2, 3];
public static $bar = [...self::ARR];
}

class D {
public const A = [...self::B];
public const B = [...self::A];
}

var_dump(C::FOO);
var_dump(C::$bar);

try {
var_dump(D::A);
} catch (Error $ex) {
echo "Exception: " . $ex->getMessage() . "\n";
}
--EXPECT--
array(5) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
[4]=>
int(4)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
Exception: Cannot declare self-referencing constant 'self::B'
10 changes: 10 additions & 0 deletions Zend/tests/array_unpack/in_destructuring.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Spread operator is not supported in destructuring assignments
--FILE--
<?php

[$head, ...$tail] = [1, 2, 3];

?>
--EXPECTF--
Fatal error: Spread operator is not supported in assignments in %s on line %d
17 changes: 17 additions & 0 deletions Zend/tests/array_unpack/non_integer_keys.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Array unpacking does not work with non-integer keys
--FILE--
<?php
function gen() {
yield [] => 1;
yield 1.23 => 123;
}

try {
[...gen()];
} catch (Error $ex) {
echo "Exception: " . $ex->getMessage() . "\n";
}

--EXPECT--
Exception: Cannot unpack Traversable with non-integer keys
17 changes: 17 additions & 0 deletions Zend/tests/array_unpack/ref1.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Array unpacking with element rc=1
--FILE--
<?php

$a = 1;
$b = [&$a]; //array (0 => (refcount=2, is_ref=1)=1)

unset($a); //array (0 => (refcount=1, is_ref=1)=1)

var_dump([...$b]); //array (0 => (refcount=0, is_ref=0)=1)

--EXPECT--
array(1) {
[0]=>
int(1)
}
21 changes: 21 additions & 0 deletions Zend/tests/array_unpack/string_keys.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
array unpacking with string keys (not supported)
--FILE--
<?php

try {
$array = [1, 2, "foo" => 3, 4];
var_dump([...$array]);
} catch (Error $ex) {
var_dump($ex->getMessage());
}
try {
$iterator = new ArrayIterator([1, 2, "foo" => 3, 4]);
var_dump([...$iterator]);
} catch (Error $ex) {
var_dump($ex->getMessage());
}

--EXPECT--
string(36) "Cannot unpack array with string keys"
string(42) "Cannot unpack Traversable with string keys"
14 changes: 14 additions & 0 deletions Zend/tests/array_unpack/undef_var.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
array unpacking with undefinded variable
--FILE--
<?php

var_dump([...$arr]);

--EXPECTF--
Notice: Undefined variable: arr in %s on line %d

Fatal error: Uncaught Error: Only arrays and Traversables can be unpacked in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/array_unpack/unpack_invalid_type_compile_time.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Unpacking non-array/Traversable detected at compile-time
--FILE--
<?php

var_dump([...42]);

?>
--EXPECTF--
Fatal error: Only arrays and Traversables can be unpacked in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/array_unpack/unpack_string_keys_compile_time.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Unpacking of string keys detected at compile-time
--FILE--
<?php

var_dump([...['a' => 'b']]);

?>
--EXPECTF--
Fatal error: Cannot unpack array with string keys in %s on line %d
39 changes: 39 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,32 @@ static int zend_ast_add_array_element(zval *result, zval *offset, zval *expr)
return SUCCESS;
}

static int zend_ast_add_unpacked_element(zval *result, zval *expr) {
if (EXPECTED(Z_TYPE_P(expr) == IS_ARRAY)) {
HashTable *ht = Z_ARRVAL_P(expr);
zval *val;
zend_string *key;

ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
if (key) {
zend_throw_error(NULL, "Cannot unpack array with string keys");
return FAILURE;
} else {
if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), val)) {
zend_error(E_WARNING, "Cannot add element to the array as the next element is already occupied");
break;
}
Z_TRY_ADDREF_P(val);
}
} ZEND_HASH_FOREACH_END();
return SUCCESS;
}

/* Objects or references cannot occur in a constant expression. */
zend_throw_error(NULL, "Only arrays and Traversables can be unpacked");
return FAILURE;
}

ZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)
{
zval op1, op2;
Expand Down Expand Up @@ -642,6 +668,19 @@ ZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_c
array_init(result);
for (i = 0; i < list->children; i++) {
zend_ast *elem = list->child[i];
if (elem->kind == ZEND_AST_UNPACK) {
if (UNEXPECTED(zend_ast_evaluate(&op1, elem->child[0], scope) != SUCCESS)) {
zval_ptr_dtor_nogc(result);
return FAILURE;
}
if (UNEXPECTED(zend_ast_add_unpacked_element(result, &op1) != SUCCESS)) {
zval_ptr_dtor_nogc(&op1);
zval_ptr_dtor_nogc(result);
return FAILURE;
}
zval_ptr_dtor_nogc(&op1);
continue;
}
if (elem->child[1]) {
if (UNEXPECTED(zend_ast_evaluate(&op1, elem->child[1], scope) != SUCCESS)) {
zval_ptr_dtor_nogc(result);
Expand Down
Loading