v5 Semantics: import/export resolution
Lesson, slides, and applied problem sets.
View SlidesLesson
v5 Semantics: modules as a typed interface boundary
v5 turns source into a multi-module package model with explicit compile-time visibility.
Every module now has:
- a public export surface
- imported aliases
- deterministic link-time rules
These are semantic contracts that must be validated before compile/link.
1) Module graph invariants
Identity rules
- module names must be unique
mainmust existmainmust be final module in declaration order
Dependency direction
import must reference a module declared earlier.
Why this strictness:
- deterministic one-pass linking
- no forward-resolution cycles in this pack
- stable compiler behavior in educational setting
Typical diagnostics
duplicate module: <name>main module must be lastunknown module: <name>import of future module: <name>
2) Two-phase semantic analysis strategy
A stable implementation uses two passes per module:
- Export surface collection pass
- gather exported names and their shapes
- build module export map for later resolution
- Body validation pass
- resolve imports into local scope as aliases
- validate statements and expressions against scope/module maps
This avoids false negatives where a body references an export declared later in the same module body and keeps member access checks simple.
3) Import alias scoping and collisions
Imported module aliases become scoped entries in the current module scope.
Rules:
- explicit and implicit aliasing supported
- duplicates in same scope are redeclaration errors
- aliases are used for all member qualification
No special runtime behavior for aliases at this stage; this is purely semantic binding.
4) Member access contract
For m.name in semantic analysis:
mmust be resolvable in current scope- binding for
mmust denote module alias namemust exist in that module’s export set
Failure classes:
unknown module: <alias>invalid module access(left side is not an identifier alias)unknown export: <module>.<name>
This makes dotted access fully typed by surface and deterministic.
5) Compatibility with previous semantics
v5 layers on v4 and does not replace it:
- undefined identifier checks
- redeclare checks
- const assignment checks
- function arity compatibility
- control-flow checks for
return,break,continue
If older rules still fire, module rules should still be present and consistent.
6) Why module checks belong here, not later
If import/export errors are deferred to link/runtime:
- error messages become less local to source
- stage ownership is violated
- debugging cost rises steeply
Doing these checks in analyzer yields:
- deterministic early failures
- cleaner capstone semantics
- better pedagogical loop (source -> analyzer -> compile)
7) Practice checkpoints (almost solved)
1) Module namespace leak + `module util { export fn id(x: number) -> number { return x; } } module main { import util; return id(1); }`
Expected:
- valid module graph
- unqualified
idis unresolved - must use
util.id(1)
2) Future module import
`module main { import util; } module util { export fn id(x:number)->number { return x; } }`
Expected: import of future module: util
3) Unknown module
module main { import missing as m; }
Expected: unknown module: missing
4) Unknown export
`module util { export fn id(x:number)->number { return x; } } module main { import util; util.nope(); }`
Expected: unknown export: util.nope
5) Alias collision
module main { import util; import util as u; }
Expected: redeclare/alias collision at analyzer level.
6) Alias target check
module main { let m = 1; m.fn(); }
Expected: invalid module access because left side is not alias.
Module Items
Semantic Analysis v5