pub fn plan_for_node<N>(
node: &N,
recursive: bool,
source_id: u32,
offset: u32,
) -> Result<Option<Rc<GroupPlan>>, NormalizeError>where
N: HasEntry,Expand description
The plan for one binder node, computed ON DEMAND and memoized.
★ Replaces a parse-door walk that planned every binder in every parsed file. Laziness means most of those are never evaluated, so that work was mostly discarded — measured as a ~4% wall-clock tax on a real nixpkgs eval. Planning at first evaluation is identical in result (a group’s plan depends only on its own entries) and pays only for groups that are reached.
The memo is keyed exactly as before, so a plan computed for a node at
offset o in one parse tree is never read for a different (imported) tree
with a binder at the same offset.
A None return is a POSITIVE statement — the group has no duplicate static
key and no dotted path, so the caller’s existing path is already correct.
See the module docs on why that is not a fallback.
§Errors
NormalizeError for a group nix itself rejects — a duplicate attribute
({ a = 1; a = 2; }) or a duplicate formal.
★ This used to swallow that error with .ok().flatten(), which silently
turned a rejection into “no plan” and sent the group down the entry loop.
The result was accepting what nix refuses, and — worse — accepting it
DIFFERENTLY from the bytecode VM: measured 2026-08-18, { a = 1; a = 2; }
is { a = 2; } on the walker and { a = 1; } on the VM, both at exit 0
where nix exits 1. Neither answer is right, so no choice of winner
reconciles the engines; only refusing does.