Compiler design / 04. Project graphs and modules
Folders become a canonical source graph with providers, consumers and one compile per physical module.
A directory is a convenience for humans. It does not tell the compiler which files form one semantic unit, which root owns them or which module must compile before another can bind an import.
Stage 0 answers those questions. It turns the greeting project's filesystem tree into a canonical source graph.
A graph here means nodes plus directed edges with meaning.
Nodes include modules, packages and other compilation units the build system recognises.
Edges record relationships: this module imports that provider, this package depends on that package.
A provider supplies a public interface. A consumer binds against that interface after the provider succeeds.
In the running example, @page.moth consumes @greeting. The edge names a provider relationship. It does not copy greet's body into the page module. Think of a graft point, not a clone.
Diagram placeholder: Roots own files while imports create provider edges
Caption: running-example identities simplified. Question: what does a folder mean to the compiler once Stage 0 finishes?
Moth marks module roots in the filesystem on purpose.
@page.moth. They may hold declarations, dormant top-level runtime work and page fragments.+helpers.moth. They are API-only semantic modules: declarations yes, implicit start and page fragments no.greeting.moth sit under a root's ownership without each file becoming its own root.Source-backed packages compile Moth source into ordinary module artefacts. Binding-backed packages expose typed external interfaces without Moth module bodies. Both appear in the capability surface. Only source-backed modules produce HIR.
Filesystem layout carries semantic constraints. That is the point. The graph should not invent a different topology than the tree authors can see.
Stage 0 distinguishes sets that newcomers often collapse.
OwnedSourceSet. Files this module root owns on disk.
SemanticSourceSet. Files that participate in semantic compilation for the command.
Check-only orphan units. Files the tool may still parse for diagnostics even when they sit outside the semantic set for a build.
Ownership does not inject disconnected declarations into a consumer. A file can be owned and still remain outside the semantic set for a given command. Import visibility and ownership are different axes.
Diagram placeholder: Owned files can remain outside the semantic source set
Caption: conceptual distinction. Question: why does ownership not imply import visibility?
After the graph finalises, the build system schedules compile waves. Providers finish before consumers bind, root growth before branch binding.
A module waits until every required provider interface exists. Binding-backed interfaces may appear earlier than source providers because they do not wait on Moth module compilation.
One physical module compiles once per project or package compilation boundary. Entries later activate roots. They do not recompile the same module once per page.
For the greeting project, greeting.moth's module compiles and publishes an interface. Only then does @page.moth bind import @greeting { greet }.
Diagram placeholder: Providers finish before consumers bind
Caption: deterministic schedule. Question: when can interface binding start for the page?
Diagram placeholder: Page consumes the greeting interface
Caption: teaching graph, not a complete package graph. Question: who must compile first?
Graphs need names that do not jitter when threads finish in a different order.
Stage 0 and the compiler assign stable identities to sources, modules and later declarations. Exact Rust type names may change. The rule does not: identity must not depend on worker completion order, cosmetic filename suffixes for roots or source position alone.
Determinism lets parallel compilation merge results in canonical order. Diagnostics and artefacts stay reproducible.
If current code still compiles a looser closure than the accepted module model, label it transitional against the roadmap plan.
Alternative: global search paths, nearest-match resolution or "scan the package folder and hope."
Choice: structural roots, explicit package identity and canonical ownership.
Benefit: graph scheduling and diagnostics share the same source facts. A missing provider is a graph fact, not a surprise during type checking.
Cost: filesystem layout carries semantic constraints authors must learn.
canonical-module-compilation-and-scoped-packages-plan.md.You leave with a canonical source graph, source sets and ordered compile waves. Next: how the compiler prepares each selected source file once, starting from characters.