// SPDX-License-Identifier: CC-BY-SA-4.0
// SPDX-FileCopyrightText: 2024-2026 hyperpolymath
= AffineScript Capability Matrix
:toc: macro
:toclevels: 3
:icons: font
:reproducible:

[IMPORTANT]
====
*This document is the single authoritative source for AffineScript feature
readiness.* Where any other document (`README.adoc`,
`docs/architecture/BACKEND-IMPLEMENTATION.md`,
`docs/reference/COMPILER-CAPABILITIES.md`,
`docs/history/ALPHA-1-RELEASE-NOTES.md`, `docs/ROADMAP.adoc`,
`.machine_readable/descriptiles/STATE.a2ml`)
disagrees with this matrix, *this matrix wins* and the other document is drift
to be corrected. Reconstructed and verified 2026-05-19 against
`origin/main`@dc5b817 with the compiler as oracle. Tracks issue
https://github.com/hyperpolymath/affinescript/issues/175[#175] /
https://github.com/hyperpolymath/affinescript/issues/176[#176] (DOC-01..09).
====

toc::[]

== How to read this

Status vocabulary, used precisely:

[cols="1,4"]
|===
|Term |Meaning

|*enforced*
|Wired into the CLI pipeline and gates real user programs today.

|*works*
|Implemented and exercised by the test gate; not necessarily complete.

|*partial*
|Implemented for common cases; known gaps documented in the row.

|*declared-but-unwired*
|Surface syntax and/or an internal module exists, but the feature is not
 enforced on user programs through the CLI.

|*parse-only*
|Surface syntax parses; no semantics behind it yet.

|*absent*
|Does not exist, regardless of what prose elsewhere claims.
|===

Gate baseline at reconstruction: **`opam exec --switch=. -- dune runtest
--force` = 260 tests, 0 failures** (2026-05-19).

== Compiler core

[cols="2,1,4"]
|===
|Component |Status |Notes (truthed)

|Lexer |works |Full Unicode. Quantity-literal `ZERO`/`ONE` tokens are *not*
emitted (QTT numeric surface uses `:1`/`:0`/`:ω` sugar parsed elsewhere).

|Parser |works |Menhir grammar, ADR-009 conformance. Stage B *CLOSED*:
#215 closed with the owner-ratified ADR-012 disposition — the residual
~68 S/R + state-401 R/R conflicts are an *intentional won't-fix* (Menhir
resolves them correctly; the gate proves every parse is intended;
eliminating them is the contortion ADR-012 forbids). `just build` masks
+ proves them, `just build-loud`/`dune build` reveal all. *Module-qualified
type/effect paths now parse* — `Pkg.Type` / `Pkg::Type` (mixed, deep),
`::` canonical (ADR-014, PR #241, Refs #228; zero Menhir conflict delta).
Cleared the dominant estate parse blocker (was 525/1177 `.affine`);
estate re-audit +  residue (#229) follow separately.

|AST |works |All current language constructs represented.

|Name resolution |works |Module loader, `use`/`::` value paths, scope analysis.

|Type checker |works |Bidirectional inference, effect rows, subsumption,
`Never`/bottom, block divergence. Enforced on every check/compile/eval path.

|Effect system |partial |Pure/impure separation + effect polymorphism
enforced. Handlers: *every compiled backend* (core-WASM, WasmGC, Deno-ESM,
JS-text, C) now *fails loud* (`UnsupportedFeature`) instead of silently
dropping handler arms — the execution-verified return-arm divergence (#555)
is closed. Interpreter dispatch is shallow single-shot tail-resume; multi-shot
now fails loud; the one non-tail single-shot residual is pinned by a test. The
#225 CPS async table-miss *fails loud*, not silently synchronous (#556 closed).
Full status + test anchors: link:SOUNDNESS.adoc[SOUNDNESS.adoc].

|Borrow checker |*partial — full CORE-01 slice stack landed, #177
CLOSED 2026-05-30; the #554 soundness hole is now FIXED; Polonius
residual (#553) is M1–M3, test-only/unwired — see link:SOUNDNESS.adoc[SOUNDNESS.adoc]* |
Place/borrow/move infra + use-after-move, conflicting-borrow,
move-while-borrowed, lambda-capture. *CORE-01 part 1* (PR #240, Refs #177,
gate 263/263): borrow-graph validation wired — `BorrowOutlivesOwner` now
emitted (`&local` escaping its block); shared-XOR-exclusive enforced at
*use* sites (`UseWhileExclusivelyBorrowed`); ownership now derived from the
parameter *type* (`TyOwn/TyRef/TyMut`) so the owned/ref/mut discipline is
actually enforced from real parsed source (closed a latent hole); call-arg
borrows made temporary. *Part 2* (Refs #177): return-escape +
`&mut e` parser surface (the exclusive borrow is finally expressible from
source). *Part 3 Slice A* (Refs #177): non-lexical lifetimes —
ref-bindings expire at their binder's *last use*, not at block exit
(`compute_last_use_index` pre-pass + per-statement expiry in
`check_block`). Patterns like `let r = &x; print(*r); x = 2` and
`let m = &mut x; let y = *m; x` now type-check, while still-live borrows
continue to block aliasing assignments. *Part 3 Slice B* (Refs #177):
flow-sensitive escape via re-assignment — `outer = &y` in `StmtAssign`
now pre-releases the held borrow and re-binds the
`(binder -> new_borrow)` ref-graph entry to the freshly-created borrow,
so the NLL last-use + return-escape analyses see the *current* referent
after re-assignment, not the stale original (3 hermetic tests: old
released, new tracked by NLL, anti-regression that new borrow still
protects). *Part 3 Slice C-light* (Refs #177): CFG-join semantics for
non-match join constructs — `ExprHandle` handler arms and `ExprTry`
catch arms are now isolated via snapshot-restore-merge (factored
`merge_arm_results` helper, mirroring `ExprMatch`'s inlined logic).
Moves/borrows from arm i no longer pollute arm i+1; finally runs
deterministically against the merged post-catch state. 2 hermetic
tests (positive + anti-regression). *Since landed (2026-05-26/27):*
Slice C′ loop soundness via 2-iteration re-check (#396 + #399
clear-on-rewrite), Slice D `@linear`-capture-by-closure rejection
(#397), ref-to-ref binding (#395 + #400 self-assign guard). *Deferred:*
true Polonius origin/region variables on `TyRef`/`TyMut` with subset
constraints + datalog-style loan-live-at-point solver — ADR-022,
tracked as #553 (design ratified in-thread, 0% implemented; M1 sketch
at branch `core-01/polonius-m1-sketch`); quantity-checker tightening
for captured linears. *Former soundness hole (#554) — now FIXED:*
use-after-move through a *callee-returned borrow* (`let r = pick(a);
consume(a); *r`) is now rejected as `MoveWhileBorrowed`, with hardening +
anti-over-rejection fixtures. The per-issue, test-anchored status (and the
Polonius #553 extractor's test-only standing) lives in
link:SOUNDNESS.adoc[SOUNDNESS.adoc].

|Quantity / affine types |*enforced* |QTT semiring in `lib/quantity.ml`,
invoked inside the standard CLI pipeline. `@linear`/`@erased`/`@unrestricted`
(canonical) + `:1`/`:0`/`:ω` sugar. Scaled-Let (ADR-002) closes BUG-001/002.
Linear arrows enforced.

|Interpreter |works |Pattern matching, control flow, effect handler dispatch,
`resume`. `let mut` mutation path has known baseline gaps.

|Traits |partial |Registry, `TopTrait`/`TopImpl`, unification-based
`find_impl`, impl-satisfies-trait, and *coherence checking* (#559 — overlapping
impls whose self types unify are rejected; wired in `typecheck.ml`
`check_all_coherence`). Missing: associated-type substitution in method bodies,
where-clause supertraits, generic-subsumption overlap (#559 follow-up). See
link:SOUNDNESS.adoc[SOUNDNESS.adoc].

|Dependent / refinement types |*removed (v1)* |Withdrawn 2026-04-10 rather than
shipped parse-only-and-unenforced (#558): no `TRefined` node remains,
`assume(...)` is rejected at parse, `T where (P)` is a parse error — you cannot
write an unenforced predicate. See link:SOUNDNESS.adoc[SOUNDNESS.adoc].

|Row polymorphism |partial |Records + effect rows in typecheck/unify; not
fully exercised end-to-end.

|Formatter / linter |works |AST pretty-printer; 4 lint rules. `affinescript
fmt` is the canonical-form authority (will own the ADR-014 `.`→`::`
normalisation once #228 lands).

|LSP |works |Phases A–D: hover, goto-def, completion, JSON-RPC
`server --stdio`.
|===

== Backends (truthed — breadth is real but uneven)

The compiler emits to many targets. *Maturity is not uniform; do not claim
"N production backends".* Order is by enforcement depth.

[cols="2,1,4"]
|===
|Target |Status |Notes

|WASM (core) |works |Cross-module imports + `extern fn` → WASM import
emission. The reference target.

|WASM-GC |partial |Variant-with-args + same-arity `PatCon` destructuring;
silent-bad-codegen fallbacks eliminated. Mixed-arity matches need uniform
variant rep; effects/try-catch/`call_ref` deferred (EH/CPS — #225 line).

|typed-wasm |*contract, narrow* |Not a backend flag — a *discipline-bearing
shape* over the WASM output: the `typedwasm.ownership` custom section +
`lib/tw_interface.ml` / `lib/tw_verify.ml` boundary verifier. Emitted-wasm
enforcement covers *typed-wasm Level 7 (aliasing) + Level 10 (linearity)
+ Level 13 (module isolation, negative form)*.
Formal spec: link:specs/TYPED-WASM-INTERFACE.adoc[specs/TYPED-WASM-INTERFACE.adoc]
(+ link:specs/TYPED-WASM-INTERFACE.a2ml[machine companion]).
Widening roadmap (L1–6, L14–16, multi-producer ABI): link:specs/TYPED-WASM-ROADMAP.adoc[specs/TYPED-WASM-ROADMAP.adoc].
Producer-side; target spec is the separate `hyperpolymath/typed-wasm` repo.

|Deno-ESM |works |Direct AST→ES-module transpiler (`lib/codegen_deno.ml`),
`--deno-esm` / `.deno.js`. Shipped + consumer-verified (ubicity).

|Node-CJS |works |`lib/codegen_node.ml`; CJS shim, handle table, the
`.affine` VS Code extension compiles through it. Live-host smoke
harness for the compiled extension landed via PR #317 / issue #139:
`@vscode/test-electron` + Mocha under `editors/vscode/test/` runs in
the `vscode-smoke` CI job, asserting activation, command registration,
`restartLsp` cycling, and `deactivate` teardown.

|Julia, JS, Rust, Lua |works (transpile) |`Module_loader.flatten_imports`
inlines imported public fns; smoke-tested.

|C, WGSL, Faust, ONNX, OCaml, Bash, Nickel, , LLVM, Verilog, Gleam,
CUDA, Metal, OpenCL, MLIR, Why3, Lean, SPIR-V
|partial / experimental
|Code-generators exist and build. Coverage varies widely; treat as
*experimental* unless a row above promotes them. Any non-Wasm backend with
no `FnExtern` story raises `Match_failure` at runtime by design (correct
"this target has no host-impl story" signal).
|===

== Standard library

`stdlib` ~95%; all `stdlib/*.affine` files compile resolve→typecheck→codegen
with the AOT CI gate (`test/test_stdlib_aot.ml`) — file list is discovered
dynamically, so new modules are gated on landing. Stage C closed 2026-05-18.
Portable primitives Http (#160) / Json (#161) / Dict-Map (#162) plus the
db-theory triplet — `Sqlite.affine` (#522 / #524 / #525), `Transaction.affine`
(#526; affine-bounded write-set isolation), `Aggregate.affine` (#527;
group-by + aggregation primitives) — are the current authoring frontier.
A few primitives remain `extern` builtins.

== Test anchors

Every enforcement claim in the tables above is anchored to an executable test,
mirroring the link:SOUNDNESS.adoc[soundness ledger]. This makes the claims
*falsifiable*: `tools/check-capability-anchors.sh` (wired into `just check` +
CI) extracts every `test/…` path named below and fails the build if any has
gone missing — so a `works` / `enforced` row cannot keep its status after its
test is deleted or renamed without this section (and CI) noticing.

[cols="2,4",options="header"]
|===
|Component (row above) |Anchoring test(s)

|Lexer |`test/test_lexer.ml`
|Parser / AST / Name resolution |`test/test_e2e.ml`, `test/test_qualified_paths.ml`
|Type checker |`test/test_e2e.ml`, `test/test_slash_effect_row.ml`
|Effect system |`test/test_effects.ml`, `test/test_effect_sites.ml`; fixtures `test/e2e/fixtures/handle_return_arm.affine`, `test/e2e/fixtures/handle_resume_tail.affine`
|Borrow checker |`test/test_borrow_polonius.ml`; fixtures `test/e2e/fixtures/borrow_callee_returned_borrow_uam.affine`, `test/e2e/fixtures/borrow_callee_value_return_ok.affine`
|Quantity / affine (QTT) |`test/test_e2e.ml`; fixtures `test/e2e/fixtures/linear_arrow_violation.affine`, `test/e2e/fixtures/affine_violation.affine`, `test/e2e/fixtures/erased_violation.affine`
|Interpreter |`test/test_e2e.ml`, `test/test_solo_cesk.ml`, `test/test_module_mut.ml`
|Traits (incl. #559 coherence) |`test/test_e2e.ml` (Trait-Coherence suite)
|Stdlib (AOT + laws) |`test/test_stdlib_aot.ml`, `test/test_stdlib_laws.ml`
|typed-wasm isolation |`test/test_tw_isolation.ml`
|Codegen (golden snapshots) |`test/test_golden.ml`
|Deno / JS host backends |`test/test_deno_builtins_consistency.ml`, `test/test_int_div_js.ml`
|Solo core (executable metatheory) |`test/test_solo_cesk.ml`
|===

NOTE: this anchors *feature-readiness* claims. *Soundness-hole* anchors live in
their own ledger (link:SOUNDNESS.adoc[SOUNDNESS.adoc]), gated by
`tools/check-soundness-ledger.sh`.

== What AffineScript is NOT (anti-over-claim)

* Not "production-ready". Alpha. CORE-01 (#177) closed 2026-05-30. The
  base-language soundness holes tracked through 2026-06 are now fixed, fenced,
  or removed — #554 (use-after-move via a callee-returned borrow) is rejected,
  #555 (effect handlers) fail loud on every compiled backend, #556 (async
  fallback) fails loud, #558 (refinements) were removed — with one pinned
  interpreter residual. The per-issue, test-anchored ledger is
  link:SOUNDNESS.adoc[SOUNDNESS.adoc]. Closing these *implementation* holes is
  not the same as *proving* soundness: the metatheory remains prose
  (link:PROOF-NEEDS.adoc[PROOF-NEEDS.adoc]). The v1 release-readiness ledger
  is #563.
* Not "N production backends". One reference target (WASM), two solid JS-host
  targets (Deno-ESM, Node-CJS), the rest experimental.
* typed-wasm is *not* an AffineScript subsystem. It is a separate,
  language-agnostic target with its own repo, proofs, and other producers
  (ephapax). AffineScript is one producer among several.
* The satellite ecosystem is mostly scaffold/skeleton — see the satellite
  registry in `ECOSYSTEM.adoc`. `affinescript-tea`/`-dom-loader`/`-cadre`
  were imaginary until the #175 scaffolds; `-dom`/`-pixijs` are skeletons.

== Change control

Any PR that re-introduces backend-breadth, "production-ready", or
stdlib-percentage over-claims must be rejected (DOC-01..09 / issue #176;
Hypatia/gitbot `DOC-FORMAT`/`DOC-DEDUP` rules). This is enforced
*mechanically* in-repo by the single guard `tools/check-doc-truthing.sh`
(DOC-16; wired into `just check` + CI): it holds the banner/primacy/mirror
invariant *and* ratchets against new over-claim phrasings beyond the frozen
`tools/doc-overclaims.allow` baseline. Update *this file* in the same PR as
any capability change; `STATE.a2ml` mirrors, it does not lead.

== See also

* link:SOUNDNESS.adoc[SOUNDNESS.adoc] — the single source of truth for
  soundness-hole status (test-anchored). This matrix owns *feature readiness*;
  the ledger owns *soundness holes*.
* link:ECOSYSTEM.adoc[ECOSYSTEM.adoc] — the spine, the real AS↔typed-wasm
  contract, the satellite registry, the Stage A–E definitions, INT-01..12.
* link:TECH-DEBT.adoc[TECH-DEBT.adoc] — the coordination ledger.
* `hyperpolymath/typed-wasm` — the target spec (separate repo).
