Skip to main content

maat_errors/
lib.rs

1#![forbid(unsafe_code)]
2
3use std::path::PathBuf;
4
5use maat_span::Span;
6
7pub type Result<T> = std::result::Result<T, Error>;
8
9#[derive(Debug, thiserror::Error)]
10pub enum Error {
11    #[error(transparent)]
12    Parse(#[from] ParseError),
13
14    #[error("eval error: {0}")]
15    Eval(#[from] EvalError),
16
17    #[error("compile error: {0}")]
18    Compile(#[from] CompileError),
19
20    #[error("type error: {0}")]
21    Type(#[from] TypeError),
22
23    #[error("vm error: {0}")]
24    Vm(#[from] VmError),
25
26    #[error("decode error: {0}")]
27    Decode(#[from] DecodeError),
28
29    #[error("serialization error: {0}")]
30    Serialization(#[from] SerializationError),
31
32    #[error("module error: {0}")]
33    Module(#[from] ModuleError),
34}
35
36#[derive(Debug, thiserror::Error)]
37#[error("parse error at {}..{}: {message}", span.start, span.end)]
38pub struct ParseError {
39    pub message: String,
40    pub span: Span,
41}
42
43impl ParseError {
44    pub fn new(message: impl Into<String>, span: Span) -> Self {
45        Self {
46            message: message.into(),
47            span,
48        }
49    }
50}
51
52#[derive(Debug, thiserror::Error)]
53pub enum EvalError {
54    #[error("{0}")]
55    Ident(String),
56
57    #[error("{0}")]
58    IndexExpr(String),
59
60    #[error("{0}")]
61    PrefixExpr(String),
62
63    #[error("{0}")]
64    InfixExpr(String),
65
66    #[error("{0}")]
67    Boolean(String),
68
69    #[error("{0}")]
70    Number(String),
71
72    #[error("{0}")]
73    NotAFunction(String),
74
75    #[error("unusable as hash key: {0}")]
76    NotHashable(String),
77
78    #[error("{0}")]
79    Builtin(String),
80}
81
82/// A type-checking error with a source span.
83///
84/// Wraps [`TypeErrorKind`] with location information for rich diagnostics.
85#[derive(Debug, thiserror::Error)]
86#[error("{kind}")]
87pub struct TypeError {
88    pub kind: TypeErrorKind,
89    pub span: Span,
90}
91
92/// The underlying variant of a type-checking error.
93#[derive(Debug, thiserror::Error)]
94pub enum TypeErrorKind {
95    #[error("type mismatch: expected `{expected}`, found `{found}`")]
96    Mismatch { expected: String, found: String },
97
98    #[error(
99        "type mismatch: expected `{expected}`, found `{found}`\n  help: consider using `as {expected}` for explicit numeric conversion"
100    )]
101    NumericMismatch { expected: String, found: String },
102
103    #[error("infinite type: `{0}` occurs in its own definition")]
104    OccursCheck(String),
105
106    #[error("wrong number of arguments: expected {expected}, found {found}")]
107    WrongArity { expected: usize, found: usize },
108
109    #[error("expression of type `{0}` is not callable")]
110    NotCallable(String),
111
112    #[error("numeric overflow: `{value}` out of range for `{target}`")]
113    NumericOverflow { value: String, target: String },
114
115    #[error("division by zero: `{value}`")]
116    DivisionByZero { value: String },
117
118    #[error("unknown type `{0}`")]
119    UnknownType(String),
120
121    #[error("no field `{field}` on type `{ty}`")]
122    UnknownField { ty: String, field: String },
123
124    #[error("no method `{method}` found for type `{ty}`")]
125    UnknownMethod { ty: String, method: String },
126
127    #[error("duplicate type definition `{0}`")]
128    DuplicateType(String),
129
130    #[error("{0}")]
131    MissingTraitMethod(Box<MissingTraitMethodError>),
132
133    #[error("{0}")]
134    TraitMethodSignatureMismatch(Box<TraitMethodSignatureMismatchError>),
135
136    #[error("non-exhaustive patterns in `match`: {missing}")]
137    NonExhaustiveMatch { missing: String },
138
139    #[error("unknown trait `{0}`")]
140    UnknownTrait(String),
141
142    #[error("unsupported: {0}")]
143    Unsupported(String),
144
145    #[error("item `{item}` is private to module `{module}`")]
146    PrivateAccess { item: String, module: String },
147}
148
149/// Detail for a missing trait method error.
150#[derive(Debug, thiserror::Error)]
151#[error("missing trait method `{method}` in impl of `{trait_name}` for `{self_type}`")]
152pub struct MissingTraitMethodError {
153    pub trait_name: String,
154    pub self_type: String,
155    pub method: String,
156}
157
158/// Detail for a trait method signature mismatch error.
159#[derive(Debug, thiserror::Error)]
160#[error(
161    "method `{method}` has wrong signature in impl of `{trait_name}` for `{self_type}`: expected `{expected}`, found `{found}`"
162)]
163pub struct TraitMethodSignatureMismatchError {
164    pub trait_name: String,
165    pub self_type: String,
166    pub method: String,
167    pub expected: String,
168    pub found: String,
169}
170
171impl TypeErrorKind {
172    /// Attaches a source span to this error kind, producing a [`TypeError`].
173    pub fn at(self, span: Span) -> TypeError {
174        TypeError { kind: self, span }
175    }
176}
177
178/// A compile-time error with an optional source span.
179///
180/// Wraps [`CompileErrorKind`] with location information for rich diagnostics.
181#[derive(Debug, thiserror::Error)]
182#[error("{kind}")]
183pub struct CompileError {
184    pub kind: CompileErrorKind,
185    pub span: Option<Span>,
186}
187
188impl CompileError {
189    /// Creates a compile error from a kind with no associated span.
190    pub fn new(kind: CompileErrorKind) -> Self {
191        Self { kind, span: None }
192    }
193}
194
195/// The underlying variant of a compile-time error.
196#[derive(Debug, thiserror::Error)]
197pub enum CompileErrorKind {
198    #[error(
199        "constant pool overflow: exceeded maximum of {max} constants (attempted index: {attempted})"
200    )]
201    ConstantPoolOverflow { max: usize, attempted: usize },
202
203    #[error("unsupported operator '{operator}' in {context}")]
204    UnsupportedOperator { operator: String, context: String },
205
206    #[error("unsupported expression type '{expr_type}'")]
207    UnsupportedExpr { expr_type: String },
208
209    #[error("invalid opcode 0x{opcode:02x} at instruction position {position}")]
210    InvalidOpcode { opcode: u8, position: usize },
211
212    #[error("undefined variable '{name}'")]
213    UndefinedVariable { name: String },
214
215    #[error(
216        "symbols table overflow: exceeded maximum of {max} global bindings (attempted to define '{name}')"
217    )]
218    SymbolsTableOverflow { max: usize, name: String },
219
220    #[error(
221        "local variable overflow: exceeded maximum of {max} local bindings in function scope (attempted to define '{name}')"
222    )]
223    LocalsOverflow { max: usize, name: String },
224
225    #[error("scope stack underflow: attempted to leave scope with no enclosing scope")]
226    ScopeUnderflow,
227
228    #[error("`break` outside of a loop")]
229    BreakOutsideLoop,
230
231    #[error("`continue` outside of a loop")]
232    ContinueOutsideLoop,
233
234    #[error("undeclared label `'{label}`")]
235    UndeclaredLabel { label: String },
236
237    #[error("cannot re-assign to immutable variable `{name}`")]
238    ImmutableAssignment { name: String },
239
240    #[error("unknown builtin macro `{name}!`")]
241    UnknownMacro { name: String },
242
243    #[error(
244        "format string has {placeholders} placeholder(s) but {arguments} argument(s) were supplied"
245    )]
246    FormatArgCountMismatch {
247        placeholders: usize,
248        arguments: usize,
249    },
250
251    #[error("`{macro_name}!` requires a format string literal as its first argument")]
252    MacroExpectsFormatString { macro_name: String },
253
254    #[error(
255        "enum `{name}` has {count} variants, exceeding the maximum of {max} (variant tags must fit in 8 bits)"
256    )]
257    VariantTagOverflow {
258        name: String,
259        count: usize,
260        max: usize,
261    },
262}
263
264impl CompileErrorKind {
265    /// Attaches a source span to this error kind, producing a [`CompileError`].
266    pub fn at(self, span: Span) -> CompileError {
267        CompileError {
268            kind: self,
269            span: Some(span),
270        }
271    }
272}
273
274impl From<CompileErrorKind> for CompileError {
275    fn from(kind: CompileErrorKind) -> Self {
276        Self { kind, span: None }
277    }
278}
279
280/// A runtime VM error with an optional source span.
281#[derive(Debug, thiserror::Error)]
282#[error("{message}")]
283pub struct VmError {
284    pub message: String,
285    pub span: Option<Span>,
286}
287
288impl VmError {
289    pub fn new(message: impl Into<String>) -> Self {
290        Self {
291            message: message.into(),
292            span: None,
293        }
294    }
295
296    /// Creates a VM error with an associated source span.
297    pub fn with_span(message: impl Into<String>, span: Span) -> Self {
298        Self {
299            message: message.into(),
300            span: Some(span),
301        }
302    }
303}
304
305impl From<String> for VmError {
306    fn from(message: String) -> Self {
307        Self {
308            message,
309            span: None,
310        }
311    }
312}
313
314impl From<&str> for VmError {
315    fn from(message: &str) -> Self {
316        Self {
317            message: message.to_string(),
318            span: None,
319        }
320    }
321}
322
323#[derive(Debug, thiserror::Error)]
324pub enum DecodeError {
325    #[error(
326        "bytecode truncated: needed {needed} bytes at offset {offset}, but only {available} bytes available"
327    )]
328    UnexpectedEndOfBytecode {
329        offset: usize,
330        needed: usize,
331        available: usize,
332    },
333
334    #[error("unsupported operand width: {0} (valid widths: 1, 2, 4, 8)")]
335    UnsupportedOperandWidth(usize),
336
337    #[error("invalid opcode: 0x{0:02x}")]
338    InvalidOpcode(u8),
339}
340
341/// Errors arising during bytecode serialization or deserialization.
342///
343/// Header-level errors (`InvalidMagic`, `UnsupportedVersion`, `UnexpectedEof`)
344/// are checked before the payload is decoded. Payload-level errors are
345/// reported by `postcard` via `serde` and surfaced as `PostcardEncode` or
346/// `PostcardDecode`.
347#[derive(Debug, thiserror::Error)]
348pub enum SerializationError {
349    /// The file does not begin with the expected `MAAT` magic bytes.
350    #[error("invalid magic bytes: expected MAAT header")]
351    InvalidMagic,
352
353    /// The format version in the header is not supported by this build.
354    #[error("unsupported bytecode format version: {0}")]
355    UnsupportedVersion(u32),
356
357    /// The byte stream was truncated before the header could be fully read.
358    #[error("unexpected end of bytecode at offset {offset}: needed {needed} more bytes")]
359    UnexpectedEof { offset: usize, needed: usize },
360
361    /// An error occurred while encoding bytecode with postcard.
362    #[error("bytecode encode error: {0}")]
363    PostcardEncode(String),
364
365    /// An error occurred while decoding bytecode with postcard.
366    #[error("bytecode decode error: {0}")]
367    PostcardDecode(String),
368
369    /// The bytecode payload exceeds the maximum allowed size.
370    #[error("bytecode payload too large: {size} bytes exceeds {limit} byte limit")]
371    PayloadTooLarge { size: usize, limit: usize },
372
373    /// A deserialized field exceeds its allowed resource limit.
374    #[error("{field} too large: {size} exceeds limit of {limit}")]
375    ResourceLimitExceeded {
376        field: &'static str,
377        size: usize,
378        limit: usize,
379    },
380}
381
382/// A module resolution error with file context and source span.
383///
384/// Wraps [`ModuleErrorKind`] with the originating file path and span
385/// for rich diagnostic output.
386#[derive(Debug, thiserror::Error)]
387#[error("{file}: {kind}", file = file.display())]
388pub struct ModuleError {
389    pub kind: ModuleErrorKind,
390    pub span: Span,
391    /// The file in which the error was encountered.
392    pub file: PathBuf,
393}
394
395/// The underlying variant of a module resolution error.
396#[derive(Debug, thiserror::Error)]
397pub enum ModuleErrorKind {
398    /// A `mod foo;` declaration could not be resolved to a source file.
399    #[error("module `{module_name}` not found; searched: {}", candidates.iter().map(|p| p.display().to_string()).collect::<Vec<_>>().join(", "))]
400    FileNotFound {
401        module_name: String,
402        candidates: Vec<PathBuf>,
403    },
404
405    /// A cycle was detected in the module dependency graph.
406    #[error("cyclic module dependency: {}", cycle.join(" -> "))]
407    CyclicDependency {
408        /// The module names forming the cycle, in visitation order.
409        cycle: Vec<String>,
410    },
411
412    /// A module was declared more than once in the same parent module.
413    #[error("duplicate module declaration `{module_name}`")]
414    DuplicateModule { module_name: String },
415
416    /// The parser encountered errors in a module source file.
417    #[error("parse errors in `{}`:{}", file.display(), messages.iter().map(|m| format!("\n  {m}")).collect::<String>())]
418    ParseErrors {
419        file: PathBuf,
420        messages: Vec<String>,
421    },
422
423    /// Type errors encountered during module type checking.
424    #[error("type errors in `{}`:{}", file.display(), messages.iter().map(|m| format!("\n  {m}")).collect::<String>())]
425    TypeErrors {
426        file: PathBuf,
427        messages: Vec<String>,
428    },
429
430    /// Compilation errors encountered during module code generation.
431    #[error("compile errors in `{}`:{}", file.display(), messages.iter().map(|m| format!("\n  {m}")).collect::<String>())]
432    CompileErrors {
433        file: PathBuf,
434        messages: Vec<String>,
435    },
436
437    /// An I/O error occurred while reading a source file.
438    #[error("cannot read `{}`: {message}", path.display())]
439    Io { path: PathBuf, message: String },
440}
441
442impl ModuleErrorKind {
443    /// Attaches a source span and file path to this error kind,
444    /// producing a [`ModuleError`].
445    pub fn at(self, span: Span, file: PathBuf) -> ModuleError {
446        ModuleError {
447            kind: self,
448            span,
449            file,
450        }
451    }
452}