From 142f665ddd1b56426ca611b16c8dc7088aa05190 Mon Sep 17 00:00:00 2001 From: GGRei Date: Sun, 10 May 2026 00:35:17 +0200 Subject: [PATCH] cgen: fix generic closure result expression temps (#27070) --- vlib/v/gen/c/assign.v | 6 + vlib/v/gen/c/cgen.v | 5 + vlib/v/gen/c/fn.v | 6 + vlib/v/gen/c/if.v | 3 +- vlib/v/gen/c/struct.v | 9 ++ vlib/v/gen/c/utils.v | 32 +++- .../generics/generics_return_closure_test.v | 138 ++++++++++++++++++ 7 files changed, 194 insertions(+), 5 deletions(-) diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index e4e226fe8..f1d7aa50f 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -574,6 +574,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { g.assign_op = .unknown g.inside_assign = false g.assign_ct_type.clear() + g.expected_rhs_type_by_pos.clear() g.arraymap_set_pos = 0 g.is_arraymap_set = false g.is_assign_lhs = false @@ -731,6 +732,11 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } } mut val := node.right[i] + expected_lhs_type := var_type + if expected_lhs_type != 0 && expected_lhs_type != ast.void_type + && !expected_lhs_type.has_option_or_result() && val in [ast.IfExpr, ast.MatchExpr] { + g.expected_rhs_type_by_pos[val.pos().pos] = expected_lhs_type + } mut str_add_rhs_tmp := '' mut str_add_rhs_needs_free := false mut skip_str_add_rhs_clone := false diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 74011cc36..539a044be 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -160,6 +160,7 @@ mut: inside_match_result bool inside_veb_tmpl bool inside_return bool + inside_return_expr bool inside_return_tmpl bool inside_struct_init bool inside_or_block bool @@ -199,6 +200,7 @@ mut: left_is_opt bool // left hand side on assignment is an option right_is_opt bool // right hand side on assignment is an option assign_ct_type map[int]ast.Type // left hand side resolved comptime type + expected_rhs_type_by_pos map[int]ast.Type // expected value type for local RHS expressions indent int empty_line bool assign_op token.Kind // *=, =, etc (for array_set) @@ -10009,9 +10011,12 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.write_v_source_line_info_stmt(node) old_inside_return := g.inside_return + old_inside_return_expr := g.inside_return_expr g.inside_return = true + g.inside_return_expr = true defer { g.inside_return = old_inside_return + g.inside_return_expr = old_inside_return_expr } exprs_len := node.exprs.len diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index c989b66f4..a8df04000 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1982,7 +1982,13 @@ fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { g.stmt_path_pos = [] g.skip_stmt_pos = false g.anon_fn = node + old_inside_return := g.inside_return + old_inside_return_expr := g.inside_return_expr + g.inside_return = false + g.inside_return_expr = false g.fn_decl(decl) + g.inside_return_expr = old_inside_return_expr + g.inside_return = old_inside_return g.anon_fn = was_anon_fn g.skip_stmt_pos = prev_skip_stmt_pos g.stmt_path_pos = prev_stmt_path_pos diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index 403341c0f..8a35c5de0 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -296,7 +296,8 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { // Try to unwrap generic, and if that doesn't work, check if we should use // the function's return type unwrapped := g.unwrap_generic(node_typ) - if unwrapped == node_typ && g.cur_fn.return_type.has_flag(.generic) { + if g.inside_return_expr && !g.inside_struct_init && unwrapped == node_typ + && g.cur_fn.return_type.has_flag(.generic) { // The node type didn't unwrap, but the function return type is generic // Get the unwrapped function return type for this instance mut fn_ret_typ := g.unwrap_generic(g.cur_fn.return_type) diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index 8f67cd699..c0e3b6ff1 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -408,9 +408,12 @@ fn (mut g Gen) struct_init(node ast.StructInit) { } if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { old_inside_return := g.inside_return + old_inside_return_expr := g.inside_return_expr g.inside_return = false + g.inside_return_expr = false resolved_sfield_typ := g.resolved_expr_type(ast.Expr(sfield.expr), sfield.typ) g.inside_return = old_inside_return + g.inside_return_expr = old_inside_return_expr if resolved_sfield_typ != 0 { sfield.typ = g.unwrap_generic(g.recheck_concrete_type(resolved_sfield_typ)) } @@ -614,10 +617,13 @@ fn (mut g Gen) direct_heap_struct_init(node ast.StructInit, styp string, info as } if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { old_inside_return := g.inside_return + old_inside_return_expr := g.inside_return_expr g.inside_return = false + g.inside_return_expr = false resolved_field_typ := g.resolved_expr_type(ast.Expr(resolved_field.expr), resolved_field.typ) g.inside_return = old_inside_return + g.inside_return_expr = old_inside_return_expr if resolved_field_typ != 0 { resolved_field.typ = g.unwrap_generic(g.recheck_concrete_type(resolved_field_typ)) } @@ -1059,9 +1065,12 @@ fn (mut g Gen) struct_init_ptr_field(target string, sfield ast.StructInitField, fn (mut g Gen) struct_init_field_value(sfield ast.StructInitField) { old_inside_return := g.inside_return + old_inside_return_expr := g.inside_return_expr g.inside_return = false + g.inside_return_expr = false defer { g.inside_return = old_inside_return + g.inside_return_expr = old_inside_return_expr } field_type_sym := g.table.sym(sfield.typ) mut cloned := false diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index bc1bb3d3c..92221f084 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -255,6 +255,22 @@ fn (mut g Gen) infer_branch_expr_type(stmts []ast.Stmt) ast.Type { return g.unwrap_generic(g.recheck_concrete_type(resolved_typ)) } +fn (mut g Gen) expected_rhs_type_for_expr(pos int, node_type ast.Type) ast.Type { + if pos < 0 || node_type == 0 || node_type == ast.void_type || !node_type.has_option_or_result() { + return ast.void_type + } + expected_type := g.expected_rhs_type_by_pos[pos] or { return ast.void_type } + if expected_type == 0 || expected_type == ast.void_type || expected_type.has_option_or_result() { + return ast.void_type + } + resolved_expected_type := g.unwrap_generic(g.recheck_concrete_type(expected_type)) + if resolved_expected_type == 0 || resolved_expected_type == ast.void_type + || resolved_expected_type.has_option_or_result() { + return ast.void_type + } + return resolved_expected_type +} + fn (mut g Gen) infer_if_expr_type(node ast.IfExpr) ast.Type { if g.inside_return && g.inside_struct_init { for branch in node.branches { @@ -265,14 +281,18 @@ fn (mut g Gen) infer_if_expr_type(node ast.IfExpr) ast.Type { } } if node.typ != 0 && node.typ != ast.void_type { + expected_rhs_type := g.expected_rhs_type_for_expr(node.pos.pos, node.typ) + if expected_rhs_type != ast.void_type { + return expected_rhs_type + } resolved := g.unwrap_generic(g.recheck_concrete_type(node.typ)) // In generic functions, node.typ may have been mutated by the checker // to a concrete type from the last processed instantiation. When the - // if-expr is used as a return value (g.inside_return), use the function's + // if-expr is used as a return expression, use the function's // return type instead, which correctly resolves via cur_concrete_types. // Only apply this override when the function's return type is actually // generic — otherwise the if-expression type is concrete and correct. - if g.inside_return && !g.inside_struct_init && g.cur_fn != unsafe { nil } + if g.inside_return_expr && !g.inside_struct_init && g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 && g.cur_fn.return_type.has_flag(.generic) { fn_ret := g.unwrap_generic(g.recheck_concrete_type(g.cur_fn.return_type)) if fn_ret != 0 && fn_ret != ast.void_type { @@ -305,14 +325,18 @@ fn (mut g Gen) infer_match_expr_type(node ast.MatchExpr) ast.Type { } } if node.return_type != 0 && node.return_type != ast.void_type { + expected_rhs_type := g.expected_rhs_type_for_expr(node.pos.pos, node.return_type) + if expected_rhs_type != ast.void_type { + return expected_rhs_type + } resolved := g.unwrap_generic(g.recheck_concrete_type(node.return_type)) // In generic functions, node.return_type may have been mutated by the checker // to a concrete type from the last processed instantiation. When the match is - // used as a return value (g.inside_return), use the function's return type + // used as a return expression, use the function's return type // instead, which correctly resolves via cur_concrete_types. // Only apply this override when the function's return type is actually // generic — otherwise the match expression type is concrete and correct. - if g.inside_return && !g.inside_struct_init && g.cur_fn != unsafe { nil } + if g.inside_return_expr && !g.inside_struct_init && g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 && g.cur_fn.return_type.has_flag(.generic) { fn_ret := g.unwrap_generic(g.recheck_concrete_type(g.cur_fn.return_type)) if fn_ret != 0 && fn_ret != ast.void_type { diff --git a/vlib/v/tests/generics/generics_return_closure_test.v b/vlib/v/tests/generics/generics_return_closure_test.v index 1e6d2e1d7..3d41b54e3 100644 --- a/vlib/v/tests/generics/generics_return_closure_test.v +++ b/vlib/v/tests/generics/generics_return_closure_test.v @@ -27,3 +27,141 @@ fn test_generic_return_generic_closure() { println(vadd2(v2)) assert vadd2(v2) == [2, 3, 4, 5] } + +fn result_closure_u64_source() !u64 { + return u64(0) +} + +fn result_closure_enabled() bool { + return true +} + +fn result_closure_disabled() bool { + return false +} + +fn result_closure_choice() int { + return 0 +} + +struct ResultClosureField[T] { + value u64 + payload T +} + +fn make_result_closure_with_local_if_propagation[T]() fn () !T { + return fn [T]() !T { + x := if result_closure_enabled() { + u64(0) + } else { + result_closure_u64_source()! + } + assert x == u64(0) + return T(0) + } +} + +fn make_result_closure_with_local_if_or_block[T]() fn () !T { + return fn [T]() !T { + x := if result_closure_disabled() { + u64(1) + } else { + result_closure_u64_source() or { u64(1) } + } + assert x == u64(0) + return T(0) + } +} + +fn make_result_closure_with_local_match_propagation[T]() fn () !T { + return fn [T]() !T { + x := match result_closure_choice() { + 0 { u64(0) } + else { result_closure_u64_source()! } + } + + assert x == u64(0) + return T(0) + } +} + +fn make_result_closure_returning_if[T]() fn () !T { + return fn [T]() !T { + return if result_closure_enabled() { + T(0) + } else { + T(1) + } + } +} + +fn make_result_closure_returning_match[T]() fn () !T { + return fn [T]() !T { + return match result_closure_choice() { + 0 { T(0) } + else { T(1) } + } + } +} + +fn make_result_struct_returning_if_field[T](payload T) !ResultClosureField[T] { + return ResultClosureField[T]{ + value: if result_closure_enabled() { + u64(0) + } else { + result_closure_u64_source()! + } + payload: payload + } +} + +fn make_result_struct_returning_if_field_or_block[T](payload T) !ResultClosureField[T] { + return ResultClosureField[T]{ + value: if result_closure_disabled() { + u64(1) + } else { + result_closure_u64_source() or { u64(1) } + } + payload: payload + } +} + +fn test_generic_result_closure_local_if_with_propagation() { + f_int := make_result_closure_with_local_if_propagation[int]() + assert f_int()! == 0 + + f_u64 := make_result_closure_with_local_if_propagation[u64]() + assert f_u64()! == u64(0) +} + +fn test_generic_result_closure_local_if_with_or_block() { + f := make_result_closure_with_local_if_or_block[int]() + assert f()! == 0 +} + +fn test_generic_result_closure_local_match_with_propagation() { + f := make_result_closure_with_local_match_propagation[int]() + assert f()! == 0 +} + +fn test_generic_result_closure_return_if_and_match() { + if_int := make_result_closure_returning_if[int]() + assert if_int()! == 0 + if_u64 := make_result_closure_returning_if[u64]() + assert if_u64()! == u64(0) + + match_int := make_result_closure_returning_match[int]() + assert match_int()! == 0 + match_u64 := make_result_closure_returning_match[u64]() + assert match_u64()! == u64(0) +} + +fn test_generic_result_struct_return_if_field() { + field_int := make_result_struct_returning_if_field[int](1)! + assert field_int.value == u64(0) + assert field_int.payload == 1 + + field_u64 := make_result_struct_returning_if_field_or_block[u64](u64(2))! + assert field_u64.value == u64(0) + assert field_u64.payload == u64(2) +} -- 2.39.5