Iter 16b.6: LetRec inside polymorphic enclosing fn

Lifts the monomorphic-only restriction. Lifted top-level fn now
becomes Forall(α..., Fn(t..., T...) -> tr) when the enclosing fn
is polymorphic; capture types may mention outer type vars and
the existing mono pipeline (Iter 12b/14a) specializes them
correctly at call sites.

- desugar.rs / lift.rs: scope-building unified — Forall fn-params
  are now KnownType, not LetBound. Lift wraps augmented_ty in
  Forall mirroring the enclosing fn.
- Name-as-value-in-in-term in a polymorphic enclosing fn is
  rejected (eta-Lam wrap from 16b.5 has no Forall). Queued
  separately as closure-conversion-with-polymorphism.
- examples/poly_rec_capture.{ailx,ail.json}: apply_n_times :
  Forall(a). drives at Int and Bool to exercise two mono
  instantiations.
- e2e + check + desugar tests: 116 → 120 (+4).

Codegen pipeline untouched — apply_subst_to_type substitutes
through Fn.params uniformly (original params + appended captures).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 22:39:28 +02:00
parent ca507c9f52
commit 76d61aeced
7 changed files with 772 additions and 28 deletions
+28
View File
@@ -537,6 +537,34 @@ fn local_rec_as_value_capture_demo() {
assert_eq!(lines, vec!["1320", "12120"]);
}
/// Iter 16b.6: LetRec inside a polymorphic enclosing fn.
/// Property protected: when the enclosing fn is `Forall(a). Fn(...)`,
/// the desugar/lift pipeline now (a) enters fn-params as `KnownType`
/// (not `LetBound`) so a LetRec inside CAN capture them, and (b)
/// builds the synthetic lifted fn's signature as
/// `Forall(a). Fn(t1..tk, T1..Tm) -> tr`, mirroring the enclosing
/// fn's type vars. Capture types `T1..Tm` may mention `a`; the
/// outer Forall binds them. Codegen's mono pipeline (Iter 12b/14a)
/// then specialises `loop$lr_0` at every call with the same type
/// args as the enclosing `apply_n_times`.
///
/// `main` drives `apply_n_times` at TWO distinct instantiations
/// (`Int` with `succ`, `Bool` with `flip`) so the mono path is
/// exercised twice, producing both `loop$lr_0__I` and
/// `loop$lr_0__B` in the IR. Without 16b.6 the desugar pass would
/// have rejected the LetRec capture as a `LetBound`-style failure
/// (the 16b.2-era `Type::Forall` fallback marked all fn-params as
/// `LetBound`).
///
/// Expected stdout: 5 (succ applied 5 times to 0), false (flip
/// applied 4 times to false — even count of flips ⇒ false).
#[test]
fn poly_rec_capture_demo() {
let stdout = build_and_run("poly_rec_capture.ail.json");
let lines: Vec<&str> = stdout.lines().collect();
assert_eq!(lines, vec!["5", "false"]);
}
/// 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.