Skip to content

Commit a51c913

Browse files
committed
Added support for passing default using named arguments to closures.
1 parent defe31a commit a51c913

10 files changed

+758
-592
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Tests passing default as a named argument to a closure
3+
--FILE--
4+
<?php
5+
6+
$F = fn ($V = 1, $default = 2) => $V + $default;
7+
var_dump($F(default: default + 1));
8+
?>
9+
--EXPECT--
10+
int(4)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Tests passing default as a named argument to a closure that doesn't declare any argument by that name
3+
--FILE--
4+
<?php
5+
6+
$F = fn ($V = 1) => $V;
7+
var_dump($F(default: default + 1));
8+
?>
9+
--EXPECTF--
10+
Fatal error: Uncaught ValueError: Cannot pass default to undeclared named parameter $default of {closure:%s:%d}() in %s:%d
11+
Stack trace:
12+
%a
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Tests passing default to a named parent argument whilst calling child arguments positionally
3+
--FILE--
4+
<?php
5+
6+
$F = fn ($X = 1, $Y = 2) => $X + $Y;
7+
var_dump($F(X: $F(0, 1) + default));
8+
?>
9+
--EXPECTF--
10+
int(4)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Tests passing default to a named parent argument that is different from the child call's last named argument
3+
--FILE--
4+
<?php
5+
6+
$F = fn ($X = 1, $Y = 2) => $X + $Y;
7+
var_dump($F(X: $F(X: 0, Y: 1) + default));
8+
?>
9+
--EXPECTF--
10+
int(4)

Zend/zend_compile.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3703,6 +3703,7 @@ static uint32_t zend_compile_args(
37033703
bool uses_arg_unpack = 0;
37043704
uint32_t arg_count = 0; /* number of arguments not including unpacks */
37053705
uint32_t prev_arg_num = CG(context).arg_num;
3706+
zend_string *prev_arg_name = CG(context).arg_name;
37063707

37073708
/* Whether named arguments are used syntactically, to enforce language level limitations.
37083709
* May not actually use named argument passing. */
@@ -3781,6 +3782,7 @@ static uint32_t zend_compile_args(
37813782
}
37823783

37833784
CG(context).arg_num = arg_num;
3785+
CG(context).arg_name = arg_name;
37843786

37853787
/* Treat passing of $GLOBALS the same as passing a call.
37863788
* This will error at runtime if the argument is by-ref. */
@@ -3897,6 +3899,7 @@ static uint32_t zend_compile_args(
38973899
}
38983900

38993901
CG(context).arg_num = prev_arg_num;
3902+
CG(context).arg_name = prev_arg_name;
39003903

39013904
return arg_count;
39023905
}
@@ -3905,13 +3908,20 @@ static uint32_t zend_compile_args(
39053908
static void zend_compile_default(znode *result, zend_ast *ast)
39063909
{
39073910
uint32_t arg_num = CG(context).arg_num;
3911+
zend_string *arg_name = CG(context).arg_name;
39083912

39093913
if (arg_num == 0) {
39103914
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use default in non-argument context.");
39113915
}
39123916

39133917
zend_op *opline = zend_emit_op_tmp(result, ZEND_FETCH_DEFAULT_ARG, NULL, NULL);
39143918
opline->op1.num = arg_num;
3919+
3920+
if (arg_num == (uint32_t) -1) {
3921+
opline->op1_type = IS_CONST;
3922+
zend_string_addref(arg_name);
3923+
opline->op1.constant = zend_add_literal_string(&arg_name);
3924+
}
39153925
}
39163926

39173927
ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, zend_function *fbc) /* {{{ */

Zend/zend_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ typedef struct _zend_oparray_context {
208208
zend_property_hook_kind active_property_hook_kind;
209209
bool in_jmp_frameless_branch;
210210
uint32_t arg_num; // Current 1-based argument index if compiling arguments, otherwise zero.
211+
zend_string *arg_name;
211212
} zend_oparray_context;
212213

213214
/* Class, property and method flags class|meth.|prop.|const*/

Zend/zend_vm_def.h

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9808,7 +9808,7 @@ ZEND_VM_HANDLER(209, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL, CONST, UNUSED|NUM, NUM
98089808
ZEND_VM_NEXT_OPCODE();
98099809
}
98109810

9811-
ZEND_VM_HANDLER(210, ZEND_FETCH_DEFAULT_ARG, UNUSED|NUM, UNUSED)
9811+
ZEND_VM_HANDLER(210, ZEND_FETCH_DEFAULT_ARG, UNUSED|NUM|CONST, UNUSED)
98129812
{
98139813
USE_OPLINE
98149814
SAVE_OPLINE();
@@ -9817,16 +9817,33 @@ ZEND_VM_HANDLER(210, ZEND_FETCH_DEFAULT_ARG, UNUSED|NUM, UNUSED)
98179817
ZVAL_UNDEF(EX_VAR(opline->result.var));
98189818

98199819
zend_function *called_func = EX(call)->func;
9820-
98219820
reflection_parameter_reference param;
9822-
param.offset = opline->op1.num - 1;
9821+
param.offset = (uint32_t) -1;
9822+
9823+
zend_string *arg_name;
9824+
if (OP1_TYPE == IS_CONST) {
9825+
arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op1));
9826+
9827+
for (uint32_t i = 0; i < called_func->common.num_args; ++i) {
9828+
if (zend_string_equals(arg_name, called_func->common.arg_info[i].name)) {
9829+
param.offset = i;
9830+
break;
9831+
}
9832+
}
9833+
} else {
9834+
param.offset = opline->op1.num - 1;
9835+
}
9836+
98239837
param.required = param.offset < called_func->common.required_num_args;
98249838
param.fptr = called_func;
98259839

98269840
if (UNEXPECTED(param.required || param.offset >= called_func->common.num_args)) {
9827-
zend_value_error("Cannot pass default to %s parameter %u of %s%s%s()",
9841+
zend_value_error(
9842+
OP1_TYPE == IS_CONST
9843+
? "Cannot pass default to %s named parameter $%s of %s%s%s()"
9844+
: "Cannot pass default to %s parameter %u of %s%s%s()",
98289845
param.required ? "required" : "undeclared",
9829-
opline->op1.num,
9846+
OP1_TYPE == IS_CONST ? ZSTR_VAL(arg_name) : opline->op1.num,
98309847
called_func->common.scope ? ZSTR_VAL(called_func->common.scope->name) : "",
98319848
called_func->common.scope ? "::" : "",
98329849
ZSTR_VAL(called_func->common.function_name)

0 commit comments

Comments
 (0)