1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! Types used in exception handling.

use std::fmt::{self, Debug};

use refuse::Trace;

use crate::compiler::syntax::Sources;
use crate::runtime::value::{AnyDynamic, CustomType, RustType, TypeRef, Value};
use crate::vm::{Code, StackFrame, VmContext};

/// A thrown exception.
///
/// An exception contains two pieces of data:
///
/// - a [`Value`].
/// - a [backtrace](Self::backtrace)
#[derive(Debug)]
pub struct Exception {
    value: Value,
    stack_trace: Vec<StackFrame>,
}

impl Exception {
    /// Returns a new exception with the current backtrace of the virtual
    /// machine.
    pub fn new(value: Value, vm: &mut VmContext<'_, '_>) -> Self {
        let stack_trace = vm.backtrace();
        Self { value, stack_trace }
    }

    /// Returns the thrown value.
    #[must_use]
    pub const fn value(&self) -> Value {
        self.value
    }

    /// Returns the list of recorded stack frames when this exception was
    /// thrown.
    #[must_use]
    pub fn backtrace(&self) -> &[StackFrame] {
        &self.stack_trace
    }

    /// Displays this exception with its backtrace into `f`.
    pub fn format(
        &self,
        sources: &Sources,
        context: &mut VmContext<'_, '_>,
        mut f: impl fmt::Write,
    ) -> fmt::Result {
        f.write_str("uncaught exception: ")?;
        self.value.format(context, &mut f)?;
        if !self.stack_trace.is_empty() {
            for entry in &self.stack_trace {
                f.write_char('\n')?;
                if let Some(range) = entry.source_range() {
                    if let Some(source) = sources.get(range.source_id) {
                        let (line_no, start) = source.offset_to_line(range.start);
                        write!(f, "in {}:{line_no}:{start}", source.name)?;
                        continue;
                    }
                }
                write!(f, "in {:?}", entry.code() as *const Code)?;
            }
        }
        Ok(())
    }
}

impl CustomType for Exception {
    fn muse_type(&self) -> &TypeRef {
        static EXCEPTION_TYPE: RustType<Exception> = RustType::new("Exception", |t| {
            t.with_fallback(|this, _guard| this.value)
                .with_eq(|_| {
                    |this, vm, rhs| {
                        if let Some(rhs) = rhs.as_rooted::<Exception>(vm.as_ref()) {
                            Ok(this.value.equals(vm, &rhs.value)?
                                && this.stack_trace == rhs.stack_trace)
                        } else {
                            Ok(false)
                        }
                    }
                })
                .with_matches(|_| |this, vm, rhs| this.value.matches(vm, rhs))
                .with_deep_clone(|_| {
                    |this, guard| {
                        this.value.deep_clone(guard).map(|value| {
                            AnyDynamic::new(
                                Exception {
                                    value,
                                    stack_trace: this.stack_trace.clone(),
                                },
                                guard,
                            )
                        })
                    }
                })
        });
        &EXCEPTION_TYPE
    }
}

impl Trace for Exception {
    const MAY_CONTAIN_REFERENCES: bool = true;

    fn trace(&self, tracer: &mut refuse::Tracer) {
        if let Some(dynamic) = self.value.as_any_dynamic() {
            tracer.mark(dynamic);
        }
    }
}