246b5c7455
Ships fn print : forall a. Show a => (a borrow) -> () !IO in the
prelude with explicit-let body \\x -> let s = show x in do
io/print_str s. Three new E2E fixtures + tests verify the full
path:
- show_print_smoke: 4 primitives smoke (print 42 / true / 'hello' /
3.14) — compile, run, expected stdout
- show_user_adt: data IntBox + instance prelude.Show IntBox +
print (MkIntBox 7) — stdout '7'
- show_no_instance: let f : Int -> Int = \\x -> x in do print f —
fires Show-aware NoInstance with DESIGN.md §Prelude(built-in)
classes cross-reference
IR-shape pin in print_mono_body_shape.rs asserts post-mono
print__Int.body is structurally Term::Let → App(show__Int) →
Do(io/print_str). Protects the explicit let-binder for the heap-Str
RC discipline per eob.1 Str carve-out.
Three plan-defects-fixed-inline surfaced during user-ADT E2E,
necessary repairs to make the spec's stated user-ADT trajectory
work end-to-end:
(a) mono.rs (2 sites): MonoTarget::FreeFn::type_args were carrying
bare type-cons references; normalised to canonical
<owner>.<bare> form via workspace_registry.normalize_type_for_lookup
so synthesised cross-module bodies' post-mono walks reach the
registry-keyed instance entries. Symmetric to the existing
class-method-arm normalisation.
(b) codegen/lib.rs (3 sites: resolve_top_level_fn, lower_app
cross-module arm, synth_with_extras Var arm): post-mono
synthesised bodies may carry cross-module references to modules
their source template didn't import (prelude.print__<UserType>
references show_user_adt.show__<UserType> even though prelude
does not import user modules). Fall back to direct
module_user_fns lookup when prefix not in import_map. Both
ends were independently typechecked before mono ran; the
cross-module ref is created by mono not by source.
(c) lib.rs synth FreeFnCall arm: walked Type::Forall.constraints,
substituted rigid vars with fresh metavars, pushed one
ResidualConstraint per declared constraint. Without this,
print f at Int -> Int would silently typecheck and fail with
a confusing 'unknown variable: show' at codegen rather than
fire the right typecheck-time NoInstance diagnostic.
NoInstance Float-aware arm in check/lib.rs:770-779 extended with
a parallel class == 'prelude.Show' branch that cross-references
DESIGN.md §Prelude(built-in) classes verbatim. Negative-fixture
test asserts code 'no-instance' + Show substring + Prelude-(built-in)-
classes substring.
DESIGN.md §Prelude(built-in) classes milestone-24 paragraph flips
fn print from 'ships in 24.3' to 'shipped in iter 24.3' with body
shape + pin file reference. §Float semantics gains a Show-Float
NaN-spelling cross-reference paragraph linking show 1.5 / show nan /
show inf to instance Show Float via float_to_str.
Roadmap P1 'Post-22 Prelude — Show + print rewire' flipped to [x]
with closing summary naming all three shipped iters (24.1 / 24.2 /
24.3). New P2 entry 'Retire io/print_int|bool|float effect-ops +
migrate example corpus to print' inserted at top of P2.
Tests: 556 passed (was 552 + 4 new). bench/cross_lang exit 0;
bench/compile_check + bench/check exit 1 on documented noise-class
metrics per the audit-cma lineage envelope (8th consecutive
audit-grade observation, baseline pristine per conservative-call).
Milestone 24 closes structurally with this iter. Standard audit
pipeline next.
257 lines
15 KiB
Markdown
257 lines
15 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 — canonical-type-names milestone closed 2026-05-11; Floats
|
||
closed 2026-05-10 and was removed once it stopped being load-bearing
|
||
context. Pick the next milestone from P1.)_
|
||
|
||
## 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\]** Retire `io/print_int` / `io/print_bool` /
|
||
`io/print_float` effect-ops + migrate example corpus to `print`.
|
||
Bulk text substitution `do io/print_<T> x` → `do print x` across
|
||
`examples/*.ail.json` (~86 fixtures affected). Per-type effect-ops
|
||
are deleted from `crates/ailang-check/src/builtins.rs`,
|
||
`crates/ailang-codegen/src/lib.rs::lower_app` arms, and the
|
||
corresponding runtime C glue.
|
||
- context: post-milestone-24 mechanical follow-up; the architecture-
|
||
shipping milestone (24) and corpus-migration milestone (this) are
|
||
separate so migration runs against a frozen architecture. Same
|
||
call milestone 23 made for `==` / `eq`.
|
||
|
||
- [ ] **\[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.
|
||
- [ ] **\[todo\]** CLI human-mode diagnostic surface for `WorkspaceLoadError` —
|
||
non-JSON `ail check` routes ct.1 errors via `anyhow`/`thiserror`
|
||
Display (`Error: <message>`) instead of going through
|
||
`workspace_error_to_diagnostic`, so the bracketed `[code]` prefix
|
||
the JSON path carries is missing. Plausibly applies to every CLI
|
||
subcommand that calls `load_workspace(&path)?` directly.
|
||
- context: JOURNAL 2026-05-11 ("Iteration ct.1") — surfaced by ct.1.8 tester.
|
||
- [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\]** 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.
|
||
|
||
## P3 — Ideas
|
||
|
||
- [ ] **\[todo\]** `compare_primitives_smoke.ail` counterpart —
|
||
the canonical happy-path exhibit for canonical-type-names ships
|
||
only as JSON. Per Decision 6, Surface is the LLM-author surface;
|
||
the milestone should have a `.ail` counterpart. Two paths: author
|
||
`examples/compare_primitives_smoke.ail` to round-trip into the
|
||
existing JSON, OR retire the JSON fixture in favour of
|
||
`examples/ct_1_ordering_signum.{ail,ail.json}` as the new canonical
|
||
happy-path exhibit.
|
||
- context: fieldtest 2026-05-11 (spec_gap).
|
||
- [ ] **\[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.
|