diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index df8678ac3e..efbe515e19 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -1446,17 +1446,7 @@ void compile_function(VertexAdaptor func_root, CodeGenerator &W) { W << VarDeclaration(var); } } - - if (func->has_variadic_param) { - auto params = func->get_params(); - kphp_assert(!params.empty()); - auto variadic_arg = std::prev(params.end()); - auto name_of_variadic_param = VarName(variadic_arg->as()->var()->var_id); - W << "if (!" << name_of_variadic_param << ".is_vector())" << BEGIN; - W << "php_warning(\"pass associative array(" << name_of_variadic_param << ") to variadic function: " << FunctionName(func) << "\");" << NL; - W << name_of_variadic_param << " = f$array_values(" << name_of_variadic_param << ");" << NL; - W << END << NL; - } + W << AsSeq{func_root->cmd()} << END << NL; } diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index 8dc0fa7afe..1f02b0aca9 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -221,7 +221,7 @@ VertexAdaptor GenTree::get_func_call() { CE (expect(tok_oppar, "'('")); skip_phpdoc_tokens(); std::vector next; - bool ok_next = gen_list(&next, &GenTree::get_expression, tok_comma); + bool ok_next = gen_list(&next, &GenTree::get_func_call_arg, tok_comma); CE (!kphp_error(ok_next, "get argument list failed")); CE (expect(tok_clpar, "')'")); @@ -856,6 +856,22 @@ VertexPtr GenTree::get_expression() { return get_expression_impl(false); } +VertexPtr GenTree::get_func_call_arg() { + skip_phpdoc_tokens(); + VertexPtr name_or_val = get_expression(); + + // in case of named argument 'name_or_val' is a name + if (cur->type() == tok_colon) { + next_cur(); + VertexPtr value = get_expression(); + CE (!kphp_error(name_or_val, "Bad value of named argument")); + return VertexAdaptor::create(VertexUtil::create_string_const(name_or_val->get_string()), value); + } + + // in case of positional argument 'name_or_val' is a value + return name_or_val; +} + VertexPtr GenTree::get_def_value() { VertexPtr val; diff --git a/compiler/gentree.h b/compiler/gentree.h index 31e5fe6314..bd1afc4db0 100644 --- a/compiler/gentree.h +++ b/compiler/gentree.h @@ -64,6 +64,7 @@ class GenTree { VertexPtr get_binary_op(int op_priority_cur, bool till_ternary); VertexPtr get_expression_impl(bool till_ternary); VertexPtr get_expression(); + VertexPtr get_func_call_arg(); VertexPtr get_statement(const PhpDocComment *phpdoc = nullptr); VertexAdaptor get_catch(); void get_instance_var_list(const PhpDocComment *phpdoc, FieldModifiers modifiers, const TypeHint *type_hint); diff --git a/compiler/pipes/check-func-calls-and-vararg.cpp b/compiler/pipes/check-func-calls-and-vararg.cpp index 450f3b8298..cfcd008f09 100644 --- a/compiler/pipes/check-func-calls-and-vararg.cpp +++ b/compiler/pipes/check-func-calls-and-vararg.cpp @@ -4,6 +4,10 @@ #include "compiler/pipes/check-func-calls-and-vararg.h" +#include +#include +#include + #include "compiler/modulite-check-rules.h" #include "compiler/data/src-file.h" #include "compiler/rewrite-rules/replace-extern-func-calls.h" @@ -26,49 +30,41 @@ VertexPtr CheckFuncCallsAndVarargPass::on_enter_vertex(VertexPtr root) { return root; } -// calling a function with variadic arguments involves some transformations: -// function fun($x, ...$args) -// transformations will be: -// fun(1, 2, 3) -> fun(1, [2, 3]) -// fun(1, ...$arr) -> fun(1, $arr) -// fun(1, 2, 3, ...$arr1, ...$arr2) -> fun(1, array_merge([2, 3], $arr1, $arr2)) -// here we also deal with unpacking fixed-size arrays into positional arguments: -// fun(...[1]) -> fun(1) -// fun(...[1,2]) -> fun(1, [2]) -// fun(...[1, ...[2, ...[3, ...$rest]]]) => fun(1, array_merge([2,3], $rest)) -// this is done here (not in deducing types), as $f(...$variadic) — in invoke, not func call — are applicable only here -VertexAdaptor CheckFuncCallsAndVarargPass::process_varargs(VertexAdaptor call, FunctionPtr f_called) { - std::vector flattened_call_args; - flattened_call_args.reserve(call->args().size()); - VertexRange f_params = f_called->get_params(); - VertexRange call_args = call->args(); +namespace { +struct VarargAppendOptions { + int variadic_func_param_idx; + bool is_just_single_arg_forwarded; + bool needs_wrap_array_merge; +}; - // at first, convert f(1, ...[2, ...[3]], ...$all, ...[5]) to f(1,2,3,...$all,5) +std::vector flatten_call_args(VertexRange args, VertexRange func_params) { + std::vector flattened_call_args; + flattened_call_args.reserve(args.size()); std::function flatten_call_varg = [&flattened_call_args, &flatten_call_varg](VertexPtr inner) { - inner = VertexUtil::get_actual_value(inner); - if (auto as_array = inner.try_as()) { - for (VertexPtr item : as_array->args()) { - kphp_error(item->type() != op_double_arrow, "Passed unpacked ...[array] must be a vector, without => keys"); - kphp_assert(item->type() != op_varg); - flattened_call_args.emplace_back(item); - } - } else if (auto as_merge = inner.try_as(); as_merge && as_merge->str_val == "array_merge_spread") { - for (VertexPtr item : as_merge->args()) { - kphp_assert(item->type() == op_conv_array); - flatten_call_varg(item.as()->expr()); - } - } else { - auto wrap_varg = VertexAdaptor::create(inner).set_location(inner); - flattened_call_args.emplace_back(wrap_varg); + inner = VertexUtil::get_actual_value(inner); + if (auto as_array = inner.try_as()) { + for (VertexPtr item: as_array->args()) { + kphp_error(item->type() != op_double_arrow, "Passed unpacked ...[array] must be a vector, without => keys"); + kphp_assert(item->type() != op_varg); + flattened_call_args.emplace_back(item); + } + } else if (auto as_merge = inner.try_as(); as_merge && as_merge->str_val == "array_merge_spread") { + for (VertexPtr item: as_merge->args()) { + kphp_assert(item->type() == op_conv_array); + flatten_call_varg(item.as()->expr()); } + } else { + auto wrap_varg = VertexAdaptor::create(inner).set_location(inner); + flattened_call_args.emplace_back(wrap_varg); + } }; - for (VertexPtr call_arg : call->args()) { + for (VertexPtr call_arg: args) { if (auto as_varg = call_arg.try_as()) { size_t n_before = flattened_call_args.size(); flatten_call_varg(as_varg->array()); - for (size_t i = n_before; i <= flattened_call_args.size() && i < f_params.size(); ++i) { - auto ith_param = f_params[i].as(); + for (size_t i = n_before; i <= flattened_call_args.size() && i < func_params.size(); ++i) { + auto ith_param = func_params[i].as(); kphp_error(!ith_param->is_cast_param, fmt_format("Invalid place for unpack, because param ${} is @kphp-infer cast", ith_param->var()->str_val)); } } else { @@ -76,49 +72,75 @@ VertexAdaptor CheckFuncCallsAndVarargPass::process_varargs(VertexA } } + return flattened_call_args; +} + +std::tuple, std::vector, VarargAppendOptions> detect_variadic_args(const std::vector &flattened_call_args, FunctionPtr f_called) { std::vector new_call_args; bool is_just_single_arg_forwarded = false; bool needs_wrap_array_merge = false; std::vector variadic_args_passed; +// variadic_args_passed.reserve(flattened_call_args.size()); TODO uncomment and measure memory and time consumption + + std::unordered_set func_arg_names; + std::for_each(f_called->get_params().begin(), f_called->get_params().end(), [&](VertexPtr param) { + if (param.as()->extra_type != op_ex_param_variadic) { + func_arg_names.insert(param.as()->var()->get_string()); + } + }); + int i_func_param = 0; - // then, having f($x,$y,...$rest) and a call f(1,2,3,...$all,5), detect that variadic_args_passed = [3,...$all,5] for (int i_call_arg = 0; i_call_arg < flattened_call_args.size(); ++i_call_arg) { VertexPtr ith_call_arg = flattened_call_args[i_call_arg]; - bool is_variadic_param = f_called->has_variadic_param && i_func_param == f_params.size() - 1; + bool is_variadic_param = f_called->has_variadic_param && i_func_param == f_called->get_params().size() - 1; if (!is_variadic_param) { i_func_param++; - new_call_args.emplace_back(ith_call_arg); - kphp_error(ith_call_arg->type() != op_varg, "It's prohibited to unpack non-fixed arrays where positional arguments expected"); + + if (auto as_named = ith_call_arg.try_as(); as_named && func_arg_names.count(as_named->name()->get_string()) == 0) { + // foo($a, ...$args); foo(name : val, a: 1) --> foo(a : 1, ["name" : val]) + auto as_double_arrow = VertexAdaptor::create(as_named->name(), as_named->expr()); + variadic_args_passed.emplace_back(as_double_arrow); + } else { + new_call_args.emplace_back(ith_call_arg); + kphp_error(ith_call_arg->type() != op_varg, "It's prohibited to unpack non-fixed arrays where positional arguments expected"); + } continue; } if (auto unpack_as_varg = ith_call_arg.try_as()) { - if (i_call_arg == call_args.size() - 1 && i_func_param == f_params.size() - 1 && variadic_args_passed.empty()) { + if (i_call_arg == flattened_call_args.size() - 1 && i_func_param == f_called->get_params().size() - 1 && variadic_args_passed.empty()) { // variadic just have been forwarded, e.g. f(...$args) transformed to f($args) without any array_merge - variadic_args_passed.emplace_back(VertexUtil::create_conv_to(tp_array, unpack_as_varg->array())); is_just_single_arg_forwarded = true; } else { // f(...$args, ...$another) transformed to f(array_merge($args, $another)) - variadic_args_passed.emplace_back(VertexUtil::create_conv_to(tp_array, unpack_as_varg->array())); needs_wrap_array_merge = true; } + variadic_args_passed.emplace_back(VertexUtil::create_conv_to(tp_array, unpack_as_varg->array())); + } else if (auto as_named = ith_call_arg.try_as(); as_named && func_arg_names.count(as_named->name()->get_string()) == 0) { + // foo($a, ...$args); foo(name : val, a: 1) --> foo(a : 1, ["name" : val]) + auto as_double_arrow = VertexAdaptor::create(as_named->name(), as_named->expr()); + variadic_args_passed.emplace_back(as_double_arrow); } else { variadic_args_passed.emplace_back(ith_call_arg); } } - // append $rest = (from variadic_args_passed) as the last new_call_args - if (f_called->has_variadic_param && i_func_param == f_params.size() - 1) { - if (is_just_single_arg_forwarded) { + + return {std::move(new_call_args), std::move(variadic_args_passed), VarargAppendOptions{i_func_param, is_just_single_arg_forwarded, needs_wrap_array_merge}}; +} + +void append_variadic(std::vector &new_call_args, const std::vector & variadic_args_passed, VarargAppendOptions opts, FunctionPtr f_called, Location loc) { + if (f_called->has_variadic_param && opts.variadic_func_param_idx == f_called->get_params().size() - 1) { + if (opts.is_just_single_arg_forwarded) { // optimization: f(...$args) transformed to f($args) without any array_merge kphp_assert(variadic_args_passed.size() == 1); new_call_args.emplace_back(variadic_args_passed.front()); - } else if (!needs_wrap_array_merge) { + } else if (!opts.needs_wrap_array_merge) { // f(1, 2, 3) transformed into f([1,2,3]) - auto rest_array = VertexAdaptor::create(variadic_args_passed).set_location(call); + auto rest_array = VertexAdaptor::create(variadic_args_passed).set_location(loc); new_call_args.emplace_back(rest_array); } else { @@ -139,16 +161,108 @@ VertexAdaptor CheckFuncCallsAndVarargPass::process_varargs(VertexA } seq_items.emplace_back(variadic_args_passed[i]); } - auto seq_array = VertexAdaptor::create(seq_items).set_location(call); + auto seq_array = VertexAdaptor::create(seq_items).set_location(loc); variadic_args_conv_array.emplace_back(seq_array); } - auto merge_arrays = VertexAdaptor::create(variadic_args_conv_array).set_location(call); + auto merge_arrays = VertexAdaptor::create(variadic_args_conv_array).set_location(loc); merge_arrays->str_val = "array_merge"; merge_arrays->func_id = G->get_function(merge_arrays->str_val); new_call_args.emplace_back(merge_arrays); } } +} +} + +VertexAdaptor CheckFuncCallsAndVarargPass::reorder_with_defaults(VertexAdaptor call, FunctionPtr f) { + auto call_params = call->args(); + auto func_params = f->get_params(); + auto find_corresponding_param = [&](const std::string &call_arg_name) -> int { + for (int i = 0; i < func_params.size(); ++i) { + auto param = func_params[i]; + if (param.as()->var()->get_string() == call_arg_name) { + return i; + } + } + return -1; + }; + + std::vector call_arg_to_func_param(std::max(func_params.size(), call_params.size())); + + int call_arg_idx = 0; + // positional args + while (call_arg_idx < call_params.size() /*&& call_arg_idx < func_params.size()*/ && call_params[call_arg_idx]->type() != op_named_arg) { + call_arg_to_func_param[call_arg_idx] = call_params[call_arg_idx]; + call_arg_idx++; + } + while (call_arg_idx < call_params.size() && call_params[call_arg_idx]->type() == op_named_arg) { + auto as_named = call_params[call_arg_idx].as(); + auto corresp_param_idx = find_corresponding_param(as_named->name()->get_string()); + kphp_assert_msg(corresp_param_idx != -1, fmt_format("Unknown named argument {}", as_named->name()->get_string())); + call_arg_to_func_param[corresp_param_idx] = as_named->expr(); + call_arg_idx++; + } + + if (call_arg_idx < call_params.size()) { + kphp_assert_msg(call_arg_idx + 1 == call_params.size(), "Unexpected parameters"); + kphp_assert_msg(call_params[call_arg_idx]->type() == op_array, "op_array as vararg is expected"); + call_arg_to_func_param[call_arg_idx] = call_params[call_arg_idx]; + call_arg_idx++; + } else { + int lst_idx = std::distance(std::find_if(call_arg_to_func_param.rbegin(), call_arg_to_func_param.rend(), [](VertexPtr v) { return static_cast(v); }), call_arg_to_func_param.rend()); + call_arg_to_func_param.resize(lst_idx); + for (int i = 0; i < lst_idx; ++i) { + if (!call_arg_to_func_param[i]) { + kphp_error(func_params[i].as()->has_default_value(), "Not enough arguments for function call"); + call_arg_to_func_param[i] = func_params[i].as()->default_value().clone(); + } + } + } + + auto new_call = VertexAdaptor::create(call_arg_to_func_param).set_location_recursively(call); + new_call->str_val = call->str_val; + new_call->func_id = call->func_id; + new_call->extra_type = call->extra_type; + new_call->auto_inserted = call->auto_inserted; + + if (call_params.size() < func_params.size()) { + for (int missing_i = call_params.size(); missing_i < func_params.size(); ++missing_i) { + if (VertexPtr auto_added = maybe_autofill_missing_call_arg(new_call, f, func_params[missing_i].as())) { + for (int def_i = call_params.size(); def_i < missing_i; ++def_i) { + new_call = VertexUtil::add_call_arg(func_params[def_i].as()->default_value().clone(), new_call, false); + } + new_call = VertexUtil::add_call_arg(auto_added, new_call, false); + call_params = new_call->args(); + } + } + } + + return new_call; +} + + +// calling a function with variadic arguments involves some transformations: +// function fun($x, ...$args) +// transformations will be: +// fun(1, 2, 3) -> fun(1, [2, 3]) +// fun(1, ...$arr) -> fun(1, $arr) +// fun(1, 2, 3, ...$arr1, ...$arr2) -> fun(1, array_merge([2, 3], $arr1, $arr2)) +// here we also deal with unpacking fixed-size arrays into positional arguments: +// fun(...[1]) -> fun(1) +// fun(...[1,2]) -> fun(1, [2]) +// fun(...[1, ...[2, ...[3, ...$rest]]]) => fun(1, array_merge([2,3], $rest)) +// this is done here (not in deducing types), as $f(...$variadic) — in invoke, not func call — are applicable only here +VertexAdaptor CheckFuncCallsAndVarargPass::process_varargs(VertexAdaptor call, FunctionPtr f_called) { + VertexRange f_params = f_called->get_params(); + + // at first, convert f(1, ...[2, ...[3]], ...$all, ...[5]) to f(1,2,3,...$all,5) + std::vector flattened_call_args = flatten_call_args(call->args(), f_params); + + // then, having f($x,$y,...$rest) and a call f(1,2,3,...$all,5), detect that variadic_args_passed = [3,...$all,5] + auto [new_call_args, variadic_args_passed, opts] = detect_variadic_args(flattened_call_args, f_called); + + // append $rest = (from variadic_args_passed) as the last new_call_args + append_variadic(new_call_args, variadic_args_passed, opts, f_called, call->location); auto new_call = VertexAdaptor::create(new_call_args).set_location(call); new_call->extra_type = call->extra_type; @@ -253,17 +367,18 @@ VertexPtr CheckFuncCallsAndVarargPass::on_func_call(VertexAdaptor VertexRange func_params = f->get_params(); VertexRange call_params = call->args(); - if (call_params.size() < func_params.size()) { - for (int missing_i = call_params.size(); missing_i < func_params.size(); ++missing_i) { - if (VertexPtr auto_added = maybe_autofill_missing_call_arg(call, f, func_params[missing_i].as())) { - for (int def_i = call_params.size(); def_i < missing_i; ++def_i) { - call = VertexUtil::add_call_arg(func_params[def_i].as()->default_value(), call, false); - } - call = VertexUtil::add_call_arg(auto_added, call, false); - call_params = call->args(); - } - } - } +// if (call_params.size() < func_params.size()) { +// for (int missing_i = call_params.size(); missing_i < func_params.size(); ++missing_i) { +// if (VertexPtr auto_added = maybe_autofill_missing_call_arg(call, f, func_params[missing_i].as())) { +// for (int def_i = call_params.size(); def_i < missing_i; ++def_i) { +// call = VertexUtil::add_call_arg(func_params[def_i].as()->default_value(), call, false); +// } +// call = VertexUtil::add_call_arg(auto_added, call, false); +// call_params = call->args(); +// } +// } +// } + call = reorder_with_defaults(call, f); int call_n_params = call_params.size(); int delta_this = f->has_implicit_this_arg() ? 1 : 0; // not to count implicit $this on error output diff --git a/compiler/pipes/check-func-calls-and-vararg.h b/compiler/pipes/check-func-calls-and-vararg.h index 30f8e65ede..ae763e807f 100644 --- a/compiler/pipes/check-func-calls-and-vararg.h +++ b/compiler/pipes/check-func-calls-and-vararg.h @@ -12,6 +12,7 @@ class CheckFuncCallsAndVarargPass final : public FunctionPassBase { VertexPtr maybe_autofill_missing_call_arg(VertexAdaptor call, FunctionPtr f_called, VertexAdaptor param); VertexPtr create_CompileTimeLocation_call_arg(const Location &call_location); + VertexAdaptor reorder_with_defaults(VertexAdaptor call, FunctionPtr f); public: std::string get_description() override { diff --git a/compiler/pipes/deduce-implicit-types-and-casts.cpp b/compiler/pipes/deduce-implicit-types-and-casts.cpp index 5396b8d9e3..40f3b5c382 100644 --- a/compiler/pipes/deduce-implicit-types-and-casts.cpp +++ b/compiler/pipes/deduce-implicit-types-and-casts.cpp @@ -5,6 +5,9 @@ #include "compiler/pipes/deduce-implicit-types-and-casts.h" #include "compiler/pipes/transform-to-smart-instanceof.h" +#include +#include + #include "compiler/compiler-core.h" #include "compiler/data/src-file.h" #include "compiler/data/generics-mixins.h" @@ -559,6 +562,83 @@ void DeduceImplicitTypesAndCastsPass::on_phpdoc_for_var(VertexAdaptor &call, VertexRange f_called_params) { + auto call_args = call->args(); + + auto find_corresponding_param = [&f_called_params](const std::string &call_arg_name) -> std::optional> { + for (auto param: f_called_params) { + if (param.as()->var()->get_string() == call_arg_name) { + return param.as(); + } + } + return std::nullopt; + }; + + std::vector> call_arg_to_func_param(call_args.size()); + int call_arg_idx = 0; + + // positional args + while (call_arg_idx < call_args.size() && call_arg_idx < f_called_params.size() && call_args[call_arg_idx]->type() != op_named_arg) { + call_arg_to_func_param[call_arg_idx] = f_called_params[call_arg_idx].as(); + + if (call_arg_idx < f_called_params.size() && f_called_params[call_arg_idx]->extra_type == op_ex_param_variadic) { + int vararg_idx = call_arg_idx; + while (call_arg_idx < call_args.size()) { + call_arg_to_func_param[call_arg_idx++] = f_called_params[vararg_idx].as(); + } + break; + } + call_arg_idx++; + } + + std::vector mismatched_named_arg; + std::unordered_set unique_names; + unique_names.reserve(10); + mismatched_named_arg.reserve(10); + + // named args + while (call_arg_idx < call_args.size() && call_arg_idx < f_called_params.size()) { + kphp_error(call_args[call_arg_idx]->type() == op_named_arg, "Positional arguments after named ones are prohibited"); + auto as_named = call_args[call_arg_idx].as(); + + if (auto [_, absent] = unique_names.insert(as_named->name()->get_string()); !absent) { + kphp_error(false, fmt_format("Named arguments with duplicated name: \'{}\'", as_named->name()->get_string())); + } + + std::optional> corresp_func_param = find_corresponding_param(as_named->name()->get_string()); + if (corresp_func_param.has_value()) { + call_arg_to_func_param[call_arg_idx] = corresp_func_param.value(); + } else { + mismatched_named_arg.push_back(call_arg_idx); + } + call_arg_idx++; + } + + // if function declaration has variadic, then we should patch each extra named arg with it + if (!f_called_params.empty() && f_called_params.back()->extra_type == op_ex_param_variadic) { + for (auto idx: mismatched_named_arg) { + call_arg_to_func_param[idx] = f_called_params.back().as(); + } + } + + + const bool ok = mismatched_named_arg.size() == 0 || f_called_params.back()->extra_type == op_ex_param_variadic; + + kphp_error(ok, fmt_format("Unknown parameter name: {}", call_args[mismatched_named_arg.front()].as()->name()->get_string())); + + + for (call_arg_idx = 0; call_arg_idx < call_args.size(); ++call_arg_idx) { + if (call_arg_to_func_param[call_arg_idx] && call_arg_to_func_param[call_arg_idx]->type_hint) { + if (auto as_named = call_args[call_arg_idx].try_as()) { + patch_call_arg_on_func_call(call_arg_to_func_param[call_arg_idx], as_named->expr(), call); + } else { + patch_call_arg_on_func_call(call_arg_to_func_param[call_arg_idx], call_args[call_arg_idx], call); + } + } + } + +} + // for every `f(...)`, bind func_id // for every call argument, patch it to fit @param of f() // if f() is a generic function `f(...)`, deduce instantiation Ts (save call->reifiedTs) @@ -639,18 +719,7 @@ void DeduceImplicitTypesAndCastsPass::on_func_call(VertexAdaptor c } // now, loop through every argument and potentially patch it - for (int i = 0; i < f_called_params.size() && i < call_args.size(); ++i) { - auto param = f_called_params[i].as(); - - if (param->type_hint) { - patch_call_arg_on_func_call(param, call_args[i], call); - if (param->extra_type == op_ex_param_variadic) { // all the rest arguments are meant to be passed to this param - for (++i; i < call_args.size(); ++i) { // here, they are not replaced with an array: see CheckFuncCallsAndVarargPass - patch_call_arg_on_func_call(param, call_args[i], call); - } - } - } - } + patch_call_args(call, f_called_params); } // a helper to print a human-readable error for `f()` when f not found diff --git a/compiler/pipes/deduce-implicit-types-and-casts.h b/compiler/pipes/deduce-implicit-types-and-casts.h index fe6fd6ef6c..5a84a12fec 100644 --- a/compiler/pipes/deduce-implicit-types-and-casts.h +++ b/compiler/pipes/deduce-implicit-types-and-casts.h @@ -35,6 +35,7 @@ class DeduceImplicitTypesAndCastsPass final : public FunctionPassBase { int print_error_unexisting_function(const std::string &call_string); + void patch_call_args(VertexAdaptor &call, VertexRange f_called_params); void patch_call_arg_on_func_call(VertexAdaptor param, VertexPtr &call_arg, VertexAdaptor call); void on_set_to_var(VertexAdaptor lhs, VertexPtr &rhs); diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index 493011e3c0..44a9bee38e 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -580,6 +580,15 @@ ] } }, + { + "comment": "proxy vertex for named argument", + "name": "op_named_arg", + "base_name": "meta_op_base", + "sons": { + "name" : 0, + "expr" : 1 + } + }, { "comment": "variadic argument: array()...", "name": "op_varg", diff --git a/tests/phpt/named_args/001_basic.php b/tests/phpt/named_args/001_basic.php new file mode 100644 index 0000000000..dc197db514 --- /dev/null +++ b/tests/phpt/named_args/001_basic.php @@ -0,0 +1,37 @@ +@php8 ok +