From 8b4ff32f4813e10705fe2932080076eef6e79ea5 Mon Sep 17 00:00:00 2001 From: Richard Wheeler Date: Mon, 23 Feb 2026 12:21:50 -0500 Subject: [PATCH] parser: fix anonymous function name collisions across files (#26642) --- vlib/v/ast/str.v | 5 +++-- vlib/v/checker/lambda_expr.v | 2 +- .../tests/modules/anon_fn_name_across_files.out | 2 ++ .../tests/modules/anon_fn_name_across_files/a.v | 11 +++++++++++ .../tests/modules/anon_fn_name_across_files/b.v | 11 +++++++++++ .../tests/modules/anon_fn_name_across_files/main.v | 8 ++++++++ vlib/v/parser/fn.v | 2 +- 7 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 vlib/v/checker/tests/modules/anon_fn_name_across_files.out create mode 100644 vlib/v/checker/tests/modules/anon_fn_name_across_files/a.v create mode 100644 vlib/v/checker/tests/modules/anon_fn_name_across_files/b.v create mode 100644 vlib/v/checker/tests/modules/anon_fn_name_across_files/main.v diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 4ffd9651c..bc979d25c 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -4,6 +4,7 @@ @[has_globals] module ast +import v.token import v.util import strings import sync.stdatomic @@ -18,8 +19,8 @@ pub fn (f &FnDecl) get_name() string { } // get_anon_fn_name returns the unique anonymous function name, based on the prefix, the func signature and its position in the source code -pub fn (table &Table) get_anon_fn_name(prefix string, func &Fn, pos int) string { - return 'anon_fn_${prefix}_${table.fn_type_signature(func)}_${pos}' +pub fn (table &Table) get_anon_fn_name(prefix string, func &Fn, pos token.Pos) string { + return 'anon_fn_${prefix}_${pos.file_idx}_${table.fn_type_signature(func)}_${pos.pos}' } // get_name returns the real name for the function calling diff --git a/vlib/v/checker/lambda_expr.v b/vlib/v/checker/lambda_expr.v index 58eb46639..7e4eb5a44 100644 --- a/vlib/v/checker/lambda_expr.v +++ b/vlib/v/checker/lambda_expr.v @@ -83,7 +83,7 @@ pub fn (mut c Checker) lambda_expr(mut node ast.LambdaExpr, exp_typ ast.Type) as return_type: return_type is_method: false } - name := c.table.get_anon_fn_name(c.file.unique_prefix, func, node.pos.pos) + name := c.table.get_anon_fn_name(c.file.unique_prefix, func, node.pos) func.name = name idx := c.table.find_or_register_fn_type(func, true, false) typ := ast.new_type(idx) diff --git a/vlib/v/checker/tests/modules/anon_fn_name_across_files.out b/vlib/v/checker/tests/modules/anon_fn_name_across_files.out new file mode 100644 index 000000000..1191247b6 --- /dev/null +++ b/vlib/v/checker/tests/modules/anon_fn_name_across_files.out @@ -0,0 +1,2 @@ +1 +2 diff --git a/vlib/v/checker/tests/modules/anon_fn_name_across_files/a.v b/vlib/v/checker/tests/modules/anon_fn_name_across_files/a.v new file mode 100644 index 000000000..48aaa398f --- /dev/null +++ b/vlib/v/checker/tests/modules/anon_fn_name_across_files/a.v @@ -0,0 +1,11 @@ +module main + +// Both a.v and b.v define an anonymous function with the same signature (fn() int) +// at the same byte offset from the start of their respective files. +// Before the fix in get_anon_fn_name(), both would produce the same C symbol name, +// causing a linker collision or silent wrong behaviour. +fn get_fn_a() fn () int { + return fn () int { + return 1 + } +} diff --git a/vlib/v/checker/tests/modules/anon_fn_name_across_files/b.v b/vlib/v/checker/tests/modules/anon_fn_name_across_files/b.v new file mode 100644 index 000000000..6751ddedf --- /dev/null +++ b/vlib/v/checker/tests/modules/anon_fn_name_across_files/b.v @@ -0,0 +1,11 @@ +module main + +// This file intentionally pads its content so that the anonymous function below +// starts at the same byte offset as the one in a.v. Before the fix in +// get_anon_fn_name() (which now includes pos.file_idx in the C symbol name), +// both would produce the same name and trigger a linker error. +fn get_fn_b() fn () int { + return fn () int { + return 2 + } +} diff --git a/vlib/v/checker/tests/modules/anon_fn_name_across_files/main.v b/vlib/v/checker/tests/modules/anon_fn_name_across_files/main.v new file mode 100644 index 000000000..caf3135db --- /dev/null +++ b/vlib/v/checker/tests/modules/anon_fn_name_across_files/main.v @@ -0,0 +1,8 @@ +module main + +fn main() { + fa := get_fn_a() + fb := get_fn_b() + println(fa()) + println(fb()) +} diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 2c7d98811..55bc44a0d 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -1218,7 +1218,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn { return_type: return_type is_method: false } - name := p.table.get_anon_fn_name(p.unique_prefix, func, p.tok.pos) + name := p.table.get_anon_fn_name(p.unique_prefix, func, p.tok.pos()) keep_fn_name := p.cur_fn_name p.cur_fn_name = name if p.tok.kind == .lcbr { -- 2.39.5