Skip to main content

rssn_advanced/error/
mod.rs

1//! Cold-path error infrastructure.
2//!
3//! Provides the `rssn_error!` macro that generates, for every variant of an
4//! error enum, a `cold_*` constructor marked `#[cold]` `#[inline(never)]`
5//! `#[track_caller]`. The hot path branches into these constructors only on
6//! genuine failure, keeping the success path's I-cache footprint minimal and
7//! letting the branch predictor assume success.
8//!
9//! Each module-level error type below is the canonical failure surface for one
10//! subsystem. They are intentionally narrow — variants describe *what* went
11//! wrong, not *where* (the `#[track_caller]` attribute on the cold constructor
12//! captures the call site).
13
14use core::fmt;
15
16// =========================================================================
17// The rssn_error! macro
18// =========================================================================
19
20/// Declares an error enum together with one `#[cold]` constructor per variant.
21///
22/// For each variant `Foo`, the macro emits a public free function
23/// `cold_<enum>_foo(...) -> Result<T, Enum>` that returns
24/// `Err(Enum::Foo { .. })`. Callers in hot code use:
25///
26/// ```ignore
27/// return cold_dag_error_arena_full();
28/// ```
29///
30/// The function is `#[cold] #[inline(never)] #[track_caller]`, ensuring the
31/// compiler lays it out off the hot path and that panic-like diagnostics
32/// preserve caller location.
33#[doc(hidden)]
34#[macro_export]
35macro_rules! rssn_error {
36    (
37        $(#[$meta:meta])*
38        $vis:vis enum $name:ident {
39            $(
40                $(#[$variant_meta:meta])*
41                $variant:ident $( { $( $(#[$field_meta:meta])* $field:ident : $ftype:ty ),* $(,)? } )? $( ( $( $(#[$tuple_meta:meta])* $tname:ident : $ttype:ty ),* $(,)? ) )?
42            ),* $(,)?
43        }
44    ) => {
45        $(#[$meta])*
46        $vis enum $name {
47            $(
48                $(#[$variant_meta])*
49                $variant $( { $( $(#[$field_meta])* $field : $ftype ),* } )? $( ( $( $(#[$tuple_meta])* $ttype ),* ) )?,
50            )*
51        }
52
53        pastey::paste! {
54            $(
55                $(#[$variant_meta])*
56                #[doc(hidden)]
57                #[cold]
58                #[track_caller]
59                #[inline(never)]
60                pub const fn [<cold_ $name:snake _ $variant:snake>]<T>(
61                    $($($field : $ftype),*)?
62                    $($($tname : $ttype),*)?
63                ) -> core::result::Result<T, $name> {
64                    core::result::Result::Err($name::$variant $( { $($field),* } )? $( ( $( $tname ),* ) )?)
65                }
66            )*
67        }
68    };
69}
70
71// =========================================================================
72// Module-level error enums
73// =========================================================================
74
75rssn_error! {
76    /// AST / projection-layer failures.
77    #[derive(Debug, Clone, PartialEq, Eq)]
78    pub enum AstError {
79        /// The AST projection ran out of inline buffer space.
80        ProjectionOverflow,
81        /// A relative pointer overflowed its underlying integer width.
82        RelPtrOverflow,
83        /// Conversion between DAG and AST encountered an unknown symbol kind.
84        UnknownSymbolKind,
85        /// A cycle was detected while converting; the input is not a DAG.
86        CycleDetected,
87    }
88}
89
90rssn_error! {
91    /// DAG arena / dedup / interning failures.
92    #[derive(Debug, Clone, PartialEq, Eq)]
93    pub enum DagError {
94        /// The arena hit its maximum addressable capacity (`u32::MAX - 1`).
95        ArenaFull,
96        /// A `DagNodeId` referred to a slot outside the current arena.
97        DanglingId,
98        /// Attempted to dereference the null sentinel.
99        NullSentinel,
100        /// A node's child count exceeded the configured arity limit.
101        ArityOverflow,
102    }
103}
104
105rssn_error! {
106    /// JIT compilation pipeline failures.
107    #[derive(Debug, Clone, PartialEq, Eq)]
108    pub enum JitError {
109        /// Cranelift rejected the emitted IR (verifier or backend failure).
110        VerifierRejected,
111        /// A custom function symbol has no registered implementation.
112        UnknownFunction,
113        /// The IR builder hit its instruction budget.
114        BudgetExhausted,
115        /// Codegen visited a node whose shape contradicts its `SymbolKind`.
116        MalformedNode,
117        /// The Cranelift backend or native target failed to initialize.
118        InitFailed,
119    }
120}
121
122rssn_error! {
123    /// Parallel evaluation / chunking failures.
124    #[derive(Debug, Clone, PartialEq, Eq)]
125    pub enum ParallelError {
126        /// The expression has no commutative split point.
127        NotSplittable,
128        /// A worker fiber panicked before reporting a result.
129        WorkerLost,
130        /// A required variable binding was missing.
131        MissingVariable,
132    }
133}
134
135rssn_error! {
136    /// Parser surface failures (token / grammar level).
137    #[derive(Debug, Clone, PartialEq, Eq)]
138    pub enum ParserError {
139        /// Reached end of input while a production was still pending.
140        UnexpectedEof,
141        /// Saw a token that no production could accept.
142        UnexpectedToken,
143        /// Parenthesis depth exceeded `MAX_PAREN_DEPTH`.
144        ParenDepthExceeded,
145        /// A numeric literal could not be parsed as `f64`.
146        InvalidNumber,
147    }
148}
149
150rssn_error! {
151    /// Storage / cache / serialization failures.
152    #[derive(Debug, Clone, PartialEq, Eq)]
153    pub enum StorageError {
154        /// Underlying IO operation failed (open, read, write, mmap).
155        Io,
156        /// `bincode` decoded a structurally invalid arena header.
157        CorruptHeader,
158        /// The on-disk version tag did not match the runtime version.
159        VersionMismatch,
160        /// Mmap requested but unsupported on this platform.
161        MmapUnsupported,
162    }
163}
164
165rssn_error! {
166    /// FFI boundary failures.
167    #[derive(Debug, Clone, PartialEq, Eq)]
168    pub enum FfiError {
169        /// A `*const T` or `*mut T` argument was null where required.
170        NullArgument,
171        /// A C string was not valid UTF-8.
172        InvalidUtf8,
173        /// The handle's tag/generation does not match a live object.
174        StaleHandle,
175        /// The fiber runtime is not initialized.
176        RuntimeUninitialized,
177    }
178}
179
180// =========================================================================
181// Display impls (kept hand-written; the macro stays narrow on purpose)
182// =========================================================================
183
184macro_rules! impl_display {
185    ($($t:ty),* $(,)?) => {
186        $(
187            impl fmt::Display for $t {
188                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189                    fmt::Debug::fmt(self, f)
190                }
191            }
192        )*
193    };
194}
195
196impl_display!(
197    AstError,
198    DagError,
199    JitError,
200    ParallelError,
201    ParserError,
202    StorageError,
203    FfiError,
204);
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn cold_constructor_returns_err_variant() {
212        let r: Result<(), DagError> = cold_dag_error_arena_full();
213        assert_eq!(r, Err(DagError::ArenaFull));
214    }
215
216    #[test]
217    fn cold_constructor_for_every_module() {
218        let _: Result<u8, AstError> = cold_ast_error_projection_overflow();
219        let _: Result<u8, JitError> = cold_jit_error_verifier_rejected();
220        let _: Result<u8, ParallelError> = cold_parallel_error_not_splittable();
221        let _: Result<u8, ParserError> = cold_parser_error_unexpected_eof();
222        let _: Result<u8, StorageError> = cold_storage_error_io();
223        let _: Result<u8, FfiError> = cold_ffi_error_null_argument();
224    }
225
226    #[test]
227    fn display_uses_debug() {
228        assert_eq!(format!("{}", DagError::ArenaFull), "ArenaFull");
229    }
230}