Modules and visibility
File layout, `public`/`internal` boundaries, and how packages compose. The driver and package manager use the same module graph the typechecker sees.
Document layout: Feature capability layoutconforms
## Purpose## Requirements
Modules and visibility Specification
Purpose
File layout, public/internal boundaries, and how packages compose. The driver and package manager use the same module graph the typechecker sees.
Requirements
Requirement: Module forms and identity
Path mod (mod a.b;) MUST declare the file’s module path and MUST be the first top-level item when used (E1505, E1506, E1507). Inline mod name { items } MUST nest a submodule in the current file. Without a file-scoped mod, module identity MUST derive from the file path relative to the project source root. Explicit mod path; MUST take precedence over path-derived module identity when present.
Scenario: File-scoped mod not first
- GIVEN a source file whose file-scoped
mod path;is not the first top-level item - WHEN module collection runs
- THEN the compiler emits E1505
Requirement: Default privacy and pub export
Items MUST default to private to their module. pub on an item MUST export it to importers of the containing module. Importing a private item MUST error (E1501, E1107). v0.1 MUST use private-by-default plus pub only; there is no internal keyword.
Scenario: Private item imported from another module
- GIVEN a
useof a non-pubitem from another module - WHEN visibility checking runs
- THEN the compiler emits E1501 or E1107
Requirement: Pub use re-exports
pub use path MUST re-export symbols, and re-exported names MUST refer to accessible items. Re-exports MUST preserve the underlying symbol’s accessibility rules; pub use of a private item from another module MUST be rejected.
Scenario: Re-export of inaccessible item
- GIVEN a
pub usethat names a private item from another module - WHEN re-export checking runs
- THEN the re-export is rejected
Requirement: File-scoped module nesting restrictions
Duplicate module declarations in one file MUST error. Nested mod declarations inside a file-scoped module file MUST error (E1507). Package boundaries MUST align with project manifests; cross-package visibility follows the same pub rules within each compilation.
Scenario: Nested mod in file-scoped module file
- GIVEN a file that begins with file-scoped
mod path;and later contains a nestedmoddeclaration - WHEN module collection runs
- THEN the compiler emits E1507
Informative Source Provenance
The records below preserve migration history and are not normative except where text was extracted into a requirement above.
Source Record: Modules and visibility
Authority: informative provenance
Legacy path: /platform-spec/language-meta/program-structure/modules-and-visibility/
Source: site/spec-content/platform-spec/language-meta/program-structure/modules-and-visibility/content.md
SHA-256: 1189b4ca722565e5d8f1aa291084340067411b1173bc3dc84af435957a02e346
Migrated source text
## Normative specification
### Scope
Defines the **module graph**, **file-scoped modules**, and **`pub` visibility** rules. Name binding is in [Name resolution](/platform-spec/language-meta/program-structure/name-resolution/).
### Module forms
| Form | Rule |
| --- | --- |
| **Path `mod`** | `mod a.b;` declares the file’s module path; **must** be the first top-level item when used (**E1505**, **E1506**, **E1507**) |
| **Inline `mod`** | `mod name { items }` nests a submodule in the current file |
| **File path** | Without file-scoped `mod`, module identity **must** derive from file path relative to project source root |
### Visibility
- Items default to **private** to their module.
- **`pub`** on an item **must** export it to importers of the containing module.
- **`pub use path`** re-exports symbols; re-exported names **must** refer to accessible items.
- Importing a private item **must** error (**E1501**, **E1107**).
### Static rules
- Duplicate module declarations in one file **must** error.
- Nested `mod` declarations inside a file-scoped module file **must** error (**E1507**).
- Package boundaries **must** align with project manifests; cross-package visibility follows the same `pub` rules within each compilation.
### Dynamic semantics
Modules exist at compile time; runtime has no separate module loader beyond linked assemblies/packages produced by the toolchain.
### Diagnostics
Visibility band **E1501–E1507**; unused import **W1503**. Registry: [Diagnostic code registry](/platform-spec/compiler/semantic-pipeline/diagnostic-code-registry/).
### Conformance
**L1** implementations **must** match reference layout tests for file-scoped modules and `pub` boundaries.
## Decisions
<!-- spec:generate:adr-index -->
No ADRs published under **`adr/`** yet.
<!-- /spec:generate:adr-index -->
## Articles
<!-- spec:generate:article-index -->
- [Modules and visibility - Contracts and edge cases](./articles/contracts-and-edge-cases/)
- [Modules and visibility - Design model](./articles/design-model/)
- [Modules and visibility - Examples](./articles/examples/)
- [Modules and visibility - FAQ and troubleshooting](./articles/faq-and-troubleshooting/)
- [Modules and visibility - Flow and algorithm](./articles/flow-and-algorithm/)
- [Modules and visibility - Verification and traceability](./articles/verification-and-traceability/)
<!-- /spec:generate:article-index -->
Source Record: Modules and visibility - Contracts and edge cases
Authority: informative provenance
Legacy path: /platform-spec/language-meta/program-structure/modules-and-visibility/articles/contracts-and-edge-cases/
Source: site/spec-content/platform-spec/language-meta/program-structure/modules-and-visibility/articles/contracts-and-edge-cases/content.md
SHA-256: a9f06769a57c140f51f714bc68b514103d1087017efce26625b7b92b64a31f6d
Migrated source text
## Hard requirements
- **File-scoped precedence** — Explicit `mod path;` wins over path-derived module identity when present.
- **No `internal` keyword** — v0.1 uses private-by-default plus `pub`; assembly-internal friends are deferred.
- **Package graph is external** — Cross-package edges come from manifests; this chapter defines symbol visibility only.
- **`pub use` re-export** — Re-exports must preserve the underlying symbol's accessibility rules.
## Diagnostic band E15xx
| Code | Condition |
| --- | --- |
| **E1501** | Visibility violation (private item imported) |
| **E1502** | Module not found |
| **E1505** | File-scoped module not first item |
| **E1506** | Duplicate file-scoped module |
| **E1507** | Module declaration forbidden in file-scoped module |
| **W1503** | Unused import |
| **W1504** | Unused private item |
## Edge cases
- **Nested `mod` in file-scoped file** — `ModuleDeclarationForbiddenInFileScopedModule` (**E1507**) prevents nested `mod` declarations inside a file-scoped module file.
- **Circular imports** — Circular `use` dependencies are allowed as long as forward references are not used.
- **Re-export of private** — `pub use` of a private item from another module is rejected.
- **Self-import** — `use self::Name;` is not supported in v0.1.
## Invariants
- Every item must have a resolved module scope after collection.
- Visibility checks must respect the importer's module, not the exporter's.
- Package boundaries must align with project manifests.
Source Record: Modules and visibility - Design model
Authority: informative provenance
Legacy path: /platform-spec/language-meta/program-structure/modules-and-visibility/articles/design-model/
Source: site/spec-content/platform-spec/language-meta/program-structure/modules-and-visibility/articles/design-model/content.md
SHA-256: 346074643c6467c81b359d90372af0de3fd8fd689b30c9cbfcd6b32cc1099a14
Migrated source text
## Vocabulary
| Construct | Role |
| --- | --- |
| **`ModuleDeclaration`** | Out-of-line module declaration (`mod path;`) |
| **`InlineModule`** | Inline nested module (`mod name { items }`) |
| **`UseDeclaration`** | Import with optional alias (`use Path as Alias;`) |
| **`Visibility`** | `pub` or private (default) |
## Module architecture
The module graph is built from file paths and explicit `mod` declarations. The driver and package manager use the same module graph the typechecker sees.
```mermaid
flowchart LR
file[File path]
mod[mod declaration]
inline[inline mod]
use[use imports]
vis[visibility check]
file --> mod --> inline --> use --> vis
```
### Subsystem boundaries
| Subsystem | Responsibility | Key file |
| --- | --- | --- |
| Parser | Parse `mod`, `use`, `pub` | `syntax/items/module_declaration.rs`, `use_declaration.rs` |
| AST | Store module structure | `syntax/items/` |
| Resolver | Build scope chain | `resolve/collect.rs` |
| Visibility rules | Check access | `analysis/rules/staged/visibility.rs` |
## Module forms
| Form | Rule |
| --- | --- |
| **Path `mod`** | `mod a.b;` declares the file's module path; must be the first top-level item when used |
| **Inline `mod`** | `mod name { items }` nests a submodule in the current file |
| **File path** | Without file-scoped `mod`, module identity derives from file path relative to project source root |
## Visibility
- Items default to private to their module.
- `pub` on an item exports it to importers of the containing module.
- `pub use path` re-exports symbols; re-exported names must refer to accessible items.
Source Record: Modules and visibility - Examples
Authority: informative provenance
Legacy path: /platform-spec/language-meta/program-structure/modules-and-visibility/articles/examples/
Source: site/spec-content/platform-spec/language-meta/program-structure/modules-and-visibility/articles/examples/content.md
SHA-256: 3f49f40cf89781c94a080bd3d27e5e3de473670c8fbac427bc138b228135ed48
Migrated source text
## File-scoped module
```beskid
mod MyApp.Services;
pub type UserService {
// ...
}
```
## Inline module
```beskid
mod Internal {
type Helper {
// private by default
}
}
```
## Import with alias
```beskid
use MyApp.Services.UserService as Service;
unit UseService() {
let s = Service {};
}
```
## Re-export
```beskid
mod PublicApi {
pub use MyApp.Services.UserService;
pub use MyApp.Models.User;
}
```
## Visibility
```beskid
mod Library {
type InternalHelper { } // private
pub type PublicApi { } // public
}
```
Source Record: Modules and visibility - FAQ and troubleshooting
Authority: informative provenance
Legacy path: /platform-spec/language-meta/program-structure/modules-and-visibility/articles/faq-and-troubleshooting/
Source: site/spec-content/platform-spec/language-meta/program-structure/modules-and-visibility/articles/faq-and-troubleshooting/content.md
SHA-256: 4a7aba3f3d5916e99b4c2c06c519d38c0d1487d2b48106206ad6eea527789beb
Migrated source text
## Locked decisions
| Decision | ID | Summary |
| --- | --- | --- |
| File-scoped precedence | D-LM-MOD-001 | Explicit `mod` wins over path-derived identity |
| No `internal` keyword | D-LM-MOD-002 | Private-by-default plus `pub` only |
| Package graph external | D-LM-MOD-003 | Cross-package edges from manifests |
| `pub use` re-export | D-LM-MOD-004 | Preserves underlying accessibility |
## FAQ
### Do I need `mod` in every file?
No. Without an explicit `mod`, the module identity derives from the file path.
### Can I nest modules infinitely?
Yes, but readability degrades. Inline modules and file-scoped modules can be nested arbitrarily.
### What is the difference between `use` and `import`?
Beskid uses `use` for imports. There is no `import` keyword.
## Troubleshooting
| Symptom | Likely cause |
| --- | --- |
| **E1505** | `mod` is not the first item in the file |
| **E1506** | Multiple file-scoped `mod` declarations |
| **E1507** | Nested `mod` inside a file-scoped module file |
| **E1501** | Accessing a private item from another module |
| **E1105** | Import path does not exist |
| **W1503** | Import is never used |
Source Record: Modules and visibility - Flow and algorithm
Authority: informative provenance
Legacy path: /platform-spec/language-meta/program-structure/modules-and-visibility/articles/flow-and-algorithm/
Source: site/spec-content/platform-spec/language-meta/program-structure/modules-and-visibility/articles/flow-and-algorithm/content.md
SHA-256: 578056c165505823aa6d529bfb49f4833173a9e087e76da6d115438ea7f7dfc8
Migrated source text
## Compile pipeline placement
```mermaid
flowchart LR
parse[parse]
collect[collect modules]
resolve[resolve imports]
vis[visibility check]
lower[lower to HIR]
parse --> collect --> resolve --> vis --> lower
```
## Module graph algorithm (normative)
1. **Parse module declarations** — `ModuleDeclaration` stores `path` and `visibility`. `InlineModule` stores nested `items`.
2. **Build file-scoped modules** — If a file starts with `mod path;`, that path becomes the file's module identity. `FileScopedModuleNotFirstItem` (**E1505**) if not first. `DuplicateFileScopedModule` (**E1506**) if multiple.
3. **Derive path modules** — Files without file-scoped `mod` derive module identity from their path relative to the source root.
4. **Build inline modules** — `mod name { items }` creates a nested module scope.
5. **Resolve imports** — `use Path;` and `use Path as Alias;` bind imported symbols. `UnknownImportPath` (**E1105**) for invalid paths. `AmbiguousImport` (**E1104**) for conflicts.
6. **Check visibility** — Accessing a private item from another module emits `VisibilityViolationImportPrivate` (**E1501**) or `ResolvePrivateItemInModule` (**E1107**).
7. **Check re-exports** — `pub use` must refer to accessible items.
## Scope chain
```mermaid
flowchart TB
global[Global scope]
file[File module scope]
inline[Inline module scope]
func[Function scope]
global --> file --> inline --> func
```
## LSP / incremental
Re-run module and visibility analysis when `mod`, `use`, or `pub` declarations change.
Source Record: Modules and visibility - Verification and traceability
Authority: informative provenance
Legacy path: /platform-spec/language-meta/program-structure/modules-and-visibility/articles/verification-and-traceability/
Source: site/spec-content/platform-spec/language-meta/program-structure/modules-and-visibility/articles/verification-and-traceability/content.md
SHA-256: 852db63b572c46fb563363361ddae9d6ba2768fd2d9ee09e1a0b22860286fa92
Migrated source text
## Verification matrix
| Scenario | Expected evidence |
| --- | --- |
| File-scoped `mod` | Parsed as first item; module identity set |
| Inline `mod` | Nested scope created |
| `use` import | Symbol bound in current scope |
| `pub` export | Accessible from importers |
| Private access | **E1501** or **E1107** emitted |
| Unused import | **W1503** warned |
## Implementation checklist
- [x] Grammar: `mod`, `use`, `pub`
- [x] AST: `ModuleDeclaration`, `InlineModule`, `UseDeclaration`
- [x] Parser: `beskid.pest` productions for modules
- [x] Resolver: module scope building in `resolve/collect.rs`
- [x] Visibility rules: `visibility.rs` with **E1501–E1507**
- [x] Diagnostics: **E1501–E1507**, **W1503**, **W1504**
- [ ] Cross-package visibility rules
- [ ] Assembly-internal friends (deferred)
## Test locations
- `compiler/crates/beskid_analysis/src/syntax/items/module_declaration.rs` — parser tests
- `compiler/crates/beskid_analysis/src/syntax/items/use_declaration.rs` — parser tests
- `compiler/crates/beskid_analysis/src/analysis/rules/staged/visibility.rs` — visibility tests
- `compiler/crates/beskid_tests` — integration tests for modules