Repository navigation
Rust-like record syntax migration: bare { = block, records use #{ } (resolves #215 families C+D) #218
Copy link
Copy link
Closed
Labels
enhancementNew capability or improvement to existing behaviourNew capability or improvement to existing behaviourmajorMajor issue — significant scope, broader impact than a feature/bugMajor issue — significant scope, broader impact than a feature/bug
Description
Decision
To eliminate the block-vs-record-literal parser ambiguity (families
C+D of #215) structurally rather than by a human-facing lookahead
heuristic, AffineScript moves to a Rust-like model:
{in expression position is ALWAYS a block.- Record/struct literals use the
#{ … }sigil — both anonymous
(#{ x: 1 }) and typed (Foo #{ x: 1 }). This also removes the
Rust struct-literal-in-if/match-scrutinee hazard entirely. - Record patterns (
match x { Foo { a } => … }) are unaffected —
pattern position has no block alternative, so no ambiguity there.
Owner-approved (2026-05-18). Supersedes the lookahead-heuristic option.
Status of the grammar change
Implemented and correct on branch stage-c/pc-brace-disambig:
HASH_LBRACE token (token.ml), #{ lexer rule (lexer.ml), both
ExprRecord productions rerouted (parser.mly). Conflicts dropped
72→68 S/R, 10→7 R/R, build clean. Not merged — breaking; needs the
migration to land atomically with it.
Scope (true cost, measured)
- ~60 of 353
.affinefiles in the affinescript repo use the old
record syntax (test fixtures, golden oracles, examples, stdlib) —
21 tests currently fail (expected, pre-migration). - Wider estate
.affinefiles on top of that. - A residual reduce/reduce remains even after migration
(state 401,LBRACE IF expr block option(else_part)— a distinct
if-expr-as-statement ambiguity, family-D-residual) plus ~68 S/R;
tracked as follow-on families, NOT blocking this migration.
Plan
- Codemod (linchpin): instrument the origin/main parser (old
grammar still parses old syntax) to emit eachExprRecord's
LBRACEbyte-offset per file; a script inserts#at those
offsets (descending). Precise — only true record literals, never
blocks/declarations/match/if. Validate on a sample. - Atomic affinescript PR: grammar/lexer/token change
(stage-c/pc-brace-disambig) + codemod-migrated repo.affine
(stdlib + tests + examples + golden) so the 257 gate goes green
with the new syntax. Conflict delta reported. - Estate sweep: run the codemod across all estate
.affine
repos, one PR per repo (or batched), each gated. - Residual families (separate, after migration lands): state-401
R/R + remaining S/R → continue Eliminate all Menhir parser conflicts (75 S/R + 10 R/R) — dedicated grammar-conflict workstream #215 family work. - Docs: update language reference / guides for
#{.
Done when
#{ is the sole record-literal syntax estate-wide, affinescript 257
gate green, conflict count reported, residual families re-listed on
#215.
🤖 Generated with Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew capability or improvement to existing behaviourNew capability or improvement to existing behaviourmajorMajor issue — significant scope, broader impact than a feature/bugMajor issue — significant scope, broader impact than a feature/bug
Activity
hyperpolymath commentedon May 18, 2026
Status update (post-recovery + accurate scoping)
Env incident recovered: affinescript main clone vanished mid-work (WSL fs instability under /home/.../dev); re-cloned to /tmp/affinescript. Branches restored & pushed durable:
stage-c/pc-brace-disambig(grammar, d22be56) +stage-c/codemod(tooling). Nothing merged was lost.Parser bug fixed & merged — #219:
Parse.parse_filehad a non-exhaustive Token→Parser match missingEXTERN→ runtime Match_failure on ALL extern code (every FFI stdlib file). Fixed; 257 green. This unblocked stdlib parsing.Accurate scope (the feared estate migration is small):
{recursive:}was a doc comment). Not affected.Foo {…}records: 8 sites / 6 fixture files.{x: 42}records (e.g. tests/codegen/test_record_simple, tests/scratch/test_spread*, conformance/007_struct_def): the OLD grammar's block-vs-record ambiguity often parses these as blocks, so the AST codemod can't detect them — but the NEW grammar makes them unambiguously blocks (→ parse error). These ~tens of small test fixtures must be migrated via compiler-as-oracle: apply grammar change, run 257, each failure pinpoints a{→#{site, fix, repeat.Revised plan: codemod does the typed-record bulk; compiler-oracle iteration does the bare-anon fixtures; combine with grammar branch → atomic PR → 257 green. Estate sweep (step 3) likely near-empty (stdlib clean; consumers rarely use bare-anon records — to be confirmed by an estate grep).
feat(#218)!: complete Rust-like record migration — 257/257 green
hyperpolymath commentedon May 18, 2026
Step 2 DONE — PR #222 open (review-gated, breaking), 257/257 green.
Grammar (bare
{=block, records#{}) + HASH_LBRACE token-bridge in both parse.ml & parse_driver.ml + all expression-position record literals migrated (compiler-as-oracle; 12 files incl stdlib/testing.affine — the one stdlib record the earlier parse-gated survey missed). Type-position/struct-decl/block/match-arm-block unchanged. Conflicts 72→68 S/R, 10→7 R/R.Note: the AST codemod (step 1) proved unsound in ambiguous cases (mis-located braces onto fn-body
{because it trusts the OLD grammar's broken disambiguation) — migration was done via compiler-as-oracle + hand verification instead. Codemod retained only as an artifact.Remaining: #222 review/merge → estate
.affinegrep (step 3, likely tiny) → #215 residual families (state-401 R/R + ~68 S/R) +#{docs (step 4-5).feat!: Rust-like record syntax — bare { = block, records use #{ } (#218…
hyperpolymath commentedon May 18, 2026
Step 3 (estate sweep) COMPLETE. Rigorous regression diff (pre-#222 vs post-#222 oracle) across all .affine-bearing estate repos. Genuine own-code record regressions found in 9 repos — all migrated, each parse-verified + diff-sanity-checked, one PR per repo:
panll#42 · nextgen-databases#21 · odds-and-sods-package-manager#29 · idaptik#91 · nextgen-language-evangeliser#12 · developer-ecosystem#73 · stapeln#54 · standards#137 · airborne-submarine-squadron#21
Vendored
affinescript/trees (proof-burrower, bofj-kitt, etc. — ~hundreds of files) excluded by design: they track upstream and migrate via re-vendoring from the now-migrated affinescript main, not parallel edits. airbornearchive/src/main_*.affinefrozen dead-code snapshots also excluded (zero value, corruption risk).Key finding: estate own-code record footprint was tiny (~16 files / 9 repos) — the bulk of
.affinerecord hits were vendored upstream copies. The AST codemod proved unsound (mislocates braces in ambiguous/nested cases) and was correctly abandoned for compiler-as-oracle + per-file hand verification throughout.Remaining on #218: steps 4-5 (#215 residual conflict families: family B
return-greedy, state-401 R/R, ~68 S/R) +#{language-reference/guide docs. Per owner direction, STAGE-C (#160, A→E spine) resumes now; steps 4-5 follow.docs: document Rust-like #{ } record syntax (#218 step 5) (#223)
fix(affine): migrate record literals to #{ } (affinescript#218) (#42)
feat(grammar)!: return/resume as diverging prefix exprs + ADR-012 + c…
feat(stdlib): portable Http primitive — Deno-ESM target (Refs #160) (#…
fix(affine): migrate record literal to #{ } (affinescript#218) (#73)
fix(affine): migrate record literals to #{ } (affinescript#218) (#21)
fix(affine): migrate record literals to #{ } (affinescript#218) (#12)
fix(affine): migrate record literals to #{ } (affinescript#218) (#29)
fix(affine): migrate record literals to #{ } (affinescript#218) (#137)
fix(affine): migrate src/main.affine record literals to #{ } (affines…
fix(affine): migrate record literals to #{ } (affinescript#218) (#28)
fix(affine): migrate record literals to #{ } (affinescript#218) (#38)
hyperpolymath commentedon May 18, 2026
Coverage scan + sweep closure (2026-05-18)
Ran an org-wide
.affinecoverage scan to confirm step-3 (estate sweep) is exhaustive. It was not — fixed now.Sweep PRs — all 9 own-code repos MERGED: panll#42, stapeln#54 (already merged) + developer-ecosystem#73, idaptik#91, nextgen-databases#21, nextgen-language-evangeliser#12, odds-and-sods-package-manager#29, standards#137, airborne-submarine-squadron#21 (merged this session).
Coverage gaps found & closed (2 repos had no PR):
#{. Auto-merge armed..affinefiles (rest arepub type … = { }type decls, correctly untouched). Auto-merge armed.Both migrated by hand-verified per-site edits (the regex codemod is known-unsound); each repo's affinescript CI is the oracle.
Out of scope — recorded dependency:
burblehas 35.affinefiles still in ReScript surface syntax (in-progress RS→AffineScript hand-port). #218 cannot apply until that separate dialect port lands. Not a #218 target — tracked as a downstream dependency.Excluded (correct): bofj-kitt + proof-burrower carry the vendored AffineScript compiler corpus (~210/215 fixtures each); cafescripto/jaffascript/lucidscript/pseudoscript/rattlescript are single "face" demo fixtures with no record literals. 11 other repos have only record-free GUI-shim
.affine.Revised closure criteria
#218 closes when double-track-browser#28 + accessibility-everywhere#38 merge green (oracle-validated). burble is explicitly out of scope and does not block #218. Grammar side is already done (#222/#223 merged; residual conflicts are ADR-012 won't-fix via #224 — #215 closed).
feat(stdlib,wasm): #225 PR1 — typed-wasm Http skeleton + verified The…
hyperpolymath commentedon May 18, 2026
Correction — oracle-validation results (the in-repo CI is NOT an oracle)
Followed up by actually building the post-#222
affinescriptcompiler from mergedmain(7018b92) andcheck-ing the two gap PRs' files (the repos' own CI has no.affinejob — generic estate workflows only,buildstuck in the concurrency-pool queue — so it never validated either merge).double-track-browser#28 — VALIDATED CORRECT ✅. Migrated file parses CLEAN on current grammar; the pre-migration (old
{ }) version is rejected with a syntax error at the first record literal. Genuine clean #218 closure.accessibility-everywhere#38 — record edits correct but repo RECLASSIFIED. The
#{changes are right in isolation, butcheckfails before reaching them — at the legacy-{Effect}->effect-row arrow present in 13 of 17.affinefiles (current grammar uses the SLASH effect-row form; see #215 family A). i.e. this repo's.affinecorpus is written against an older AffineScript dialect and does not compile on currentmainindependent of records — the same class asburble, not a clean #218 gap. #38 is not reverted (it's a correct, monotonic improvement), but it does not close coverage for this repo.Revised closure criteria (final)
.affinefiles need a broader dialect-modernisation port (legacy-{E}->effect-arrow → current form; burble also ReScript-surface) of which#{is only one part. Tracked as downstream dependencies, do not block Rust-like record syntax migration: bare { = block, records use #{ } (resolves #215 families C+D) #218.feat: #225 typed-wasm Http — ADR-013 + PR1 verified Thenable foundati…
hyperpolymath commentedon May 18, 2026
Closing as completed. Record-syntax migration done and oracle-validated: grammar #222/#223 merged (residual conflicts ADR-012 won't-fix via #224; #215 closed); estate sweep complete — 9 own-code sweep PRs + double-track-browser#28, the latter built-and-
check-validated against post-#222main. accessibility-everywhere + burble are reclassified as out-of-scope downstream dialect-modernisation dependencies (legacy-{E}->effect-arrow / ReScript surface) and do not block this epic. Full detail in the two comments above (issuecomment-4481762722 + correction -4481810156).