From 5e36217f815db4fcfc1c49f50d8420f2fbdcbd3f Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 5 Jun 2026 17:09:00 +0300 Subject: [PATCH] checker: fix return if fn multi-return (#27347) --- vlib/v/checker/checker.v | 84 +++++++++++++++++++ vlib/v/checker/if.v | 3 +- vlib/v/checker/match.v | 13 ++- ...i_return_option_result_return_expr_err.out | 14 ++++ ...ti_return_option_result_return_expr_err.vv | 18 ++++ ..._return_pointer_number_return_expr_err.out | 13 +++ ...i_return_pointer_number_return_expr_err.vv | 10 +++ .../tests/multi_return_use_void_type_err.out | 13 +++ .../tests/multi_return_use_void_type_err.vv | 4 + vlib/v/tests/fns/fn_multiple_returns_test.v | 45 ++++++++++ 10 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 vlib/v/checker/tests/multi_return_option_result_return_expr_err.out create mode 100644 vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv create mode 100644 vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.out create mode 100644 vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f758f2f3e..c1d7ddcc8 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -6900,6 +6900,23 @@ fn (mut c Checker) concat_expr(mut node ast.ConcatExpr) ast.Type { return ast.void_type } } + expected_multi_return_type := c.expected_type.clear_option_and_result() + expected_sym := c.table.sym(expected_multi_return_type) + if c.inside_return && expected_sym.info is ast.MultiReturn + && expected_sym.info.types.len == mr_types.len { + mut use_expected_type := true + for i, expected_typ in expected_sym.info.types { + if !c.can_use_expected_multi_return_value_type(mr_types[i], expected_typ, + node.vals[i]) { + use_expected_type = false + break + } + } + if use_expected_type { + node.return_type = expected_multi_return_type + return expected_multi_return_type + } + } typ := c.table.find_or_register_multi_return(mr_types) ast.new_type(typ) node.return_type = typ @@ -6907,6 +6924,73 @@ fn (mut c Checker) concat_expr(mut node ast.ConcatExpr) ast.Type { } } +fn (mut c Checker) can_use_expected_multi_return_expr_type(got_type ast.Type, expected_type ast.Type, expr ast.Expr) bool { + got_sym := c.table.sym(got_type) + expected_sym := c.table.sym(expected_type) + if got_sym.info is ast.MultiReturn && expected_sym.info is ast.MultiReturn { + got_types := got_sym.info.types.map(ast.mktyp(it)) + expected_types := expected_sym.info.types + if got_types.len != expected_types.len { + return false + } + for i, got_value_type in got_types { + expected_value_type := expected_types[i] + value_expr := if expr is ast.ConcatExpr && i < expr.vals.len { + expr.vals[i] + } else { + expr + } + if !c.can_use_expected_multi_return_value_type(got_value_type, expected_value_type, + value_expr) { + return false + } + } + return true + } + return false +} + +fn (mut c Checker) can_use_expected_multi_return_value_type(got_type ast.Type, expected_type ast.Type, expr ast.Expr) bool { + if got_type.has_flag(.option) { + if !expected_type.has_flag(.option) + || !c.check_types(got_type.clear_flag(.option), expected_type.clear_flag(.option)) { + return false + } + } + if got_type.has_flag(.result) { + payload_compat := if returns_call_like_result_expr(expr) { + c.table.are_payloads_alias_compatible(got_type.clear_flag(.result), + expected_type.clear_flag(.result)) + } else { + got_type.clear_flag(.result) == expected_type.clear_flag(.result) + } + if !expected_type.has_flag(.result) || !payload_compat { + return false + } + } + if !c.check_types(got_type, expected_type) { + return false + } + if got_type.is_any_kind_of_pointer() && !expected_type.is_any_kind_of_pointer() + && !c.table.unaliased_type(expected_type).is_any_kind_of_pointer() { + return expr.is_auto_deref_var() + } + if expected_type.is_any_kind_of_pointer() && !got_type.is_any_kind_of_pointer() + && !c.table.unaliased_type(got_type).is_any_kind_of_pointer() + && got_type != ast.int_literal_type && !c.pref.translated && !c.file.is_translated { + if expr.is_auto_deref_var() { + return true + } + if c.table.final_sym(expected_type).kind == .interface + && c.table.final_sym(got_type).kind == .interface + && expected_type.deref().idx() == got_type.idx() { + return true + } + return false + } + return true +} + fn smartcast_index_expr_scope_key(expr ast.IndexExpr) string { return '__smartcast_index_expr__${ast.Expr(expr).str()}' } diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 06e2bbf0c..204428bc7 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -427,7 +427,8 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { stmt.typ = c.expr(mut stmt.expr) if c.table.type_kind(c.expected_type) == .multi_return && c.table.type_kind(stmt.typ) == .multi_return { - if node.typ == ast.void_type { + if node.typ == ast.void_type + && c.can_use_expected_multi_return_expr_type(stmt.typ, c.expected_type, stmt.expr) { node.is_expr = true node.typ = c.expected_type } diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index 2c100d4ff..192d776c2 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -337,7 +337,14 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { if unwrapped_expected_type.has_option_or_result() || c.table.type_kind(unwrapped_expected_type) in [.sum_type, .interface, .multi_return] { c.check_match_branch_last_stmt(mut stmt, unwrapped_expected_type, expr_type) - ret_type = node.expected_type + if c.table.type_kind(unwrapped_expected_type) == .multi_return + && c.table.type_kind(expr_type) == .multi_return + && !c.can_use_expected_multi_return_expr_type(expr_type, unwrapped_expected_type, stmt.expr) { + ret_type = expr_type + c.expected_expr_type = expr_type + } else { + ret_type = node.expected_type + } } else { ret_type = expr_type if expr_type.is_ptr() { @@ -572,9 +579,7 @@ fn (mut c Checker) check_match_branch_last_stmt(mut last_stmt ast.ExprStmt, ret_ if !(ret_sym.kind == .sum_type && (ret_type.has_flag(.generic) || c.table.is_sumtype_or_in_variant(ret_type, expr_type))) && !is_noreturn { if expr_sym.kind == .multi_return && ret_sym.kind == .multi_return { - ret_types := ret_sym.mr_info().types - expr_types := expr_sym.mr_info().types.map(ast.mktyp(it)) - if expr_types == ret_types { + if c.can_use_expected_multi_return_expr_type(expr_type, ret_type, last_stmt.expr) { return } } diff --git a/vlib/v/checker/tests/multi_return_option_result_return_expr_err.out b/vlib/v/checker/tests/multi_return_option_result_return_expr_err.out new file mode 100644 index 000000000..b5b001a80 --- /dev/null +++ b/vlib/v/checker/tests/multi_return_option_result_return_expr_err.out @@ -0,0 +1,14 @@ +vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv:10:9: error: cannot use `?int` as type `int` in return argument + 8 | + 9 | fn return_if_with_option_value(x bool) (int, int) { + 10 | return if x { maybe_int(), 1 } else { 2, 2 } + | ~~ + 11 | } + 12 | +vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv:14:9: error: cannot use `!int` as type `int` in return argument + 12 | + 13 | fn return_match_with_result_value(x bool) (int, int) { + 14 | return match x { + | ~~~~~~~~~ + 15 | true { result_int(), 1 } + 16 | else { 2, 2 } diff --git a/vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv b/vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv new file mode 100644 index 000000000..6137709d4 --- /dev/null +++ b/vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv @@ -0,0 +1,18 @@ +fn maybe_int() ?int { + return 1 +} + +fn result_int() !int { + return 1 +} + +fn return_if_with_option_value(x bool) (int, int) { + return if x { maybe_int(), 1 } else { 2, 2 } +} + +fn return_match_with_result_value(x bool) (int, int) { + return match x { + true { result_int(), 1 } + else { 2, 2 } + } +} diff --git a/vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.out b/vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.out new file mode 100644 index 000000000..dd69cc1ec --- /dev/null +++ b/vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.vv:2:9: error: fn `return_if_with_number_to_pointer` expects you to return a reference type `voidptr`, but you are returning `u64` instead + 1 | fn return_if_with_number_to_pointer(x bool, n u64) (voidptr, int) { + 2 | return if x { n, 1 } else { (unsafe { voidptr(1) }), 2 } + | ~~ + 3 | } + 4 | +vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.vv:6:9: error: fn `return_match_with_number_to_pointer` expects you to return a reference type `voidptr`, but you are returning `u64` instead + 4 | + 5 | fn return_match_with_number_to_pointer(x bool, n u64) (voidptr, int) { + 6 | return match x { + | ~~~~~~~~~ + 7 | true { n, 1 } + 8 | else { (unsafe { voidptr(1) }), 2 } diff --git a/vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.vv b/vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.vv new file mode 100644 index 000000000..b57a211d6 --- /dev/null +++ b/vlib/v/checker/tests/multi_return_pointer_number_return_expr_err.vv @@ -0,0 +1,10 @@ +fn return_if_with_number_to_pointer(x bool, n u64) (voidptr, int) { + return if x { n, 1 } else { (unsafe { voidptr(1) }), 2 } +} + +fn return_match_with_number_to_pointer(x bool, n u64) (voidptr, int) { + return match x { + true { n, 1 } + else { (unsafe { voidptr(1) }), 2 } + } +} diff --git a/vlib/v/checker/tests/multi_return_use_void_type_err.out b/vlib/v/checker/tests/multi_return_use_void_type_err.out index 2066f5ec3..4e95cccfd 100644 --- a/vlib/v/checker/tests/multi_return_use_void_type_err.out +++ b/vlib/v/checker/tests/multi_return_use_void_type_err.out @@ -3,3 +3,16 @@ vlib/v/checker/tests/multi_return_use_void_type_err.vv:2:2: error: type `void` c 2 | print('a'), print('b') | ~~~~~~~~~~ 3 | } + 4 | +vlib/v/checker/tests/multi_return_use_void_type_err.vv:6:16: error: type `void` cannot be used in multi-return + 4 | + 5 | fn return_if_with_void_value(x bool) (voidptr, int) { + 6 | return if x { print('a'), 1 } else { voidptr(0), 2 } + | ~~~~~~~~~~ + 7 | } +vlib/v/checker/tests/multi_return_use_void_type_err.vv:6:16: error: the final expression in `if` or `match`, must have a value of a non-void type + 4 | + 5 | fn return_if_with_void_value(x bool) (voidptr, int) { + 6 | return if x { print('a'), 1 } else { voidptr(0), 2 } + | ~~~~~ + 7 | } diff --git a/vlib/v/checker/tests/multi_return_use_void_type_err.vv b/vlib/v/checker/tests/multi_return_use_void_type_err.vv index b4343e024..7cbf150c5 100644 --- a/vlib/v/checker/tests/multi_return_use_void_type_err.vv +++ b/vlib/v/checker/tests/multi_return_use_void_type_err.vv @@ -1,3 +1,7 @@ fn main() { print('a'), print('b') } + +fn return_if_with_void_value(x bool) (voidptr, int) { + return if x { print('a'), 1 } else { voidptr(0), 2 } +} diff --git a/vlib/v/tests/fns/fn_multiple_returns_test.v b/vlib/v/tests/fns/fn_multiple_returns_test.v index 224b0b792..8dc2a5242 100644 --- a/vlib/v/tests/fns/fn_multiple_returns_test.v +++ b/vlib/v/tests/fns/fn_multiple_returns_test.v @@ -94,11 +94,48 @@ fn if_expr(x bool) (int, int) { return if x { 3, 3 } else { 2, 2 } } +fn noop_cmd_for_multi_return_if() int { + return 0 +} + +fn if_expr_with_fn_value(x bool) (int, fn () int) { + return if x { 1, noop_cmd_for_multi_return_if } else { 2, noop_cmd_for_multi_return_if } +} + +fn fail_for_multi_return_if_or_block() !int { + return error('failed') +} + +fn if_expr_with_fn_value_in_or_block_return(x bool) (int, fn () int) { + _ := fail_for_multi_return_if_or_block() or { + return if x { 3, noop_cmd_for_multi_return_if } else { 4, noop_cmd_for_multi_return_if } + } + return 0, noop_cmd_for_multi_return_if +} + +fn result_if_expr_with_fn_value(x bool) !(int, fn () int) { + return if x { 5, noop_cmd_for_multi_return_if } else { 6, noop_cmd_for_multi_return_if } +} + +fn option_if_expr_with_fn_value(x bool) ?(int, fn () int) { + return if x { 7, noop_cmd_for_multi_return_if } else { 8, noop_cmd_for_multi_return_if } +} + fn test_multi_return_if_match_expr() { a, b := match_expr(true) c, d := match_expr(false) x, y := if_expr(true) z, w := if_expr(false) + n, f := if_expr_with_fn_value(true) + on, of := if_expr_with_fn_value_in_or_block_return(false) + rn, rf := result_if_expr_with_fn_value(true) or { + assert false + return + } + pn, pf := option_if_expr_with_fn_value(false) or { + assert false + return + } assert a == 1 assert b == 1 assert c == 0 @@ -107,4 +144,12 @@ fn test_multi_return_if_match_expr() { assert y == 3 assert z == 2 assert w == 2 + assert n == 1 + assert f() == 0 + assert on == 4 + assert of() == 0 + assert rn == 5 + assert rf() == 0 + assert pn == 8 + assert pf() == 0 } -- 2.39.5