From d2e4e24f41c0cb82283759ad39299e633cd613a1 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Thu, 12 Feb 2026 21:58:27 +0800 Subject: [PATCH 1/2] checker: fix generic type resolution in struct init with T is Type (fix #24471) --- vlib/v/checker/checker.v | 16 ++-- ...eneric_struct_init_with_comptime_if_test.v | 74 +++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 vlib/v/tests/generics/generic_struct_init_with_comptime_if_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 52bfcf68955742..535571934ca2db 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3210,12 +3210,10 @@ fn (mut c Checker) stmts_ending_with_expression(mut stmts []ast.Stmt, expected_o fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { if typ.has_flag(.generic) { - if c.inside_generic_struct_init { - generic_names := c.cur_struct_generic_types.map(c.table.sym(it).name) - if t_typ := c.table.convert_generic_type(typ, generic_names, c.cur_struct_concrete_types) { - return t_typ - } - } + // Check function-level generic parameters first (more outer scope), + // then struct init context. This prevents function-level generic + // parameters from being incorrectly resolved using struct init + // concrete types when both use the same name (e.g., T). if c.table.cur_fn != unsafe { nil } { if t_typ := c.table.convert_generic_type(typ, c.table.cur_fn.generic_names, c.table.cur_concrete_types) @@ -3230,6 +3228,12 @@ fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { } } } + if c.inside_generic_struct_init { + generic_names := c.cur_struct_generic_types.map(c.table.sym(it).name) + if t_typ := c.table.convert_generic_type(typ, generic_names, c.cur_struct_concrete_types) { + return t_typ + } + } } return typ } diff --git a/vlib/v/tests/generics/generic_struct_init_with_comptime_if_test.v b/vlib/v/tests/generics/generic_struct_init_with_comptime_if_test.v new file mode 100644 index 00000000000000..8ec935cd634ced --- /dev/null +++ b/vlib/v/tests/generics/generic_struct_init_with_comptime_if_test.v @@ -0,0 +1,74 @@ +// Test for issue #24471 +// When returning a generic struct from a generic method with $if T is Type, +// the compiler should correctly resolve the function's generic parameter T +// instead of incorrectly using the struct init's concrete types. + +module main + +struct Type1 { + item f32 +} + +struct Type2 { + item int +} + +struct GenericThing[T] { + part1 T + part2 T +} + +struct AnotherGenericThing[T] { + part3 T +} + +// This used to fail with: `int` has no property `item` +// The bug was that inside `AnotherGenericThing[int]{...}`, the compiler +// incorrectly resolved T (the function's generic param) to `int` instead +// of keeping it as the receiver's actual type. +pub fn (thing GenericThing[T]) weird() AnotherGenericThing[int] { + $if T is Type2 { + return AnotherGenericThing[int]{thing.part1.item + thing.part2.item} + } $else $if T is Type1 { + return AnotherGenericThing[int]{int(thing.part1.item + thing.part2.item)} + } $else { + $compile_error('unrecognised type') + } +} + +fn test_generic_struct_init_with_comptime_if() { + // Test with Type1 (f32 fields) + thing1 := GenericThing{Type1{1.5}, Type1{2.5}} + result1 := thing1.weird() + assert result1.part3 == 4 + + // Test with Type2 (int fields) + thing2 := GenericThing{Type2{1}, Type2{2}} + result2 := thing2.weird() + assert result2.part3 == 3 +} + +// Additional test case: generic struct return with different type parameter +struct GenericResult[T] { + val T +} + +fn (thing GenericThing[T]) to_result() GenericResult[int] { + $if T is Type2 { + return GenericResult[int]{thing.part1.item + thing.part2.item} + } $else $if T is Type1 { + return GenericResult[int]{int(thing.part1.item + thing.part2.item)} + } $else { + return GenericResult[int]{0} + } +} + +fn test_generic_result_with_comptime_if() { + g1 := GenericThing{Type1{3.0}, Type1{4.0}} + r1 := g1.to_result() + assert r1.val == 7 + + g2 := GenericThing{Type2{5}, Type2{6}} + r2 := g2.to_result() + assert r2.val == 11 +} From 426b53e2c8db189a12c7d19506e288e7c2066ab9 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Thu, 12 Feb 2026 21:58:27 +0800 Subject: [PATCH 2/2] checker: fix generic type resolution in struct init with T is Type (fix #24471) --- vlib/v/checker/checker.v | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 535571934ca2db..217101cef430bf 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3210,11 +3210,24 @@ fn (mut c Checker) stmts_ending_with_expression(mut stmts []ast.Stmt, expected_o fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { if typ.has_flag(.generic) { - // Check function-level generic parameters first (more outer scope), - // then struct init context. This prevents function-level generic - // parameters from being incorrectly resolved using struct init - // concrete types when both use the same name (e.g., T). - if c.table.cur_fn != unsafe { nil } { + // Get the type's symbol name to check for potential conflicts + sym := c.table.sym(typ) + type_name := sym.name + + // Check if there's a conflict between function-level generic and struct init generic + // (both have the same generic name, e.g., 'T') + mut has_conflict := false + if c.inside_generic_struct_init && c.table.cur_fn != unsafe { nil } { + struct_generic_names := c.cur_struct_generic_types.map(c.table.sym(it).name) + if type_name in c.table.cur_fn.generic_names && type_name in struct_generic_names { + has_conflict = true + } + } + + // If there's a conflict, prioritize function-level generic parameters (more outer scope) + // This prevents function-level generic parameters from being incorrectly resolved + // using struct init concrete types when both use the same name (e.g., T). + if has_conflict { if t_typ := c.table.convert_generic_type(typ, c.table.cur_fn.generic_names, c.table.cur_concrete_types) { @@ -3228,12 +3241,28 @@ fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { } } } + + // Original order: struct init first, then function-level if c.inside_generic_struct_init { generic_names := c.cur_struct_generic_types.map(c.table.sym(it).name) if t_typ := c.table.convert_generic_type(typ, generic_names, c.cur_struct_concrete_types) { return t_typ } } + if c.table.cur_fn != unsafe { nil } { + if t_typ := c.table.convert_generic_type(typ, c.table.cur_fn.generic_names, + c.table.cur_concrete_types) + { + return t_typ + } + if c.inside_lambda && c.table.cur_lambda.call_ctx != unsafe { nil } { + if t_typ := c.table.convert_generic_type(typ, c.table.cur_lambda.func.decl.generic_names, + c.table.cur_lambda.call_ctx.concrete_types) + { + return t_typ + } + } + } } return typ }