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}