Skip to content

Commit e829d08

Browse files
jhdxrnikic
authored andcommitted
1 parent 49de3ce commit e829d08

25 files changed

+1358
-556
lines changed

UPGRADING

+8
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ PHP 7.4 UPGRADE NOTES
166166

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

169+
. Added support for unpacking inside arrays. For example:
170+
171+
$arr1 = [3, 4];
172+
$arr2 = [1, 2, ...$arr1, 5];
173+
// $arr2 == [1, 2, 3, 4, 5]
174+
175+
RFC: https://wiki.php.net/rfc/spread_operator_for_array
176+
169177
. Support for WeakReferences has been added.
170178
RFC: https://wiki.php.net/rfc/weakrefs
171179

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Appending to an array via unpack may fail
3+
--SKIPIF--
4+
<?php if (PHP_INT_SIZE != 8) die("skip 64bit only"); ?>
5+
--FILE--
6+
<?php
7+
8+
$arr = [1, 2, 3];
9+
var_dump([PHP_INT_MAX-1 => 0, ...$arr]);
10+
11+
var_dump([PHP_INT_MAX-1 => 0, ...[1, 2, 3]]);
12+
13+
const ARR = [1, 2, 3];
14+
const ARR2 = [PHP_INT_MAX-1 => 0, ...ARR];
15+
var_dump(ARR2);
16+
17+
?>
18+
--EXPECTF--
19+
Warning: Cannot add element to the array as the next element is already occupied in %s on line %d
20+
array(2) {
21+
[9223372036854775806]=>
22+
int(0)
23+
[9223372036854775807]=>
24+
int(1)
25+
}
26+
27+
Warning: Cannot add element to the array as the next element is already occupied in %s on line %d
28+
array(2) {
29+
[9223372036854775806]=>
30+
int(0)
31+
[9223372036854775807]=>
32+
int(1)
33+
}
34+
35+
Warning: Cannot add element to the array as the next element is already occupied in %s on line %d
36+
array(2) {
37+
[9223372036854775806]=>
38+
int(0)
39+
[9223372036854775807]=>
40+
int(1)
41+
}

Zend/tests/array_unpack/basic.phpt

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
--TEST--
2+
Basic array unpacking
3+
--FILE--
4+
<?php
5+
$array = [1, 2, 3];
6+
7+
function getArr() {
8+
return [4, 5];
9+
}
10+
11+
function arrGen() {
12+
for($i = 11; $i < 15; $i++) {
13+
yield $i;
14+
}
15+
}
16+
17+
var_dump([...[]]);
18+
var_dump([...[1, 2, 3]]);
19+
var_dump([...$array]);
20+
var_dump([...getArr()]);
21+
var_dump([...arrGen()]);
22+
var_dump([...new ArrayIterator(['a', 'b', 'c'])]);
23+
24+
var_dump([0, ...$array, ...getArr(), 6, 7, 8, 9, 10, ...arrGen()]);
25+
var_dump([0, ...$array, ...$array, 'end']);
26+
27+
--EXPECT--
28+
array(0) {
29+
}
30+
array(3) {
31+
[0]=>
32+
int(1)
33+
[1]=>
34+
int(2)
35+
[2]=>
36+
int(3)
37+
}
38+
array(3) {
39+
[0]=>
40+
int(1)
41+
[1]=>
42+
int(2)
43+
[2]=>
44+
int(3)
45+
}
46+
array(2) {
47+
[0]=>
48+
int(4)
49+
[1]=>
50+
int(5)
51+
}
52+
array(4) {
53+
[0]=>
54+
int(11)
55+
[1]=>
56+
int(12)
57+
[2]=>
58+
int(13)
59+
[3]=>
60+
int(14)
61+
}
62+
array(3) {
63+
[0]=>
64+
string(1) "a"
65+
[1]=>
66+
string(1) "b"
67+
[2]=>
68+
string(1) "c"
69+
}
70+
array(15) {
71+
[0]=>
72+
int(0)
73+
[1]=>
74+
int(1)
75+
[2]=>
76+
int(2)
77+
[3]=>
78+
int(3)
79+
[4]=>
80+
int(4)
81+
[5]=>
82+
int(5)
83+
[6]=>
84+
int(6)
85+
[7]=>
86+
int(7)
87+
[8]=>
88+
int(8)
89+
[9]=>
90+
int(9)
91+
[10]=>
92+
int(10)
93+
[11]=>
94+
int(11)
95+
[12]=>
96+
int(12)
97+
[13]=>
98+
int(13)
99+
[14]=>
100+
int(14)
101+
}
102+
array(8) {
103+
[0]=>
104+
int(0)
105+
[1]=>
106+
int(1)
107+
[2]=>
108+
int(2)
109+
[3]=>
110+
int(3)
111+
[4]=>
112+
int(1)
113+
[5]=>
114+
int(2)
115+
[6]=>
116+
int(3)
117+
[7]=>
118+
string(3) "end"
119+
}

Zend/tests/array_unpack/classes.phpt

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
Array unpacking with classes
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public const FOO = [0, ...self::ARR, 4];
8+
public const ARR = [1, 2, 3];
9+
public static $bar = [...self::ARR];
10+
}
11+
12+
class D {
13+
public const A = [...self::B];
14+
public const B = [...self::A];
15+
}
16+
17+
var_dump(C::FOO);
18+
var_dump(C::$bar);
19+
20+
try {
21+
var_dump(D::A);
22+
} catch (Error $ex) {
23+
echo "Exception: " . $ex->getMessage() . "\n";
24+
}
25+
--EXPECT--
26+
array(5) {
27+
[0]=>
28+
int(0)
29+
[1]=>
30+
int(1)
31+
[2]=>
32+
int(2)
33+
[3]=>
34+
int(3)
35+
[4]=>
36+
int(4)
37+
}
38+
array(3) {
39+
[0]=>
40+
int(1)
41+
[1]=>
42+
int(2)
43+
[2]=>
44+
int(3)
45+
}
46+
Exception: Cannot declare self-referencing constant 'self::B'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Spread operator is not supported in destructuring assignments
3+
--FILE--
4+
<?php
5+
6+
[$head, ...$tail] = [1, 2, 3];
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Spread operator is not supported in assignments in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Array unpacking does not work with non-integer keys
3+
--FILE--
4+
<?php
5+
function gen() {
6+
yield [] => 1;
7+
yield 1.23 => 123;
8+
}
9+
10+
try {
11+
[...gen()];
12+
} catch (Error $ex) {
13+
echo "Exception: " . $ex->getMessage() . "\n";
14+
}
15+
16+
--EXPECT--
17+
Exception: Cannot unpack Traversable with non-integer keys

Zend/tests/array_unpack/ref1.phpt

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Array unpacking with element rc=1
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
$b = [&$a]; //array (0 => (refcount=2, is_ref=1)=1)
8+
9+
unset($a); //array (0 => (refcount=1, is_ref=1)=1)
10+
11+
var_dump([...$b]); //array (0 => (refcount=0, is_ref=0)=1)
12+
13+
--EXPECT--
14+
array(1) {
15+
[0]=>
16+
int(1)
17+
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
array unpacking with string keys (not supported)
3+
--FILE--
4+
<?php
5+
6+
try {
7+
$array = [1, 2, "foo" => 3, 4];
8+
var_dump([...$array]);
9+
} catch (Error $ex) {
10+
var_dump($ex->getMessage());
11+
}
12+
try {
13+
$iterator = new ArrayIterator([1, 2, "foo" => 3, 4]);
14+
var_dump([...$iterator]);
15+
} catch (Error $ex) {
16+
var_dump($ex->getMessage());
17+
}
18+
19+
--EXPECT--
20+
string(36) "Cannot unpack array with string keys"
21+
string(42) "Cannot unpack Traversable with string keys"
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
array unpacking with undefinded variable
3+
--FILE--
4+
<?php
5+
6+
var_dump([...$arr]);
7+
8+
--EXPECTF--
9+
Notice: Undefined variable: arr in %s on line %d
10+
11+
Fatal error: Uncaught Error: Only arrays and Traversables can be unpacked in %s:%d
12+
Stack trace:
13+
#0 {main}
14+
thrown in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Unpacking non-array/Traversable detected at compile-time
3+
--FILE--
4+
<?php
5+
6+
var_dump([...42]);
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Only arrays and Traversables can be unpacked in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Unpacking of string keys detected at compile-time
3+
--FILE--
4+
<?php
5+
6+
var_dump([...['a' => 'b']]);
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot unpack array with string keys in %s on line %d

Zend/zend_ast.c

+39
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,32 @@ static int zend_ast_add_array_element(zval *result, zval *offset, zval *expr)
446446
return SUCCESS;
447447
}
448448

449+
static int zend_ast_add_unpacked_element(zval *result, zval *expr) {
450+
if (EXPECTED(Z_TYPE_P(expr) == IS_ARRAY)) {
451+
HashTable *ht = Z_ARRVAL_P(expr);
452+
zval *val;
453+
zend_string *key;
454+
455+
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val) {
456+
if (key) {
457+
zend_throw_error(NULL, "Cannot unpack array with string keys");
458+
return FAILURE;
459+
} else {
460+
if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), val)) {
461+
zend_error(E_WARNING, "Cannot add element to the array as the next element is already occupied");
462+
break;
463+
}
464+
Z_TRY_ADDREF_P(val);
465+
}
466+
} ZEND_HASH_FOREACH_END();
467+
return SUCCESS;
468+
}
469+
470+
/* Objects or references cannot occur in a constant expression. */
471+
zend_throw_error(NULL, "Only arrays and Traversables can be unpacked");
472+
return FAILURE;
473+
}
474+
449475
ZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)
450476
{
451477
zval op1, op2;
@@ -642,6 +668,19 @@ ZEND_API int ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast, zend_c
642668
array_init(result);
643669
for (i = 0; i < list->children; i++) {
644670
zend_ast *elem = list->child[i];
671+
if (elem->kind == ZEND_AST_UNPACK) {
672+
if (UNEXPECTED(zend_ast_evaluate(&op1, elem->child[0], scope) != SUCCESS)) {
673+
zval_ptr_dtor_nogc(result);
674+
return FAILURE;
675+
}
676+
if (UNEXPECTED(zend_ast_add_unpacked_element(result, &op1) != SUCCESS)) {
677+
zval_ptr_dtor_nogc(&op1);
678+
zval_ptr_dtor_nogc(result);
679+
return FAILURE;
680+
}
681+
zval_ptr_dtor_nogc(&op1);
682+
continue;
683+
}
645684
if (elem->child[1]) {
646685
if (UNEXPECTED(zend_ast_evaluate(&op1, elem->child[1], scope) != SUCCESS)) {
647686
zval_ptr_dtor_nogc(result);

0 commit comments

Comments
 (0)