From 9aeb6bf72d92a340a676bdeef88d2b9d3f1e8a34 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 4 Jun 2026 02:36:16 +0300 Subject: [PATCH] cgen: fix C codegen for generic chan pop via if-guard bound var (#27205) (#27337) --- vlib/v/gen/c/cgen.v | 9 +- vlib/v/gen/c/utils.v | 17 +++ .../generic_chan_pop_via_if_guard_test.v | 100 ++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 vlib/v/tests/generics/generic_chan_pop_via_if_guard_test.v diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index c4c27720d..05861a66d 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -8873,7 +8873,14 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) { || branch.stmt.right_types[0] != branch.stmt.left_types[0] { tmp_obj := g.new_tmp_var() tmp_objs << tmp_obj - el_stype := g.styp(branch.stmt.right_types[0]) + // Re-resolve the channel element type through the current generic + // instantiation; `right_types[0]` may be a stale concrete type baked + // from a different instantiation of the same generic body (issue #27205). + mut elem_type := g.resolved_expr_type(rec_expr, branch.stmt.right_types[0]) + if elem_type == 0 { + elem_type = branch.stmt.right_types[0] + } + el_stype := g.styp(elem_type) elem_types << if branch.stmt.op == .decl_assign { el_stype + ' ' } else { diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index 289ba2a6e..7dd1780b3 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -1476,6 +1476,23 @@ fn (mut g Gen) resolved_expr_type(expr ast.Expr, default_typ ast.Type) ast.Type return g.unwrap_generic(left_type) } } + ast.IfGuardExpr { + // A variable bound by an `if x := opt() {` guard stores the whole + // `IfGuardExpr` as its init expression. Resolve from the guard's source + // expression so the variable's type follows the current generic + // instantiation, instead of a stale type baked from a different one + // (see issue #27205). Only single-var guards map to one value type. + if expr.vars.len == 1 { + resolved := g.resolved_expr_type(expr.expr, if expr.expr_type != 0 { + expr.expr_type + } else { + default_typ + }) + if resolved != 0 { + return g.unwrap_generic(g.recheck_concrete_type(resolved)).clear_option_and_result() + } + } + } ast.IfExpr { inferred_typ := g.infer_if_expr_type(expr) if inferred_typ != 0 && inferred_typ != ast.void_type { diff --git a/vlib/v/tests/generics/generic_chan_pop_via_if_guard_test.v b/vlib/v/tests/generics/generic_chan_pop_via_if_guard_test.v new file mode 100644 index 000000000..77ab0503a --- /dev/null +++ b/vlib/v/tests/generics/generic_chan_pop_via_if_guard_test.v @@ -0,0 +1,100 @@ +// Regression test for https://github.com/vlang/v/issues/27205 +// +// A generic struct with a `chan T` field, popped through a variable that is +// bound by a map-index `if`-guard (`if w := c.waiters[id] { ... <-w.c.c ... }`). +// The body of such a generic method is generated once per concrete type. The +// channel element type must follow the current instantiation; previously it +// leaked the concrete type of a *different* instantiation, producing invalid C. +module main + +import time + +struct Inner[T] { + c chan T +} + +struct Waiter[T] { + c Inner[T] +} + +struct Controller[T] { +mut: + waiters map[int]Waiter[T] +} + +fn (mut c Controller[T]) add(id int) { + c.waiters[id] = Waiter[T]{ + c: Inner[T]{ + c: chan T{cap: 1} + } + } +} + +fn (mut c Controller[T]) emit(id int, e T) { + if w := c.waiters[id] { + w.c.c <- e + } +} + +// bare channel pop through an `if`-guard bound variable +fn (mut c Controller[T]) recv(id int) ?T { + if w := c.waiters[id] { + return <-w.c.c + } + return none +} + +// `select`-based channel pop through an `if`-guard bound variable +fn (mut c Controller[T]) recv_select(id int) ?T { + if w := c.waiters[id] { + select { + r := <-w.c.c { + return r + } + 500 * time.millisecond { + return none + } + } + } + return none +} + +struct EvA { + a int +} + +struct EvB { + b string +} + +fn test_generic_chan_pop_via_if_guard() { + mut ca := Controller[EvA]{} + ca.add(0) + mut cb := Controller[EvB]{} + cb.add(0) + + ca.emit(0, EvA{ a: 42 }) + cb.emit(0, EvB{ b: 'hello' }) + + ra := ca.recv(0) or { EvA{} } + assert ra.a == 42 + + rb := cb.recv(0) or { EvB{} } + assert rb.b == 'hello' +} + +fn test_generic_chan_select_via_if_guard() { + mut ca := Controller[EvA]{} + ca.add(0) + mut cb := Controller[EvB]{} + cb.add(0) + + ca.emit(0, EvA{ a: 7 }) + cb.emit(0, EvB{ b: 'world' }) + + ra := ca.recv_select(0) or { EvA{} } + assert ra.a == 7 + + rb := cb.recv_select(0) or { EvB{} } + assert rb.b == 'world' +} -- 2.39.5