ergotree_interpreter/eval/
error.rs1use 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#[derive(Error, PartialEq, Eq, Debug, Clone, TryInto)]
22pub enum EvalError {
23 #[error("AvlTree: {0}")]
25 AvlTree(String),
26 #[error("Only boolean or SigmaBoolean is a valid result expr type")]
28 InvalidResultType,
29 #[error("Unexpected Expr: {0}")]
31 UnexpectedExpr(String),
32 #[error("Error on cost calculation: {0:?}")]
34 CostError(#[from] CostError),
35 #[error("Unexpected value type: {0:?}")]
37 TryExtractFrom(#[from] TryExtractFromError),
38 #[error("Not found: {0}")]
40 NotFound(String),
41 #[error("{0}")]
43 RegisterIdOutOfBounds(String),
44 #[error("Unexpected value: {0}")]
46 UnexpectedValue(String),
47 #[error("Arithmetic exception: {0}")]
49 ArithmeticException(String),
50 #[error("error: {0}")]
52 Misc(String),
53 #[error("Serialization error: {0}")]
55 SigmaSerializationError(#[from] SigmaSerializationError),
56 #[error("Serialization parsing error: {0}")]
58 SigmaParsingError(#[from] SigmaParsingError),
59 #[error("ErgoTree error: {0}")]
61 ErgoTreeError(#[from] ErgoTreeError),
62 #[error("Invalid item quantity for BoundedVec: {0}")]
64 BoundedVecError(#[from] BoundedVecOutOfBounds),
65 #[error("Serialization error: {0}")]
67 ScorexSerializationError(#[from] ScorexSerializationError),
68 #[error("Serialization parsing error: {0}")]
70 ScorexParsingError(#[from] ScorexParsingError),
71 #[error("eval error: {0}")]
73 SpannedWithSource(SpannedWithSourceEvalError),
74 #[error("eval error: {0:?}")]
76 Spanned(SpannedEvalError),
77}
78
79#[derive(PartialEq, Eq, Debug, Clone)]
81pub struct SpannedEvalError {
82 error: Box<EvalError>,
84 source_span: SourceSpan,
86 env: Env<'static>,
88}
89
90#[derive(PartialEq, Eq, Clone)]
92pub struct SpannedWithSourceEvalError {
93 error: Box<EvalError>,
95 source_span: SourceSpan,
97 env: Env<'static>,
99 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 "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 "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 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 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 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_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_error_span(expr, (31, 4).into());
360 }
361}