miden_processor/
errors.rs

1use alloc::{boxed::Box, sync::Arc, vec::Vec};
2use core::error::Error;
3
4use miden_air::RowIndex;
5use miette::Diagnostic;
6use vm_core::{
7    debuginfo::{SourceFile, SourceManager, SourceSpan},
8    mast::{BasicBlockNode, DecoratorId, MastForest, MastNodeExt, MastNodeId},
9    stack::MIN_STACK_DEPTH,
10    utils::to_hex,
11};
12use winter_prover::{ProverError, math::FieldElement};
13
14use super::{
15    Digest, Felt, QuadFelt, Word,
16    crypto::MerkleError,
17    system::{FMP_MAX, FMP_MIN},
18};
19use crate::MemoryError;
20
21// EXECUTION ERROR
22// ================================================================================================
23
24#[derive(Debug, thiserror::Error, Diagnostic)]
25pub enum ExecutionError {
26    #[error("value for key {} not present in the advice map", to_hex(Felt::elements_as_bytes(.key)))]
27    #[diagnostic()]
28    AdviceMapKeyNotFound {
29        #[label]
30        label: SourceSpan,
31        #[source_code]
32        source_file: Option<Arc<SourceFile>>,
33        key: Word,
34    },
35    #[error("value for key {} already present in the advice map when loading MAST forest", to_hex(Felt::elements_as_bytes(.key)))]
36    #[diagnostic(help(
37        "previous values at key were '{prev_values:?}'. Operation would have replaced them with '{new_values:?}'",
38    ))]
39    AdviceMapKeyAlreadyPresent {
40        key: Word,
41        prev_values: Vec<Felt>,
42        new_values: Vec<Felt>,
43    },
44    #[error("advice stack read failed at clock cycle {row}")]
45    #[diagnostic()]
46    AdviceStackReadFailed {
47        #[label]
48        label: SourceSpan,
49        #[source_code]
50        source_file: Option<Arc<SourceFile>>,
51        row: RowIndex,
52    },
53    /// This error is caught by the assembler, so we don't need diagnostics here.
54    #[error("illegal use of instruction {0} while inside a syscall")]
55    CallInSyscall(&'static str),
56    /// This error is caught by the assembler, so we don't need diagnostics here.
57    #[error("instruction `caller` used outside of kernel context")]
58    CallerNotInSyscall,
59    #[error("external node with mast root {0} resolved to an external node")]
60    CircularExternalNode(Digest),
61    #[error("exceeded the allowed number of max cycles {0}")]
62    CycleLimitExceeded(u32),
63    #[error("decorator id {decorator_id} does not exist in MAST forest")]
64    DecoratorNotFoundInForest { decorator_id: DecoratorId },
65    #[error("division by zero at clock cycle {clk}")]
66    #[diagnostic()]
67    DivideByZero {
68        #[label]
69        label: SourceSpan,
70        #[source_code]
71        source_file: Option<Arc<SourceFile>>,
72        clk: RowIndex,
73    },
74    #[error("failed to execute the dynamic code block provided by the stack with root 0x{hex}; the block could not be found",
75      hex = to_hex(.digest.as_bytes())
76    )]
77    #[diagnostic()]
78    DynamicNodeNotFound {
79        #[label]
80        label: SourceSpan,
81        #[source_code]
82        source_file: Option<Arc<SourceFile>>,
83        digest: Digest,
84    },
85    #[error("error during processing of event in on_event handler")]
86    #[diagnostic()]
87    EventError {
88        #[label]
89        label: SourceSpan,
90        #[source_code]
91        source_file: Option<Arc<SourceFile>>,
92        #[source]
93        error: Box<dyn Error + Send + Sync + 'static>,
94    },
95    #[error("failed to execute Ext2Intt operation: {0}")]
96    Ext2InttError(Ext2InttError),
97    #[error("assertion failed at clock cycle {clk} with error {}",
98      match err_msg {
99        Some(msg) => format!("message: {msg}"),
100        None => format!("code: {err_code}"),
101      }
102    )]
103    #[diagnostic()]
104    FailedAssertion {
105        #[label]
106        label: SourceSpan,
107        #[source_code]
108        source_file: Option<Arc<SourceFile>>,
109        clk: RowIndex,
110        err_code: Felt,
111        err_msg: Option<Arc<str>>,
112    },
113    #[error("failed to execute the program for internal reason: {0}")]
114    FailedToExecuteProgram(&'static str),
115    #[error(
116        "Updating FMP register from {0} to {1} failed because {1} is outside of {FMP_MIN}..{FMP_MAX}"
117    )]
118    InvalidFmpValue(Felt, Felt),
119    #[error("FRI domain segment value cannot exceed 3, but was {0}")]
120    InvalidFriDomainSegment(u64),
121    #[error("degree-respecting projection is inconsistent: expected {0} but was {1}")]
122    InvalidFriLayerFolding(QuadFelt, QuadFelt),
123    #[error(
124        "when returning from a call or dyncall, stack depth must be {MIN_STACK_DEPTH}, but was {depth}"
125    )]
126    #[diagnostic()]
127    InvalidStackDepthOnReturn {
128        #[label("when returning from this call site")]
129        label: SourceSpan,
130        #[source_code]
131        source_file: Option<Arc<SourceFile>>,
132        depth: usize,
133    },
134    #[error(
135        "provided merkle tree {depth} is out of bounds and cannot be represented as an unsigned 8-bit integer"
136    )]
137    #[diagnostic()]
138    InvalidMerkleTreeDepth {
139        #[label]
140        label: SourceSpan,
141        #[source_code]
142        source_file: Option<Arc<SourceFile>>,
143        depth: Felt,
144    },
145    #[error("provided node index {index} is out of bounds for a merkle tree node at depth {depth}")]
146    #[diagnostic()]
147    InvalidMerkleTreeNodeIndex {
148        #[label]
149        label: SourceSpan,
150        #[source_code]
151        source_file: Option<Arc<SourceFile>>,
152        depth: Felt,
153        index: Felt,
154    },
155    #[error("attempted to calculate integer logarithm with zero argument at clock cycle {clk}")]
156    #[diagnostic()]
157    LogArgumentZero {
158        #[label]
159        label: SourceSpan,
160        #[source_code]
161        source_file: Option<Arc<SourceFile>>,
162        clk: RowIndex,
163    },
164    #[error("malformed signature key: {key_type}")]
165    #[diagnostic(help("the secret key associated with the provided public key is malformed"))]
166    MalformedSignatureKey {
167        #[label]
168        label: SourceSpan,
169        #[source_code]
170        source_file: Option<Arc<SourceFile>>,
171        key_type: &'static str,
172    },
173    #[error(
174        "MAST forest in host indexed by procedure root {root_digest} doesn't contain that root"
175    )]
176    MalformedMastForestInHost {
177        #[label]
178        label: SourceSpan,
179        #[source_code]
180        source_file: Option<Arc<SourceFile>>,
181        root_digest: Digest,
182    },
183    #[error("node id {node_id} does not exist in MAST forest")]
184    MastNodeNotFoundInForest { node_id: MastNodeId },
185    #[error(transparent)]
186    #[diagnostic(transparent)]
187    MemoryError(MemoryError),
188    #[error("no MAST forest contains the procedure with root digest {root_digest}")]
189    NoMastForestWithProcedure {
190        #[label]
191        label: SourceSpan,
192        #[source_code]
193        source_file: Option<Arc<SourceFile>>,
194        root_digest: Digest,
195    },
196    #[error("merkle path verification failed for value {value} at index {index} in the Merkle tree with root {root} (error {err})",
197      value = to_hex(Felt::elements_as_bytes(value)),
198      root = to_hex(root.as_bytes()),
199      err = match err_msg {
200        Some(msg) => format!("message: {msg}"),
201        None => format!("code: {err_code}"),
202      }
203    )]
204    MerklePathVerificationFailed {
205        #[label]
206        label: SourceSpan,
207        #[source_code]
208        source_file: Option<Arc<SourceFile>>,
209        value: Word,
210        index: Felt,
211        root: Digest,
212        err_code: Felt,
213        err_msg: Option<Arc<str>>,
214    },
215    #[error("failed to lookup value in Merkle store")]
216    MerkleStoreLookupFailed {
217        #[label]
218        label: SourceSpan,
219        #[source_code]
220        source_file: Option<Arc<SourceFile>>,
221        #[source]
222        err: MerkleError,
223    },
224    #[error("advice provider Merkle store backend merge failed")]
225    MerkleStoreMergeFailed {
226        #[label]
227        label: SourceSpan,
228        #[source_code]
229        source_file: Option<Arc<SourceFile>>,
230        #[source]
231        err: MerkleError,
232    },
233    #[error("advice provider Merkle store backend update failed")]
234    MerkleStoreUpdateFailed {
235        #[label]
236        label: SourceSpan,
237        #[source_code]
238        source_file: Option<Arc<SourceFile>>,
239        #[source]
240        err: MerkleError,
241    },
242    #[error("if statement expected a binary value on top of the stack, but got {value}")]
243    #[diagnostic()]
244    NotBinaryValueIf {
245        #[label]
246        label: SourceSpan,
247        #[source_code]
248        source_file: Option<Arc<SourceFile>>,
249        value: Felt,
250    },
251    #[error("operation expected a binary value, but got {value}")]
252    #[diagnostic()]
253    NotBinaryValueOp {
254        #[label]
255        label: SourceSpan,
256        #[source_code]
257        source_file: Option<Arc<SourceFile>>,
258        value: Felt,
259    },
260    #[error("loop condition must be a binary value, but got {value}")]
261    #[diagnostic(help(
262        "this could happen either when first entering the loop, or any subsequent iteration"
263    ))]
264    NotBinaryValueLoop {
265        #[label]
266        label: SourceSpan,
267        #[source_code]
268        source_file: Option<Arc<SourceFile>>,
269        value: Felt,
270    },
271    #[error("operation expected a u32 value, but got {value} (error code: {err_code})")]
272    NotU32Value {
273        #[label]
274        label: SourceSpan,
275        #[source_code]
276        source_file: Option<Arc<SourceFile>>,
277        value: Felt,
278        err_code: Felt,
279    },
280    #[error(
281        "Operand stack input is {input} but it is expected to fit in a u32 at clock cycle {clk}"
282    )]
283    #[diagnostic()]
284    NotU32StackValue {
285        #[label]
286        label: SourceSpan,
287        #[source_code]
288        source_file: Option<Arc<SourceFile>>,
289        clk: RowIndex,
290        input: u64,
291    },
292    #[error("stack should have at most {MIN_STACK_DEPTH} elements at the end of program execution, but had {} elements", MIN_STACK_DEPTH + .0)]
293    OutputStackOverflow(usize),
294    #[error("a program has already been executed in this process")]
295    ProgramAlreadyExecuted,
296    #[error("proof generation failed")]
297    ProverError(#[source] ProverError),
298    #[error("smt node {node_hex} not found", node_hex = to_hex(Felt::elements_as_bytes(node)))]
299    SmtNodeNotFound {
300        #[label]
301        label: SourceSpan,
302        #[source_code]
303        source_file: Option<Arc<SourceFile>>,
304        node: Word,
305    },
306    #[error("expected pre-image length of node {node_hex} to be a multiple of 8 but was {preimage_len}",
307      node_hex = to_hex(Felt::elements_as_bytes(node)),
308    )]
309    SmtNodePreImageNotValid {
310        #[label]
311        label: SourceSpan,
312        #[source_code]
313        source_file: Option<Arc<SourceFile>>,
314        node: Word,
315        preimage_len: usize,
316    },
317    #[error("syscall failed: procedure with root {hex} was not found in the kernel",
318      hex = to_hex(proc_root.as_bytes())
319    )]
320    SyscallTargetNotInKernel {
321        #[label]
322        label: SourceSpan,
323        #[source_code]
324        source_file: Option<Arc<SourceFile>>,
325        proc_root: Digest,
326    },
327    #[error("failed to execute arithmetic circuit evaluation operation: {error}")]
328    #[diagnostic()]
329    AceChipError {
330        #[label("this call failed")]
331        label: SourceSpan,
332        #[source_code]
333        source_file: Option<Arc<SourceFile>>,
334        error: AceError,
335    },
336}
337
338impl From<Ext2InttError> for ExecutionError {
339    fn from(value: Ext2InttError) -> Self {
340        Self::Ext2InttError(value)
341    }
342}
343
344impl ExecutionError {
345    pub fn advice_map_key_not_found(
346        key: Word,
347        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
348    ) -> Self {
349        let (label, source_file) = err_ctx.label_and_source_file();
350        Self::AdviceMapKeyNotFound { label, source_file, key }
351    }
352
353    pub fn advice_stack_read_failed(
354        row: RowIndex,
355        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
356    ) -> Self {
357        let (label, source_file) = err_ctx.label_and_source_file();
358        Self::AdviceStackReadFailed { label, source_file, row }
359    }
360
361    pub fn divide_by_zero(clk: RowIndex, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
362        let (label, source_file) = err_ctx.label_and_source_file();
363        Self::DivideByZero { clk, label, source_file }
364    }
365
366    pub fn input_not_u32(
367        clk: RowIndex,
368        input: u64,
369        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
370    ) -> Self {
371        let (label, source_file) = err_ctx.label_and_source_file();
372        Self::NotU32StackValue { clk, input, label, source_file }
373    }
374
375    pub fn dynamic_node_not_found(
376        digest: Digest,
377        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
378    ) -> Self {
379        let (label, source_file) = err_ctx.label_and_source_file();
380
381        Self::DynamicNodeNotFound { label, source_file, digest }
382    }
383
384    pub fn event_error(
385        error: Box<dyn Error + Send + Sync + 'static>,
386        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
387    ) -> Self {
388        let (label, source_file) = err_ctx.label_and_source_file();
389
390        Self::EventError { label, source_file, error }
391    }
392
393    pub fn failed_assertion(
394        clk: RowIndex,
395        err_code: Felt,
396        err_msg: Option<Arc<str>>,
397        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
398    ) -> Self {
399        let (label, source_file) = err_ctx.label_and_source_file();
400
401        Self::FailedAssertion {
402            label,
403            source_file,
404            clk,
405            err_code,
406            err_msg,
407        }
408    }
409
410    pub fn invalid_merkle_tree_depth(
411        depth: Felt,
412        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
413    ) -> Self {
414        let (label, source_file) = err_ctx.label_and_source_file();
415        Self::InvalidMerkleTreeDepth { label, source_file, depth }
416    }
417
418    pub fn invalid_merkle_tree_node_index(
419        depth: Felt,
420        index: Felt,
421        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
422    ) -> Self {
423        let (label, source_file) = err_ctx.label_and_source_file();
424        Self::InvalidMerkleTreeNodeIndex { label, source_file, depth, index }
425    }
426
427    pub fn invalid_stack_depth_on_return(
428        depth: usize,
429        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
430    ) -> Self {
431        let (label, source_file) = err_ctx.label_and_source_file();
432        Self::InvalidStackDepthOnReturn { label, source_file, depth }
433    }
434
435    pub fn log_argument_zero(clk: RowIndex, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
436        let (label, source_file) = err_ctx.label_and_source_file();
437        Self::LogArgumentZero { label, source_file, clk }
438    }
439
440    pub fn malfored_mast_forest_in_host(
441        root_digest: Digest,
442        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
443    ) -> Self {
444        let (label, source_file) = err_ctx.label_and_source_file();
445        Self::MalformedMastForestInHost { label, source_file, root_digest }
446    }
447
448    pub fn malformed_signature_key(
449        key_type: &'static str,
450        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
451    ) -> Self {
452        let (label, source_file) = err_ctx.label_and_source_file();
453        Self::MalformedSignatureKey { label, source_file, key_type }
454    }
455
456    pub fn merkle_path_verification_failed(
457        value: Word,
458        index: Felt,
459        root: Digest,
460        err_code: Felt,
461        err_msg: Option<Arc<str>>,
462        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
463    ) -> Self {
464        let (label, source_file) = err_ctx.label_and_source_file();
465
466        Self::MerklePathVerificationFailed {
467            label,
468            source_file,
469            value,
470            index,
471            root,
472            err_code,
473            err_msg,
474        }
475    }
476
477    pub fn merkle_store_lookup_failed(
478        err: MerkleError,
479        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
480    ) -> Self {
481        let (label, source_file) = err_ctx.label_and_source_file();
482        Self::MerkleStoreLookupFailed { label, source_file, err }
483    }
484
485    /// Note: This error currently never occurs, since `MerkleStore::merge_roots()` never fails.
486    pub fn merkle_store_merge_failed(
487        err: MerkleError,
488        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
489    ) -> Self {
490        let (label, source_file) = err_ctx.label_and_source_file();
491        Self::MerkleStoreMergeFailed { label, source_file, err }
492    }
493
494    pub fn merkle_store_update_failed(
495        err: MerkleError,
496        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
497    ) -> Self {
498        let (label, source_file) = err_ctx.label_and_source_file();
499        Self::MerkleStoreUpdateFailed { label, source_file, err }
500    }
501
502    pub fn no_mast_forest_with_procedure(
503        root_digest: Digest,
504        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
505    ) -> Self {
506        let (label, source_file) = err_ctx.label_and_source_file();
507        Self::NoMastForestWithProcedure { label, source_file, root_digest }
508    }
509
510    pub fn not_binary_value_if(value: Felt, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
511        let (label, source_file) = err_ctx.label_and_source_file();
512        Self::NotBinaryValueIf { label, source_file, value }
513    }
514
515    pub fn not_binary_value_op(value: Felt, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
516        let (label, source_file) = err_ctx.label_and_source_file();
517        Self::NotBinaryValueOp { label, source_file, value }
518    }
519
520    pub fn not_binary_value_loop(
521        value: Felt,
522        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
523    ) -> Self {
524        let (label, source_file) = err_ctx.label_and_source_file();
525        Self::NotBinaryValueLoop { label, source_file, value }
526    }
527
528    pub fn not_u32_value(
529        value: Felt,
530        err_code: Felt,
531        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
532    ) -> Self {
533        let (label, source_file) = err_ctx.label_and_source_file();
534        Self::NotU32Value { label, source_file, value, err_code }
535    }
536
537    pub fn smt_node_not_found(node: Word, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
538        let (label, source_file) = err_ctx.label_and_source_file();
539        Self::SmtNodeNotFound { label, source_file, node }
540    }
541
542    pub fn smt_node_preimage_not_valid(
543        node: Word,
544        preimage_len: usize,
545        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
546    ) -> Self {
547        let (label, source_file) = err_ctx.label_and_source_file();
548        Self::SmtNodePreImageNotValid { label, source_file, node, preimage_len }
549    }
550
551    pub fn syscall_target_not_in_kernel(
552        proc_root: Digest,
553        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
554    ) -> Self {
555        let (label, source_file) = err_ctx.label_and_source_file();
556        Self::SyscallTargetNotInKernel { label, source_file, proc_root }
557    }
558
559    pub fn failed_arithmetic_evaluation(
560        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
561        error: AceError,
562    ) -> Self {
563        let (label, source_file) = err_ctx.label_and_source_file();
564        Self::AceChipError { label, source_file, error }
565    }
566}
567
568impl AsRef<dyn Diagnostic> for ExecutionError {
569    fn as_ref(&self) -> &(dyn Diagnostic + 'static) {
570        self
571    }
572}
573
574// ACE ERROR
575// ================================================================================================
576
577#[derive(Debug, thiserror::Error)]
578pub enum AceError {
579    #[error("num of variables should be word aligned and non-zero but was {0}")]
580    NumVarIsNotWordAlignedOrIsEmpty(u64),
581    #[error("num of evaluation gates should be word aligned and non-zero but was {0}")]
582    NumEvalIsNotWordAlignedOrIsEmpty(u64),
583    #[error("circuit does not evaluate to zero")]
584    CircuitNotEvaluateZero,
585    #[error("failed to read from memory")]
586    FailedMemoryRead,
587    #[error("failed to decode instruction")]
588    FailedDecodeInstruction,
589    #[error("failed to read from the wiring bus")]
590    FailedWireBusRead,
591    #[error("num of wires must be less than 2^30 but was {0}")]
592    TooManyWires(u64),
593}
594
595// EXT2INTT ERROR
596// ================================================================================================
597
598#[derive(Debug, thiserror::Error)]
599pub enum Ext2InttError {
600    #[error("input domain size must be a power of two, but was {0}")]
601    DomainSizeNotPowerOf2(u64),
602    #[error("input domain size ({0} elements) is too small")]
603    DomainSizeTooSmall(u64),
604    #[error("address of the last input must be smaller than 2^32, but was {0}")]
605    InputEndAddressTooBig(u64),
606    #[error("input size must be smaller than 2^32, but was {0}")]
607    InputSizeTooBig(u64),
608    #[error("address of the first input must be smaller than 2^32, but was {0}")]
609    InputStartAddressTooBig(u64),
610    #[error("address of the first input is not word aligned: {0}")]
611    InputStartNotWordAligned(u64),
612    #[error("output size ({0}) cannot be greater than the input size ({1})")]
613    OutputSizeTooBig(usize, usize),
614    #[error("output size must be greater than 0")]
615    OutputSizeIsZero,
616    #[error("uninitialized memory at address {0}")]
617    UninitializedMemoryAddress(u32),
618}
619
620// ERROR CONTEXT
621// ===============================================================================================
622
623/// Context information to be used when reporting errors.
624#[derive(Debug)]
625pub struct ErrorContext<'a, N: MastNodeExt>(Option<ErrorContextImpl<'a, N>>);
626
627impl<'a, N: MastNodeExt> ErrorContext<'a, N> {
628    /// Creates a new error context for the specified node and source manager.
629    ///
630    /// This method should be used for all nodes except basic block nodes.
631    pub fn new(
632        mast_forest: &'a MastForest,
633        node: &'a N,
634        source_manager: Arc<dyn SourceManager>,
635    ) -> Self {
636        Self(Some(ErrorContextImpl::new(mast_forest, node, source_manager)))
637    }
638
639    /// Creates a new error context for the specified node and source manager.
640    ///
641    /// This method should be used for basic block nodes.
642    pub fn new_with_op_idx(
643        mast_forest: &'a MastForest,
644        node: &'a N,
645        source_manager: Arc<dyn SourceManager>,
646        op_idx: usize,
647    ) -> Self {
648        Self(Some(ErrorContextImpl::new_with_op_idx(
649            mast_forest,
650            node,
651            source_manager,
652            op_idx,
653        )))
654    }
655
656    /// Creates a new empty error context.
657    ///
658    /// This error context will not provide any information about the source of the error.
659    pub fn none() -> Self {
660        Self(None)
661    }
662
663    /// Returns the label and source file associated with the error context, if any.
664    ///
665    /// Note that `SourceSpan::UNKNOWN` will be returned to indicate an empty span.
666    pub fn label_and_source_file(&self) -> (SourceSpan, Option<Arc<SourceFile>>) {
667        self.0
668            .as_ref()
669            .map_or((SourceSpan::UNKNOWN, None), |ctx| ctx.label_and_source_file())
670    }
671}
672
673impl Default for ErrorContext<'_, BasicBlockNode> {
674    fn default() -> Self {
675        Self::none()
676    }
677}
678
679#[derive(Debug)]
680struct ErrorContextImpl<'a, N: MastNodeExt> {
681    mast_forest: &'a MastForest,
682    node: &'a N,
683    source_manager: Arc<dyn SourceManager>,
684    op_idx: Option<usize>,
685}
686
687impl<'a, N: MastNodeExt> ErrorContextImpl<'a, N> {
688    pub fn new(
689        mast_forest: &'a MastForest,
690        node: &'a N,
691        source_manager: Arc<dyn SourceManager>,
692    ) -> Self {
693        Self {
694            mast_forest,
695            node,
696            source_manager,
697            op_idx: None,
698        }
699    }
700
701    pub fn new_with_op_idx(
702        mast_forest: &'a MastForest,
703        node: &'a N,
704        source_manager: Arc<dyn SourceManager>,
705        op_idx: usize,
706    ) -> Self {
707        Self {
708            mast_forest,
709            node,
710            source_manager,
711            op_idx: Some(op_idx),
712        }
713    }
714
715    pub fn label_and_source_file(&self) -> (SourceSpan, Option<Arc<SourceFile>>) {
716        self.node
717            .get_assembly_op(self.mast_forest, self.op_idx)
718            .and_then(|assembly_op| assembly_op.location())
719            .map_or_else(
720                || (SourceSpan::UNKNOWN, None),
721                |location| {
722                    (
723                        self.source_manager.location_to_span(location.clone()).unwrap_or_default(),
724                        self.source_manager.get_by_path(&location.path),
725                    )
726                },
727            )
728    }
729}
730
731// TESTS
732// ================================================================================================
733
734#[cfg(test)]
735mod error_assertions {
736    use super::*;
737
738    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
739    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
740
741    fn _assert_execution_error_bounds(err: ExecutionError) {
742        _assert_error_is_send_sync_static(err);
743    }
744}