Files
AILang/docs/roadmap.md
T
Brummel c41e0e5a9e tidy setup: retire effect-op-arg-modes bundle; split out effect-doc-honesty + AILang-code spec discipline
The effect-op-arg-modes brainstorm spec bundled a real, self-contained
DESIGN.md honesty fix with speculative build-ahead infra (an EffectOpSig
arg-mode field whose only consumer was a synthetic test op). Recon had
already destroyed the "effect-handler infrastructure" premise: the effect
raise/declare/subset/propagation machinery is fully generic over any
effect string; the only real prerequisite was one struct field, not a
milestone. The user rejected the bundle as incoherent and the
build-ahead half as the iteration-discipline trap repeated.

- Retire the uncommitted effect-op-arg-modes spec (deleted from the
  working tree; never committed).
- roadmap: drop the false "sub-ms-2 = effect-handler infrastructure"
  framing; mark Stateful-islands sub-milestone sequencing as UNDER
  RE-THINK with the user (no !Mut planning until that conversation);
  add the split-out effect-doc-honesty as an in-flight P2 [~] todo.
- plan docs/plans/2026-05-16-effect-doc-honesty.md: placeholder-free
  tidy plan (the three DESIGN.md fictions + form_a.md + main.rs +
  a new doc-presence pin) over recon-mapped exact byte sites.
- brainstorm SKILL: specs and design talk LEAD with the AILang (.ail)
  program that should work (= the feature-acceptance clause-1
  evidence); the Rust implementation shape is secondary, never a
  substitute; a no-surface infra milestone still shows concrete .ail
  (often a must-fail fixture). Codifies user feedback from this session.
2026-05-16 13:18:20 +02:00

532 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 in
`docs/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.
- Optional `depends on:` line names another entry that has to land
first.
- Optional `context:` line points to the journal entry (per-iter
file under `docs/journals/` or, for pre-2026-05-11 entries, the
archived `docs/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
- [x] **\[milestone\]** Heap-`Str` ABI — runtime infrastructure for
malloc-backed, refcounted `Str` values alongside the existing
static `@.str_*` globals. Today the `Str` path is static-only
(DESIGN.md §"Float semantics" notes this explicitly), so any
primitive that needs to produce a `Str` at runtime — `int_to_str`,
`float_to_str` (currently type-installed but `CodegenError::Internal`
on call), eventual `Show.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 heap `Str` at the same ABI slot,
wire RC `inc`/`drop`/`clone`/`compare`/`eq` on the heap form, and
ship `int_to_str` + `float_to_str` as the first two callers so the
ABI gets exercised end-to-end. Unblocks Show + print rewire below.
- context: DESIGN.md §"Float semantics" (`float_to_str` is 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 an `int_to_str` primitive returning heap-allocated Str").
- [x] **\[milestone\]** Post-22 Prelude — Show + print rewire — shipped
2026-05-13 as iters 24.1 (heap-Str runtime + codegen for `bool_to_str`
+ `str_clone`, f38bad8), 24.2 (prelude `class Show` + four primitive
instances Int/Bool/Str/Float + 22b TShow/tshow migration), and 24.3
(polymorphic `fn 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 `MethodNameCollision` workaround that
blocked the original spec retired in mq.3 (2026-05-13); spec
`docs/specs/2026-05-13-24-show-print.md` re-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\]** Iteration-totality story — structural +
Int-bounded total recursion with *enforced* non-negativity.
AILang's iteration story stays as-is (structural / tail recursion;
`tail-app` intact). The genuine ambition — make `f(n-1)`-family
recursion (incl. branching tree builders) total *by construction*
with the non-negative-entry precondition **enforced**, not merely
documented — is deferred here because doing it without a purity-
pillar concession requires refinement/`Nat` type machinery the
language has not built (Decision 4 keeps refinements opaque, no
SMT). Not abandoned; correctly sequenced after the type machinery.
- depends on: a future `Nat`/refinement-types milestone (no spec
yet).
- context: `docs/specs/2026-05-16-iteration-discipline-revert.md`
(why the 2026-05 attempt was reverted) and
`docs/journals/2026-05-15-iter-it.3.md` (the branching-builder
counter-example that surfaced the gap).
- [x] **\[milestone\]** Retire `io/print_int` / `io/print_bool` /
`io/print_float` effect-ops + migrate example corpus to `print`.
Shipped 2026-05-14 as iter rpe.1.
- context: per-iter journal `docs/journals/2026-05-14-iter-rpe.1.md`.
- [x] **\[todo\]** Author `examples/prelude.ail` alongside
`examples/prelude.ail.json`. (Satisfied 2026-05-13 by iter
form-a.0 — `examples/prelude.ail` rendered via `ail render`,
116 lines / 6386 bytes, round-trip-CI green.)
- [x] **\[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.json` to its
`.ail` sibling via `ail render`, **delete the now-redundant
`.ail.json`**, and regenerate the JSON-AST per `ail parse` at
build / test time. Same for inline JSON-AST blocks in `docs/`
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 `.ail` source. The JSON-AST is a
build artefact, not a checked-in twin. Tests and benches that
currently glob `*.ail.json` either parse-then-consume or are
rewired to point at `.ail` directly. The round-trip invariant
flips role: today it gates that the two forms agree; afterwards
it gates that `parse` is deterministic.
Carve-outs that MUST stay JSON-AST (no Form-A counterpart, and
no `.ail` sibling 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.ail` entry 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.
- [ ] **\[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)
- [x] **\[todo\]** `types` / `ctor_index` overlay 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.
- [x] **\[todo\]** CLI human-mode diagnostic surface for `WorkspaceLoadError`.
Shipped 2026-05-14 as iter cli-diag-human — a new `load_workspace_human`
helper in `crates/ail/src/main.rs` routes 9 non-JSON
`ailang_surface::load_workspace(&path)?` call sites through
`workspace_error_to_diagnostic`, so the bracketed `[code]` prefix is
preserved across `ail check`, `build`, `run`, `emit-ir`, `prose`,
`describe`, `deps`, `diff`, `manifest`.
- context: per-iter journal `docs/journals/2026-05-14-iter-cli-diag-human.md`.
- [x] **\[todo\]** Retire dead `KindMismatch` arm — `validate_classdefs`'s
`walk_kind_mismatch` path is structurally unreachable through
well-formed schema post-ct.1 (the canonical-form validator catches
the malformed `Type::Con { name: param }` shape earlier). The
enum variant + `walk_kind_mismatch` helper 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`.
- [x] **\[todo\]** Re-key `Registry.type_def_module` to handle
bare-name-collision-across-modules — `BTreeMap<String, String>` keyed
by bare type name silently overwrites when two modules each define
`type Foo`; `normalize_type_for_registry` would then collapse `M.Foo`
and `N.Foo` to 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
- [x] **\[milestone\]** Module-qualified class names + type-driven
method dispatch — retired the `MethodNameCollision` workaround;
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_classes`
inverse index, multi-candidate `ResidualConstraint`,
`resolve_method_dispatch` 5-step rule, `AmbiguousMethodResolution` +
`UnknownClass` diagnostics), and mq.3 (retirement + multi-class E2E
+ `class-method-shadowed-by-fn` warning + DESIGN.md sync). Two
libraries can now each declare `class Eq` with their own `eq`;
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::synthetic` flag — 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\]** DESIGN.md effect-prose is fiction — standalone
documentation-honesty tidy (split out 2026-05-16 from the rejected
effect-op-arg-modes bundle; in flight via planner→implement). Three
load-bearing effect-system claims in DESIGN.md are presently false
and mislead every future brainstorm + the architect, who read
DESIGN.md as truth: (a) DESIGN.md:172/2722 say `IO` **and**
`Diverge` are wired-up built-in ops — `Diverge` has zero code in
any crate (not registered, never injected, no string literal);
reconcile to "only `IO` (`io/print_str`) is wired up; `Diverge`
reserved/unimplemented", modelled on Decision 4's
reserved-but-opaque refinements precedent. (b) DESIGN.md:171-172
"the effect set is row-polymorphic (`![IO | r]`)" — no row variable
/ `EffectRow` exists anywhere; effect sets are flat string
collections unified by set-equality; remove the claim (a flat
closed set is *more* locally legible — Decision 3 pillar — so this
is honesty, not a concession; pillar-doc edit, flag at review).
(c) `crates/ailang-core/src/ast.rs:438-439` `Term::Do` doc-comment
"resolved against the effect-handler table at link time" — there is
no handler table; it is an `IndexMap` lookup at typecheck + a
literal codegen `match`; correct it. Lockstep: `form_a.md:226,358`
+ CLI-help `main.rs:289` Diverge mentions reconciled. Guard: a new
doc-presence test (false strings absent, corrected text present).
No language/codegen change; pure documentation truth. Grounding
triple-verified 2026-05-16 (three `ailang-grounding-check` PASSes).
- context: surfaced by the effect-subsystem recon during the
retired effect-op-arg-modes brainstorm; see the Stateful-islands
"UNDER RE-THINK" note for why the bundle was rejected.
- [ ] **\[todo\]** `io/print_float` always-emit-`.0` — surface
printer always emits `.` or `e/E` so re-lex routes to Float;
the runtime printer (`printf("%g\n", v)`) doesn't, so `2.0`
prints as `2` (Int-shaped). Asymmetric. Either switch the
runtime path to a `.0`-fallback printer (matching surface) or
document the `%g` contract in DESIGN.md §"Float semantics" so
the LLM-author knows `io/print_float`'s output is for-humans
not round-trip.
- context: `docs/specs/2026-05-10-fieldtest-floats.md` finding F1.
- [ ] **\[todo\]** Rustdoc warning sweep — `cargo doc --no-deps`
reports 16 pre-existing warnings (15 in `ailang-check`, 1 in
`ailang-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.rs` fidelity 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 `BadCrossModuleTypeRef` into 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 way `bare-cross-module-type-ref`
lists candidates from imports.
- context: fieldtest 2026-05-11 — `examples/ct_3*.ail` exhibits both branches with identical-shape diagnostics.
- [ ] **\[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.md` finding F3.
- [ ] **\[todo\]** Workspace search beyond entry-module's directory —
`load_workspace` only finds sibling `.ail.json` files 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-root` flag on `ail 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.
- [ ] **\[todo\]** `*.bump_s` throughput baseline is stale vs current
hardware — `bench/check.py`'s `throughput.*.bump_s` family (the
fastest, most jitter-prone metrics) reads ~+513% over
`bench/baseline.json` on the current machine. Localised at the
iteration-discipline-revert audit (2026-05-16): an interleaved
3×60-run measurement of `bench_list_sum` bump_s built from the
byte-oracle commit `1ff7e81` shows the *same* ~+11% elevation as
HEAD, and the two bump binaries are `cmp`-identical — so this is
environmental drift relative to the 2026-05-09 baseline-capture
machine state, **not** a codegen regression. Re-capture the full
`*.bump_s` set on current hardware from a known-clean commit and
recalibrate the `bump_s` baseline+tolerance pair (or widen the
tolerance) so the noise floor stops tripping `check.py` exit 1.
Pure bench-harness recalibration; no language change.
- context: `docs/journals/2026-05-16-audit-iteration-discipline-revert.md`
(the bencher localisation evidence).
- [x] **\[todo\]** `check_in_workspace` per-module overlay narrowing —
`crates/ailang-check/src/lib.rs:1234` still clears+rebuilds
`env.ctor_index` per-module; ct.3.2 narrowed the analogous mono
overlay to types-only. The typecheck-side overlay's `env.ctor_index`
half 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.
- [x] **\[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.1 `str_clone` / `int_to_str` / `bool_to_str` plumbing:
runtime C helper (`ailang_str_concat`), `ailang-check` builtin
registration, `ailang-codegen` extern + `lower_app` arm, plus a
fresh `examples/show_user_adt_with_label.ail` corpus 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` + `!Mut` effect + `mut`
syntactic block). Adds a sealed mutable-state layer on top of
the pure core: a `Stateful a b` first-class type whose interior
is mutable, whose exterior is a typed callable with `!Mut` in
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`/
`assign` blocks for Int/Float/Bool/Unit scalars, alloca-resident,
no escape, no `!Mut` effect leakage. Foundation in place;
fn signatures stay pure while LLM authors can write imperative
accumulators directly. Spec `docs/specs/2026-05-15-mut-local.md`;
iters mut.1/mut.2/mut.3/mut.4-tidy (commits 7b92719, b24718a,
03fb633, 20add51).
**Sub-milestone sequencing is UNDER RE-THINK with the user
(2026-05-16) — no further sub-milestone is planned or brainstormed
until that conversation happens.** Why: the 2026-05-15 decomposition
named "(2) effect-handler infrastructure as a prerequisite for any
non-IO/non-Diverge effect" as the next step. A read-only recon of
the live effect subsystem (2026-05-16) showed that premise is false:
the effect raise / declared-set / `UndeclaredEffect`-subset / call-
propagation machinery is already fully generic over an arbitrary
effect string; `!Mut` needs **no** effect-handler machinery. The
only real code prerequisite the recon found is one hard-coded
assumption (`linearity.rs` walks every `Term::Do` arg as `Borrow`;
`EffectOpSig` has no per-arg mode field) — a single struct field,
not a milestone. A brainstorm spec that tried to make sub-ms-2 a
standalone deliverable bundled that speculative one-field capability
with an unrelated DESIGN.md honesty fix; the user correctly rejected
the bundle as incoherent and the build-ahead-of-consumer half as the
iteration-discipline trap repeated. **Resolution:** (a) the DESIGN.md
effect-honesty correction is split out and runs now as a standalone
documentation tidy (see the P2 `[todo]` "DESIGN.md effect-prose is
fiction" below); (b) the `arg_modes` finding is recorded as recon
context for the future `!Mut` design, NOT built ahead — it is
first-iteration material of whatever the `!Mut` milestone turns out
to be, validated there against a real consuming op, never a
synthetic test op; (c) the old "(2)…(5)" sequence (effect-handler
infra → `!Mut`+`ref a``MutArray a``Stateful a b`+`pipe`) is
no longer treated as settled — the whole ordering and granularity
is what the pending user conversation re-decides.
**Motivation.** AILang's signature-as-contract thesis is *better*
served by an explicit `Stateful a b` + `!Mut` annotation 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 existing
`own` / `borrow` mode 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 b` as a first-class type — a sealed callable with a
hidden mutable env, externally a typed callable whose effect
set includes `!Mut`.
- `!Mut` effect, the first non-IO / non-Diverge effect; forces
the effect-handler infrastructure forward.
- `mut` block 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: AILang has no
`while`/`for` and this milestone does not introduce one —
repetition stays recursion, exactly as the language has it today
(structural / tail recursion); a `mut` block is a sealed
expression, never a loop over mutable state.)
- `var x = expr` + `assign x expr` AST nodes, legal only inside
`mut`.
- 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 c` as 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." `!Mut` is 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 `var` captures.* 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 or `borrow`-able,
bounds-checking discipline).
- *`runST`-equivalent escape discharge.* Producing a `Stateful`
that legitimately escapes its `mut` block 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` / `assign` are genuine AST
nodes with round-trip-invariant coverage. Surface needs LLM-
utility validation (does the author reach for `var` / `mut`
unprompted?) before implementation.
- *Codegen for in-place struct-field writes.* `Term::ReuseAs`
today lowers ADT in-place rewrite; no direct mutable struct-
field-write path exists. `crates/ailang-codegen/src/escape.rs`
plus the lowering passes need extension.
- *Boundary escape analysis.* "Interior state doesn't leak" is an
escape analysis specifically over `var` / `ref` values; 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.myc` analysis —
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 produce `docs/specs/<date>-stateful-islands.md` and decide
the effect-handler-prerequisite question.
## P3 — Ideas
- [x] **\[todo\]** `compare_primitives_smoke.ail` counterpart.
Satisfied 2026-05-13 by milestone form-a-default-authoring —
`examples/compare_primitives_smoke.ail` is 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_pattern` type-anchoring —
`crates/ailang-codegen/src/lib.rs:1790` still walks every module's
ctor_index by bare ctor name. Plumbing the scrutinee's
qualified `Type::Con` through 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_smoke` IR-shape assertion —
observe `compare__Int` / `compare__Bool` / `compare__Str` symbols
in the emitted IR. Blocked on `emit-ir` CLI not running mono;
resolution paths include extending the CLI to run mono, using
library APIs directly in e2e.rs, or adding `--dump-ir` to `ail
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_type` `Type::Forall` arm in
`crates/ailang-prose/src/lib.rs` silently 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.