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:
2026-05-07 20:33:48 +02:00
parent ee5807d73c
commit 8860600e37
11 changed files with 930 additions and 38 deletions
+16
View File
@@ -245,6 +245,22 @@ fn write_term(out: &mut String, t: &Term, level: usize) {
write_term(out, body, level);
out.push(')');
}
Term::LetRec { name, ty, params, body, in_term } => {
out.push_str("(let-rec ");
out.push_str(name);
out.push_str(" (params");
for p in params {
out.push(' ');
out.push_str(p);
}
out.push_str(") (type ");
write_type(out, ty);
out.push_str(") (body ");
write_term(out, body, level);
out.push_str(") (in ");
write_term(out, in_term, level);
out.push_str("))");
}
Term::If { cond, then, else_ } => {
out.push_str("(if ");
write_term(out, cond, level);