From 72e77c5c2ae98c4f9bc0111128acc95a82a408d3 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:35 +0300 Subject: [PATCH] checker: maps with non-generic interfaces as values don't work (fixes #16576) --- vlib/v/checker/checker.v | 7 +- .../generics_interface_field_type_err.out | 4 +- .../generics_interface_field_type_err.vv | 9 +- vlib/v/gen/c/cgen.v | 23 +++-- .../generic_interface_map_value_test.v | 86 +++++++++++++++++++ 5 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 vlib/v/tests/generics/generic_interface_map_value_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index aec42c2e4..4dd76b8f6 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2087,8 +2087,8 @@ fn (mut c Checker) type_implements(typ ast.Type, interface_type ast.Type, pos to // Verify methods for imethod in imethods { - mut method := c.table.find_method_with_embeds(typ_sym, imethod.name) or { - typ_sym.find_method_with_generic_parent(imethod.name) or { + mut method := typ_sym.find_method_with_generic_parent(imethod.name) or { + c.table.find_method_with_embeds(typ_sym, imethod.name) or { // `str() string` is auto-generated for all types during cgen if imethod.name == 'str' && imethod.params.len == 1 && imethod.return_type == ast.string_type { @@ -7844,7 +7844,8 @@ fn (mut c Checker) ensure_generic_type_specify_type_names(typ ast.Type, pos toke } .interface { info := sym.info as ast.Interface - if info.generic_types.len > 0 && !typ.has_flag(.generic) && info.concrete_types.len == 0 { + if info.generic_types.len > 0 && !typ.has_flag(.generic) && info.concrete_types.len == 0 + && !is_container_typ { c.error('`${sym.name}` type is generic interface, must specify the generic type names, e.g. ${sym.name}[T], ${sym.name}[int]', pos) return false diff --git a/vlib/v/checker/tests/generics_interface_field_type_err.out b/vlib/v/checker/tests/generics_interface_field_type_err.out index 0b24eb197..bb5e4c5dd 100644 --- a/vlib/v/checker/tests/generics_interface_field_type_err.out +++ b/vlib/v/checker/tests/generics_interface_field_type_err.out @@ -1,7 +1,7 @@ vlib/v/checker/tests/generics_interface_field_type_err.vv:34:7: error: `IComponentStore` type is generic interface, must specify the generic type names, e.g. IComponentStore[T], IComponentStore[int] 32 | struct Registry { 33 | pub mut: - 34 | data map[string]IComponentStore - | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + 34 | data IComponentStore + | ~~~~~~~~~~~~~~~ 35 | } 36 | diff --git a/vlib/v/checker/tests/generics_interface_field_type_err.vv b/vlib/v/checker/tests/generics_interface_field_type_err.vv index f2823153f..8c62ab3b7 100644 --- a/vlib/v/checker/tests/generics_interface_field_type_err.vv +++ b/vlib/v/checker/tests/generics_interface_field_type_err.vv @@ -31,14 +31,7 @@ pub fn (mut cs ComponentStore[T]) add(e Entity, value T) { @[heap] struct Registry { pub mut: - data map[string]IComponentStore -} - -pub fn registry() &Registry { - mut r := &Registry{ - data: map[string]IComponentStore{} - } - return r + data IComponentStore } fn main() { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index dd247238b..727d6c308 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1465,6 +1465,17 @@ fn (mut g Gen) base_type(_t ast.Type) string { return styp } +fn (g &Gen) can_write_interface_typesymbol_declaration(sym ast.TypeSymbol) bool { + if sym.info !is ast.Interface { + return false + } + info := sym.info as ast.Interface + if !info.is_generic { + return true + } + return !info.fields.any(it.typ.has_flag(.generic) || g.type_has_unresolved_generic_parts(it.typ)) +} + fn (mut g Gen) generic_fn_name(types []ast.Type, before string) string { if types.len == 0 { return before @@ -2315,7 +2326,7 @@ fn (mut g Gen) cc_type(typ ast.Type, is_prefix_struct bool) string { // TODO: this needs to be removed; cgen shouldn't resolve generic types (job of checker) match sym.info { ast.Struct, ast.Interface, ast.SumType { - if sym.info.is_generic && sym.generic_types.len == 0 { + if sym.info.is_generic && sym.generic_types.len == 0 && sym.kind != .interface { mut sgtyps := '' for gt in sym.info.generic_types { gts := g.table.sym(g.unwrap_generic(gt)) @@ -2495,15 +2506,15 @@ pub fn (mut g Gen) write_typedef_types() { // to prevent generating interface struct before definition of field types interfaces := g.table.type_symbols.filter(it.info is ast.Interface && !it.is_builtin) - mut interface_non_generic_syms := []&ast.TypeSymbol{cap: interfaces.len} + mut interface_declaration_syms := []&ast.TypeSymbol{cap: interfaces.len} for sym in interfaces { g.write_interface_typedef(sym) - if sym.info is ast.Interface && !sym.info.is_generic { - interface_non_generic_syms << sym + if g.can_write_interface_typesymbol_declaration(sym) { + interface_declaration_syms << sym } } mut already_generated_ifaces := map[string]bool{} - for sym in interface_non_generic_syms { + for sym in interface_declaration_syms { if g.pref.skip_unused && sym.idx !in g.table.used_features.used_syms { continue } @@ -9151,7 +9162,7 @@ fn (mut g Gen) write_builtin_types() { } if sym.info is ast.Interface { g.write_interface_typedef(sym) - if !sym.info.is_generic { + if g.can_write_interface_typesymbol_declaration(sym) { g.write_interface_typesymbol_declaration(sym) } } else { diff --git a/vlib/v/tests/generics/generic_interface_map_value_test.v b/vlib/v/tests/generics/generic_interface_map_value_test.v new file mode 100644 index 000000000..ae6228818 --- /dev/null +++ b/vlib/v/tests/generics/generic_interface_map_value_test.v @@ -0,0 +1,86 @@ +struct Entity { + id u16 +} + +struct Position { + x f64 + y f64 + z f64 +} + +struct Velocity { + x f64 + y f64 + z f64 +} + +interface ComponentStore[T] { + len() int +mut: + add(Entity, T) +} + +struct Store[T] { +mut: + items []T +} + +fn (mut s Store[T]) add(_ Entity, value T) { + s.items << value +} + +fn (s &Store[T]) len() int { + return s.items.len +} + +@[heap] +struct Registry { +mut: + data map[string]ComponentStore +} + +fn new_registry() &Registry { + return &Registry{ + data: map[string]ComponentStore{} + } +} + +fn (mut r Registry) create_store[T]() { + if T.name in r.data { + return + } + r.data[T.name] = Store[T]{} +} + +fn (mut r Registry) add_component[T](entity Entity, component T) { + r.create_store[T]() + r.data[T.name].add(entity, component) +} + +fn (mut r Registry) count[T]() int { + return r.data[T.name].len() +} + +fn test_generic_interface_map_values_can_store_multiple_concrete_instantiations() { + mut registry := new_registry() + entity := Entity{ + id: 1 + } + registry.add_component(entity, Position{ + x: 10 + y: 2 + z: 0 + }) + registry.add_component(entity, Position{ + x: 11 + y: 3 + z: 1 + }) + registry.add_component(entity, Velocity{ + x: 1 + y: 2 + z: 3 + }) + assert registry.count[Position]() == 2 + assert registry.count[Velocity]() == 1 +} -- 2.39.5