Skip to content

Commit cd8ed72

Browse files
committed
Initial support for unpacking
1 parent df191b3 commit cd8ed72

File tree

6 files changed

+126
-36
lines changed

6 files changed

+126
-36
lines changed

Zend/tests/arg_unpack/string_keys.phpt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@ set_error_handler(function($errno, $errstr) {
77
var_dump($errstr);
88
});
99

10-
try {
11-
var_dump(...[1, 2, "foo" => 3, 4]);
12-
} catch (Error $ex) {
13-
var_dump($ex->getMessage());
14-
}
1510
try {
1611
var_dump(...new ArrayIterator([1, 2, "foo" => 3, 4]));
1712
} catch (Error $ex) {
@@ -20,5 +15,4 @@ try {
2015

2116
?>
2217
--EXPECT--
23-
string(36) "Cannot unpack array with string keys"
2418
string(42) "Cannot unpack Traversable with string keys"

Zend/tests/named_params/unpack.phpt

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
--TEST--
2+
Unpacking named parameters
3+
--FILE--
4+
<?php
5+
6+
function test($a, $b, $c) {
7+
echo "a = $a, b = $b, c = $c\n";
8+
}
9+
10+
function test2($a = null, &$b = null) {
11+
$b++;
12+
}
13+
14+
test(...['a' => 'a', 'b' => 'b', 'c' => 'c']);
15+
16+
test(...['a', 'b' => 'b', 'c' => 'c']);
17+
18+
try {
19+
test(...['a', 'b' => 'b', 'c']);
20+
} catch (Error $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
24+
try {
25+
test(...['a', 'a' => 'a']);
26+
} catch (Error $e) {
27+
echo $e->getMessage(), "\n";
28+
}
29+
30+
$ary = ['b' => 0];
31+
$ary2 = $ary;
32+
test2(...$ary);
33+
var_dump($ary, $ary2);
34+
35+
?>
36+
--EXPECT--
37+
a = a, b = b, c = c
38+
a = a, b = b, c = c
39+
Cannot use positional argument after named argument during unpacking
40+
Named parameter $a overwrites previous argument
41+
array(1) {
42+
["b"]=>
43+
int(1)
44+
}
45+
array(1) {
46+
["b"]=>
47+
int(0)
48+
}

Zend/tests/named_params/variadic.phpt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,31 @@ Additional named params are collect into variadics
33
--FILE--
44
<?php
55

6-
function test($a, string...$extra) {
6+
function test($a, string ...$extra) {
77
var_dump($a);
88
var_dump($extra);
99
// Extra named parameters do not contribute toward func_num_args() and func_get_args().
1010
var_dump(func_num_args());
1111
var_dump(func_get_args());
1212
}
1313

14+
function test2(&...$refs) {
15+
foreach ($refs as &$ref) $ref++;
16+
}
17+
1418
test(b => 'b', a => 'a', c => 'c', extra => 'extra');
1519
echo "\n";
20+
1621
test('a', 'b', 'c', d => 'd');
1722
echo "\n";
18-
test(...['b' => 'b', 'a' => 'a', 'c' => 'c', 'extra' => 'extra']);
1923

24+
$x = 0;
25+
$y = 0;
26+
test2(x => $x, y => $y);
27+
var_dump($x, $y);
2028

2129
?>
22-
--EXPECTF--
30+
--EXPECT--
2331
string(1) "a"
2432
array(3) {
2533
["b"]=>
@@ -54,8 +62,5 @@ array(3) {
5462
string(1) "c"
5563
}
5664

57-
58-
Fatal error: Uncaught Error: Cannot unpack array with string keys in %s:%d
59-
Stack trace:
60-
#0 {main}
61-
thrown in %s on line %d
65+
int(1)
66+
int(1)

Zend/zend_execute.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4337,6 +4337,7 @@ static zval * ZEND_FASTCALL zend_handle_named_arg(
43374337
ZSTR_VAL(arg_name));
43384338
return NULL;
43394339
}
4340+
*arg_num_ptr = arg_offset + 1;
43404341
return arg;
43414342
}
43424343

Zend/zend_vm_def.h

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4927,7 +4927,7 @@ ZEND_VM_HANDLER(165, ZEND_SEND_UNPACK, ANY, ANY)
49274927
{
49284928
USE_OPLINE
49294929
zval *args;
4930-
int arg_num;
4930+
uint32_t arg_num;
49314931

49324932
SAVE_OPLINE();
49334933
args = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
@@ -4938,34 +4938,55 @@ ZEND_VM_C_LABEL(send_again):
49384938
HashTable *ht = Z_ARRVAL_P(args);
49394939
zval *arg, *top;
49404940
zend_string *name;
4941+
zend_bool have_named_params = 0;
49414942

49424943
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, zend_hash_num_elements(ht));
49434944

4945+
// TODO: Speed this up using a flag that specifies whether there are any ref parameters.
49444946
if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_REFCOUNT_P(args) > 1) {
4945-
uint32_t i;
4947+
uint32_t tmp_arg_num = arg_num;
49464948
int separate = 0;
49474949

49484950
/* check if any of arguments are going to be passed by reference */
4949-
for (i = 0; i < zend_hash_num_elements(ht); i++) {
4950-
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num + i)) {
4951+
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, name, arg) {
4952+
if (UNEXPECTED(name)) {
4953+
void *cache_slot[2] = {NULL, NULL};
4954+
tmp_arg_num = zend_get_arg_offset_by_name(
4955+
EX(call)->func, name, cache_slot) + 1;
4956+
}
4957+
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, tmp_arg_num)) {
49514958
separate = 1;
49524959
break;
49534960
}
4954-
}
4961+
tmp_arg_num++;
4962+
} ZEND_HASH_FOREACH_END();
49554963
if (separate) {
49564964
SEPARATE_ARRAY(args);
49574965
ht = Z_ARRVAL_P(args);
49584966
}
49594967
}
49604968

49614969
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, name, arg) {
4962-
if (name) {
4963-
zend_throw_error(NULL, "Cannot unpack array with string keys");
4964-
FREE_OP1();
4965-
HANDLE_EXCEPTION();
4970+
if (UNEXPECTED(name)) {
4971+
void *cache_slot[2] = {NULL, NULL};
4972+
have_named_params = 1;
4973+
top = zend_handle_named_arg(&EX(call), name, &arg_num, cache_slot);
4974+
if (UNEXPECTED(!top)) {
4975+
FREE_OP1();
4976+
HANDLE_EXCEPTION();
4977+
}
4978+
} else {
4979+
if (have_named_params) {
4980+
zend_throw_error(NULL,
4981+
"Cannot use positional argument after named argument during unpacking");
4982+
FREE_OP1();
4983+
HANDLE_EXCEPTION();
4984+
}
4985+
4986+
top = ZEND_CALL_ARG(EX(call), arg_num);
4987+
ZEND_CALL_NUM_ARGS(EX(call))++;
49664988
}
49674989

4968-
top = ZEND_CALL_ARG(EX(call), arg_num);
49694990
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
49704991
if (Z_ISREF_P(arg)) {
49714992
Z_ADDREF_P(arg);
@@ -4982,7 +5003,6 @@ ZEND_VM_C_LABEL(send_again):
49825003
ZVAL_COPY_DEREF(top, arg);
49835004
}
49845005

4985-
ZEND_CALL_NUM_ARGS(EX(call))++;
49865006
arg_num++;
49875007
} ZEND_HASH_FOREACH_END();
49885008

@@ -5028,6 +5048,7 @@ ZEND_VM_C_LABEL(send_again):
50285048
break;
50295049
}
50305050

5051+
// TODO: Support named params.
50315052
if (UNEXPECTED(Z_TYPE(key) != IS_LONG)) {
50325053
zend_throw_error(NULL,
50335054
(Z_TYPE(key) == IS_STRING) ?

Zend/zend_vm_execute.h

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,7 +1860,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_
18601860
{
18611861
USE_OPLINE
18621862
zval *args;
1863-
int arg_num;
1863+
uint32_t arg_num;
18641864

18651865
SAVE_OPLINE();
18661866
args = get_zval_ptr_undef(opline->op1_type, opline->op1, BP_VAR_R);
@@ -1871,34 +1871,55 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_
18711871
HashTable *ht = Z_ARRVAL_P(args);
18721872
zval *arg, *top;
18731873
zend_string *name;
1874+
zend_bool have_named_params = 0;
18741875

18751876
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, zend_hash_num_elements(ht));
18761877

1878+
// TODO: Speed this up using a flag that specifies whether there are any ref parameters.
18771879
if ((opline->op1_type & (IS_VAR|IS_CV)) && Z_REFCOUNT_P(args) > 1) {
1878-
uint32_t i;
1880+
uint32_t tmp_arg_num = arg_num;
18791881
int separate = 0;
18801882

18811883
/* check if any of arguments are going to be passed by reference */
1882-
for (i = 0; i < zend_hash_num_elements(ht); i++) {
1883-
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num + i)) {
1884+
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, name, arg) {
1885+
if (UNEXPECTED(name)) {
1886+
void *cache_slot[2] = {NULL, NULL};
1887+
tmp_arg_num = zend_get_arg_offset_by_name(
1888+
EX(call)->func, name, cache_slot) + 1;
1889+
}
1890+
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, tmp_arg_num)) {
18841891
separate = 1;
18851892
break;
18861893
}
1887-
}
1894+
tmp_arg_num++;
1895+
} ZEND_HASH_FOREACH_END();
18881896
if (separate) {
18891897
SEPARATE_ARRAY(args);
18901898
ht = Z_ARRVAL_P(args);
18911899
}
18921900
}
18931901

18941902
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, name, arg) {
1895-
if (name) {
1896-
zend_throw_error(NULL, "Cannot unpack array with string keys");
1897-
FREE_OP(opline->op1_type, opline->op1.var);
1898-
HANDLE_EXCEPTION();
1903+
if (UNEXPECTED(name)) {
1904+
void *cache_slot[2] = {NULL, NULL};
1905+
have_named_params = 1;
1906+
top = zend_handle_named_arg(&EX(call), name, &arg_num, cache_slot);
1907+
if (UNEXPECTED(!top)) {
1908+
FREE_OP(opline->op1_type, opline->op1.var);
1909+
HANDLE_EXCEPTION();
1910+
}
1911+
} else {
1912+
if (have_named_params) {
1913+
zend_throw_error(NULL,
1914+
"Cannot use positional argument after named argument during unpacking");
1915+
FREE_OP(opline->op1_type, opline->op1.var);
1916+
HANDLE_EXCEPTION();
1917+
}
1918+
1919+
top = ZEND_CALL_ARG(EX(call), arg_num);
1920+
ZEND_CALL_NUM_ARGS(EX(call))++;
18991921
}
19001922

1901-
top = ZEND_CALL_ARG(EX(call), arg_num);
19021923
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
19031924
if (Z_ISREF_P(arg)) {
19041925
Z_ADDREF_P(arg);
@@ -1915,7 +1936,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_
19151936
ZVAL_COPY_DEREF(top, arg);
19161937
}
19171938

1918-
ZEND_CALL_NUM_ARGS(EX(call))++;
19191939
arg_num++;
19201940
} ZEND_HASH_FOREACH_END();
19211941

@@ -1961,6 +1981,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_
19611981
break;
19621982
}
19631983

1984+
// TODO: Support named params.
19641985
if (UNEXPECTED(Z_TYPE(key) != IS_LONG)) {
19651986
zend_throw_error(NULL,
19661987
(Z_TYPE(key) == IS_STRING) ?

0 commit comments

Comments
 (0)