Skip to content

Commit a9da175

Browse files
committed
Initial support for collecting extra named params
1 parent c9fcbd5 commit a9da175

File tree

8 files changed

+177
-6
lines changed

8 files changed

+177
-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
@@ -493,6 +493,7 @@ struct _zend_execute_data {
493493
zend_execute_data *prev_execute_data;
494494
zend_array *symbol_table;
495495
void **run_time_cache; /* cache op_array->run_time_cache */
496+
zend_array *extra_named_params;
496497
};
497498

498499
#define ZEND_CALL_HAS_THIS IS_OBJECT_EX
@@ -511,6 +512,7 @@ struct _zend_execute_data {
511512
#define ZEND_CALL_GENERATOR (1 << 24)
512513
#define ZEND_CALL_DYNAMIC (1 << 25)
513514
#define ZEND_CALL_MAY_HAVE_UNDEF (1 << 26)
515+
#define ZEND_CALL_HAS_EXTRA_NAMED_PARAMS (1 << 27)
514516
#define ZEND_CALL_SEND_ARG_BY_REF (1u << 31)
515517

516518
#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
@@ -4303,6 +4303,12 @@ static zend_always_inline uint32_t zend_get_arg_offset_by_name(
43034303
}
43044304
}
43054305

4306+
if (fbc->common.fn_flags & ZEND_ACC_VARIADIC) {
4307+
*cache_slot = fbc;
4308+
*(uintptr_t *)(cache_slot + 1) = fbc->common.num_args;
4309+
return fbc->common.num_args;
4310+
}
4311+
43064312
return (uint32_t) -1;
43074313
}
43084314

@@ -4313,12 +4319,27 @@ static zval * ZEND_FASTCALL zend_handle_named_arg(
43134319
zend_function *fbc = call->func;
43144320
uint32_t arg_offset = zend_get_arg_offset_by_name(fbc, arg_name, cache_slot);
43154321
if (UNEXPECTED(arg_offset == (uint32_t) -1)) {
4316-
// TODO: Allow collection of non-existent named parameters.
43174322
zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(arg_name));
43184323
return NULL;
43194324
}
43204325

43214326
zval *arg;
4327+
if (UNEXPECTED(arg_offset == fbc->common.num_args)) {
4328+
/* Unknown named parameter that will be collected into a variadic. */
4329+
if (!(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
4330+
ZEND_ADD_CALL_FLAG(call, ZEND_CALL_HAS_EXTRA_NAMED_PARAMS);
4331+
call->extra_named_params = zend_new_array(0);
4332+
}
4333+
4334+
arg = zend_hash_add_empty_element(call->extra_named_params, arg_name);
4335+
if (!arg) {
4336+
zend_throw_error(NULL, "Named parameter $%s overwrites previous argument",
4337+
ZSTR_VAL(arg_name));
4338+
return NULL;
4339+
}
4340+
return arg;
4341+
}
4342+
43224343
uint32_t current_num_args = ZEND_CALL_NUM_ARGS(call);
43234344
// TODO: We may wish to optimize the arg_offset == current_num_args case,
43244345
// which is probably common (if the named parameters are in order of declaration).

Zend/zend_generators.c

Lines changed: 7 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

@@ -296,6 +299,7 @@ static uint32_t calc_gc_buffer_size(zend_generator *generator) /* {{{ */
296299
}
297300
size += (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) != 0; /* $this */
298301
size += (EX_CALL_INFO() & ZEND_CALL_CLOSURE) != 0; /* Closure object */
302+
size += (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) != 0;
299303

300304
/* Live vars */
301305
if (execute_data->opline != op_array->opcodes) {
@@ -381,6 +385,9 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
381385
if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
382386
ZVAL_OBJ(gc_buffer++, ZEND_CLOSURE_OBJECT(EX(func)));
383387
}
388+
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
389+
ZVAL_ARR(gc_buffer++, EX(extra_named_params));
390+
}
384391

385392
if (execute_data->opline != op_array->opcodes) {
386393
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);
@@ -5334,6 +5338,22 @@ ZEND_VM_HANDLER(164, ZEND_RECV_VARIADIC, NUM, UNUSED, CACHE_SLOT)
53345338
ZVAL_EMPTY_ARRAY(params);
53355339
}
53365340

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

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);
@@ -3196,6 +3200,22 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_VARIADIC_SPEC_UNUSED_HAND
31963200
ZVAL_EMPTY_ARRAY(params);
31973201
}
31983202

3203+
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
3204+
zend_string *name;
3205+
zval *param;
3206+
3207+
SEPARATE_ARRAY(params);
3208+
ZEND_HASH_FOREACH_STR_KEY_VAL(EX(extra_named_params), name, param) {
3209+
if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0)) {
3210+
if (UNEXPECTED(!zend_verify_variadic_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->extended_value)))) {
3211+
HANDLE_EXCEPTION();
3212+
}
3213+
}
3214+
Z_TRY_ADDREF_P(param);
3215+
zend_hash_add_new(Z_ARRVAL_P(params), name, param);
3216+
} ZEND_HASH_FOREACH_END();
3217+
}
3218+
31993219
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
32003220
}
32013221

@@ -52984,7 +53004,7 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5298453004
uint32_t call_info = EX_CALL_INFO();
5298553005
SAVE_OPLINE();
5298653006

52987-
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED)) == 0)) {
53007+
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)) {
5298853008
EG(current_execute_data) = EX(prev_execute_data);
5298953009
i_free_compiled_variables(execute_data);
5299053010

@@ -53017,6 +53037,10 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5301753037
zend_clean_and_cache_symbol_table(EX(symbol_table));
5301853038
}
5301953039

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

0 commit comments

Comments
 (0)