pub fn lower_roots(
checked: &Program,
sources: &SourceMap,
schemas: &HostSchemas,
roots: &[(&str, &str)],
) -> Result<Program, Vec<Diagnostic>>Expand description
Lowers only the declarations roots can reach.
A package holds programs that have nothing to do with each other —
benches/ is nine of them and tests/e2e/ is a hundred — and a gap in
one of them is not a reason to refuse the others. So a command lowers
what it is about to run and leaves the rest as a stub nothing names.
A root is a (module, name) pair naming a declaration the way the
checker’s own tables do, and the slice is what any of them reaches.
That is the whole of the API a command needs: it selects roots — the
entry it was asked for, the test it is about to run, the entries a
package configures — and reachability stays here. A caller that walked
the call graph itself would be a second answer to a question this
module already has to answer, and the two would drift.
A root that names nothing this package declares contributes nothing. It
is not an error here, because what a name denotes is the checker’s
question and every caller has already asked it: run_entry answers
“this package does not declare m.f” better than a lowering could, and
it answers it about the program that was actually going to run.
§One answer for the whole set
The gaps this returns are the gaps of everything roots reaches,
together, and there is no telling which root a gap came from. So a
caller that needs one root’s failure to be one root’s failure passes
one root — which is what lower_entry is, and why cove test lowers
each test rather than the suite.
§What “reachable” is, and how this is sure of it
The seed is Program::call_graph,
which the checker already derived: for each declaration, every declaration
it may call. Both precisions are followed, because
CallPrecision::Approximate is a
superset of what may run and a slice has to hold everything that might.
The seed is not the answer, though, and the graph itself says why: a
callee that is a value — xs.map(double), a conformance a dyn
dispatch picks, a Snapshot implementation nothing writes a call to —
contributes no edge, because there is no call site naming it. A
declaration missing from the slice is not a gap the person holding the
source can act on; it is a stub that answers () where a call was meant
to go.
So the slice is closed against the lowering rather than against the
graph. Every place that turns a name into a FunctionId asks whether
this pass lowered it first, and a body that names a declaration this
slice left out records it rather than emitting a call to a stub. What was
recorded is added to the slice and the package is lowered again,
until a round wants nothing — which is a fixed point over what this
lowering emits references to, not over what a second reachability
analysis believes.
The call graph is what makes that one round rather than one per level of the call tree: seeded with nothing, each round could only discover the callees of what the round before it lowered.