Skip to main content

miden_processor/
errors.rs

1// Allow unused assignments - required by miette::Diagnostic derive macro
2#![allow(unused_assignments)]
3
4use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
5
6use miden_core::program::MIN_STACK_DEPTH;
7use miden_debug_types::{SourceFile, SourceSpan};
8use miden_utils_diagnostics::{Diagnostic, miette};
9
10use crate::{
11    ContextId, DebugError, Felt, Host, TraceError, Word,
12    advice::AdviceError,
13    event::{EventError, EventId, EventName},
14    fast::SystemEventError,
15    field::QuadFelt,
16    mast::{MastForest, MastNodeId},
17    utils::to_hex,
18};
19
20// EXECUTION ERROR
21// ================================================================================================
22
23#[derive(Debug, thiserror::Error, Diagnostic)]
24pub enum ExecutionError {
25    #[error("failed to execute arithmetic circuit evaluation operation: {error}")]
26    #[diagnostic()]
27    AceChipError {
28        #[label("this call failed")]
29        label: SourceSpan,
30        #[source_code]
31        source_file: Option<Arc<SourceFile>>,
32        error: AceError,
33    },
34    #[error("{err}")]
35    #[diagnostic(forward(err))]
36    AdviceError {
37        #[label]
38        label: SourceSpan,
39        #[source_code]
40        source_file: Option<Arc<SourceFile>>,
41        err: AdviceError,
42    },
43    #[error("exceeded the allowed number of max cycles {0}")]
44    CycleLimitExceeded(u32),
45    #[error("debug handler error: {err}")]
46    DebugHandlerError {
47        #[source]
48        err: DebugError,
49    },
50    #[error("attempted to add event handler for '{event}' (already registered)")]
51    DuplicateEventHandler { event: EventName },
52    #[error("error during processing of event {}", match event_name {
53        Some(name) => format!("'{}' (ID: {})", name, event_id),
54        None => format!("with ID: {}", event_id),
55    })]
56    #[diagnostic()]
57    EventError {
58        #[label]
59        label: SourceSpan,
60        #[source_code]
61        source_file: Option<Arc<SourceFile>>,
62        event_id: EventId,
63        event_name: Option<EventName>,
64        #[source]
65        error: EventError,
66    },
67    #[error("failed to execute the program for internal reason: {0}")]
68    Internal(&'static str),
69    /// Memory error with source context for diagnostics.
70    ///
71    /// Use `MemoryResultExt::map_mem_err` to convert `Result<T, MemoryError>` with context.
72    #[error("{err}")]
73    #[diagnostic(forward(err))]
74    MemoryError {
75        #[label]
76        label: SourceSpan,
77        #[source_code]
78        source_file: Option<Arc<SourceFile>>,
79        err: MemoryError,
80    },
81    /// Memory error without source context (for internal operations like FMP initialization).
82    ///
83    /// Use `ExecutionError::MemoryErrorNoCtx` for memory errors that don't have error context
84    /// available (e.g., during call/syscall context initialization).
85    #[error(transparent)]
86    #[diagnostic(transparent)]
87    MemoryErrorNoCtx(MemoryError),
88    #[error("{err}")]
89    #[diagnostic(forward(err))]
90    OperationError {
91        #[label]
92        label: SourceSpan,
93        #[source_code]
94        source_file: Option<Arc<SourceFile>>,
95        err: OperationError,
96    },
97    #[error("stack should have at most {MIN_STACK_DEPTH} elements at the end of program execution, but had {} elements", MIN_STACK_DEPTH + .0)]
98    OutputStackOverflow(usize),
99    #[error("failed to serialize proof: {0}")]
100    ProofSerializationError(String),
101    #[error("attempted to add event handler for '{event}' (reserved system event)")]
102    ReservedEventNamespace { event: EventName },
103    #[error("trace handler error for trace ID {trace_id}: {err}")]
104    TraceHandlerError {
105        trace_id: u32,
106        #[source]
107        err: TraceError,
108    },
109}
110
111impl AsRef<dyn Diagnostic> for ExecutionError {
112    fn as_ref(&self) -> &(dyn Diagnostic + 'static) {
113        self
114    }
115}
116
117// ACE ERROR
118// ================================================================================================
119
120#[derive(Debug, thiserror::Error)]
121pub enum AceError {
122    #[error("num of variables should be word aligned and non-zero but was {0}")]
123    NumVarIsNotWordAlignedOrIsEmpty(u64),
124    #[error("num of evaluation gates should be word aligned and non-zero but was {0}")]
125    NumEvalIsNotWordAlignedOrIsEmpty(u64),
126    #[error("circuit does not evaluate to zero")]
127    CircuitNotEvaluateZero,
128    #[error("failed to read from memory")]
129    FailedMemoryRead,
130    #[error("failed to decode instruction")]
131    FailedDecodeInstruction,
132    #[error("failed to read from the wiring bus")]
133    FailedWireBusRead,
134    #[error("num of wires must be less than 2^30 but was {0}")]
135    TooManyWires(u64),
136}
137
138// ACE EVAL ERROR
139// ================================================================================================
140
141/// Context-free error type for ACE circuit evaluation operations.
142///
143/// This enum wraps errors from ACE evaluation and memory subsystems without
144/// carrying source location context. Context is added at the call site via
145/// `AceEvalResultExt::map_ace_eval_err`.
146#[derive(Debug, thiserror::Error)]
147pub enum AceEvalError {
148    #[error(transparent)]
149    Ace(#[from] AceError),
150    #[error(transparent)]
151    Memory(#[from] MemoryError),
152}
153
154// IO ERROR
155// ================================================================================================
156
157/// Context-free error type for IO operations.
158///
159/// This enum wraps errors from the advice provider and memory subsystems without
160/// carrying source location context. Context is added at the call site via
161/// `IoResultExt::map_io_err`.
162#[derive(Debug, thiserror::Error, Diagnostic)]
163pub enum IoError {
164    #[error(transparent)]
165    Advice(#[from] AdviceError),
166    #[error(transparent)]
167    Memory(#[from] MemoryError),
168    /// Stack operation error (increment/decrement size failures).
169    ///
170    /// These are internal execution errors that don't need additional context
171    /// since they already carry their own error information.
172    #[error(transparent)]
173    #[diagnostic(transparent)]
174    Execution(Box<ExecutionError>),
175}
176
177impl From<ExecutionError> for IoError {
178    fn from(err: ExecutionError) -> Self {
179        IoError::Execution(Box::new(err))
180    }
181}
182
183// MEMORY ERROR
184// ================================================================================================
185
186/// Lightweight error type for memory operations.
187///
188/// This enum captures error conditions without expensive context information (no source location,
189/// no file references). When a `MemoryError` propagates up to become an `ExecutionError`, the
190/// context is resolved lazily via `MapExecErr::map_exec_err`.
191#[derive(Debug, thiserror::Error, Diagnostic)]
192pub enum MemoryError {
193    #[error("memory address cannot exceed 2^32 but was {addr}")]
194    AddressOutOfBounds { addr: u64 },
195    #[error(
196        "memory address {addr} in context {ctx} was read and written, or written twice, in the same clock cycle {clk}"
197    )]
198    IllegalMemoryAccess { ctx: ContextId, addr: u32, clk: Felt },
199    #[error(
200        "memory range start address cannot exceed end address, but was ({start_addr}, {end_addr})"
201    )]
202    InvalidMemoryRange { start_addr: u64, end_addr: u64 },
203    #[error("word access at memory address {addr} in context {ctx} is unaligned")]
204    #[diagnostic(help(
205        "ensure that the memory address accessed is aligned to a word boundary (it is a multiple of 4)"
206    ))]
207    UnalignedWordAccess { addr: u32, ctx: ContextId },
208}
209
210// CRYPTO ERROR
211// ================================================================================================
212
213/// Context-free error type for cryptographic operations (Merkle path verification, updates).
214///
215/// This enum wraps errors from the advice provider and operation subsystems without carrying
216/// source location context. Context is added at the call site via
217/// `CryptoResultExt::map_crypto_err`.
218#[derive(Debug, thiserror::Error, Diagnostic)]
219pub enum CryptoError {
220    #[error(transparent)]
221    Advice(#[from] AdviceError),
222    #[error(transparent)]
223    #[diagnostic(transparent)]
224    Operation(#[from] OperationError),
225}
226
227// OPERATION ERROR
228// ================================================================================================
229
230/// Lightweight error type for operations that can fail.
231///
232/// This enum captures error conditions without expensive context information (no source location,
233/// no file references). When an `OperationError` propagates up to become an `ExecutionError`, the
234/// context is resolved lazily via extension traits like `OperationResultExt::map_exec_err`.
235///
236/// # Adding new errors (for contributors)
237///
238/// **Use `OperationError` when:**
239/// - The error occurs during operation execution (e.g., assertion failures, type mismatches)
240/// - Context can be resolved at the call site via the extension traits
241/// - The error needs both a human-readable message and optional diagnostic help
242///
243/// **Avoid duplicating error context.** Context is added by the extension traits,
244/// so do NOT add `label` or `source_file` fields to the variant.
245///
246/// **Pattern at call sites:**
247/// ```ignore
248/// // Return OperationError and let the caller wrap it:
249/// fn some_op() -> Result<(), OperationError> {
250///     Err(OperationError::DivideByZero)
251/// }
252///
253/// // Caller wraps with context lazily:
254/// some_op().map_exec_err(mast_forest, node_id, host)?;
255/// ```
256///
257/// For wrapper errors (`AdviceError`, `EventError`, `AceError`), use the corresponding extension
258/// traits (`AdviceResultExt`, `AceResultExt`) or helper functions (`advice_error_with_context`,
259/// `event_error_with_context`).
260#[derive(Debug, Clone, thiserror::Error, Diagnostic)]
261pub enum OperationError {
262    #[error("external node with mast root {0} resolved to an external node")]
263    CircularExternalNode(Word),
264    #[error("division by zero")]
265    #[diagnostic(help(
266        "ensure the divisor (second stack element) is non-zero before division or modulo operations"
267    ))]
268    DivideByZero,
269    #[error("failed to execute dynamic code block; block with root {digest} could not be found")]
270    DynamicNodeNotFound { digest: Word },
271    #[error(
272        "assertion failed with error {}",
273        match err_msg {
274            Some(msg) => format!("message: {msg}"),
275            None => format!("code: {err_code}"),
276        }
277    )]
278    #[diagnostic(help(
279        "assertions validate program invariants. Review the assertion condition and ensure all prerequisites are met"
280    ))]
281    FailedAssertion {
282        err_code: Felt,
283        err_msg: Option<Arc<str>>,
284    },
285    #[error("FRI domain size was 0")]
286    InvalidFriDomainGenerator,
287    #[error("FRI domain segment value cannot exceed 3, but was {0}")]
288    InvalidFriDomainSegment(u64),
289    #[error("degree-respecting projection is inconsistent: expected {0} but was {1}")]
290    InvalidFriLayerFolding(QuadFelt, QuadFelt),
291    #[error(
292        "invalid crypto operation: Merkle path length {path_len} does not match expected depth {depth}"
293    )]
294    InvalidMerklePathLength { path_len: usize, depth: Felt },
295    #[error("when returning from a call, stack depth must be {MIN_STACK_DEPTH}, but was {depth}")]
296    InvalidStackDepthOnReturn { depth: usize },
297    #[error("attempted to calculate integer logarithm with zero argument")]
298    #[diagnostic(help("ilog2 requires a non-zero argument"))]
299    LogArgumentZero,
300    #[error(
301        "MAST forest in host indexed by procedure root {root_digest} doesn't contain that root"
302    )]
303    MalformedMastForestInHost { root_digest: Word },
304    #[error("merkle path verification failed for value {value} at index {index} in the Merkle tree with root {root} (error {err})",
305      value = to_hex(inner.value.as_bytes()),
306      root = to_hex(inner.root.as_bytes()),
307      index = inner.index,
308      err = match &inner.err_msg {
309        Some(msg) => format!("message: {msg}"),
310        None => format!("code: {}", inner.err_code),
311      }
312    )]
313    MerklePathVerificationFailed {
314        inner: Box<MerklePathVerificationFailedInner>,
315    },
316    #[error("no MAST forest contains the procedure with root digest {root_digest}")]
317    NoMastForestWithProcedure { root_digest: Word },
318    #[error("operation expected a binary value, but got {value}")]
319    NotBinaryValue { value: Felt },
320    #[error("if statement expected a binary value on top of the stack, but got {value}")]
321    NotBinaryValueIf { value: Felt },
322    #[error("loop condition must be a binary value, but got {value}")]
323    #[diagnostic(help(
324        "this could happen either when first entering the loop, or any subsequent iteration"
325    ))]
326    NotBinaryValueLoop { value: Felt },
327    #[error("operation expected u32 values, but got values: {values:?}")]
328    NotU32Values { values: Vec<Felt> },
329    #[error("syscall failed: procedure with root {proc_root} was not found in the kernel")]
330    SyscallTargetNotInKernel { proc_root: Word },
331}
332
333impl OperationError {
334    /// Wraps this error with execution context to produce an `ExecutionError`.
335    ///
336    /// This is useful when working with `ControlFlow` or other non-`Result` return types
337    /// where the `OperationResultExt::map_exec_err` extension trait cannot be used directly.
338    pub fn with_context(
339        self,
340        mast_forest: &MastForest,
341        node_id: MastNodeId,
342        host: &impl Host,
343    ) -> ExecutionError {
344        let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
345        ExecutionError::OperationError { label, source_file, err: self }
346    }
347}
348
349/// Inner data for `OperationError::MerklePathVerificationFailed`.
350///
351/// Boxed to reduce the size of `OperationError`.
352#[derive(Debug, Clone)]
353pub struct MerklePathVerificationFailedInner {
354    pub value: Word,
355    pub index: Felt,
356    pub root: Word,
357    pub err_code: Felt,
358    pub err_msg: Option<Arc<str>>,
359}
360
361// EXTENSION TRAITS
362// ================================================================================================
363
364/// Computes the label and source file for error context.
365///
366/// This function is called by the extension traits to compute source location
367/// only when an error occurs. Since errors are rare, the cost of decorator
368/// traversal is acceptable.
369fn get_label_and_source_file(
370    op_idx: Option<usize>,
371    mast_forest: &MastForest,
372    node_id: MastNodeId,
373    host: &impl Host,
374) -> (SourceSpan, Option<Arc<SourceFile>>) {
375    mast_forest
376        .get_assembly_op(node_id, op_idx)
377        .and_then(|assembly_op| assembly_op.location())
378        .map_or_else(
379            || (SourceSpan::UNKNOWN, None),
380            |location| host.get_label_and_source_file(location),
381        )
382}
383
384/// Wraps an `AdviceError` with execution context to produce an `ExecutionError`.
385///
386/// This is useful when working with `ControlFlow` or other non-`Result` return types
387/// where the extension traits cannot be used directly.
388pub fn advice_error_with_context(
389    err: AdviceError,
390    mast_forest: &MastForest,
391    node_id: MastNodeId,
392    host: &impl Host,
393) -> ExecutionError {
394    let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
395    ExecutionError::AdviceError { label, source_file, err }
396}
397
398/// Wraps an `EventError` with execution context to produce an `ExecutionError`.
399///
400/// This is useful when working with `ControlFlow` or other non-`Result` return types
401/// where an extension trait on `Result` cannot be used directly.
402pub fn event_error_with_context(
403    error: EventError,
404    mast_forest: &MastForest,
405    node_id: MastNodeId,
406    host: &impl Host,
407    event_id: EventId,
408    event_name: Option<EventName>,
409) -> ExecutionError {
410    let (label, source_file) = get_label_and_source_file(None, mast_forest, node_id, host);
411    ExecutionError::EventError {
412        label,
413        source_file,
414        event_id,
415        event_name,
416        error,
417    }
418}
419
420// CONSOLIDATED EXTENSION TRAITS (plafer's approach)
421// ================================================================================================
422//
423// Three traits organized by method signature rather than by error type:
424// 1. MapExecErr - for errors with basic context (forest, node_id, host)
425// 2. MapExecErrWithOpIdx - for errors in basic blocks that need op_idx
426// 3. MapExecErrNoCtx - for errors without any context
427
428/// Extension trait for mapping errors to `ExecutionError` with basic context.
429///
430/// Implement this for error types that can be converted to `ExecutionError` using
431/// just the MAST forest, node ID, and host for source location lookup.
432pub trait MapExecErr<T> {
433    fn map_exec_err(
434        self,
435        mast_forest: &MastForest,
436        node_id: MastNodeId,
437        host: &impl Host,
438    ) -> Result<T, ExecutionError>;
439}
440
441/// Extension trait for mapping errors to `ExecutionError` with op index context.
442///
443/// Implement this for error types that occur within basic blocks where the
444/// operation index is available for more precise source location.
445pub trait MapExecErrWithOpIdx<T> {
446    fn map_exec_err_with_op_idx(
447        self,
448        mast_forest: &MastForest,
449        node_id: MastNodeId,
450        host: &impl Host,
451        op_idx: usize,
452    ) -> Result<T, ExecutionError>;
453}
454
455/// Extension trait for mapping errors to `ExecutionError` without context.
456///
457/// Implement this for error types that may need to be converted when no
458/// error context is available (e.g., during initialization).
459pub trait MapExecErrNoCtx<T> {
460    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError>;
461}
462
463// OperationError implementations
464impl<T> MapExecErr<T> for Result<T, OperationError> {
465    #[inline(always)]
466    fn map_exec_err(
467        self,
468        mast_forest: &MastForest,
469        node_id: MastNodeId,
470        host: &impl Host,
471    ) -> Result<T, ExecutionError> {
472        match self {
473            Ok(v) => Ok(v),
474            Err(err) => {
475                let (label, source_file) =
476                    get_label_and_source_file(None, mast_forest, node_id, host);
477                Err(ExecutionError::OperationError { label, source_file, err })
478            },
479        }
480    }
481}
482
483impl<T> MapExecErrWithOpIdx<T> for Result<T, OperationError> {
484    #[inline(always)]
485    fn map_exec_err_with_op_idx(
486        self,
487        mast_forest: &MastForest,
488        node_id: MastNodeId,
489        host: &impl Host,
490        op_idx: usize,
491    ) -> Result<T, ExecutionError> {
492        match self {
493            Ok(v) => Ok(v),
494            Err(err) => {
495                let (label, source_file) =
496                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
497                Err(ExecutionError::OperationError { label, source_file, err })
498            },
499        }
500    }
501}
502
503// AdviceError implementations
504impl<T> MapExecErr<T> for Result<T, AdviceError> {
505    #[inline(always)]
506    fn map_exec_err(
507        self,
508        mast_forest: &MastForest,
509        node_id: MastNodeId,
510        host: &impl Host,
511    ) -> Result<T, ExecutionError> {
512        match self {
513            Ok(v) => Ok(v),
514            Err(err) => Err(advice_error_with_context(err, mast_forest, node_id, host)),
515        }
516    }
517}
518
519impl<T> MapExecErrNoCtx<T> for Result<T, AdviceError> {
520    #[inline(always)]
521    fn map_exec_err_no_ctx(self) -> Result<T, ExecutionError> {
522        match self {
523            Ok(v) => Ok(v),
524            Err(err) => Err(ExecutionError::AdviceError {
525                label: SourceSpan::UNKNOWN,
526                source_file: None,
527                err,
528            }),
529        }
530    }
531}
532
533// MemoryError implementations
534impl<T> MapExecErr<T> for Result<T, MemoryError> {
535    #[inline(always)]
536    fn map_exec_err(
537        self,
538        mast_forest: &MastForest,
539        node_id: MastNodeId,
540        host: &impl Host,
541    ) -> Result<T, ExecutionError> {
542        match self {
543            Ok(v) => Ok(v),
544            Err(err) => {
545                let (label, source_file) =
546                    get_label_and_source_file(None, mast_forest, node_id, host);
547                Err(ExecutionError::MemoryError { label, source_file, err })
548            },
549        }
550    }
551}
552
553impl<T> MapExecErrWithOpIdx<T> for Result<T, MemoryError> {
554    #[inline(always)]
555    fn map_exec_err_with_op_idx(
556        self,
557        mast_forest: &MastForest,
558        node_id: MastNodeId,
559        host: &impl Host,
560        op_idx: usize,
561    ) -> Result<T, ExecutionError> {
562        match self {
563            Ok(v) => Ok(v),
564            Err(err) => {
565                let (label, source_file) =
566                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
567                Err(ExecutionError::MemoryError { label, source_file, err })
568            },
569        }
570    }
571}
572
573// SystemEventError implementations
574impl<T> MapExecErr<T> for Result<T, SystemEventError> {
575    #[inline(always)]
576    fn map_exec_err(
577        self,
578        mast_forest: &MastForest,
579        node_id: MastNodeId,
580        host: &impl Host,
581    ) -> Result<T, ExecutionError> {
582        match self {
583            Ok(v) => Ok(v),
584            Err(err) => {
585                let (label, source_file) =
586                    get_label_and_source_file(None, mast_forest, node_id, host);
587                Err(match err {
588                    SystemEventError::Advice(err) => {
589                        ExecutionError::AdviceError { label, source_file, err }
590                    },
591                    SystemEventError::Operation(err) => {
592                        ExecutionError::OperationError { label, source_file, err }
593                    },
594                    SystemEventError::Memory(err) => {
595                        ExecutionError::MemoryError { label, source_file, err }
596                    },
597                })
598            },
599        }
600    }
601}
602
603impl<T> MapExecErrWithOpIdx<T> for Result<T, SystemEventError> {
604    #[inline(always)]
605    fn map_exec_err_with_op_idx(
606        self,
607        mast_forest: &MastForest,
608        node_id: MastNodeId,
609        host: &impl Host,
610        op_idx: usize,
611    ) -> Result<T, ExecutionError> {
612        match self {
613            Ok(v) => Ok(v),
614            Err(err) => {
615                let (label, source_file) =
616                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
617                Err(match err {
618                    SystemEventError::Advice(err) => {
619                        ExecutionError::AdviceError { label, source_file, err }
620                    },
621                    SystemEventError::Operation(err) => {
622                        ExecutionError::OperationError { label, source_file, err }
623                    },
624                    SystemEventError::Memory(err) => {
625                        ExecutionError::MemoryError { label, source_file, err }
626                    },
627                })
628            },
629        }
630    }
631}
632
633// IoError implementations
634impl<T> MapExecErrWithOpIdx<T> for Result<T, IoError> {
635    #[inline(always)]
636    fn map_exec_err_with_op_idx(
637        self,
638        mast_forest: &MastForest,
639        node_id: MastNodeId,
640        host: &impl Host,
641        op_idx: usize,
642    ) -> Result<T, ExecutionError> {
643        match self {
644            Ok(v) => Ok(v),
645            Err(err) => {
646                let (label, source_file) =
647                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
648                Err(match err {
649                    IoError::Advice(err) => ExecutionError::AdviceError { label, source_file, err },
650                    IoError::Memory(err) => ExecutionError::MemoryError { label, source_file, err },
651                    // Execution errors are already fully formed with their own message.
652                    IoError::Execution(boxed_err) => *boxed_err,
653                })
654            },
655        }
656    }
657}
658
659// CryptoError implementations
660impl<T> MapExecErrWithOpIdx<T> for Result<T, CryptoError> {
661    #[inline(always)]
662    fn map_exec_err_with_op_idx(
663        self,
664        mast_forest: &MastForest,
665        node_id: MastNodeId,
666        host: &impl Host,
667        op_idx: usize,
668    ) -> Result<T, ExecutionError> {
669        match self {
670            Ok(v) => Ok(v),
671            Err(err) => {
672                let (label, source_file) =
673                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
674                Err(match err {
675                    CryptoError::Advice(err) => {
676                        ExecutionError::AdviceError { label, source_file, err }
677                    },
678                    CryptoError::Operation(err) => {
679                        ExecutionError::OperationError { label, source_file, err }
680                    },
681                })
682            },
683        }
684    }
685}
686
687// AceEvalError implementations
688impl<T> MapExecErrWithOpIdx<T> for Result<T, AceEvalError> {
689    #[inline(always)]
690    fn map_exec_err_with_op_idx(
691        self,
692        mast_forest: &MastForest,
693        node_id: MastNodeId,
694        host: &impl Host,
695        op_idx: usize,
696    ) -> Result<T, ExecutionError> {
697        match self {
698            Ok(v) => Ok(v),
699            Err(err) => {
700                let (label, source_file) =
701                    get_label_and_source_file(Some(op_idx), mast_forest, node_id, host);
702                Err(match err {
703                    AceEvalError::Ace(error) => {
704                        ExecutionError::AceChipError { label, source_file, error }
705                    },
706                    AceEvalError::Memory(err) => {
707                        ExecutionError::MemoryError { label, source_file, err }
708                    },
709                })
710            },
711        }
712    }
713}
714
715// TESTS
716// ================================================================================================
717
718#[cfg(test)]
719mod error_assertions {
720    use super::*;
721
722    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
723    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
724
725    fn _assert_execution_error_bounds(err: ExecutionError) {
726        _assert_error_is_send_sync_static(err);
727    }
728}