miden_assembly_syntax/sema/
errors.rs

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