Skip to main content

cljrs_runtime/env/
error.rs

1//! Evaluation-time error types.
2
3use cljrs_value::Value;
4use cljrs_value::ValueError;
5
6#[derive(Debug, thiserror::Error)]
7pub enum EvalError {
8    #[error("runtime error: {0}")]
9    Runtime(String),
10
11    /// The cooperative execution-credit budget was exhausted.
12    #[error("gas exhausted")]
13    GasExhausted,
14
15    /// The process memory governor refused a governed allocation (strict
16    /// mode), and a collection did not bring committed memory back within
17    /// the hard limit.  Fixed-size; building it never allocates.  Like
18    /// `GasExhausted`, `try`/`catch` does not intercept it.
19    #[error("{0}")]
20    MemoryLimitExceeded(cljrs_gc::MemoryLimitExceeded),
21
22    /// An operation attempted to use a capability unavailable to an isolated
23    /// transaction function.
24    #[error("effect forbidden in transaction function: {0}")]
25    ForbiddenEffect(String),
26
27    #[error("unbound symbol: {0}")]
28    UnboundSymbol(String),
29
30    #[error("arity error calling {name}: expected {expected}, got {got}")]
31    Arity {
32        name: String,
33        expected: String,
34        got: usize,
35    },
36
37    #[error("not callable: {0}")]
38    NotCallable(String),
39
40    /// A value thrown via `throw` or `ex-info`.
41    #[error("{0}")]
42    Thrown(Value),
43
44    #[error("read error: {0}")]
45    Read(#[from] cljrs_types::error::CljxError),
46
47    /// Internal signal for `recur` — caught by the loop/fn trampoline.
48    /// Never propagated to user code.
49    #[doc(hidden)]
50    #[error("internal: recur outside loop or fn")]
51    Recur(Vec<Value>),
52
53    #[error(
54        "commit {commit:?} failed signature verification — \
55         refusing to execute versioned symbol (enable GPG/SSH trust or disable \
56         :verify-commit-signatures): {reason}"
57    )]
58    CommitSignatureVerificationFailed { commit: String, reason: String },
59}
60
61impl EvalError {
62    /// Convert this error into a Clojure error *value* (`Value::Error`).
63    ///
64    /// A `Thrown` value is returned unchanged (preserving its `ex-data` /
65    /// `ex-cause`); any other error is wrapped in a fresh `ExceptionInfo` with
66    /// the error's display string as the message. Used where an error must be
67    /// stored as a value and later re-thrown — e.g. a failed `Future`'s state.
68    pub fn to_error_value(self) -> Value {
69        match self {
70            EvalError::Thrown(v) => v,
71            other => {
72                let msg = other.to_string();
73                let error = match other {
74                    EvalError::MemoryLimitExceeded(fault) => {
75                        cljrs_value::ValueError::MemoryLimitExceeded(fault)
76                    }
77                    _ => cljrs_value::ValueError::Other(msg.clone()),
78                };
79                // Error reporting may use the critical memory reserve.
80                Value::Error(cljrs_gc::GcPtr::new_critical(
81                    cljrs_value::ExceptionInfo::new(error, msg, None, None),
82                ))
83            }
84        }
85    }
86}
87
88/// Surface a builtin's `ValueError` to the evaluator as a *catchable* condition.
89///
90/// Internal runtime errors (`IndexOutOfBounds`, `WrongType`, `ArityError`, …)
91/// are normalized into an `EvalError::Thrown(Value::Error(..))` carrying the
92/// original `ValueError` variant and its plain display message — no
93/// `runtime error:` prefix — so that `(catch :default e ..)` /
94/// `(catch Throwable e ..)` bind a value on which `ex-message` / `ex-data`
95/// behave the same as for a user `throw` / `ex-info`. A `ValueError::Thrown`
96/// (a builtin re-throwing a Clojure value) is surfaced as that exact value.
97pub fn value_error_to_eval_error(err: ValueError) -> EvalError {
98    match err {
99        ValueError::Thrown(v) => EvalError::Thrown(v),
100        ValueError::GasExhausted => EvalError::GasExhausted,
101        ValueError::MemoryLimitExceeded(fault) => {
102            // The error is back on a path that carries it; a copy left in
103            // the runtime-fault slot (see `callback::invoke`) would report
104            // it a second time.
105            let _ = cljrs_gc::governor::take_runtime_fault();
106            EvalError::MemoryLimitExceeded(fault)
107        }
108        other => {
109            let msg = other.to_string();
110            // Error reporting may use the critical memory reserve.
111            EvalError::Thrown(Value::Error(cljrs_gc::GcPtr::new_critical(
112                cljrs_value::ExceptionInfo::new(other, msg, None, None),
113            )))
114        }
115    }
116}
117
118pub type EvalResult<T = Value> = Result<T, EvalError>;