Expand description
§module_lang
Module and import resolution across multiple source files. Given the modules a
front-end has already parsed — each exporting a set of named items — this crate
answers the question every use/import asks: which item does this name refer
to?
A ModuleGraph holds one module per source file, each carrying the
SourceId it was read from and a flat namespace of names. A name is either
defined in the module, with a Visibility and a
language-supplied payload, or imported into it from
another module. resolve walks definitions and import
edges to the payload a name refers to, or returns a defined ResolveError for
a name that is missing, private, or part of an import cycle.
It sits in the SEMA tier of the -lang family, above
symbol_lang — which interns the names this
crate keys on — and source_lang — which owns
the files the modules came from. It owns module and import resolution only: no
parsing, no type checking, no code generation.
§Model
Modules are added in order and identified by a stable ModuleId. Each
module’s names live in a BTreeMap keyed on the
interned Symbol, so a lookup compares integers in O(log items) and
iteration is deterministic. A name is declared at most once per module — a
second definition or a colliding import is a ResolveError::DuplicateName.
Resolution treats a module’s own names as fully visible: a name defined in a
module resolves from within it whether it is public or private. Crossing a
module boundary through an import is where Visibility applies — an import
reaches a name in another module only if it is Visibility::Public. Imports
chain (a module may re-export what it imported), and the walk tracks the
(module, name) pairs it has seen, so a chain that loops back is reported as a
ResolveError::ImportCycle in bounded time rather than recursing without end.
§Quickstart
use intern_lang::Interner;
use module_lang::{ModuleGraph, ResolveError, Visibility};
use source_lang::SourceMap;
// Two files, each a module: `app` uses an item exported by `util`.
let mut sources = SourceMap::new();
let app_src = sources.add("app.lang", "use util.helper;")?;
let util_src = sources.add("util.lang", "pub fn helper() {}")?;
let mut names = Interner::new();
let helper = names.intern("helper");
let mut graph: ModuleGraph<&str> = ModuleGraph::new();
let app = graph.add_module(names.intern("app"), app_src);
let util = graph.add_module(names.intern("util"), util_src);
graph.define(util, helper, Visibility::Public, "fn helper")?;
graph.import(app, util, helper)?;
// The import resolves through to the definition in `util`.
assert_eq!(graph.resolve(app, helper), Ok(&"fn helper"));
// A name nobody declared is a defined error, never a panic.
let missing = names.intern("nope");
assert!(matches!(graph.resolve(app, missing), Err(ResolveError::Unresolved { .. })));§Stability
The public API is being designed across the 0.x series and freezes at 1.0.
Until then a minor release may make a breaking change, each documented in the
CHANGELOG.
See docs/API.md.
Structs§
- Module
Graph - A set of modules and the import edges between them, against which names are resolved.
- Module
Id - A small, copyable handle to one module in a
ModuleGraph. - Source
Id - A small, copyable handle to one source in a
SourceMap. - Symbol
- A small, copyable handle to a string held by an
Interner.
Enums§
- Resolve
Error - The reason a graph operation or a name resolution did not succeed.
- Visibility
- Whether an item is visible outside the module that declares it.