Skip to content

stdlib has never been AOT-compiled as a coherent unit (angle-applied types + flat-namespace collisions; string.affine is the first symptom) #128

Closed
8 of 8 issues completed

Description

@hyperpolymath

Summary

The AffineScript stdlib has never been compiled through the static
resolve → typecheck → codegen pipeline as a coherent unit.
It is
interpreter-era code (dynamically typed, flat global namespace) that the
AOT path has never exercised end-to-end. stdlib/string.affine is the
first 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.affine does not compile" to
the actual underlying defect.

Reproduce (original symptom)

affinescript compile stdlib/string.affine -o /tmp/x.deno.js --deno-esm
# => affinescript: Resolution error  (fails at resolve, before typecheck/codegen)

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 but
rejects it for applied types in value/return position
(fn char_at(...) -> Option<Char>). That asymmetry is the actual
defect. It is pervasive, not local:

file <…> usages
option.affine 36
result.affine 24
collections.affine 22
testing.affine 18
prelude.affine 15
io.affine 10
string.affine 5

Rewriting only string.affine to bracket form yields a file that still
cannot use option.affine / prelude.affine because those don't
resolve 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.affine and
option.affine both define is_some / is_none / unwrap /
unwrap_or / map / filter / contains, with conflicting
signatures
(prelude.map(arr, f) vs option.map(f, opt)). Commit
b895374 ("seed Some/None/Ok/Err builtins 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 stdlib
has 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.

  • (Preferred) Accept Option<Char> in applied position in the
    resolver.
    Cheap, consistent with the <...> parameter syntax
    already accepted, makes the entire existing stdlib compile
    unchanged.
  • Bracket-only. Migrate all of stdlib/*.affine + docs + examples
    in one sweep and remove the angle alias so it cannot rot back.

Scope of work

  1. Decide + implement the applied-type surface uniformly (above).
  2. Resolve the duplicate-definition / namespace question: does the
    stdlib have modules, or stay flat-and-deduplicated? Remove the
    conflicting prelude vs option overlaps either way.
  3. Add a stdlib-wide AOT compile-smoke gate: every
    stdlib/*.affine through resolve → typecheck → codegen, plus at
    least one test that uses several stdlib modules together, so the
    AOT 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 fn primitives; ubicity green 44/44). This is
the 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:

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.res port (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:

echidna issue primitive needed maps to stdlib
hyperpolymath/echidna#61 Http/Fetch (GET/POST, RequestInit, Response ok/status/json) new stdlib/http.affine (no module today; effects.affine has no HTTP)
hyperpolymath/echidna#62 Async[T]/Promise[T] (pure/bind/catch or await) concrete Async[T] over effects.affine (currently no documented type)
hyperpolymath/echidna#63 Json (opaque type + Decode/Encode combinators, stringify) new stdlib/json.affine
hyperpolymath/echidna#64 Dict/Map (empty/get/insert/set) collections.affine (listed TODO; depends on #133/#135)

Implication for scope: #135's "every stdlib/*.affine compiles" must
include the new http.affine and json.affine plus a real
collections.affine, and #137's multi-module integration test should
exercise 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.

Activity

  1. added
    bugSomething is broken or behaves incorrectly
    on May 16, 2026
  2. 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
  3. added
    majorMajor issue — significant scope, broader impact than a feature/bug
    on May 16, 2026
  4. 27 remaining items

  5. hyperpolymath commented on May 18, 2026

    @hyperpolymath
    OwnerAuthor

    STAGE A complete — stdlib AOT coherence achieved.

    All 19 stdlib/*.affine compile 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):

    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 → mut param syntax; assignment-arm vs Unit-arm typing; option/collections exposed zero pub API.

    Leaving #128 open for joint close at your discretion. STAGE A unblocks STAGE B (effect system, #59→#103).

  6. hyperpolymath commented on May 18, 2026

    @hyperpolymath
    OwnerAuthor

    Post-merge verification (independent re-check, 2026-05-18)

    Confirming the STAGE-A completion comment above (04:42) now that the dust has settled:

    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.

  7. hyperpolymath commented on May 18, 2026

    @hyperpolymath
    OwnerAuthor

    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.

  8. added a commit that references this issue on May 18, 2026
  9. hyperpolymath commented on Jun 1, 2026

    @hyperpolymath
    OwnerAuthor

    Status update — AS stdlib codegen pattern now established (2026-06-01) 📌

    For ticket coordinators / anyone tracking this gap:

    What this means for the gap tracked in this ticket:

    • The codegen pattern is now a known repeatable shape: declare in stdlib/*.affine → wire in deno_builtins table + prelude → smoke-test in tests/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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaves incorrectlymajorMajor issue — significant scope, broader impact than a feature/bug

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions