1use crate::source::SourceSpan;
2use std::error::Error as StdError;
3use std::fmt;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum ErrorKind {
7 Syntax,
8 Type,
9 Reference,
10 Range,
11 JsInternal,
14 Internal,
15 Io,
16 Unsupported,
24}
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
29#[repr(u8)]
30pub enum NativeErrorKind {
31 Eval,
32 Range,
33 Reference,
34 Syntax,
35 Type,
36 Uri,
37 Internal,
38 Aggregate,
39}
40
41#[derive(Clone, Eq, PartialEq)]
45pub struct NativeErrorMessage {
46 bytes: [u8; 256],
47 len: usize,
48}
49
50impl fmt::Debug for NativeErrorMessage {
51 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52 formatter
53 .debug_tuple("NativeErrorMessage")
54 .field(&&self.bytes[..self.len])
55 .finish()
56 }
57}
58
59impl Default for NativeErrorMessage {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl NativeErrorMessage {
66 #[must_use]
67 pub fn new() -> Self {
68 Self {
69 bytes: [0; 256],
70 len: 0,
71 }
72 }
73
74 #[must_use]
75 pub fn from_utf8(value: &str) -> Self {
76 let mut message = Self::new();
77 message.push_bytes(value.bytes());
78 message
79 }
80
81 pub fn push_bytes(&mut self, bytes: impl IntoIterator<Item = u8>) {
82 for byte in bytes {
83 if self.len == self.bytes.len() - 1 {
84 break;
85 }
86 self.bytes[self.len] = byte;
87 self.len += 1;
88 }
89 }
90
91 pub fn push_c_string_bytes(&mut self, bytes: impl IntoIterator<Item = u8>) {
92 for byte in bytes {
93 if byte == 0 || self.len == self.bytes.len() - 1 {
94 break;
95 }
96 self.bytes[self.len] = byte;
97 self.len += 1;
98 }
99 }
100
101 pub fn push_utf8(&mut self, value: &str) {
102 self.push_bytes(value.bytes());
103 }
104
105 #[must_use]
106 pub fn visible_bytes(&self) -> &[u8] {
107 let visible_len = self.bytes[..self.len]
108 .iter()
109 .position(|byte| *byte == 0)
110 .unwrap_or(self.len);
111 &self.bytes[..visible_len]
112 }
113}
114
115impl NativeErrorKind {
116 pub const ALL: [Self; 8] = [
117 Self::Eval,
118 Self::Range,
119 Self::Reference,
120 Self::Syntax,
121 Self::Type,
122 Self::Uri,
123 Self::Internal,
124 Self::Aggregate,
125 ];
126 pub const COUNT: usize = Self::ALL.len();
127
128 #[must_use]
129 pub const fn index(self) -> usize {
130 self as usize
131 }
132
133 #[must_use]
134 pub const fn name(self) -> &'static str {
135 match self {
136 Self::Eval => "EvalError",
137 Self::Range => "RangeError",
138 Self::Reference => "ReferenceError",
139 Self::Syntax => "SyntaxError",
140 Self::Type => "TypeError",
141 Self::Uri => "URIError",
142 Self::Internal => "InternalError",
143 Self::Aggregate => "AggregateError",
144 }
145 }
146
147 #[must_use]
152 pub const fn from_javascript_error(kind: ErrorKind) -> Option<Self> {
153 match kind {
154 ErrorKind::Syntax => Some(Self::Syntax),
155 ErrorKind::Type => Some(Self::Type),
156 ErrorKind::Reference => Some(Self::Reference),
157 ErrorKind::Range => Some(Self::Range),
158 ErrorKind::JsInternal => Some(Self::Internal),
159 ErrorKind::Unsupported | ErrorKind::Internal | ErrorKind::Io => None,
160 }
161 }
162}
163
164#[derive(Clone, Eq, PartialEq)]
169struct ErrorData {
170 kind: ErrorKind,
171 message: String,
172 native_message: Option<Box<NativeErrorMessage>>,
173 span: Option<SourceSpan>,
174}
175
176#[derive(Clone, Eq, PartialEq)]
179pub struct Error(Box<ErrorData>);
180
181impl fmt::Debug for Error {
182 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
183 formatter
184 .debug_struct("Error")
185 .field("kind", &self.0.kind)
186 .field("message", &self.0.message)
187 .field("native_message", &self.0.native_message)
188 .field("span", &self.0.span)
189 .finish()
190 }
191}
192
193impl Error {
194 #[must_use]
195 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
196 Self(Box::new(ErrorData {
197 kind,
198 message: message.into(),
199 native_message: None,
200 span: None,
201 }))
202 }
203
204 #[must_use]
205 pub fn from_native_message(kind: ErrorKind, native_message: NativeErrorMessage) -> Self {
206 let message = native_message.to_utf8_lossy();
207 Self(Box::new(ErrorData {
208 kind,
209 message,
210 native_message: Some(Box::new(native_message)),
211 span: None,
212 }))
213 }
214
215 #[must_use]
216 pub fn syntax(message: impl Into<String>, span: SourceSpan) -> Self {
217 Self::new(ErrorKind::Syntax, message).with_span(span)
218 }
219
220 #[must_use]
221 pub fn unsupported(message: impl Into<String>, span: SourceSpan) -> Self {
222 Self::new(ErrorKind::Unsupported, message).with_span(span)
223 }
224
225 #[must_use]
226 pub fn internal(message: impl Into<String>) -> Self {
227 Self::new(ErrorKind::Internal, message)
228 }
229
230 #[must_use]
231 pub const fn kind(&self) -> ErrorKind {
232 self.0.kind
233 }
234
235 #[must_use]
236 pub fn message(&self) -> &str {
237 &self.0.message
238 }
239
240 #[must_use]
241 pub fn native_message(&self) -> Option<&NativeErrorMessage> {
242 self.0.native_message.as_deref()
243 }
244
245 #[must_use]
246 pub const fn span(&self) -> Option<SourceSpan> {
247 self.0.span
248 }
249
250 #[must_use]
251 pub const fn with_span(mut self, span: SourceSpan) -> Self {
252 self.0.span = Some(span);
253 self
254 }
255}
256
257impl fmt::Display for Error {
258 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
259 if let Some(span) = self.0.span {
260 if self.0.kind == ErrorKind::JsInternal {
261 write!(
262 formatter,
263 "InternalError at {}:{}: {}",
264 span.start.line, span.start.column, self.0.message
265 )
266 } else {
267 write!(
268 formatter,
269 "{:?}Error at {}:{}: {}",
270 self.0.kind, span.start.line, span.start.column, self.0.message
271 )
272 }
273 } else {
274 match self.0.kind {
275 ErrorKind::JsInternal => write!(formatter, "InternalError: {}", self.0.message),
276 _ => write!(formatter, "{:?}Error: {}", self.0.kind, self.0.message),
277 }
278 }
279 }
280}
281
282impl StdError for Error {}
283
284#[cfg(test)]
285mod tests {
286 use super::{Error, ErrorKind, NativeErrorMessage};
287 use crate::engine::api::core::RuntimeError;
288 use crate::source::{SourceLocation, SourceSpan};
289
290 #[test]
291 fn error_equality_and_debug_preserve_exact_native_payload() {
292 let mut raw = NativeErrorMessage::new();
293 raw.push_bytes([0x80, b'A']);
294 let exact = Error::from_native_message(ErrorKind::Type, raw);
295 let public = Error::new(ErrorKind::Type, "\u{fffd}");
296
297 assert_eq!(exact.message(), "\u{fffd}");
298 assert_ne!(exact, public);
299 assert_ne!(format!("{exact:?}"), format!("{public:?}"));
300 assert_eq!(
301 format!("{exact:?}"),
302 "Error { kind: Type, message: \"�\", native_message: Some(NativeErrorMessage([128, 65])), span: None }"
303 );
304 assert_eq!(format!("{exact}"), "TypeError: �");
305 assert_eq!(exact.clone(), exact);
306 assert_eq!(
307 exact.native_message().unwrap().visible_bytes(),
308 [0x80, b'A']
309 );
310 assert!(public.native_message().is_none());
311 }
312
313 #[test]
314 fn error_channel_stays_one_register() {
315 use std::mem::size_of;
316 let word = size_of::<usize>();
317 assert_eq!(size_of::<Error>(), word);
318 assert_eq!(size_of::<Result<(), Error>>(), word);
319 assert!(size_of::<Result<bool, Error>>() <= 2 * word);
320 assert!(size_of::<Result<&u8, Error>>() <= 2 * word);
321 }
322
323 #[test]
324 fn error_span_and_debug_survive_runtime_wrapping_and_clone() {
325 let span = SourceSpan::new(
326 SourceLocation::new(12, 3, 5),
327 SourceLocation::new(18, 3, 11),
328 );
329 let original = Error::syntax("unexpected token", span);
330 assert_eq!(original.kind(), ErrorKind::Syntax);
331 assert_eq!(original.message(), "unexpected token");
332 assert_eq!(original.span(), Some(span));
333 assert_eq!(
334 format!("{original}"),
335 "SyntaxError at 3:5: unexpected token"
336 );
337 assert_eq!(
338 format!("{original:?}"),
339 "Error { kind: Syntax, message: \"unexpected token\", native_message: None, span: Some(SourceSpan { start: SourceLocation { byte_offset: 12, line: 3, column: 5 }, end: SourceLocation { byte_offset: 18, line: 3, column: 11 } }) }"
340 );
341
342 let wrapped = RuntimeError::from(original.clone());
343 assert_eq!(wrapped, RuntimeError::Compile(original.clone()));
344 assert_eq!(format!("{wrapped}"), format!("{original}"));
345 assert_eq!(format!("{wrapped:?}"), format!("Compile({original:?})"));
346 assert_eq!(original.clone(), original);
347
348 let relocated = original.clone().with_span(SourceSpan::default());
349 assert_eq!(relocated.span(), Some(SourceSpan::default()));
350 assert_ne!(relocated, original);
351 assert_eq!(original.span(), Some(span));
352 }
353
354 #[test]
355 fn with_span_remains_usable_from_a_const_function() {
356 const fn attach_span(error: Error, span: SourceSpan) -> Error {
357 error.with_span(span)
358 }
359
360 let span = SourceSpan::new(SourceLocation::new(2, 1, 3), SourceLocation::new(5, 1, 6));
361 let error = attach_span(Error::new(ErrorKind::Unsupported, "frontier"), span);
362 assert_eq!(error.kind(), ErrorKind::Unsupported);
363 assert_eq!(error.message(), "frontier");
364 assert_eq!(error.span(), Some(span));
365 }
366
367 #[test]
368 fn native_error_payload_remains_exact_after_clone_and_span_change() {
369 let mut raw = NativeErrorMessage::new();
370 raw.push_bytes([b'A', 0, 0x80]);
371 let error = Error::from_native_message(ErrorKind::JsInternal, raw);
372 let span = SourceSpan::new(SourceLocation::new(4, 2, 1), SourceLocation::new(7, 2, 4));
373 let error = error.with_span(span);
374 let cloned = error.clone();
375
376 assert_eq!(cloned, error);
377 assert_eq!(cloned.kind(), ErrorKind::JsInternal);
378 assert_eq!(cloned.span(), Some(span));
379 assert_eq!(cloned.message(), "A");
380 assert_eq!(cloned.native_message().unwrap().visible_bytes(), b"A");
381 assert_eq!(format!("{cloned}"), "InternalError at 2:1: A");
382 assert!(format!("{cloned:?}").contains("NativeErrorMessage([65, 0, 128])"));
383 }
384}