The model
resolveresolves one unit with the default (lexical, Kraken-style) policy: the lazy path.- A
Policyis a language's scoping rules as data. Presets:kraken,php,python. - A
Resolverresolves one unit with a policy, anEnv(names outside the program: other packages, the standard library, primitive types), and aBudget. - A
Programresolves several units against each other; a unit given a root name is reachable from every unit asname::item. - The
Resolutionholds the resolved units, the **Diagnostic**s, and theIndex.
What it guarantees, and how each guarantee is checked:
| Guarantee | How it is held |
|---|---|
| Lexical resolution is right: the innermost visible binder or item wins, hoisting and frames included. | Property tests compare it with a separate reference resolver (a stack of scopes searched innermost-out) on random programs of nested blocks, items, closures, and parameters, under scope hoisting and under declare-before-use. |
| Imports reach the least fixpoint of their rules, whatever the order. | Property tests compare it with a naive fixpoint over random module graphs of definitions, aliased imports, and globs, public and private, with cycles and ambiguity; a second property permutes the modules and checks that nothing changes. |
| Every reference ends resolved, or with exactly one diagnostic. | Checked by the property tests on every random program; access errors keep their resolution and carry one diagnostic. |
The result is a valid Hir. |
Every resolution goes through Hir::resolve_partial; a refusal becomes Res::Err and a diagnostic. The tests validate the result again. |
| The index is consistent: a definition lists exactly the references that point at it. | Checked by the property tests on every random program. |
| No recursion over input; deep or wide programs never overflow the stack. | Tests resolve 100,000 nested blocks, 25,000 nested closures, 20,000 classes nested through method bodies, and a 50,000-item module. |
| Untrusted input cannot drive unbounded work. | Glob propagation, inheritance walks, and did-you-mean comparisons each run against an explicit Budget; exceeding the first two fails the run cleanly, the third only stops suggesting. |
Installation
[]
= "0.2"
= "0.3"
= "1"
Without the standard library:
[]
= { = "0.2", = false }
Quick start
Build a unit with hir-lang's builder, resolve it, read the result:
use ;
use Interner;
// fn length(n) { n } fn main() { lenght }
let mut names = new;
let n = new;
let mut b = new;
let = b.local_param;
let use_n = b.name_expr;
let body = b.block;
let length = b.func;
let typo = b.name_expr;
let main_body = b.block;
let main = b.func;
let root = b.module;
let hir = b.finish?;
let unit = hir.unit;
let res = resolve?;
let hir = res.hir.unwrap;
let Path = *hir.expr else ;
assert_eq!;
assert_eq!;
# Ok::
A language's policy and environment
use ;
use Interner;
use ;
let mut names = new;
let int = new;
// The host provides primitive type names.
let env = new.with_prelude;
// type Count = int
let mut b = new;
let path = b.name_path;
let ty = b.ty;
let alias = b.item;
let root = b.module;
let res = new.with_env.resolve?;
assert_eq!;
# Ok::
Several units, and the index
use ;
use Interner;
use ;
let mut names = new;
let = ;
// Unit 1, reachable as `util`: pub fn helper() {}
let mut b = for_unit;
let body = b.block;
let f = b.item;
let root = b.module;
let util_hir = b.finish?;
// Unit 2: fn main() { util::helper }
let mut b = for_unit;
let segs = ;
let segs = b.list;
let path = b.path;
let call = b.expr;
let body = b.block;
let main = b.func;
let root = b.module;
let app_hir = b.finish?;
let mut program = new;
program.add_unit;
program.add_unit;
let res = program.resolve?;
assert!;
// Find references: `util::helper` is one reference to `helper`.
let index = res.index;
let def = index.def_of.unwrap;
assert_eq!;
assert_eq!;
# Ok::
More in examples/: basic (the lazy path), multi_unit (two units importing each other), lsp (go-to-definition, references, rename, outline).
What resolution covers
- Binders through
Hir::lookup_localand the walk'sBind/Scope/Frameevents: the HIR's own scope rules, never re-derived. A local behind a frame it cannot cross (a nested function) is reported, not silently skipped. - Items by namespace and hoisting: visible in their whole scope, from their declaration on, or (PHP) in their whole module wherever declared.
- Paths:
a::b::cthrough modules, sum variants, and the environment's containers; partial resolution where the rest is type-directed (Vec::new,T::Item,<T as Tr>::Out); roots::,self::,super::;self::/parent::/static::early or late per policy. - Imports: single, aliased, glob, re-exported, private, across units, in cycles; ambiguity between globs reported where the name is used.
- Patterns: a bare identifier pattern matches a constant, unit variant, or unit record of that name, and binds otherwise.
- Classes: member tables with mixin expansion (
insteadof,as), inherited lookups, private and protected access. - Diagnostics: unresolved names (with a suggestion within a small edit distance), ambiguous globs, private access, duplicates, uncapturable locals, wrong kinds, import cycles, mixin conflicts.
Performance
Measured with cargo bench --bench bench on a desktop x86_64 machine (Windows, release profile), resolution only (building the HIR is excluded):
| Benchmark | Size | Time |
|---|---|---|
| One unit, functions with locals and calls | 124,000 nodes | 24.6 ms |
| One unit, functions with locals and calls | 1,240,000 nodes | 317 ms |
| 1,000 modules in a glob ring, aliased imports | 53,000 nodes | 19.9 ms |
| Eight units, each calling into the next | 992,000 nodes | 263 ms |
These are library numbers: the time includes building the index and writing every resolution into the Hir.
Testing
The property tests in tests/properties.rs and tests/import_graph.rs hold the resolver to independent references written in the tests. Every rust example in this README and in docs/API.md is compiled and run as a doctest.
Cross-platform support
- Linux (x86_64, aarch64)
- macOS (x86_64, Apple Silicon)
- Windows (x86_64)
The crate uses no operating-system facilities and no platform-specific code.
Contributing
See REPS.md for the engineering standards every change is held to, dev/DIRECTIVES.md for the definition of done, and dev/ROADMAP.md for the plan. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.