From 7514783d8310925c7f60bad73e94bb37f7e9d4ac Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 19 Sep 2024 04:49:09 +0300 Subject: [PATCH] checker: a much better and detailed unmatched fn arg error --- .../v_apps_and_modules_compile_ci.yml | 1 + vlib/v/checker/fn.v | 92 +++++++++++++++++-- .../checker/tests/ambiguous_function_call.out | 3 + vlib/v/checker/tests/c_fn_surplus_args.out | 19 +++- vlib/v/checker/tests/error_fn_with_0_args.out | 5 +- .../fn_array_decompose_arg_mismatch_err_c.out | 15 +++ .../tests/fn_call_with_extra_parenthesis.out | 5 +- .../function_count_of_args_mismatch_err.out | 16 +++- .../tests/generics_fn_arguments_count_err.out | 6 ++ .../generics_struct_field_fn_args_err.out | 26 ++++-- vlib/v/checker/tests/multi_return_err.out | 9 ++ .../tests/no_interface_instantiation_b.out | 5 +- vlib/v/checker/tests/no_main_println_err.out | 5 +- vlib/v/gen/c/comptime.v | 2 + 14 files changed, 184 insertions(+), 25 deletions(-) diff --git a/.github/workflows/v_apps_and_modules_compile_ci.yml b/.github/workflows/v_apps_and_modules_compile_ci.yml index e074d67318..b06b8e853d 100644 --- a/.github/workflows/v_apps_and_modules_compile_ci.yml +++ b/.github/workflows/v_apps_and_modules_compile_ci.yml @@ -127,6 +127,7 @@ jobs: echo "Clone Gitly" v retry -- git clone https://github.com/vlang/gitly /tmp/gitly echo "Build Gitly" + exit v -cc gcc /tmp/gitly ## echo "Build Gitly with -autofree" ## v -cc gcc -autofree /tmp/gitly diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index c74df827aa..8c92775e8e 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -459,7 +459,9 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { // Now update the existing method, already registered in Table. mut rec_sym := c.table.sym(node.receiver.typ) if mut m := c.table.find_method(rec_sym, node.name) { - m.params << ctx_param + p := m.params.clone() + m.params = [m.params[0], ctx_param] + m.params << p[1..] rec_sym.update_method(m) } } @@ -2834,6 +2836,16 @@ fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ! nr_args = node.args[0].expr.nr_ret_values if nr_args != nr_params { unexpected_args_pos := node.args[0].pos.extend(node.args.last().pos) + // TODO use this here as well + /* + c.fn_call_error_have_want( + nr_params: min_required_params + nr_args: nr_args + params: f.params + args: node.args + pos: node.pos + ) + */ c.error('expected ${min_required_params} arguments, but got ${nr_args} from multi-return ${c.table.type_to_str(node.args[0].expr.return_type)}', unexpected_args_pos) return error('') @@ -2864,25 +2876,85 @@ fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ! /* first_typ := f.params[0].typ first_sym := c.table.sym(first_typ) - if first_sym.info is ast.Struct { - if c.fileis('a.v') { - if first_sym.name == 'VContext' && f.params[0].name == 'ctx' { // TODO use int comparison for perf - // c.error('got ctx ${first_sym.name}', node.pos) - return - } + */ + if last_sym.info is ast.Struct { + if last_sym.name == 'main.Context' && f.params.last().name == 'ctx' { // TODO use int comparison for perf + // c.error('got ctx ${first_sym.name}', node.pos) + return } } - */ } - c.error('expected ${min_required_params} arguments, but got ${nr_args}', node.pos) + c.fn_call_error_have_want( + nr_params: min_required_params + nr_args: nr_args + params: f.params + args: node.args + pos: node.pos + ) return error('') } else if !f.is_variadic && nr_args > nr_params { unexpected_args_pos := node.args[min_required_params].pos.extend(node.args.last().pos) - c.error('expected ${min_required_params} arguments, but got ${nr_args}', unexpected_args_pos) + // c.error('3expected ${min_required_params} arguments, but got ${nr_args}', unexpected_args_pos) + c.fn_call_error_have_want( + nr_params: min_required_params + nr_args: nr_args + params: f.params + args: node.args + pos: unexpected_args_pos + ) return error('') } } +@[params] +struct HaveWantParams { + nr_params int + nr_args int + args []ast.CallArg + params []ast.Param + pos token.Pos +} + +fn (mut c Checker) fn_call_error_have_want(p HaveWantParams) { + mut have_want := '\n\thave (' + // Fetch arg types, they are always 0 at this point + // Duplicate logic, but we don't care, since this is an error, so no perf cost + mut arg_types := []ast.Type{len: p.args.len} + for i, arg in p.args { + mut e := arg.expr + arg_types[i] = c.expr(mut e) + } + // Args provided by the user + for i, _ in p.args { + if arg_types[i] == 0 { // arg.typ == 0 { + // Arguments can have an unknown (invalid) type + // This should never happen. + have_want += '?' + } else { + have_want += c.table.type_to_str(arg_types[i]) // arg.typ) + } + if i < p.args.len - 1 { + have_want += ', ' + } + } + // Actual parameters we expect + have_want += ')\n\twant (' + for i, param in p.params { + if i == 0 && p.nr_params == p.params.len - 1 { + // Skip receiver + continue + } + have_want += c.table.type_to_str(param.typ) + if i < p.params.len - 1 { + have_want += ', ' + } + } + have_want += ')\n' + args_plural := if p.nr_params == 1 { 'argument' } else { 'arguments' } + c.error('expected ${p.nr_params} ${args_plural}, but got ${p.nr_args}${have_want}', + p.pos) +} + fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ ast.Type, node ast.CallExpr) { if node.args.len != 1 { c.error('expected 1 argument, but got ${node.args.len}', node.pos) diff --git a/vlib/v/checker/tests/ambiguous_function_call.out b/vlib/v/checker/tests/ambiguous_function_call.out index cd4e22ee55..0eaaaec5a1 100644 --- a/vlib/v/checker/tests/ambiguous_function_call.out +++ b/vlib/v/checker/tests/ambiguous_function_call.out @@ -12,6 +12,9 @@ vlib/v/checker/tests/ambiguous_function_call.vv:7:2: error: ambiguous call to: ` 8 | } 9 | vlib/v/checker/tests/ambiguous_function_call.vv:7:7: error: expected 0 arguments, but got 1 + have (int) + want () + 5 | fn foo2() { 6 | foo2 := 1 7 | foo2(foo2) diff --git a/vlib/v/checker/tests/c_fn_surplus_args.out b/vlib/v/checker/tests/c_fn_surplus_args.out index a462c8470d..92c794d533 100644 --- a/vlib/v/checker/tests/c_fn_surplus_args.out +++ b/vlib/v/checker/tests/c_fn_surplus_args.out @@ -1,18 +1,27 @@ vlib/v/checker/tests/c_fn_surplus_args.vv:6:7: error: expected 0 arguments, but got 1 + have (int literal) + want () + 4 | 5 | fn main() { 6 | C.no(1) // allowed | ^ 7 | C.y1() 8 | C.y1(1) // ok -vlib/v/checker/tests/c_fn_surplus_args.vv:7:4: error: expected 1 arguments, but got 0 +vlib/v/checker/tests/c_fn_surplus_args.vv:7:4: error: expected 1 argument, but got 0 + have () + want (int) + 5 | fn main() { 6 | C.no(1) // allowed 7 | C.y1() | ~~~~ 8 | C.y1(1) // ok 9 | C.y1(1, 2) -vlib/v/checker/tests/c_fn_surplus_args.vv:9:10: error: expected 1 arguments, but got 2 +vlib/v/checker/tests/c_fn_surplus_args.vv:9:10: error: expected 1 argument, but got 2 + have (int literal, int literal) + want (int) + 7 | C.y1() 8 | C.y1(1) // ok 9 | C.y1(1, 2) @@ -20,6 +29,9 @@ vlib/v/checker/tests/c_fn_surplus_args.vv:9:10: error: expected 1 arguments, but 10 | C.ret() // ok 11 | C.ret(1) vlib/v/checker/tests/c_fn_surplus_args.vv:11:8: error: expected 0 arguments, but got 1 + have (int literal) + want () + 9 | C.y1(1, 2) 10 | C.ret() // ok 11 | C.ret(1) @@ -34,6 +46,9 @@ vlib/v/checker/tests/c_fn_surplus_args.vv:13:2: error: the `main` function canno 14 | C.af() // ok 15 | C.af(3) vlib/v/checker/tests/c_fn_surplus_args.vv:15:7: error: expected 0 arguments, but got 1 + have (int literal) + want () + 13 | main() 14 | C.af() // ok 15 | C.af(3) diff --git a/vlib/v/checker/tests/error_fn_with_0_args.out b/vlib/v/checker/tests/error_fn_with_0_args.out index df6b2375d8..9b38a840db 100644 --- a/vlib/v/checker/tests/error_fn_with_0_args.out +++ b/vlib/v/checker/tests/error_fn_with_0_args.out @@ -1,4 +1,7 @@ -vlib/v/checker/tests/error_fn_with_0_args.vv:2:9: error: expected 1 arguments, but got 0 +vlib/v/checker/tests/error_fn_with_0_args.vv:2:9: error: expected 1 argument, but got 0 + have () + want (string) + 1 | fn abc() ! { 2 | return error() | ~~~~~~~ diff --git a/vlib/v/checker/tests/fn_array_decompose_arg_mismatch_err_c.out b/vlib/v/checker/tests/fn_array_decompose_arg_mismatch_err_c.out index 80a9d20011..abe4846d78 100644 --- a/vlib/v/checker/tests/fn_array_decompose_arg_mismatch_err_c.out +++ b/vlib/v/checker/tests/fn_array_decompose_arg_mismatch_err_c.out @@ -1,4 +1,19 @@ +vlib/v/checker/tests/fn_array_decompose_arg_mismatch_err_c.vv:2:9: error: undefined ident: `args` + 1 | fn main() { + 2 | foo(...args) + | ~~~~ + 3 | } + 4 | +vlib/v/checker/tests/fn_array_decompose_arg_mismatch_err_c.vv:2:9: error: decomposition can only be used on arrays + 1 | fn main() { + 2 | foo(...args) + | ~~~~ + 3 | } + 4 | vlib/v/checker/tests/fn_array_decompose_arg_mismatch_err_c.vv:2:6: error: expected 0 arguments, but got 1 + have (void) + want () + 1 | fn main() { 2 | foo(...args) | ~~~~~~~ diff --git a/vlib/v/checker/tests/fn_call_with_extra_parenthesis.out b/vlib/v/checker/tests/fn_call_with_extra_parenthesis.out index bbdb3de313..ebf0be2aa0 100644 --- a/vlib/v/checker/tests/fn_call_with_extra_parenthesis.out +++ b/vlib/v/checker/tests/fn_call_with_extra_parenthesis.out @@ -1,4 +1,7 @@ -vlib/v/checker/tests/fn_call_with_extra_parenthesis.vv:5:2: error: expected 1 arguments, but got 0 +vlib/v/checker/tests/fn_call_with_extra_parenthesis.vv:5:2: error: expected 1 argument, but got 0 + have () + want (int) + 3 | 4 | fn main() { 5 | doit()(1) diff --git a/vlib/v/checker/tests/function_count_of_args_mismatch_err.out b/vlib/v/checker/tests/function_count_of_args_mismatch_err.out index d9ad12eea0..e3cf907f54 100644 --- a/vlib/v/checker/tests/function_count_of_args_mismatch_err.out +++ b/vlib/v/checker/tests/function_count_of_args_mismatch_err.out @@ -1,11 +1,17 @@ -vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:8:13: error: expected 1 arguments, but got 3 +vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:8:13: error: expected 1 argument, but got 3 + have (bool, bool, int literal) + want (bool) + 6 | 7 | fn main() { 8 | test(true, false, 1) | ~~~~~~~~ 9 | test() 10 | test2(true, false, 1) -vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:9:2: error: expected 1 arguments, but got 0 +vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:9:2: error: expected 1 argument, but got 0 + have () + want (bool) + 7 | fn main() { 8 | test(true, false, 1) 9 | test() @@ -13,6 +19,9 @@ vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:9:2: error: expected 10 | test2(true, false, 1) 11 | test2(true) vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:10:21: error: expected 2 arguments, but got 3 + have (bool, bool, int literal) + want (bool, T) + 8 | test(true, false, 1) 9 | test() 10 | test2(true, false, 1) @@ -20,6 +29,9 @@ vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:10:21: error: expect 11 | test2(true) 12 | } vlib/v/checker/tests/function_count_of_args_mismatch_err.vv:11:2: error: expected 2 arguments, but got 1 + have (bool) + want (bool, T) + 9 | test() 10 | test2(true, false, 1) 11 | test2(true) diff --git a/vlib/v/checker/tests/generics_fn_arguments_count_err.out b/vlib/v/checker/tests/generics_fn_arguments_count_err.out index 32975359d0..d264284be5 100644 --- a/vlib/v/checker/tests/generics_fn_arguments_count_err.out +++ b/vlib/v/checker/tests/generics_fn_arguments_count_err.out @@ -13,6 +13,9 @@ vlib/v/checker/tests/generics_fn_arguments_count_err.vv:15:18: error: expected 2 16 | println(ret2) 17 | vlib/v/checker/tests/generics_fn_arguments_count_err.vv:15:45: error: expected 2 arguments, but got 3 + have (int literal, int literal, string) + want (A, B) + 13 | println(ret1) 14 | 15 | ret2 := get_name[int, int, string](11, 22, 'hello') @@ -34,6 +37,9 @@ vlib/v/checker/tests/generics_fn_arguments_count_err.vv:22:22: error: expected 2 23 | println(ret4) 24 | } vlib/v/checker/tests/generics_fn_arguments_count_err.vv:22:49: error: expected 2 arguments, but got 3 + have (int literal, int literal, string) + want (A, B) + 20 | println(ret3) 21 | 22 | ret4 := foo.get_name[int, int, string](11, 22, 'hello') diff --git a/vlib/v/checker/tests/generics_struct_field_fn_args_err.out b/vlib/v/checker/tests/generics_struct_field_fn_args_err.out index d5e31a54a7..12a1faff79 100644 --- a/vlib/v/checker/tests/generics_struct_field_fn_args_err.out +++ b/vlib/v/checker/tests/generics_struct_field_fn_args_err.out @@ -1,4 +1,7 @@ vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:21:20: error: expected 0 arguments, but got 1 + have (int literal) + want () + 19 | } 20 | println(fun0.call()) 21 | println(fun0.call(1234)) @@ -6,35 +9,44 @@ vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:21:20: error: expected 22 | println(fun0.call(1234, 5678)) 23 | vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:22:20: error: expected 0 arguments, but got 2 + have (int literal, int literal) + want () + 20 | println(fun0.call()) 21 | println(fun0.call(1234)) 22 | println(fun0.call(1234, 5678)) | ~~~~~~~~~~ - 23 | + 23 | 24 | fun1 := Fun[fn (int) int]{ -vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:29:15: error: expected 1 arguments, but got 0 - 27 | +vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:29:15: error: expected 1 argument, but got 0 + have () + want (int) + + 27 | 28 | println(fun1.call(42)) 29 | println(fun1.call()) | ~~~~~~ 30 | println(fun1.call(42, 43)) 31 | -vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:30:24: error: expected 1 arguments, but got 2 +vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:30:24: error: expected 1 argument, but got 2 + have (int literal, int literal) + want (int) + 28 | println(fun1.call(42)) 29 | println(fun1.call()) 30 | println(fun1.call(42, 43)) | ~~ - 31 | + 31 | 32 | println(fun1.call(true)) vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:32:20: error: cannot use `bool` as `int` in argument 1 to `Fun[fn (int) int].call` 30 | println(fun1.call(42, 43)) - 31 | + 31 | 32 | println(fun1.call(true)) | ~~~~ 33 | println(fun1.call('text')) 34 | println(fun1.call(22.2)) vlib/v/checker/tests/generics_struct_field_fn_args_err.vv:33:20: error: cannot use `string` as `int` in argument 1 to `Fun[fn (int) int].call` - 31 | + 31 | 32 | println(fun1.call(true)) 33 | println(fun1.call('text')) | ~~~~~~ diff --git a/vlib/v/checker/tests/multi_return_err.out b/vlib/v/checker/tests/multi_return_err.out index 28604d08bb..e6e94dcf7b 100644 --- a/vlib/v/checker/tests/multi_return_err.out +++ b/vlib/v/checker/tests/multi_return_err.out @@ -6,6 +6,9 @@ vlib/v/checker/tests/multi_return_err.vv:18:10: error: cannot use `f64` as `int` 19 | my_func3(my_func2(), 'foo') 20 | my_func4('foo', my_func2()) vlib/v/checker/tests/multi_return_err.vv:19:2: error: expected 3 arguments, but got 2 + have ((int, f64), string) + want (int, int, string) + 17 | fn main() { 18 | my_func(my_func2()) 19 | my_func3(my_func2(), 'foo') @@ -13,6 +16,9 @@ vlib/v/checker/tests/multi_return_err.vv:19:2: error: expected 3 arguments, but 20 | my_func4('foo', my_func2()) 21 | my_func(my_func5()) vlib/v/checker/tests/multi_return_err.vv:20:2: error: expected 3 arguments, but got 2 + have (string, (int, f64)) + want (string, int, int) + 18 | my_func(my_func2()) 19 | my_func3(my_func2(), 'foo') 20 | my_func4('foo', my_func2()) @@ -20,6 +26,9 @@ vlib/v/checker/tests/multi_return_err.vv:20:2: error: expected 3 arguments, but 21 | my_func(my_func5()) 22 | my_func(my_func6()) vlib/v/checker/tests/multi_return_err.vv:21:2: error: expected 2 arguments, but got 1 + have (void) + want (int, int) + 19 | my_func3(my_func2(), 'foo') 20 | my_func4('foo', my_func2()) 21 | my_func(my_func5()) diff --git a/vlib/v/checker/tests/no_interface_instantiation_b.out b/vlib/v/checker/tests/no_interface_instantiation_b.out index f21a4e61b1..da89dbbedd 100644 --- a/vlib/v/checker/tests/no_interface_instantiation_b.out +++ b/vlib/v/checker/tests/no_interface_instantiation_b.out @@ -1,4 +1,7 @@ -vlib/v/checker/tests/no_interface_instantiation_b.vv:6:2: error: expected 1 arguments, but got 0 +vlib/v/checker/tests/no_interface_instantiation_b.vv:6:2: error: expected 1 argument, but got 0 + have () + want (Speaker) + 4 | 5 | fn main() { 6 | my_fn() diff --git a/vlib/v/checker/tests/no_main_println_err.out b/vlib/v/checker/tests/no_main_println_err.out index 7dc5a00e36..b1df0c28f1 100644 --- a/vlib/v/checker/tests/no_main_println_err.out +++ b/vlib/v/checker/tests/no_main_println_err.out @@ -1,3 +1,6 @@ -vlib/v/checker/tests/no_main_println_err.vv:1:1: error: expected 1 arguments, but got 0 +vlib/v/checker/tests/no_main_println_err.vv:1:1: error: expected 1 argument, but got 0 + have () + want (string) + 1 | println() | ~~~~~~~~~ diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 9e934e6b3d..9a437ad5b4 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -243,6 +243,8 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { // last argument; try to expand if it's []string idx := i - node.args.len last_arg := g.expr_string(node.args.last().expr) + // t := g.table.sym(m.params[i].typ) + // g.write('/*nr_args=${node.args.len} m.params.len=${m.params.len} i=${i} t=${t.name} ${m.params}*/') if m.params[i].typ.is_int() || m.params[i].typ.idx() == ast.bool_type_idx { // Gets the type name and cast the string to the type with the string_ function type_name := g.table.type_symbols[int(m.params[i].typ)].str()