Error
Diagnostic Error ADT.
An Error has a Layer (Kernel | Generic | Contract), a layer-discriminated
Detail record, a short msg, an optional hint, and a list of children.
A leaf has children = []; a branch has a non-empty children list
whose entries are { position, error } pairs. Sibling failures produce
many children; a chained descent produces one child; a leaf has none.
Constructors: mkKernelError { position?, rule, msg, expected?, got?, ctx_depth?, hint? } mkGenericError { type?, context?, value, desc?, index?, guard?, msg, hint? } mkContractError { type?, context?, value, guard, msg, hint? }
Per-layer Detail builders: mkKernelDetail / mkGenericDetail / mkContractDetail
Combinators: nestUnder : Position -> Error -> Error addChild : Position -> Error -> Error -> Error setLeafHint : Hint -> Error -> Error
Layer constants: Kernel, Generic, Contract. Predicates: isError, isLayer, isDetail, isKernel, isGeneric, isContract. Equality: eq (structural).
Pure data. No dependencies on kernel, trampoline, effects, tc, or types modules.
Contract
Contract: Layer constant tagging a value with the correct shape that violates a refinement predicate (guard).
Paired with a ContractDetail carrying value, the mandatory
guard = { predicate = "…"; }, and optionally the surface
type and outer context. Renderer formats Contract failures
as predicate violations distinct from shape mismatches.
Pre-allocated; never construct via { _tag = ...; }.
Generic
Generic: Layer constant tagging a value whose shape fails to inhabit a description.
Emitted by sugar validators (fx.types.*) and the generic
descElim-driven shape checker. Paired with a GenericDetail
carrying value (the failing input) and usually type /
desc identifying the violated contract. Pure shape failures
(record-field type mismatch, wrong variant tag) live here;
refinement-predicate failures belong in Contract.
Kernel
Kernel: Layer constant tagging a kernel-layer diagnostic error; Error.layer is set to this for typechecker/elaborator failures.
Emitted by checkHoas / infer / elaborator passes. Paired
with a KernelDetail carrying rule (the failing kernel rule
name) and typically expected / got carrying normalised
value-domain types. Pre-allocated; never construct via
{ _tag = ...; }.
addChild
addChild: append a keyed child Error to a branching parent; used by collecting handlers that gather many sibling failures under one root without flattening the blame paths.
addChild : Position -> Error -> Error -> Error -- position, parent, child
Use for sibling collection (record fields, variant cases, list
elements all rejected against the same root contract). Order
of insertion is preserved in the resulting children list, so
callers control reporting order. The parent's layer /
detail / msg are not overwritten — they describe the
outer-shape failure, while each child describes one element's
failure. For descent (one-deep hop), use nestUnder instead;
addChild is the multi-sibling counterpart.
appendTrace
appendTrace: append a { rule, position } entry to a Kernel-layer error's detail.trace; non-Kernel errors pass through unchanged. Used by bindP/bindPR to auto-capture the descent chain as the error unwinds.
appendTrace : (String | null) -> Position -> Error -> Error
captureFrame
captureFrame: project a typing context into a { depth, env, types, names } Frame suitable for KernelDetail.frame; reads ctx.names defensively so contexts without name tracking still produce a well-formed frame.
captureFrame : Ctx -> Frame
eq
eq: structural equality on Error values — relies on Nix's attrset-by-content comparison so equal trees compare equal regardless of construction path.
eq : Error -> Error -> Bool
Equality is content-based, so two distinct constructions of
the same shape (mkKernelError { rule = "r"; msg = "m"; }
twice) compare equal — useful for test assertions and for
deduplicating siblings in a collecting handler. Position
ADT values inside children participate in the comparison,
so chains with reordered hops are not equal.
isContract
isContract: Layer variant predicate — true iff the value is the Contract constant.
isContract : Any -> Bool
isDetail
isDetail: predicate recognising the Detail ADT — checks _tag == "Detail" plus the presence of a layer self-discriminator.
isDetail : Any -> Bool
isError
isError: predicate recognising the Error ADT — checks _tag == "Error" plus the required fields layer, detail, msg, children.
isError : Any -> Bool
Use at module boundaries where input could be anything;
internal code that constructs Errors via the per-layer
constructors doesn't need to check. The predicate rejects bare
attrsets that happen to have _tag = "Error" but are missing
required fields — guards against accidental partial
construction more than against type confusion.
isGeneric
isGeneric: Layer variant predicate — true iff the value is the Generic constant.
isGeneric : Any -> Bool
isKernel
isKernel: Layer variant predicate — true iff the value is the Kernel constant.
isKernel : Any -> Bool
isLayer
isLayer: predicate recognising the Layer ADT — checks _tag == "Layer" plus the presence of tag; accepts Kernel, Generic, and Contract.
isLayer : Any -> Bool
Does not distinguish the variants — pair with a check on .tag
for layer-specific dispatch, or use isKernel / isGeneric /
isContract. Rejects plain attrsets { tag = "Kernel"; } that
lack _tag, so renderer branching can trust a positive result
to mean a canonical constant.
mkContractDetail
mkContractDetail: build a Contract-layer Detail record from optional field overrides; defaults set every field to null and carry the layer self-discriminator. Callers should always populate guard.
mkContractDetail : { type?, value?, context?, guard? } -> Detail
mkContractError
mkContractError: build a contract-layer leaf Error from { value, guard, msg, type?, context?, hint? }; value has the correct shape but guard rejected it.
mkContractError : { value, guard, msg, type?, context?, hint? } -> Error
guard is mandatory — a { predicate = "…"; } record naming
the refinement that rejected value. Without a guard the
failure is structural, not contractual, and belongs in
mkGenericError. type and context describe the surrounding
contract for the renderer.
mkGenericDetail
mkGenericDetail: build a Generic-layer Detail record from optional field overrides; defaults set every field to null and carry the layer self-discriminator.
mkGenericDetail : { type?, desc?, value?, context?, index?, guard? } -> Detail
mkGenericError
mkGenericError: build a generic-layer leaf Error from { value, msg, type?, context?, desc?, index?, guard?, hint? }; value is the thing whose shape failed to inhabit the description.
mkGenericError : { value, msg, type?, context?, desc?, index?, guard?, hint? } -> Error
type is the surface name presented to the user ("PersonT",
"NonEmptyList"); desc is the underlying Desc I shape if
the producer has it; context is an outer-scope name used in
nested record/variant failures. The guard slot is retained
for callers that have not yet migrated refinement failures to
mkContractError; prefer Contract for new producers.
mkKernelDetail
mkKernelDetail: build a Kernel-layer Detail record from optional field overrides; defaults set every field to null (trace to []) and carry the layer self-discriminator.
mkKernelDetail : { rule?, expected?, got?, ctx_depth?, term?, frame?, trace? } -> Detail
mkKernelError
mkKernelError: build a kernel-layer leaf Error from { rule, msg, position?, expected?, got?, ctx_depth?, term?, frame?, trace?, hint? }; when position is supplied, wraps the leaf in nestUnder so the rule emits its own descent coordinate.
mkKernelError : { rule, msg, position?, expected?, got?, ctx_depth?, term?, frame?, trace?, hint? } -> Error
rule is the kernel-rule identifier — hints.nix keys its
Hint table off rule plus blame-path-suffix, so use the
canonical name ("check", "desc-arg", "univ-poly", …) not
an ad-hoc string. expected / got should be value-domain
terms (post-eval), not unelaborated HOAS — the diff renderer
expects normalised shape. Supplying position is equivalent
to nestUnder p (mkKernelError { … }); use it when the rule
emits a single descent hop, not when the caller will add hops
of its own. term names the failing surface-term shape;
frame is a Ctx snapshot via captureFrame; trace is
usually left empty at emission and populated by bindP /
bindPR as the error unwinds.
nestUnder
nestUnder: add a positional hop above inner, producing a new branch whose single child carries position as its edge label; pass-through of layer/detail/msg/hint preserves leaf rendering at any depth.
nestUnder : Position -> Error -> Error
Pass-through invariant: the wrapper's layer, detail, msg,
and hint are copied from inner. This is what lets the
renderer pick its branch (Kernel vs Generic) and pick the
leaf's payload at any depth without descending the chain —
the entire single-child chain reports the same diagnostic,
just with a longer blame path. Stacking is by repeated
application: nestUnder pOuter (nestUnder pInner leaf) yields
a chain whose outer edge is pOuter, inner edge pInner.
setLeafHint
setLeafHint: walk the single-child chain to its leaf and overwrite hint on the endpoint, returning a structurally-equivalent tree; stack-safe via splitChainFast/splitChainSlow to kernel-descent depth.
setLeafHint : Hint -> Error -> Error
Only mutates the chain endpoint. If the endpoint is branching
(children count > 1), returns the tree unchanged — sibling-
specific hint attachment is the caller's responsibility, the
function is intentionally a no-op there to avoid clobbering
ambiguity. Stack-safe to arbitrary depth: switches from direct
recursion to a genericClosure worklist past fastPathLimit
(500) frames. Use after hints.resolve has produced a Hint
for the failure, not before.