miden_assembly/
errors.rs

1use alloc::{string::String, sync::Arc, vec::Vec};
2
3use vm_core::mast::MastForestError;
4
5use crate::{
6    ast::QualifiedProcedureName,
7    diagnostics::{Diagnostic, RelatedError, RelatedLabel, SourceFile},
8    LibraryNamespace, LibraryPath, SourceSpan,
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("two procedures found with same mast root, but conflicting definitions ('{first}' and '{second}')")]
32    #[diagnostic()]
33    ConflictingDefinitions {
34        first: QualifiedProcedureName,
35        second: QualifiedProcedureName,
36    },
37    #[error("duplicate definition found for module '{path}'")]
38    #[diagnostic()]
39    DuplicateModule { path: LibraryPath },
40    #[error("undefined module '{path}'")]
41    #[diagnostic()]
42    UndefinedModule {
43        #[label]
44        span: SourceSpan,
45        #[source_code]
46        source_file: Option<Arc<SourceFile>>,
47        path: LibraryPath,
48    },
49    #[error("module namespace is inconsistent with library ('{actual}' vs '{expected}')")]
50    #[diagnostic()]
51    InconsistentNamespace {
52        expected: LibraryNamespace,
53        actual: LibraryNamespace,
54    },
55    #[error("invalid syscall: '{callee}' is not an exported kernel procedure")]
56    #[diagnostic()]
57    InvalidSysCallTarget {
58        #[label("call occurs here")]
59        span: SourceSpan,
60        #[source_code]
61        source_file: Option<Arc<SourceFile>>,
62        callee: QualifiedProcedureName,
63    },
64    #[error("invalid local word index: {local_addr}")]
65    #[diagnostic(help("the index to a local word must be a multiple of 4"))]
66    InvalidLocalWordIndex {
67        #[label]
68        span: SourceSpan,
69        #[source_code]
70        source_file: Option<Arc<SourceFile>>,
71        local_addr: u16,
72    },
73    #[error("invalid use of 'caller' instruction outside of kernel")]
74    #[diagnostic(help(
75        "the 'caller' instruction is only allowed in procedures defined in a kernel"
76    ))]
77    CallerOutsideOfKernel {
78        #[label]
79        span: SourceSpan,
80        #[source_code]
81        source_file: Option<Arc<SourceFile>>,
82    },
83
84    #[error("invalid procedure: body must contain at least one instruction if it has decorators")]
85    #[diagnostic()]
86    EmptyProcedureBodyWithDecorators {
87        span: SourceSpan,
88        #[source_code]
89        source_file: Option<Arc<SourceFile>>,
90    },
91    #[error(transparent)]
92    #[diagnostic(transparent)]
93    Other(RelatedError),
94    // Technically MastForestError is the source error here, but since AssemblyError is converted
95    // into a Report and that doesn't implement core::error::Error, treating MastForestError as a
96    // source error would effectively swallow it, so we include it in the error message instead.
97    #[error("{0}: {1}")]
98    Forest(&'static str, MastForestError),
99}
100
101impl AssemblyError {
102    pub(super) fn forest_error(message: &'static str, source: MastForestError) -> Self {
103        Self::Forest(message, source)
104    }
105}