Skip to content

Commit 62ec38a

Browse files
authored
cgen: unwrap option smartcast for interface casts (#27352)
1 parent 5e36217 commit 62ec38a

4 files changed

Lines changed: 196 additions & 9 deletions

File tree

vlib/v/gen/c/cgen.v

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ mut:
174174
inside_for_c_stmt bool
175175
inside_cast_in_heap int // inside cast to interface type in heap (resolve recursive calls)
176176
inside_cast bool
177+
inside_interface_cast bool
177178
inside_sumtype_cast bool
178179
inside_selector bool
179180
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
45104511
} else {
45114512
old_inside_sumtype_cast := g.inside_sumtype_cast
45124513
g.inside_sumtype_cast = true
4514+
old_inside_interface_cast := g.inside_interface_cast
4515+
g.inside_interface_cast = g.inside_interface_cast || is_interface_cast
45134516
old_left_is_opt := g.left_is_opt
45144517
g.left_is_opt = !exp.has_flag(.option)
45154518
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
45254528
}
45264529
g.inside_assign_fn_var = old_inside_assign_fn_var
45274530
g.left_is_opt = old_left_is_opt
4531+
g.inside_interface_cast = old_inside_interface_cast
45284532
g.inside_sumtype_cast = old_inside_sumtype_cast
45294533
}
45304534
if is_sumtype_cast {
@@ -9105,6 +9109,11 @@ fn (mut g Gen) ident(node ast.Ident) {
91059109
}
91069110
}
91079111
}
9112+
ident_option_name := if has_resolved_var && resolved_var.is_inherited {
9113+
'${closure_ctx}->${name}'
9114+
} else {
9115+
name
9116+
}
91089117
if node.info is ast.IdentVar {
91099118
node_info_is_option = node.info.is_option
91109119
if node.or_expr.kind == .absent {
@@ -9155,7 +9164,7 @@ fn (mut g Gen) ident(node ast.Ident) {
91559164
if orig_has_option && !comptime_type.has_flag(.option) {
91569165
styp := g.base_type(comptime_type)
91579166
ptr := if is_auto_heap { '->' } else { '.' }
9158-
g.write('(*(${styp}*)${name}${ptr}data)')
9167+
g.write('(*(${styp}*)${ident_option_name}${ptr}data)')
91599168
} else if comptime_type.has_flag(.option) {
91609169
if (g.inside_opt_or_res || g.left_is_opt) && node.or_expr.kind == .absent {
91619170
if !g.is_assign_lhs && is_auto_heap {
@@ -9166,7 +9175,7 @@ fn (mut g Gen) ident(node ast.Ident) {
91669175
} else {
91679176
styp := g.base_type(comptime_type)
91689177
ptr := if is_auto_heap { '->' } else { '.' }
9169-
g.write('(*(${styp}*)${name}${ptr}data)')
9178+
g.write('(*(${styp}*)${ident_option_name}${ptr}data)')
91709179
}
91719180
} else {
91729181
emit_auto_heap_deref := is_auto_heap && !g.inside_assign_fn_var
@@ -9222,7 +9231,8 @@ fn (mut g Gen) ident(node ast.Ident) {
92229231
g.write('*(')
92239232
}
92249233
if !has_smartcast && !selector_uses_unwrapped_option_smartcast
9225-
&& (g.inside_opt_or_res || g.left_is_opt) && node.or_expr.kind == .absent {
9234+
&& (g.inside_opt_or_res || (g.left_is_opt && !g.inside_interface_cast))
9235+
&& node.or_expr.kind == .absent {
92269236
if !g.is_assign_lhs && is_auto_heap {
92279237
g.write('(*${name})')
92289238
} else {
@@ -9256,7 +9266,7 @@ fn (mut g Gen) ident(node ast.Ident) {
92569266
} else {
92579267
g.get_comptime_for_var_type(node, node.info.typ)
92589268
}
9259-
g.unwrap_option_type(unwrap_typ, name, is_auto_heap)
9269+
g.unwrap_option_type(unwrap_typ, ident_option_name, is_auto_heap)
92609270
}
92619271
if node.or_expr.kind != .absent && !(g.inside_opt_or_res && g.inside_assign
92629272
&& !g.is_assign_lhs) {
@@ -9336,6 +9346,20 @@ fn (mut g Gen) ident(node ast.Ident) {
93369346
}
93379347
}
93389348
is_option = is_option || (has_resolved_var && resolved_var.orig_type.has_flag(.option))
9349+
runtime_option_type := if resolved_var.orig_type.has_flag(.option) {
9350+
resolved_var.orig_type
9351+
} else if resolved_var.typ.has_flag(.option) {
9352+
resolved_var.typ
9353+
} else {
9354+
ast.no_type
9355+
}
9356+
if has_resolved_var && runtime_option_type != ast.no_type && !node_info_is_option
9357+
&& resolved_var.smartcasts.len == 0 && resolved_var.ct_type_var != .smartcast
9358+
&& !g.inside_opt_or_res && !g.is_assign_lhs && !g.inside_selector_lhs
9359+
&& !g.right_is_opt && (!g.left_is_opt || g.inside_cast || g.inside_interface_cast) {
9360+
g.unwrap_option_type(runtime_option_type, ident_option_name, is_auto_heap)
9361+
return
9362+
}
93399363
// When an auto-heap option variable is being smartcast-unwrapped
93409364
// (e.g. `if svc != none { use(svc) }`), the option unwrap code
93419365
// at `->data` already handles the pointer indirection. Skip the
@@ -9594,13 +9618,13 @@ fn (mut g Gen) ident(node ast.Ident) {
95949618
if g.inside_selector_lhs && !node_info_is_option && has_resolved_var
95959619
&& !resolved_var.is_unwrapped && resolved_var.smartcasts.len == 0
95969620
&& resolved_var.typ.has_flag(.option) && !g.is_assign_lhs {
9597-
g.unwrap_option_type(resolved_var.typ, name, is_auto_heap)
9621+
g.unwrap_option_type(resolved_var.typ, ident_option_name, is_auto_heap)
95989622
return
95999623
}
96009624
if g.inside_selector_lhs && has_resolved_var && resolved_var.is_unwrapped
96019625
&& resolved_var.smartcasts.len == 0 && resolved_var.orig_type.has_flag(.option)
96029626
&& !g.is_assign_lhs {
9603-
g.unwrap_option_type(resolved_var.orig_type, name, is_auto_heap)
9627+
g.unwrap_option_type(resolved_var.orig_type, ident_option_name, is_auto_heap)
96049628
return
96059629
}
96069630
if has_resolved_var && resolved_var.is_inherited {

vlib/v/gen/c/fn.v

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6408,7 +6408,7 @@ fn (mut g Gen) call_args(node ast.CallExpr) {
64086408
exp_sym := g.table.sym(expected_types[i])
64096409
orig_sym := g.table.sym(arg.expr.obj.orig_type)
64106410
if !expected_types[i].has_option_or_result() && orig_sym.kind != .interface
6411-
&& (exp_sym.kind != .sum_type
6411+
&& (exp_sym.kind !in [.sum_type, .interface]
64126412
&& expected_types[i] != arg.expr.obj.orig_type) {
64136413
expected_types[i] = g.unwrap_generic(arg.expr.obj.smartcasts.last())
64146414
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
69046904
if needs_resolved_ident_type {
69056905
resolved_arg_typ := g.resolved_expr_type(ast.Expr(arg.expr), arg_typ)
69066906
if resolved_arg_typ != 0 {
6907-
arg_typ = g.unwrap_generic(g.recheck_concrete_type(resolved_arg_typ))
6907+
resolved_arg_type := g.unwrap_generic(g.recheck_concrete_type(resolved_arg_typ))
6908+
skip_inherited_option_storage_type := arg.expr.obj is ast.Var
6909+
&& arg.expr.obj.is_inherited && !expected_type.has_option_or_result()
6910+
&& arg_typ != 0 && !arg_typ.has_option_or_result()
6911+
&& resolved_arg_type.has_option_or_result()
6912+
if !skip_inherited_option_storage_type {
6913+
arg_typ = resolved_arg_type
6914+
}
69086915
}
69096916
}
69106917
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
69336940
if resolved_arg_typ != 0 && (arg_typ == 0 || g.type_needs_generic_resolution(arg_typ)
69346941
|| in_generic_context
69356942
|| g.unwrap_generic(resolved_arg_typ) != g.unwrap_generic(arg_typ)) {
6936-
arg_typ = g.unwrap_generic(g.recheck_concrete_type(resolved_arg_typ))
6943+
resolved_arg_type := g.unwrap_generic(g.recheck_concrete_type(resolved_arg_typ))
6944+
skip_inherited_option_storage_type := arg.expr is ast.Ident && arg.expr.obj is ast.Var
6945+
&& arg.expr.obj.is_inherited && !expected_type.has_option_or_result()
6946+
&& arg_typ != 0 && !arg_typ.has_option_or_result()
6947+
&& resolved_arg_type.has_option_or_result()
6948+
if !skip_inherited_option_storage_type {
6949+
arg_typ = resolved_arg_type
6950+
}
69376951
}
69386952
}
69396953
// Array slice expressions (e.g. b[..8]) always return a value in C (via builtin__array_slice),

vlib/v/tests/casts/cast_option_to_interface_test.v

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,84 @@ fn test_cast_option_to_interface() {
3838
assert e.parser.main.str == 'test'
3939
eprintln(voidptr(e.parser.main))
4040
}
41+
42+
interface Issue27340Value {
43+
}
44+
45+
struct Issue27340Cat {
46+
state int
47+
}
48+
49+
fn maybe_issue_27340_cat() ?Issue27340Cat {
50+
return Issue27340Cat{
51+
state: 1
52+
}
53+
}
54+
55+
fn test_option_none_guard_interface_cast() {
56+
x := maybe_issue_27340_cat()
57+
if x == none {
58+
assert false
59+
return
60+
}
61+
v := Issue27340Value(x)
62+
assert v is Issue27340Cat
63+
cat := v as Issue27340Cat
64+
assert cat.state == 1
65+
}
66+
67+
fn issue_27340_value_state(v Issue27340Value) int {
68+
assert v is Issue27340Cat
69+
cat := v as Issue27340Cat
70+
return cat.state
71+
}
72+
73+
fn test_option_none_guard_implicit_interface_arg() {
74+
x := maybe_issue_27340_cat()
75+
if x == none {
76+
assert false
77+
return
78+
}
79+
assert issue_27340_value_state(x) == 1
80+
}
81+
82+
fn test_option_none_guard_interface_array_append() {
83+
x := maybe_issue_27340_cat()
84+
if x == none {
85+
assert false
86+
return
87+
}
88+
mut values := []Issue27340Value{}
89+
values << x
90+
assert issue_27340_value_state(values[0]) == 1
91+
}
92+
93+
fn test_option_positive_none_guard_interface_cast() {
94+
x := maybe_issue_27340_cat()
95+
if x != none {
96+
v := Issue27340Value(x)
97+
assert issue_27340_value_state(v) == 1
98+
return
99+
}
100+
assert false
101+
}
102+
103+
fn test_option_positive_none_guard_implicit_interface_arg() {
104+
x := maybe_issue_27340_cat()
105+
if x != none {
106+
assert issue_27340_value_state(x) == 1
107+
return
108+
}
109+
assert false
110+
}
111+
112+
fn test_option_positive_none_guard_interface_array_append() {
113+
x := maybe_issue_27340_cat()
114+
if x != none {
115+
mut values := []Issue27340Value{}
116+
values << x
117+
assert issue_27340_value_state(values[0]) == 1
118+
return
119+
}
120+
assert false
121+
}

vlib/v/tests/options/option_none_guard_return_unwrap_test.v

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,71 @@ fn test_option_none_guard_return_unwrap() {
99
assert guarded_name_len(none) == 0
1010
assert guarded_name_len('guarded') == 7
1111
}
12+
13+
struct NoneGuardCat {
14+
state int
15+
}
16+
17+
fn maybe_none_guard_cat() ?NoneGuardCat {
18+
return NoneGuardCat{
19+
state: 1
20+
}
21+
}
22+
23+
fn none_guard_cat_state(cat NoneGuardCat) int {
24+
return cat.state
25+
}
26+
27+
fn guarded_cat_arg_state() int {
28+
x := maybe_none_guard_cat()
29+
if x == none {
30+
return 0
31+
}
32+
return none_guard_cat_state(x)
33+
}
34+
35+
fn guarded_cat_cast_state() int {
36+
x := maybe_none_guard_cat()
37+
if x == none {
38+
return 0
39+
}
40+
cat := NoneGuardCat(x)
41+
return cat.state
42+
}
43+
44+
fn guarded_cat_closure_arg_state() int {
45+
x := maybe_none_guard_cat()
46+
if x == none {
47+
return 0
48+
}
49+
callback := fn [x] () int {
50+
return none_guard_cat_state(x)
51+
}
52+
return callback()
53+
}
54+
55+
fn generic_none_guard_identity[T](value T) T {
56+
return value
57+
}
58+
59+
fn guarded_generic_closure_unwrap[T](value ?T) ?T {
60+
if value == none {
61+
return none
62+
}
63+
callback := fn [value] [T]() T {
64+
return generic_none_guard_identity[T](value)
65+
}
66+
return callback[T]()
67+
}
68+
69+
fn guarded_cat_generic_closure_arg_state() int {
70+
cat := guarded_generic_closure_unwrap[NoneGuardCat](maybe_none_guard_cat()) or { return 0 }
71+
return cat.state
72+
}
73+
74+
fn test_option_none_guard_return_unwrap_for_args_and_casts() {
75+
assert guarded_cat_arg_state() == 1
76+
assert guarded_cat_cast_state() == 1
77+
assert guarded_cat_closure_arg_state() == 1
78+
assert guarded_cat_generic_closure_arg_state() == 1
79+
}

0 commit comments

Comments
 (0)