Skip to main content

sim_kernel/
error.rs

1//! The error and diagnostic contract for the kernel.
2//!
3//! Defines the [`enum@Error`] enum, the [`Result`] alias, and the [`Diagnostic`]
4//! and [`Severity`] types that every kernel contract and library reports
5//! against.
6
7use thiserror::Error;
8
9use crate::{
10    capability::{CapabilityName, TrustLevel},
11    expr::{SourceId, Span},
12    id::{CaseId, ClassId, CodecId, FunctionId, ShapeId, Symbol},
13    library::{ExportKind, Version},
14};
15
16/// The kernel result alias, defaulting the error type to [`enum@Error`].
17pub type Result<T, E = Error> = core::result::Result<T, E>;
18
19/// A structured diagnostic message with optional source location.
20///
21/// The kernel defines the diagnostic record; libraries and contracts attach
22/// diagnostics to errors to explain failures with source context.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct Diagnostic {
25    /// The severity level.
26    pub severity: Severity,
27    /// The human-readable message.
28    pub message: String,
29    /// The optional source the diagnostic refers to.
30    pub source: Option<SourceId>,
31    /// The optional span within the source.
32    pub span: Option<Span>,
33    /// The optional machine-readable diagnostic code.
34    pub code: Option<Symbol>,
35    /// Related sub-diagnostics providing more detail.
36    pub related: Vec<Diagnostic>,
37}
38
39/// The severity level of a [`Diagnostic`].
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum Severity {
42    /// An error.
43    Error,
44    /// A warning.
45    Warning,
46    /// Informational output.
47    Info,
48    /// A note attached to another diagnostic.
49    Note,
50}
51
52impl Diagnostic {
53    /// Builds an error-severity diagnostic with just a message.
54    pub fn error(message: impl Into<String>) -> Self {
55        Self {
56            severity: Severity::Error,
57            message: message.into(),
58            source: None,
59            span: None,
60            code: None,
61            related: Vec::new(),
62        }
63    }
64
65    /// Builds an info-severity diagnostic with just a message.
66    pub fn info(message: impl Into<String>) -> Self {
67        Self {
68            severity: Severity::Info,
69            message: message.into(),
70            source: None,
71            span: None,
72            code: None,
73            related: Vec::new(),
74        }
75    }
76
77    /// Adds a machine-readable diagnostic code.
78    pub fn with_code(mut self, code: Symbol) -> Self {
79        self.code = Some(code);
80        self
81    }
82
83    /// Adds a related sub-diagnostic.
84    pub fn with_related(mut self, related: Diagnostic) -> Self {
85        self.related.push(related);
86        self
87    }
88}
89
90/// The kernel error type reported by every contract and library.
91///
92/// The kernel defines the shared error vocabulary; libraries raise these
93/// variants (and wrap their own messages in the string-carrying variants)
94/// rather than inventing parallel error types.
95#[derive(Clone, Debug, Error)]
96pub enum Error {
97    /// A symbol could not be resolved.
98    #[error("unknown symbol {symbol}")]
99    UnknownSymbol {
100        /// The unresolved symbol.
101        symbol: Symbol,
102    },
103    /// A class name could not be resolved.
104    #[error("unknown class {class}")]
105    UnknownClass {
106        /// The unresolved class name.
107        class: Symbol,
108    },
109    /// A function name could not be resolved.
110    #[error("unknown function {function}")]
111    UnknownFunction {
112        /// The unresolved function name.
113        function: Symbol,
114    },
115    /// No class is registered under the given id.
116    #[error("missing class with id {0:?}")]
117    MissingClass(ClassId),
118    /// A value's class did not match the expected class.
119    #[error("wrong class: expected {expected:?}, found {found:?}")]
120    WrongClass {
121        /// The class that was required.
122        expected: ClassId,
123        /// The class actually found.
124        found: ClassId,
125    },
126    /// A value did not match the expected shape.
127    #[error("wrong shape: expected {expected:?}")]
128    WrongShape {
129        /// The shape that was required.
130        expected: ShapeId,
131        /// Diagnostics explaining the mismatch.
132        diagnostics: Vec<Diagnostic>,
133    },
134    /// More than one overload matched a call.
135    #[error("ambiguous overload for function {function:?}")]
136    AmbiguousOverload {
137        /// The function being dispatched.
138        function: FunctionId,
139        /// The competing candidate cases.
140        candidates: Vec<CaseId>,
141    },
142    /// No overload matched a call.
143    #[error("no matching overload for function {function:?}")]
144    NoMatchingOverload {
145        /// The function being dispatched.
146        function: FunctionId,
147        /// Diagnostics explaining why each case was rejected.
148        diagnostics: Vec<Diagnostic>,
149    },
150    /// More than one numeric domain pairing matched an operator.
151    #[error("ambiguous numeric dispatch for operator {operator}")]
152    AmbiguousNumberDispatch {
153        /// The operator being dispatched.
154        operator: Symbol,
155        /// The competing left/right domain pairings.
156        candidates: Vec<(Symbol, Symbol)>,
157    },
158    /// No promotion path joined two number domains for an operator.
159    #[error("no promotion path for operator {operator} from {left_domain} and {right_domain}")]
160    NoPromotionPath {
161        /// The operator being dispatched.
162        operator: Symbol,
163        /// The left operand's domain.
164        left_domain: Symbol,
165        /// The right operand's domain.
166        right_domain: Symbol,
167    },
168    /// Promotion search exceeded its configured limits.
169    #[error(
170        "number promotion search from {from_domain} to {target_domain} exceeded limits \
171         (max_depth={max_depth}, max_states={max_states})"
172    )]
173    PromotionSearchLimitExceeded {
174        /// The domain the search started from.
175        from_domain: Symbol,
176        /// The domain the search was targeting.
177        target_domain: Symbol,
178        /// The depth limit that was hit.
179        max_depth: usize,
180        /// The state-count limit that was hit.
181        max_states: usize,
182    },
183    /// A required capability was not granted.
184    #[error("capability denied: {capability}")]
185    CapabilityDenied {
186        /// The denied capability.
187        capability: CapabilityName,
188    },
189    /// A capability is not allowed at the caller's trust level.
190    #[error("trust denied: {capability} is not allowed for {trust:?}")]
191    TrustDenied {
192        /// The requested capability.
193        capability: CapabilityName,
194        /// The caller's trust level.
195        trust: TrustLevel,
196    },
197    /// A codec failed to read or write a form.
198    #[error("codec error in {codec:?}: {message}")]
199    CodecError {
200        /// The codec that failed.
201        codec: CodecId,
202        /// The failure message.
203        message: String,
204    },
205    /// A number (or other) domain reported a categorized failure.
206    #[error("domain error in {domain} ({category}): {message}")]
207    DomainError {
208        /// The domain that failed.
209        domain: Symbol,
210        /// The error category within the domain.
211        category: Symbol,
212        /// The failure message.
213        message: String,
214    },
215    /// Two exports of the same kind claimed the same symbol.
216    #[error("duplicate export for {kind} {symbol}")]
217    DuplicateExport {
218        /// The export kind label.
219        kind: &'static str,
220        /// The conflicting symbol.
221        symbol: Symbol,
222    },
223    /// Two libraries claimed the same name.
224    #[error("duplicate lib {symbol}")]
225    DuplicateLib {
226        /// The conflicting library name.
227        symbol: Symbol,
228    },
229    /// A catalog write conflicted with an existing key.
230    #[error("catalog conflict in {table} for {key}")]
231    CatalogConflict {
232        /// The catalog table.
233        table: Symbol,
234        /// The conflicting key.
235        key: Symbol,
236    },
237    /// A write targeted a read-only catalog table.
238    #[error("catalog table {table} is read-only")]
239    CatalogReadOnly {
240        /// The read-only table.
241        table: Symbol,
242    },
243    /// A catalog row violated its table schema.
244    #[error("catalog schema error in {table}: {message}")]
245    CatalogSchema {
246        /// The table whose schema was violated.
247        table: Symbol,
248        /// The schema-violation message.
249        message: String,
250    },
251    /// A library declared a dependency that is not loaded.
252    #[error("missing dependency {dependency} for {lib}")]
253    MissingDependency {
254        /// The depending library.
255        lib: Symbol,
256        /// The missing dependency.
257        dependency: Symbol,
258    },
259    /// A dependency is present but older than required.
260    #[error(
261        "dependency {dependency} for {lib} requires at least version {required:?} but loaded {loaded:?}"
262    )]
263    DependencyVersionMismatch {
264        /// The depending library.
265        lib: Symbol,
266        /// The dependency name.
267        dependency: Symbol,
268        /// The minimum required version.
269        required: Version,
270        /// The version actually loaded.
271        loaded: Version,
272    },
273    /// A cycle was found among library dependencies.
274    #[error("cyclic lib dependency involving {symbol}")]
275    CyclicDependency {
276        /// A library on the cycle.
277        symbol: Symbol,
278    },
279    /// A library cannot be unloaded because loaded libraries depend on it.
280    #[error("cannot unload {lib}; loaded dependents remain: {dependents:?}")]
281    LibHasDependents {
282        /// The library requested for unload.
283        lib: Symbol,
284        /// Loaded libraries that depend on it.
285        dependents: Vec<Symbol>,
286    },
287    /// An export record was produced without a matching manifest declaration.
288    #[error("export record for {kind:?} {symbol} was not declared in the manifest")]
289    UndeclaredExportRecord {
290        /// The export kind.
291        kind: ExportKind,
292        /// The undeclared symbol.
293        symbol: Symbol,
294    },
295    /// A value's static type did not match what was expected.
296    #[error("type mismatch: expected {expected}, found {found}")]
297    TypeMismatch {
298        /// The expected type label.
299        expected: &'static str,
300        /// The type label actually found.
301        found: &'static str,
302    },
303    /// Evaluation failed; carries a free-form message.
304    #[error("evaluation error: {0}")]
305    Eval(String),
306    /// A library reported a free-form failure.
307    #[error("lib error: {0}")]
308    Lib(String),
309    /// A lock was poisoned by a panic; carries the lock name.
310    #[error("poisoned lock: {0}")]
311    PoisonedLock(&'static str),
312    /// A thunk was forced while already being forced.
313    #[error("recursive thunk force detected")]
314    RecursiveThunkForce,
315    /// The host environment reported a free-form failure.
316    #[error("host error: {0}")]
317    HostError(String),
318}
319
320impl Error {
321    /// Builds a [`Error::DomainError`] from its parts.
322    pub fn domain_error(domain: Symbol, category: Symbol, message: impl Into<String>) -> Self {
323        Self::DomainError {
324            domain,
325            category,
326            message: message.into(),
327        }
328    }
329}