ergotree_interpreter/eval/
error.rs

1use miette::miette;
2use miette::LabeledSpan;
3use std::fmt::Debug;
4use std::fmt::Display;
5
6use bounded_vec::BoundedVecOutOfBounds;
7use derive_more::TryInto;
8use ergotree_ir::ergo_tree::ErgoTreeError;
9use ergotree_ir::mir::constant::TryExtractFromError;
10use ergotree_ir::serialization::SigmaParsingError;
11use ergotree_ir::serialization::SigmaSerializationError;
12use ergotree_ir::source_span::SourceSpan;
13use sigma_ser::ScorexParsingError;
14use sigma_ser::ScorexSerializationError;
15use thiserror::Error;
16
17use super::cost_accum::CostError;
18use super::env::Env;
19
20/// Interpreter errors
21#[derive(Error, PartialEq, Eq, Debug, Clone, TryInto)]
22pub enum EvalError {
23    /// AVL tree errors
24    #[error("AvlTree: {0}")]
25    AvlTree(String),
26    /// Only boolean or SigmaBoolean is a valid result expr type
27    #[error("Only boolean or SigmaBoolean is a valid result expr type")]
28    InvalidResultType,
29    /// Unexpected Expr encountered during the evaluation
30    #[error("Unexpected Expr: {0}")]
31    UnexpectedExpr(String),
32    /// Error on cost calculation
33    #[error("Error on cost calculation: {0:?}")]
34    CostError(#[from] CostError),
35    /// Unexpected value type
36    #[error("Unexpected value type: {0:?}")]
37    TryExtractFrom(#[from] TryExtractFromError),
38    /// Not found (missing value, argument, etc.)
39    #[error("Not found: {0}")]
40    NotFound(String),
41    /// Register id out of bounds
42    #[error("{0}")]
43    RegisterIdOutOfBounds(String),
44    /// Unexpected value
45    #[error("Unexpected value: {0}")]
46    UnexpectedValue(String),
47    /// Arithmetic exception error
48    #[error("Arithmetic exception: {0}")]
49    ArithmeticException(String),
50    /// Misc error
51    #[error("error: {0}")]
52    Misc(String),
53    /// Sigma serialization error
54    #[error("Serialization error: {0}")]
55    SigmaSerializationError(#[from] SigmaSerializationError),
56    /// Sigma serialization parsing error
57    #[error("Serialization parsing error: {0}")]
58    SigmaParsingError(#[from] SigmaParsingError),
59    /// ErgoTree error
60    #[error("ErgoTree error: {0}")]
61    ErgoTreeError(#[from] ErgoTreeError),
62    /// Invalid item quantity for BoundedVec
63    #[error("Invalid item quantity for BoundedVec: {0}")]
64    BoundedVecError(#[from] BoundedVecOutOfBounds),
65    /// Scorex serialization error
66    #[error("Serialization error: {0}")]
67    ScorexSerializationError(#[from] ScorexSerializationError),
68    /// Scorex serialization parsing error
69    #[error("Serialization parsing error: {0}")]
70    ScorexParsingError(#[from] ScorexParsingError),
71    /// Wrapped error with source span and source code
72    #[error("eval error: {0}")]
73    SpannedWithSource(SpannedWithSourceEvalError),
74    /// Wrapped error with source span
75    #[error("eval error: {0:?}")]
76    Spanned(SpannedEvalError),
77}
78
79/// Wrapped error with source span
80#[derive(PartialEq, Eq, Debug, Clone)]
81pub struct SpannedEvalError {
82    /// eval error
83    error: Box<EvalError>,
84    /// source span for the expression where error occurred
85    source_span: SourceSpan,
86    /// environment at the time when error occurred
87    env: Env<'static>,
88}
89
90/// Wrapped error with source span and source code
91#[derive(PartialEq, Eq, Clone)]
92pub struct SpannedWithSourceEvalError {
93    /// eval error
94    error: Box<EvalError>,
95    /// source span for the expression where error occurred
96    source_span: SourceSpan,
97    /// environment at the time when error occurred
98    env: Env<'static>,
99    /// source code
100    source: String,
101}
102
103impl Display for SpannedWithSourceEvalError {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        let _ = miette::set_hook(Box::new(|_| {
106            Box::new(
107                miette::MietteHandlerOpts::new()
108                    .terminal_links(false)
109                    .unicode(false)
110                    .color(false)
111                    .context_lines(5)
112                    .tab_width(2)
113                    .build(),
114            )
115        }));
116        let err_msg = self.error.to_string();
117        let report = miette!(
118            labels = vec![LabeledSpan::at(self.source_span, err_msg,)],
119            // help = "Help msg",
120            "Evaluation error"
121        )
122        .with_source_code(self.source.clone());
123        write!(f, "{:?}", report)
124    }
125}
126
127impl Debug for SpannedWithSourceEvalError {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        let _ = miette::set_hook(Box::new(|_| {
130            Box::new(
131                miette::MietteHandlerOpts::new()
132                    .terminal_links(false)
133                    .unicode(false)
134                    .color(false)
135                    .context_lines(5)
136                    .tab_width(2)
137                    .build(),
138            )
139        }));
140        let err_msg = self.error.to_string();
141        let report = miette!(
142            labels = vec![LabeledSpan::at(self.source_span, err_msg,)],
143            // help = "Help msg",
144            "Evaluation error"
145        )
146        .with_source_code(self.source.clone());
147        write!(f, "{:?}", report)?;
148        write!(f, "Env:\n{}", self.env)
149    }
150}
151
152impl EvalError {
153    /// Wrap eval error with source span
154    pub fn wrap(self, source_span: SourceSpan, env: Env) -> Self {
155        EvalError::Spanned(SpannedEvalError {
156            error: Box::new(self),
157            source_span,
158            env: env.to_static(),
159        })
160    }
161
162    /// Wrap eval error with source code
163    pub fn wrap_spanned_with_src(self, source: String) -> Self {
164        #[allow(clippy::panic)]
165        match self {
166            EvalError::Spanned(e) => EvalError::SpannedWithSource(SpannedWithSourceEvalError {
167                error: e.error,
168                source_span: e.source_span,
169                env: e.env,
170                source,
171            }),
172            e => panic!("Expected Spanned, got {:?}", e),
173        }
174    }
175}
176
177pub trait ExtResultEvalError<T> {
178    fn enrich_err(self, span: SourceSpan, env: &Env) -> Result<T, EvalError>;
179}
180
181impl<T> ExtResultEvalError<T> for Result<T, EvalError> {
182    fn enrich_err<'ctx>(self, span: SourceSpan, env: &Env<'ctx>) -> Result<T, EvalError> {
183        self.map_err(|e| match e {
184            // skip already wrapped errors
185            w @ EvalError::Spanned { .. } => w,
186            e => e.wrap(span, env.clone()),
187        })
188    }
189}
190
191#[allow(clippy::unwrap_used, unused_imports, dead_code)]
192#[cfg(test)]
193mod tests {
194    use ergotree_ir::mir::coll_by_index::ByIndex;
195    use ergotree_ir::mir::global_vars::GlobalVars;
196    use ergotree_ir::source_span::SourceSpan;
197    use expect_test::expect;
198
199    use ergotree_ir::mir::bin_op::ArithOp;
200    use ergotree_ir::mir::bin_op::BinOp;
201    use ergotree_ir::mir::block::BlockValue;
202    use ergotree_ir::mir::expr::Expr;
203    use ergotree_ir::mir::val_def::ValDef;
204    use ergotree_ir::mir::val_use::ValUse;
205    use ergotree_ir::pretty_printer::PosTrackingWriter;
206    use ergotree_ir::pretty_printer::Print;
207    use ergotree_ir::types::stype::SType;
208    use sigma_test_util::force_any_val;
209
210    use crate::eval::context::Context;
211    use crate::eval::error::SpannedEvalError;
212    use crate::eval::error::SpannedWithSourceEvalError;
213    use crate::eval::tests::try_eval_out;
214
215    fn check(expr: Expr, expected_tree: expect_test::Expect) {
216        let mut w = PosTrackingWriter::new();
217        let spanned_expr = expr.print(&mut w).unwrap();
218        dbg!(&spanned_expr);
219        let ctx = force_any_val::<Context>();
220        let err_raw: SpannedEvalError = try_eval_out::<i32>(&spanned_expr, &ctx)
221            .err()
222            .unwrap()
223            .try_into()
224            .unwrap();
225        let err = SpannedWithSourceEvalError {
226            error: err_raw.error,
227            source_span: err_raw.source_span,
228            env: err_raw.env,
229            source: w.get_buf().to_string(),
230        };
231        expected_tree.assert_eq(&err.to_string());
232    }
233
234    fn check_error_span(expr: Expr, expected_span: SourceSpan) {
235        let mut w = PosTrackingWriter::new();
236        let spanned_expr = expr.print(&mut w).unwrap();
237        dbg!(&spanned_expr);
238        let ctx = force_any_val::<Context>();
239        let err_raw: SpannedEvalError = try_eval_out::<i32>(&spanned_expr, &ctx)
240            .err()
241            .unwrap()
242            .try_into()
243            .unwrap();
244        assert_eq!(err_raw.source_span, expected_span);
245    }
246
247    #[test]
248    fn pretty_binop_div_zero() {
249        let lhs_val_id = 1.into();
250        let rhs_val_id = 2.into();
251        let res_val_id = 3.into();
252        let expr = Expr::BlockValue(
253            BlockValue {
254                items: vec![
255                    ValDef {
256                        id: lhs_val_id,
257                        rhs: Box::new(Expr::Const(42i32.into())),
258                    }
259                    .into(),
260                    ValDef {
261                        id: rhs_val_id,
262                        rhs: Box::new(Expr::Const(0i32.into())),
263                    }
264                    .into(),
265                    ValDef {
266                        id: res_val_id,
267                        rhs: Box::new(
268                            BinOp {
269                                kind: ArithOp::Divide.into(),
270                                left: Box::new(
271                                    ValUse {
272                                        val_id: lhs_val_id,
273                                        tpe: SType::SInt,
274                                    }
275                                    .into(),
276                                ),
277                                right: Box::new(
278                                    ValUse {
279                                        val_id: rhs_val_id,
280                                        tpe: SType::SInt,
281                                    }
282                                    .into(),
283                                ),
284                            }
285                            .into(),
286                        ),
287                    }
288                    .into(),
289                ],
290                result: Box::new(
291                    ValUse {
292                        val_id: res_val_id,
293                        tpe: SType::SInt,
294                    }
295                    .into(),
296                ),
297            }
298            .into(),
299        );
300        // check(
301        //     expr,
302        //     expect![[r#"
303        //           x Evaluation error
304        //            ,-[1:1]
305        //          1 | {
306        //          2 |   val v1 = 42
307        //          3 |   val v2 = 0
308        //          4 |   val v3 = v1 / v2
309        //            :            ^^^|^^^
310        //            :               `-- Arithmetic exception: (42) / (0) resulted in exception
311        //          5 |   v3
312        //          6 | }
313        //            `----
314        //     "#]],
315        // );
316        check_error_span(expr, (40, 7).into());
317    }
318
319    #[test]
320    fn pretty_out_of_bounds() {
321        let v1_id = 1.into();
322        let expr = Expr::BlockValue(
323            BlockValue {
324                items: vec![ValDef {
325                    id: v1_id,
326                    rhs: Box::new(Expr::Const(99999999i32.into())),
327                }
328                .into()],
329                result: Box::new(Expr::ByIndex(
330                    ByIndex::new(
331                        Expr::GlobalVars(GlobalVars::Outputs),
332                        ValUse {
333                            val_id: v1_id,
334                            tpe: SType::SInt,
335                        }
336                        .into(),
337                        None,
338                    )
339                    .unwrap()
340                    .into(),
341                )),
342            }
343            .into(),
344        );
345        // check(
346        //     expr,
347        //     expect![[r#"
348        //           x Evaluation error
349        //            ,-[1:1]
350        //          1 | {
351        //          2 |   val v1 = 99999999
352        //          3 |   OUTPUTS(v1)
353        //            :          ^^|^
354        //            :            `-- error: ByIndex: index Int(99999999) out of bounds for collection size 1
355        //          4 | }
356        //            `----
357        //     "#]],
358        // );
359        check_error_span(expr, (31, 4).into());
360    }
361}