Iter 16b.1 — local recursive let (no-capture)
Add `(let-rec NAME (params ...) (type ...) (body ...) (in ...))` as a form-A surface and a `Term::LetRec` AST variant. The 16a desugar pass lifts each LetRec whose body has no captures from the enclosing scope to a synthetic top-level fn `<hint>$lr_N` and substitutes the original name; typecheck and codegen never see LetRec. Capture detection panics at desugar time, queued for 16b.2. Tests: 95 → 99 (+1 e2e local_rec_factorial_demo, +2 desugar unit, +1 parse unit). The new fixture `examples/local_rec_demo` runs `fact` at n=1, 3, 5 → prints 1, 6, 120. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -931,6 +931,28 @@ fn walk_term(
|
||||
walk_term(lhs, out, builtins, scope);
|
||||
walk_term(rhs, out, builtins, scope);
|
||||
}
|
||||
Term::LetRec { name, params, body, in_term, .. } => {
|
||||
// Iter 16b.1: `deps` walks the on-disk module before the
|
||||
// desugar pass, so a `Term::LetRec` is reachable here.
|
||||
// Treat it like a fn def for dependency purposes:
|
||||
// `name` shadows for both body and in_term; params shadow
|
||||
// inside the body.
|
||||
let inserted_name = scope.insert(name.clone());
|
||||
let mut newly = Vec::new();
|
||||
for p in params {
|
||||
if scope.insert(p.clone()) {
|
||||
newly.push(p.clone());
|
||||
}
|
||||
}
|
||||
walk_term(body, out, builtins, scope);
|
||||
for p in newly {
|
||||
scope.remove(&p);
|
||||
}
|
||||
walk_term(in_term, out, builtins, scope);
|
||||
if inserted_name {
|
||||
scope.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user