From 02e1765ecf21e057e69b1b88fb2bc7e717ac52f0 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 19 Jun 2026 23:19:32 +0300 Subject: [PATCH] cgen: fix bogus generic suffix on alias-of-generic method calls (#27465) (#27505) --- vlib/v/gen/c/fn.v | 23 +++++++++++---- .../bitset27465/bitset.v | 19 ++++++++++++ .../candidates27465/candidates.v | 28 ++++++++++++++++++ .../main_test.v | 29 +++++++++++++++++++ .../generic_type_alias_method_27465/v.mod | 1 + 5 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 vlib/v/tests/modules/generic_type_alias_method_27465/bitset27465/bitset.v create mode 100644 vlib/v/tests/modules/generic_type_alias_method_27465/candidates27465/candidates.v create mode 100644 vlib/v/tests/modules/generic_type_alias_method_27465/main_test.v create mode 100644 vlib/v/tests/modules/generic_type_alias_method_27465/v.mod diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 40839d4f7..ae6e0278d 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -34,16 +34,21 @@ fn (g &Gen) receiver_exact_method_for_type(typ ast.Type, method_name string) ?as return none } sym := g.table.sym(typ) + // A `mut` receiver is stored as a pointer (`&T`), so compare the base types + // (ignoring the pointer level) to match such a method against both the value + // and pointer forms of `typ`. See #27465. + typ_base := typ.set_nr_muls(0) for method in sym.methods { - if method.name == method_name && method.receiver_type == typ { + if method.name == method_name && method.receiver_type.set_nr_muls(0) == typ_base { return method } } if sym.info is ast.Alias { parent_type := sym.info.parent_type.derive(typ) + parent_base := parent_type.set_nr_muls(0) parent_sym := g.table.sym(parent_type) for method in parent_sym.methods { - if method.name == method_name && method.receiver_type == parent_type { + if method.name == method_name && method.receiver_type.set_nr_muls(0) == parent_base { return method } } @@ -3520,9 +3525,17 @@ fn (mut g Gen) receiver_generic_call_context(left_type ast.Type, method_name str rec_sym := g.table.final_sym(left_type) mut receiver_concrete_types := []ast.Type{} mut parent_method := ast.Fn{} - if has_structured_method_for_exact { + // A method defined directly on an alias type (e.g. `fn (c Candidates) set()` where + // `type Candidates = BitSet[u32]`) takes priority over the generic parent method + // inherited from the aliased type. Treat it as the resolved (non-generic) method so + // no generic instantiation suffix (`_T_u32`) is appended to its C name. See #27465. + exact_method_on_alias := exact_method_exists && exact_method.receiver_type != 0 + && g.table.sym(left_type).info is ast.Alias + && g.unwrap_generic(exact_method.receiver_type).set_nr_muls(0) == g.unwrap_generic(left_type).set_nr_muls(0) + if has_structured_method_for_exact || exact_method_on_alias { parent_method = exact_method } + keep_alias_method := exact_method_on_alias && exact_method.generic_names.len == 0 match rec_sym.info { ast.Struct, ast.Interface, ast.SumType { if rec_sym.info.concrete_types.len > 0 { @@ -3534,7 +3547,7 @@ fn (mut g Gen) receiver_generic_call_context(left_type ast.Type, method_name str if rec_sym.info.parent_type.has_flag(.generic) { parent_sym := g.table.sym(rec_sym.info.parent_type) if m := parent_sym.find_method(method_name) { - if !has_structured_method_for_exact { + if !has_structured_method_for_exact && !keep_alias_method { parent_method = m } } @@ -3545,7 +3558,7 @@ fn (mut g Gen) receiver_generic_call_context(left_type ast.Type, method_name str if rec_sym.info.parent_idx > 0 { parent_sym := g.table.sym(ast.idx_to_type(rec_sym.info.parent_idx)) if m := parent_sym.find_method(method_name) { - if !has_structured_method_for_exact { + if !has_structured_method_for_exact && !keep_alias_method { parent_method = m } } diff --git a/vlib/v/tests/modules/generic_type_alias_method_27465/bitset27465/bitset.v b/vlib/v/tests/modules/generic_type_alias_method_27465/bitset27465/bitset.v new file mode 100644 index 000000000..53320061a --- /dev/null +++ b/vlib/v/tests/modules/generic_type_alias_method_27465/bitset27465/bitset.v @@ -0,0 +1,19 @@ +module bitset27465 + +pub struct BitSet[T] { +pub mut: + bits T + index int = -1 +} + +// set shares its name with the alias method `Candidates.set` defined in the +// importing module. Before #27465 was fixed, that name collision made the call +// site of the alias method emit a bogus generic suffix (`..._set_T_u32`), +// referencing an undefined C symbol. +pub fn (mut b BitSet[T]) set(bit T) { + b.bits |= T(1) << bit +} + +pub fn (mut b BitSet[T]) set_all() { + b.bits = ~T(0) +} diff --git a/vlib/v/tests/modules/generic_type_alias_method_27465/candidates27465/candidates.v b/vlib/v/tests/modules/generic_type_alias_method_27465/candidates27465/candidates.v new file mode 100644 index 000000000..ef129560f --- /dev/null +++ b/vlib/v/tests/modules/generic_type_alias_method_27465/candidates27465/candidates.v @@ -0,0 +1,28 @@ +module candidates27465 + +import bitset27465 as bs + +pub type Candidates = bs.BitSet[u32] + +// set shadows the generic `BitSet[T].set`. Every call to it - both the in-module +// call below and the cross-module call from the test - must resolve to this +// non-generic method (`candidates27465__Candidates_set`), not the generic parent. +pub fn (mut c Candidates) set(digits ...int) { + for d in digits { + c.bits |= u32(1) << u32(d) + } +} + +// make exercises an in-module call to the alias method. +pub fn make() Candidates { + mut c := Candidates{} + c.set(0, 1, 2) + return c +} + +// full exercises the inherited generic method on the alias type. +pub fn full() Candidates { + mut c := Candidates{} + c.set_all() + return c +} diff --git a/vlib/v/tests/modules/generic_type_alias_method_27465/main_test.v b/vlib/v/tests/modules/generic_type_alias_method_27465/main_test.v new file mode 100644 index 000000000..506ea9ce7 --- /dev/null +++ b/vlib/v/tests/modules/generic_type_alias_method_27465/main_test.v @@ -0,0 +1,29 @@ +module main + +import candidates27465 as se + +// Regression test for https://github.com/vlang/v/issues/27465 +// +// A type alias of a generic instantiation (`type Candidates = BitSet[u32]`) that +// defines its own method whose name collides with a method on the generic parent +// (`set`) used to make the alias method's call sites emit a bogus generic suffix, +// e.g. `candidates27465__Candidates_set_T_u32`, an undefined C symbol. + +fn test_alias_method_in_module() { + // se.make() calls the alias method `Candidates.set` from inside its own module. + c := se.make() + assert c.bits == u32(0b111) +} + +fn test_alias_method_cross_module() { + // Cross-module call to the alias method via a fully qualified `se.Candidates{}`. + mut c := se.Candidates{} + c.set(3, 5) + assert c.bits == u32(0b101000) +} + +fn test_inherited_generic_method_still_works() { + // The inherited generic method keeps its proper generic suffix. + c := se.full() + assert c.bits == ~u32(0) +} diff --git a/vlib/v/tests/modules/generic_type_alias_method_27465/v.mod b/vlib/v/tests/modules/generic_type_alias_method_27465/v.mod new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/vlib/v/tests/modules/generic_type_alias_method_27465/v.mod @@ -0,0 +1 @@ +1 -- 2.39.5