Iter 16b.3: post-typecheck lift for LetRec with Let-bound captures
Adds path-2 from the 16b.2 planning entry. Desugar now defers
LetRecs whose captures include Term::Let-bound names; a new
ailang-check::lift_letrecs pass runs after typecheck and uses the
elaborated env to resolve capture types. ailang-check learns a real
Term::LetRec typing rule in synth and verify_tail_positions.
- desugar: Term::LetRec arm gains a defer-arm; helpers promoted to
pub for reuse by the lift pass; find_non_callee_use moved before
classification.
- ailang-check::synth/verify_tail_positions: real LetRec rules
(effect-subset, locals install, recursive name in body+in_term).
- ailang-check::lift.rs (new, 720 LOC): post-typecheck lift with
post-order traversal, env-walk for capture-type resolution,
fast-path skip when no LetRec is present.
- ail::main.rs: build path now does load → check → desugar →
lift_letrecs → codegen.
- examples/local_rec_let_capture.{ailx,ail.json}: new fixture
capturing a let-bound `threshold` in a recursive helper.
- e2e + check unit tests: 106 → 110 (+4).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -467,6 +467,24 @@ fn local_rec_capture_demo() {
|
||||
assert_eq!(lines, vec!["0", "10", "45"]);
|
||||
}
|
||||
|
||||
/// Iter 16b.3: LetRec capture of a `Term::Let`-bound name. Property
|
||||
/// protected: the desugar pass leaves a LetRec whose only outside-
|
||||
/// scope captures are Let-bound (type unknown until typecheck) in
|
||||
/// place; the post-typecheck `lift_letrecs` pass in `ailang-check`
|
||||
/// resolves capture types from the typechecker's env, lifts to a
|
||||
/// synthetic top-level fn (`loop$lr_0(i, n, threshold)`), and
|
||||
/// rewrites every call site. Without 16b.3, the 16b.2-era panic
|
||||
/// ("16b.3 — let-binding captures") would fire. The threshold here
|
||||
/// is `(app + 5 5)` so the lift exercises the type-synthesis path
|
||||
/// (not just a literal). count_below(0)=0, count_below(5)=5,
|
||||
/// count_below(15)=9 (i in 1..9 are below threshold 10).
|
||||
#[test]
|
||||
fn local_rec_let_capture_demo() {
|
||||
let stdout = build_and_run("local_rec_let_capture.ail.json");
|
||||
let lines: Vec<&str> = stdout.lines().collect();
|
||||
assert_eq!(lines, vec!["0", "5", "9"]);
|
||||
}
|
||||
|
||||
/// Guards `ail diff`: a modified body changes the hash of `sum`, while
|
||||
/// `main` stays unchanged. Expects exit code 1, `changed` contains exactly
|
||||
/// `sum`, `unchanged` contains `main`, `added`/`removed` empty.
|
||||
|
||||
Reference in New Issue
Block a user