|
3 | 3 |
|
4 | 4 | # Stdlib AOT triage — #135 punch list |
5 | 5 |
|
6 | | -**Generated 2026-05-17**, against the compiler at `feat/135a-lambda-expr` |
7 | | -(origin/main + #131/#134 merged, #133/PR #152 auto-merging, + #135 slice 1 |
8 | | -`fn(params) => expr`). This is the live per-file state of driving every |
9 | | -`stdlib/*.affine` through `resolve → typecheck → codegen` (Deno-ESM). |
| 6 | +**Refreshed 2026-05-17** after merging slices 1, 2, 3, 6. Live per-file |
| 7 | +state of driving every `stdlib/*.affine` through |
| 8 | +`resolve → typecheck → codegen` (Deno-ESM). |
10 | 9 |
|
11 | | -Methodology: `affinescript compile stdlib/<f>.affine -o /tmp/x.js |
12 | | ---deno-esm`, first error per file, classified by pipeline stage. |
| 10 | +## Done (merged to main) |
13 | 11 |
|
14 | | -## Status snapshot |
15 | | - |
16 | | -| File | Stage | Site | Root cause / slice | |
| 12 | +| Slice | What | PR | |
| 13 | +|---|---|---| |
| 14 | +| #131 | `>>` nested-generic close (keystone) | #149 | |
| 15 | +| #134 | prelude unwrap/unwrap_result soundness | #150 | |
| 16 | +| #132 | namespace ADR-011 | #151 | |
| 17 | +| #133 | single-ownership dedup | #152 | |
| 18 | +| #135 sl.1 | `fn(x) => e` anon-function expressions | #153 | |
| 19 | +| #135 sl.2 | slice/range index `e[a:b]` via `slice` builtin | #154 | |
| 20 | +| #135 sl.3 | bare `effect E;` + ADR-008 `-> T / E` row | #155 | |
| 21 | +| #135 sl.6 | `try`→`attempt`, `ref`→`make_ref` (keyword-as-ident) | #156 | |
| 22 | + |
| 23 | +## Current sweep |
| 24 | + |
| 25 | +| File | Stage | Site | Slice / root cause | |
17 | 26 | |---|---|---|---| |
18 | 27 | | `string` | ✅ OK | — | full pipeline | |
19 | 28 | | `Core` | ✅ OK | — | full pipeline | |
20 | | -| `Ajv` `Crypto` `Grammy` `Network` `Sqlite` `Vscode` `VscodeLanguageClient` | ✅ OK | — | full pipeline (already `module`-declared) | |
21 | | -| `prelude` | TYPECHECK | `Unify.TypeMismatch (T, Int)` | **Slice 7** — empty-array literal `let result = []` then `result ++ [..]` not generalised | |
22 | | -| `option` | PARSE | `238:15` `Some(list[1:])` | **Slice 2** — slice/range index `list[1:]` | |
23 | | -| `collections` | PARSE | `24:35` `take(n-1, list[1:])` | **Slice 2** — same slice syntax | |
24 | | -| `result` | PARSE | `221:1` `fn try<T>(...)` | **Slice 6** — `try` is a reserved keyword (TRY); used as a fn name | |
25 | | -| `effects` | PARSE | `5:8` `effect io;` | **Slice 3** — bare `effect <name>;` declaration form | |
26 | | -| `testing` | PARSE | `302:3` `let total = ...;` then `{` | **Slice 4** — statement sequencing: `let;` followed by a block expr | |
27 | | -| `math` | PARSE | `354:3` `let total = 0.0;` then `for` | **Slice 4** — same: `let;` followed by `for` statement | |
28 | | -| `traits` | PARSE | `12:43` `pub fn ne(ref self, ...) -> Bool {` | **Slice 5** — trait method *default bodies* / `ref self` receiver | |
29 | | -| `io` | RESOLVE | `Resolve.UndefinedVariable` | **Slice 8** — builtin/extern + namespace wiring; interacts with #132/#133 module model | |
30 | | - |
31 | | -9/19 already compile end-to-end. The remaining 10 reduce to **7 distinct |
32 | | -feature slices**, none of which is the #131 angle-bracket or #135-slice-1 |
33 | | -lambda defect (both fixed). |
34 | | - |
35 | | -## Slices (proposed order, each its own PR) |
36 | | - |
37 | | -Ordering rationale: do the two-for-one parser slices first (max files |
38 | | -unblocked per change), keep the model-coupled one (Slice 8) until after |
39 | | -#133/PR #152 lands, and the typecheck/keyword ones are independent. |
40 | | - |
41 | | -### Slice 2 — slice/range index `list[1:]` *(unblocks `option`, `collections`)* |
42 | | -Postfix index currently accepts `e[i]` only. Add range-index |
43 | | -`e[a:b]` / `e[a:]` / `e[:b]` / `e[:]` to the postfix-expr rule, lowering |
44 | | -to the existing slice/`Array` op (check `lib/parser.mly` postfix rule + |
45 | | -how `typecheck.ml`/codegen model slices — `string` already slices, so a |
46 | | -lowering target likely exists). Two files, one feature. Independent. |
47 | | - |
48 | | -### Slice 3 — `effect <name>;` declaration *(unblocks `effects`)* |
49 | | -Grammar has `effect_decl` for `effect E { ops }`; `effects.affine` uses |
50 | | -the bare forward-declaration form `effect io;`. Add the bare form to |
51 | | -`effect_decl` (empty-op effect) or a dedicated production. Independent; |
52 | | -small. |
53 | | - |
54 | | -### Slice 4 — statement sequencing: `let …;` then block/`for` *(unblocks `testing`, `math`)* |
55 | | -Both fail where a `let …;` statement is immediately followed by another |
56 | | -statement that is a block expr (`{ … }`) or a `for`. Root cause is in |
57 | | -the block/statement-list rule (the `list(stmt)` vs `expr_record_body` |
58 | | -reduce/reduce noted in the grammar). Needs the statement-sequence rule |
59 | | -disambiguated so a trailing-semicolon `let` composes with a following |
60 | | -block/`for`. Independent; medium (touches the known r/r conflict — do |
61 | | -rigorously, re-check conflict counts before/after). |
62 | | - |
63 | | -### Slice 5 — trait method default bodies + `ref self` *(unblocks `traits`)* |
64 | | -`trait_decl` accepts method *signatures*; `traits.affine` provides |
65 | | -*default bodies* (`pub fn ne(ref self, ref other: Self) -> Bool { … }`). |
66 | | -Add optional `fn_body` to trait method items and accept the `ref self` |
67 | | -receiver form. Independent; medium. |
68 | | - |
69 | | -### Slice 6 — `try` as a value identifier *(unblocks `result`)* |
70 | | -`try` is the TRY keyword (try/catch). `result.affine` defines |
71 | | -`fn try<T>(…)`. Decide: (a) make `try` a contextual keyword (allow as |
72 | | -fn name / ident), or (b) rename the stdlib function (e.g. `attempt`). |
73 | | -(a) is the resolve-at-source fix but wider; (b) is local and safe. |
74 | | -Recommend (b) unless `try`-as-ident is wanted broadly. Independent; small. |
75 | | - |
76 | | -### Slice 7 — empty-array literal generalisation *(unblocks `prelude`)* |
77 | | -`map`/`filter` do `let result = []; for x in arr { result = result ++ [f(x)] }`. |
78 | | -`[]` is inferred at a concrete element type (`Int`) and then conflicts |
79 | | -with `[U]`. Needs the empty-array literal to take a fresh element tyvar |
80 | | -unified by later use (or an annotation path). Typecheck/inference, not |
81 | | -parsing. Independent; medium — touch `typecheck.ml` array-literal rule |
82 | | -with care + regression test. |
83 | | - |
84 | | -### Slice 8 — `io` resolution / builtin + namespace *(unblocks `io`)* — **do after #133/PR #152** |
85 | | -`io.affine` references runtime builtins (`print`, `read_file`, …) the |
86 | | -static resolver doesn't know. This couples to the module/extern model |
87 | | -(ADR-011 / #132) and the #133 ownership changes. Sequence it after |
88 | | -PR #152 (#133) lands so it is solved against the real module model, not |
89 | | -the interpreter-era flat namespace. Medium; model-coupled. |
90 | | - |
91 | | -## After all slices |
92 | | - |
93 | | -Then #135 closes; unblocks #136 (CI AOT smoke gate), #137 (multi-module |
94 | | -integration test), #138 (remove b895374 band-aid). Estimated 3–5 further |
95 | | -focused sessions for slices 2–8 (each: feature + tests + conflict |
96 | | -re-verification), per the "rigorous over partial-hack" discipline. |
97 | | - |
98 | | -## Done so far (this epic) |
99 | | - |
100 | | -| Issue | PR | State | |
101 | | -|---|---|---| |
102 | | -| #131 `>>` nested-generic | #149 | merged | |
103 | | -| #134 prelude unwrap soundness | #150 | merged | |
104 | | -| #132 namespace ADR-011 | #151 | merged | |
105 | | -| #133 single-ownership dedup | #152 | auto-merging | |
106 | | -| #135 slice 1 (`fn(x)=>e`) | this PR | open | |
| 29 | +| `Ajv`/`Crypto`/`Grammy`/`Network`/`Sqlite`/`Vscode`/`VscodeLanguageClient` | ✅ OK | — | full pipeline | |
| 30 | +| `traits` | PARSE | `12:43` | **Slice 5** — trait method *default body* + `ref self` (`pub fn ne(ref self, ...) { ... }`) | |
| 31 | +| `collections` | PARSE | `40:13` | **Slice 6b** — `as` used as a parameter name (`fn zip<A,B>(as: [A], ...)`); `as` is the AS keyword. Same class as slice 6 → rename (`elems`) | |
| 32 | +| `math` | PARSE | `354:3` | **Slice 4** — `if cond { ... }` (no else) used as a statement between other statements; the parser commits to if-as-trailing-expr | |
| 33 | +| `testing` | PARSE | `302:3` | **Slice 4** — record literal `{ f: v }` as the block's final expression after statements (the pre-existing `list(stmt)` vs `expr_record_body` r/r) | |
| 34 | +| `option` | PARSE | `320:15` | **Slice 9** — `&mut Option<T>` reference parameter type (`fn take<T>(opt: &mut Option<T>)`); flagged in #128 itself (take/get_or_insert + affine/borrow lowering) | |
| 35 | +| `result` | RESOLVE | — | **Slice 8** — module/`use prelude::{...}` resolution (post-#133 model) | |
| 36 | +| `io` | RESOLVE | — | **Slice 8** — builtin/extern + namespace resolution; model-coupled | |
| 37 | +| `prelude` | TYPECHECK | `Unify (T, Int)` | **Slice 7 (root cause refined)** — *not* a simple empty-array literal (that pattern compiles in isolation). Deeper type-instantiation: `fold`'s `(U,T)->U` HOF instantiated monomorphically by `sum`/`product` (`fold(arr, 0, …)` forces `0:Int`). Needs careful typecheck investigation | |
| 38 | +| `effects` | TYPECHECK | "Not implemented: Too many arguments for kind" | **Slice 10** — kind-checking of generic externs (`extern fn make_ref<T>(x: T) -> Ref<T> / state`); distinct codegen/kind defect | |
| 39 | + |
| 40 | +9/19 compile end-to-end. Parse walls + deeper defects regroup into the |
| 41 | +slices below. |
| 42 | + |
| 43 | +## Remaining slices — difficulty & autonomy |
| 44 | + |
| 45 | +**Safe to do autonomously (bounded, low risk):** |
| 46 | +- **Slice 6b** — `as` param-name in `collections.affine` → rename |
| 47 | + (`elems`/`xs`). Pure stdlib edit, zero grammar risk. ~15 min. |
| 48 | +- **Slice 5** — trait default bodies + `ref self`. `trait_item` |
| 49 | + already has `TraitFnDefault f` (line ~537); the gap is the `ref self` |
| 50 | + receiver in trait-method position. Grammar-bounded, medium; verify |
| 51 | + conflict counts. |
| 52 | + |
| 53 | +**Needs care — rigorous, not auto-rushed:** |
| 54 | +- **Slice 4** (testing, math) — block/statement ambiguity. The hardest: |
| 55 | + `if`/`while`/`for` as statements vs trailing-expr, and record-literal |
| 56 | + vs block `{`. This is the grammar's pre-existing `list(stmt)` / |
| 57 | + `expr_record_body` reduce/reduce. High regression risk; needs a |
| 58 | + careful block-grammar restructure with full conflict + suite |
| 59 | + re-verification. Multi-step. |
| 60 | +- **Slice 9** (option) — `&mut T` reference parameters with |
| 61 | + reassignment. Touches affine/borrow lowering (the #128-noted hard |
| 62 | + case). Correctness-critical (ownership) — must be rigorous. |
| 63 | +- **Slice 7** (prelude) — type-checker instantiation of HOF `fold` |
| 64 | + under monomorphic `sum`/`product`. Correctness-critical typecheck |
| 65 | + code; investigate, don't guess. |
| 66 | +- **Slice 8** (result, io) — module/extern resolution against the |
| 67 | + post-#133 model (ADR-011). Model-coupled. |
| 68 | +- **Slice 10** (effects) — generic-extern kind-checking |
| 69 | + ("Too many arguments for kind"). Distinct kind/codegen defect. |
| 70 | + |
| 71 | +## Closure |
| 72 | + |
| 73 | +#135 closes when all files compile resolve→typecheck→codegen; then |
| 74 | +#138 (remove b895374 band-aid), #136 (CI AOT smoke gate), #137 |
| 75 | +(multi-module integration test). The safe slices (6b, 5) are ~1 short |
| 76 | +session; slices 4/7/8/9/10 are correctness-/grammar-critical and are |
| 77 | +each their own focused, rigorous unit (the "rigorous over partial-hack" |
| 78 | +discipline applies — no guessed changes to typecheck/borrow/block |
| 79 | +grammar). |
0 commit comments