Skip to main content

geam_core/runtime/error/
panic.rs

1use crate::plan::{PanicSite, SourceContext, SourceSpan};
2use crate::runtime::Value;
3use ecow::EcoString;
4use miette::NamedSource;
5use num_bigint::BigInt;
6use std::fmt;
7
8#[derive(Debug, Clone)]
9pub struct Panic {
10    kind: PanicKind,
11    message: PanicMessage,
12    site: PanicSite,
13    source: Option<Box<NamedSource<String>>>,
14    details: Option<Box<PanicDetails>>,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum PanicKind {
19    Panic,
20    Todo,
21    Assert,
22    LetAssert,
23    BitArraySegment,
24    EmptyFunction,
25    EmptyBlock,
26    IncompleteUse,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum PanicMessage {
31    Default,
32    Explicit(EcoString),
33}
34
35#[derive(Debug, Clone, PartialEq)]
36pub enum PanicDetails {
37    LetAssert {
38        value: Value,
39        pattern_span: SourceSpan,
40    },
41    BitArraySegment {
42        reason: BitArraySegmentPanicReason,
43    },
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum BitArraySegmentPanicReason {
48    InvalidFloatSize { bit_size: BigInt },
49    InsufficientBits { requested: usize, available: usize },
50    SizeOutOfRange { bit_size: BigInt },
51}
52
53impl Panic {
54    pub(crate) fn new(
55        kind: PanicKind,
56        message: PanicMessage,
57        site: PanicSite,
58        source_context: Option<&SourceContext>,
59        details: Option<PanicDetails>,
60    ) -> Self {
61        Self {
62            kind,
63            message,
64            site,
65            source: source_context
66                .map(SourceContext::named_source)
67                .map(Box::new),
68            details: details.map(Box::new),
69        }
70    }
71
72    pub fn kind(&self) -> PanicKind {
73        self.kind
74    }
75
76    pub fn message(&self) -> &PanicMessage {
77        &self.message
78    }
79
80    pub fn site(&self) -> &PanicSite {
81        &self.site
82    }
83
84    pub fn details(&self) -> Option<&PanicDetails> {
85        self.details.as_deref()
86    }
87
88    pub(in crate::runtime::error) fn source(&self) -> Option<&NamedSource<String>> {
89        self.source.as_deref()
90    }
91
92    pub(in crate::runtime::error) fn message_text(&self) -> std::borrow::Cow<'_, str> {
93        self.message.text(self.kind)
94    }
95
96    pub(in crate::runtime::error) fn primary_label(&self) -> String {
97        format!(
98            "{} in {}.{}",
99            self.kind.label(),
100            self.site.module(),
101            self.site.function(),
102        )
103    }
104}
105
106impl PartialEq for Panic {
107    fn eq(&self, other: &Self) -> bool {
108        self.kind == other.kind
109            && self.message == other.message
110            && self.site == other.site
111            && self.details() == other.details()
112            && named_source_eq(self.source.as_deref(), other.source.as_deref())
113    }
114}
115
116fn named_source_eq(
117    left: Option<&NamedSource<String>>,
118    right: Option<&NamedSource<String>>,
119) -> bool {
120    match (left, right) {
121        (Some(left), Some(right)) => left.name() == right.name() && left.inner() == right.inner(),
122        (None, None) => true,
123        (Some(_), None) | (None, Some(_)) => false,
124    }
125}
126
127impl fmt::Display for Panic {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        write!(f, "{}: {}", self.kind.code(), self.message_text())
130    }
131}
132
133impl std::error::Error for Panic {}
134
135impl PanicKind {
136    pub(in crate::runtime::error) fn code(&self) -> &'static str {
137        match self {
138            Self::Panic => "panic",
139            Self::Todo => "todo",
140            Self::Assert => "assert",
141            Self::LetAssert => "let_assert",
142            Self::BitArraySegment => "bit_array_segment",
143            Self::EmptyFunction => "empty_function",
144            Self::EmptyBlock => "empty_block",
145            Self::IncompleteUse => "incomplete_use",
146        }
147    }
148
149    fn label(&self) -> &'static str {
150        match self {
151            Self::Panic => "panic",
152            Self::Todo => "todo",
153            Self::Assert => "assert",
154            Self::LetAssert => "let assert",
155            Self::BitArraySegment => "bit array segment",
156            Self::EmptyFunction => "empty function",
157            Self::EmptyBlock => "empty block",
158            Self::IncompleteUse => "incomplete use",
159        }
160    }
161
162    fn default_message(&self) -> &'static str {
163        match self {
164            Self::Panic => "`panic` expression evaluated.",
165            Self::Todo => "`todo` expression evaluated. This code has not yet been implemented.",
166            Self::Assert => "Assertion failed.",
167            Self::LetAssert => "Pattern match failed, no pattern matched the value.",
168            Self::BitArraySegment => "BitArray segment construction failed.",
169            Self::EmptyFunction => "Function body is empty.",
170            Self::EmptyBlock => "Block is empty.",
171            Self::IncompleteUse => "Use callback is incomplete.",
172        }
173    }
174}
175
176impl PanicMessage {
177    pub(crate) fn from_optional_explicit(message: Option<EcoString>) -> Self {
178        match message {
179            Some(message) => Self::Explicit(message),
180            None => Self::Default,
181        }
182    }
183
184    fn text(&self, kind: PanicKind) -> std::borrow::Cow<'_, str> {
185        match self {
186            Self::Explicit(message) => std::borrow::Cow::Borrowed(message.as_str()),
187            Self::Default => std::borrow::Cow::Borrowed(kind.default_message()),
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::{BitArraySegmentPanicReason, Panic, PanicDetails, PanicKind, PanicMessage};
195    use crate::plan::{PanicSite, SourceContext, SourceSpan, ValueType};
196    use crate::runtime::ExecutionError;
197    use crate::runtime::Value;
198
199    #[test]
200    fn panic_display_uses_kind_and_default_or_explicit_message() {
201        for (error, expected) in [
202            (
203                ExecutionError::source_panic(None, PanicKind::Panic, None, PanicSite::unknown()),
204                "panic: `panic` expression evaluated.",
205            ),
206            (
207                ExecutionError::source_panic(
208                    None,
209                    PanicKind::Panic,
210                    Some("boom".into()),
211                    PanicSite::unknown(),
212                ),
213                "panic: boom",
214            ),
215            (
216                ExecutionError::source_panic(None, PanicKind::Todo, None, PanicSite::unknown()),
217                "todo: `todo` expression evaluated. This code has not yet been implemented.",
218            ),
219            (
220                ExecutionError::source_panic(None, PanicKind::Assert, None, PanicSite::unknown()),
221                "assert: Assertion failed.",
222            ),
223            (
224                ExecutionError::source_panic(
225                    None,
226                    PanicKind::LetAssert,
227                    None,
228                    PanicSite::unknown(),
229                ),
230                "let_assert: Pattern match failed, no pattern matched the value.",
231            ),
232            (
233                ExecutionError::bit_array_segment_panic(
234                    None,
235                    BitArraySegmentPanicReason::InvalidFloatSize {
236                        bit_size: 24.into(),
237                    },
238                    PanicSite::unknown(),
239                ),
240                "bit_array_segment: BitArray segment construction failed.",
241            ),
242            (
243                ExecutionError::source_panic(
244                    None,
245                    PanicKind::EmptyFunction,
246                    None,
247                    PanicSite::unknown(),
248                ),
249                "empty_function: Function body is empty.",
250            ),
251            (
252                ExecutionError::source_panic(
253                    None,
254                    PanicKind::EmptyBlock,
255                    None,
256                    PanicSite::unknown(),
257                ),
258                "empty_block: Block is empty.",
259            ),
260            (
261                ExecutionError::source_panic(
262                    None,
263                    PanicKind::IncompleteUse,
264                    None,
265                    PanicSite::unknown(),
266                ),
267                "incomplete_use: Use callback is incomplete.",
268            ),
269        ] {
270            assert_eq!(error.to_string(), expected);
271        }
272    }
273
274    #[test]
275    fn panic_accessors_preserve_kind_message_site_and_details() {
276        let site = PanicSite::new("main".into(), "main".into(), SourceSpan::new(12, 18));
277        let details = PanicDetails::LetAssert {
278            value: Value::List(crate::runtime::ListValue::empty(ValueType::Int)),
279            pattern_span: SourceSpan::new(23, 32),
280        };
281        let panic = Panic::new(
282            PanicKind::LetAssert,
283            PanicMessage::Explicit("not empty".into()),
284            site.clone(),
285            None,
286            Some(details.clone()),
287        );
288
289        assert_eq!(panic.kind(), PanicKind::LetAssert);
290        assert_eq!(panic.message(), &PanicMessage::Explicit("not empty".into()),);
291        assert_eq!(panic.site(), &site);
292        assert_eq!(panic.details(), Some(&details));
293    }
294
295    #[test]
296    fn panic_equality_includes_source_context() {
297        let source = SourceContext::new("main.gleam", "pub fn main() { panic }");
298        let same_source = SourceContext::new("main.gleam", "pub fn main() { panic }");
299        let different_path = SourceContext::new("other.gleam", "pub fn main() { panic }");
300        let different_source = SourceContext::new("main.gleam", "pub fn main() { todo }");
301        let site = PanicSite::new("main".into(), "main".into(), SourceSpan::new(16, 21));
302
303        assert_eq!(
304            ExecutionError::source_panic(Some(&source), PanicKind::Panic, None, site.clone()),
305            ExecutionError::source_panic(Some(&same_source), PanicKind::Panic, None, site.clone()),
306        );
307        assert_ne!(
308            ExecutionError::source_panic(Some(&source), PanicKind::Panic, None, site.clone()),
309            ExecutionError::source_panic(
310                Some(&different_path),
311                PanicKind::Panic,
312                None,
313                site.clone()
314            ),
315        );
316        assert_ne!(
317            ExecutionError::source_panic(Some(&source), PanicKind::Panic, None, site.clone()),
318            ExecutionError::source_panic(Some(&different_source), PanicKind::Panic, None, site),
319        );
320        assert_ne!(
321            ExecutionError::source_panic(
322                Some(&source),
323                PanicKind::Panic,
324                None,
325                PanicSite::unknown(),
326            ),
327            ExecutionError::source_panic(None, PanicKind::Panic, None, PanicSite::unknown()),
328        );
329        assert_eq!(
330            ExecutionError::source_panic(None, PanicKind::Todo, None, PanicSite::unknown()),
331            ExecutionError::source_panic(None, PanicKind::Todo, None, PanicSite::unknown()),
332        );
333    }
334}