From ed3acac302b8a2242f2fe830c4f2f615fd895270 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Fri, 9 Aug 2024 14:47:57 -0300 Subject: [PATCH] all: allow multi return as fn argument (#21991) --- vlib/v/ast/ast.v | 3 +- vlib/v/checker/fn.v | 83 ++++++++++++++++++++--- vlib/v/checker/tests/multi_return_err.out | 34 ++++++++++ vlib/v/checker/tests/multi_return_err.vv | 19 ++++++ vlib/v/gen/c/fn.v | 19 ++++++ vlib/v/tests/multi_return_test.v | 9 +++ 6 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 vlib/v/checker/tests/multi_return_err.out create mode 100644 vlib/v/checker/tests/multi_return_err.vv create mode 100644 vlib/v/tests/multi_return_test.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 610452e89911ef..3432fb393dff52 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -778,7 +778,8 @@ pub mut: receiver_type Type // User / T, if receiver is generic, then cgen requires receiver_type to be T receiver_concrete_type Type // if receiver_type is T, then receiver_concrete_type is concrete type, otherwise it is the same as receiver_type return_type Type - return_type_generic Type // the original generic return type from fn def + return_type_generic Type // the original generic return type from fn def + nr_ret_values int = -1 // amount of return values fn_var_type Type // the fn type, when `is_fn_a_const` or `is_fn_var` is true const_name string // the fully qualified name of the const, i.e. `main.c`, given `const c = abc`, and callexpr: `c()` should_be_skipped bool // true for calls to `[if someflag?]` functions, when there is no `-d someflag` diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 352eef48ee53a4..a7a4beb23a1e26 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -4,6 +4,8 @@ import v.ast import v.util import v.token +const print_everything_fns = ['println', 'print', 'eprintln', 'eprint', 'panic'] + fn (mut c Checker) fn_decl(mut node ast.FnDecl) { $if trace_post_process_generic_fns_types ? { if node.generic_names.len > 0 { @@ -606,6 +608,18 @@ fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { node.free_receiver = true } } + if node.nr_ret_values == -1 && node.return_type != 0 { + if node.return_type == ast.void_type { + node.nr_ret_values = 0 + } else { + ret_sym := c.table.sym(node.return_type) + if ret_sym.info is ast.MultiReturn { + node.nr_ret_values = ret_sym.info.types.len + } else { + node.nr_ret_values = 1 + } + } + } c.expected_or_type = node.return_type.clear_flag(.result) c.stmts_ending_with_expression(mut node.or_block.stmts, c.expected_or_type) c.expected_or_type = ast.void_type @@ -632,7 +646,9 @@ fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { fn (mut c Checker) builtin_args(mut node ast.CallExpr, fn_name string, func ast.Fn) { c.inside_interface_deref = true c.expected_type = ast.string_type - node.args[0].typ = c.expr(mut node.args[0].expr) + if !(node.language != .js && node.args[0].expr is ast.CallExpr) { + node.args[0].typ = c.expr(mut node.args[0].expr) + } arg := node.args[0] c.check_expr_option_or_result_call(arg.expr, arg.typ) if arg.typ.is_void() { @@ -1005,7 +1021,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } } if is_native_builtin { - if node.args.len > 0 && fn_name in ['println', 'print', 'eprintln', 'eprint', 'panic'] { + if node.args.len > 0 && fn_name in checker.print_everything_fns { c.builtin_args(mut node, fn_name, func) return func.return_type } @@ -1160,12 +1176,18 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. && func.ctdefine_idx != ast.invalid_type_idx { node.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut func.attrs[func.ctdefine_idx]) } + // dont check number of args for JS functions since arguments are not required if node.language != .js { + for i, mut call_arg in node.args { + if call_arg.expr is ast.CallExpr { + node.args[i].typ = c.expr(mut call_arg.expr) + } + } c.check_expected_arg_count(mut node, func) or { return func.return_type } } // println / eprintln / panic can print anything - if node.args.len > 0 && fn_name in ['println', 'print', 'eprintln', 'eprint', 'panic'] { + if node.args.len > 0 && fn_name in checker.print_everything_fns { c.builtin_args(mut node, fn_name, func) return func.return_type } @@ -1203,6 +1225,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. } } has_decompose = call_arg.expr is ast.ArrayDecompose + already_checked := node.language != .js && call_arg.expr is ast.CallExpr if func.is_variadic && i >= func.params.len - 1 { param_sym := c.table.sym(param.typ) mut expected_type := param.typ @@ -1210,7 +1233,11 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. expected_type = param_sym.info.elem_type c.expected_type = expected_type } - typ := c.expr(mut call_arg.expr) + typ := if already_checked && mut call_arg.expr is ast.CallExpr { + call_arg.expr.return_type + } else { + c.expr(mut call_arg.expr) + } if i == node.args.len - 1 { if c.table.sym(typ).kind == .array && call_arg.expr !is ast.ArrayDecompose && c.table.sym(expected_type).kind !in [.sum_type, .interface_] @@ -1240,8 +1267,11 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. c.error('cannot initialize a map with a struct', call_arg.pos) continue } - - mut arg_typ := c.check_expr_option_or_result_call(call_arg.expr, c.expr(mut call_arg.expr)) + mut arg_typ := c.check_expr_option_or_result_call(call_arg.expr, if already_checked { + node.args[i].typ + } else { + c.expr(mut call_arg.expr) + }) if call_arg.expr is ast.StructInit { arg_typ = c.expr(mut call_arg.expr) } @@ -1365,6 +1395,23 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. && param_elem_type == arg_elem_type { continue } + } else if arg_typ_sym.info is ast.MultiReturn { + arg_typs := arg_typ_sym.info.types + out: for n in 0 .. arg_typ_sym.info.types.len { + curr_arg := arg_typs[n] + multi_param := if func.is_variadic && i >= func.params.len - 1 { + func.params.last() + } else { + func.params[n + i] + } + c.check_expected_call_arg(curr_arg, c.unwrap_generic(multi_param.typ), + node.language, call_arg) or { + c.error('${err.msg()} in argument ${i + n + 1} to `${fn_name}` from ${c.table.type_to_str(arg_typ)}', + call_arg.pos) + continue out + } + } + continue } if c.pref.translated || c.file.is_translated { // in case of variadic make sure to use array elem type for checks @@ -1480,7 +1527,13 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. func.params[i] } c.expected_type = param.typ - typ := c.check_expr_option_or_result_call(call_arg.expr, c.expr(mut call_arg.expr)) + already_checked := node.language != .js && call_arg.expr is ast.CallExpr + typ := c.check_expr_option_or_result_call(call_arg.expr, if already_checked + && mut call_arg.expr is ast.CallExpr { + call_arg.expr.return_type + } else { + c.expr(mut call_arg.expr) + }) if param.typ.has_flag(.generic) && func.generic_names.len == node.concrete_types.len { if unwrap_typ := c.table.resolve_generic_to_concrete(param.typ, func.generic_names, @@ -2691,7 +2744,7 @@ fn (mut c Checker) post_process_generic_fns() ! { } fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ! { - nr_args := node.args.len + mut nr_args := node.args.len nr_params := if node.is_method && f.params.len > 0 { f.params.len - 1 } else { f.params.len } mut min_required_params := f.params.len if node.is_method { @@ -2706,6 +2759,20 @@ fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ! min_required_params = nr_args - 1 } } + // check if multi-return is used as unique argument to the function + if node.args.len == 1 && mut node.args[0].expr is ast.CallExpr { + is_multi := node.args[0].expr.nr_ret_values > 1 + if is_multi && node.name !in checker.print_everything_fns { + // it is a multi-return argument + 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) + 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('') + } + } + } if min_required_params < 0 { min_required_params = 0 } diff --git a/vlib/v/checker/tests/multi_return_err.out b/vlib/v/checker/tests/multi_return_err.out new file mode 100644 index 00000000000000..0846519fe8f5ad --- /dev/null +++ b/vlib/v/checker/tests/multi_return_err.out @@ -0,0 +1,34 @@ +vlib/v/checker/tests/multi_return_err.vv:14:10: error: cannot use `f64` as `int` in argument 2 to `my_func` from (int, f64) + 12 | + 13 | fn main() { + 14 | my_func(my_func2()) + | ~~~~~~~~~~ + 15 | my_func3(my_func2(), 'foo') + 16 | my_func4('foo', my_func2()) +vlib/v/checker/tests/multi_return_err.vv:15:2: error: expected 3 arguments, but got 2 + 13 | fn main() { + 14 | my_func(my_func2()) + 15 | my_func3(my_func2(), 'foo') + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 16 | my_func4('foo', my_func2()) + 17 | my_func(my_func5()) +vlib/v/checker/tests/multi_return_err.vv:16:2: error: expected 3 arguments, but got 2 + 14 | my_func(my_func2()) + 15 | my_func3(my_func2(), 'foo') + 16 | my_func4('foo', my_func2()) + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 17 | my_func(my_func5()) + 18 | my_func(my_func6()) +vlib/v/checker/tests/multi_return_err.vv:17:2: error: expected 2 arguments, but got 1 + 15 | my_func3(my_func2(), 'foo') + 16 | my_func4('foo', my_func2()) + 17 | my_func(my_func5()) + | ~~~~~~~~~~~~~~~~~~~ + 18 | my_func(my_func6()) + 19 | } +vlib/v/checker/tests/multi_return_err.vv:18:10: error: expected 2 arguments, but got 3 from multi-return (int, int, int) + 16 | my_func4('foo', my_func2()) + 17 | my_func(my_func5()) + 18 | my_func(my_func6()) + | ~~~~~~~~~~ + 19 | } diff --git a/vlib/v/checker/tests/multi_return_err.vv b/vlib/v/checker/tests/multi_return_err.vv new file mode 100644 index 00000000000000..cf87421326b004 --- /dev/null +++ b/vlib/v/checker/tests/multi_return_err.vv @@ -0,0 +1,19 @@ +fn my_func(a1 int, a2 int) {} + +fn my_func2() (int, f64) { return 0,0} + +fn my_func3(a1 int, a2 int, a3 string) {} + +fn my_func4(a0 string, a1 int, a2 int) {} + +fn my_func5() {} + +fn my_func6() (int, int, int) { return 0,0,0} + +fn main() { + my_func(my_func2()) + my_func3(my_func2(), 'foo') + my_func4('foo', my_func2()) + my_func(my_func5()) + my_func(my_func6()) +} diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 27b08c70c14001..e339e1c15b2537 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -2462,6 +2462,25 @@ fn (mut g Gen) call_args(node ast.CallExpr) { if !exp_option { expected_types[i] = expected_types[i].clear_flag(.option) } + } else if arg.expr is ast.CallExpr { + if arg.expr.nr_ret_values > 1 { + line := g.go_before_last_stmt().trim_space() + g.empty_line = true + ret_type := arg.expr.return_type + tmp_var := g.new_tmp_var() + g.write('${g.typ(ret_type)} ${tmp_var} = ') + g.expr(arg.expr) + g.writeln(';') + g.write(line) + for n in 0 .. arg.expr.nr_ret_values { + if n != arg.expr.nr_ret_values - 1 || i != args.len - 1 { + g.write('${tmp_var}.arg${n}, ') + } else { + g.write('${tmp_var}.arg${n}') + } + } + 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/multi_return_test.v b/vlib/v/tests/multi_return_test.v new file mode 100644 index 00000000000000..8b3eb30debe550 --- /dev/null +++ b/vlib/v/tests/multi_return_test.v @@ -0,0 +1,9 @@ +fn my_func(a1 int, a2 int) {} + +fn my_func2() (int, int) { + return 0, 0 +} + +fn test_main() { + my_func(my_func2()) +}