From 04e00a46d47bcdd3cf96f4bc3759060c65697f97 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Mon, 13 Feb 2023 08:13:52 -0300 Subject: [PATCH] cgen, checker: allow array decompose on non-variadic func call (e.g call(...arr)) (#17284) --- vlib/v/checker/fn.v | 12 +- .../checker/tests/arraydecompose_arg2_err.out | 6 + .../checker/tests/arraydecompose_arg2_err.vv | 8 + .../checker/tests/arraydecompose_arg_err.out | 6 + .../v/checker/tests/arraydecompose_arg_err.vv | 8 + vlib/v/fmt/fmt.v | 6 +- vlib/v/gen/c/comptime.v | 34 ++++- vlib/v/gen/c/fn.v | 13 ++ .../v/tests/arraydecompose_nonvariadic_test.v | 137 ++++++++++++++++++ 9 files changed, 220 insertions(+), 10 deletions(-) create mode 100644 vlib/v/checker/tests/arraydecompose_arg2_err.out create mode 100644 vlib/v/checker/tests/arraydecompose_arg2_err.vv create mode 100644 vlib/v/checker/tests/arraydecompose_arg_err.out create mode 100644 vlib/v/checker/tests/arraydecompose_arg_err.vv create mode 100644 vlib/v/tests/arraydecompose_nonvariadic_test.v diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 6b074e0df6..a05cc46fd1 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -965,11 +965,14 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. c.error('too many arguments in call to `${func.name}` (non-js backend: ${c.pref.backend})', node.pos) } + mut has_decompose := false for i, mut call_arg in node.args { if func.params.len == 0 { continue } - + if !func.is_variadic && has_decompose { + c.error('cannot have parameter after array decompose', node.pos) + } param := if func.is_variadic && i >= func.params.len - 1 { func.params.last() } else { @@ -980,6 +983,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. c.error('too many arguments in call to `${func.name}`', node.pos) } } + has_decompose = call_arg.expr is ast.ArrayDecompose if func.is_variadic && i >= func.params.len - 1 { param_sym := c.table.sym(param.typ) mut expected_type := param.typ @@ -2048,6 +2052,12 @@ fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ! } if f.is_variadic { min_required_params-- + } else { + has_decompose := node.args.filter(it.expr is ast.ArrayDecompose).len > 0 + if has_decompose { + // if call(...args) is present + min_required_params = nr_args + } } if min_required_params < 0 { min_required_params = 0 diff --git a/vlib/v/checker/tests/arraydecompose_arg2_err.out b/vlib/v/checker/tests/arraydecompose_arg2_err.out new file mode 100644 index 0000000000..33ba8eb3fe --- /dev/null +++ b/vlib/v/checker/tests/arraydecompose_arg2_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/arraydecompose_arg2_err.vv:7:12: error: cannot use `f64` as `int` in argument 1 to `test_arr` + 5 | fn main() { + 6 | var := []f64{} + 7 | test_arr(...var) + | ~~~~~~ + 8 | } diff --git a/vlib/v/checker/tests/arraydecompose_arg2_err.vv b/vlib/v/checker/tests/arraydecompose_arg2_err.vv new file mode 100644 index 0000000000..b4555387fe --- /dev/null +++ b/vlib/v/checker/tests/arraydecompose_arg2_err.vv @@ -0,0 +1,8 @@ +fn test_arr(i int) string { + return '${i}' +} + +fn main() { + var := []f64{} + test_arr(...var) +} \ No newline at end of file diff --git a/vlib/v/checker/tests/arraydecompose_arg_err.out b/vlib/v/checker/tests/arraydecompose_arg_err.out new file mode 100644 index 0000000000..1d86616dd9 --- /dev/null +++ b/vlib/v/checker/tests/arraydecompose_arg_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/arraydecompose_arg_err.vv:7:2: error: cannot have parameter after array decompose + 5 | fn test_main() { + 6 | var := [0.0] + 7 | test_arr(1, ...var, 'a') + | ~~~~~~~~~~~~~~~~~~~~~~~~ + 8 | } diff --git a/vlib/v/checker/tests/arraydecompose_arg_err.vv b/vlib/v/checker/tests/arraydecompose_arg_err.vv new file mode 100644 index 0000000000..df4f7844c0 --- /dev/null +++ b/vlib/v/checker/tests/arraydecompose_arg_err.vv @@ -0,0 +1,8 @@ +fn test_arr(i int, f f64, s string) string { + return '${i} : ${f}' +} + +fn test_main() { + var := [0.0] + test_arr(1, ...var, 'a') +} diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index ee6ec22a28..9b7c9b1970 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1906,7 +1906,11 @@ pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) { inner_args := if node.args_var != '' { node.args_var } else { - node.args.map(it.str()).join(', ') + node.args.map(if it.expr is ast.ArrayDecompose { + '...${it.expr.expr.str()}' + } else { + it.str() + }).join(', ') } method_expr := if node.has_parens { '(${node.method_name}(${inner_args}))' diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 371a419bd0..57ad0402f0 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -141,20 +141,24 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { } else { false } + mut has_decompose := !m.is_variadic + && node.args.filter(it.expr is ast.ArrayDecompose).len > 0 // check argument length and types if m.params.len - 1 != node.args.len && !expand_strs { if g.inside_call { g.error('expected ${m.params.len - 1} arguments to method ${sym.name}.${m.name}, but got ${node.args.len}', node.pos) } else { - // do not generate anything if the argument lengths don't match - g.writeln('/* skipping ${sym.name}.${m.name} due to mismatched arguments list */') - // g.writeln('println(_SLIT("skipping ${node.sym.name}.$m.name due to mismatched arguments list"));') - // eprintln('info: skipping ${node.sym.name}.$m.name due to mismatched arguments list\n' + - //'method.params: $m.params, args: $node.args\n\n') - // verror('expected ${m.params.len-1} arguments to method ${node.sym.name}.$m.name, but got $node.args.len') + if !has_decompose { + // do not generate anything if the argument lengths don't match + g.writeln('/* skipping ${sym.name}.${m.name} due to mismatched arguments list */') + // g.writeln('println(_SLIT("skipping ${node.sym.name}.$m.name due to mismatched arguments list"));') + // eprintln('info: skipping ${node.sym.name}.$m.name due to mismatched arguments list\n' + + //'method.params: $m.params, args: $node.args\n\n') + // verror('expected ${m.params.len-1} arguments to method ${node.sym.name}.$m.name, but got $node.args.len') + return + } } - return } if !g.inside_call && (m.return_type.has_flag(.option) || m.return_type.has_flag(.result)) { @@ -181,7 +185,21 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { continue } } - if i - 1 < node.args.len - 1 { + if (i - 1 <= node.args.len - 1) && has_decompose + && node.args[i - 1].expr is ast.ArrayDecompose { + mut d_count := 0 + for d_i in i .. m.params.len { + g.write('*(${g.typ(m.params[i].typ)}*)array_get(') + g.expr(node.args[i - 1].expr) + g.write(', ${d_count})') + + if d_i < m.params.len - 1 { + g.write(', ') + } + d_count++ + } + break + } else if i - 1 < node.args.len - 1 { g.expr(node.args[i - 1].expr) g.write(', ') } else if !expand_strs && i == node.args.len { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 9d1b23532a..104b0e1d7f 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1759,6 +1759,19 @@ fn (mut g Gen) call_args(node ast.CallExpr) { } } } + } else if arg.expr is ast.ArrayDecompose { + mut d_count := 0 + for d_i in i .. expected_types.len { + g.write('*(${g.typ(expected_types[d_i])}*)array_get(') + g.expr(arg.expr) + g.write(', ${d_count})') + + if d_i < expected_types.len - 1 { + g.write(', ') + } + d_count++ + } + continue } use_tmp_var_autofree := g.is_autofree && arg.typ == ast.string_type && arg.is_tmp_autofree && !g.inside_const && !g.is_builtin_mod diff --git a/vlib/v/tests/arraydecompose_nonvariadic_test.v b/vlib/v/tests/arraydecompose_nonvariadic_test.v new file mode 100644 index 0000000000..08b3b2a7a7 --- /dev/null +++ b/vlib/v/tests/arraydecompose_nonvariadic_test.v @@ -0,0 +1,137 @@ +type Any = f64 | int | string + +struct Test {} + +struct Test2 {} + +struct Test3 {} + +fn (t Test) test(args ...Any) { + println('called with ${args}') +} + +fn (t Test2) test_str(arg string) { + println('called with ${arg}') +} + +fn (t Test2) test_int(arg int, arg2 int) { + println('called with ${arg}, ${arg2}') +} + +fn (t Test3) test_int(arg int, arg2 int) { + println('called with ${arg}, ${arg2}') +} + +fn foo_any(i Any, k Any, j Any) string { + return '${i} : ${k} : ${j}' +} + +fn foo(i int, k int) string { + return '${i} : ${k}' +} + +fn bar(i f64, k f64, j f64) string { + return '${i} : ${k} : ${j}' +} + +fn baz(s string) string { + return s +} + +fn f_arr(i int, f f64) string { + return '${i} : ${f}' +} + +fn f_var(s string, args ...string) string { + return '${s} [ ${args.map(it).join(',')} ]' +} + +fn varargs[T](args ...T) string { + assert args.len > 0 + return args.map(it.str()).join(' : ') +} + +fn call[T](func_name string, args ...T) string { + return match func_name { + 'foo' { foo(...args) } + 'bar' { bar(...args) } + 'baz' { baz(...args) } + 'varargs' { varargs(...args) } + else { '' } + } +} + +fn call_any(func_name string, args ...Any) string { + return match func_name { + 'foo_any' { foo_any(...args) } + else { '' } + } +} + +fn comptime_call[T](instance T, method_name string, args ...Any) bool { + $for method in T.methods { + if method.name == method_name { + instance.$method(...args) + return true + } + } + return false +} + +fn comptime_call_vargs[T, R](instance T, method_name string, args ...R) bool { + $for method in T.methods { + if method.name == method_name { + instance.$method(...args) + return true + } + } + return false +} + +fn comptime_call_vargs2[T, R](instance T, method_name string, args ...R) bool { + $for method in T.methods { + if method.name == method_name { + instance.$method(200, ...args) + return true + } + } + return false +} + +fn test_main() { + assert call('foo', 10, 100) == '10 : 100' + assert call('bar', 1.1, 1.2, 1.3) == '1.1 : 1.2 : 1.3' + assert call('baz', 'test') == 'test' + assert call_any('foo_any', 10, 1.2, 'test') == "Any(10) : Any(1.2) : Any('test')" + assert call[Any]('varargs', 10, 1.2, 'test') == "Any(10) : Any(1.2) : Any('test')" + + a := []int{len: 2, init: 50} + assert foo(...a) == '50 : 50' + + b := []f64{len: 3, init: 1.2} + assert bar(...b) == '1.2 : 1.2 : 1.2' + + mut c := []Any{} + c << 10 + c << 1.2 + c << 'test' + assert varargs(...c) == "Any(10) : Any(1.2) : Any('test')" + + var := [0.0] + assert f_arr(1, ...var) == '1 : 0.0' + + var2 := ['a', 'b', 'c'] + assert f_var('foo', ...var2) == 'foo [ a,b,c ]' +} + +fn test_comptime() { + var := Test{} + assert comptime_call(var, 'test', 1, 2.3, '') + + var2 := Test2{} + assert comptime_call_vargs(var2, 'test_int', 1, 100) + assert comptime_call_vargs(var2, 'test_str', 'foo') + + var3 := Test3{} + assert comptime_call_vargs2(var3, 'test_int', 100) +}