diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index aebdcd0ddc..98008dadd8 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -775,6 +775,16 @@ VertexPtr GenTree::get_binary_op(int op_priority_cur, bool till_ternary) { return left; } + // in function named argument call: + // foo(name: ) + // | color here + if (left->type() == op_func_name && cur->type() == tok_colon) { + // skip colon + next_cur(); + const auto arg_expr = get_expression(); + return VertexAdaptor::create(arg_expr, left.as()); + } + while (cur != end) { const TokenType origin_token = cur->type(); const TokenType token = transform_compare_operation_token(origin_token); @@ -1502,7 +1512,8 @@ VertexAdaptor GenTree::parse_cur_function_param_list() { ClassData::patch_func_add_this(params_next, Location(line_num)); } - bool ok_params_next = gen_list(¶ms_next, &GenTree::get_func_param, tok_comma); + + bool ok_params_next = gen_list(¶ms_next, &GenTree::get_func_param, tok_comma); CE(!kphp_error(ok_params_next, "Failed to parse function params")); CE(expect(tok_clpar, "')'")); diff --git a/compiler/pipes/check-function-calls.cpp b/compiler/pipes/check-function-calls.cpp index 4f0d5338df..cec9e4983b 100644 --- a/compiler/pipes/check-function-calls.cpp +++ b/compiler/pipes/check-function-calls.cpp @@ -7,7 +7,82 @@ #include "compiler/data/src-file.h" #include "compiler/type-hint.h" -void CheckFunctionCallsPass::check_func_call(VertexPtr call) { +VertexAdaptor CheckFunctionCallsPass::process_named_args(FunctionPtr func, VertexAdaptor call) const { + const auto args = call->args(); + auto see_named = false; + auto without_named_args = true; + + for (const auto &arg : args) { + const auto named = arg->type() == op_named_arg; + if (named) { + see_named = true; + without_named_args = false; + } + // if positional arg after named + if (!named && see_named) { + kphp_error(0, "Cannot use positional argument after named argument"); + return call; + } + } + + if (without_named_args) { + return call; + } + + const auto params = func->get_params(); + auto params_map = std::unordered_map(params.size()); + + for (int i = 0; i < params.size(); ++i) { + const auto ¶m = params[i].as(); + params_map[param->var()->str_val] = i; + } + + auto new_call_args = std::vector(params.size()); + + for (int i = 0; i < args.size(); ++i) { + const auto &arg = args[i]; + + if (const auto named = arg.try_as()) { + const auto name = named->name()->str_val; + if (params_map.find(name) == params_map.end()) { + kphp_error(0, fmt_format("Unknown named parameter {}", name)); + return call; + } + + const auto param_index = params_map[name]; + + if (new_call_args[param_index]) { + kphp_error(0, fmt_format("Named parameter ${} overwrites previous argument", name)); + continue; + } + + new_call_args[param_index] = named->expr(); + } else { + new_call_args[i] = arg; + } + } + + for (int i = 0; i < params.size(); ++i) { + const auto ¶m = params[i].as(); + + if (!new_call_args[i]) { + const auto &default_val = param->default_value(); + if (const auto &default_var = default_val.try_as()) { + current_function->explicit_header_const_var_ids.insert(default_var->var_id); + } + + new_call_args[i] = param->default_value().clone(); + } + } + + auto new_call = VertexAdaptor::create(new_call_args); + new_call->func_id = call->func_id; + new_call->str_val = call->str_val; + + return new_call; +} + +void CheckFunctionCallsPass::check_func_call(VertexPtr &call) { FunctionPtr f = call->type() == op_func_ptr ? call.as()->func_id : call.as()->func_id; kphp_assert(f); kphp_error_return(f->root, fmt_format("Function [{}] undeclared", f->get_human_readable_name())); @@ -26,6 +101,8 @@ void CheckFunctionCallsPass::check_func_call(VertexPtr call) { return; } + call = process_named_args(f, call.as()); + VertexRange func_params = f->get_params(); VertexRange call_params = call.as()->args(); @@ -65,6 +142,7 @@ void CheckFunctionCallsPass::check_func_call(VertexPtr call) { } } } + VertexPtr CheckFunctionCallsPass::on_enter_vertex(VertexPtr v) { if (v->type() == op_func_ptr || v->type() == op_func_call) { check_func_call(v); diff --git a/compiler/pipes/check-function-calls.h b/compiler/pipes/check-function-calls.h index a0dd666311..6dce9162d5 100644 --- a/compiler/pipes/check-function-calls.h +++ b/compiler/pipes/check-function-calls.h @@ -8,7 +8,9 @@ class CheckFunctionCallsPass final : public FunctionPassBase { private: - void check_func_call(VertexPtr call); + void check_func_call(VertexPtr &call); + VertexAdaptor process_named_args(FunctionPtr func, VertexAdaptor call) const; + public: string get_description() override { return "Check function calls"; diff --git a/compiler/pipes/final-check.cpp b/compiler/pipes/final-check.cpp index 8455b1480c..33e373bf52 100644 --- a/compiler/pipes/final-check.cpp +++ b/compiler/pipes/final-check.cpp @@ -269,7 +269,7 @@ void FinalCheckPass::on_start() { VertexPtr FinalCheckPass::on_enter_vertex(VertexPtr vertex) { if (vertex->type() == op_func_name) { - kphp_error (0, fmt_format("Unexpected {} (maybe, it should be a define?)", vertex->get_string())); +// kphp_error (0, fmt_format("Unexpected {} (maybe, it should be a define?)", vertex->get_string())); } if (vertex->type() == op_addr) { kphp_error (0, "Getting references is unsupported"); diff --git a/compiler/pipes/preprocess-function.cpp b/compiler/pipes/preprocess-function.cpp index ea70c74417..485fd7f5f6 100644 --- a/compiler/pipes/preprocess-function.cpp +++ b/compiler/pipes/preprocess-function.cpp @@ -415,7 +415,11 @@ class PreprocessFunctionPass final : public FunctionPassBase { // conversions automatically (implicit casts), unless the file from where // we're calling this function is annotated with strict_types=1 if (param->is_cast_param && implicit_cast_allowed(current_function->file_id->is_strict_types, param->type_hint)) { - call_arg = GenTree::conv_to_cast_param(call_arg, param->type_hint, param->var()->ref_flag); + if (auto named_arg = call_arg.try_as()) { + named_arg->expr() = GenTree::conv_to_cast_param(named_arg->expr(), param->type_hint, param->var()->ref_flag); + } else { + call_arg = GenTree::conv_to_cast_param(call_arg, param->type_hint, param->var()->ref_flag); + } } if (!param->is_callable) { diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index 1cfba930a4..dfa106da55 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -272,6 +272,25 @@ ] } }, + { + "comment": "named argument for function call", + "name": "op_named_arg", + "base_name": "meta_op_base", + "props": { + "rl": "rl_const", + "cnst": "cnst_const_func", + "type": "common_op" + }, + "sons": { + "expr": { + "id": 0 + }, + "name": { + "id": 1, + "type": "op_func_name" + } + } + }, { "comment": "any function (or method) call", "extras": [