feat: capture-less anonymous functions (closures)

Implement MVP closures — anonymous functions without captures.

Syntax:
  |a: int, b: int| -> int { return a + b; }

Changes:
- ast.bux + ast.nim: add ekClosure AST node
- parser.bux + parser.nim: parse |params| -> Ret { body }
- sema.bux + sema.nim: type-check closure params/body, return tyFunc
- hir_lower.bux + hir_lower.nim: generate __closure_N function + hAddrOf
- lir_c_backend.nim: fix function-pointer variable declaration (cParamDecl)
- C backend: closures compile to global functions with unique names

Test: _test_closure/src/Main.bux
- Closure as variable
- Closure passed to higher-order function
- Address of named function as function pointer

Both bootstrap and selfhost compilers build and pass the test.
This commit is contained in:
2026-06-09 20:24:10 +03:00
parent 34504d1647
commit c83f6d5994
16 changed files with 2290 additions and 8 deletions
+30
View File
@@ -276,6 +276,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode
proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if expr == nil: return makeUnknown()
@@ -1139,6 +1140,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
resultNode = hirCall("String_Concat", @[resultNode, lastTextNode], makeStr(), loc)
return resultNode
of ekClosure:
let f = ctx.lowerClosureFunc(expr)
return hirUnary(tkAmp, hirVar(f.name, makeFunc(@[], makeVoid()), loc), typ, loc)
else:
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
typ: makeVoid(), loc: loc)
@@ -1167,6 +1172,12 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
of tekSlice:
let elemType = ctx.resolveTypeExpr(stmt.stmtLetType.sliceElement)
makeSlice(elemType)
of tekFunc:
var params: seq[Type] = @[]
for p in stmt.stmtLetType.funcParams:
params.add(ctx.resolveTypeExpr(p))
let ret = if stmt.stmtLetType.funcRet != nil: ctx.resolveTypeExpr(stmt.stmtLetType.funcRet) else: makeVoid()
makeFunc(params, ret)
else: makeUnknown()
elif stmt.stmtLetInit != nil:
ctx.resolveExprType(stmt.stmtLetInit)
@@ -1475,6 +1486,25 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs:
ctx.generatedFuncInsts[mangledName] = true
return mangledName
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
let loc = expr.loc
let name = "__closure_" & $ctx.varCounter
inc ctx.varCounter
var f = HirFunc(name: name, isPublic: false)
# Params
for p in expr.exprClosureParams:
f.params.add((name: p.name, typ: if p.ptype != nil: ctx.resolveTypeExpr(p.ptype) else: makeUnknown()))
# Return type
if expr.exprClosureReturnType != nil:
f.retType = ctx.resolveTypeExpr(expr.exprClosureReturnType)
else:
f.retType = makeVoid()
# Body
if expr.exprClosureBody != nil:
f.body = ctx.lowerBlock(expr.exprClosureBody)
ctx.extraFuncs.add(f)
return f
proc lowerModule*(module: Module, sema: Sema): HirModule =
var ctx = initLowerCtx(module, sema)
var funcs: seq[HirFunc] = @[]