miden_assembly/
errors.rs

1use alloc::{string::String, sync::Arc, vec::Vec};
2
3use vm_core::mast::MastForestError;
4
5use crate::{
6    LibraryNamespace, LibraryPath, SourceSpan,
7    ast::QualifiedProcedureName,
8    diagnostics::{Diagnostic, RelatedError, RelatedLabel, SourceFile},
9};
10
11// ASSEMBLY ERROR
12// ================================================================================================
13
14/// An error which can be generated while compiling a Miden assembly program into a MAST.
15#[derive(Debug, thiserror::Error, Diagnostic)]
16#[non_exhaustive]
17#[allow(clippy::large_enum_variant)]
18pub enum AssemblyError {
19    #[error("there are no modules to analyze")]
20    #[diagnostic()]
21    Empty,
22    #[error("assembly failed")]
23    #[diagnostic(help("see diagnostics for details"))]
24    Failed {
25        #[related]
26        labels: Vec<RelatedLabel>,
27    },
28    #[error("found a cycle in the call graph, involving these procedures: {}", nodes.as_slice().join(", "))]
29    #[diagnostic()]
30    Cycle { nodes: Vec<String> },
31    #[error(
32        "two procedures found with same mast root, but conflicting definitions ('{first}' and '{second}')"
33    )]
34    #[diagnostic()]
35    ConflictingDefinitions {
36        first: QualifiedProcedureName,
37        second: QualifiedProcedureName,
38    },
39    #[error("duplicate definition found for module '{path}'")]
40    #[diagnostic()]
41    DuplicateModule { path: LibraryPath },
42    #[error("undefined module '{path}'")]
43    #[diagnostic()]
44    UndefinedModule {
45        #[label]
46        span: SourceSpan,
47        #[source_code]
48        source_file: Option<Arc<SourceFile>>,
49        path: LibraryPath,
50    },
51    #[error("module namespace is inconsistent with library ('{actual}' vs '{expected}')")]
52    #[diagnostic()]
53    InconsistentNamespace {
54        expected: LibraryNamespace,
55        actual: LibraryNamespace,
56    },
57    #[error("invalid syscall: '{callee}' is not an exported kernel procedure")]
58    #[diagnostic()]
59    InvalidSysCallTarget {
60        #[label("call occurs here")]
61        span: SourceSpan,
62        #[source_code]
63        source_file: Option<Arc<SourceFile>>,
64        callee: QualifiedProcedureName,
65    },
66    #[error("invalid local word index: {local_addr}")]
67    #[diagnostic(help("the index to a local word must be a multiple of 4"))]
68    InvalidLocalWordIndex {
69        #[label]
70        span: SourceSpan,
71        #[source_code]
72        source_file: Option<Arc<SourceFile>>,
73        local_addr: u16,
74    },
75    #[error("invalid use of 'caller' instruction outside of kernel")]
76    #[diagnostic(help(
77        "the 'caller' instruction is only allowed in procedures defined in a kernel"
78    ))]
79    CallerOutsideOfKernel {
80        #[label]
81        span: SourceSpan,
82        #[source_code]
83        source_file: Option<Arc<SourceFile>>,
84    },
85
86    #[error("invalid procedure: body must contain at least one instruction if it has decorators")]
87    #[diagnostic()]
88    EmptyProcedureBodyWithDecorators {
89        span: SourceSpan,
90        #[source_code]
91        source_file: Option<Arc<SourceFile>>,
92    },
93    #[error(transparent)]
94    #[diagnostic(transparent)]
95    Other(RelatedError),
96    // Technically MastForestError is the source error here, but since AssemblyError is converted
97    // into a Report and that doesn't implement core::error::Error, treating MastForestError as a
98    // source error would effectively swallow it, so we include it in the error message instead.
99    #[error("{0}: {1}")]
100    Forest(&'static str, MastForestError),
101}
102
103impl AssemblyError {
104    pub(super) fn forest_error(message: &'static str, source: MastForestError) -> Self {
105        Self::Forest(message, source)
106    }
107}