Skip to content

Commit 5337164

Browse files
authored
cgen: fix generic multi-return interface cast with stale checker types (#27610)
1 parent f352fbf commit 5337164

3 files changed

Lines changed: 211 additions & 5 deletions

File tree

vlib/v/gen/c/cgen.v

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ mut:
121121
is_fn_index_call bool
122122
is_cc_msvc bool // g.pref.ccompiler == 'msvc'
123123
is_option_auto_heap bool
124+
is_auto_deref_synthetic bool
124125
vlines_path string // set to the proper path for generating #line directives
125126
options_pos_forward int // insertion point to forward
126127
options_forward []string // to forward
@@ -7267,7 +7268,7 @@ fn (mut g Gen) expr(node_ ast.Expr) {
72677268
// For `*val` where val is a mut generic param with pointer type
72687269
// (e.g. `mut val T` where T=&int -> C: `int** val`), auto-deref needs
72697270
// an extra `*` so `*val` in V becomes `**val` in C.
7270-
if node.op == .mul && node.right is ast.Ident {
7271+
if node.op == .mul && node.right is ast.Ident && !g.is_auto_deref_synthetic {
72717272
ident_right := node.right as ast.Ident
72727273
if ident_right.obj is ast.Var && ident_right.obj.is_auto_deref
72737274
&& ident_right.obj.is_arg && ident_right.obj.generic_typ != 0 {
@@ -11656,14 +11657,42 @@ fn (mut g Gen) return_stmt(node ast.Return) {
1165611657
final_assignments += g.go_before_last_stmt() + '\t'
1165711658
g.write2(line, '{0}')
1165811659
} else {
11659-
if expr.is_auto_deref_var() {
11660-
g.write('*')
11660+
is_auto_deref := expr.is_auto_deref_var()
11661+
mut resolved_ret_type := ret_expr_types[i]
11662+
if g.cur_concrete_types.len > 0 {
11663+
if expr is ast.Ident {
11664+
resolved_type := g.resolved_scope_var_type_uncached(expr)
11665+
if resolved_type != 0 {
11666+
resolved_ret_type = resolved_type
11667+
if is_auto_deref {
11668+
resolved_ret_type = resolved_ret_type.deref()
11669+
}
11670+
}
11671+
} else {
11672+
resolved_type := g.resolved_expr_type(expr, resolved_ret_type)
11673+
if resolved_type != 0 && !g.type_has_unresolved_generic_parts(resolved_type) {
11674+
resolved_ret_type = resolved_type
11675+
}
11676+
}
1166111677
}
1166211678
if mr_info.types[i].has_flag(.option) {
11663-
g.expr_with_opt(expr, ret_expr_types[i], mr_info.types[i])
11679+
if is_auto_deref {
11680+
g.write('*')
11681+
}
11682+
g.expr_with_opt(expr, resolved_ret_type, mr_info.types[i])
1166411683
} else if g.table.sym(mr_info.types[i]).kind in [.sum_type, .interface] {
11665-
g.expr_with_cast(expr, ret_expr_types[i], mr_info.types[i])
11684+
if is_auto_deref {
11685+
g.is_auto_deref_synthetic = true
11686+
g.expr_with_cast(ast.PrefixExpr{ op: .mul, right: expr },
11687+
resolved_ret_type, mr_info.types[i])
11688+
g.is_auto_deref_synthetic = false
11689+
} else {
11690+
g.expr_with_cast(expr, resolved_ret_type, mr_info.types[i])
11691+
}
1166611692
} else {
11693+
if is_auto_deref {
11694+
g.write('*')
11695+
}
1166711696
g.expr(expr)
1166811697
}
1166911698
}

vlib/v/gen/c/fn.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,7 @@ fn (mut g Gen) post_process_generic_fns_for_files(files []&ast.File) {
11161116
}
11171117
emitted_generic_specializations[specialization_key] = true
11181118
g.cur_concrete_types = concrete_specialization.clone()
1119+
g.clear_type_resolution_caches()
11191120
g.fn_decl(*generic_fn)
11201121
emitted_this_round = true
11211122
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
module main
2+
3+
interface Getter {
4+
f() int
5+
}
6+
7+
struct Impl1 {
8+
x int
9+
}
10+
11+
struct Impl2 {
12+
x int
13+
}
14+
15+
fn (i Impl1) f() int {
16+
return i.x + 100
17+
}
18+
19+
fn (i Impl2) f() int {
20+
return i.x + 200
21+
}
22+
23+
struct Container[T] {
24+
v T
25+
}
26+
27+
fn (c Container[T]) get() (Getter, int) {
28+
return c.v, 42
29+
}
30+
31+
fn test_generic_multi_return_interface_cast() {
32+
// Test with Impl1 — should call Impl1.f() which returns x+100
33+
c1 := Container[Impl1]{
34+
v: Impl1{10}
35+
}
36+
i1, extra1 := c1.get()
37+
assert i1.f() == 110
38+
assert extra1 == 42
39+
40+
// Test with Impl2 — should call Impl2.f() which returns x+200
41+
c2 := Container[Impl2]{
42+
v: Impl2{20}
43+
}
44+
i2, extra2 := c2.get()
45+
assert i2.f() == 220
46+
assert extra2 == 42
47+
}
48+
49+
// Regression test: generic multi-return with mut param and interface cast
50+
// The resolved type from scope needs to be dereferenced when auto-deref is active.
51+
@[heap]
52+
struct HeapStruct {
53+
x int
54+
}
55+
56+
fn (h HeapStruct) f() int {
57+
return h.x
58+
}
59+
60+
fn get_mut[T](mut x T) (Getter, int) {
61+
return x, 42
62+
}
63+
64+
fn test_generic_multi_return_mut_param_interface_cast() {
65+
mut h := HeapStruct{7}
66+
g, extra := get_mut[HeapStruct](mut h)
67+
assert g.f() == 7
68+
assert extra == 42
69+
}
70+
71+
// Regression test: generic multi-return with mut param whose concrete type
72+
// is itself a pointer (T = &Impl). The synthetic PrefixExpr should not
73+
// trigger the extra deref for mut generic pointer params.
74+
struct Impl {
75+
x int
76+
}
77+
78+
fn (i Impl) f() int {
79+
return i.x
80+
}
81+
82+
fn get_ptr_multi[T](mut x T) (Getter, int) {
83+
return x, 0
84+
}
85+
86+
fn test_generic_multi_return_ptr_param_interface_cast() {
87+
mut i := &Impl{42}
88+
g, n := get_ptr_multi[&Impl](mut i)
89+
assert g.f() == 42
90+
assert n == 0
91+
}
92+
93+
// Regression test: generic multi-return with `as T` cast then interface cast.
94+
// Tests both value-interface and &Interface pointer codegen paths.
95+
// Two concrete types with DIFFERENT struct layouts ensure the C compiler
96+
// catches any stale-type codegen.
97+
98+
interface Gettable {
99+
get() int
100+
}
101+
102+
struct TypeA {
103+
x int
104+
y int
105+
}
106+
107+
fn (a TypeA) get() int {
108+
return a.x + a.y
109+
}
110+
111+
struct TypeB {
112+
msg string
113+
}
114+
115+
fn (b TypeB) get() int {
116+
return b.msg.len
117+
}
118+
119+
struct Holder {
120+
ptr &Gettable
121+
}
122+
123+
fn (h Holder) inner_ptr() &Gettable {
124+
return h.ptr
125+
}
126+
127+
// Value interface path: Gettable stored directly, `as T` cast, multi-return
128+
@[heap]
129+
struct Pool[T] {
130+
mut:
131+
raw Gettable
132+
}
133+
134+
fn (mut p Pool[T]) acquire() (Gettable, int) {
135+
v := p.raw as T
136+
return v, 42
137+
}
138+
139+
// &Interface pointer path: Holder stores &Gettable, extracted via inner_ptr()
140+
struct Wrapper[T] {
141+
src Holder
142+
}
143+
144+
fn (w Wrapper[T]) extract() (Gettable, &Gettable) {
145+
raw_conn := w.src.inner_ptr()
146+
raw := raw_conn as T
147+
return raw, raw_conn
148+
}
149+
150+
fn test_generic_as_cast_multi_return_interface() {
151+
// Value interface path
152+
mut p1 := Pool[TypeA]{
153+
raw: TypeA{10, 20}
154+
}
155+
v1, _ := p1.acquire()
156+
assert v1.get() == 30
157+
158+
mut p2 := Pool[TypeB]{
159+
raw: TypeB{'hi'}
160+
}
161+
v2, _ := p2.acquire()
162+
assert v2.get() == 2
163+
164+
// &Interface pointer path
165+
w1 := Wrapper[TypeA]{
166+
src: Holder{&TypeA{10, 20}}
167+
}
168+
g1, _ := w1.extract()
169+
assert g1.get() == 30
170+
171+
w2 := Wrapper[TypeB]{
172+
src: Holder{&TypeB{'hi'}}
173+
}
174+
g2, _ := w2.extract()
175+
assert g2.get() == 2
176+
}

0 commit comments

Comments
 (0)