From d2e291e5b83a4d00e5f6d1286b39cf92063e6dc5 Mon Sep 17 00:00:00 2001 From: Felix Ehlers Date: Mon, 29 Dec 2025 06:19:37 +0100 Subject: [PATCH] checker,table,cgen: fix generic interface confusion (fix #25478) (#26181) --- vlib/v/ast/table.v | 7 +- vlib/v/checker/interface.v | 10 ++- vlib/v/gen/c/fn.v | 15 ++++- ...generic_interface_multi_type_params_test.v | 64 +++++++++++++++++++ vlib/x/sessions/store.v | 4 +- 5 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 vlib/v/tests/generics/generic_interface_multi_type_params_test.v diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index afa9120bb..d8915c50c 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -2327,7 +2327,7 @@ pub fn (mut t Table) unwrap_generic_type_ex(typ Type, generic_names []string, co } } } - mut all_methods := unsafe { ts.methods } + mut all_methods := ts.methods.clone() for imethod in imethods { for mut method in all_methods { if imethod.name == method.name { @@ -2353,6 +2353,9 @@ pub fn (mut t Table) unwrap_generic_type_ex(typ Type, generic_names []string, co for method in all_methods { ts_copy.register_method(method) } + if final_concrete_types.len > 0 { + t.unwrap_method_types(ts, generic_names, concrete_types, final_concrete_types) + } return new_type(new_idx).derive(typ).clear_flag(.generic) } else {} @@ -2485,7 +2488,7 @@ pub fn (mut t Table) generic_insts_to_concrete() { } sym.register_method(method) } - mut all_methods := unsafe { parent.methods } + mut all_methods := parent.methods.clone() for imethod in imethods { for mut method in all_methods { if imethod.name == method.name { diff --git a/vlib/v/checker/interface.v b/vlib/v/checker/interface.v index cbbdec8f3..8a42c1da2 100644 --- a/vlib/v/checker/interface.v +++ b/vlib/v/checker/interface.v @@ -382,8 +382,14 @@ fn (mut c Checker) unwrap_generic_interface(typ ast.Type, interface_type ast.Typ c.table.fn_generic_types[im_fkey] << inferred_types } } - inter_sym.info.concrete_types = inferred_types - return c.table.unwrap_generic_type(interface_type, generic_names, inter_sym.info.concrete_types) + result_type := c.table.unwrap_generic_type(interface_type, generic_names, + inferred_types) + // Set concrete types on the instantiated interface symbol + mut result_sym := c.table.sym(result_type) + if mut result_sym.info is ast.Interface { + result_sym.info.concrete_types = inferred_types + } + return result_type } } return interface_type diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 4e22fcd41..2de17fea4 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1529,8 +1529,21 @@ fn (mut g Gen) unwrap_receiver_type(node ast.CallExpr) (ast.Type, &ast.TypeSymbo generic_names := sym.info.generic_types.map(g.table.sym(it).name) // see comment at top of vlib/v/gen/c/utils.v mut muttable := unsafe { &ast.Table(g.table) } + // If receiver_type is the generic parent but left_type is an instantiated version, + // use left_type's concrete_types for the conversion + mut concrete_types := sym.info.concrete_types.clone() + if concrete_types.len == 0 && generic_names.len > 0 { + left_sym := g.table.sym(left_type) + if left_sym.info is ast.Interface { + concrete_types = left_sym.info.concrete_types.clone() + } else if left_sym.info is ast.Struct { + concrete_types = left_sym.info.concrete_types.clone() + } else if left_sym.info is ast.SumType { + concrete_types = left_sym.info.concrete_types.clone() + } + } if utyp := muttable.convert_generic_type(node.receiver_type, generic_names, - sym.info.concrete_types) + concrete_types) { unwrapped_rec_type = utyp } diff --git a/vlib/v/tests/generics/generic_interface_multi_type_params_test.v b/vlib/v/tests/generics/generic_interface_multi_type_params_test.v new file mode 100644 index 000000000..a71a5424d --- /dev/null +++ b/vlib/v/tests/generics/generic_interface_multi_type_params_test.v @@ -0,0 +1,64 @@ +// Test for issue #25478 +// Using the same generic interface with different type parameters in a function +// should correctly resolve the return types of interface methods. + +interface Bug[T] { + buggy() T +} + +struct StringBug implements Bug[string] { + value string +} + +fn (s StringBug) buggy() string { + return s.value +} + +struct StringArrayBug implements Bug[[]string] { + values []string +} + +fn (s StringArrayBug) buggy() []string { + return s.values +} + +fn list(items Bug[[]string], selected Bug[string], generator fn (arg string, selected bool) string) []string { + value := items.buggy() // should be []string + children := value.map(generator(it, it == selected.buggy())) + return children +} + +fn test_generic_interface_with_different_type_params() { + items := StringArrayBug{ + values: ['Hello', 'Hola'] + } + selected := StringBug{ + value: 'Hola' + } + result := list(items, selected, fn (arg string, is_selected bool) string { + if is_selected { + return '--${arg}' + } + return arg + }) + assert result.len == 2 + assert result[0] == 'Hello' + assert result[1] == '--Hola' +} + +fn test_generic_interface_method_return_types() { + items := StringArrayBug{ + values: ['a', 'b', 'c'] + } + selected := StringBug{ + value: 'b' + } + + // Verify return types are correctly resolved + arr := items.buggy() + assert arr.len == 3 + assert arr[0] == 'a' + + str := selected.buggy() + assert str == 'b' +} diff --git a/vlib/x/sessions/store.v b/vlib/x/sessions/store.v index 76b8ff264..b4614cc68 100644 --- a/vlib/x/sessions/store.v +++ b/vlib/x/sessions/store.v @@ -13,9 +13,9 @@ mut: } // get data from all sessions, optional to implement. -pub fn (mut s Store) all[T]() ![]T { +pub fn (mut s Store[T]) all[T]() ![]T { return []T{} } // clear all session data, optional to implement. -pub fn (mut s Store) clear[T]() ! {} +pub fn (mut s Store[T]) clear[T]() ! {} -- 2.39.5