271 lines
11 KiB
Markdown
271 lines
11 KiB
Markdown
# Name Resolution - Scope, Lookup, and Imports Decision
|
|
|
|
Status: Accepted
|
|
Cycle: Initial name-resolution closure pass
|
|
|
|
## 1. Context
|
|
|
|
PBS v1 needs a deterministic frontend-visible rule for:
|
|
|
|
- how module scope is formed,
|
|
- how lookup works by namespace,
|
|
- what imports actually introduce into local scope,
|
|
- how collisions between local and imported names are handled,
|
|
- and which failures belong to syntax, manifest/import resolution, static semantics, or linking.
|
|
|
|
Existing specs already fix important inputs:
|
|
|
|
- `mod.barrel` is the single source of module visibility,
|
|
- imports target modules, not files,
|
|
- only `pub` names may be imported from another module,
|
|
- PBS has distinct type, value, callable, and host-owner namespaces,
|
|
- builtin simple types `int`, `float`, `bool`, and `str` are always available in type position,
|
|
- and reserved stdlib project spaces resolve only from the selected stdlib environment.
|
|
|
|
The remaining goal of this decision is to close the minimum name-resolution baseline needed for normative frontend work around ordinary scope construction, ordinary lookup, and import naming.
|
|
|
|
## 2. Decision
|
|
|
|
PBS v1 adopts the following baseline for scope construction, lookup, and imports:
|
|
|
|
1. Top-level declarations of a module are collected across all `.pbs` files in the module before visibility filtering is applied.
|
|
2. `mod.barrel` is a visibility filter over existing module declarations, not the source of declaration existence.
|
|
3. Module-internal top-level availability does not depend on source-file order.
|
|
4. Local block scopes nest normally over module scope.
|
|
5. In value position, lookup prefers the nearest lexical value binding before any module-level or imported value.
|
|
6. Parameters and local `let` bindings participate in the same nearest lexical value-scope layer for lookup purposes.
|
|
7. In type position, visible module-local type declarations are considered before imported type declarations, and builtin simple types remain always-available reserved type names outside ordinary import competition.
|
|
8. In callable position, visible module-local callable declarations are considered before imported callable declarations.
|
|
9. In host-owner position, visible module-local host owners are considered before imported host owners.
|
|
10. The local visible name introduced by an import is always the post-alias name when an alias is present.
|
|
11. `import { X } from @project:path;` introduces the imported exported name `X` into the matching namespace.
|
|
12. `import { X as Y } from @project:path;` introduces only the local visible name `Y` into the matching namespace.
|
|
13. Alias spelling changes only the local visible name, never canonical builtin identity or canonical host identity.
|
|
14. `import { * } from @project:path;` is the whole-module import form for bringing the target module's exported names into local visibility under their exported names.
|
|
15. A collision between a module-local declaration and an imported visible name in the same namespace is a deterministic error rather than silent shadowing.
|
|
16. A collision between two imported visible names in the same namespace is not an error only when both imports denote the same canonical underlying declaration after module resolution.
|
|
17. If two imported visible names in the same namespace come from different canonical underlying declarations, the program is rejected and one of the imports must be aliased or removed.
|
|
18. `import { * } from @project:path;` does not create a first-class module object, module namespace value, or other source-visible binding by itself; it only introduces the exported names of the target module.
|
|
19. A module-local function and an imported function with the same visible name produce a deterministic error in this closure pass.
|
|
20. Non-callable namespaces do not merge by name.
|
|
|
|
## 3. Scope Construction
|
|
|
|
### 3.1 Module collection
|
|
|
|
For one module:
|
|
|
|
- the compiler collects top-level declarations from all `.pbs` files in that module,
|
|
- forms one module-level declaration space,
|
|
- then applies `mod.barrel` to determine `mod` and `pub` visibility.
|
|
|
|
This means:
|
|
|
|
- declaration existence is not derived from barrel entries,
|
|
- and module-internal declaration availability is not ordered by file traversal.
|
|
|
|
### 3.2 Lexical scope
|
|
|
|
Inside executable bodies:
|
|
|
|
- lexical block scope is nested normally,
|
|
- nearest local bindings win within value lookup,
|
|
- and lexical nesting remains independent from cross-module visibility.
|
|
|
|
## 4. Lookup By Namespace
|
|
|
|
### 4.1 Value position
|
|
|
|
Value-position lookup follows this order:
|
|
|
|
1. nearest local lexical bindings,
|
|
2. parameters in the current lexical function scope,
|
|
3. visible module-local values,
|
|
4. visible imported values.
|
|
|
|
For lookup purposes, parameters and local `let` bindings are one nearest lexical value layer.
|
|
The distinction between them may still matter for diagnostics wording.
|
|
|
|
### 4.2 Type position
|
|
|
|
Type-position lookup follows this order:
|
|
|
|
1. visible module-local type declarations,
|
|
2. visible imported type declarations,
|
|
3. builtin simple types `int`, `float`, `bool`, and `str` as always-available reserved type names.
|
|
|
|
Builtin simple types are not treated as ordinary imported declarations and do not participate in ordinary import competition.
|
|
|
|
### 4.3 Callable position
|
|
|
|
Callable-position lookup follows this order:
|
|
|
|
1. visible module-local callables,
|
|
2. visible imported callables.
|
|
|
|
If a visible module-local function name and a visible imported function name are the same, the program is rejected in this closure pass rather than merged.
|
|
|
|
### 4.4 Host-owner position
|
|
|
|
Host-owner lookup follows this order:
|
|
|
|
1. visible module-local host owners,
|
|
2. visible imported host owners.
|
|
|
|
Host-owner lookup remains separate from type, value, and callable lookup.
|
|
|
|
## 5. Import Surface
|
|
|
|
### 5.1 Named import
|
|
|
|
`import { X } from @project:path;`:
|
|
|
|
- resolves the target module,
|
|
- checks that `X` is exported and importable from that module,
|
|
- and introduces `X` into the corresponding namespace locally.
|
|
|
|
### 5.2 Aliased import
|
|
|
|
`import { X as Y } from @project:path;`:
|
|
|
|
- resolves the same exported declaration as the non-aliased form,
|
|
- but introduces only `Y` as the local visible name.
|
|
|
|
Alias spelling does not change canonical identity governed elsewhere.
|
|
|
|
### 5.3 Whole-module import
|
|
|
|
`import { * } from @project:path;`:
|
|
|
|
- validates and resolves the target module,
|
|
- introduces the target module's exported visible names under their exported spellings,
|
|
- but does not create a module-valued binding,
|
|
- does not create a namespace object,
|
|
- and does not authorize qualified member access by itself.
|
|
|
|
If PBS later wants module-object or namespace-qualified source semantics, that must be added explicitly rather than inferred from this form.
|
|
|
|
## 6. Collision Policy
|
|
|
|
### 6.1 Local versus imported
|
|
|
|
If a module-local declaration and an imported declaration produce the same visible name in the same namespace:
|
|
|
|
- the program is rejected,
|
|
- and the implementation must not silently shadow the imported declaration or the local declaration.
|
|
|
|
This includes function names.
|
|
In this closure pass, a local function and an imported function with the same visible name are rejected rather than merged.
|
|
|
|
### 6.2 Imported versus imported
|
|
|
|
If two imports produce the same visible name in the same namespace:
|
|
|
|
- the program is not rejected if both imports resolve to the same canonical underlying declaration after module resolution,
|
|
- but the duplicate import is still redundant,
|
|
- and the program is rejected if the imports resolve to different canonical underlying declarations.
|
|
|
|
### 6.3 Namespace separation
|
|
|
|
Names in different namespaces do not collide merely by spelling.
|
|
|
|
For example:
|
|
|
|
- a host owner and a type declaration do not collide by spelling alone,
|
|
- because host-owner namespace remains distinct from type namespace.
|
|
|
|
## 7. Relationship To Later Name-Resolution Decisions
|
|
|
|
This decision is intentionally limited to:
|
|
|
|
- ordinary scope construction,
|
|
- ordinary namespace lookup,
|
|
- import naming,
|
|
- and ordinary collision policy.
|
|
|
|
Later name-resolution decisions close:
|
|
|
|
- reserved builtin shells and host owners,
|
|
- callable-set visibility across modules,
|
|
- and the final phase boundary between syntax, manifest/import resolution, linking, and static semantics.
|
|
|
|
## 8. Invariants
|
|
|
|
- Name lookup must be deterministic.
|
|
- Module-internal top-level declaration availability must not depend on file order.
|
|
- `mod.barrel` remains a visibility mechanism rather than a declaration source.
|
|
- Imports must not invent first-class module-object semantics accidentally.
|
|
- The effective visible name of an import is always the post-alias name when an alias is present.
|
|
- Builtin simple types remain a reserved always-available core type set, distinct from ordinary imported declarations.
|
|
- Implementations must not assume silent local-over-import shadowing.
|
|
- Implementations must not merge local and imported function names automatically.
|
|
|
|
## 9. Explicit Non-Decisions
|
|
|
|
This decision record does not yet close:
|
|
|
|
- reserved-shell-specific lookup and collision details,
|
|
- callable-set import behavior across module boundaries beyond the local-versus-import collision baseline,
|
|
- and backend-facing lowering consequences of the resolved lookup model.
|
|
|
|
## 10. Spec Impact
|
|
|
|
This decision should feed at least:
|
|
|
|
- `docs/pbs/specs/14. Name Resolution and Module Linking Specification.md`
|
|
- `docs/pbs/specs/11. Diagnostics Specification.md`
|
|
- `docs/pbs/specs/13. Conformance Test Specification.md`
|
|
|
|
It should also constrain future work in:
|
|
|
|
- `docs/pbs/specs/12. IR and Lowering Specification.md`
|
|
- `docs/pbs/decisions/Name Resolution - Builtin Shells and Host Owners Decision.md`
|
|
- `docs/pbs/decisions/Name Resolution - Callable Sets and Cross-Module Linking Decision.md`
|
|
- `docs/pbs/decisions/Name Resolution - Linking Phase Boundary Decision.md`
|
|
|
|
## 11. Validation Notes
|
|
|
|
The intended baseline is:
|
|
|
|
- all top-level declarations in a module exist before barrel filtering,
|
|
- lookup is namespace-specific and deterministic,
|
|
- the effective visible import name is the alias name when an alias is present,
|
|
- whole-module import through `import { * } from @project:path;` introduces exported names rather than a module object,
|
|
- local/import collisions are rejected rather than shadowed,
|
|
- and builtin simple types remain reserved always-available names outside ordinary import competition.
|
|
|
|
Illustrative examples:
|
|
|
|
```pbs
|
|
import { Foo } from @a:m;
|
|
declare const Foo: int = 1;
|
|
```
|
|
|
|
This is rejected as a local-versus-import collision in the value namespace.
|
|
|
|
```pbs
|
|
import { f } from @a:m;
|
|
import { f } from @b:n;
|
|
```
|
|
|
|
This is rejected because the visible imported names match but the canonical origins differ.
|
|
|
|
```pbs
|
|
fn f(a: int) -> int { ... }
|
|
import { f } from @a:m;
|
|
```
|
|
|
|
This is rejected as a local-versus-import collision in callable position.
|
|
|
|
```pbs
|
|
import { * } from @a:m;
|
|
```
|
|
|
|
This introduces the exported visible names of `@a:m`, but does not introduce a source-visible module object in v1.
|
|
|
|
```pbs
|
|
import { A as aaa } from @a:m;
|
|
```
|
|
|
|
The visible local declaration name is `aaa`, not `A`.
|