closures with captures: env struct + global instance + body rewriting

- AST: captureCount + captureName0..7 + captureType0..7 in Expr
- Sema: Scope_LookupUpTo + closureScope for capture analysis
- HIR Lower: env struct generation, capture assignments in skLet,
  body rewriting (ekIdent -> hFieldAccess on env instance)
- C Backend: env struct def + global instance emission for closures
- Bootstrap: full capture support in sema, hir_lower, lir_lower, lir_c_backend
- Selfhost loop remains deterministic
This commit is contained in:
2026-06-09 21:23:05 +03:00
parent ba54aa240e
commit 9b473c667c
17 changed files with 5267 additions and 53 deletions
+61
View File
@@ -24,6 +24,9 @@ struct Sema {
movedName5: String;
movedName6: String;
movedName7: String;
closureDepth: int; // nesting depth inside closures
currentClosureExpr: *Expr; // current closure being analyzed (for capture tracking)
closureScope: *Scope; // scope at which the current closure was entered
}
struct SemaDiag {
@@ -337,6 +340,42 @@ func Sema_RemoveMoved(sema: *Sema, name: String) {
}
}
// ---------------------------------------------------------------------------
// Capture tracking for closures
// ---------------------------------------------------------------------------
func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
if closureExpr == null as *Expr { return; }
// Check if already captured
var i: int = 0;
while i < closureExpr.captureCount {
var capName: String = "";
if i == 0 { capName = closureExpr.captureName0; }
else if i == 1 { capName = closureExpr.captureName1; }
else if i == 2 { capName = closureExpr.captureName2; }
else if i == 3 { capName = closureExpr.captureName3; }
else if i == 4 { capName = closureExpr.captureName4; }
else if i == 5 { capName = closureExpr.captureName5; }
else if i == 6 { capName = closureExpr.captureName6; }
else if i == 7 { capName = closureExpr.captureName7; }
if String_Eq(capName, name) { return; }
i = i + 1;
}
// Add new capture (up to 8)
if closureExpr.captureCount < 8 {
let idx: int = closureExpr.captureCount;
if idx == 0 { closureExpr.captureName0 = name; closureExpr.captureType0 = typeKind; }
else if idx == 1 { closureExpr.captureName1 = name; closureExpr.captureType1 = typeKind; }
else if idx == 2 { closureExpr.captureName2 = name; closureExpr.captureType2 = typeKind; }
else if idx == 3 { closureExpr.captureName3 = name; closureExpr.captureType3 = typeKind; }
else if idx == 4 { closureExpr.captureName4 = name; closureExpr.captureType4 = typeKind; }
else if idx == 5 { closureExpr.captureName5 = name; closureExpr.captureType5 = typeKind; }
else if idx == 6 { closureExpr.captureName6 = name; closureExpr.captureType6 = typeKind; }
else if idx == 7 { closureExpr.captureName7 = name; closureExpr.captureType7 = typeKind; }
closureExpr.captureCount = closureExpr.captureCount + 1;
}
}
// ---------------------------------------------------------------------------
// Expression type checking
// ---------------------------------------------------------------------------
@@ -379,6 +418,16 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
te.typeName = sym.typeName;
expr.refType = te;
}
// Capture tracking: if inside closure and identifier is not local but from parent scope
if sema.closureDepth > 0 && sema.currentClosureExpr != null as *Expr && sema.closureScope != null as *Scope {
let localSym: Symbol = Scope_LookupUpTo(sema.scope, expr.strValue, sema.closureScope);
if localSym.kind == 0 && !String_Eq(localSym.name, expr.strValue) {
// Not local to closure scope — check if it's a variable from outer scope
if sym.kind == skVar {
Sema_AddCapture(sema.currentClosureExpr, expr.strValue, sym.typeKind);
}
}
}
return sym.typeKind;
}
@@ -568,9 +617,18 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if kind == ekClosure {
let savedRetType: int = sema.currentRetType;
let savedScope: *Scope = sema.scope;
let savedClosureDepth: int = sema.closureDepth;
let savedClosureExpr: *Expr = sema.currentClosureExpr;
let savedClosureScope: *Scope = sema.closureScope;
let childScope: Scope = Scope_NewChild(sema.scope);
sema.scope = &childScope;
// Set up closure tracking
sema.closureDepth = sema.closureDepth + 1;
sema.currentClosureExpr = expr;
sema.closureScope = &childScope;
expr.captureCount = 0;
// Set return type from annotation, or unknown for inference
var closureRetType: int = tyUnknown;
if expr.refType != null as *TypeExpr {
@@ -621,6 +679,9 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Restore
sema.scope = savedScope;
sema.currentRetType = savedRetType;
sema.closureDepth = savedClosureDepth;
sema.currentClosureExpr = savedClosureExpr;
sema.closureScope = savedClosureScope;
// Build function type expression for later use
let funcType: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;