From 5ac3852c944a7b2065ef231a3c628f89038b4491 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Thu, 16 Oct 2025 10:41:39 -0300 Subject: [PATCH] cgen, checker: fix $(field.name) access on closure fn (fix #25513) (#25514) --- cmd/tools/vast/vast.v | 1 + vlib/v/ast/ast.v | 1 + vlib/v/checker/fn.v | 2 ++ vlib/v/gen/c/assign.v | 2 +- vlib/v/gen/c/cgen.v | 4 +++- vlib/v/gen/c/fn.v | 10 ++++---- .../comptime_closure_field_access_test.v | 23 +++++++++++++++++++ 7 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 vlib/v/tests/comptime/comptime_closure_field_access_test.v diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index f74e32529..668c4fe36 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -653,6 +653,7 @@ fn (t Tree) anon_fn(node ast.AnonFn) &Node { obj.add_terse('decl', t.fn_decl(node.decl)) obj.add('inherited_vars', t.array_node_arg(node.inherited_vars)) obj.add_terse('typ', t.type_node(node.typ)) + obj.add('has_ct_var', t.bool_node(node.has_ct_var)) mut symbol_obj := create_object() for key, val in node.has_gen { symbol_obj.add_terse(key.str(), t.bool_node(val)) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 4afc895ea..711bea46b 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -572,6 +572,7 @@ pub struct AnonFn { pub mut: decl FnDecl inherited_vars []Param // note: closures have inherited_vars.len > 0 + has_ct_var bool // has $for var as inherited var typ Type // the type of anonymous fn. Both .typ and .decl.name are auto generated has_gen map[string]bool // a map of the names of all generic anon functions, generated from it } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index e0b7bae54..75a06a83b 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -690,6 +690,8 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { } else { parent_var.typ } + node.has_ct_var = node.has_ct_var + || var.name in [c.comptime.comptime_for_field_var, c.comptime.comptime_for_method_var] if parent_var.typ != ast.no_type { parent_var_sym := c.table.final_sym(ptyp) if parent_var_sym.info is ast.FnType { diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index d4a3860d3..900017d73 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -1226,7 +1226,7 @@ fn (mut g Gen) gen_cross_var_assign(node &ast.AssignStmt) { left_typ := node.left_types[i] left_sym := g.table.sym(left_typ) mut anon_ctx := '' - if g.anon_fn { + if g.anon_fn != unsafe { nil } { if obj := left.scope.find_var(left.name) { if obj.is_inherited { anon_ctx = '${closure_ctx}->' diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 451a84ec7..ada86dd0c 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -248,7 +248,7 @@ mut: expected_cast_type ast.Type // for match expr of sumtypes expected_arg_mut bool // generating a mutable fn parameter or_expr_return_type ast.Type // or { 0, 1 } return type - anon_fn bool + anon_fn &ast.AnonFn tests_inited bool has_main bool // main_fn_decl_node ast.FnDecl @@ -346,6 +346,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO table: table pref: pref_ fn_decl: unsafe { nil } + anon_fn: unsafe { nil } is_autofree: pref_.autofree indent: -1 module_built: module_built @@ -845,6 +846,7 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) &Gen { table: global_g.table pref: global_g.pref fn_decl: unsafe { nil } + anon_fn: unsafe { nil } indent: -1 module_built: global_g.module_built timers: util.new_timers( diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 19c391ad6..5dd2295d4 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -181,7 +181,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { } tmp_defer_vars := g.defer_vars // must be here because of workflow - if !g.anon_fn { + if g.anon_fn == unsafe { nil } { g.defer_vars = []string{} } else { if node.defer_stmts.len > 0 { @@ -582,8 +582,7 @@ fn (mut g Gen) c_fn_name(node &ast.FnDecl) string { name = name.replace_each(c_fn_name_escape_seq) } } - if node.is_anon && g.comptime.comptime_for_method_var != '' - && node.scope.is_inherited_var('method') { + if node.is_anon && g.anon_fn.has_ct_var { name = '${name}_${g.comptime.comptime_loop_id}' } if node.language == .c { @@ -621,8 +620,7 @@ fn (mut g Gen) gen_closure_fn_name(node ast.AnonFn) string { if node.decl.generic_names.len > 0 { fn_name = g.generic_fn_name(g.cur_concrete_types, fn_name) } - if node.inherited_vars.len > 0 && g.comptime.comptime_for_method_var != '' - && node.inherited_vars.any(it.name == 'method') { + if node.has_ct_var { fn_name += '_${g.comptime.comptime_loop_id}' } return fn_name @@ -742,7 +740,7 @@ fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { } pos := g.out.len was_anon_fn := g.anon_fn - g.anon_fn = true + g.anon_fn = node g.fn_decl(node.decl) g.anon_fn = was_anon_fn builder.write_string(g.out.cut_to(pos)) diff --git a/vlib/v/tests/comptime/comptime_closure_field_access_test.v b/vlib/v/tests/comptime/comptime_closure_field_access_test.v new file mode 100644 index 000000000..f48042631 --- /dev/null +++ b/vlib/v/tests/comptime/comptime_closure_field_access_test.v @@ -0,0 +1,23 @@ +struct TestObj { +mut: + a int + b int +} + +fn test_main() { + x := &TestObj{ + a: 1 + b: 1 + } + $for field in TestObj.fields { + run_fn(fn [x, field] () { + (*x).$(field.name) += 1 + }) + } + assert x.a == 2 + assert x.b == 2 +} + +fn run_fn(f fn ()) { + f() +} -- 2.39.5