Skip to content

Commit 0e96505

Browse files
committed
Support named args in traversable unpacking
1 parent 38e2fd5 commit 0e96505

File tree

3 files changed

+125
-44
lines changed

3 files changed

+125
-44
lines changed

Zend/tests/named_params/unpack.phpt

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function test2($a = null, &$b = null) {
1212
}
1313

1414
test(...['a' => 'a', 'b' => 'b', 'c' => 'c']);
15-
15+
test(...['c' => 'c', 'b' => 'b', 'a' => 'a']);
1616
test(...['a', 'b' => 'b', 'c' => 'c']);
1717

1818
try {
@@ -32,8 +32,30 @@ $ary2 = $ary;
3232
test2(...$ary);
3333
var_dump($ary, $ary2);
3434

35+
test(...new ArrayIterator(['a' => 'a', 'b' => 'b', 'c' => 'c']));
36+
test(...new ArrayIterator(['c' => 'c', 'b' => 'b', 'a' => 'a']));
37+
test(...new ArrayIterator(['a', 'b' => 'b', 'c' => 'c']));
38+
39+
try {
40+
test(...new ArrayIterator(['a', 'b' => 'b', 'c']));
41+
} catch (Error $e) {
42+
echo $e->getMessage(), "\n";
43+
}
44+
45+
try {
46+
test(...new ArrayIterator(['a', 'a' => 'a']));
47+
} catch (Error $e) {
48+
echo $e->getMessage(), "\n";
49+
}
50+
51+
$ary = ['b' => 0];
52+
$ary2 = $ary;
53+
test2(...new ArrayIterator($ary));
54+
var_dump($ary, $ary2);
55+
3556
?>
36-
--EXPECT--
57+
--EXPECTF--
58+
a = a, b = b, c = c
3759
a = a, b = b, c = c
3860
a = a, b = b, c = c
3961
Cannot use positional argument after named argument during unpacking
@@ -46,3 +68,17 @@ array(1) {
4668
["b"]=>
4769
int(0)
4870
}
71+
a = a, b = b, c = c
72+
a = a, b = b, c = c
73+
Cannot use positional argument after named argument during unpacking
74+
Named parameter $a overwrites previous argument
75+
76+
Warning: Cannot pass by-reference argument 2 of test2() by unpacking a Traversable, passing by-value instead in %s on line %d
77+
array(1) {
78+
["b"]=>
79+
int(0)
80+
}
81+
array(1) {
82+
["b"]=>
83+
int(0)
84+
}

Zend/zend_vm_def.h

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5007,6 +5007,7 @@ ZEND_VM_C_LABEL(send_again):
50075007
} else if (EXPECTED(Z_TYPE_P(args) == IS_OBJECT)) {
50085008
zend_class_entry *ce = Z_OBJCE_P(args);
50095009
zend_object_iterator *iter;
5010+
zend_bool have_named_params = 0;
50105011

50115012
if (!ce || !ce->get_iterator) {
50125013
zend_type_error("Only arrays and Traversables can be unpacked");
@@ -5047,7 +5048,6 @@ ZEND_VM_C_LABEL(send_again):
50475048
break;
50485049
}
50495050

5050-
// TODO: Support named params.
50515051
if (UNEXPECTED(Z_TYPE(key) != IS_LONG)) {
50525052
if (UNEXPECTED(Z_TYPE(key) != IS_STRING)) {
50535053
zend_throw_error(NULL,
@@ -5060,34 +5060,50 @@ ZEND_VM_C_LABEL(send_again):
50605060
}
50615061
}
50625062

5063-
if (name) {
5063+
if (UNEXPECTED(name)) {
50645064
void *cache_slot[2] = {NULL, NULL};
50655065
have_named_params = 1;
50665066
top = zend_handle_named_arg(&EX(call), name, &arg_num, cache_slot);
5067-
} else {
5068-
}
5067+
if (UNEXPECTED(!top)) {
5068+
break;
5069+
}
50695070

5070-
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
5071-
zend_error(
5072-
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
5073-
" by unpacking a Traversable, passing by-value instead", arg_num,
5074-
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
5075-
EX(call)->func->common.scope ? "::" : "",
5076-
ZSTR_VAL(EX(call)->func->common.function_name)
5077-
);
5078-
}
5071+
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
5072+
zend_error(
5073+
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
5074+
" by unpacking a Traversable, passing by-value instead", arg_num,
5075+
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
5076+
EX(call)->func->common.scope ? "::" : "",
5077+
ZSTR_VAL(EX(call)->func->common.function_name)
5078+
);
5079+
}
50795080

5080-
ZVAL_DEREF(arg);
5081-
Z_TRY_ADDREF_P(arg);
5081+
ZVAL_COPY_DEREF(top, arg);
5082+
zend_string_release(name);
5083+
} else {
5084+
if (have_named_params) {
5085+
zend_throw_error(NULL,
5086+
"Cannot use positional argument after named argument during unpacking");
5087+
break;
5088+
}
50825089

5083-
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, 1);
5084-
top = ZEND_CALL_ARG(EX(call), arg_num);
5085-
ZVAL_COPY_VALUE(top, arg);
5086-
ZEND_CALL_NUM_ARGS(EX(call))++;
5090+
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
5091+
zend_error(
5092+
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
5093+
" by unpacking a Traversable, passing by-value instead", arg_num,
5094+
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
5095+
EX(call)->func->common.scope ? "::" : "",
5096+
ZSTR_VAL(EX(call)->func->common.function_name)
5097+
);
5098+
}
50875099

5088-
if (name) {
5089-
zend_string_release(name);
5100+
5101+
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, 1);
5102+
top = ZEND_CALL_ARG(EX(call), arg_num);
5103+
ZVAL_COPY_DEREF(top, arg);
5104+
ZEND_CALL_NUM_ARGS(EX(call))++;
50905105
}
5106+
50915107
iter->funcs->move_forward(iter);
50925108
}
50935109

Zend/zend_vm_execute.h

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,6 +1942,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_
19421942
} else if (EXPECTED(Z_TYPE_P(args) == IS_OBJECT)) {
19431943
zend_class_entry *ce = Z_OBJCE_P(args);
19441944
zend_object_iterator *iter;
1945+
zend_bool have_named_params = 0;
19451946

19461947
if (!ce || !ce->get_iterator) {
19471948
zend_type_error("Only arrays and Traversables can be unpacked");
@@ -1974,41 +1975,69 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_
19741975
break;
19751976
}
19761977

1978+
zend_string *name = NULL;
19771979
if (iter->funcs->get_current_key) {
19781980
zval key;
19791981
iter->funcs->get_current_key(iter, &key);
19801982
if (UNEXPECTED(EG(exception) != NULL)) {
19811983
break;
19821984
}
19831985

1984-
// TODO: Support named params.
19851986
if (UNEXPECTED(Z_TYPE(key) != IS_LONG)) {
1987+
if (UNEXPECTED(Z_TYPE(key) != IS_STRING)) {
1988+
zend_throw_error(NULL,
1989+
"Keys must be of type int|string during argument unpacking");
1990+
zval_ptr_dtor(&key);
1991+
break;
1992+
}
1993+
1994+
name = Z_STR_P(&key);
1995+
}
1996+
}
1997+
1998+
if (UNEXPECTED(name)) {
1999+
void *cache_slot[2] = {NULL, NULL};
2000+
have_named_params = 1;
2001+
top = zend_handle_named_arg(&EX(call), name, &arg_num, cache_slot);
2002+
if (UNEXPECTED(!top)) {
2003+
break;
2004+
}
2005+
2006+
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
2007+
zend_error(
2008+
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
2009+
" by unpacking a Traversable, passing by-value instead", arg_num,
2010+
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
2011+
EX(call)->func->common.scope ? "::" : "",
2012+
ZSTR_VAL(EX(call)->func->common.function_name)
2013+
);
2014+
}
2015+
2016+
ZVAL_COPY_DEREF(top, arg);
2017+
zend_string_release(name);
2018+
} else {
2019+
if (have_named_params) {
19862020
zend_throw_error(NULL,
1987-
(Z_TYPE(key) == IS_STRING) ?
1988-
"Cannot unpack Traversable with string keys" :
1989-
"Cannot unpack Traversable with non-integer keys");
1990-
zval_ptr_dtor(&key);
2021+
"Cannot use positional argument after named argument during unpacking");
19912022
break;
19922023
}
1993-
}
19942024

1995-
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
1996-
zend_error(
1997-
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
1998-
" by unpacking a Traversable, passing by-value instead", arg_num,
1999-
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
2000-
EX(call)->func->common.scope ? "::" : "",
2001-
ZSTR_VAL(EX(call)->func->common.function_name)
2002-
);
2003-
}
2025+
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
2026+
zend_error(
2027+
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
2028+
" by unpacking a Traversable, passing by-value instead", arg_num,
2029+
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
2030+
EX(call)->func->common.scope ? "::" : "",
2031+
ZSTR_VAL(EX(call)->func->common.function_name)
2032+
);
2033+
}
20042034

2005-
ZVAL_DEREF(arg);
2006-
Z_TRY_ADDREF_P(arg);
20072035

2008-
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, 1);
2009-
top = ZEND_CALL_ARG(EX(call), arg_num);
2010-
ZVAL_COPY_VALUE(top, arg);
2011-
ZEND_CALL_NUM_ARGS(EX(call))++;
2036+
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, 1);
2037+
top = ZEND_CALL_ARG(EX(call), arg_num);
2038+
ZVAL_COPY_DEREF(top, arg);
2039+
ZEND_CALL_NUM_ARGS(EX(call))++;
2040+
}
20122041

20132042
iter->funcs->move_forward(iter);
20142043
}

0 commit comments

Comments
 (0)