Skip to content
12 changes: 1 addition & 11 deletions compiler/code-gen/vertex-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1446,17 +1446,7 @@ void compile_function(VertexAdaptor<op_function> 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<op_func_param>()->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;
}

Expand Down
18 changes: 17 additions & 1 deletion compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ VertexAdaptor<Op> GenTree::get_func_call() {
CE (expect(tok_oppar, "'('"));
skip_phpdoc_tokens();
std::vector<VertexPtr> next;
bool ok_next = gen_list<EmptyOp>(&next, &GenTree::get_expression, tok_comma);
bool ok_next = gen_list<EmptyOp>(&next, &GenTree::get_func_call_arg, tok_comma);
CE (!kphp_error(ok_next, "get argument list failed"));
CE (expect(tok_clpar, "')'"));

Expand Down Expand Up @@ -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();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably add kphp_error (...) here.

VertexPtr value = get_expression();
kphp_error (value, "...");

CE (!kphp_error(name_or_val, "Bad value of named argument"));
return VertexAdaptor<op_named_arg>::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;

Expand Down
1 change: 1 addition & 0 deletions compiler/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<op_catch> get_catch();
void get_instance_var_list(const PhpDocComment *phpdoc, FieldModifiers modifiers, const TypeHint *type_hint);
Expand Down
235 changes: 175 additions & 60 deletions compiler/pipes/check-func-calls-and-vararg.cpp

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions compiler/pipes/check-func-calls-and-vararg.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class CheckFuncCallsAndVarargPass final : public FunctionPassBase {

VertexPtr maybe_autofill_missing_call_arg(VertexAdaptor<op_func_call> call, FunctionPtr f_called, VertexAdaptor<op_func_param> param);
VertexPtr create_CompileTimeLocation_call_arg(const Location &call_location);
VertexAdaptor<op_func_call> reorder_with_defaults(VertexAdaptor<op_func_call> call, FunctionPtr f);

public:
std::string get_description() override {
Expand Down
93 changes: 81 additions & 12 deletions compiler/pipes/deduce-implicit-types-and-casts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include "compiler/pipes/deduce-implicit-types-and-casts.h"
#include "compiler/pipes/transform-to-smart-instanceof.h"

#include <optional>
#include <vector>

#include "compiler/compiler-core.h"
#include "compiler/data/src-file.h"
#include "compiler/data/generics-mixins.h"
Expand Down Expand Up @@ -559,6 +562,83 @@ void DeduceImplicitTypesAndCastsPass::on_phpdoc_for_var(VertexAdaptor<op_phpdoc_
phpdocs_for_vars.emplace_front(v_phpdoc);
}

void DeduceImplicitTypesAndCastsPass::patch_call_args(VertexAdaptor<op_func_call> &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<VertexAdaptor<op_func_param>> {
for (auto param: f_called_params) {
if (param.as<op_func_param>()->var()->get_string() == call_arg_name) {
return param.as<op_func_param>();
}
}
return std::nullopt;
};

std::vector<VertexAdaptor<op_func_param>> 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<op_func_param>();

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<op_func_param>();
}
break;
}
call_arg_idx++;
}

std::vector<int> mismatched_named_arg;
std::unordered_set<vk::string_view> 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<op_named_arg>();

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<VertexAdaptor<op_func_param>> 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<op_func_param>();
}
}


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<op_named_arg>()->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<op_named_arg>()) {
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<T1,T2,...>(...)`, deduce instantiation Ts (save call->reifiedTs)
Expand Down Expand Up @@ -639,18 +719,7 @@ void DeduceImplicitTypesAndCastsPass::on_func_call(VertexAdaptor<op_func_call> 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<op_func_param>();

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
Expand Down
1 change: 1 addition & 0 deletions compiler/pipes/deduce-implicit-types-and-casts.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class DeduceImplicitTypesAndCastsPass final : public FunctionPassBase {

int print_error_unexisting_function(const std::string &call_string);

void patch_call_args(VertexAdaptor<op_func_call> &call, VertexRange f_called_params);
void patch_call_arg_on_func_call(VertexAdaptor<op_func_param> param, VertexPtr &call_arg, VertexAdaptor<op_func_call> call);

void on_set_to_var(VertexAdaptor<op_var> lhs, VertexPtr &rhs);
Expand Down
9 changes: 9 additions & 0 deletions compiler/vertex-desc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
37 changes: 37 additions & 0 deletions tests/phpt/named_args/001_basic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@php8 ok
<?php

function test($a, $b, $c = "c", $d = "d", $e = "e") {
echo "a=$a, b=$b, c=$c, d=$d, e=$e\n";
}

$a = "A"; $b = "B"; $c = "C"; $d = "D"; $e = "E";

test("A", "B", "C", d: "D", e: "E");
test("A", "B", "C", e: "E", d: "D");
test(e: "E", a: "A", d: "D", b: "B", c: "C");
test("A", "B", "C", e: "E");

test("A", "B", e : "E");
test("A", "B", d : "D");
test("A", "B", d : "D", e : "E");

test2("A", "B", "C", d: "D", e: "E");
test2("A", "B", "C", e: "E", d: "D");
test2(e: "E", a: "A", d: "D", b: "B", c: "C");
test2("A", "B", "C", e: "E");

test($a, $b, $c, d: $d, e: $e);
test($a, $b, $c, e: $e, d: $d);
test(e: $e, a: $a, d: $d, b: $b, c: $c);
test(a: $a, b: $b, c: $c, e: $e);

test2($a, $b, $c, d: $d, e: $e);
test2($a, $b, $c, e: $e, d: $d);
test2(e: $e, a: $a, d: $d, b: $b, c: $c);
test2(a: $a, b: $b, c: $c, e: $e);

function test2($a, $b, $c = "c", $d = "d", $e = "e") {
echo "a=$a, b=$b, c=$c, d=$d, e=$e\n";
}

13 changes: 13 additions & 0 deletions tests/phpt/named_args/002_vararg.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@ok php8
<?php

function foo(int $a, int $b, ...$args) {
var_dump($args);
var_dump($a);
var_dump($b);
}

foo(a : 1, b : 2, args2: [3, 4, 5]);
foo(1, b : 2, args2: [3, 4, 5]);
foo(1, 2, args2 : [3, 4, 5]);

15 changes: 15 additions & 0 deletions tests/phpt/named_args/003_defaults.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@ok php8
<?php

function test1($a = 'a', $b = 'b') {
echo "a: $a, b: $b\n";
}

function test2($a = SOME_CONST, $b = 'b') {
echo "a: $a, b: $b\n";
}

test1(b: 'B');

define('SOME_CONST', 'X');
test2(b: 'B');
8 changes: 8 additions & 0 deletions tests/phpt/named_args/100_duplicate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@kphp_should_fail
/Named arguments with duplicated name: 'a'/
<?php


function test($a, $b) {}

test(a: 1, a: 2);