miden_assembly/
errors.rs

1use alloc::{boxed::Box, string::String, sync::Arc};
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]
17pub enum AssemblyError {
18    #[error("there are no modules to analyze")]
19    #[diagnostic()]
20    Empty,
21    #[error("assembly failed")]
22    #[diagnostic(help("see diagnostics for details"))]
23    Failed {
24        #[related]
25        labels: Box<[RelatedLabel]>,
26    },
27    #[error("found a cycle in the call graph, involving these procedures: {}", nodes.join(", "))]
28    #[diagnostic()]
29    Cycle { nodes: Box<[String]> },
30    #[error(
31        "two procedures found with same mast root, but conflicting definitions ('{first}' and '{second}')"
32    )]
33    #[diagnostic()]
34    ConflictingDefinitions {
35        first: Box<QualifiedProcedureName>,
36        second: Box<QualifiedProcedureName>,
37    },
38    #[error("duplicate definition found for module '{path}'")]
39    #[diagnostic()]
40    DuplicateModule { path: LibraryPath },
41    #[error("undefined module '{path}'")]
42    #[diagnostic()]
43    UndefinedModule {
44        #[label]
45        span: SourceSpan,
46        #[source_code]
47        source_file: Option<Arc<SourceFile>>,
48        path: LibraryPath,
49    },
50    #[error("module namespace is inconsistent with library ('{actual}' vs '{expected}')")]
51    #[diagnostic()]
52    InconsistentNamespace {
53        expected: LibraryNamespace,
54        actual: LibraryNamespace,
55    },
56    #[error("invalid syscall: '{callee}' is not an exported kernel procedure")]
57    #[diagnostic()]
58    InvalidSysCallTarget {
59        #[label("call occurs here")]
60        span: SourceSpan,
61        #[source_code]
62        source_file: Option<Arc<SourceFile>>,
63        callee: Box<QualifiedProcedureName>,
64    },
65    #[error("invalid local word index: {local_addr}")]
66    #[diagnostic(help("the index to a local word must be a multiple of 4"))]
67    InvalidLocalWordIndex {
68        #[label]
69        span: SourceSpan,
70        #[source_code]
71        source_file: Option<Arc<SourceFile>>,
72        local_addr: u16,
73    },
74    #[error(
75        "word accessed from local memory but the number of procedure locals was only {num_proc_locals}"
76    )]
77    #[diagnostic(help(
78        "when a word is accessed from local memory, the number of procedure locals must be at least 4 (since a word is 4 locals)"
79    ))]
80    InvalidNumProcLocals {
81        #[label("local word accessed here")]
82        span: SourceSpan,
83        #[source_code]
84        source_file: Option<Arc<SourceFile>>,
85        num_proc_locals: u16,
86    },
87    #[error("invalid parameter value: {param}; expected to be between {min} and {max}")]
88    #[diagnostic()]
89    InvalidU8Param {
90        #[label]
91        span: SourceSpan,
92        #[source_code]
93        source_file: Option<Arc<SourceFile>>,
94        param: u8,
95        min: u8,
96        max: u8,
97    },
98    #[error("invalid use of 'caller' instruction outside of kernel")]
99    #[diagnostic(help(
100        "the 'caller' instruction is only allowed in procedures defined in a kernel"
101    ))]
102    CallerOutsideOfKernel {
103        #[label]
104        span: SourceSpan,
105        #[source_code]
106        source_file: Option<Arc<SourceFile>>,
107    },
108
109    #[error("invalid procedure: body must contain at least one instruction if it has decorators")]
110    #[diagnostic()]
111    EmptyProcedureBodyWithDecorators {
112        span: SourceSpan,
113        #[source_code]
114        source_file: Option<Arc<SourceFile>>,
115    },
116    #[error("number of procedure locals was not set (or set to 0), but local memory was accessed")]
117    #[diagnostic(help(
118        "to use procedure locals, the number of procedure locals must be declared at the start of the procedure"
119    ))]
120    ProcLocalsNotDeclared {
121        #[label("procedure local accessed here")]
122        span: SourceSpan,
123        #[source_code]
124        source_file: Option<Arc<SourceFile>>,
125    },
126    #[error(transparent)]
127    #[diagnostic(transparent)]
128    Other(RelatedError),
129    // Technically MastForestError is the source error here, but since AssemblyError is converted
130    // into a Report and that doesn't implement core::error::Error, treating MastForestError as a
131    // source error would effectively swallow it, so we include it in the error message instead.
132    #[error("{0}: {1}")]
133    Forest(&'static str, MastForestError),
134}
135
136impl AssemblyError {
137    pub(super) fn forest_error(message: &'static str, source: MastForestError) -> Self {
138        Self::Forest(message, source)
139    }
140}