Skip to content

Commit

Permalink
all: allow multi return as fn argument (#21991)
Browse files Browse the repository at this point in the history
  • Loading branch information
felipensp committed Aug 9, 2024
1 parent 4f718d2 commit ed3acac
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 9 deletions.
3 changes: 2 additions & 1 deletion vlib/v/ast/ast.v
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
83 changes: 75 additions & 8 deletions vlib/v/checker/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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() {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -1203,14 +1225,19 @@ 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
if param_sym.info is ast.Array {
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_]
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down
34 changes: 34 additions & 0 deletions vlib/v/checker/tests/multi_return_err.out
Original file line number Diff line number Diff line change
@@ -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 | }
19 changes: 19 additions & 0 deletions vlib/v/checker/tests/multi_return_err.vv
Original file line number Diff line number Diff line change
@@ -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())
}
19 changes: 19 additions & 0 deletions vlib/v/gen/c/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions vlib/v/tests/multi_return_test.v
Original file line number Diff line number Diff line change
@@ -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())
}

0 comments on commit ed3acac

Please sign in to comment.