155 lines
7.2 KiB
Markdown
155 lines
7.2 KiB
Markdown
# Globals Surface, Identity, and Module Boundaries Decision
|
|
|
|
Status: Accepted
|
|
Date: 2026-03-22
|
|
Related Agenda: `docs/compiler/pbs/agendas/19.1. PBS Globals Surface, Identity, and Module Boundaries Agenda.md`
|
|
|
|
## Context
|
|
|
|
PBS v1 needed a precise language and linking policy for runtime globals before lifecycle, published entrypoint, and lowering work under topic `19` could proceed safely.
|
|
|
|
The open problem was not merely syntax.
|
|
It also required closing:
|
|
|
|
- how globals differ from `declare const`,
|
|
- how globals participate in value visibility and import flow,
|
|
- how module ownership of storage is preserved,
|
|
- how dependency order between globals is modeled,
|
|
- and which diagnostics are mandatory when that model is violated.
|
|
|
|
Important fixed inputs already existed:
|
|
|
|
- `declare const` is compile-time only and does not denote mutable module storage,
|
|
- top-level executable statements remain forbidden,
|
|
- the VM already exposes global-slot access,
|
|
- and `mod.barrel` remains the visibility authority for module exports.
|
|
|
|
## Decision
|
|
|
|
PBS adopts the following policy for runtime globals in v1:
|
|
|
|
1. PBS introduces a distinct top-level declaration form:
|
|
- `declare global Name: T = expr;`
|
|
2. `declare global` is not a variant of `declare const`.
|
|
3. `declare const` remains reserved to immutable values and must not be reused as runtime module storage.
|
|
4. `declare global` requires:
|
|
- an explicit type annotation,
|
|
- and an explicit initializer in all ordinary v1 cases.
|
|
5. Globals participate in the value namespace, but remain a distinct declaration category.
|
|
6. `mod.barrel` must expose globals through explicit `global` entries:
|
|
- `mod global Name;`
|
|
- `pub global Name;`
|
|
7. PBS does not introduce global re-export in this line of work.
|
|
8. Import of a global preserves the storage identity of the original owner module.
|
|
9. Imports may use aliasing when needed, but aliasing changes only the local visible name, never the canonical storage owner.
|
|
10. Shadowing between visible `fn`, `service`, `global`, and `const` names is a compile-time error and must be resolved with aliasing.
|
|
11. Lookup precedence remains:
|
|
- locals,
|
|
- then struct/class members,
|
|
- then globals,
|
|
including globals introduced by import.
|
|
12. Global dependency order is defined by a deterministic dependency graph:
|
|
- every read of another global in a global initializer creates a dependency edge,
|
|
- imports and aliases preserve canonical owner identity in that graph,
|
|
- source-file order must not affect dependency resolution.
|
|
13. Cycles between globals are compile-time errors.
|
|
14. Modules and globals must share the same structural dependency-analysis kernel through a refactor of the existing module dependency algorithm into:
|
|
- `DependencyGraphAnaliser`
|
|
- located in infra `util.structures`.
|
|
15. What is shared is the structural graph analysis kernel:
|
|
- topological ordering,
|
|
- cycle detection,
|
|
- deterministic traversal support.
|
|
16. What is not shared is domain semantics:
|
|
- each domain remains responsible for constructing canonical nodes, edges, and diagnostics.
|
|
|
|
## Global Initializer Policy
|
|
|
|
PBS v1 adopts the following initializer policy for `declare global`:
|
|
|
|
1. The initializer exists only to materialize the initial module storage value.
|
|
2. Admissible forms in v1 include:
|
|
- primitive literals and simple value operations,
|
|
- value-bearing member access at this stage,
|
|
- `new Struct(...)`,
|
|
- and reads of other globals compatible with the dependency graph.
|
|
3. Top-level `fn` calls are not permitted in a global initializer in v1.
|
|
4. `some(...)` and `none` are not permitted in a global initializer in v1.
|
|
5. `if` and `switch` are not permitted directly in the declaration initializer in v1.
|
|
6. Richer procedural setup belongs to later lifecycle stages rather than the declaration initializer itself.
|
|
|
|
## Rationale
|
|
|
|
This decision intentionally keeps global declarations narrow and explicit.
|
|
|
|
That choice:
|
|
|
|
- prevents semantic collapse between immutable constants and mutable runtime storage,
|
|
- keeps import and visibility rules legible,
|
|
- makes dependency analysis deterministic and explainable,
|
|
- avoids hiding lifecycle logic inside declaration expressions,
|
|
- and prepares a clean handoff to later topic `19` work on module init, project init, and published entrypoint behavior.
|
|
|
|
The decision also rejects implicit complexity:
|
|
|
|
- no silent reuse of `const`,
|
|
- no global re-export,
|
|
- no ambiguous cross-category name merging,
|
|
- and no procedural `fn`-driven initialization inside `declare global`.
|
|
|
|
## Mandatory Diagnostics
|
|
|
|
PBS must provide, at minimum, the following diagnostics for this policy:
|
|
|
|
1. `global initializer uses unsupported form`
|
|
- emitted at the invalid subexpression inside a global initializer.
|
|
2. `global dependency cycle detected`
|
|
- emitted on the local participating global,
|
|
- and should include the canonical cycle path when available.
|
|
3. `imported symbol shadows existing visible symbol; alias required`
|
|
- emitted when an imported `fn`, `service`, `global`, or `const` collides with an already-visible symbol of those categories.
|
|
4. `global import must resolve through a global barrel entry`
|
|
- emitted when import resolution would otherwise degrade a global into another category.
|
|
|
|
## Invariants
|
|
|
|
1. `declare const` and `declare global` remain semantically distinct.
|
|
2. Runtime storage ownership remains attached to the canonical owner module.
|
|
3. Global dependency order is semantic, not textual.
|
|
4. Alias spelling must not change canonical global identity.
|
|
5. The dependency-analysis kernel may be shared structurally, but semantic graph construction remains domain-owned.
|
|
|
|
## Explicit Non-Decisions
|
|
|
|
1. This decision does not define module init or project init behavior.
|
|
2. This decision does not define `[INIT]`, `[FRAME]`, or lifecycle markers.
|
|
3. This decision does not define published entrypoint or `FRAME_RET` ownership.
|
|
4. This decision does not define the final IR representation of globals.
|
|
5. This decision does not define host-call admissibility during lifecycle hooks.
|
|
|
|
## Spec Impact
|
|
|
|
This decision should feed at least:
|
|
|
|
1. `docs/compiler/pbs/specs/3. Core Syntax Specification.md`
|
|
2. `docs/compiler/pbs/specs/4. Static Semantics Specification.md`
|
|
3. `docs/compiler/pbs/specs/11. AST Specification.md`
|
|
4. `docs/compiler/pbs/specs/12. Diagnostics Specification.md`
|
|
|
|
It also constrains future topic `19` work in:
|
|
|
|
1. `docs/compiler/pbs/agendas/19.2. PBS Lifecycle Markers, Program Init, and Frame Root Semantics Agenda.md`
|
|
2. `docs/compiler/pbs/agendas/19.3. Published Entrypoint, Synthetic Wrapper, and FRAME_RET Ownership Agenda.md`
|
|
3. `docs/compiler/pbs/agendas/19.4. Globals and Lifecycle Lowering to IRBackend/IRVM Agenda.md`
|
|
|
|
## Validation Notes
|
|
|
|
At minimum, validation for this decision should include:
|
|
|
|
1. accepted fixtures for `declare global` with primitive, member-value, `new`, and dependent-global initializers;
|
|
2. rejection fixtures for forbidden initializer forms such as top-level `fn`, `some(...)`, `if`, and `switch`;
|
|
3. import fixtures proving alias-based disambiguation;
|
|
4. negative fixtures for cross-category collisions;
|
|
5. dependency fixtures proving deterministic ordering independent of source-file order;
|
|
6. cycle fixtures proving deterministic detection and diagnostics for intra-module and inter-module cycles.
|