37ac704bf3
One forward iteration; main never rewound.1ff7e81(the pre-9973546 commit) is the per-region byte oracle — every reverted source/test file is byte-identical to it; crates/ailang-check/src/lib.rs fully so. Root cause being corrected: the Iteration-discipline milestone was an over-escalation of fieldtest finding F1 (a [friction] item whose own minimal recommendation was a DESIGN.md note). Its totality dichotomy made the maximally-LLM-natural build(d:Int)=Node(1,build(d-1), build(d-1)) inexpressible (it.3 BLOCKED); the only in-thesis escape (A1/it.2b) conceded the language's first documented-unenforced totality precondition — a purity-pillar dilution the user rejected in favour of a full revert + rebuild. Removed: Term::Loop/Term::Recur/LoopBinder; the verify_structural_ recursion guardedness pass + term_contains_loop + Diverge-injection + the transitively-it.2 module_fns plumbing; the five Recur*/ NonStructuralRecursion CheckError variants (+ code() + the 3 dedicated ctx() arms); the it.1 codegen loop-header/phi/back-edge + parallel block_terminated setter; all Loop/Recur walker arms; 16 it.1/it.2 fixtures; 2 pin files; bench/it3-oracle/. Restored: 2 RC fixtures to1ff7e81content. Surgically kept (not in1ff7e81, landed with the milestone but independently sound): feature-acceptance clause 3 in DESIGN.md and skills/brainstorm/SKILL.md, with its worked example de-claimed from "shipped" to hypothetical-illustration form; the F3 P2 todo. bench/orchestrator-stats/2026-05-15-iter-it.{1,2,3}.json kept as historical record (like journals/plans). Sole net addition: an honest F1/F4 documented-idiom note in DESIGN.md (the tail-recursive accumulator fallback; examples/mut_counter.ail), guarded by a doc-presence test — "a documentation note is not a reshape", asserts nothing at the typecheck level. Roadmap: the Iteration-discipline block + blocking-fork section removed; the genuine total-Int-recursion ambition preserved as a deferred P2 milestone sequenced behind a future Nat/refinement-types milestone (not abandoned — correctly sequenced after the type machinery it needs). 2026-05-15-iteration-discipline.md carries a superseded header; it.1/it.2/it.3 journals + plans stay as history. Correctness gate PRISTINE: 164 surviving 1ff7e81-era fixtures ail check/ail run byte-identical to pre-milestone behaviour (verified against a1ff7e81worktree reference compiler, zero drift); cargo test --workspace 600/0; zero residual it.1/it.2 production surface. Spec docs/specs/2026-05-16-iteration-discipline-revert.md (b3853bf), plan docs/plans/2026-05-16-iter-revert.md (abf0013).
466 lines
26 KiB
Markdown
466 lines
26 KiB
Markdown
# 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\]** `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.
|
||
- [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). Remaining sub-milestones (to be brainstormed
|
||
separately, in order): (2) effect-handler infrastructure as a
|
||
prerequisite for any non-IO/non-Diverge effect; (3) `!Mut` effect
|
||
+ `ref a` first-class type that can escape mut blocks under
|
||
uniqueness-tracking; (4) mutable-array primitive `MutArray a`;
|
||
(5) `Stateful a b` sealed callable type + `pipe` combinator with
|
||
zero-allocation composition + `runST`-equivalent escape discharge.
|
||
|
||
**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: the "Iteration
|
||
discipline" P1 milestone supersedes the earlier "while-loops
|
||
legal" scope here — repetition is structural recursion or named
|
||
`loop`/`recur`, never a `while` 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.
|