Iter 14a: polymorphic List a end-to-end + monomorphisation fix
Dogfood payoff for parameterised ADTs (13a/b/c). Adds the first
program to nest a nullary ctor of a parameterised ADT inside a
parent ctor (Cons(Int, Nil)) and to call a polymorphic recursive
higher-order fn over a recursive parameterised ADT.
Fixture (examples/list_map_poly.ail.json):
- data List a = Nil | Cons a (List a)
- inc : (Int) -> Int = \x. x + 1
- map : forall a b. ((a) -> b, List<a>) -> List<b>
recursive, instantiated at (Int, Int)
- print_list : (List<Int>) -> Unit !{IO}, recursive
- main builds [1,2,3], maps inc, prints each: "2", "3", "4"
E2E test list_map_poly_inc_then_prints in crates/ail/tests/e2e.rs.
Bug fixed (crates/ailang-codegen/src/lib.rs, +30/-9):
synth_arg_type used Type::unit() as placeholder for ADT type
vars that the ctor's args couldn't pin (Nil for List<a>).
Inside Cons(Int, Nil), unify_for_subst then bound a=Int from
the head and collided with a=Unit from the tail. Replaced the
placeholder with a synth-only wildcard Type::Var{name:"$u"}
mirroring the checker's $m metavar convention; unify_for_subst
short-circuits on $u-prefixed arg-side vars (accept without
binding, let a sibling pin the var).
No schema or API change. No new variant. Tester's recursion
hypothesis was refuted by debugger via a non-recursive
Cons(7, Nil) repro before the fix landed.
Tests: 25/25 e2e (was 24). All Iter 12/13 regressions green.
cargo doc --no-deps zero warnings (workspace invariant from
13d/e/f preserved).
Three-agent flow (tester -> debugger, no implementer needed
since the fix was inside debugger's <50 LOC scope). Process
note in JOURNAL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2076,18 +2076,27 @@ impl<'a> Emitter<'a> {
|
||||
for (exp, actual) in cref.ail_fields.iter().zip(arg_tys.iter()) {
|
||||
unify_for_subst(exp, actual, &var_set, &mut subst)?;
|
||||
}
|
||||
// Vars not pinned by ctor args (e.g. `None` for
|
||||
// `Maybe a`) are filled with `Type::unit()` as a
|
||||
// placeholder. That's harmless for monomorphic call
|
||||
// sites — codegen never instantiates a polymorphic fn
|
||||
// off this un-pinned shape because the typechecker
|
||||
// would have rejected an under-determined call —
|
||||
// and keeps the function total for the well-formed
|
||||
// ones (e.g. `Some(7)` pins `a` from the arg).
|
||||
// Vars not pinned by ctor args (e.g. `Nil` for `List a`,
|
||||
// `None` for `Maybe a`) are filled with a synth-only
|
||||
// wildcard `Type::Var { name: "$u" }`. The `$u`-prefix
|
||||
// is reserved here (mirrors the checker's `$m` for
|
||||
// metavars) and is treated as a match-anything wildcard
|
||||
// by `unify_for_subst` on the arg side. This matters
|
||||
// when a nullary ctor like `Nil` is nested inside a
|
||||
// parent ctor whose other args pin the same type var
|
||||
// concretely — e.g. `Cons(Int, Nil) : List<a>` must
|
||||
// pin `a = Int` from the head and let the tail's
|
||||
// unconstrained `a` defer rather than collide on
|
||||
// `Type::unit()` as it would have pre-fix.
|
||||
let resolved: Vec<Type> = cref
|
||||
.type_vars
|
||||
.iter()
|
||||
.map(|v| subst.get(v).cloned().unwrap_or_else(Type::unit))
|
||||
.map(|v| {
|
||||
subst
|
||||
.get(v)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Type::Var { name: "$u".into() })
|
||||
})
|
||||
.collect();
|
||||
Ok(Type::Con {
|
||||
name: type_name.clone(),
|
||||
@@ -2237,6 +2246,18 @@ fn unify_for_subst(
|
||||
vars: &BTreeSet<&str>,
|
||||
subst: &mut BTreeMap<String, Type>,
|
||||
) -> Result<()> {
|
||||
// Iter 14a fix: an arg-side `$u`-prefixed var is a synth-only
|
||||
// wildcard produced by `synth_arg_type` for nullary ctors of a
|
||||
// parameterised ADT (e.g. `Nil : List<$u>`). It carries no real
|
||||
// constraint — accept without binding so a sibling arg can pin
|
||||
// the type var instead. Without this, `Cons(Int, Nil)` synth
|
||||
// would unify `a = Int` (from head) and then `a = $u` (from
|
||||
// tail's recursive `List<a>` slot) and falsely error.
|
||||
if let Type::Var { name } = arg {
|
||||
if name.starts_with("$u") {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
match (param, arg) {
|
||||
(Type::Var { name }, _) if vars.contains(name.as_str()) => {
|
||||
if let Some(prev) = subst.get(name) {
|
||||
|
||||
Reference in New Issue
Block a user