arithmetic_eval/
error.rs

1//! Evaluation errors.
2
3pub use arithmetic_parser::UnsupportedType;
4
5use derive_more::Display;
6use hashbrown::HashSet;
7
8use core::fmt;
9
10use crate::{
11    alloc::{format, vec, Box, String, ToOwned, ToString, Vec},
12    fns::FromValueError,
13    ModuleId, Value,
14};
15use arithmetic_parser::{
16    BinaryOp, CodeFragment, LocatedSpan, LvalueLen, MaybeSpanned, Op, StripCode, UnaryOp,
17};
18
19/// Arithmetic errors raised by [`Arithmetic`] operations on primitive values.
20///
21/// [`Arithmetic`]: crate::arith::Arithmetic
22#[derive(Debug)]
23#[non_exhaustive]
24pub enum ArithmeticError {
25    /// Integer overflow or underflow.
26    IntegerOverflow,
27    /// Division by zero.
28    DivisionByZero,
29    /// Exponent of [`Arithmetic::pow()`] cannot be converted to `usize`, for example because
30    /// it is too large or negative.
31    ///
32    /// [`Arithmetic::pow()`]: crate::arith::Arithmetic::pow()
33    InvalidExponent,
34    /// Integer used as a denominator in [`Arithmetic::div()`] has no multiplicative inverse.
35    ///
36    /// [`Arithmetic::div()`]: crate::arith::Arithmetic::div()
37    NoInverse,
38    /// Invalid operation with a custom error message.
39    ///
40    /// This error may be used by [`Arithmetic`](crate::arith::Arithmetic) implementations
41    /// as a catch-all fallback.
42    InvalidOp(anyhow::Error),
43}
44
45impl ArithmeticError {
46    /// Creates a new invalid operation error with the specified `message`.
47    pub fn invalid_op(message: impl Into<String>) -> Self {
48        Self::InvalidOp(anyhow::Error::msg(message.into()))
49    }
50}
51
52impl fmt::Display for ArithmeticError {
53    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Self::IntegerOverflow => formatter.write_str("Integer overflow or underflow"),
56            Self::DivisionByZero => formatter.write_str("Integer division by zero"),
57            Self::InvalidExponent => formatter.write_str("Exponent is too large or negative"),
58            Self::NoInverse => formatter.write_str("Integer has no multiplicative inverse"),
59            Self::InvalidOp(e) => write!(formatter, "Invalid operation: {}", e),
60        }
61    }
62}
63
64#[cfg(feature = "std")]
65impl std::error::Error for ArithmeticError {
66    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
67        match self {
68            Self::InvalidOp(e) => Some(e.as_ref()),
69            _ => None,
70        }
71    }
72}
73
74/// Context for [`ErrorKind::TupleLenMismatch`].
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum TupleLenMismatchContext {
77    /// An error has occurred when evaluating a binary operation.
78    BinaryOp(BinaryOp),
79    /// An error has occurred during assignment.
80    Assignment,
81}
82
83impl fmt::Display for TupleLenMismatchContext {
84    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Self::BinaryOp(op) => write!(formatter, "{}", op),
87            Self::Assignment => formatter.write_str("assignment"),
88        }
89    }
90}
91
92/// Context for [`ErrorKind::RepeatedAssignment`].
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum RepeatedAssignmentContext {
95    /// A duplicated variable is in function args.
96    FnArgs,
97    /// A duplicated variable is in an lvalue of an assignment.
98    Assignment,
99}
100
101impl fmt::Display for RepeatedAssignmentContext {
102    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
103        formatter.write_str(match self {
104            Self::FnArgs => "function args",
105            Self::Assignment => "assignment",
106        })
107    }
108}
109
110/// Kinds of errors that can occur when compiling or interpreting expressions and statements.
111#[derive(Debug, Display)]
112#[non_exhaustive]
113pub enum ErrorKind {
114    /// Mismatch between length of tuples in a binary operation or assignment.
115    #[display(
116        fmt = "Mismatch between length of tuples in {}: LHS has {} element(s), whereas RHS has {}",
117        context,
118        lhs,
119        rhs
120    )]
121    TupleLenMismatch {
122        /// Length of a tuple on the left-hand side.
123        lhs: LvalueLen,
124        /// Length of a tuple on the right-hand side.
125        rhs: usize,
126        /// Context in which the error has occurred.
127        context: TupleLenMismatchContext,
128    },
129
130    /// Field set differs between LHS and RHS, which are both objects.
131    #[display(
132        fmt = "Cannot perform {} on objects: LHS has fields {:?}, whereas RHS has fields {:?}",
133        op,
134        lhs_fields,
135        rhs_fields
136    )]
137    FieldsMismatch {
138        /// Fields in LHS.
139        lhs_fields: HashSet<String>,
140        /// Fields in RHS.
141        rhs_fields: HashSet<String>,
142        /// Operation being performed.
143        op: BinaryOp,
144    },
145
146    /// Mismatch between the number of arguments in the function definition and its call.
147    #[display(
148        fmt = "Mismatch between the number of arguments in the function definition and its call: \
149            definition requires {} arg(s), call has {}",
150        def,
151        call
152    )]
153    ArgsLenMismatch {
154        /// Number of args at the function definition.
155        def: LvalueLen,
156        /// Number of args at the function call.
157        call: usize,
158    },
159
160    /// Cannot destructure a non-tuple variable.
161    #[display(fmt = "Cannot destructure a non-tuple variable")]
162    CannotDestructure,
163
164    /// Repeated assignment to the same variable in function args or tuple destructuring.
165    #[display(fmt = "Repeated assignment to the same variable in {}", context)]
166    RepeatedAssignment {
167        /// Context in which the error has occurred.
168        context: RepeatedAssignmentContext,
169    },
170
171    /// Repeated field in object initialization (e.g., `#{ x: 1, x: 2 }`) or destructure
172    /// (e.g., `{ x, x }`).
173    #[display(fmt = "Repeated object field")]
174    RepeatedField,
175
176    /// Variable with the enclosed name is not defined.
177    #[display(fmt = "Variable `{}` is not defined", _0)]
178    Undefined(String),
179
180    /// Field name is invalid.
181    #[display(fmt = "`{}` is not a valid field name", _0)]
182    InvalidFieldName(String),
183
184    /// Value is not callable (i.e., it is not a function).
185    #[display(fmt = "Value is not callable")]
186    CannotCall,
187    /// Value cannot be indexed (i.e., it is not a tuple).
188    #[display(fmt = "Value cannot be indexed")]
189    CannotIndex,
190    /// A field cannot be accessed for the value (i.e., it is not an object).
191    #[display(fmt = "Fields cannot be accessed for the object")]
192    CannotAccessFields,
193
194    /// Index is out of bounds for the indexed tuple.
195    #[display(
196        fmt = "Attempting to get element {} from tuple with length {}",
197        index,
198        len
199    )]
200    IndexOutOfBounds {
201        /// Index.
202        index: usize,
203        /// Actual tuple length.
204        len: usize,
205    },
206    /// Object does not have a required field.
207    #[display(fmt = "Object does not have field {}", field)]
208    NoField {
209        /// Missing field.
210        field: String,
211        /// Available fields in the object in no particular order.
212        available_fields: Vec<String>,
213    },
214
215    /// Generic error during execution of a native function.
216    #[display(fmt = "Failed executing native function: {}", _0)]
217    NativeCall(String),
218
219    /// Error while converting arguments for [`FnWrapper`](crate::fns::FnWrapper).
220    #[display(
221        fmt = "Failed converting arguments for native function wrapper: {}",
222        _0
223    )]
224    Wrapper(FromValueError),
225
226    /// Unexpected operand type for the specified operation.
227    #[display(fmt = "Unexpected operand type for {}", op)]
228    UnexpectedOperand {
229        /// Operation which failed.
230        op: Op,
231    },
232
233    /// Value cannot be compared to other values. Only primitive values can be compared; other value types
234    /// cannot.
235    #[display(fmt = "Value cannot be compared to other values")]
236    CannotCompare,
237
238    /// Construct not supported by the interpreter.
239    #[display(fmt = "Unsupported {}", _0)]
240    Unsupported(UnsupportedType),
241
242    /// [`Arithmetic`](crate::arith::Arithmetic) error, such as division by zero.
243    #[display(fmt = "Arithmetic error: {}", _0)]
244    Arithmetic(ArithmeticError),
245}
246
247impl ErrorKind {
248    /// Creates a native error.
249    pub fn native(message: impl Into<String>) -> Self {
250        Self::NativeCall(message.into())
251    }
252
253    /// Creates an error for an lvalue type not supported by the interpreter.
254    pub fn unsupported<T: Into<UnsupportedType>>(ty: T) -> Self {
255        Self::Unsupported(ty.into())
256    }
257
258    /// Returned shortened error cause.
259    pub fn to_short_string(&self) -> String {
260        match self {
261            Self::TupleLenMismatch { context, .. } => {
262                format!("Mismatch between length of tuples in {}", context)
263            }
264            Self::FieldsMismatch { op, .. } => {
265                format!("Mismatch between object shapes during {}", op)
266            }
267            Self::ArgsLenMismatch { .. } => {
268                "Mismatch between the number of arguments in the function definition and its call"
269                    .to_owned()
270            }
271            Self::CannotDestructure => "Cannot destructure a non-tuple variable".to_owned(),
272            Self::RepeatedAssignment { context } => {
273                format!("Repeated assignment to the same variable in {}", context)
274            }
275            Self::RepeatedField => "Repeated object field".to_owned(),
276            Self::Undefined(name) => format!("Variable `{}` is not defined", name),
277            Self::InvalidFieldName(name) => format!("`{}` is not a valid field name", name),
278            Self::CannotCall => "Value is not callable".to_owned(),
279            Self::CannotIndex => "Value cannot be indexed".to_owned(),
280            Self::CannotAccessFields => "Value has no fields".to_owned(),
281            Self::IndexOutOfBounds { len, .. } => {
282                format!("Index out of bounds for tuple with length {}", len)
283            }
284            Self::NoField { field, .. } => format!("Object does not have field {}", field),
285            Self::NativeCall(message) => message.clone(),
286            Self::Wrapper(err) => err.to_string(),
287            Self::UnexpectedOperand { op } => format!("Unexpected operand type for {}", op),
288            Self::CannotCompare => "Value is not comparable".to_owned(),
289            Self::Unsupported(_) => "Grammar construct not supported".to_owned(),
290            Self::Arithmetic(_) => "Arithmetic error".to_owned(),
291        }
292    }
293
294    /// Returns a short description of the spanned information.
295    pub fn main_span_info(&self) -> String {
296        match self {
297            Self::TupleLenMismatch { context, lhs, .. } => {
298                format!("LHS of {} with {} element(s)", context, lhs)
299            }
300            Self::FieldsMismatch { lhs_fields, .. } => {
301                format!("LHS with fields {:?}", lhs_fields)
302            }
303            Self::ArgsLenMismatch { call, .. } => format!("Called with {} arg(s) here", call),
304            Self::CannotDestructure => "Failed destructuring".to_owned(),
305            Self::RepeatedAssignment { .. } => "Re-assigned variable".to_owned(),
306            Self::RepeatedField => "Repeated object field".to_owned(),
307            Self::Undefined(_) => "Undefined variable occurrence".to_owned(),
308            Self::InvalidFieldName(_) => "Invalid field".to_owned(),
309            Self::CannotIndex | Self::IndexOutOfBounds { .. } => "Indexing operation".to_owned(),
310            Self::CannotAccessFields | Self::NoField { .. } => "Field access".to_owned(),
311            Self::CannotCall | Self::NativeCall(_) | Self::Wrapper(_) => "Failed call".to_owned(),
312            Self::UnexpectedOperand { .. } => "Operand of wrong type".to_owned(),
313            Self::CannotCompare => "Cannot be compared".to_owned(),
314            Self::Unsupported(ty) => format!("Unsupported {}", ty),
315            Self::Arithmetic(e) => e.to_string(),
316        }
317    }
318
319    /// Returns information helping fix the error.
320    pub fn help(&self) -> Option<String> {
321        Some(match self {
322            Self::TupleLenMismatch { context, .. } => format!(
323                "If both args of {} are tuples, the number of elements in them must agree",
324                context
325            ),
326            Self::FieldsMismatch { op, .. } => format!(
327                "If both args of {} are objects, their field names must be the same",
328                op
329            ),
330            Self::CannotDestructure => "Only tuples can be destructured".to_owned(),
331            Self::RepeatedAssignment { context } => format!(
332                "In {}, all assigned variables must have different names",
333                context
334            ),
335            Self::RepeatedField => "Field names in objects must be unique".to_owned(),
336            Self::InvalidFieldName(_) => "Field names must be `usize`s or identifiers".to_owned(),
337            Self::CannotCall => "Only functions are callable, i.e., can be used as `fn_name` \
338                in `fn_name(...)` expressions"
339                .to_owned(),
340            Self::CannotIndex => "Only tuples can be indexed".to_owned(),
341            Self::CannotAccessFields => "Only objects have fields".to_owned(),
342
343            Self::UnexpectedOperand { op: Op::Binary(op) } if op.is_arithmetic() => {
344                "Operands of binary arithmetic ops must be primitive values or tuples / objects \
345                 consisting of primitive values"
346                    .to_owned()
347            }
348            Self::UnexpectedOperand { op: Op::Binary(op) } if op.is_comparison() => {
349                "Operands of comparison ops must be primitive values".to_owned()
350            }
351            Self::UnexpectedOperand {
352                op: Op::Binary(BinaryOp::And),
353            }
354            | Self::UnexpectedOperand {
355                op: Op::Binary(BinaryOp::Or),
356            } => "Operands of binary boolean ops must be boolean".to_owned(),
357            Self::UnexpectedOperand {
358                op: Op::Unary(UnaryOp::Neg),
359            } => "Operand of negation must be primitive values or tuples / objects \
360                  consisting of primitive values"
361                .to_owned(),
362            Self::UnexpectedOperand {
363                op: Op::Unary(UnaryOp::Not),
364            } => "Operand of boolean negation must be boolean".to_owned(),
365
366            Self::CannotCompare => {
367                "Only primitive values can be compared; complex values cannot".to_owned()
368            }
369
370            _ => return None,
371        })
372    }
373}
374
375#[cfg(feature = "std")]
376impl std::error::Error for ErrorKind {
377    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
378        match self {
379            Self::Wrapper(error) => Some(error),
380            Self::Arithmetic(error) => Some(error),
381            _ => None,
382        }
383    }
384}
385
386/// Auxiliary information about an evaluation error.
387#[derive(Debug, Clone, PartialEq)]
388#[non_exhaustive]
389pub enum AuxErrorInfo {
390    /// Function arguments declaration for [`ErrorKind::ArgsLenMismatch`].
391    FnArgs,
392
393    /// Previous variable assignment for [`ErrorKind::RepeatedAssignment`].
394    PrevAssignment,
395
396    /// Rvalue containing an invalid assignment for [`ErrorKind::CannotDestructure`]
397    /// or [`ErrorKind::TupleLenMismatch`].
398    Rvalue,
399
400    /// RHS of a binary operation on differently sized tuples.
401    UnbalancedRhsTuple(usize),
402    /// RHS of a binary operation on differently shaped objects.
403    UnbalancedRhsObject(HashSet<String>),
404
405    /// Invalid argument.
406    InvalidArg,
407
408    /// String representation of an argument value (e.g., for a failed equality assertion).
409    ArgValue(String),
410}
411
412impl AuxErrorInfo {
413    pub(crate) fn arg_value<T: fmt::Display>(value: &Value<'_, T>) -> Self {
414        Self::ArgValue(value.to_string())
415    }
416}
417
418impl fmt::Display for AuxErrorInfo {
419    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
420        match self {
421            Self::FnArgs => formatter.write_str("Function arguments declared here"),
422            Self::PrevAssignment => formatter.write_str("Previous declaration"),
423            Self::Rvalue => formatter.write_str("RHS containing the invalid assignment"),
424            Self::UnbalancedRhsTuple(size) => {
425                write!(formatter, "RHS with the {}-element tuple", size)
426            }
427            Self::UnbalancedRhsObject(fields) => {
428                write!(formatter, "RHS object with fields {:?}", fields)
429            }
430            Self::InvalidArg => formatter.write_str("Invalid argument"),
431            Self::ArgValue(val) => write!(formatter, "Has value: {}", val),
432        }
433    }
434}
435
436/// Evaluation error together with one or more relevant code spans.
437///
438/// Use the [`StripCode`] implementation to convert an `Error` to the `'static` lifetime, e.g.,
439/// before boxing it into `Box<dyn std::error::Error>` or `anyhow::Error`.
440/// If the error is wrapped into a [`Result`], you can do this via the `StripResultExt` trait
441/// defined in the `arithmetic-parser` crate:
442///
443/// ```
444/// # use arithmetic_parser::{grammars::{F64Grammar, Parse, Untyped}, StripResultExt};
445/// # use arithmetic_eval::{Environment, Error, ExecutableModule, VariableMap, WildcardId};
446/// fn compile_code(code: &str) -> anyhow::Result<ExecutableModule<'_, f64>> {
447///     let block = Untyped::<F64Grammar>::parse_statements(code).strip_err()?;
448///
449///     // Without `strip_err()` call, the code below won't compile:
450///     // `Error<'_>` in general cannot be boxed into `anyhow::Error`,
451///     // only `Error<'static>` can.
452///     Ok(Environment::new().compile_module(WildcardId, &block).strip_err()?)
453/// }
454/// ```
455#[derive(Debug)]
456pub struct Error<'a> {
457    kind: ErrorKind,
458    main_span: CodeInModule<'a>,
459    aux_spans: Vec<CodeInModule<'a, AuxErrorInfo>>,
460}
461
462impl<'a> Error<'a> {
463    pub(crate) fn new<Span, T>(
464        module_id: &dyn ModuleId,
465        main_span: &LocatedSpan<Span, T>,
466        kind: ErrorKind,
467    ) -> Self
468    where
469        Span: Copy + Into<CodeFragment<'a>>,
470    {
471        Self {
472            kind,
473            main_span: CodeInModule::new(
474                module_id,
475                main_span.with_no_extra().map_fragment(Into::into),
476            ),
477            aux_spans: vec![],
478        }
479    }
480
481    pub(crate) fn from_parts(main_span: CodeInModule<'a>, kind: ErrorKind) -> Self {
482        Self {
483            kind,
484            main_span,
485            aux_spans: vec![],
486        }
487    }
488
489    /// Adds an auxiliary span to this error. The `span` must be in the same module
490    /// as the main span.
491    pub fn with_span<T>(mut self, span: &MaybeSpanned<'a, T>, info: AuxErrorInfo) -> Self {
492        self.aux_spans.push(CodeInModule {
493            module_id: self.main_span.module_id.clone_boxed(),
494            code: span.copy_with_extra(info),
495        });
496        self
497    }
498
499    /// Returns the source of the error.
500    pub fn kind(&self) -> &ErrorKind {
501        &self.kind
502    }
503
504    /// Returns the main span for this error.
505    pub fn main_span(&self) -> &CodeInModule<'a> {
506        &self.main_span
507    }
508
509    /// Returns auxiliary spans for this error.
510    pub fn aux_spans(&self) -> &[CodeInModule<'a, AuxErrorInfo>] {
511        &self.aux_spans
512    }
513}
514
515impl fmt::Display for Error<'_> {
516    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
517        self.main_span.fmt_location(formatter)?;
518        write!(formatter, ": {}", self.kind)
519    }
520}
521
522#[cfg(feature = "std")]
523impl std::error::Error for Error<'_> {
524    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
525        Some(&self.kind)
526    }
527}
528
529impl StripCode for Error<'_> {
530    type Stripped = Error<'static>;
531
532    fn strip_code(self) -> Self::Stripped {
533        Error {
534            kind: self.kind,
535            main_span: self.main_span.strip_code(),
536            aux_spans: self
537                .aux_spans
538                .into_iter()
539                .map(StripCode::strip_code)
540                .collect(),
541        }
542    }
543}
544
545/// Result of an expression evaluation.
546pub type EvalResult<'a, T> = Result<Value<'a, T>, Error<'a>>;
547
548/// Code fragment together with information about the module containing the fragment.
549#[derive(Debug)]
550pub struct CodeInModule<'a, T = ()> {
551    module_id: Box<dyn ModuleId>,
552    code: MaybeSpanned<'a, T>,
553}
554
555impl<T: Clone> Clone for CodeInModule<'_, T> {
556    fn clone(&self) -> Self {
557        Self {
558            module_id: self.module_id.clone_boxed(),
559            code: self.code.clone(),
560        }
561    }
562}
563
564impl<'a> CodeInModule<'a> {
565    pub(crate) fn new(module_id: &dyn ModuleId, span: MaybeSpanned<'a>) -> Self {
566        Self {
567            module_id: module_id.clone_boxed(),
568            code: span,
569        }
570    }
571}
572
573impl<'a, T> CodeInModule<'a, T> {
574    /// Returns the ID of the module containing this fragment.
575    pub fn module_id(&self) -> &dyn ModuleId {
576        self.module_id.as_ref()
577    }
578
579    /// Returns the code fragment within the module. The fragment may be stripped
580    /// (i.e., contain only location info, not the code string itself).
581    pub fn code(&self) -> &MaybeSpanned<'a, T> {
582        &self.code
583    }
584
585    fn fmt_location(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
586        write!(
587            formatter,
588            "{}:{}:{}",
589            self.module_id,
590            self.code.location_line(),
591            self.code.get_column()
592        )
593    }
594}
595
596impl<T: Clone + 'static> StripCode for CodeInModule<'_, T> {
597    type Stripped = CodeInModule<'static, T>;
598
599    fn strip_code(self) -> Self::Stripped {
600        CodeInModule {
601            module_id: self.module_id,
602            code: self.code.strip_code(),
603        }
604    }
605}
606
607/// Element of a backtrace, i.e., a function / method call.
608#[derive(Debug, Clone)]
609#[non_exhaustive]
610pub struct BacktraceElement<'a> {
611    /// Function name.
612    pub fn_name: String,
613    /// Code span of the function definition. `None` for native functions.
614    pub def_span: Option<CodeInModule<'a>>,
615    /// Code span of the function call.
616    pub call_span: CodeInModule<'a>,
617}
618
619impl StripCode for BacktraceElement<'_> {
620    type Stripped = BacktraceElement<'static>;
621
622    fn strip_code(self) -> Self::Stripped {
623        BacktraceElement {
624            fn_name: self.fn_name,
625            def_span: self.def_span.map(StripCode::strip_code),
626            call_span: self.call_span.strip_code(),
627        }
628    }
629}
630
631/// Error backtrace.
632#[derive(Debug, Default)]
633pub(crate) struct Backtrace<'a> {
634    calls: Vec<BacktraceElement<'a>>,
635}
636
637impl<'a> Backtrace<'a> {
638    /// Appends a function call into the backtrace.
639    pub fn push_call(
640        &mut self,
641        fn_name: &str,
642        def_span: Option<CodeInModule<'a>>,
643        call_span: CodeInModule<'a>,
644    ) {
645        self.calls.push(BacktraceElement {
646            fn_name: fn_name.to_owned(),
647            def_span,
648            call_span,
649        });
650    }
651
652    /// Pops a function call.
653    pub fn pop_call(&mut self) {
654        self.calls.pop();
655    }
656}
657
658impl StripCode for Backtrace<'_> {
659    type Stripped = Backtrace<'static>;
660
661    fn strip_code(self) -> Self::Stripped {
662        Backtrace {
663            calls: self.calls.into_iter().map(StripCode::strip_code).collect(),
664        }
665    }
666}
667
668/// Error with the associated backtrace.
669///
670/// Use the [`StripCode`] implementation to convert this to the `'static` lifetime, e.g.,
671/// before boxing it into `Box<dyn std::error::Error>` or `anyhow::Error`.
672#[derive(Debug)]
673pub struct ErrorWithBacktrace<'a> {
674    inner: Error<'a>,
675    backtrace: Backtrace<'a>,
676}
677
678impl<'a> ErrorWithBacktrace<'a> {
679    pub(crate) fn new(inner: Error<'a>, backtrace: Backtrace<'a>) -> Self {
680        Self { inner, backtrace }
681    }
682
683    /// Returns the source of the error.
684    pub fn source(&self) -> &Error<'a> {
685        &self.inner
686    }
687
688    /// Iterates over the error backtrace, starting from the most recent call.
689    pub fn backtrace(&self) -> impl Iterator<Item = &BacktraceElement<'a>> + '_ {
690        self.backtrace.calls.iter().rev()
691    }
692}
693
694impl fmt::Display for ErrorWithBacktrace<'_> {
695    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
696        fmt::Display::fmt(&self.inner, formatter)?;
697
698        if formatter.alternate() && !self.backtrace.calls.is_empty() {
699            writeln!(formatter, "\nBacktrace (most recent call last):")?;
700            for (index, call) in self.backtrace.calls.iter().enumerate() {
701                write!(formatter, "{:>4}: {} ", index + 1, call.fn_name)?;
702
703                if let Some(ref def_span) = call.def_span {
704                    write!(formatter, "(module `{}`)", def_span.module_id)?;
705                } else {
706                    formatter.write_str("(native)")?;
707                }
708
709                write!(formatter, " called at ")?;
710                call.call_span.fmt_location(formatter)?;
711                writeln!(formatter)?;
712            }
713        }
714        Ok(())
715    }
716}
717
718impl StripCode for ErrorWithBacktrace<'_> {
719    type Stripped = ErrorWithBacktrace<'static>;
720
721    fn strip_code(self) -> Self::Stripped {
722        ErrorWithBacktrace {
723            inner: self.inner.strip_code(),
724            backtrace: self.backtrace.strip_code(),
725        }
726    }
727}
728
729#[cfg(feature = "std")]
730impl std::error::Error for ErrorWithBacktrace<'_> {
731    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
732        std::error::Error::source(&self.inner)
733    }
734}
735
736#[cfg(test)]
737mod tests {
738    use super::*;
739    use crate::alloc::ToString;
740
741    #[test]
742    fn display_for_eval_error() {
743        let err = ErrorKind::Undefined("test".to_owned());
744        assert_eq!(err.to_string(), "Variable `test` is not defined");
745
746        let err = ErrorKind::ArgsLenMismatch {
747            def: LvalueLen::AtLeast(2),
748            call: 1,
749        };
750        assert!(err
751            .to_string()
752            .ends_with("definition requires at least 2 arg(s), call has 1"));
753    }
754
755    #[test]
756    fn display_for_spanned_eval_error() {
757        let input = "(_, test) = (1, 2);";
758        let main_span = MaybeSpanned::from_str(input, 4..8);
759        let err = Error::new(
760            &"test_module",
761            &main_span,
762            ErrorKind::Undefined("test".to_owned()),
763        );
764        let err_string = err.to_string();
765        assert_eq!(
766            err_string,
767            "test_module:1:5: Variable `test` is not defined"
768        );
769    }
770
771    #[test]
772    fn display_for_error_with_backtrace() {
773        let input = "(_, test) = (1, 2);";
774        let main_span = MaybeSpanned::from_str(input, 4..8);
775        let err = Error::new(&"test", &main_span, ErrorKind::Undefined("test".to_owned()));
776
777        let mut err = ErrorWithBacktrace::new(err, Backtrace::default());
778        let call_span = CodeInModule::new(&"test", MaybeSpanned::from_str(input, ..));
779        err.backtrace.push_call("test_fn", None, call_span);
780
781        let err_string = err.to_string();
782        assert_eq!(err_string, "test:1:5: Variable `test` is not defined");
783
784        let expanded_err_string = format!("{:#}", err);
785        assert!(expanded_err_string.starts_with("test:1:5: Variable `test` is not defined"));
786        assert!(expanded_err_string.contains("\nBacktrace"));
787        assert!(expanded_err_string.contains("\n   1: test_fn (native) called at test:1:1"));
788    }
789}