Compiler design / 04. Project graphs and modules

Folders become a canonical source graph with providers, consumers and one compile per physical module.

Turning folders into a compiler graph

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.

Nodes and edges

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?

Roots, packages and ownership

Moth marks module roots in the filesystem on purpose.

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.

Owned and semantic source sets

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?

Provider-first scheduling

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?

Deterministic identity

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.

What can fail here

If current code still compiles a looser closure than the accepted module model, label it transitional against the roadmap plan.

Why Moth does it this way

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.

Roadmap and current status

Exit state

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.

Compiler design / Previous / Next