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}