Skip to content

Commit 613c4b9

Browse files
committed
Initial support for collecting extra named params
1 parent 97d79a6 commit 613c4b9

File tree

8 files changed

+178
-6
lines changed

8 files changed

+178
-6
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
Require parameter not passed
3+
--FILE--
4+
<?php
5+
6+
function test($a, $b, $c, $d) {
7+
}
8+
9+
try {
10+
test(a => 'a', d => 'd');
11+
} catch (ArgumentCountError $e) {
12+
echo $e->getMessage(), "\n";
13+
}
14+
15+
try {
16+
array_keys(strict => true);
17+
} catch (ArgumentCountError $e) {
18+
echo $e->getMessage(), "\n";
19+
}
20+
21+
try {
22+
array_keys([], strict => true);
23+
} catch (ArgumentCountError $e) {
24+
echo $e->getMessage(), "\n";
25+
}
26+
27+
// This works fine, as search_value is explicitly specified.
28+
var_dump(array_keys([41, 42], search_value => 42, strict => true));
29+
30+
?>
31+
--EXPECT--
32+
test(): Argument #2 ($b) not passed
33+
array_keys(): Argument #1 ($arg) not passed
34+
array_keys(): Argument #2 ($search_value) must be passed explicitly, because the default value is not known
35+
array(1) {
36+
[0]=>
37+
int(1)
38+
}

Zend/tests/named_params/unknown_named_param.phpt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ try {
1515
echo $e->getMessage(), "\n";
1616
}
1717

18-
// This may be supported in the future.
1918
try {
2019
test2(a => 42);
2120
} catch (Error $e) {
@@ -25,4 +24,3 @@ try {
2524
?>
2625
--EXPECT--
2726
Unknown named parameter $b
28-
Unknown named parameter $a

Zend/tests/named_params/variadic.phpt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
--TEST--
2+
Additional named params are collect into variadics
3+
--FILE--
4+
<?php
5+
6+
function test($a, string...$extra) {
7+
var_dump($a);
8+
var_dump($extra);
9+
// Extra named parameters do not contribute toward func_num_args() and func_get_args().
10+
var_dump(func_num_args());
11+
var_dump(func_get_args());
12+
}
13+
14+
test(b => 'b', a => 'a', c => 'c', extra => 'extra');
15+
echo "\n";
16+
test('a', 'b', 'c', d => 'd');
17+
echo "\n";
18+
test(...['b' => 'b', 'a' => 'a', 'c' => 'c', 'extra' => 'extra']);
19+
20+
21+
?>
22+
--EXPECTF--
23+
string(1) "a"
24+
array(3) {
25+
["b"]=>
26+
string(1) "b"
27+
["c"]=>
28+
string(1) "c"
29+
["extra"]=>
30+
string(5) "extra"
31+
}
32+
int(1)
33+
array(1) {
34+
[0]=>
35+
string(1) "a"
36+
}
37+
38+
string(1) "a"
39+
array(3) {
40+
[0]=>
41+
string(1) "b"
42+
[1]=>
43+
string(1) "c"
44+
["d"]=>
45+
string(1) "d"
46+
}
47+
int(3)
48+
array(3) {
49+
[0]=>
50+
string(1) "a"
51+
[1]=>
52+
string(1) "b"
53+
[2]=>
54+
string(1) "c"
55+
}
56+
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

Zend/zend_compile.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ struct _zend_execute_data {
507507
zend_execute_data *prev_execute_data;
508508
zend_array *symbol_table;
509509
void **run_time_cache; /* cache op_array->run_time_cache */
510+
zend_array *extra_named_params;
510511
};
511512

512513
#define ZEND_CALL_HAS_THIS IS_OBJECT_EX
@@ -525,6 +526,7 @@ struct _zend_execute_data {
525526
#define ZEND_CALL_GENERATOR (1 << 24)
526527
#define ZEND_CALL_DYNAMIC (1 << 25)
527528
#define ZEND_CALL_MAY_HAVE_UNDEF (1 << 26)
529+
#define ZEND_CALL_HAS_EXTRA_NAMED_PARAMS (1 << 27)
528530
#define ZEND_CALL_SEND_ARG_BY_REF (1u << 31)
529531

530532
#define ZEND_CALL_NESTED_FUNCTION (ZEND_CALL_FUNCTION | ZEND_CALL_NESTED)

Zend/zend_execute.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4311,6 +4311,12 @@ static zend_always_inline uint32_t zend_get_arg_offset_by_name(
43114311
}
43124312
}
43134313

4314+
if (fbc->common.fn_flags & ZEND_ACC_VARIADIC) {
4315+
*cache_slot = fbc;
4316+
*(uintptr_t *)(cache_slot + 1) = fbc->common.num_args;
4317+
return fbc->common.num_args;
4318+
}
4319+
43144320
return (uint32_t) -1;
43154321
}
43164322

@@ -4321,12 +4327,27 @@ static zval * ZEND_FASTCALL zend_handle_named_arg(
43214327
zend_function *fbc = call->func;
43224328
uint32_t arg_offset = zend_get_arg_offset_by_name(fbc, arg_name, cache_slot);
43234329
if (UNEXPECTED(arg_offset == (uint32_t) -1)) {
4324-
// TODO: Allow collection of non-existent named parameters.
43254330
zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(arg_name));
43264331
return NULL;
43274332
}
43284333

43294334
zval *arg;
4335+
if (UNEXPECTED(arg_offset == fbc->common.num_args)) {
4336+
/* Unknown named parameter that will be collected into a variadic. */
4337+
if (!(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
4338+
ZEND_ADD_CALL_FLAG(call, ZEND_CALL_HAS_EXTRA_NAMED_PARAMS);
4339+
call->extra_named_params = zend_new_array(0);
4340+
}
4341+
4342+
arg = zend_hash_add_empty_element(call->extra_named_params, arg_name);
4343+
if (!arg) {
4344+
zend_throw_error(NULL, "Named parameter $%s overwrites previous argument",
4345+
ZSTR_VAL(arg_name));
4346+
return NULL;
4347+
}
4348+
return arg;
4349+
}
4350+
43304351
uint32_t current_num_args = ZEND_CALL_NUM_ARGS(call);
43314352
// TODO: We may wish to optimize the arg_offset == current_num_args case,
43324353
// which is probably common (if the named parameters are in order of declaration).

Zend/zend_generators.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished
125125
if (EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE) {
126126
zend_clean_and_cache_symbol_table(execute_data->symbol_table);
127127
}
128+
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
129+
zend_array_destroy(execute_data->extra_named_params);
130+
}
128131
/* always free the CV's, in the symtable are only not-free'd IS_INDIRECT's */
129132
zend_free_compiled_variables(execute_data);
130133

@@ -312,6 +315,11 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
312315
if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
313316
zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(EX(func)));
314317
}
318+
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
319+
zval extra_named_params;
320+
ZVAL_ARR(&extra_named_params, EX(extra_named_params));
321+
zend_get_gc_buffer_add_zval(gc_buffer, &extra_named_params);
322+
}
315323

316324
if (execute_data->opline != op_array->opcodes) {
317325
uint32_t i, op_num = execute_data->opline - op_array->opcodes - 1;

Zend/zend_vm_def.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2749,7 +2749,7 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY)
27492749
uint32_t call_info = EX_CALL_INFO();
27502750
SAVE_OPLINE();
27512751

2752-
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED)) == 0)) {
2752+
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) {
27532753
EG(current_execute_data) = EX(prev_execute_data);
27542754
i_free_compiled_variables(execute_data);
27552755

@@ -2782,6 +2782,10 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY)
27822782
zend_clean_and_cache_symbol_table(EX(symbol_table));
27832783
}
27842784

2785+
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
2786+
zend_array_destroy(EX(extra_named_params));
2787+
}
2788+
27852789
/* Free extra args before releasing the closure,
27862790
* as that may free the op_array. */
27872791
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
@@ -5332,6 +5336,22 @@ ZEND_VM_HANDLER(164, ZEND_RECV_VARIADIC, NUM, UNUSED, CACHE_SLOT)
53325336
ZVAL_EMPTY_ARRAY(params);
53335337
}
53345338

5339+
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
5340+
zend_string *name;
5341+
zval *param;
5342+
5343+
SEPARATE_ARRAY(params);
5344+
ZEND_HASH_FOREACH_STR_KEY_VAL(EX(extra_named_params), name, param) {
5345+
if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0)) {
5346+
if (UNEXPECTED(!zend_verify_variadic_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->extended_value)))) {
5347+
HANDLE_EXCEPTION();
5348+
}
5349+
}
5350+
Z_TRY_ADDREF_P(param);
5351+
zend_hash_add_new(Z_ARRVAL_P(params), name, param);
5352+
} ZEND_HASH_FOREACH_END();
5353+
}
5354+
53355355
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
53365356
}
53375357

Zend/zend_vm_execute.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper
10951095
uint32_t call_info = EX_CALL_INFO();
10961096
SAVE_OPLINE();
10971097

1098-
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED)) == 0)) {
1098+
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) {
10991099
EG(current_execute_data) = EX(prev_execute_data);
11001100
i_free_compiled_variables(execute_data);
11011101

@@ -1128,6 +1128,10 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper
11281128
zend_clean_and_cache_symbol_table(EX(symbol_table));
11291129
}
11301130

1131+
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
1132+
zend_array_destroy(EX(extra_named_params));
1133+
}
1134+
11311135
/* Free extra args before releasing the closure,
11321136
* as that may free the op_array. */
11331137
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
@@ -3200,6 +3204,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_VARIADIC_SPEC_UNUSED_HAND
32003204
ZVAL_EMPTY_ARRAY(params);
32013205
}
32023206

3207+
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
3208+
zend_string *name;
3209+
zval *param;
3210+
3211+
SEPARATE_ARRAY(params);
3212+
ZEND_HASH_FOREACH_STR_KEY_VAL(EX(extra_named_params), name, param) {
3213+
if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0)) {
3214+
if (UNEXPECTED(!zend_verify_variadic_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->extended_value)))) {
3215+
HANDLE_EXCEPTION();
3216+
}
3217+
}
3218+
Z_TRY_ADDREF_P(param);
3219+
zend_hash_add_new(Z_ARRVAL_P(params), name, param);
3220+
} ZEND_HASH_FOREACH_END();
3221+
}
3222+
32033223
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
32043224
}
32053225

@@ -52983,7 +53003,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5298353003
uint32_t call_info = EX_CALL_INFO();
5298453004
SAVE_OPLINE();
5298553005

52986-
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED)) == 0)) {
53006+
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) {
5298753007
EG(current_execute_data) = EX(prev_execute_data);
5298853008
i_free_compiled_variables(execute_data);
5298953009

@@ -53016,6 +53036,10 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5301653036
zend_clean_and_cache_symbol_table(EX(symbol_table));
5301753037
}
5301853038

53039+
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
53040+
zend_array_destroy(EX(extra_named_params));
53041+
}
53042+
5301953043
/* Free extra args before releasing the closure,
5302053044
* as that may free the op_array. */
5302153045
zend_vm_stack_free_extra_args_ex(call_info, execute_data);

0 commit comments

Comments
 (0)