From 62ec38ab47e4ad3471c9f419eab5f4fbe3ead1f7 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 5 Jun 2026 18:10:35 +0300 Subject: [PATCH] cgen: unwrap option smartcast for interface casts (#27352) --- vlib/v/gen/c/cgen.v | 36 +++++++-- vlib/v/gen/c/fn.v | 20 ++++- .../casts/cast_option_to_interface_test.v | 81 +++++++++++++++++++ .../option_none_guard_return_unwrap_test.v | 68 ++++++++++++++++ 4 files changed, 196 insertions(+), 9 deletions(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 15f47b0d5..084bacfb5 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -174,6 +174,7 @@ mut: inside_for_c_stmt bool inside_cast_in_heap int // inside cast to interface type in heap (resolve recursive calls) inside_cast bool + inside_interface_cast bool inside_sumtype_cast bool inside_selector bool inside_selector_lhs bool @@ -4510,6 +4511,8 @@ fn (mut g Gen) call_cfn_for_casting_expr(fname string, expr ast.Expr, exp ast.Ty } else { old_inside_sumtype_cast := g.inside_sumtype_cast g.inside_sumtype_cast = true + old_inside_interface_cast := g.inside_interface_cast + g.inside_interface_cast = g.inside_interface_cast || is_interface_cast old_left_is_opt := g.left_is_opt g.left_is_opt = !exp.has_flag(.option) old_inside_assign_fn_var := g.inside_assign_fn_var @@ -4525,6 +4528,7 @@ fn (mut g Gen) call_cfn_for_casting_expr(fname string, expr ast.Expr, exp ast.Ty } g.inside_assign_fn_var = old_inside_assign_fn_var g.left_is_opt = old_left_is_opt + g.inside_interface_cast = old_inside_interface_cast g.inside_sumtype_cast = old_inside_sumtype_cast } if is_sumtype_cast { @@ -9105,6 +9109,11 @@ fn (mut g Gen) ident(node ast.Ident) { } } } + ident_option_name := if has_resolved_var && resolved_var.is_inherited { + '${closure_ctx}->${name}' + } else { + name + } if node.info is ast.IdentVar { node_info_is_option = node.info.is_option if node.or_expr.kind == .absent { @@ -9155,7 +9164,7 @@ fn (mut g Gen) ident(node ast.Ident) { if orig_has_option && !comptime_type.has_flag(.option) { styp := g.base_type(comptime_type) ptr := if is_auto_heap { '->' } else { '.' } - g.write('(*(${styp}*)${name}${ptr}data)') + g.write('(*(${styp}*)${ident_option_name}${ptr}data)') } else if comptime_type.has_flag(.option) { if (g.inside_opt_or_res || g.left_is_opt) && node.or_expr.kind == .absent { if !g.is_assign_lhs && is_auto_heap { @@ -9166,7 +9175,7 @@ fn (mut g Gen) ident(node ast.Ident) { } else { styp := g.base_type(comptime_type) ptr := if is_auto_heap { '->' } else { '.' } - g.write('(*(${styp}*)${name}${ptr}data)') + g.write('(*(${styp}*)${ident_option_name}${ptr}data)') } } else { emit_auto_heap_deref := is_auto_heap && !g.inside_assign_fn_var @@ -9222,7 +9231,8 @@ fn (mut g Gen) ident(node ast.Ident) { g.write('*(') } if !has_smartcast && !selector_uses_unwrapped_option_smartcast - && (g.inside_opt_or_res || g.left_is_opt) && node.or_expr.kind == .absent { + && (g.inside_opt_or_res || (g.left_is_opt && !g.inside_interface_cast)) + && node.or_expr.kind == .absent { if !g.is_assign_lhs && is_auto_heap { g.write('(*${name})') } else { @@ -9256,7 +9266,7 @@ fn (mut g Gen) ident(node ast.Ident) { } else { g.get_comptime_for_var_type(node, node.info.typ) } - g.unwrap_option_type(unwrap_typ, name, is_auto_heap) + g.unwrap_option_type(unwrap_typ, ident_option_name, is_auto_heap) } if node.or_expr.kind != .absent && !(g.inside_opt_or_res && g.inside_assign && !g.is_assign_lhs) { @@ -9336,6 +9346,20 @@ fn (mut g Gen) ident(node ast.Ident) { } } is_option = is_option || (has_resolved_var && resolved_var.orig_type.has_flag(.option)) + runtime_option_type := if resolved_var.orig_type.has_flag(.option) { + resolved_var.orig_type + } else if resolved_var.typ.has_flag(.option) { + resolved_var.typ + } else { + ast.no_type + } + if has_resolved_var && runtime_option_type != ast.no_type && !node_info_is_option + && resolved_var.smartcasts.len == 0 && resolved_var.ct_type_var != .smartcast + && !g.inside_opt_or_res && !g.is_assign_lhs && !g.inside_selector_lhs + && !g.right_is_opt && (!g.left_is_opt || g.inside_cast || g.inside_interface_cast) { + g.unwrap_option_type(runtime_option_type, ident_option_name, is_auto_heap) + return + } // When an auto-heap option variable is being smartcast-unwrapped // (e.g. `if svc != none { use(svc) }`), the option unwrap code // at `->data` already handles the pointer indirection. Skip the @@ -9594,13 +9618,13 @@ fn (mut g Gen) ident(node ast.Ident) { if g.inside_selector_lhs && !node_info_is_option && has_resolved_var && !resolved_var.is_unwrapped && resolved_var.smartcasts.len == 0 && resolved_var.typ.has_flag(.option) && !g.is_assign_lhs { - g.unwrap_option_type(resolved_var.typ, name, is_auto_heap) + g.unwrap_option_type(resolved_var.typ, ident_option_name, is_auto_heap) return } if g.inside_selector_lhs && has_resolved_var && resolved_var.is_unwrapped && resolved_var.smartcasts.len == 0 && resolved_var.orig_type.has_flag(.option) && !g.is_assign_lhs { - g.unwrap_option_type(resolved_var.orig_type, name, is_auto_heap) + g.unwrap_option_type(resolved_var.orig_type, ident_option_name, is_auto_heap) return } if has_resolved_var && resolved_var.is_inherited { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index ec92a84c1..a3882267c 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -6408,7 +6408,7 @@ fn (mut g Gen) call_args(node ast.CallExpr) { exp_sym := g.table.sym(expected_types[i]) orig_sym := g.table.sym(arg.expr.obj.orig_type) if !expected_types[i].has_option_or_result() && orig_sym.kind != .interface - && (exp_sym.kind != .sum_type + && (exp_sym.kind !in [.sum_type, .interface] && expected_types[i] != arg.expr.obj.orig_type) { expected_types[i] = g.unwrap_generic(arg.expr.obj.smartcasts.last()) cast_sym := g.table.sym(expected_types[i]) @@ -6904,7 +6904,14 @@ fn (mut g Gen) ref_or_deref_arg_ex(arg ast.CallArg, expected_type_ ast.Type, lan if needs_resolved_ident_type { resolved_arg_typ := g.resolved_expr_type(ast.Expr(arg.expr), arg_typ) if resolved_arg_typ != 0 { - arg_typ = g.unwrap_generic(g.recheck_concrete_type(resolved_arg_typ)) + resolved_arg_type := g.unwrap_generic(g.recheck_concrete_type(resolved_arg_typ)) + skip_inherited_option_storage_type := arg.expr.obj is ast.Var + && arg.expr.obj.is_inherited && !expected_type.has_option_or_result() + && arg_typ != 0 && !arg_typ.has_option_or_result() + && resolved_arg_type.has_option_or_result() + if !skip_inherited_option_storage_type { + arg_typ = resolved_arg_type + } } } if expected_type.has_option_or_result() && !arg_typ.has_option_or_result() { @@ -6933,7 +6940,14 @@ fn (mut g Gen) ref_or_deref_arg_ex(arg ast.CallArg, expected_type_ ast.Type, lan if resolved_arg_typ != 0 && (arg_typ == 0 || g.type_needs_generic_resolution(arg_typ) || in_generic_context || g.unwrap_generic(resolved_arg_typ) != g.unwrap_generic(arg_typ)) { - arg_typ = g.unwrap_generic(g.recheck_concrete_type(resolved_arg_typ)) + resolved_arg_type := g.unwrap_generic(g.recheck_concrete_type(resolved_arg_typ)) + skip_inherited_option_storage_type := arg.expr is ast.Ident && arg.expr.obj is ast.Var + && arg.expr.obj.is_inherited && !expected_type.has_option_or_result() + && arg_typ != 0 && !arg_typ.has_option_or_result() + && resolved_arg_type.has_option_or_result() + if !skip_inherited_option_storage_type { + arg_typ = resolved_arg_type + } } } // Array slice expressions (e.g. b[..8]) always return a value in C (via builtin__array_slice), diff --git a/vlib/v/tests/casts/cast_option_to_interface_test.v b/vlib/v/tests/casts/cast_option_to_interface_test.v index 17c7700f6..de278e5d6 100644 --- a/vlib/v/tests/casts/cast_option_to_interface_test.v +++ b/vlib/v/tests/casts/cast_option_to_interface_test.v @@ -38,3 +38,84 @@ fn test_cast_option_to_interface() { assert e.parser.main.str == 'test' eprintln(voidptr(e.parser.main)) } + +interface Issue27340Value { +} + +struct Issue27340Cat { + state int +} + +fn maybe_issue_27340_cat() ?Issue27340Cat { + return Issue27340Cat{ + state: 1 + } +} + +fn test_option_none_guard_interface_cast() { + x := maybe_issue_27340_cat() + if x == none { + assert false + return + } + v := Issue27340Value(x) + assert v is Issue27340Cat + cat := v as Issue27340Cat + assert cat.state == 1 +} + +fn issue_27340_value_state(v Issue27340Value) int { + assert v is Issue27340Cat + cat := v as Issue27340Cat + return cat.state +} + +fn test_option_none_guard_implicit_interface_arg() { + x := maybe_issue_27340_cat() + if x == none { + assert false + return + } + assert issue_27340_value_state(x) == 1 +} + +fn test_option_none_guard_interface_array_append() { + x := maybe_issue_27340_cat() + if x == none { + assert false + return + } + mut values := []Issue27340Value{} + values << x + assert issue_27340_value_state(values[0]) == 1 +} + +fn test_option_positive_none_guard_interface_cast() { + x := maybe_issue_27340_cat() + if x != none { + v := Issue27340Value(x) + assert issue_27340_value_state(v) == 1 + return + } + assert false +} + +fn test_option_positive_none_guard_implicit_interface_arg() { + x := maybe_issue_27340_cat() + if x != none { + assert issue_27340_value_state(x) == 1 + return + } + assert false +} + +fn test_option_positive_none_guard_interface_array_append() { + x := maybe_issue_27340_cat() + if x != none { + mut values := []Issue27340Value{} + values << x + assert issue_27340_value_state(values[0]) == 1 + return + } + assert false +} diff --git a/vlib/v/tests/options/option_none_guard_return_unwrap_test.v b/vlib/v/tests/options/option_none_guard_return_unwrap_test.v index 85171fdcc..51f16d42a 100644 --- a/vlib/v/tests/options/option_none_guard_return_unwrap_test.v +++ b/vlib/v/tests/options/option_none_guard_return_unwrap_test.v @@ -9,3 +9,71 @@ fn test_option_none_guard_return_unwrap() { assert guarded_name_len(none) == 0 assert guarded_name_len('guarded') == 7 } + +struct NoneGuardCat { + state int +} + +fn maybe_none_guard_cat() ?NoneGuardCat { + return NoneGuardCat{ + state: 1 + } +} + +fn none_guard_cat_state(cat NoneGuardCat) int { + return cat.state +} + +fn guarded_cat_arg_state() int { + x := maybe_none_guard_cat() + if x == none { + return 0 + } + return none_guard_cat_state(x) +} + +fn guarded_cat_cast_state() int { + x := maybe_none_guard_cat() + if x == none { + return 0 + } + cat := NoneGuardCat(x) + return cat.state +} + +fn guarded_cat_closure_arg_state() int { + x := maybe_none_guard_cat() + if x == none { + return 0 + } + callback := fn [x] () int { + return none_guard_cat_state(x) + } + return callback() +} + +fn generic_none_guard_identity[T](value T) T { + return value +} + +fn guarded_generic_closure_unwrap[T](value ?T) ?T { + if value == none { + return none + } + callback := fn [value] [T]() T { + return generic_none_guard_identity[T](value) + } + return callback[T]() +} + +fn guarded_cat_generic_closure_arg_state() int { + cat := guarded_generic_closure_unwrap[NoneGuardCat](maybe_none_guard_cat()) or { return 0 } + return cat.state +} + +fn test_option_none_guard_return_unwrap_for_args_and_casts() { + assert guarded_cat_arg_state() == 1 + assert guarded_cat_cast_state() == 1 + assert guarded_cat_closure_arg_state() == 1 + assert guarded_cat_generic_closure_arg_state() == 1 +} -- 2.39.5