From 978a6c6981f8f0b093621337e11acd4611285a94 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 14 Dec 2022 22:00:56 +0100 Subject: [PATCH] Collect and display evaluation hints on test failures. --- crates/lang/src/ast.rs | 15 +++++++++++ crates/project/src/error.rs | 50 +++++++++++++++++++++++++++++++------ crates/project/src/lib.rs | 38 +++++++++++++++++++++++++--- 3 files changed, 93 insertions(+), 10 deletions(-) diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index 56e8f84c..ccb93e3a 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -85,6 +85,21 @@ pub struct Function { pub type TypedTypeAlias = TypeAlias>; pub type UntypedTypeAlias = TypeAlias<()>; +impl TypedFunction { + pub fn test_hint(&self) -> Option<(BinOp, Box, Box)> { + match &self.body { + TypedExpr::BinOp { + name, + tipo, + left, + right, + .. + } if tipo == &bool() => Some((name.clone(), left.clone(), right.clone())), + _ => None, + } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct TypeAlias { pub alias: String, diff --git a/crates/project/src/error.rs b/crates/project/src/error.rs index 8bfb0a1d..34c1c100 100644 --- a/crates/project/src/error.rs +++ b/crates/project/src/error.rs @@ -1,13 +1,18 @@ +use crate::{pretty, script::EvalHint}; +use aiken_lang::{ + ast::{BinOp, Span}, + parser::error::ParseError, + tipo, +}; +use miette::{ + Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode, +}; use std::{ fmt::{Debug, Display}, io, path::{Path, PathBuf}, }; - -use aiken_lang::{ast::Span, parser::error::ParseError, tipo}; -use miette::{ - Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode, -}; +use uplc::machine::cost_model::ExBudget; #[allow(dead_code)] #[derive(thiserror::Error)] @@ -75,7 +80,11 @@ pub enum Error { }, #[error("{} failed", name)] - TestFailure { name: String, path: PathBuf }, + TestFailure { + name: String, + path: PathBuf, + evaluation_hint: Option, + }, } impl Error { @@ -231,7 +240,34 @@ impl Diagnostic for Error { Error::Format { .. } => None, Error::ValidatorMustReturnBool { .. } => Some(Box::new("Try annotating the validator's return type with Bool")), Error::WrongValidatorArity { .. } => Some(Box::new("Validators require a minimum number of arguments please add the missing arguments.\nIf you don't need one of the required arguments use an underscore `_datum`.")), - Error::TestFailure { .. } => None, + Error::TestFailure { evaluation_hint, .. } =>{ + match evaluation_hint { + None => None, + Some(hint) => { + let budget = ExBudget { mem: i64::MAX, cpu: i64::MAX, }; + let left = pretty::boxed("left", match hint.left.eval(budget) { + (Ok(term), _, _) => format!("{term}"), + (Err(err), _, _) => format!("{err}"), + }); + let right = pretty::boxed("right", match hint.right.eval(budget) { + (Ok(term), _, _) => format!("{term}"), + (Err(err), _, _) => format!("{err}"), + }); + let msg = match hint.bin_op { + BinOp::And => Some(format!("{left}\n\nand\n\n{right}\n\nshould both be true.")), + BinOp::Or => Some(format!("{left}\n\nor\n\n{right}\n\nshould be true.")), + BinOp::Eq => Some(format!("{left}\n\nshould be equal to\n\n{right}")), + BinOp::NotEq => Some(format!("{left}\n\nshould not be equal to\n\n{right}")), + BinOp::LtInt => Some(format!("{left}\n\nshould be lower than\n\n{right}")), + BinOp::LtEqInt => Some(format!("{left}\n\nshould be lower than or equal to\n\n{right}")), + BinOp::GtEqInt => Some(format!("{left}\n\nshould be greater than\n\n{right}")), + BinOp::GtInt => Some(format!("{left}\n\nshould be greater than or equal to\n\n{right}")), + _ => None + }?; + Some(Box::new(msg)) + } + } + }, } } diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index 286582b2..f2495e4e 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -25,9 +25,14 @@ use pallas::{ ledger::{addresses::Address, primitives::babbage}, }; use pallas_traverse::ComputeHash; -use script::Script; +use script::{EvalHint, EvalInfo, Script}; use serde_json::json; -use telemetry::{EvalInfo, EventListener}; +use std::{ + collections::HashMap, + fs, + path::{Path, PathBuf}, +}; +use telemetry::EventListener; use uplc::{ ast::{Constant, DeBruijn, Program, Term}, machine::cost_model::ExBudget, @@ -159,6 +164,7 @@ where Some(Error::TestFailure { name: e.script.name.clone(), path: e.script.input_path.clone(), + evaluation_hint: e.script.evaluation_hint.clone(), }) } }) @@ -472,7 +478,13 @@ where let program = generator.generate(body, arguments, true); - let script = Script::new(input_path, module_name, name, program.try_into().unwrap()); + let script = Script::new( + input_path, + module_name, + name, + program.try_into().unwrap(), + None, + ); programs.push(script); } @@ -571,6 +583,25 @@ where &self.module_types, ); + let evaluation_hint = if let Some((bin_op, left_src, right_src)) = func_def.test_hint() + { + let left = CodeGenerator::new(&functions, &data_types, &self.module_types) + .generate(*left_src, vec![], false) + .try_into() + .unwrap(); + let right = CodeGenerator::new(&functions, &data_types, &self.module_types) + .generate(*right_src, vec![], false) + .try_into() + .unwrap(); + Some(EvalHint { + bin_op, + left, + right, + }) + } else { + None + }; + let program = generator.generate(body.clone(), arguments.clone(), false); let script = Script::new( @@ -578,6 +609,7 @@ where module_name, name.to_string(), program.try_into().unwrap(), + evaluation_hint, ); programs.push(script);