Adds ailang-docwriter to /agents/ — a recurring role for keeping
crate-, module-, and pub-item-level rustdoc accurate. First mission:
ailang-core. Crate root, every module root, every pub item documented;
intra-doc links throughout; Iter-13a additions (TypeDef.vars, Type::Con.args)
get an explicit backwards-compat note. Two stale broken-link warnings in
ailang-check fixed in passing. cargo doc --no-deps now warning-free across
the workspace; promoted to verification invariant 6 in DESIGN.md.
Adds type parameters to ADTs. `TypeDef` gets a `vars: Vec<String>` and
`Type::Con` gets an `args: Vec<Type>`, both `skip_serializing_if =
"Vec::is_empty"` so canonical-JSON hashes of every pre-13a definition
stay bit-identical (regression test in `hash::tests`).
Checker:
- `check_type_def` installs `td.vars` as rigid vars while validating
ctor field types — `Cons(a, List a)` now resolves both occurrences
of `a` and the recursive `List a` use.
- `check_type_well_formed` accepts `Type::Con { name, args }` only
when the type is in scope and `args.len()` matches its declared
arity; primitives stay zero-arg.
- `Term::Ctor` synth instantiates `td.vars` with fresh metavars, so
`MkBox(42)` synthesises to `Box<$m0>` and unifies field types
through the surrounding context.
- `type_check_pattern` substitutes the scrutinee's concrete type-args
through ctor field types, so a `MkBox(x)` arm against a `Box<Int>`
scrutinee binds `x : Int`.
- `check_fn` validates declared param/return types via
`check_type_well_formed` so arity mismatches on parameterised
ADTs surface before the body is checked.
`unify`, `occurs`, `Subst::apply`, `substitute_rigids`, and codegen's
`unify_for_subst` / `apply_subst_to_type` recurse into `args`.
Pretty-print:
- `type_to_string` renders `Box<Int>` for parameterised cons.
- `def_block`/`manifest` carry the `[a b ...]` vars list.
Three new check unit tests cover ctor instantiation at a concrete
arg, the polymorphic-`unbox` round trip at two distinct
instantiations, and arity mismatch on `Box<Int, Bool>`. All 62 tests
green; clippy clean (two pre-existing warnings untouched). Hashes
db33f57cb329935e (sum) and b082192bd0c99202 (IntList) verified
unchanged.
Codegen still synthesises `Type::Con` with `args: vec![]` from
`Term::Ctor` — full ADT monomorphisation lands in 13b.
Polymorphism is now opt-in via Type::Forall at top-level def types.
Implementation is the textbook ML rule: peel the Forall when checking
the def body, instantiate fresh metavars at every use site, unify.
Mechanics:
- New Subst struct + unify(): standard occurs-check unification, with
effects compared as a set.
- Metavars encoded inside the existing Type::Var as Type::Var{name:
"$m<id>"}. The "$" prefix can't collide with source-level identifiers,
so the AST schema stays untouched and existing module hashes don't
shift (verified: sum.ail.json keeps db33f57cb329935e / d9a916a0ed10a3d3).
- check_fn peels an outer Forall and installs the rigid vars in
Env.rigid_vars, where check_type_well_formed accepts them. Inner
forall vars unify only with themselves.
- Term::Var lookup runs maybe_instantiate on the resolved type, so
polymorphic globals get fresh metavars at each occurrence. Two
use sites of the same `id` get independent ($m0)→$m0 and ($m1)→$m1
shapes — no bleed.
- All expect_eq calls inside synth replaced with unify; expect_eq is
still used for the App-arity / etc. structural checks where no
metavars can appear.
Constraints kept tight on purpose:
- Const types may not be Forall (rejected outright).
- Forall in ADT field positions still rejected.
- No higher-rank polymorphism: App's callee can be Forall (defensive
instantiate) but the body doesn't introduce new quantifiers.
- No let-generalisation of lambdas — all polymorphism is at top-level
defs only.
Codegen still rejects Forall types, so polymorphic programs typecheck
but don't yet compile. Iter 12b adds monomorphisation.
Tests: 14/14 (was 10/10) — added polymorphic_id_def_typechecks,
polymorphic_id_can_be_used_at_int_and_bool,
polymorphic_id_consistency_is_enforced, polymorphic_apply_with_two_vars.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four small cleanups, no semantic change. Workspace is now clippy-
clean (default lints):
- ailang-check: collapse `if-same-then-else` for primitive vs ADT
type lookup into one branch via a local boolean.
- ailang-codegen: iterate `all_strings.values()` directly instead of
`for (_mname, entries) in &all_strings`.
- ailang-codegen: drop `import_map` parameter from `collect_captures`
— was threaded speculatively through every recursive call but
never read.
- ailang-codegen: `s.len()` instead of `s.as_bytes().len()` in
c_byte_len (the warning predates Iter 6).
Tests: 52 still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `Term::Seq { lhs, rhs }` (serde tag "seq") as a first-class
AST node for sequencing effectful expressions. Equivalent in
behaviour to `let _ = lhs in rhs`, but the dedicated node gives the
pretty-printer and diagnostics a cleaner shape and surfaces the
intent ("run for effect, then yield rhs") to future tooling.
Typecheck: lhs must be Unit; rhs's type is the result; effects
accumulate.
Codegen: lower lhs (drop SSA), lower rhs (return).
Capture / deps walkers: recurse into both sides.
Refactored examples/list_map.ail.json's print_list to use seq
instead of `let _ = ...`. Output unchanged (2/4/6).
Hash stability: existing examples without Term::Seq serialise
bit-identical; only list_map.ail.json's hashes changed (deliberate
refactor).
Tests: 51 green (was 50). New unit test
`ailang_check::tests::seq_lhs_must_be_unit` covers the type-error
path; existing list_map e2e covers the happy path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds anonymous functions to AILang. A lambda value is constructed by
malloc'ing an env struct that holds its captured locals, plus a
closure pair `{ thunk_ptr, env_ptr }` (Iter 8a's ABI). The lambda's
body lifts to a top-level thunk `@ail_<m>_<def>_lam<id>(ptr %env,
params...)` that unpacks captures back into named locals before
running.
AST: new Term::Lam { params, paramTypes, retType, effects, body }.
Serde tag is "lam"; existing modules (no Lam) serialize identically,
so all current hashes stay stable.
Pretty-printer: renders `(\\ (x: T ...) -> R . body)`.
Typecheck (ailang-check): a lambda's type is the declared Type::Fn.
Body's effect set must be a subset of declared effects (no row
polymorphism). Constructing a lambda is pure — only calling it picks
up the declared effects, via the existing App branch.
Codegen (ailang-codegen):
- collect_captures: free-var walk; excludes builtins, current-module
top-level fns, and qualified names.
- lower_lambda: per-fn lam counter, save/restore emitter state, emit
thunk into a deferred queue (flushed after the parent fn), pack env
+ closure pair on heap, register the resulting closure-pair SSA in
the sidetable so subsequent indirect calls find it.
- Capture sigs propagate: a fn-typed capture keeps its FnSig in the
thunk's sidetable so `f(args)` inside the body still indirect-calls.
- Env layout: 8 bytes per capture (typed load/store reads only the
needed bytes; padding wasted but uniform).
Tests: 49 green (was 48). New examples/closure.ail.json + e2e
`closure_captures_let_n` exercises `let n = 3 in apply(\\x. x + n, 39)`
end-to-end and asserts the binary prints 42.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ail deps now filters builtins, fn params, and let/match-pattern
bindings. New value_names() in ailang-check::builtins is the single
source of truth shared with the typechecker install path. walk_term
threads a scope set; qualified `prefix.def` refs pass through.
- check_in_workspace returns Vec<CheckError>; check_workspace
accumulates body diagnostics across defs and modules. Pass-1
(top-level symbol table) and per-module type-def setup stay
fail-fast — corrupt env would taint later diagnostics.
- DESIGN.md "What the MVP is NOT" was lying (ADTs, strings landed
in Iter 2/3). Renamed to "What is not (yet) supported" and split
into "not yet" + supported/smoke-tested. JOURNAL Iter 6 entry
records the architecture self-check.
Tests: 47 green (was 44). +2 deps filter tests in e2e,
+1 multi-diagnose test in ailang-check workspace integration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make English the project-wide language: all comments, string literals,
CLI help text, design docs, journal, agent prompts, and README. Only
CLAUDE.md (the user's own instruction file) stays German, and the live
conversation between user and Claude continues in German.
Adds a new "Project language: English" section to docs/DESIGN.md as
the durable convention. No logic changes — translation only. Four
internal error strings in the typechecker were retranslated; no
test asserts on their wording.
Verified: cargo test --workspace passes (44/44).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neue API check_workspace(&Workspace) -> Vec<Diagnostic>; check_module
hebt das Modul intern in einen Trivial-Workspace. Term::Var mit genau
einem Punkt im Namen ist ein qualifizierter Verweis <prefix>.<def>,
aufgelöst über Import-Map (alias-oder-modulname → echter Modulname).
Drei neue Diagnostic-Codes: unknown-module, unknown-import,
invalid-def-name. ail check lädt jetzt immer via load_workspace;
Loader-Fehler werden im JSON-Modus zu strukturierten Diagnostics
(module-not-found, module-cycle, …). DESIGN.md und JOURNAL.md
dokumentieren die Konvention. Hash-Stabilität: alle ir_snapshot_*-
Tests bitidentisch grün, kein neuer AST-Knoten.
Neue Top-Level-API check_module(&Module) -> Vec<Diagnostic> in
ailang-check, plus stabile Codes (unbound-var, type-mismatch,
arity-mismatch, non-exhaustive-match, unknown-ctor-in-pattern,
duplicate-def, …). CLI bekommt --json-Flag für maschinenlesbares
Output, Exit 1 bei Errors. Text-Modus unverändert. E2E-Test
check_json_unbound_var sichert das Format ab.
- AST: Def::Type mit Ctors; Term::Ctor (Konstruktion) und Term::Match
mit Arm/Pattern. Patterns: Wild, Var, Lit, Ctor { ctor, fields } —
Sub-Patterns im MVP auf Var/Wild beschränkt.
- Typchecker: Type-Registry, ctor_index für O(1)-Resolution, Pattern-
Bindings, Exhaustiveness-Check gegen volle Konstruktormenge plus
Negativ-Tests.
- Codegen: Boxed-Heap-Layout via malloc; Tag in Offset 0, Felder ab
Offset 8 in 8-Byte-Slots. Match lowert zu load tag + switch + Phi
am Join. Default-Block ist unreachable, wenn vom Typchecker geprüft.
- examples/list.ail.json: rekursive Int-Liste mit sum_list via match.
E2E-Test + Exhaustiveness-Tests. 19/19 Tests grün.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Codegen: current_block-Tracking ersetzt die Heuristik im phi-Lowering;
verschachtelte if-Ausdrücke produzieren jetzt korrekte LLVM IR.
examples/max3.ail.json + Test schützt gegen Regression.
- Strings: Lit::Str / Type Str / io/print_str Effekt-Op; Strings sind im
MVP immutable Konstanten. examples/hello.ail.json als zweiter E2E-Test.
- CLI: --json für manifest und builtins; neuer deps-Subcommand listet
statische Symbol-Referenzen pro Definition. Effekt-Ops mit Prefix
effect: markiert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Erste lauffähige Iteration. examples/sum.ail.json wird zu nativem Binary
kompiliert und druckt 55 (Summe 1..10) als End-to-End-Test.
Architektur:
- ailang-core: hashbares JSON-AST + canonical-form + pretty-printer
- ailang-check: monomorpher HM-Subset + Effekt-Set-Tracking
- ailang-codegen: LLVM-IR-Text-Emitter (kein libllvm-link)
- ail: CLI mit check/manifest/render/describe/emit-ir/build/builtins
Designentscheidungen sind in docs/DESIGN.md dokumentiert; der Verlauf
in docs/JOURNAL.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>