it.1/it.2 shipped; it.2's D1 resolved the original two open spec
questions (same-family mutual rule; foldl=structural). it.3 BLOCKED
on a fundamental design-thesis gap: the totality story recognised
only ADT-structural recursion; the corpus sweep proved a second
canonical total scheme (Int-bounded well-founded recursion, incl.
branching build(d-1),build(d-1)) is overlooked and the milestone
as-specified makes it inexpressible — failing its own
feature-acceptance gate. Records the full option analysis with a
recommendation (A1: trichotomy + a documented non-negative-entry
obligation), flagged as a USER decision because it trades against
the purity pillar the user co-designed. Next: user decision →
it.2b → it.3 re-dispatch from c992eb9.
33 KiB
AILang Roadmap
Priority-ordered list of upcoming work — milestones, features, todos, and ideas. The orchestrator maintains this file. The user can request additions; the orchestrator chooses what to remove or reprioritise as work progresses.
Conventions
- One checkbox per entry.
- [ ]is open;- [~]is in progress (work has started — a plan exists, a branch is open, or commits are landing);- [x]is done. A finished entry may stay briefly for context, then is removed (with a one-line mirror indocs/journals/). - Each entry is tagged by kind and lives under a priority
bucket:
- [milestone] — big chunk that will get a
docs/specs/<milestone>.md. - [feature] — smaller addition inside a milestone, no full spec.
- [todo] — concrete task that can run without a brainstorm (cleanup, doc fix, mechanical refactor, test backfill).
- [idea] — not yet decision-ready, no commitment.
- [milestone] — big chunk that will get a
- Optional
depends on:line names another entry that has to land first. - Optional
context:line points to the journal entry (per-iter file underdocs/journals/or, for pre-2026-05-11 entries, the archiveddocs/journal-archive.md) where the rationale lives. The roadmap is intentionally terse; rationale stays in the journals. - Priority buckets:
- P0 — in flight. Spec or plan already exists.
- P1 — next up. Decision made; not yet started.
- P2 — medium-term. Decided in principle, scheduled later.
- P3 — ideas. No commitment; may be cut.
P0 — In flight
(empty — prelude-decouple milestone closed 2026-05-14, audit-pd clean. Pick the next milestone from P2.)
P1 — Next
-
[~] [milestone] Iteration discipline — structural recursion + named
loop/recur; recursion-by-call restricted;tail-appretired. Specdocs/specs/2026-05-15-iteration-discipline.md(approved fda9b78; scope-corrected 2018178/10a0595). Status 2026-05-15: it.1 SHIPPED (96db54d— loop/recur additive), it.2 SHIPPED (a4be1e5— guardedness checker + first real Diverge effect), it.3 BLOCKED on a surfaced fundamental design fork (see "The blocking fork" below — needs a user design decision + a new iteration it.2b before it.3 can re-dispatch). The iteration story of the language, decided in principle 2026-05-15. Two constructs survive, one is removed:- Structural recursion stays — and becomes total-by-construction.
A recursive call is permitted iff it is structurally guarded:
each recursive call is on a constructor-smaller component of an
algebraic argument. Decision 10 already guarantees ADTs are
acyclic, so a structurally-guarded recursion terminates by the
invariant the language already holds — permitting it costs
nothing on the provability pillar and it stays locally checkable.
This is the most LLM-natural correct pattern (tree / AST / JSON /
list
match-and-recurse, saturated in every FP corpus) and must not be amputated. - All other repetition must go through a named
(loop ...)head +recur.recuris the single explicit backward jump; it targets a lexically-enclosing named loop, never an implicit fn head (Clojure permits the implicit form — AILang does not, for the same no-outward-inference reason that motivated explicittail-app). Accumulator loops, counted iteration, streaming, state machines — none are structurally decreasing, so all are forced into the explicit named-loop form. tail-appis retired.Term::App { tail: bool }and themusttail-lowering surface go away: a call is either structural recursion (a plain call, checked total) or it isloop/recur(the only loop). No marked-tail-call concept survives.
Motivation. Discharges fieldtest finding F1 — the most consequential mut-local finding: mut-local's own motivation ("removes friction from accumulators that today need tail-recursive helpers") was structurally unmet, because without a loop the only iteration mechanism remained the very tail-rec helper.
loop/recurcloses that honestly without introducing an imperative construct (it is pure: no mutable loop variable, no effect, composes into pure contexts freely). It also closes the residual "forgot to mark tail" trap completely — a recursive call that is not structurally guarded is a compile error that names itself, not a silent stack-overflow. And it unifies with Decision 10: acyclic values and an acyclic implicit call graph; the only cycles anywhere in a program are explicit, named, typedlooppoints. Both surviving constructs pass all three feature-acceptance clauses including the 2026-05-15 clause 3 (structural recursion and named loop/recur are LLM-natural and their bug class is closed by construction; a barewhileis exactly what clause 3 rejects).Original open spec questions — RESOLVED in it.2 (
a4be1e5). (1) mutual structural recursion → the conservative same-ADT-family rule (connected-components union-find over the type-reference graph; admits tree/forest, even/odd-over-Nat, mutual JSON; a general size-measure ordering stays out of scope). (2) thefoldl-shaped accumulator-carrying structural walk → counts as structural recursion (plain, pure, total — only the structural argument must decrease; accumulator positions are unconstrained). Both shipped and pinned.The blocking fork (it.3-surfaced 2026-05-15 — needs a USER design decision; touches the purity pillar the user co-designed). The milestone's totality thesis recognised exactly ONE total-by-construction recursion scheme: structural-over-an-acyclic- ADT (Decision 10). The it.3 mandated corpus sweep proved this is incomplete. Six corpus fixtures contain the single most LLM-natural tree-builder shape:
build(d: Int) = if d==0 then Leaf else Node(1, build(d-1), build(d-1)). It is provably terminating, but: not ADT-structural (disInt, no constructor-smaller component) and notloop/recur- able (it is branching/double recursion;recuris a single tail-position back-edge — a balanced-tree build is not a tail loop; the onlyloopform is a hand-rolled explicit-stack worklist, which no LLM writes). So the milestone as-specified makes a trivially-total, maximally-natural function inexpressible — failing its own feature-acceptance criterion (clause 1: the LLM reaches forbuild(d-1),build(d-1); clause 2: the milestone would remove expressivity). This is a real design-thesis gap, not a migration nuisance: there is a SECOND canonical total-by-construction scheme the design overlooked — well-founded recursion on a strictly-decreasing non-negative integer measure (f(0)=base; f(n)=…f(n-1)…, branching allowed).Decision the user must make (it trades against the purity pillar — Int ≠ ℕ; AILang has no
Nat/refinement types, so Int-bounded recursion is total only for non-negative entry):- A1 (RECOMMENDED). Make the totality story a trichotomy:
ADT-structural | Int-bounded-well-founded | explicit
loop/recur. Accept Int-bounded recursion (recursive call onp - <pos-lit>with a non-recursing base guard, branching allowed) as a second total-by-construction class, with one documented obligation: non-negative entry (a negative seed diverges — the same precondition class as array-bounds the language already lives with). Restores the entiref(n-1)recursion family (the second-most-common shape in any corpus); keeps all 6 fixtures verbatim; the trichotomy is more complete, not less principled. Rationale is the language's own gate: a milestone that makesbuild(d)inexpressible cannot pass the feature-acceptance criterion. Why this is a user call: it concedes "total by construction, sign nothing" → "total by construction modulo one documented non-negative-entry obligation" — a dilution of a purity pillar the user personally weighted in the original Socratic design dialogue. - A2. Introduce a
Nattype; accept Int-bounded recursion only onNat— preserves zero-precondition purity, butNatis its own sub-milestone and an LLM writesdepth: Intunless the prelude makesNatthe natural default (clause-1 friction). - A3 / B / C (not recommended). Strict-dichotomy +
explicit-worklist (fails clause 1 — no LLM hand-rolls a stack
for tree-build); a narrow carve-out predicate (the patch smell);
reshaping the LLM-natural fixtures (forbidden —
feedback_dont_adapt_tests_to_bugs).
If A1 (or A2): new iteration it.2b — additive widening of the it.2 guardedness checker to also accept Int-bounded (incl. branching) recursion as total; no removal. After it.2b the 6 fixtures check clean with zero migration; it.3 then re-dispatches from
c992eb9(thebench/it3-oracle/40-fixture behavioural oracle is preserved and reused; nothing in it.3 was destructive). The it.3 RC-RSS/18g.1 load-bearing risk (Task 5.5/5b) is still UNTESTED — must not be treated as resolved by the milestone-close audit.Relation to Stateful islands. This entry owns the iteration story and supersedes the "while-loops legal" scope bullet of the Stateful-islands milestone below. The genuinely-escaping mutable-state path (
ref a/MutArray a/Stateful a b/pipe) is unaffected and stays in Stateful-islands.- context: spec
docs/specs/2026-05-15-iteration-discipline.md; per-iter journals 2026-05-15-iter-it.1 (shipped), -it.2 (shipped), -it.3 (BLOCKED — the design fork above). Next action is a USER decision on the blocking fork (A1 recommended), then iteration it.2b, then it.3 re-dispatch fromc992eb9.
- Structural recursion stays — and becomes total-by-construction.
A recursive call is permitted iff it is structurally guarded:
each recursive call is on a constructor-smaller component of an
algebraic argument. Decision 10 already guarantees ADTs are
acyclic, so a structurally-guarded recursion terminates by the
invariant the language already holds — permitting it costs
nothing on the provability pillar and it stays locally checkable.
This is the most LLM-natural correct pattern (tree / AST / JSON /
list
-
[milestone] Heap-
StrABI — runtime infrastructure for malloc-backed, refcountedStrvalues alongside the existing static@.str_*globals. Today theStrpath is static-only (DESIGN.md §"Float semantics" notes this explicitly), so any primitive that needs to produce aStrat runtime —int_to_str,float_to_str(currently type-installed butCodegenError::Internalon call), eventualShow.show, future++on strings — cannot ship. Scope: pick the representation (likely a{rc_header, len, bytes…}slab consistent with the rest of the RC runtime), teach codegen to accept both static and heapStrat the same ABI slot, wire RCinc/drop/clone/compare/eqon the heap form, and shipint_to_str+float_to_stras the first two callers so the ABI gets exercised end-to-end. Unblocks Show + print rewire below.- context: DESIGN.md §"Float semantics" (
float_to_stris type- installed, codegen-deferred); spec 2026-05-11-23-eq-ord-prelude §"Show. Defers behind a heap-Str-ABI milestone"; spec 2026-05-10-fieldtest-floats §F4 ("dynamic Str allocation in the runtime"); spec 2026-05-09-22-typeclasses §22b.4b ("Show#Int needs anint_to_strprimitive returning heap-allocated Str").
- context: DESIGN.md §"Float semantics" (
-
[milestone] Post-22 Prelude — Show + print rewire — shipped 2026-05-13 as iters 24.1 (heap-Str runtime + codegen for
bool_to_strstr_clone,f38bad8), 24.2 (preludeclass Show+ four primitive instances Int/Bool/Str/Float + 22b TShow/tshow migration), and 24.3 (polymorphicfn print : forall a. Show a => (a borrow) -> () !IO- positive 4-prim E2E + user-ADT E2E + Show-aware NoInstance
diagnostic + DESIGN.md amendments to §"Prelude (built-in) classes"
and §"Float semantics"). The
MethodNameCollisionworkaround that blocked the original spec retired in mq.3 (2026-05-13); specdocs/specs/2026-05-13-24-show-print.mdre-derived 24.2 + 24.3 against the post-mq architecture.
- context: spec
docs/specs/2026-05-13-24-show-print.md; per-iter journals 2026-05-12-iter-24.1, 2026-05-13-iter-24.2, 2026-05-13-iter-24.3.
P2 — Medium-term
-
[milestone] Retire
io/print_int/io/print_bool/io/print_floateffect-ops + migrate example corpus toprint. Shipped 2026-05-14 as iter rpe.1.- context: per-iter journal
docs/journals/2026-05-14-iter-rpe.1.md.
- context: per-iter journal
-
[todo] Author
examples/prelude.ailalongsideexamples/prelude.ail.json. (Satisfied 2026-05-13 by iter form-a.0 —examples/prelude.ailrendered viaail render, 116 lines / 6386 bytes, round-trip-CI green.) -
[milestone] Form-A as the default authoring surface for examples and docs. (Closed 2026-05-13 by iter form-a.1.) Render every
examples/*.ail.jsonto its.ailsibling viaail render, delete the now-redundant.ail.json, and regenerate the JSON-AST perail parseat build / test time. Same for inline JSON-AST blocks indocs/markdown (~7 in DESIGN.md plus ~29 other md files) — convert to Form-A snippets where the snippet's purpose is to show "what the language looks like", not "what the schema is".After this milestone the working tree contains exactly one representation per program: the
.ailsource. The JSON-AST is a build artefact, not a checked-in twin. Tests and benches that currently glob*.ail.jsoneither parse-then-consume or are rewired to point at.aildirectly. The round-trip invariant flips role: today it gates that the two forms agree; afterwards it gates thatparseis deterministic.Carve-outs that MUST stay JSON-AST (no Form-A counterpart, and no
.ailsibling shall be created — these are the only seven files that remain.ail.json-only post-milestone):- Fixtures that test canonical-form rejection — Form A would
reject them at parse, defeating the test:
test_ct1_bare_xmod_rejected.ail.json,test_ct1_qualified_class_rejected.ail.json,test_ct1_bad_qualifier.ail.json,broken_unbound.ail.json,test_22b2_invalid_superclass_param.ail.json,test_22b2_kind_mismatch.ail.json,test_22b2_unbound_constraint_var.ail.json. - DESIGN.md schema documentation blocks where the JSON-AST shape is the point (Decision 11, ParamMode, canonical-form invariants).
- Any other case where the structured form is the artefact under discussion, not a vehicle for a program.
Touches CLAUDE.md ("source of truth is structured data") — the language doctrine doesn't change (JSON-AST is still the canonical hashable form), but the authoring doctrine does: authors write
.ail, the build derives JSON-AST. Sentence in CLAUDE.md needs rewording in the same milestone.Mechanical for the bulk; per-fixture judgement call only for the carve-out list. Run as one milestone so the corpus flip-over happens at a single, audit-gated point.
- context: post-Form-A-as-canonical-authoring corollary of the
examples/prelude.ailentry above. Generalises the same idea to the rest of the corpus and follows through on the cross- model authoring data (textual form cheaper, more first-try hits) by treating Form A as the privileged surface in the working tree, not just in flavour text.
- Fixtures that test canonical-form rejection — Form A would
reject them at parse, defeating the test:
-
[feature] Operator routing through
Eq/Ord—==,<etc. resolved via the typeclass instead of the built-in primitive comparators. No commitment; gated on bench re-baselining to make sure the indirection doesn't tank latency.- context: JOURNAL 2026-05-09
- depends on: Post-22 Prelude — Eq/Ord (shipped 23.5)
-
[todo]
types/ctor_indexoverlay shape question — decide whether the env's two parallel ctor maps should collapse into one overlay, or stay split. Surfaced during the env-construction unify audit.- context: JOURNAL 2026-05-10 ("Audit close: env-construction unify"); closed by iter ctt.1 — DESIGN.md §"Env construction" anchors the split decision.
-
[todo] CLI human-mode diagnostic surface for
WorkspaceLoadError. Shipped 2026-05-14 as iter cli-diag-human — a newload_workspace_humanhelper incrates/ail/src/main.rsroutes 9 non-JSONailang_surface::load_workspace(&path)?call sites throughworkspace_error_to_diagnostic, so the bracketed[code]prefix is preserved acrossail check,build,run,emit-ir,prose,describe,deps,diff,manifest.- context: per-iter journal
docs/journals/2026-05-14-iter-cli-diag-human.md.
- context: per-iter journal
-
[todo] Retire dead
KindMismatcharm —validate_classdefs'swalk_kind_mismatchpath is structurally unreachable through well-formed schema post-ct.1 (the canonical-form validator catches the malformedType::Con { name: param }shape earlier). The enum variant +walk_kind_mismatchhelper stay as dead-but-defensive code; a future tidy can delete both.- context: JOURNAL 2026-05-11 ("Iteration ct.1"); closed by iter ctt.3 — variant + helper + dispatch + Display arm all deleted; canonical-form-rejection test stays green asserting
BareCrossModuleTypeRef.
- context: JOURNAL 2026-05-11 ("Iteration ct.1"); closed by iter ctt.3 — variant + helper + dispatch + Display arm all deleted; canonical-form-rejection test stays green asserting
-
[todo] Re-key
Registry.type_def_moduleto handle bare-name-collision-across-modules —BTreeMap<String, String>keyed by bare type name silently overwrites when two modules each definetype Foo;normalize_type_for_registrywould then collapseM.FooandN.Footo whichever insert won. Acceptable for current corpus (distinct bare type names across modules), but the proper fix is to key by(owning_module, bare_name) → defining_module.- context: JOURNAL 2026-05-11 ("Iteration ct.1") — flagged by ct.1.5a quality reviewer.
-
[feature] 22c — typeclass corpus expansion. User-defined classes beyond the prelude four; multi-parameter classes; superclass chains; richer instance bodies. Deferred from milestone 22.
- context: JOURNAL 2026-05-09
-
[milestone] Module-qualified class names + type-driven method dispatch — retired the
MethodNameCollisionworkaround; shipped 2026-05-13 as iters mq.1 (canonical-form extension for class-ref fields + workspace-internal qualification), mq.2 (type- driven dispatch mechanism installed:method_to_candidate_classesinverse index, multi-candidateResidualConstraint,resolve_method_dispatch5-step rule,AmbiguousMethodResolution+UnknownClassdiagnostics), and mq.3 (retirement + multi-class E2Eclass-method-shadowed-by-fnwarning + DESIGN.md sync). Two libraries can now each declareclass Eqwith their owneq; cross-class method ambiguity is resolved at the call site via type-driven dispatch with<module>.<Class>.<method>as the disambiguation form.
- context:
docs/specs/2026-05-10-canonical-type-names.md"Out of scope: Class names" — the workaround was named there so it stayed visible until this milestone retired it.
-
[todo] Boehm full retirement — remove the transitional Boehm GC path now that RC + uniqueness is the canonical memory story.
- context: JOURNAL pre-22, "Boehm transitional"
-
[feature] Closure-pair slab / pool — codegen tweak to pool the env+code closure pairs instead of one-shot heap allocs. Bench-gated.
-
[todo]
FnDef::syntheticflag — formalise the monomorphiser-emitted FnDefs so downstream passes can tell user-authored from synthesised at a glance. Currently inferred from symbol naming. -
[todo] 21'h iteration — final 21' carry-over (latency methodology pass). Numbering kept for continuity with the 21' arc.
-
[todo]
io/print_floatalways-emit-.0— surface printer always emits.ore/Eso re-lex routes to Float; the runtime printer (printf("%g\n", v)) doesn't, so2.0prints as2(Int-shaped). Asymmetric. Either switch the runtime path to a.0-fallback printer (matching surface) or document the%gcontract in DESIGN.md §"Float semantics" so the LLM-author knowsio/print_float's output is for-humans not round-trip.- context:
docs/specs/2026-05-10-fieldtest-floats.mdfinding F1.
- context:
-
[todo] Rustdoc warning sweep —
cargo doc --no-depsreports 16 pre-existing warnings (15 inailang-check, 1 inailang-core: private-item links from public doc, unresolved intra-crate links). All predate the design-md-consolidation milestone; treat as a one-off sweep.- context: JOURNAL 2026-05-10 ("Audit close: design-md-consolidation").
-
[todo]
design_schema_drift.rsfidelity widening — current test checks anchor presence anywhere in DESIGN.md; the audit found that[high]schema gaps in §"Data model" are invisible because anchors live in Decision 11 instead. Constrain the test to scan only §"Data model" + ParamMode block, or extract JSON-schema blocks into a machine-readable file the test consumes.- context: JOURNAL 2026-05-10 ("Audit close").
-
[todo] Split
BadCrossModuleTypeRefinto two diagnostics — the current single shape collapses unknown-owner and known-owner / unknown-type-in-owner into one message. The two cases suggest different fixes (add(import <owner>)vs. fix the type name). In the known-owner branch, list the owner's available type defs as candidates the waybare-cross-module-type-reflists candidates from imports.- context: fieldtest 2026-05-11 —
examples/ct_3*.ailexhibits both branches with identical-shape diagnostics.
- context: fieldtest 2026-05-11 —
-
[todo] Zero-arg
(app f)rejected at parse — the Form-A parser refuses an application with an empty argument list, so a nullary call has no surface form. Surfaced by the mut-local fieldtest (F3). Decide: accept(app f)as the nullary-call surface, or ratify in DESIGN.md that nullary functions are expressed differently (and say how). Not a blocker; no current corpus program needs it, but an LLM author reaches for it.- context:
docs/specs/2026-05-15-fieldtest-mut-local.mdfinding F3.
- context:
-
[todo] Workspace search beyond entry-module's directory —
load_workspaceonly finds sibling.ail.jsonfiles in the same directory as the entry module, so any consumer of prelude/std in a subdirectory has no way to resolve cross-module imports. Add either a--workspace-rootflag onail check/ail build/ail run, or upward-search from the entry module's directory. Alternatively ratify the flat-workspace assumption in DESIGN.md if intentional.- context: fieldtest 2026-05-11 — fieldtest fixtures could not be
placed under
examples/fieldtest/because of this; predates the canonical-type-names milestone but surfaces every time.
- context: fieldtest 2026-05-11 — fieldtest fixtures could not be
placed under
-
[todo]
check_in_workspaceper-module overlay narrowing —crates/ailang-check/src/lib.rs:1234still clears+rebuildsenv.ctor_indexper-module; ct.3.2 narrowed the analogous mono overlay to types-only. The typecheck-side overlay'senv.ctor_indexhalf serves the duplicate-detection diagnostic at workspace-build time, not the runtime ctor lookup (which is type-driven post-ct.2.2). Narrowing is mechanical but needs a careful read to confirm no other consumer survives.- context: JOURNAL 2026-05-11 ("Iteration ct.4") — milestone close
follow-up; closed by iter ctt.1 — recon confirmed the rebuild is the load-bearing consumer of in-band DuplicateCtor (pinned by
crates/ailang-check/tests/duplicate_ctor_pin.rs); the asymmetry with the mono side is intentional.
- context: JOURNAL 2026-05-11 ("Iteration ct.4") — milestone close
follow-up; closed by iter ctt.1 — recon confirmed the rebuild is the load-bearing consumer of in-band DuplicateCtor (pinned by
-
[feature]
str_concat : (borrow Str, borrow Str) -> Str— heap-Str concatenation primitive. Shipped 2026-05-13 as iter str-concat (closes fieldtest-form-a friction #4). Symmetric to the iter 24.1str_clone/int_to_str/bool_to_strplumbing: runtime C helper (ailang_str_concat),ailang-checkbuiltin registration,ailang-codegenextern +lower_apparm, plus a freshexamples/show_user_adt_with_label.ailcorpus fixture exercising the LLM-natural Show-body shape.- context: per-iter journal 2026-05-13-iter-str-concat.md.
-
[~] [milestone] Stateful islands — bounded mutation for streaming workloads (
Stateful a b+!Muteffect +mutsyntactic block). Adds a sealed mutable-state layer on top of the pure core: aStateful a bfirst-class type whose interior is mutable, whose exterior is a typed callable with!Mutin its effect set, and whose state non-aliasing is enforced by uniqueness inference at the block boundary. Targets the online / streaming workload class — rolling indicators, IIR filters, online aggregates, sensor fusion, online learning — whose mathematics is "new sample + old state → new state + output" per step, and which today's pure-functional state- threading makes ergonomically expensive at scale (multi-record explicit threading for the canonical sliding-window SMA, no zero-allocation pipe combinator, growing tuple-state types as pipelines lengthen).Progress. Decomposed at brainstorm time (2026-05-15) into a sequence of shippable sub-milestones. Sub-milestone 1 closed 2026-05-15: mut-local — sealed-by-construction
mut/var/assignblocks for Int/Float/Bool/Unit scalars, alloca-resident, no escape, no!Muteffect leakage. Foundation in place; fn signatures stay pure while LLM authors can write imperative accumulators directly. Specdocs/specs/2026-05-15-mut-local.md; iters mut.1/mut.2/mut.3/mut.4-tidy (commits7b92719,b24718a,03fb633,20add51). Remaining sub-milestones (to be brainstormed separately, in order): (2) effect-handler infrastructure as a prerequisite for any non-IO/non-Diverge effect; (3)!Muteffectref afirst-class type that can escape mut blocks under uniqueness-tracking; (4) mutable-array primitiveMutArray a; (5)Stateful a bsealed callable type +pipecombinator with zero-allocation composition +runST-equivalent escape discharge.
Motivation. AILang's signature-as-contract thesis is better served by an explicit
Stateful a b+!Mutannotation than by the implicit-closure-mutation idiom of myc / Lua / JS factory patterns: the signature tells the truth about time-identity without the body needing to be read — exactly the LLM-author correctness affordance AILang exists to deliver, extended to a workload class it does not yet serve. Decision 10's constraint #3 forbids shared mutable refs (DESIGN.md:1082); it does not forbid uniqueness-bounded mutation. Lean 4 (ST), Roc (Task), Haskell (ST/IO), and Koka (effects) all use a layered design of this shape to host exactly this workload. AILang's existingown/borrowmode machinery plus its effect-slot architecture are the foundation; this milestone supplies the layer that sits on top.Scope (subject to brainstorm refinement).
Stateful a bas a first-class type — a sealed callable with a hidden mutable env, externally a typed callable whose effect set includes!Mut.!Muteffect, the first non-IO / non-Diverge effect; forces the effect-handler infrastructure forward.mutblock as the syntactic boundary in Form A — outside, AILang's pure self; inside,var/assign/ mutable arrays legal. (Iteration is not part of this boundary: the "Iteration discipline" P1 milestone supersedes the earlier "while-loops legal" scope here — repetition is structural recursion or namedloop/recur, never awhileover mutable state.)var x = expr+assign x exprAST nodes, legal only insidemut.- Mutable array primitive (
MutArray a, O(1) index/update under uniqueness) — co-developed because the ring-buffer use case that motivates the whole effort has no carrier today. pipe : Stateful a b → Stateful b c → Stateful a cas a built-in combinator with zero-allocation lowering — composition is fusion at codegen, not closure-pair layering at runtime.- Decision 12 (or amendment to Decision 10) that names uniqueness-bounded mutation as the legitimate exception to "no shared mutable refs", and the pure / mutable-island layering as the architectural shape.
Blockers (hard prerequisites + open design questions).
-
Effect handlers absent. DESIGN.md:2663 — "No effect handlers — only the built-in IO and Diverge ops."
!Mutis the first non-trivial effect and forces handler infrastructure to ship. May warrant lifting out as its own prerequisite milestone; brainstorm decides. -
Uniqueness inference through
varcaptures. Current inference operates over the immutable RC graph; mutable bindings captured by closures need a more powerful pass. Lean 4 has a known algorithm; AILang does not implement it. -
No mutable-array primitive. No
Array a, no slab container of any kind in the language today. Separate spec needed inside this milestone (representation,own-only orborrow-able, bounds-checking discipline). -
runST-equivalent escape discharge. Producing aStatefulthat legitimately escapes itsmutblock while proving the inner state doesn't leak. Haskell's rank-2 trick (runST :: (forall s. ST s a) -> a) is unavailable — DESIGN.md:1930 confirms higher-rank polymorphism is not supported. Need an alternative — likely sealed-by-construction at the factory boundary, or an effect-mode tag that gates escape. -
Form A surface design. Decision 1 forbids macros (source = data, not text), so
mut/var/assignare genuine AST nodes with round-trip-invariant coverage. Surface needs LLM- utility validation (does the author reach forvar/mutunprompted?) before implementation. -
Codegen for in-place struct-field writes.
Term::ReuseAstoday lowers ADT in-place rewrite; no direct mutable struct- field-write path exists.crates/ailang-codegen/src/escape.rsplus the lowering passes need extension. -
Boundary escape analysis. "Interior state doesn't leak" is an escape analysis specifically over
var/refvalues; the existing pass covers allocations, not mutable cells. -
Decision-10 status. Whether to amend Decision 10 inline or add a Decision 12 that names the mutable-island layer alongside the pure layer. The latter is probably cleaner — Decision 10 stays load-bearing for the pure layer, Decision 12 names the extension and its discipline.
-
Streaming bench corpus. Current corpus (list_sum, tree_walk, closure_chain, hof_pipeline) is all pure. Streaming-specific benches (SMA pipeline, IIR filter, online stats) plus an external baseline (myc, hand-C, Python/NumPy) need to exist to validate that the layered design hits the zero-alloc performance target that myc demonstrates.
-
LLM-utility test. DESIGN.md §"Feature-acceptance criterion" is the gate: the surface must produce code an LLM author naturally writes. Fieldtest after spec, before any code commits.
-
context: 2026-05-15 chat on the
~/sma_factory.mycanalysis — myc's stateful-closure-plus-pipe idiom delivers a streaming workload pattern (3-line SMA factory, 2-line pipeline, zero runtime allocation per tick) that AILang's pure-only model cannot match without this layered extension. Pending brainstorm will producedocs/specs/<date>-stateful-islands.mdand decide the effect-handler-prerequisite question.
P3 — Ideas
-
[todo]
compare_primitives_smoke.ailcounterpart. Satisfied 2026-05-13 by milestone form-a-default-authoring —examples/compare_primitives_smoke.ailis the canonical authoring form for that fixture. Form A is now the default authoring surface across the entire corpus.- context: closed incidentally by form-a iter form-a.1.
-
[todo] Codegen
lookup_ctor_in_patterntype-anchoring —crates/ailang-codegen/src/lib.rs:1790still walks every module's ctor_index by bare ctor name. Plumbing the scrutinee's qualifiedType::Conthrough pattern lowering would type-anchor it symmetric to the ct.2.2 typecheck-side fix. Not load-bearing (uniqueness is enforced at typecheck), but cleaner.- context: JOURNAL 2026-05-11 ("Iteration ct.3" + ct.4 close).
-
[todo]
compare_primitives_smokeIR-shape assertion — observecompare__Int/compare__Bool/compare__Strsymbols in the emitted IR. Blocked onemit-irCLI not running mono; resolution paths include extending the CLI to run mono, using library APIs directly in e2e.rs, or adding--dump-irtoail build. The E2E stdout assertion already covers correctness; the IR-shape test would catch refactor regressions that rename mono symbols.- context: JOURNAL 2026-05-11 ("Iteration ct.4") — dropped from ct.4.4 when the BLOCKED condition surfaced.
-
[idea] Latency methodology rework — switch from per-run timing to a histogram-based approach so tail-latency regressions are visible without re-running. Queued from 21'g.
-
[idea] Parser-test backfill for 22b.4a-era duplicate-clause sites (class × 3, class method × 2, instance × 3, instance method × 1). No regression pin today; only worth it if a 22b.4a-era diagnostic needs to change.
- context: JOURNAL 2026-05-10 ("Iteration 22-tidy.7")
-
[idea]
write_typeType::Forallarm incrates/ailang-prose/src/lib.rssilently drops constraints in inline-type rendering. Dormant — surface forms today only carry forall at fn-signature top level. Fix only if a future feature carries forall + constraints inline.- context: JOURNAL 2026-05-10 ("Iteration 22-tidy.6")
-
[idea] Richer integration paths between RC and uniqueness — deferred from the 21' arc; revisit once the uniqueness inference covers more program shapes.