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
+1
View File
@@ -0,0 +1 @@
{"defs":[{"body":{"args":[{"name":"x","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"+","t":"var"},"t":"app"},"doc":"Helper: increment Int. Used as the Fn(Int) -> Int instance.","kind":"fn","name":"succ","params":["x"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"}],"ret":{"k":"con","name":"Int"}}},{"body":{"args":[{"name":"b","t":"var"}],"fn":{"name":"not","t":"var"},"t":"app"},"doc":"Helper: boolean negation as a top-level fn (so it has an adapter for value-position).","kind":"fn","name":"flip","params":["b"],"type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Bool"}],"ret":{"k":"con","name":"Bool"}}},{"body":{"body":{"cond":{"args":[{"name":"k","t":"var"},{"lit":{"kind":"int","value":0},"t":"lit"}],"fn":{"name":"==","t":"var"},"t":"app"},"else":{"args":[{"args":[{"name":"k","t":"var"},{"lit":{"kind":"int","value":1},"t":"lit"}],"fn":{"name":"-","t":"var"},"t":"app"},{"args":[{"name":"acc","t":"var"}],"fn":{"name":"f","t":"var"},"t":"app"}],"fn":{"name":"loop","t":"var"},"t":"app"},"t":"if","then":{"name":"acc","t":"var"}},"in":{"args":[{"name":"n","t":"var"},{"name":"x","t":"var"}],"fn":{"name":"loop","t":"var"},"t":"app"},"name":"loop","params":["k","acc"],"t":"letrec","type":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"},{"k":"var","name":"a"}],"ret":{"k":"var","name":"a"}}},"doc":"Apply f to x exactly n times. Polymorphic in a.","kind":"fn","name":"apply_n_times","params":["n","x","f"],"type":{"body":{"effects":[],"k":"fn","params":[{"k":"con","name":"Int"},{"k":"var","name":"a"},{"effects":[],"k":"fn","params":[{"k":"var","name":"a"}],"ret":{"k":"var","name":"a"}}],"ret":{"k":"var","name":"a"}},"k":"forall","vars":["a"]}},{"body":{"lhs":{"args":[{"args":[{"lit":{"kind":"int","value":5},"t":"lit"},{"lit":{"kind":"int","value":0},"t":"lit"},{"name":"succ","t":"var"}],"fn":{"name":"apply_n_times","t":"var"},"t":"app"}],"op":"io/print_int","t":"do"},"rhs":{"args":[{"args":[{"lit":{"kind":"int","value":4},"t":"lit"},{"lit":{"kind":"bool","value":false},"t":"lit"},{"name":"flip","t":"var"}],"fn":{"name":"apply_n_times","t":"var"},"t":"app"}],"op":"io/print_bool","t":"do"},"t":"seq"},"doc":"Drive apply_n_times at Int (succ 5 times from 0) and Bool (not 4 times from false).","kind":"fn","name":"main","params":[],"type":{"effects":["IO"],"k":"fn","params":[],"ret":{"k":"con","name":"Unit"}}}],"imports":[],"name":"poly_rec_capture","schema":"ailang/v0"}
+57
View File
@@ -0,0 +1,57 @@
; Iter 16b.6 — LetRec inside a polymorphic enclosing fn.
; `apply_n_times : Forall(a). Fn(Int, a, Fn(a) -> a) -> a`
; applies `f` to `x` exactly `n` times. The recursive helper `loop`
; captures `f` from the enclosing fn's params — `f`'s declared type
; is `Fn(a) -> a`, which mentions the outer fn's type var `a`. The
; 16b.6 lift produces a synthetic top-level fn
; `loop$lr_0 : Forall(a). Fn(Int, a, Fn(a) -> a) -> a`
; and rewrites `(app loop k acc)` → `(app loop$lr_0 k acc f)`.
;
; `main` drives `apply_n_times` at TWO distinct type instantiations
; so codegen's monomorphisation queue picks up `loop$lr_0` twice
; (once at `a = Int` to compute `succ` applied 5x to 0 → 5; once at
; `a = Bool` to compute `not` applied 4x to false → false). This
; exercises the substitution of the outer Forall.vars into both the
; original LetRec params and the appended capture types.
;
; Expected stdout (one per line): 5, false.
(module poly_rec_capture
(fn succ
(doc "Helper: increment Int. Used as the Fn(Int) -> Int instance.")
(type (fn-type (params (con Int)) (ret (con Int))))
(params x)
(body (app + x 1)))
(fn flip
(doc "Helper: boolean negation as a top-level fn (so it has an adapter for value-position).")
(type (fn-type (params (con Bool)) (ret (con Bool))))
(params b)
(body (app not b)))
(fn apply_n_times
(doc "Apply f to x exactly n times. Polymorphic in a.")
(type
(forall (vars a)
(fn-type
(params (con Int) a (fn-type (params a) (ret a)))
(ret a))))
(params n x f)
(body
(let-rec loop
(params k acc)
(type (fn-type (params (con Int) a) (ret a)))
(body
(if (app == k 0)
acc
(app loop (app - k 1) (app f acc))))
(in (app loop n x)))))
(fn main
(doc "Drive apply_n_times at Int (succ 5 times from 0) and Bool (not 4 times from false).")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(seq (do io/print_int (app apply_n_times 5 0 succ))
(do io/print_bool (app apply_n_times 4 false flip))))))