From 72744361257bf1b954befe9cd83c92363599f37e Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 10 May 2026 07:58:14 +0300 Subject: [PATCH] cgen: fix generic sort comparator reusing wrong body across instantiations (fixes #27121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `.sort(a < b)` lives in a generic body and the generic is instantiated for multiple types in the same program (e.g. `arrays.distinct` on both `[]int` and `[]string`), the shared `InfixExpr.left_type` AST field was overwritten by the checker for the last instantiation, so cgen used that poisoned type for every comparator body — producing e.g. `compare_0_int` calling `builtin__string__lt` on `int*`. --- vlib/v/gen/c/array.v | 12 +++++++- .../generic_sort_multi_instantiation_test.v | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 vlib/v/tests/generics/generic_sort_multi_instantiation_test.v diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index c6a6749fa..86cf653b8 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -1201,7 +1201,17 @@ fn (mut g Gen) gen_array_sort(node ast.CallExpr) { g.gen_anon_fn_decl(mut lambda_node.func) } else { infix_expr := node.args[0].expr as ast.InfixExpr - comparison_type = g.unwrap(infix_expr.left_type.set_nr_muls(0)) + // For plain `a < b` / `b < a`, the comparison_type is the element type. + // Avoid using `infix_expr.left_type` here because the AST node is shared + // across generic instantiations and may have been mutated by a later + // checker pass for a different concrete type. See vlang/v#27121. + comparison_left_type := if infix_expr.left is ast.Ident + && (infix_expr.left.name == 'a' || infix_expr.left.name == 'b') { + elem_type + } else { + infix_expr.left_type + } + comparison_type = g.unwrap(comparison_left_type.set_nr_muls(0)) left_name := infix_expr.left.str() if left_name.len > 1 { compare_fn += '_by' + diff --git a/vlib/v/tests/generics/generic_sort_multi_instantiation_test.v b/vlib/v/tests/generics/generic_sort_multi_instantiation_test.v new file mode 100644 index 000000000..ac6b5471b --- /dev/null +++ b/vlib/v/tests/generics/generic_sort_multi_instantiation_test.v @@ -0,0 +1,28 @@ +// Regression test for vlang/v#27121: a generic sort comparator was reusing +// the body of an earlier instantiation, so calling `arrays.distinct` (or any +// generic that uses `.sort(a < b)` internally) on `[]int` and `[]string` in +// the same program produced a comparator that compared `int*` values via +// `builtin__string__lt`, failing C compilation. +import arrays + +fn sort_helper[T](a []T) []T { + mut x := a.clone() + x.sort(a < b) + return x +} + +fn test_distinct_int_then_string() { + assert arrays.distinct([3, 1, 2, 1, 3]) == [1, 2, 3] + assert arrays.distinct(['c', 'a', 'b', 'a', 'c']) == ['a', 'b', 'c'] +} + +fn test_distinct_string_then_int() { + assert arrays.distinct(['c', 'a', 'b', 'a', 'c']) == ['a', 'b', 'c'] + assert arrays.distinct([3, 1, 2, 1, 3]) == [1, 2, 3] +} + +fn test_generic_sort_helper_reuse() { + assert sort_helper([3, 1, 2]) == [1, 2, 3] + assert sort_helper(['c', 'a', 'b']) == ['a', 'b', 'c'] + assert sort_helper([3.0, 1.0, 2.0]) == [1.0, 2.0, 3.0] +} -- 2.39.5