Files
AILang/examples/maybe_int.ail.json
T
Brummel 1631f6065c Iter 13b: codegen for parameterised ADTs
Closes the gap between Iter 13a (parameterised ADTs in the
checker) and end-to-end execution. ADT ctor code stays inlined at
every use site — no mono-queue for types, no new symbols — but
LLVM field types are now derived per use site via substitution.

`CtorRef` gains `type_vars: Vec<String>` so use sites can identify
which fields reference rigid vars. `cref.fields` (precomputed
LLVM strings) is documented as monomorphic-only and read past for
parameterised ADTs.

`lower_ctor`: derives a `BTreeMap<String, Type>` substitution from
`synth_arg_type` of each arg via `unify_for_subst`, then maps every
`ail_field` through `apply_subst_to_type` + `llvm_type` for the
per-store types. Monomorphic ADTs hit the original fast path.

`lower_match`: builds `arm_subst` from `s_ail.args` ↔ `cref.type_vars`,
substitutes through each `cref.ail_fields[idx]`, and uses the
substituted type both for the LLVM `load` AND as the AILang slot
of the local — so a downstream `unbox(b)` sees `b: Int`, not
`b: a`.

`synth_arg_type` for `Term::Ctor`: returns concrete type-args
derived from the ctor's term-args. Vars left unpinned (e.g. `None`
for `Maybe a`) fall back to `Type::unit()`.

`llvm_type(Type::Var)` now hard-errors. Earlier this silently
fell through to `ptr` (the ADT-via-ptr fallback), producing
garbage IR. Failing loudly here surfaces missed substitutions in
the test suite.

Two e2e tests (`box.ail.json`, `maybe_int.ail.json`) cover ctor
lower with substituted field types and match-arm field
substitution. 64 tests green; clippy clean (two pre-existing
warnings untouched). Hash invariant holds.

Out of scope per agent assignment: poly-fn-as-value, higher-rank
polymorphism, DESIGN/JOURNAL updates (deferred to 13c).
2026-05-07 14:45:35 +02:00

113 lines
2.7 KiB
JSON

{
"schema": "ailang/v0",
"name": "maybe_int",
"imports": [],
"defs": [
{
"kind": "type",
"name": "Maybe",
"vars": ["a"],
"doc": "Optional value with a polymorphic payload.",
"ctors": [
{ "name": "None", "fields": [] },
{
"name": "Some",
"fields": [{ "k": "var", "name": "a" }]
}
]
},
{
"kind": "fn",
"name": "or_else",
"type": {
"k": "fn",
"params": [
{
"k": "con",
"name": "Maybe",
"args": [{ "k": "con", "name": "Int" }]
},
{ "k": "con", "name": "Int" }
],
"ret": { "k": "con", "name": "Int" },
"effects": []
},
"params": ["m", "d"],
"doc": "Returns the wrapped Int for Some(x) and the default d for None.",
"body": {
"t": "match",
"scrutinee": { "t": "var", "name": "m" },
"arms": [
{
"pat": { "p": "ctor", "ctor": "None", "fields": [] },
"body": { "t": "var", "name": "d" }
},
{
"pat": {
"p": "ctor",
"ctor": "Some",
"fields": [{ "p": "var", "name": "x" }]
},
"body": { "t": "var", "name": "x" }
}
]
}
},
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": { "k": "con", "name": "Unit" },
"effects": ["IO"]
},
"params": [],
"doc": "Print or_else for both arms; expected 7 then 99.",
"body": {
"t": "seq",
"lhs": {
"t": "do",
"op": "io/print_int",
"args": [
{
"t": "app",
"fn": { "t": "var", "name": "or_else" },
"args": [
{
"t": "ctor",
"type": "Maybe",
"ctor": "Some",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 7 } }
]
},
{ "t": "lit", "lit": { "kind": "int", "value": 99 } }
]
}
]
},
"rhs": {
"t": "do",
"op": "io/print_int",
"args": [
{
"t": "app",
"fn": { "t": "var", "name": "or_else" },
"args": [
{
"t": "ctor",
"type": "Maybe",
"ctor": "None",
"args": []
},
{ "t": "lit", "lit": { "kind": "int", "value": 99 } }
]
}
]
}
}
}
]
}