Skip to content

Commit 61f1b51

Browse files
kbkpbotiFlow CLI
andcommitted
cgen: fix inline if expression returning closure (fixes #26595)
Fixed a bug where inline if expressions could not return a closure when: - Local variables are declared in the if block before the closure literal - The else branch returns an existing function-pointer variable The fix ensures that function pointer types in if expressions are generated correctly, using inline function pointer declarations instead of relying on typedefs that may not exist for closure-specific type names. Co-authored-by: iFlow CLI <iflow@anthropic.com>
1 parent 5cfde2e commit 61f1b51

2 files changed

Lines changed: 213 additions & 2 deletions

File tree

vlib/v/gen/c/if.v

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,18 @@ fn (mut g Gen) if_expr(node ast.IfExpr) {
282282
// nested if on return stmt
283283
g.write2(g.styp(g.unwrap_generic(g.last_if_option_type)), ' ')
284284
} else {
285-
g.write('${styp} ')
285+
// For function types, generate the function pointer declaration inline
286+
// to avoid issues with closure-specific type names that lack typedefs
287+
resolved_sym := g.table.sym(resolved_typ)
288+
if resolved_sym.kind == .function && resolved_sym.info is ast.FnType
289+
&& !resolved_typ.has_option_or_result() {
290+
g.write_fn_ptr_decl(&resolved_sym.info, tmp)
291+
g.writeln('; /* if prepend */')
292+
} else {
293+
g.write('${styp} ')
294+
g.writeln('${tmp}; /* if prepend */')
295+
}
286296
}
287-
g.writeln('${tmp}; /* if prepend */')
288297
g.set_current_pos_as_last_stmt_pos()
289298
}
290299
if g.infix_left_var_name.len > 0 {
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Test for closures in if expressions
2+
// Regression test for https://github.com/vlang/v/issues/26595
3+
//
4+
// Bug: inline `if` expression cannot return a closure when
5+
// local variables are declared in the `if` block before the
6+
// closure literal, and the else branch returns an existing
7+
// function-pointer variable.
8+
//
9+
// The fix ensures that function pointer types in if expressions
10+
// are generated correctly, using inline function pointer declarations
11+
// instead of relying on typedefs that may not exist for closure types.
12+
module main
13+
14+
struct Event {
15+
mut:
16+
value int
17+
}
18+
19+
struct Cfg {
20+
delay int
21+
callback fn (int, mut Event) = unsafe { nil }
22+
}
23+
24+
// Test closure with captured variable in if expression
25+
fn test_closure_in_if_expr_with_capture() {
26+
cond := true
27+
28+
result := if cond {
29+
tag := 'captured'
30+
fn [tag] (x int, mut e Event) {
31+
assert tag == 'captured'
32+
}
33+
} else {
34+
fn (x int, mut e Event) {}
35+
}
36+
37+
mut e := Event{}
38+
result(0, mut e)
39+
}
40+
41+
// Test two closures with captures in if expression
42+
fn test_two_closures_in_if_expr() {
43+
cond1 := true
44+
cond2 := false
45+
46+
// Test first branch taken
47+
result1 := if cond1 {
48+
tag := 'first'
49+
fn [tag] (x int, mut e Event) {
50+
assert tag == 'first'
51+
}
52+
} else {
53+
tag := 'second'
54+
fn [tag] (x int, mut e Event) {
55+
assert tag == 'second'
56+
}
57+
}
58+
59+
mut e := Event{}
60+
result1(0, mut e)
61+
62+
// Test second branch taken
63+
result2 := if cond2 {
64+
tag := 'first'
65+
fn [tag] (x int, mut e Event) {
66+
assert tag == 'first'
67+
}
68+
} else {
69+
tag := 'second'
70+
fn [tag] (x int, mut e Event) {
71+
assert tag == 'second'
72+
}
73+
}
74+
75+
result2(0, mut e)
76+
}
77+
78+
// Test closure in if expression with function type return
79+
// This is the core pattern from issue #26595:
80+
// - if branch has local declarations before closure literal
81+
// - else branch returns struct field (function pointer)
82+
fn resolve(cfg &Cfg) fn (int, mut Event) {
83+
result := if cfg.delay > 0 {
84+
tag := 'tag'
85+
fn [cfg, tag] (x int, mut e Event) {
86+
assert tag == 'tag'
87+
assert cfg.delay == 100
88+
}
89+
} else {
90+
cfg.callback
91+
}
92+
return result
93+
}
94+
95+
fn test_closure_with_struct_field_capture() {
96+
callback := fn (x int, mut e Event) {
97+
e.value = 42
98+
}
99+
100+
cfg := Cfg{
101+
delay: 100
102+
callback: callback
103+
}
104+
105+
resolved_fn := resolve(&cfg)
106+
mut e := Event{}
107+
resolved_fn(0, mut e)
108+
109+
// Test the else branch
110+
cfg2 := Cfg{
111+
delay: 0
112+
callback: callback
113+
}
114+
resolved_fn2 := resolve(&cfg2)
115+
mut e2 := Event{}
116+
resolved_fn2(0, mut e2)
117+
assert e2.value == 42
118+
}
119+
120+
// Test nested if expressions with closures
121+
fn test_nested_if_expr_with_closures() {
122+
outer_cond := true
123+
inner_cond := true
124+
125+
result := if outer_cond {
126+
tag1 := 'outer'
127+
if inner_cond {
128+
tag2 := 'inner'
129+
fn [tag1, tag2] () string {
130+
return '${tag1}_${tag2}'
131+
}
132+
} else {
133+
fn [tag1] () string {
134+
return tag1
135+
}
136+
}
137+
} else {
138+
fn () string {
139+
return 'none'
140+
}
141+
}
142+
143+
assert result() == 'outer_inner'
144+
}
145+
146+
// Test closure in if expression with different return types
147+
fn test_closure_returning_value() {
148+
cond := true
149+
150+
result := if cond {
151+
multiplier := 2
152+
fn [multiplier] (n int) int {
153+
return n * multiplier
154+
}
155+
} else {
156+
fn (n int) int {
157+
return n
158+
}
159+
}
160+
161+
assert result(5) == 10
162+
}
163+
164+
// Test the exact pattern from issue #26595
165+
fn test_issue_26595_pattern() {
166+
struct Event2 {}
167+
168+
struct Cfg2 {
169+
delay int
170+
callback fn (&int, mut Event2) = unsafe { nil }
171+
}
172+
173+
resolve2 := fn (cfg &Cfg2) fn (&int, mut Event2) {
174+
// This pattern previously caused C compilation error:
175+
// "error: expected ';' after expression"
176+
// because the closure type name lacked a typedef
177+
result := if cfg.delay > 0 {
178+
tag := 'tag'
179+
fn [cfg, tag] (x &int, mut e Event2) {
180+
dump('${tag}')
181+
}
182+
} else {
183+
cfg.callback
184+
}
185+
return result
186+
}
187+
188+
resolve2(&Cfg2{
189+
delay: 2000
190+
callback: fn (x &int, mut e Event2) {}
191+
})
192+
}
193+
194+
fn main() {
195+
test_closure_in_if_expr_with_capture()
196+
test_two_closures_in_if_expr()
197+
test_closure_with_struct_field_capture()
198+
test_nested_if_expr_with_closures()
199+
test_closure_returning_value()
200+
test_issue_26595_pattern()
201+
println('All tests passed!')
202+
}

0 commit comments

Comments
 (0)