Skip to content
Permalink
Browse files

Implement spread operator in arrays

  • Loading branch information...
jhdxr authored and nikic committed Oct 6, 2018
1 parent 49de3ce commit e829d087299b59e638e91f18ea76f4dbe920c77b
@@ -166,6 +166,14 @@ PHP 7.4 UPGRADE NOTES

RFC: https://wiki.php.net/rfc/null_coalesce_equal_operator

. Added support for unpacking inside arrays. For example:

$arr1 = [3, 4];
$arr2 = [1, 2, ...$arr1, 5];
// $arr2 == [1, 2, 3, 4, 5]

RFC: https://wiki.php.net/rfc/spread_operator_for_array

. Support for WeakReferences has been added.
RFC: https://wiki.php.net/rfc/weakrefs

@@ -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)
}
@@ -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"
}
@@ -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'
@@ -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
@@ -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
@@ -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)
}
@@ -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"
@@ -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
@@ -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
@@ -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
@@ -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;
@@ -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);
Oops, something went wrong.

0 comments on commit e829d08

Please sign in to comment.
You can’t perform that action at this time.