resolver module#
The resolver#
Batching, lockfile replay, and the run’s parts report. Mostly internal machinery;
what a caller uses is the driver surface at the end of the file –
parts_verdict(), flush(), commit_lock() and
uncommitted().
Construction enqueues, a flush resolves the batch, and writes happen once at the end of the run.
A flush is triggered by the first read of deferred state, by introspection, or by
Design.Initialized at the end of elaboration, whichever comes first. The last
is the common case, which is what makes batching worth having. Resolving a batch
means replaying whatever the lock can answer, sweeping orphaned entries for
adoption, and asking the chain for the rest; each component then binds its part in
the frame it was constructed in, through the PartsResolved event.
Pins are committed once the end-of-elaboration flush has resolved everything, so
an ordinary run pins by itself and a driver need do nothing.
parts_verdict() is still the driver’s to ask for, on every exit path
including a failed one, since only the driver knows how a run ended and what to do
about it. Verdict carries the counts, the failures and the exit status.
- class PartsResolved[source]#
Bases:
EventFired after a batch resolves, so each component binds in its own frame.
A provider’s materialize therefore runs as though it were where the component was constructed, and any ambient context it reads while building geometry – a symbol style set in an enclosing class body, say – is the one that was active there. A handler on this event must not construct a part that itself resolves through a chain; see NestedDeferral.
- class Resolution(part, member)[source]#
Bases:
objectA part, and the chain member that answered.
The two travel together because the member cannot be recovered from the part: identity.provider is free text the provider chose, two members may answer to one name, and a delegating provider names its inner source. Only the chain knows, and it only says so at the moment it answers – so the answer carries it from there to the lockfile.
- Parameters:
part (ResolvedPart)
member (str)
- part: ResolvedPart#
- class Severity(*values)[source]#
Bases:
EnumWhether an event fails the run. A property of the mode, not of the event.
- INFO = 0#
- FAILURE = 1#
- class EventKind(*values)[source]#
Bases:
EnumWhat the resolver records. Severity is decided per mode; see severity_of.
- PINNED = 'pinned'#
- ADOPTED = 'adopted'#
- FORFEITED = 'forfeited'#
- RESOLVED = 'resolved'#
The lock could not answer, so the query resolved fresh. Informational under read-write, where it is the ordinary way a design grows; unlocked and fatal under a pinned mode.
- REKEYED = 'rekeyed'#
A chain group the lock does not recognize. stale-chain under a pinned mode.
- REPLACED = 'replaced'#
A provider disowned its payload. replay-failed under a pinned mode.
- UNRESOLVABLE = 'unresolvable'#
the chain is unreachable. A failure in every mode.
- Type:
Could not resolve at all
- DIFFERS = 'differs'#
Fresh resolution differs from the lock. check only, and its namesake.
- severity_of(kind, mode)[source]#
Whether kind fails the run under mode.
The same event is informational in one mode and a failure in another, which is what makes the report categories differ by mode without the mechanism differing.
- class VerdictEvent(kind, description, detail='', origin='')[source]#
Bases:
objectOne recorded event, with what it was about.
- class Verdict(mode=LockMode.OFF, events=list())[source]#
Bases:
objectWhat the run has to say about parts.
Non-empty on essentially every successful build, since hits and adoptions are recorded too. The exit status keys to the failure categories alone, never to emptiness: “has something to say” is not “has something to complain about”.
- Parameters:
mode (LockMode)
events (list[VerdictEvent])
- events: list[VerdictEvent]#
- property failures: list[VerdictEvent]#
- class DeferredState(query, source, lock, lock_scope, origin, batch, part=None, resolved=False, binding=False, failure=None)[source]#
Bases:
objectEverything one deferred component carries.
Lives on the component rather than in a side table, so nothing has to reach around the proxy to find it.
- Parameters:
query (CanonicalQuery)
source (PartSource)
lock (Lock)
lock_scope (str)
origin (str)
batch (Batch)
part (ResolvedPart | None)
resolved (bool)
binding (bool)
failure (str | None)
- query: CanonicalQuery#
- source: PartSource#
- batch: Batch#
The batch this query is pending in.
A direct reference rather than an id into a registry. The registry existed only because this was an id, and nothing ever removed from it, so it grew by one entry per design for the life of the process. A reference keeps the batch alive for exactly as long as a state needs it, which is what the registry was emulating.
- part: ResolvedPart | None = None#
- binding: bool = False#
_bind sets fields and provider code reads them back while the part is not yet marked resolved.
- Type:
Reentrancy guard, and not optional
- failure: str | None = None#
Why this query will never resolve, once that is settled.
A flush drains pending, so a state that failed is terminal but otherwise indistinguishable from one that has not been asked yet: part is None and resolved is False in both cases. Recording the reason here is what lets a later attribute read say which it is, and say why, rather than raising the name it was asked for. Set wherever an unresolvable event is recorded, and carrying the same text, so the exception and the report agree.
- caller_site(component)[source]#
Where a part was constructed, for diagnostics.
Reads jitx’s own FileInfo, which Sourced.__new__ attaches to every structural object at construction. Walking the stack from here instead found the instantiation trampoline in jitx._structural, because by the time a query is enqueued the design frame is no longer the caller – the same reason jitx captures this at __new__ rather than on demand.
- class Run[source]#
Bases:
objectState that outlives any one design: the report, and the lockfiles.
A process may elaborate several designs – jitx build A B, or a test suite – and what they share is exactly what is keyed by file rather than by design. Two designs writing one lockfile must hold the same parsed object or their staged writes diverge, and parts_verdict() is asked once for the run however many designs it covered.
What they do not share is on Batch; see Batch.resolution_cache.
- class Batch(run)[source]#
Bases:
objectOne pending set of queries, resolved together. One per design.
- Parameters:
run (Run)
- pending: list[DeferredState]#
- resolution_cache: dict[tuple[str, str], Resolution]#
Answers already had from the chain, keyed (query digest, chain).
Holds Resolution, not a bare part: the answering member has to survive the cache, or a part served from it loses the provenance the lockfile needs.
Per design, and spanning that design’s flushes, which is the lifetime it needs: nothing stages a resolved miss into the lockfile under off, read or check, so without it a second flush could re-ask a question the first already answered and bound, and one run would give two answers to one question. It does nothing within a flush – the key carries the chain digest and _chain_answers is called once per chain group, so a cross-group hit is impossible and the per-call dedup covers the rest.
Not shared between designs. Two boards carry no reproducibility promise to each other beyond the lockfile, and under read-write they agree anyway, because the first design’s answer is staged into the shared in-memory lockfile and the second finds it at the lock lookup.
- add(state)[source]#
- Return type:
- Parameters:
state (DeferredState)
- current_batch()[source]#
This thread’s pending batch, creating and arming one if needed.
The end-of-elaboration flush is registered here rather than at module import. Event.on outside an active instantiation returns a PrePostInit wrapper for use in a class body, so a module-level decorator registers nothing; called from _defer, this runs inside instantiation and registers for real, with the frame.
- Return type:
- reset()[source]#
Drop all resolver state. For tests and for a fresh run in one process.
- Return type:
- enqueue(component, query, *, source, lock, origin)[source]#
Register a component’s query with the current batch.
Everything the resolution depends on is captured here; nothing is read again at flush time.
- Return type:
- Parameters:
component (Any)
query (PartQuery)
source (PartSource)
lock (Lock)
origin (str)
- parts_verdict()[source]#
The run’s parts report and exit status.
Called on every exit path, a failed translation included: an unresolvable miss leaves a component unbound, and an unbound component raises in translation, so a driver that asked only on success would report nothing in exactly the cases that most need explaining.
When the verdict contains failures, that report is what the user should see, with the translation exception named as its consequence. When it contains only informational events, the exception stands on its own.
- Return type:
- commit_lock()[source]#
Write pending pins, and return the paths written.
Called automatically once the end-of-elaboration flush has resolved everything, so an ordinary run pins without a driver doing anything. Public and idempotent so a harness that flushed by hand can still commit; it writes nothing when nothing is staged.
- uncommitted()[source]#
How many lockfiles are holding pins that were never written.
Zero after any run that reached the end of elaboration. Non-zero means pins were staged by an early flush and the run stopped before finishing, which is the case where writing would produce a partial lock.
- Return type: