From 6787724420b4a9dcb65becb303ee74f3d95fe35c Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 23:11:18 +0300 Subject: [PATCH] cgen: fix recursive anonymous functions and closures (fixes #16349) --- vlib/builtin/closure/closure.c.v | 13 +++++++++++++ vlib/v/gen/c/assign.v | 15 +++++++++++++++ vlib/v/markused/markused.v | 1 + .../tests/fns/recursive_closure_assignment_test.v | 13 +++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 vlib/v/tests/fns/recursive_closure_assignment_test.v diff --git a/vlib/builtin/closure/closure.c.v b/vlib/builtin/closure/closure.c.v index b17984fcd..ceb47d330 100644 --- a/vlib/builtin/closure/closure.c.v +++ b/vlib/builtin/closure/closure.c.v @@ -322,3 +322,16 @@ fn closure_create(func voidptr, data voidptr) voidptr { } return curr_closure } + +// closure_data returns the userdata pointer associated with a closure object. +@[direct_array_access] +fn closure_data(closure voidptr) voidptr { + unsafe { + mut p := &voidptr(&u8(closure) - assumed_page_size) + $if ppc64 { + return p[2] + } $else { + return p[0] + } + } +} diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 49f9d3b99..222980e25 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -7,6 +7,20 @@ import v.ast import v.util import v.token +fn (mut g Gen) gen_self_recursing_anon_fn_capture_patch(left ast.Expr, anon_fn ast.AnonFn) { + if left !is ast.Ident || anon_fn.inherited_vars.len == 0 { + return + } + ident := left as ast.Ident + if ident.name !in anon_fn.inherited_vars.map(it.name) { + return + } + concrete_types := g.get_anon_fn_concrete_types(anon_fn) + ctx_struct := g.closure_ctx(anon_fn.decl, concrete_types) + left_expr := g.expr_string(left) + g.writeln('((${ctx_struct}*)builtin__closure__closure_data(${left_expr}))->${c_name(ident.name)} = ${left_expr};') +} + fn (mut g Gen) expr_with_opt_or_block(expr ast.Expr, expr_typ ast.Type, var_expr ast.Expr, ret_typ ast.Type, in_heap bool) { gen_or := expr is ast.Ident && expr.or_expr.kind != .absent @@ -538,6 +552,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } g.expr(val) g.writeln(';') + g.gen_self_recursing_anon_fn_capture_patch(left, val) if blank_assign { g.write('}') } diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index 963f686db..1f7453c80 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -116,6 +116,7 @@ pub fn mark_used(mut table ast.Table, mut pref_ pref.Preferences, ast_files []&a core_fns << 'builtin.closure.closure_alloc' core_fns << 'builtin.closure.closure_init' core_fns << 'builtin.closure.closure_create' + core_fns << 'builtin.closure.closure_data' } if table.used_features.arr_map { include_panic_deps = true diff --git a/vlib/v/tests/fns/recursive_closure_assignment_test.v b/vlib/v/tests/fns/recursive_closure_assignment_test.v new file mode 100644 index 000000000..4b5f1108a --- /dev/null +++ b/vlib/v/tests/fns/recursive_closure_assignment_test.v @@ -0,0 +1,13 @@ +fn test_recursive_closure_assignment() { + one := 1 + mut fact := fn (n int) int { + return 1 + } + fact = fn [one, fact] (n int) int { + if n <= 1 { + return one + } + return n * fact(n - 1) + } + assert fact(5) == 120 +} -- 2.39.5