Skip to main content

miden_assembly_syntax/sema/
errors.rs

1// Allow unused assignments - required by miette::Diagnostic derive macro
2#![allow(unused_assignments)]
3
4use alloc::{boxed::Box, sync::Arc, vec::Vec};
5use core::fmt;
6
7use miden_debug_types::{SourceFile, SourceSpan};
8use miden_utils_diagnostics::{Diagnostic, miette};
9
10use crate::ast::{SymbolResolutionError, constants::ConstEvalError};
11
12/// The high-level error type for all semantic analysis errors.
13///
14/// This rolls up multiple errors into a single one, and as such, can emit many
15/// diagnostics at once. You should prefer to gather up errors and continue with
16/// analysis for as long as possible before returning an error of this type, so
17/// as to provide as much information to the user as possible, but there are cases
18/// where doing so would not be profitable - it is a judgement call.
19///
20/// The semantic analyzer does this though, and you can examine its implementation
21/// to see how we approach this during the semantic analysis phase specifically.
22#[derive(Debug, thiserror::Error, Diagnostic)]
23#[error("syntax error")]
24#[diagnostic(help("see emitted diagnostics for details"))]
25pub struct SyntaxError {
26    #[source_code]
27    pub source_file: Arc<SourceFile>,
28    #[related]
29    pub errors: Vec<SemanticAnalysisError>,
30}
31
32/// This type is used when emitting advice/warnings, when those are not being treated as errors.
33///
34/// Like [SyntaxError], this rolls up all such notices into a single batch, and emits them all
35/// at once. The difference is that we never return this as an error from any API, it simply
36/// exists to leverage the diagnostic infrastructure of `miette`.
37#[derive(Debug, thiserror::Error, Diagnostic)]
38#[error("one or more warnings were emitted")]
39#[diagnostic(help("see below for details"))]
40#[cfg_attr(not(feature = "std"), expect(unused))]
41pub struct SyntaxWarning {
42    #[source_code]
43    pub source_file: Arc<SourceFile>,
44    #[related]
45    pub errors: Vec<SemanticAnalysisError>,
46}
47
48/// Represents an error that occurs during semantic analysis
49#[derive(Debug, thiserror::Error, Diagnostic)]
50pub enum SemanticAnalysisError {
51    #[error("invalid program: no entrypoint defined")]
52    #[diagnostic(help(
53        "ensure you define an entrypoint somewhere in the body with `begin`..`end`"
54    ))]
55    MissingEntrypoint,
56    #[error("invalid module: unexpected entrypoint definition")]
57    #[diagnostic(help("library modules cannot contain `begin`..`end` blocks"))]
58    UnexpectedEntrypoint {
59        #[label]
60        span: SourceSpan,
61    },
62    #[error("invalid module: multiple conflicting entrypoints defined")]
63    #[diagnostic(help("an executable module can only have a single `begin`..`end` block"))]
64    MultipleEntrypoints {
65        #[label]
66        span: SourceSpan,
67        #[label]
68        prev_span: SourceSpan,
69    },
70    #[error("invalid invocation target")]
71    #[diagnostic(help("path contains an invalid component"))]
72    InvalidInvokePath {
73        #[label]
74        span: SourceSpan,
75    },
76    #[error("invalid program: procedure exports are not allowed")]
77    #[diagnostic(help("perhaps you meant to use `proc` instead of `export`?"))]
78    UnexpectedExport {
79        #[label]
80        span: SourceSpan,
81    },
82    #[error("invalid enum type representation: underlying type must be an integral or felt type")]
83    #[diagnostic()]
84    InvalidEnumRepr {
85        #[label]
86        span: SourceSpan,
87    },
88    #[error("invalid enum discriminant: value is not a valid instance of the {repr} type")]
89    #[diagnostic()]
90    InvalidEnumDiscriminant {
91        #[label]
92        span: SourceSpan,
93        repr: crate::ast::types::Type,
94    },
95    #[error("invalid enum discriminant: value conflicts with another variant of the same enum")]
96    #[diagnostic()]
97    EnumDiscriminantConflict {
98        #[label("this discriminant value conflicts with a previous variant")]
99        span: SourceSpan,
100        #[label("discriminant previously observed here")]
101        prev: SourceSpan,
102    },
103    #[error("symbol conflict: found duplicate definitions of the same name")]
104    #[diagnostic()]
105    SymbolConflict {
106        #[label("conflict occurs here")]
107        span: SourceSpan,
108        #[label("previously defined here")]
109        prev_span: SourceSpan,
110    },
111    #[error("unused import")]
112    #[diagnostic(severity(Warning), help("this import is never used and can be safely removed"))]
113    UnusedImport {
114        #[label]
115        span: SourceSpan,
116    },
117    #[error("missing import: the referenced module has not been imported")]
118    #[diagnostic()]
119    MissingImport {
120        #[label("this reference is invalid without a corresponding import")]
121        span: SourceSpan,
122    },
123    #[error("symbol conflict: import would shadow a previous import of the same name")]
124    #[diagnostic(help(
125        "imports must have unique names within a module, \
126        try aliasing one of the imports if both are needed"
127    ))]
128    ImportConflict {
129        #[label("caused by this import")]
130        span: SourceSpan,
131        #[label("previously imported here")]
132        prev_span: SourceSpan,
133    },
134    #[error(
135        "invalid re-exported procedure: kernel modules may not re-export procedures from other modules"
136    )]
137    #[diagnostic()]
138    ReexportFromKernel {
139        #[label]
140        span: SourceSpan,
141    },
142    #[error("invalid instruction usage: 'caller' is only valid in kernel modules")]
143    #[diagnostic()]
144    CallerInKernel {
145        #[label]
146        span: SourceSpan,
147    },
148    #[error("invalid syscall: callee must be resolvable to kernel module")]
149    #[diagnostic()]
150    InvalidSyscallTarget {
151        #[label]
152        span: SourceSpan,
153    },
154    #[error("invalid procedure path: not an item")]
155    #[diagnostic()]
156    InvalidInvokeTargetViaImport {
157        #[label("call occurs here")]
158        span: SourceSpan,
159        #[label("expected this to resolve to a module, but got a module item")]
160        import: SourceSpan,
161    },
162    #[error("invalid recursive procedure call")]
163    #[diagnostic(help(
164        "this call induces a cycle that returns back to the caller, you must break that cycle"
165    ))]
166    InvalidRecursiveCall {
167        #[label("caused by this call")]
168        span: SourceSpan,
169    },
170    #[error("invalid recursive procedure call")]
171    #[diagnostic(help("this call is self-recursive, which is not allowed"))]
172    SelfRecursive {
173        #[label]
174        span: SourceSpan,
175    },
176    #[error("invalid repeat count")]
177    #[diagnostic(help("repeat count must be in the range {min}..={max}"))]
178    InvalidRepeatCount {
179        #[label]
180        span: SourceSpan,
181        min: u32,
182        max: u32,
183    },
184    #[error("invalid immediate: value is larger than expected range")]
185    #[diagnostic()]
186    ImmediateOverflow {
187        #[label]
188        span: SourceSpan,
189    },
190    #[error("invalid module: {}", kind)]
191    #[diagnostic(help("try breaking this module up into submodules"))]
192    LimitExceeded {
193        #[label]
194        span: SourceSpan,
195        kind: LimitKind,
196    },
197    #[error("unused docstring")]
198    #[diagnostic(
199        severity(Warning),
200        help(
201            "this docstring is immediately followed by at least one empty line, then another docstring,\
202            if you intended these to be a single docstring, you should remove the empty lines"
203        )
204    )]
205    UnusedDocstring {
206        #[label]
207        span: SourceSpan,
208    },
209    #[error("unused docstring")]
210    #[diagnostic(
211        severity(Warning),
212        help(
213            "module imports cannot have docstrings, you should use line comment syntax here instead"
214        )
215    )]
216    ImportDocstring {
217        #[label]
218        span: SourceSpan,
219    },
220    #[error("advmap key already defined")]
221    #[diagnostic()]
222    AdvMapKeyAlreadyDefined {
223        #[label]
224        span: SourceSpan,
225    },
226    #[error(transparent)]
227    #[diagnostic(transparent)]
228    ConstEvalError(#[from] ConstEvalError),
229    #[error(transparent)]
230    #[diagnostic(transparent)]
231    SymbolResolutionError(#[from] Box<SymbolResolutionError>),
232}
233
234impl From<SymbolResolutionError> for SemanticAnalysisError {
235    fn from(value: SymbolResolutionError) -> Self {
236        Self::SymbolResolutionError(Box::new(value))
237    }
238}
239
240/// Represents a system limit that was exceeded
241#[derive(Debug, Copy, Clone, PartialEq, Eq)]
242pub enum LimitKind {
243    /// The total number of procedures in a module
244    Procedures,
245    /// The total number of procedure locals
246    Locals,
247    /// The total number of imports in a module
248    Imports,
249    /// The total number of calls to imports
250    ///
251    /// TODO(pauls): Is this even a limit anymore?
252    CalledImports,
253    /// The total number of instructions in a procedure.
254    Instructions,
255}
256
257impl fmt::Display for LimitKind {
258    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259        match self {
260            Self::Procedures => f.write_str("too many procedures in module"),
261            Self::Locals => f.write_str("too many procedure locals"),
262            Self::Imports => f.write_str("too many imported procedures"),
263            Self::CalledImports => f.write_str("too many calls to imported procedures"),
264            Self::Instructions => f.write_str("too many instructions in block"),
265        }
266    }
267}