Iter 12b: codegen monomorphisation for polymorphic defs
Polymorphic defs are now actually emitted: each unique instantiation gets its own specialised LLVM fn, mangled @ail_<m>_<def>__<descriptor>. The typechecker's Forall instantiations from Iter 12a now have a real backend. Strategy: - Pass 1 of lower_workspace splits fn-typed defs into mono and poly: module_user_fns keeps LLVM-typed FnSig only for monomorphic fns; module_polymorphic_fns holds the full FnDef for poly defs; module_def_ail_types is a unified AILang-type lookup for both. - Direct calls to poly defs (current-module or qualified cross-module) flow through lower_polymorphic_call: it derives the type substitution from the actual arg AILang types, mangles the symbol, and queues (def, subst) for specialisation. - emit_module drains the queue after the regular defs. Each entry produces a specialised FnDef with rigid vars substituted in both the type and the body (incl. Lam param/ret annotations), then emits via the existing emit_fn pipeline. The synthetic name embeds the descriptor; standard mangling produces the right symbol. Codegen-side type tracking: - locals tuple grew from (name, ssa, llvm_type) to 4-tuple with ail_type. Updated all 6 push sites: fn entry, Let, match-ctor field, match-open-arm, lambda capture (cap_meta also extended), lambda param. - CtorRef gained ail_fields parallel to fields, used by match-arm bindings to inherit AILang types. - New synth_arg_type / synth_with_extras: a small recursive walker that derives the AILang type of an expression in the current scope. Used at poly call sites for arg-type inference. The `extras` parameter shadows locals during Let recursion without &mut self. Helpers added: - derive_substitution(vars, params, arg_tys): unifies the param shapes against the actual args, binding rigid vars; reports unbound vars as an internal error. - apply_subst_to_type / apply_subst_to_term: substitute rigid vars throughout a Type or Term (the Term variant only matters for Lam, the only Term arm carrying types). - descriptor_for_subst / type_descriptor: stable identifier-safe name suffix. `Int → I`, `Bool → B`, `Unit → U`, `Str → S`, ADT `Foo → FFoo`, `Fn → Fn_<...>__r_<ret>`. So `id__I` for id at Int, `apply__I_I` for apply at (Int, Int). - builtin_ail_type / builtin_effect_op_ret: AILang types for the builtin operators and effect ops, used by the codegen-side type tracker. Examples + tests: - examples/poly_id.ail.json: id used at Int (42) and Bool (true). Output: 42 / true. Two specialised fns + adapters + static closures get emitted (visible in the IR). - examples/poly_apply.ail.json: apply(succ, 41) == 42. Exercises the harder case where one of the polymorphic params is itself a function value — the closure-pair ABI survives substitution. - crates/ail/tests/e2e.rs: polymorphic_id_at_int_and_bool and polymorphic_apply_with_fn_param. Tests: 58/58 (was 56/56). Hash invariant holds: sum.ail.json keeps db33f57cb329935e / d9a916a0ed10a3d3. Limitations (known, deferred): - Polymorphic fns can only be DIRECTLY called. Passing a poly fn as a value (let f = id in f(42)) fails — resolve_top_level_fn looks in module_user_fns which doesn't include poly defs. Adding this needs per-instantiation closure-pair globals. - No higher-rank polymorphism: a polymorphic arg passed to another polymorphic call (apply(id, 42)) trips the simple unify_for_subst which doesn't recurse into Forall. Acceptable for the MVP. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,26 @@ fn insertion_sort_orders_list() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 12b: polymorphism end-to-end. `id : forall a. (a) -> a`
|
||||
/// gets called with `42` (Int) and `true` (Bool); each instantiation
|
||||
/// triggers a separate specialised LLVM fn. Output: 42, then "true".
|
||||
#[test]
|
||||
fn polymorphic_id_at_int_and_bool() {
|
||||
let stdout = build_and_run("poly_id.ail.json");
|
||||
let lines: Vec<&str> = stdout.lines().collect();
|
||||
assert_eq!(lines, vec!["42", "true"]);
|
||||
}
|
||||
|
||||
/// Iter 12b: polymorphism with a function-typed parameter.
|
||||
/// `apply : forall a b. ((a) -> b, a) -> b` invoked as
|
||||
/// `apply(succ, 41) == 42`. Exercises the closure-pair ABI inside a
|
||||
/// monomorphised body (a fn-typed param survives substitution).
|
||||
#[test]
|
||||
fn polymorphic_apply_with_fn_param() {
|
||||
let stdout = build_and_run("poly_apply.ail.json");
|
||||
assert_eq!(stdout.trim(), "42");
|
||||
}
|
||||
|
||||
/// 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