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("stack should have at most {MIN_STACK_DEPTH} elements at the end of program execution, but had {} elements", MIN_STACK_DEPTH + .0)]
281    OutputStackOverflow(usize),
282    #[error("a program has already been executed in this process")]
283    ProgramAlreadyExecuted,
284    #[error("proof generation failed")]
285    ProverError(#[source] ProverError),
286    #[error("smt node {node_hex} not found", node_hex = to_hex(Felt::elements_as_bytes(node)))]
287    SmtNodeNotFound {
288        #[label]
289        label: SourceSpan,
290        #[source_code]
291        source_file: Option<Arc<SourceFile>>,
292        node: Word,
293    },
294    #[error("expected pre-image length of node {node_hex} to be a multiple of 8 but was {preimage_len}",
295      node_hex = to_hex(Felt::elements_as_bytes(node)),
296    )]
297    SmtNodePreImageNotValid {
298        #[label]
299        label: SourceSpan,
300        #[source_code]
301        source_file: Option<Arc<SourceFile>>,
302        node: Word,
303        preimage_len: usize,
304    },
305    #[error("syscall failed: procedure with root {hex} was not found in the kernel",
306      hex = to_hex(proc_root.as_bytes())
307    )]
308    SyscallTargetNotInKernel {
309        #[label]
310        label: SourceSpan,
311        #[source_code]
312        source_file: Option<Arc<SourceFile>>,
313        proc_root: Digest,
314    },
315    #[error("failed to execute arithmetic circuit evaluation operation: {error}")]
316    #[diagnostic()]
317    AceChipError {
318        #[label("this call failed")]
319        label: SourceSpan,
320        #[source_code]
321        source_file: Option<Arc<SourceFile>>,
322        error: AceError,
323    },
324}
325
326impl From<Ext2InttError> for ExecutionError {
327    fn from(value: Ext2InttError) -> Self {
328        Self::Ext2InttError(value)
329    }
330}
331
332impl ExecutionError {
333    pub fn advice_map_key_not_found(
334        key: Word,
335        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
336    ) -> Self {
337        let (label, source_file) = err_ctx.label_and_source_file();
338        Self::AdviceMapKeyNotFound { label, source_file, key }
339    }
340
341    pub fn advice_stack_read_failed(
342        row: RowIndex,
343        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
344    ) -> Self {
345        let (label, source_file) = err_ctx.label_and_source_file();
346        Self::AdviceStackReadFailed { label, source_file, row }
347    }
348
349    pub fn divide_by_zero(clk: RowIndex, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
350        let (label, source_file) = err_ctx.label_and_source_file();
351        Self::DivideByZero { clk, label, source_file }
352    }
353
354    pub fn dynamic_node_not_found(
355        digest: Digest,
356        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
357    ) -> Self {
358        let (label, source_file) = err_ctx.label_and_source_file();
359
360        Self::DynamicNodeNotFound { label, source_file, digest }
361    }
362
363    pub fn event_error(
364        error: Box<dyn Error + Send + Sync + 'static>,
365        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
366    ) -> Self {
367        let (label, source_file) = err_ctx.label_and_source_file();
368
369        Self::EventError { label, source_file, error }
370    }
371
372    pub fn failed_assertion(
373        clk: RowIndex,
374        err_code: Felt,
375        err_msg: Option<Arc<str>>,
376        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
377    ) -> Self {
378        let (label, source_file) = err_ctx.label_and_source_file();
379
380        Self::FailedAssertion {
381            label,
382            source_file,
383            clk,
384            err_code,
385            err_msg,
386        }
387    }
388
389    pub fn invalid_merkle_tree_depth(
390        depth: Felt,
391        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
392    ) -> Self {
393        let (label, source_file) = err_ctx.label_and_source_file();
394        Self::InvalidMerkleTreeDepth { label, source_file, depth }
395    }
396
397    pub fn invalid_merkle_tree_node_index(
398        depth: Felt,
399        index: Felt,
400        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
401    ) -> Self {
402        let (label, source_file) = err_ctx.label_and_source_file();
403        Self::InvalidMerkleTreeNodeIndex { label, source_file, depth, index }
404    }
405
406    pub fn invalid_stack_depth_on_return(
407        depth: usize,
408        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
409    ) -> Self {
410        let (label, source_file) = err_ctx.label_and_source_file();
411        Self::InvalidStackDepthOnReturn { label, source_file, depth }
412    }
413
414    pub fn log_argument_zero(clk: RowIndex, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
415        let (label, source_file) = err_ctx.label_and_source_file();
416        Self::LogArgumentZero { label, source_file, clk }
417    }
418
419    pub fn malfored_mast_forest_in_host(
420        root_digest: Digest,
421        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
422    ) -> Self {
423        let (label, source_file) = err_ctx.label_and_source_file();
424        Self::MalformedMastForestInHost { label, source_file, root_digest }
425    }
426
427    pub fn malformed_signature_key(
428        key_type: &'static str,
429        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
430    ) -> Self {
431        let (label, source_file) = err_ctx.label_and_source_file();
432        Self::MalformedSignatureKey { label, source_file, key_type }
433    }
434
435    pub fn merkle_path_verification_failed(
436        value: Word,
437        index: Felt,
438        root: Digest,
439        err_code: Felt,
440        err_msg: Option<Arc<str>>,
441        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
442    ) -> Self {
443        let (label, source_file) = err_ctx.label_and_source_file();
444
445        Self::MerklePathVerificationFailed {
446            label,
447            source_file,
448            value,
449            index,
450            root,
451            err_code,
452            err_msg,
453        }
454    }
455
456    pub fn merkle_store_lookup_failed(
457        err: MerkleError,
458        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
459    ) -> Self {
460        let (label, source_file) = err_ctx.label_and_source_file();
461        Self::MerkleStoreLookupFailed { label, source_file, err }
462    }
463
464    /// Note: This error currently never occurs, since `MerkleStore::merge_roots()` never fails.
465    pub fn merkle_store_merge_failed(
466        err: MerkleError,
467        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
468    ) -> Self {
469        let (label, source_file) = err_ctx.label_and_source_file();
470        Self::MerkleStoreMergeFailed { label, source_file, err }
471    }
472
473    pub fn merkle_store_update_failed(
474        err: MerkleError,
475        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
476    ) -> Self {
477        let (label, source_file) = err_ctx.label_and_source_file();
478        Self::MerkleStoreUpdateFailed { label, source_file, err }
479    }
480
481    pub fn no_mast_forest_with_procedure(
482        root_digest: Digest,
483        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
484    ) -> Self {
485        let (label, source_file) = err_ctx.label_and_source_file();
486        Self::NoMastForestWithProcedure { label, source_file, root_digest }
487    }
488
489    pub fn not_binary_value_if(value: Felt, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
490        let (label, source_file) = err_ctx.label_and_source_file();
491        Self::NotBinaryValueIf { label, source_file, value }
492    }
493
494    pub fn not_binary_value_op(value: Felt, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
495        let (label, source_file) = err_ctx.label_and_source_file();
496        Self::NotBinaryValueOp { label, source_file, value }
497    }
498
499    pub fn not_binary_value_loop(
500        value: Felt,
501        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
502    ) -> Self {
503        let (label, source_file) = err_ctx.label_and_source_file();
504        Self::NotBinaryValueLoop { label, source_file, value }
505    }
506
507    pub fn not_u32_value(
508        value: Felt,
509        err_code: Felt,
510        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
511    ) -> Self {
512        let (label, source_file) = err_ctx.label_and_source_file();
513        Self::NotU32Value { label, source_file, value, err_code }
514    }
515
516    pub fn smt_node_not_found(node: Word, err_ctx: &ErrorContext<'_, impl MastNodeExt>) -> Self {
517        let (label, source_file) = err_ctx.label_and_source_file();
518        Self::SmtNodeNotFound { label, source_file, node }
519    }
520
521    pub fn smt_node_preimage_not_valid(
522        node: Word,
523        preimage_len: usize,
524        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
525    ) -> Self {
526        let (label, source_file) = err_ctx.label_and_source_file();
527        Self::SmtNodePreImageNotValid { label, source_file, node, preimage_len }
528    }
529
530    pub fn syscall_target_not_in_kernel(
531        proc_root: Digest,
532        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
533    ) -> Self {
534        let (label, source_file) = err_ctx.label_and_source_file();
535        Self::SyscallTargetNotInKernel { label, source_file, proc_root }
536    }
537
538    pub fn failed_arithmetic_evaluation(
539        err_ctx: &ErrorContext<'_, impl MastNodeExt>,
540        error: AceError,
541    ) -> Self {
542        let (label, source_file) = err_ctx.label_and_source_file();
543        Self::AceChipError { label, source_file, error }
544    }
545}
546
547impl AsRef<dyn Diagnostic> for ExecutionError {
548    fn as_ref(&self) -> &(dyn Diagnostic + 'static) {
549        self
550    }
551}
552
553// ACE ERROR
554// ================================================================================================
555
556#[derive(Debug, thiserror::Error)]
557pub enum AceError {
558    #[error("num of variables should be word aligned and non-zero but was {0}")]
559    NumVarIsNotWordAlignedOrIsEmpty(u64),
560    #[error("num of evaluation gates should be word aligned and non-zero but was {0}")]
561    NumEvalIsNotWordAlignedOrIsEmpty(u64),
562    #[error("circuit does not evaluate to zero")]
563    CircuitNotEvaluateZero,
564    #[error("failed to read from memory")]
565    FailedMemoryRead,
566    #[error("failed to decode instruction")]
567    FailedDecodeInstruction,
568    #[error("failed to read from the wiring bus")]
569    FailedWireBusRead,
570}
571
572// EXT2INTT ERROR
573// ================================================================================================
574
575#[derive(Debug, thiserror::Error)]
576pub enum Ext2InttError {
577    #[error("input domain size must be a power of two, but was {0}")]
578    DomainSizeNotPowerOf2(u64),
579    #[error("input domain size ({0} elements) is too small")]
580    DomainSizeTooSmall(u64),
581    #[error("address of the last input must be smaller than 2^32, but was {0}")]
582    InputEndAddressTooBig(u64),
583    #[error("input size must be smaller than 2^32, but was {0}")]
584    InputSizeTooBig(u64),
585    #[error("address of the first input must be smaller than 2^32, but was {0}")]
586    InputStartAddressTooBig(u64),
587    #[error("address of the first input is not word aligned: {0}")]
588    InputStartNotWordAligned(u64),
589    #[error("output size ({0}) cannot be greater than the input size ({1})")]
590    OutputSizeTooBig(usize, usize),
591    #[error("output size must be greater than 0")]
592    OutputSizeIsZero,
593    #[error("uninitialized memory at address {0}")]
594    UninitializedMemoryAddress(u32),
595}
596
597// ERROR CONTEXT
598// ===============================================================================================
599
600/// Context information to be used when reporting errors.
601#[derive(Debug)]
602pub struct ErrorContext<'a, N: MastNodeExt>(Option<ErrorContextImpl<'a, N>>);
603
604impl<'a, N: MastNodeExt> ErrorContext<'a, N> {
605    /// Creates a new error context for the specified node and source manager.
606    ///
607    /// This method should be used for all nodes except basic block nodes.
608    pub fn new(
609        mast_forest: &'a MastForest,
610        node: &'a N,
611        source_manager: Arc<dyn SourceManager>,
612    ) -> Self {
613        Self(Some(ErrorContextImpl::new(mast_forest, node, source_manager)))
614    }
615
616    /// Creates a new error context for the specified node and source manager.
617    ///
618    /// This method should be used for basic block nodes.
619    pub fn new_with_op_idx(
620        mast_forest: &'a MastForest,
621        node: &'a N,
622        source_manager: Arc<dyn SourceManager>,
623        op_idx: usize,
624    ) -> Self {
625        Self(Some(ErrorContextImpl::new_with_op_idx(
626            mast_forest,
627            node,
628            source_manager,
629            op_idx,
630        )))
631    }
632
633    /// Creates a new empty error context.
634    ///
635    /// This error context will not provide any information about the source of the error.
636    pub fn none() -> Self {
637        Self(None)
638    }
639
640    /// Returns the label and source file associated with the error context, if any.
641    ///
642    /// Note that `SourceSpan::UNKNOWN` will be returned to indicate an empty span.
643    pub fn label_and_source_file(&self) -> (SourceSpan, Option<Arc<SourceFile>>) {
644        self.0
645            .as_ref()
646            .map_or((SourceSpan::UNKNOWN, None), |ctx| ctx.label_and_source_file())
647    }
648}
649
650impl Default for ErrorContext<'_, BasicBlockNode> {
651    fn default() -> Self {
652        Self::none()
653    }
654}
655
656#[derive(Debug)]
657struct ErrorContextImpl<'a, N: MastNodeExt> {
658    mast_forest: &'a MastForest,
659    node: &'a N,
660    source_manager: Arc<dyn SourceManager>,
661    op_idx: Option<usize>,
662}
663
664impl<'a, N: MastNodeExt> ErrorContextImpl<'a, N> {
665    pub fn new(
666        mast_forest: &'a MastForest,
667        node: &'a N,
668        source_manager: Arc<dyn SourceManager>,
669    ) -> Self {
670        Self {
671            mast_forest,
672            node,
673            source_manager,
674            op_idx: None,
675        }
676    }
677
678    pub fn new_with_op_idx(
679        mast_forest: &'a MastForest,
680        node: &'a N,
681        source_manager: Arc<dyn SourceManager>,
682        op_idx: usize,
683    ) -> Self {
684        Self {
685            mast_forest,
686            node,
687            source_manager,
688            op_idx: Some(op_idx),
689        }
690    }
691
692    pub fn label_and_source_file(&self) -> (SourceSpan, Option<Arc<SourceFile>>) {
693        self.node
694            .get_assembly_op(self.mast_forest, self.op_idx)
695            .and_then(|assembly_op| assembly_op.location())
696            .map_or_else(
697                || (SourceSpan::UNKNOWN, None),
698                |location| {
699                    (
700                        self.source_manager.location_to_span(location.clone()).unwrap_or_default(),
701                        self.source_manager.get_by_path(&location.path),
702                    )
703                },
704            )
705    }
706}
707
708// TESTS
709// ================================================================================================
710
711#[cfg(test)]
712mod error_assertions {
713    use super::*;
714
715    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
716    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
717
718    fn _assert_execution_error_bounds(err: ExecutionError) {
719        _assert_error_is_send_sync_static(err);
720    }
721}