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
- Decide + implement the applied-type surface uniformly (above).
- 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.
- 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.
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.