Iter 7: first-class function references (no capture)

Top-level fn names are now usable as values, fn-typed parameters can
be called as `f(args)`. KISS slice of "closures + HOFs + TIR": no
TIR, no heap, no ABI shift — that bundle stays in Iter 8 where
closure-with-capture and lambdas land together.

Codegen:
- Type::Fn lowers to LLVM `ptr`; sig travels via a per-fn-body
  `ssa_fn_sigs` sidetable on `Emitter`.
- Term::Var falls through to `resolve_top_level_fn` when the name is
  not a local; emits `@ail_<m>_<def>` and registers the sig.
- Term::App splits: static callee (builtin / current-module / qualified)
  keeps the existing direct path; otherwise lower the callee, look up
  the sig, emit indirect `call <ret> (<param-tys>) %fn(args...)`.
- emit_fn registers fn-typed params in the sidetable on entry.
- If branches propagate a fn-pointer sig to their phi when both arms
  match.

Typechecker: unchanged. It already accepted `synth(callee)` against
Type::Fn; the only blocker was codegen rejecting non-Var callees.

Tests: 48 green (was 47). New `examples/hof.ail.json` and e2e
`higher_order_apply_inc` exercise `apply(inc, 41) == 42` end-to-end.

DESIGN.md: "What is not (yet) supported" rewritten — closures-with-
capture and lambdas remain pending, first-class fn-refs added as
positive bullet. Smoke-test list extended with `hof.ail.json`.

JOURNAL.md: Iter 7 entry with rationale, scope choice, architecture
self-check, Iter 8 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 12:44:10 +02:00
parent 1a448309fa
commit c6c0a10788
5 changed files with 358 additions and 23 deletions
+15 -5
View File
@@ -229,11 +229,13 @@ ail build <module> — full pipeline → binary
## What is not (yet) supported
Snapshot of the boundary at the end of Iter 6. Items move out of this list
Snapshot of the boundary at the end of Iter 7. Items move out of this list
as iterations land; the JOURNAL records the exact iteration.
- No closures / higher-order functions. Will require a typed IR stage (TIR)
before lowering.
- No closures with capture / no anonymous lambdas. Both will require a
typed IR stage (TIR) and closure conversion in lowering. First-class
function references (top-level fns as values, fn-typed parameters,
indirect calls) **are** supported as of Iter 7 — see below.
- No effect handlers — only the built-in IO and Diverge ops.
- No refinements / SMT escalation.
- No cross-module ADTs. ADTs are local to a module; ctor names must be
@@ -253,6 +255,14 @@ What **is** supported (and used as the smoke test for the pipeline):
pattern are restricted to `Var` / `Wild`.
- **Imports + qualified cross-module references** via dotted names
(Iter 5).
- **First-class function references** (Iter 7). A top-level fn name (or
qualified `prefix.def`) used as a `Term::Var` is a fn-pointer value of
AILang type `Type::Fn { ... }` and LLVM type `ptr`. Fn-typed parameters
may be called directly as `f(args)`. No capture, no lambdas — the only
way to construct a fn-value is to refer to a top-level def.
Pipeline regression smoke test: `examples/sum.ail.json` produces a
binary that prints 55.
Pipeline regression smoke tests:
- `examples/sum.ail.json` → prints 55 (recursion, arithmetic).
- `examples/list.ail.json` → prints 42 (ADTs + match).
- `examples/hof.ail.json` → prints 42 (first-class fn-refs, indirect call).