Skip to content

Commit b8a0f9c

Browse files
hyperpolymathclaude
andcommitted
feat(quantity): Stage 11 — source-level Cmd linearity (QTT enforcement)
Adds source-level Cmd linearity to the QTT quantity checker: - typecheck.ml: register Cmd as KArrow(KType,KType); add cmd_none and cmd_perform builtins to the type environment - resolve.ml: expose cmd_none and cmd_perform as built-in names - quantity.ml: - is_cmd_type / quantity_of_ty_annotation: infer QOne from Cmd[_] type annotations on let-bindings (ExprLet + StmtLet) - infer_usage_block: declare QOne locals in env before processing subsequent statements so uses are tracked; check at block end (LinearVariableUnused fired when Cmd is dropped) - interp.ml: runtime values for cmd_none (VUnit) and cmd_perform - test/e2e/fixtures/cmd_linear.affine: Cmd returned in tuple → OK - test/e2e/fixtures/cmd_dropped.affine: Cmd dropped → quantity error - test/test_e2e.ml: 4 new E2E Cmd Linearity tests (173/173 pass) The AffineScript TEA guarantee: a forgotten Cmd side-effect obligation is now a compile-time type error, not a silent behavioural bug. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 39995dc commit b8a0f9c

21 files changed

Lines changed: 491 additions & 756 deletions

COMPILER-CAPABILITIES.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,24 +122,24 @@ WebAssembly Binary (.wasm)
122122
```
123123

124124
### Supported Features
125-
-**Lexer & Parser:** Complete (note: lexer does not yet emit ZERO/ONE quantity literal tokens — blocks QTT surface syntax)
126-
- ⚠️ **Type Checker:** Rules implemented (bidirectional inference in `lib/typecheck.ml`), but **experimental (enforcement landing soon)** — not yet wired into `check`/`compile`/`eval` CLI paths
127-
- ⚠️ **Affine Types / QTT:** **experimental (enforcement landing soon)** — quantity semiring in `lib/quantity.ml` exists but is not called from `bin/main.ml`
128-
- ⚠️ **Borrow Checker:** **experimental (enforcement landing soon)**`lib/borrow.ml` carries `[IMPL-DEP: Phase 3]` markers; runtime checks in interpreter only
125+
-**Lexer & Parser:** Complete (quantity sugar `:0/:1/:ω` and attribute form `@erased/@linear/@unrestricted` accepted)
126+
- **Type Checker:** Wired into `check`/`compile`/`eval` CLI paths
127+
- **Affine Types / QTT:** Live gate (`Quantity.check_program_quantities` runs in standard CLI pipeline)
128+
- ⚠️ **Borrow Checker:** Live gate with ongoing Phase 3 work
129129
- ⚠️ **Dependent / Refinement Types:** parse-only — `TRefined` AST node exists but predicates do not reduce
130-
-**WASM Codegen:** Complete (basic operations)
130+
-**WASM Codegen:** Primary backend (feature gaps remain for advanced effect-handler lowering)
131131
-**Julia Codegen:** Phase 1 (basic types)
132132
-**Native Codegen:** Not implemented
133133
-**GPU Codegen:** Not implemented
134-
- **Optimizer:** Not implemented
134+
- ⚠️ **Optimizer:** Basic constant folding in `lib/opt.ml`; no full optimization pipeline yet
135135

136136
### Missing Features
137137
-**SIMD Operations:** No WASM SIMD support
138138
-**Multithreading:** No WASM threads
139139
-**Tail Calls:** Not implemented
140140
-**Reference Types:** Not implemented
141-
- **Exception Handling:** Not implemented
142-
- **Garbage Collection:** No GC (manual memory management)
141+
- ⚠️ **Exception Handling in WASM Backends:** `try/finally` lowering exists; `try/catch` still requires EH proposal/CPS path
142+
- ⚠️ **Garbage Collection:** WASM GC backend exists (`--wasm-gc`) but is not full feature parity
143143

144144
---
145145

@@ -394,4 +394,4 @@ Parallel Operations| N/A | 2x-4x (cores) | 1000x (cores)
394394
**Recommendation:** For current development, use WASM deployment targeting browsers. For future-proof design, plan for native and GPU backends coming in later releases.
395395

396396
SPDX-License-Identifier: PMPL-1.0-or-later
397-
SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
397+
SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell

README.adoc

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ image:https://img.shields.io/badge/Powered%20By-Gossamer%20%26%20Burble-orange.s
1414

1515
[NOTE]
1616
====
17-
*Honest status (2026-04-10):* AffineScript's affine types, QTT quantity checking, borrow checker, and dependent types are **experimental (enforcement landing soon)**. The type-checking rules for these features are implemented in `lib/typecheck.ml` and `lib/quantity.ml`, but they are not yet wired into the `check`/`compile`/`eval` CLI paths, so user programs are not gated on them today. Track A of the Manhattan plan closes this gap; ETA T+7d. See `.machine_readable/6a2/STATE.a2ml` `[features]` for the per-feature honest breakdown.
17+
*Honest status sync (2026-04-12):* Affine/QTT and borrow checking are wired into the CLI pipeline (`check`, `compile`, `eval`) and gate user programs today. Specifically, `Typecheck.check_program`, `Borrow.check_program`, and `Quantity.check_program_quantities` are all invoked from `bin/main.ml`. Dependent/refinement types remain parse-first (predicates do not reduce yet), and codegen backends still have feature gaps (notably effect-handler lowering in Wasm backends). See `.machine_readable/6a2/STATE.a2ml` `[features]` for the authoritative per-feature status.
1818
====
1919

2020
AffineScript is the only language that combines affine types, quantitative type theory, row polymorphism, and algebraic effects in a single practical systems language. This means you can write game code where the compiler *proves* your protocol states, resource lifecycles, and effect boundaries are correct -- with syntax that feels like a modern game scripting language, not a theorem prover.
@@ -124,15 +124,15 @@ AffineScript's type system proves your game logic is correct at compile time. No
124124
// Game resource that MUST be properly managed
125125
type GameTexture = own { id: Int, width: Int, height: Int }
126126
127-
fn load_texture(path: ref String) -> own GameTexture / IO + Exn[LoadError] {
127+
fn load_texture(path: ref String) -{IO + Exn[LoadError]}-> own GameTexture {
128128
GameTexture { id: 42, width: 1024, height: 1024 }
129129
}
130130
131-
fn render(scene: ref Scene, texture: ref GameTexture) -> () / Render {
131+
fn render(scene: ref Scene, texture: ref GameTexture) -{Render}-> () {
132132
// Use the texture in rendering
133133
}
134134
135-
fn unload(1 texture: own GameTexture) -> () / IO {
135+
fn unload(@linear texture: own GameTexture) -{IO}-> () {
136136
// texture is consumed -- can't use it after this
137137
// Compiler guarantees no memory leaks!
138138
}
@@ -141,9 +141,9 @@ fn unload(1 texture: own GameTexture) -> () / IO {
141141
// - texture is loaded before use
142142
// - texture is not used after unload
143143
// - texture is always unloaded (affine: must be consumed)
144-
fn game_loop() -> () / IO + Render + Exn[LoadError] {
144+
fn game_loop() -{IO + Render + Exn[LoadError]}-> () {
145145
let texture = load_texture("player.png"); // texture: own GameTexture
146-
render(ref scene, ref texture); // borrows texture
146+
render(scene, texture); // borrows texture
147147
unload(texture); // consumes texture
148148
// texture is GONE here -- using it would be a compile error!
149149
// No memory leaks, no dangling pointers, no crashes.
@@ -162,8 +162,8 @@ type Connection[..state] = own {
162162
163163
// State transition: Unauthenticated -> Authenticated
164164
fn authenticate(
165-
1 conn: own Connection[{status: Unauthenticated}]
166-
) -> Connection[{status: Authenticated, user: String}] / Session + IO {
165+
@linear conn: own Connection[{status: Unauthenticated}]
166+
) -{Session + IO}-> Connection[{status: Authenticated, user: String}] {
167167
let creds = recv();
168168
// conn is consumed (linear), new state returned
169169
Connection { socket: conn.socket, status: Authenticated, user: creds.user }
@@ -173,7 +173,7 @@ fn authenticate(
173173
fn query(
174174
conn: ref Connection[{status: Authenticated, ..r}],
175175
sql: ref String
176-
) -> Result[Rows, DbError] / IO {
176+
) -{IO}-> Result[Rows, DbError] {
177177
// ...
178178
}
179179
----
@@ -190,17 +190,17 @@ fn query(
190190
[source,affinescript]
191191
----
192192
effect IO {
193-
fn print(s: String);
193+
fn print(s: String) -> ();
194194
fn read_line() -> String;
195195
}
196196
197197
effect State[S] {
198198
fn get() -> S;
199-
fn put(s: S);
199+
fn put(s: S) -> ();
200200
}
201201
202202
// The type signature tells you EXACTLY what this function can do
203-
fn interactive_counter() -> Int / IO + State[Int] {
203+
fn interactive_counter() -{IO + State[Int]}-> Int {
204204
let input = read_line();
205205
let current = get();
206206
let next = current + 1;
@@ -210,7 +210,7 @@ fn interactive_counter() -> Int / IO + State[Int] {
210210
}
211211
212212
// Pure functions are PROVEN pure -- no hidden I/O
213-
fn add(a: Int, b: Int) -> Int / Pure {
213+
fn add(a: Int, b: Int) -> Int {
214214
a + b
215215
}
216216
----
@@ -220,7 +220,7 @@ fn add(a: Int, b: Int) -> Int / Pure {
220220
[source,affinescript]
221221
----
222222
// Works on ANY record with a 'name' field
223-
fn greet[..r](person: {name: String, ..r}) -> String / Pure {
223+
fn greet[..r](person: {name: String, ..r}) -> String {
224224
"Hello, " ++ person.name
225225
}
226226
@@ -240,7 +240,7 @@ type Vec[n: Nat, T: Type] =
240240
| Cons(head: T, tail: Vec[n, T]) : Vec[n + 1, T]
241241
242242
// Can ONLY be called on non-empty vectors
243-
total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T / Pure {
243+
total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T {
244244
match v {
245245
Cons(h, _) => h
246246
}
@@ -249,7 +249,7 @@ total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T / Pure {
249249
// Result length is provably the sum of input lengths
250250
total fn append[n: Nat, m: Nat, T](
251251
a: Vec[n, T], b: Vec[m, T]
252-
) -> Vec[n + m, T] / Pure {
252+
) -> Vec[n + m, T] {
253253
match a {
254254
Nil => b,
255255
Cons(h, t) => Cons(h, append(t, b))
@@ -304,31 +304,32 @@ total fn append[n: Nat, m: Nat, T](
304304
dune build
305305
306306
# Type check a file
307-
dune exec affinescript -- check examples/hello.affine
307+
_build/default/bin/main.exe check test/e2e/fixtures/affine_basic.affine
308308
309309
# Run with interpreter
310-
dune exec affinescript -- eval examples/factorial.affine
310+
_build/default/bin/main.exe eval test/e2e/fixtures/interp_simple.affine
311311
312312
# Compile to WebAssembly
313-
dune exec affinescript -- compile examples/hello.affine -o hello.wasm
313+
_build/default/bin/main.exe compile -o hello.wasm test/e2e/fixtures/wasm_simple.affine
314314
315315
# Format code
316-
dune exec affinescript -- fmt examples/hello.affine
316+
_build/default/bin/main.exe fmt test/e2e/fixtures/affine_basic.affine
317317
318318
# Lint for code quality
319-
dune exec affinescript -- lint examples/hello.affine
319+
_build/default/bin/main.exe lint test/e2e/fixtures/affine_basic.affine
320320
321321
# JSON diagnostics (for editors/CI)
322-
dune exec affinescript -- check --json examples/hello.affine
322+
_build/default/bin/main.exe check --json test/e2e/fixtures/affine_basic.affine
323323
----
324324

325325
== Project Status
326326

327-
**Version 0.8 Frontend Complete:** The frontend surface syntax for core type features (Linear Arrows `-[q]->`, Generic Variants, Refinement Types, Kind Checking) parses and elaborates. Note: several of these features are *experimental (enforcement landing soon)* — the quantity checker is not yet called from the CLI, the lexer does not yet emit ZERO/ONE quantity literal tokens, and refinement predicates parse but do not reduce. See `.machine_readable/6a2/STATE.a2ml` `[features]` for per-feature status.
327+
**Status sync (2026-04-12):** This section is aligned with `.machine_readable/6a2/STATE.a2ml`.
328+
Core affine/QTT and borrow gates are live in the standard CLI paths. Dependent/refinement types are still parse-only. Effects run in the interpreter; Wasm backend support is still partial for handlers/resume semantics.
328329

329-
NOTE: As of v0.8, the standard file extension for AffineScript is `.affine` (formerly `.affine`, changed to avoid clashes with ActionScript).
330+
NOTE: The standard file extension for AffineScript source is `.affine`.
330331

331-
AffineScript is in active development. The frontend is solid; the backend and advanced type features are in progress.
332+
AffineScript is in active development with active weekly commits and a passing `dune runtest` baseline.
332333

333334
[cols="2,1,3"]
334335
|===
@@ -343,51 +344,56 @@ AffineScript is in active development. The frontend is solid; the backend and ad
343344
|Module loader, scope analysis, import system
344345

345346
|Type Checker
346-
|Working
347-
|Bidirectional inference, unification, let-polymorphism. Quantities and effects not yet integrated into inference.
347+
|Wired
348+
|Bidirectional inference/unification active in `check`/`compile`/`eval` pipeline.
348349

349350
|Effect System
350-
|Declarations only
351-
|Effect types parsed and tracked; handler semantics not yet in interpreter or backends
351+
|Interpreter complete
352+
|Effect operations + handlers + `resume` wired in interpreter. Wasm backends have handler-dispatch limitations.
352353

353354
|Quantity Checking
354-
|Separate pass
355-
|Usage tracking works; needs integration with type checker for full QTT
355+
|Live gate
356+
|QTT quantity checks run after typecheck and fail builds on violations.
356357

357358
|Borrow Checker
358-
|Partial
359-
|Affine tracking at runtime in interpreter; compile-time checking in progress
359+
|Live gate (Phase 3 in progress)
360+
|Compile-time borrow checks run in CLI pipeline; advanced phases still in progress.
360361

361362
|Trait System
362-
|Skeletal
363-
|Registry and lookup work; generic trait resolution needs unification integration
363+
|Partial
364+
|Registry + unification-based impl lookup wired; associated-type/coherence work remains.
364365

365366
|Interpreter
366-
|75%
367-
|Closures, pattern matching, builtins. No effect handlers or exceptions yet.
367+
|95%
368+
|Closures, pattern matching, effects, and recent try/catch/finally plumbing.
368369

369370
|WASM Codegen
370-
|30%
371-
|Basic arithmetic, functions, closures. Types collapse to i32; records missing.
371+
|92%
372+
|Primary deploy backend. Effect-handler lowering remains incomplete.
373+
374+
|WASM GC Codegen
375+
|70%
376+
|GC backend available via `--wasm-gc`; not full feature parity with interpreter.
372377

373378
|Julia Codegen
374-
|10%
375-
|Phase 1 MVP skeleton only
379+
|Phase 1
380+
|Basic generation path exists; not the primary deployment backend.
376381

377382
|LSP Server
378-
|Phase A done
379-
|JSON diagnostics via `--json`; go-to-definition and references pending
383+
|Complete (Phases A-D)
384+
|`server --stdio` shipped with hover, goto-def, completion, diagnostics.
380385

381386
|Formatter + Linter
382387
|Complete
383388
|AST-based formatter; 4 lint rules
384389
|===
385390

386-
=== Roadmap: v0.8 to v0.9
387-
The immediate priority for v0.9 is to fully propagate the v0.8 language features down through the entire stack, solidifying the architecture for the **Airborne Submarine Squadron** remake:
388-
* Propagate v0.8 syntax (like `-[q]->`), semantics, and type system upgrades down through the AST, Interpreter, REPL, and Standard Library.
389-
* Update the compiler backend (WASM generation) to fully support the newly checked Generic Variants and Refinement constraints.
390-
* Finalize the metacompiler and metainterpreter features.
391+
=== Next Priorities (Post-Sync)
392+
The immediate priorities are execution and drift control:
393+
* Keep `README`, `ROADMAP`, `COMPILER-CAPABILITIES`, and `.machine_readable/6a2/STATE.a2ml` synchronized on each feature milestone.
394+
* Finish parser/README/example syntax convergence around effects and quantity annotations.
395+
* Close remaining backend gaps for effect handler lowering so interpreter and Wasm behavior converge.
396+
* Keep `examples/` parse-checkable in CI to prevent documentation drift from reappearing.
391397

392398
== Design Principles
393399

ROADMAP.adoc

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@
44
:toclevels: 3
55
:icons: font
66

7-
Development plan for AffineScript from current state (85% complete) to production-ready 1.0 release.
7+
Historical development plan from the January 2026 baseline.
8+
9+
[IMPORTANT]
10+
====
11+
Status sync note (2026-04-12): This file contains historical planning text and
12+
is not the authoritative source for current feature readiness. Use
13+
`.machine_readable/6a2/STATE.a2ml` for live status (including affine/QTT
14+
wiring, borrow-checker gating, effect handling status, row polymorphism, and
15+
dependent/refinement progress).
16+
====
817

918
toc::[]
1019

11-
== Current Status (January 2026)
20+
== Current Status (January 2026 Baseline)
1221

13-
**Overall Completion: 85%**
22+
**Overall Completion (historical baseline): 85%**
1423

1524
=== ✅ Completed Components
1625

docs/DESIGN-VISION.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ effect Async {
150150
}
151151
152152
// Effects are in the type signature. Compose freely.
153-
fn fetch_user(id: Int) -> User / Http, Async {
153+
fn fetch_user(id: Int) -{Http + Async}-> User {
154154
let resp = Http.get("/users/" ++ show(id));
155155
Async.await(resp.json())
156156
}

0 commit comments

Comments
 (0)