Skip to content

Commit 30d487a

Browse files
docs(stdlib): settle namespace model — real modules + qualified paths (#132) (#151)
Records the ruling that gates the rest of the #128 stdlib-AOT epic (#133/#135/#137/#138): the stdlib uses real modules with qualified paths, not a flat de-duplicated prelude. - ADR-011 (accepted) added to .machine_readable/6a2/META.a2ml using the existing [[adr]] schema. - Ledger entry in docs/specs/SETTLED-DECISIONS.adoc. - Dated decision section + downstream sequencing table in the named docs/history/MODULE-SYSTEM-PROGRESS.md. Rationale: the compiler already has module/use/:: + module_loader.ml, and the newer stdlib files already use it; only the legacy core files are flat. Single ownership resolves the prelude/option/result signature-divergent duplicates. No code change. Closes #132 Refs #128 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fd03d29 commit 30d487a

3 files changed

Lines changed: 147 additions & 0 deletions

File tree

.machine_readable/6a2/META.a2ml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,3 +669,77 @@ references = [
669669
"docs/DESIGN-VISION.adoc (faces section)",
670670
"docs/specs/faces.adoc",
671671
]
672+
673+
[[adr]]
674+
id = "ADR-011"
675+
status = "accepted"
676+
date = "2026-05-17"
677+
title = "Stdlib namespace model: real modules with qualified paths"
678+
context = """
679+
The AffineScript stdlib was never compiled through the static
680+
resolve → typecheck → codegen pipeline as a coherent unit (issue #128).
681+
Its core files (prelude, option, result, collections, string, io,
682+
testing, effects, math, traits) are interpreter-era code: a flat global
683+
namespace with no `module`/`use`/`open`, and conflicting duplicate
684+
definitions across files — `prelude.affine` and `option.affine` both
685+
define `is_some`/`unwrap`/`map`/… with *incompatible* signatures
686+
(`prelude.map(arr, f)` vs `option.map(f, opt)`). Commit b895374 seeded
687+
`Some/None/Ok/Err` as builtins to make `string.affine` resolve
688+
standalone, a band-aid that entrenches the flat namespace.
689+
690+
Issue #132 required a single ruling — real modules vs intentionally
691+
flat-and-deduplicated — because #133 (dedup), #135 (whole-stdlib
692+
compile), #137 (multi-module integration test) and #138 (band-aid
693+
removal) all branch on it.
694+
695+
The compiler already has the machinery: the grammar accepts
696+
`module X;`, `use path;` and `::`-qualified paths; `module_loader.ml`
697+
resolves module paths to files with search paths, nested modules and
698+
caching; and the *newer* stdlib files (Core, Crypto, Ajv, Sqlite,
699+
Grammy, Deno, Network, Vscode, VscodeLanguageClient) already declare
700+
`module X;` and use qualified constructors (`Ordering::Less` in
701+
traits.affine). Only the legacy core files are flat.
702+
"""
703+
decision = """
704+
The stdlib uses **real modules with qualified paths**, not a flat
705+
de-duplicated prelude.
706+
707+
- Every `stdlib/*.affine` declares `module <Name>;` and is addressed by
708+
its module path.
709+
- Cross-file use is explicit: `use option::{Option, Some, None};` /
710+
qualified references `Result::unwrap`.
711+
- There is exactly one canonical definition per name, owned by its
712+
module. The `prelude`/`option`/`result` overlaps are resolved by
713+
giving each name a single owning module and having the others `use`
714+
it (no duplicate, signature-divergent copies).
715+
- A minimal, explicit prelude module may re-export the few universally
716+
needed names (`Option`, `Result`, `Some`, `None`, `Ok`, `Err`); it
717+
contains *re-exports*, not independent redefinitions.
718+
- The b895374 seeded builtins are removed once resolution flows through
719+
the module path (tracked by #138); they are not load-bearing.
720+
721+
This is the path consistent with the machinery the compiler and the
722+
newer stdlib already use; it makes the AOT pipeline exercise real
723+
cross-module resolution, which is the actual #128 objective.
724+
"""
725+
consequences = """
726+
- #133 removes the duplicate/conflicting prelude/option/result defs by
727+
assigning each name one owning module; non-owners `use` it.
728+
- #135 brings the legacy core files under `module`/`use` as it makes
729+
each compile through resolve → typecheck → codegen.
730+
- #137 (multi-module integration test) is meaningful: it exercises
731+
several modules `use`d together, which the flat model could not test.
732+
- #138 can delete the seeded-builtins band-aid once the prelude
733+
re-export module exists and resolution uses it.
734+
- Slightly more up-front import wiring than a flat prelude, but it is
735+
the model the language already commits to elsewhere (ADR pointer:
736+
newer stdlib + `module_loader.ml`), so no new compiler design debt.
737+
- This decision is settled; do not reopen without amending this ADR.
738+
"""
739+
references = [
740+
"https://github.com/hyperpolymath/affinescript/issues/128",
741+
"https://github.com/hyperpolymath/affinescript/issues/132",
742+
"docs/history/MODULE-SYSTEM-PROGRESS.md (2026-05-17 decision section)",
743+
"lib/module_loader.ml",
744+
"lib/parser.mly (module_decl / import_decl / COLONCOLON)",
745+
]

docs/history/MODULE-SYSTEM-PROGRESS.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,54 @@ type context = {
349349
2. Phase 2: Function call implementation
350350

351351
**Current State:** Ready to begin Phase 3 (Advanced Type System)
352+
353+
---
354+
355+
## Decision: stdlib namespace model (2026-05-17, issue #132 / ADR-011)
356+
357+
Settles the open question gating the #128 stdlib-AOT epic: does the stdlib
358+
have real modules, or stay flat-and-deduplicated?
359+
360+
**Decision: real modules with qualified paths.** Not a flat de-duplicated
361+
prelude. Status: **accepted, settled** (ADR-011 in
362+
`.machine_readable/6a2/META.a2ml`; ledger entry in
363+
`docs/specs/SETTLED-DECISIONS.adoc`).
364+
365+
### Rationale
366+
367+
The compiler already has the machinery — the grammar accepts `module X;`,
368+
`use path;` and `::`-qualified paths; `module_loader.ml` resolves module
369+
paths with search paths, nested modules and caching; and the *newer*
370+
stdlib files (Core, Crypto, Ajv, Sqlite, Grammy, Deno, Network, Vscode,
371+
VscodeLanguageClient) already declare `module X;` and use qualified
372+
constructors (`Ordering::Less`). Only the legacy core files (prelude,
373+
option, result, collections, string, io, testing, effects, math, traits)
374+
are flat interpreter-era code with conflicting duplicate definitions
375+
(`prelude.map(arr, f)` vs `option.map(f, opt)`). Choosing real modules
376+
aligns the legacy files with the model the language already commits to,
377+
and makes the AOT pipeline exercise real cross-module resolution — the
378+
actual objective of #128.
379+
380+
### Model
381+
382+
- Every `stdlib/*.affine` declares `module <Name>;`.
383+
- Cross-file use is explicit: `use option::{Option, Some, None};` /
384+
qualified `Result::unwrap`.
385+
- Exactly one canonical definition per name, owned by its module. The
386+
prelude/option/result overlaps are resolved by single ownership; the
387+
others `use` the owner (no signature-divergent copies).
388+
- A minimal prelude module may *re-export* the universally needed names
389+
(`Option`, `Result`, `Some`/`None`/`Ok`/`Err`) — re-exports only.
390+
- The b895374 seeded `Some/None/Ok/Err` builtins are removed once
391+
resolution flows through the module path (#138); not load-bearing.
392+
393+
### Downstream sequencing (this epic)
394+
395+
| Issue | Work unlocked by this decision |
396+
|---|---|
397+
| #133 | Remove prelude/option/result conflicting dups via single ownership; non-owners `use` the owner. |
398+
| #135 | Bring legacy core files under `module`/`use` as each is made to compile resolve→typecheck→codegen. |
399+
| #137 | Multi-module integration test (`use`s several stdlib modules together) — now a meaningful test. |
400+
| #138 | Delete the b895374 seeded-builtins band-aid once the prelude re-export module exists. |
401+
402+
No code change in #132 (decision + documentation only).

docs/specs/SETTLED-DECISIONS.adoc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,25 @@ Neither role is subordinate. They are kept distinct in documentation and in
132132

133133
See `typed-wasm/docs/architecture/AGGREGATE-LIBRARY-VISION.adoc` for the
134134
aggregate library design.
135+
136+
== Stdlib Namespace Model: Real Modules (ADR-011)
137+
138+
The standard library uses real modules with qualified paths, *not* a flat
139+
de-duplicated prelude.
140+
141+
Every `stdlib/*.affine` declares `module <Name>;`; cross-file use is
142+
explicit (`use option::{Option, Some, None};` / `Result::unwrap`); there is
143+
exactly one canonical definition per name, owned by its module. A minimal
144+
prelude module may *re-export* the universally needed names (`Option`,
145+
`Result`, `Some`/`None`/`Ok`/`Err`) — re-exports, never independent
146+
redefinitions.
147+
148+
This resolves the `prelude` vs `option` vs `result` signature-divergent
149+
duplicates (`prelude.map(arr, f)` vs `option.map(f, opt)`) by single
150+
ownership, and makes the AOT pipeline exercise real cross-module
151+
resolution — the actual objective of issue #128. It is consistent with
152+
the machinery the compiler (`module_loader.ml`, `module`/`use`/`::`
153+
grammar) and the newer stdlib files (Core, Deno, Vscode, …) already use.
154+
155+
Settles issue #132; gates #133/#135/#137/#138. Full ADR in
156+
`.machine_readable/6a2/META.a2ml` (ADR-011).

0 commit comments

Comments
 (0)