From 2efab47ee393d97eb0faf8d75e7f11796310aad6 Mon Sep 17 00:00:00 2001 From: Kim Shrier Date: Tue, 12 May 2026 05:47:10 -0600 Subject: [PATCH] ast: fix and test for list of generic bug (#27141) --- vlib/v/ast/table.v | 12 +++- ...eric_method_slice_two_param_sibling_test.v | 71 +++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 vlib/v/tests/generics/generic_method_slice_two_param_sibling_test.v diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index ccebd507b..ef6f94952 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -3775,7 +3775,14 @@ fn (mut t Table) unwrap_generic_type_ex_with_depth(typ Type, generic_names []str // If concrete types still contain generic parameters (e.g. Vec3[U] where U // is unresolved), don't create a partially-resolved struct entry. The struct // will be properly instantiated when all type parameters are known. - if final_concrete_types.any(it.has_flag(.generic)) { + // + // Structural check via generic_type_names catches entries like `[]T` + // whose `.generic` Type-flag has been cleared but whose type structure + // still references an unresolved generic parameter. Without this check, + // such entries reach register_sym below and leak into codegen as + // `Array_T` / placeholder-typed struct fields. + if final_concrete_types.any(it.has_flag(.generic)) + || final_concrete_types.any(t.generic_type_names(it).len > 0) { return typ } } @@ -4532,7 +4539,8 @@ pub fn (mut t Table) generic_insts_to_concrete() { } if sym.info is Struct { if sym.info.concrete_types.len > 0 && sym.info.parent_type.has_flag(.generic) - && !sym.info.concrete_types.any(it.has_flag(.generic)) { + && !sym.info.concrete_types.any(it.has_flag(.generic)) + && !sym.info.concrete_types.any(t.generic_type_names(it).len > 0) { parent_sym := t.sym(sym.info.parent_type) for method in parent_sym.methods { if method.generic_names.len == sym.info.concrete_types.len diff --git a/vlib/v/tests/generics/generic_method_slice_two_param_sibling_test.v b/vlib/v/tests/generics/generic_method_slice_two_param_sibling_test.v new file mode 100644 index 000000000..c66ab025a --- /dev/null +++ b/vlib/v/tests/generics/generic_method_slice_two_param_sibling_test.v @@ -0,0 +1,71 @@ +module main + +struct Pair[A, B] { + a A + b B +} + +struct Container[T] { + value T +} + +pub fn (c Container[T]) get() !T { + return c.value +} + +fn make_pair[A, B](a A, b B) Container[Pair[A, B]] { + return Container[Pair[A, B]]{ + value: Pair[A, B]{ + a: a + b: b + } + } +} + +fn make_list[T](items []T) Container[[]T] { + return Container[[]T]{ + value: items + } +} + +fn test_pair_container_get_returns_pair() { + c := make_pair[int, int](7, 11) + p := c.get() or { + assert false, 'get() on Container[Pair[int, int]] errored: ${err}' + return + } + assert p.a == 7, 'expected p.a == 7, got ${p.a}' + assert p.b == 11, 'expected p.b == 11, got ${p.b}' +} + +fn test_list_container_get_returns_slice() { + c := make_list[int]([1, 2, 3]) + lst := c.get() or { + assert false, 'get() on Container[[]int] errored: ${err}' + return + } + assert lst.len == 3, 'expected length 3, got ${lst.len}' + assert lst[0] == 1 + assert lst[1] == 2 + assert lst[2] == 3 +} + +fn test_sibling_factories_return_independent_shapes() { + pc := make_pair[string, int]('hello', 42) + lc := make_list[string](['a', 'b']) + + p := pc.get() or { + assert false, 'pair get failed: ${err}' + return + } + assert p.a == 'hello' + assert p.b == 42 + + lst := lc.get() or { + assert false, 'list get failed: ${err}' + return + } + assert lst.len == 2 + assert lst[0] == 'a' + assert lst[1] == 'b' +} -- 2.39.5