diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs index 356a48261..dca71c685 100644 --- a/ergotree-interpreter/src/eval.rs +++ b/ergotree-interpreter/src/eval.rs @@ -537,4 +537,24 @@ mod test { "#]] .assert_eq(&res.diag.to_string()); } + + // Regression: `inner` returns a bare `InvalidResultType` (eval.rs match arm + // for non-Boolean/non-SigmaProp Value) when the expression reduces to e.g. + // an Int. That bare variant enters the Err(_) diagnostic retry arm, the + // spanned re-eval also returns bare, and `wrap_spanned_with_src` used to + // panic. The fix in error.rs wraps any variant — assert reduce_to_crypto + // returns Err instead of panicking. + #[test] + fn reduce_to_crypto_non_sigma_prop_result_returns_err_not_panic() { + use super::EvalError; + let tree = ErgoTree::try_from(Expr::Const(42i32.into())).unwrap(); + let ctx = force_any_val::(); + let res = reduce_to_crypto(&tree, &ctx); + assert!( + matches!(res, Err(EvalError::SpannedWithSource(_))), + "expected Err(SpannedWithSource) after wrap_spanned_with_src \ + fix, got {:?}", + res + ); + } } diff --git a/ergotree-interpreter/src/eval/error.rs b/ergotree-interpreter/src/eval/error.rs index a10e1ab94..e03a42483 100644 --- a/ergotree-interpreter/src/eval/error.rs +++ b/ergotree-interpreter/src/eval/error.rs @@ -193,9 +193,14 @@ impl EvalError { }) } - /// Wrap eval error with source code + /// Wrap eval error with source code. + /// + /// `Spanned` variants are unwrapped and re-wrapped with `source` attached. + /// Other variants are wrapped with an empty span/env — reachable from the + /// diagnostic retry path in `reduce_to_crypto` when the deeper eval returns + /// a bare variant (e.g. `InvalidResultType`, `UnexpectedExpr`) without + /// having gone through `enrich_err`. pub fn wrap_spanned_with_src(self, source: String) -> Self { - #[allow(clippy::panic)] match self { EvalError::Spanned(e) => EvalError::SpannedWithSource(SpannedWithSourceEvalError { error: e.error, @@ -203,7 +208,12 @@ impl EvalError { env: e.env, source, }), - e => panic!("Expected Spanned, got {:?}", e), + e => EvalError::SpannedWithSource(SpannedWithSourceEvalError { + error: Box::new(e), + source_span: SourceSpan::empty(), + env: Env::empty().to_static(), + source, + }), } } } @@ -392,4 +402,20 @@ mod tests { // ); check_error_span(expr, (31, 4).into()); } + + #[test] + fn wrap_spanned_with_src_handles_bare_variant() { + use super::EvalError; + let bare = EvalError::InvalidResultType; + let wrapped = bare.wrap_spanned_with_src("script source".to_string()); + assert!( + matches!(&wrapped, EvalError::SpannedWithSource(_)), + "expected SpannedWithSource, got {:?}", + wrapped + ); + if let EvalError::SpannedWithSource(swse) = wrapped { + assert_eq!(swse.source, "script source"); + assert!(matches!(*swse.error, EvalError::InvalidResultType)); + } + } }