Repository navigation
stdlib has never been AOT-compiled as a coherent unit (angle-applied types + flat-namespace collisions; string.affine is the first symptom) #128
Description
Activity
- addedbugSomething is broken or behaves incorrectlySomething is broken or behaves incorrectly
on May 16, 2026 - changed the title
[-]stdlib/string.affine does not compile through the static pipeline (Option<Char> angle-generics + unimported Some/None)[/-][+]stdlib has never been AOT-compiled as a coherent unit (angle-applied types + flat-namespace collisions; string.affine is the first symptom)[/+]on May 16, 2026 - addedmajorMajor issue — significant scope, broader impact than a feature/bugMajor issue — significant scope, broader impact than a feature/bug
on May 16, 2026 - added sub-issues
on May 16, 2026 - added 4 commits that reference this issue
on May 17, 2026 27 remaining items
- added 8 commits that reference this issue
on May 18, 2026 STAGE A complete — stdlib AOT coherence achieved.
All 19
stdlib/*.affinecompile resolve→typecheck→borrow→Deno-ESM codegen, standalone and as a coherent multi-module set. Landed this session via PRs #186, #187, #188, #189, #190, #191, #192, #193, #194 (each 233→253/253, zero regression).Closers done at root cause (not patched):
- cleanup: remove b895374 seeded Some/None/Ok/Err builtins band-aid #138 — removed the b895374
Some/None/Ok/Errflat-seed band-aid; root fix:resolve_and_typecheck_modulenow recursively resolves an imported module's ownuseimports through the loader, and type-checks viacheck_program(forward-declaration) so imported modules behave identically to standalone.RuntimeErrorkept (genuine interpreter exception, no module home). - ci: add stdlib-wide AOT compile-smoke gate #136 —
test_stdlib_aot.ml: stdlib-wide AOT smoke gate, one case per file, wired intodune runtest(CI). - test: multi-module integration test using several stdlib modules together #137 — multi-module integration test (prelude+string+option+collections in one compiled program).
Coherence gaps found & fixed along the way: parametric enum ctor generalization on the import path; lenient deref for
ref-mode params; prelude arg-order / name-conflict drift in collections & option;&mut→mutparam syntax; assignment-arm vs Unit-arm typing;option/collectionsexposed zeropubAPI.Leaving #128 open for joint close at your discretion. STAGE A unblocks STAGE B (effect system, #59→#103).
- cleanup: remove b895374 seeded Some/None/Ok/Err builtins band-aid #138 — removed the b895374
Post-merge verification (independent re-check, 2026-05-18)
Confirming the STAGE-A completion comment above (04:42) now that the dust has settled:
- All 9 PRs verified
MERGEDtomain: fix(typecheck): zero-arg thunk calls, tuple-destructuring let, RuntimeError catch (#128) #186 fix(typecheck): instantiate constructor schemes per use-site in patterns (#128) #187 fix(typecheck): generalize parametric enum ctor schemes (import path) (#128) #188 fix(stdlib): io.affine reassigned locals need let mut (#128) #189 fix(typecheck): lenient deref for ref-mode params (#128) #190 fix(stdlib): collections.affine prelude arg-order + let mut (#128) #191 fix(stdlib): option.affine — final STAGE-A file, stdlib 19/19 (#128) #192 fix(resolve): remove b895374 seed band-aid; recursive cross-module resolution (#138) #193 test(stdlib): AOT smoke gate (#136) + multi-module integration (#137) #194. - CI gate live: test(stdlib): AOT smoke gate (#136) + multi-module integration (#137) #194's stdlib-wide AOT smoke gate (
test/test_stdlib_aot.ml, resolve→typecheck→borrow→Deno-ESM codegen, one case/file) runs indune runteston every push — STAGE-A coherence is now regression-protected onmain, not just a point-in-time pass. - Closers cleanup: remove b895374 seeded Some/None/Ok/Err builtins band-aid #138/ci: add stdlib-wide AOT compile-smoke gate #136/test: multi-module integration test using several stdlib modules together #137 landed at root cause (recursive cross-module resolution in
lib/resolve.ml), not patched.
No remaining STAGE-A work. Refs #128 — leaving this open for your joint-close per the requirements-target protocol; it's ready whenever you want to close it. STAGE-B (effect system, #59→#103) is now unblocked and proceeding.
- All 9 PRs verified
Joint-close approved by @hyperpolymath (2026-05-18).
STAGE-A complete and regression-protected on
main(stdlib 19/19 AOT-coherent; 253/253; CI smoke-gate via #194). Closers #138/#136/#137 landed at root cause. Closing per the requirements-target joint-close protocol. STAGE-B (effect system, #59→#103) is underway.- added a commit that references this issue
on May 18, 2026 Status update — AS stdlib codegen pattern now established (2026-06-01) 📌
For ticket coordinators / anyone tracking this gap:
- affinescript#522 + feat(stdlib): close 6 STEP-3 Deno-ESM gaps surfaced by TS→AS ports (Refs #239, #242) #504 merged 2026-06-01 establish the canonical stdlib extern → Deno-ESM lowering pattern via
lib/codegen_deno.ml. Tests live intests/codegen-deno/*. - affinescript#463 unblocked non-ASCII string emit in Deno-ESM (Unicode escapes, not octal).
- affinescript#421 put stdlib/json on the Zig-FFI rails (hpm-json-rsr).
What this means for the gap tracked in this ticket:
- The codegen pattern is now a known repeatable shape: declare in
stdlib/*.affine→ wire indeno_builtinstable + prelude → smoke-test intests/codegen-deno/*. - This ticket remains open as a tracked gap; no new mass-port is being kicked off from this post.
- Surfacing status so the broader picture stays accurate as more sub-tickets / dependent campaigns reference it.
🤖 Cross-estate AS-status sweep on 2026-06-01.
- affinescript#522 + feat(stdlib): close 6 STEP-3 Deno-ESM gaps surfaced by TS→AS ports (Refs #239, #242) #504 merged 2026-06-01 establish the canonical stdlib extern → Deno-ESM lowering pattern via
- added a commit that references this issue
on Jul 7, 2026
Summary
The AffineScript stdlib has never been compiled through the static
resolve → typecheck → codegenpipeline as a coherent unit. It isinterpreter-era code (dynamically typed, flat global namespace) that the
AOT path has never exercised end-to-end.
stdlib/string.affineis thefirst file that surfaced this (originally reported here), but the two
root causes are stdlib-wide, not string-specific.
This issue is rescoped from "
stdlib/string.affinedoes not compile" tothe actual underlying defect.
Reproduce (original symptom)
Root causes (both stdlib-wide)
1. Angle-bracket applied types are the stdlib's surface, not a string.affine quirk
The static front-end accepts
<...>for type parameters butrejects it for applied types in value/return position
(
fn char_at(...) -> Option<Char>). That asymmetry is the actualdefect. It is pervasive, not local:
<…>usagesoption.affineresult.affinecollections.affinetesting.affineprelude.affineio.affinestring.affineRewriting only
string.affineto bracket form yields a file that stillcannot
useoption.affine/prelude.affinebecause those don'tresolve either — a single-file smoke check would pass while the stdlib
remains incoherent.
2. No module/import story; flat-namespace collisions
No stdlib file uses
use/import/open.prelude.affineandoption.affineboth defineis_some/is_none/unwrap/unwrap_or/map/filter/contains, with conflictingsignatures (
prelude.map(arr, f)vsoption.map(f, opt)). Commitb895374("seedSome/None/Ok/Errbuiltins so it resolves standalone")is a band-aid that entrenches the interpreter-era global namespace
rather than resolving it; the original "add prelude/option imports or
qualify
Option::Some" suggestion assumes import machinery the stdlibhas never been run through under AOT.
Decision required (resolve at source)
Pick the applied-type surface once and apply it uniformly. Per-file
rewrites are the worst of both options.
Option<Char>in applied position in theresolver. Cheap, consistent with the
<...>parameter syntaxalready accepted, makes the entire existing stdlib compile
unchanged.
stdlib/*.affine+ docs + examplesin one sweep and remove the angle alias so it cannot rot back.
Scope of work
stdlib have modules, or stay flat-and-deduplicated? Remove the
conflicting
preludevsoptionoverlaps either way.stdlib/*.affinethroughresolve → typecheck → codegen, plus atleast one test that
uses several stdlib modules together, so theAOT path can't silently rot again. (Not a single-file check.)
Impact / scope
Non-blocking and separate from #122. The Deno-ESM consumer migration
(ubicity#30 → ubicity#31/#33) deliberately does not depend on the stdlib
(self-contained
extern fnprimitives; ubicity green 44/44). This isthe separately-tracked structural loose end noted during the #122 work,
now scoped to its true root cause.
Refs #122, #123, #125, #127. Supersedes the original
string.affine-only framing of this issue.
Tracking (sub-issues)
This issue is now the tracking epic. Work items, with dependencies:
unwrap/unwrap_resultfall-through (independent)stdlib/*.affinecompiles via resolve→typecheck→codegen (needs resolver: accept angle-bracket applied types (Option<Char>) in value/return position #131, stdlib: remove prelude/option/result duplicate & conflicting definitions #133; blocks ci: add stdlib-wide AOT compile-smoke gate #136)b895374seeded-builtins band-aid (needs resolver: accept angle-bracket applied types (Option<Char>) in value/return position #131, stdlib: decide & document the namespace/module model (modules vs flat-deduplicated) #132, stdlib: remove prelude/option/result duplicate & conflicting definitions #133)Suggested order: #131 → #132 → #133 → #135 → #136 → #137 → #138, with #134 doable anytime in parallel.
Downstream requirements (consumer-driven, from the ECHIDNA migration)
Folded in from the ReScript→AffineScript migration backlog. These are
requirements this epic must satisfy, not separate work streams — the
Client.resport (echidna[migration-roadmap.rescript-to-affinescript])is blocked until the stdlib exposes these primitives. They extend the
stdlib surface the AOT pipeline (#135) must compile, beyond the existing
angle-bracket/namespace work:
Http/Fetch(GET/POST, RequestInit, Response ok/status/json)stdlib/http.affine(no module today;effects.affinehas no HTTP)Async[T]/Promise[T](pure/bind/catchorawait)Async[T]overeffects.affine(currently no documented type)Json(opaque type + Decode/Encode combinators,stringify)stdlib/json.affineDict/Map(empty/get/insert/set)collections.affine(listed TODO; depends on #133/#135)Implication for scope: #135's "every
stdlib/*.affinecompiles" mustinclude the new
http.affineandjson.affineplus a realcollections.affine, and #137's multi-module integration test shouldexercise the
Client.res-shaped path (Http + Async + Json + Dict together)as the migration's validation target. The four echidna issues stay open as
the consumer-side trackers and close when the migration lands, not when this
epic does.
Refs hyperpolymath/echidna#61, hyperpolymath/echidna#62, hyperpolymath/echidna#63, hyperpolymath/echidna#64.