Skip to main content

tidepool_eval/
error.rs

1use crate::value::ThunkId;
2use tidepool_repr::{JoinId, PrimOpKind, VarId};
3
4/// Describes the kind of a Value for error reporting.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum ValueKind {
7    Literal(&'static str), // "Int#", "Word#", "Double#", "Char#", "String"
8    Constructor,
9    Closure,
10    Thunk,
11    /// Fallback for complex values — stores Debug output
12    Other(String),
13}
14
15impl std::fmt::Display for ValueKind {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        match self {
18            ValueKind::Literal(name) => write!(f, "{}", name),
19            ValueKind::Constructor => write!(f, "constructor"),
20            ValueKind::Closure => write!(f, "closure"),
21            ValueKind::Thunk => write!(f, "thunk"),
22            ValueKind::Other(s) => write!(f, "{}", s),
23        }
24    }
25}
26
27/// Evaluation error.
28#[derive(Debug, Clone)]
29pub enum EvalError {
30    /// Variable not found in environment
31    UnboundVar(VarId),
32    /// Arity mismatch (wrong number of arguments or fields)
33    ArityMismatch {
34        context: &'static str, // "arguments", "fields", "case binders"
35        expected: usize,
36        got: usize,
37    },
38    /// Type mismatch during evaluation
39    TypeMismatch {
40        expected: &'static str,
41        got: ValueKind,
42    },
43    /// No matching alternative in case expression
44    NoMatchingAlt,
45    /// Infinite loop detected (thunk forced itself)
46    InfiniteLoop(ThunkId),
47    /// Unsupported primop
48    UnsupportedPrimOp(PrimOpKind),
49    /// Heap exhausted
50    HeapExhausted,
51    /// Application of non-function value
52    NotAFunction,
53    /// Jump to unknown join point
54    UnboundJoin(JoinId),
55    /// Haskell `error "..."` called
56    UserError,
57    /// Haskell `undefined` forced
58    Undefined,
59    /// Recursion depth limit exceeded during deep_force
60    DepthLimit,
61    /// Internal invariant violation (should never happen)
62    InternalError(String),
63}
64
65impl std::fmt::Display for EvalError {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        match self {
68            EvalError::UnboundVar(v) => write!(f, "unbound variable: v_{}", v.0),
69            EvalError::ArityMismatch {
70                context,
71                expected,
72                got,
73            } => {
74                write!(
75                    f,
76                    "arity mismatch: expected {} {}, got {}",
77                    expected, context, got
78                )
79            }
80            EvalError::TypeMismatch { expected, got } => {
81                write!(f, "type mismatch: expected {}, got {}", expected, got)
82            }
83            EvalError::NoMatchingAlt => write!(f, "no matching case alternative"),
84            EvalError::InfiniteLoop(id) => write!(f, "infinite loop: thunk {} forced itself", id.0),
85            EvalError::UnsupportedPrimOp(op) => write!(f, "unsupported primop: {:?}", op),
86            EvalError::HeapExhausted => write!(f, "heap exhausted"),
87            EvalError::NotAFunction => write!(f, "application of non-function value"),
88            EvalError::UnboundJoin(id) => write!(f, "jump to unbound join point: j_{}", id.0),
89            EvalError::UserError => write!(f, "Haskell error called"),
90            EvalError::Undefined => write!(f, "Haskell undefined forced"),
91            EvalError::DepthLimit => write!(f, "recursion depth limit exceeded"),
92            EvalError::InternalError(msg) => write!(f, "internal error: {}", msg),
93        }
94    }
95}
96
97impl std::error::Error for EvalError {}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_error_display() {
105        let errs = vec![
106            EvalError::UnboundVar(VarId(42)),
107            EvalError::ArityMismatch {
108                context: "arguments",
109                expected: 2,
110                got: 1,
111            },
112            EvalError::TypeMismatch {
113                expected: "Int#",
114                got: ValueKind::Literal("Char#"),
115            },
116            EvalError::NoMatchingAlt,
117            EvalError::InfiniteLoop(ThunkId(0)),
118            EvalError::UnsupportedPrimOp(PrimOpKind::IntAdd),
119            EvalError::HeapExhausted,
120            EvalError::NotAFunction,
121            EvalError::UnboundJoin(JoinId(7)),
122        ];
123
124        for err in errs {
125            let s = format!("{}", err);
126            assert!(!s.is_empty(), "Display for {:?} should not be empty", err);
127        }
128    }
129}