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:
@@ -219,6 +219,22 @@ pub enum Term {
|
||||
value: Box<Term>,
|
||||
body: Box<Term>,
|
||||
},
|
||||
/// Local recursive let-binding (Iter 16b.1). Always fn-shaped:
|
||||
/// the bound name `name` is recursively visible inside `body`.
|
||||
/// Eliminated by `crate::desugar` before typecheck — lifted to a
|
||||
/// synthetic top-level fn when `body` does not capture any name
|
||||
/// from the enclosing lexical scope. The on-disk schema gains the
|
||||
/// `"t": "letrec"` tag; pre-existing fixtures hash bit-identically
|
||||
/// because the variant is additive.
|
||||
LetRec {
|
||||
name: String,
|
||||
#[serde(rename = "type")]
|
||||
ty: Type,
|
||||
params: Vec<String>,
|
||||
body: Box<Term>,
|
||||
#[serde(rename = "in")]
|
||||
in_term: Box<Term>,
|
||||
},
|
||||
/// If-expression. Both branches must have the same type.
|
||||
If {
|
||||
cond: Box<Term>,
|
||||
|
||||
Reference in New Issue
Block a user