Skip to content

Commit 493890a

Browse files
committed
cgen: interface smartcast fix
1 parent 2f3c0f1 commit 493890a

3 files changed

Lines changed: 131 additions & 2 deletions

File tree

vlib/v/gen/c/cgen.v

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4874,8 +4874,8 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ
48744874
}
48754875
}
48764876
// Generic dereferencing logic
4877-
neither_void := ast.voidptr_type !in [got_type, expected_type]
4878-
&& ast.nil_type !in [got_type, expected_type]
4877+
neither_void := ast.voidptr_type !in [got_type.idx_type(), expected_type.idx_type()]
4878+
&& ast.nil_type !in [got_type.idx_type(), expected_type.idx_type()]
48794879
if expected_type.has_flag(.shared_f) && !got_type_raw.has_flag(.shared_f)
48804880
&& !expected_type.has_option_or_result() {
48814881
shared_styp := exp_styp[0..exp_styp.len - 1] // `shared` implies ptr, so eat one `*`
@@ -7198,8 +7198,14 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
71987198
|| (is_interface_smartcast_expr
71997199
&& g.table.final_sym(g.unwrap_generic(smartcast_expr_var.smartcasts.last())).kind != .interface
72007200
&& exposed_interface_smartcast_type.is_ptr())
7201+
// Interface→interface smartcast: the conversion `I_X_as_I_Y(parent)` returns
7202+
// a struct value, not a pointer, so field access must use `.`, not `->`.
7203+
is_interface_to_interface_smartcast := is_interface_smartcast_expr
7204+
&& g.table.final_sym(g.unwrap_generic(smartcast_expr_var.smartcasts.last())).kind == .interface
72017205
left_is_ptr := if expr_is_unwrapped_autoheap_option {
72027206
false
7207+
} else if is_interface_to_interface_smartcast {
7208+
false
72037209
} else {
72047210
field_is_opt || expr_is_auto_heap || interface_smartcast_selector_emits_ptr
72057211
|| (is_interface_smartcast_lhs && !interface_smartcast_expr_is_dereferenced

vlib/v/gen/c/utils.v

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,13 @@ fn (mut g Gen) resolved_expr_type(expr ast.Expr, default_typ ast.Type) ast.Type
11691169
}
11701170
}
11711171
ast.SelectorExpr {
1172+
// If this selector has been smart-cast in the current scope (e.g.
1173+
// `if mut w.face is X { ... w.face ... }`), use the smart-cast type
1174+
// rather than the field's declared type.
1175+
smartcast_typ := g.resolve_selector_smartcast_type(expr)
1176+
if smartcast_typ != 0 {
1177+
return smartcast_typ
1178+
}
11721179
left_default := if expr.expr_type != 0 { expr.expr_type } else { default_typ }
11731180
left_type := g.recheck_concrete_type(g.resolved_expr_type(expr.expr, left_default))
11741181
if left_type != 0 {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Tests for interface smart-cast cgen bugs in field access and variable
2+
// declaration paths.
3+
//
4+
// 1. Interface→interface smart-cast: `parent.id` where parent is smart-cast
5+
// from one interface to another. Cgen used to emit
6+
// `(I_X_as_I_Y(parent)->id)` (`->` on a struct value) instead of
7+
// `(I_X_as_I_Y(parent).id)`.
8+
//
9+
// 2. Variable declared from a smart-cast selector should take the smart-cast
10+
// type, not the original interface. `mut dd := w.face` inside
11+
// `if mut w.face is Concrete` was being typed as the original interface,
12+
// so embedded-field access (e.g. `dd.context.x`) crashed in cgen.
13+
//
14+
// 3. Auto-deref of a `mut` method receiver passed as `voidptr` to a generic
15+
// method was emitting `*f` instead of `f` because the voidptr parameter
16+
// type carried the `.generic` flag during cgen, defeating the
17+
// `voidptr_type !in [...]` early-return.
18+
import eventbus
19+
20+
// ── (1) interface→interface smart-cast field access ────────────────────────
21+
22+
interface Foo {
23+
id string
24+
}
25+
26+
interface Bar {
27+
id string
28+
}
29+
30+
struct ImplFB {
31+
id string
32+
}
33+
34+
fn check_iface_to_iface(parent Foo) string {
35+
if parent is Bar {
36+
return parent.id
37+
}
38+
return ''
39+
}
40+
41+
fn test_interface_to_interface_smartcast_field_access() {
42+
i := &ImplFB{
43+
id: 'iface_to_iface'
44+
}
45+
assert check_iface_to_iface(i) == 'iface_to_iface'
46+
}
47+
48+
// ── (2) selector smart-cast var declaration ────────────────────────────────
49+
50+
struct Inner {
51+
value string
52+
}
53+
54+
struct ConcreteWithEmbed {
55+
Inner
56+
tag string
57+
}
58+
59+
interface IFace {}
60+
61+
struct WrapIF {
62+
mut:
63+
face IFace
64+
}
65+
66+
fn check_selector_smartcast_var(mut w WrapIF) string {
67+
if mut w.face is ConcreteWithEmbed {
68+
// `dd` should be ConcreteWithEmbed, not IFace, so accessing the
69+
// embedded `Inner.value` field works.
70+
dd := w.face
71+
return '${dd.tag}:${dd.value}'
72+
}
73+
return ''
74+
}
75+
76+
fn test_selector_smartcast_var_declaration() {
77+
mut c := ConcreteWithEmbed{
78+
Inner: Inner{
79+
value: 'inner_v'
80+
}
81+
tag: 'tag_v'
82+
}
83+
mut w := WrapIF{
84+
face: &c
85+
}
86+
assert check_selector_smartcast_var(mut w) == 'tag_v:inner_v'
87+
}
88+
89+
// ── (3) auto-deref of mut receiver passed as voidptr to a generic method ───
90+
91+
struct Counter {
92+
mut:
93+
got_self int
94+
hits int
95+
}
96+
97+
fn counter_handler(receiver voidptr, args voidptr, sender voidptr) {
98+
mut c := unsafe { &Counter(receiver) }
99+
c.hits++
100+
}
101+
102+
fn (mut c Counter) wire(mut bus eventbus.EventBus[string]) {
103+
// Bare `c` here. The receiver is a `voidptr` parameter on a generic
104+
// method, and the bug emitted `*c` (struct value) for the C call.
105+
bus.subscriber.subscribe_method('hit', counter_handler, c)
106+
c.got_self = unsafe { int(i64(voidptr(c)) & 0xffffffff) }
107+
}
108+
109+
fn test_voidptr_mut_receiver_in_generic_method() {
110+
mut c := &Counter{}
111+
mut bus := eventbus.new[string]()
112+
c.wire(mut bus)
113+
bus.publish('hit', unsafe { nil }, unsafe { nil })
114+
bus.publish('hit', unsafe { nil }, unsafe { nil })
115+
assert c.hits == 2
116+
}

0 commit comments

Comments
 (0)