diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 6953f6a0..4487e7b0 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -118,6 +118,7 @@ fn str_to_keyword(word: &str) -> Option { "test" => Some(Token::Test), "error" => Some(Token::ErrorTerm), "validator" => Some(Token::Validator), + "fail" => Some(Token::Fail), _ => None, } } @@ -136,6 +137,7 @@ pub struct Function { pub return_annotation: Option, pub return_type: T, pub end_position: usize, + pub can_error: bool, } pub type TypedTypeAlias = TypeAlias>; diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index b6e8dca5..12d8622c 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -681,6 +681,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap IndexMap IndexMap IndexMap impl Parser impl Parser impl Parser { just(Token::Test) - .ignore_then(select! {Token::Name {name} => name}) + .ignore_then(just(Token::Fail).ignored().or_not()) + .then(select! {Token::Name {name} => name}) .then_ignore(just(Token::LeftParen)) .then_ignore(just(Token::RightParen)) .map_with_span(|name, span| (name, span)) @@ -345,7 +348,7 @@ pub fn test_parser() -> impl Parser impl Parser impl Parser, Error = ParseError> { "todo" => Token::Todo, "type" => Token::Type, "when" => Token::When, + "fail" => Token::Fail, "validator" => Token::Validator, _ => { if s.chars().next().map_or(false, |c| c.is_uppercase()) { diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index b06a4783..1fe4418b 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -80,6 +80,7 @@ pub enum Token { When, Trace, Validator, + Fail, } impl fmt::Display for Token { @@ -164,6 +165,7 @@ impl fmt::Display for Token { Token::Test => "test", Token::ErrorTerm => "error", Token::Validator => "validator", + Token::Fail => "fail", }; write!(f, "\"{s}\"") } diff --git a/crates/aiken-lang/src/tests/parser.rs b/crates/aiken-lang/src/tests/parser.rs index 579a7a78..b2a0b4de 100644 --- a/crates/aiken-lang/src/tests/parser.rs +++ b/crates/aiken-lang/src/tests/parser.rs @@ -38,6 +38,60 @@ fn windows_newline() { ) } +#[test] +fn test_fail() { + let code = indoc! {r#" + test fail invalid_inputs() { + expect True = False + + False + } + "#}; + + assert_definitions( + code, + vec![ast::UntypedDefinition::Test(ast::Function { + arguments: vec![], + body: expr::UntypedExpr::Sequence { + location: Span::new((), 31..59), + expressions: vec![ + expr::UntypedExpr::Assignment { + location: Span::new((), 31..50), + value: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 45..50), + name: "False".to_string(), + }), + pattern: ast::UntypedPattern::Constructor { + is_record: false, + location: Span::new((), 38..42), + name: "True".to_string(), + arguments: vec![], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + kind: ast::AssignmentKind::Expect, + annotation: None, + }, + expr::UntypedExpr::Var { + location: Span::new((), 54..59), + name: "False".to_string(), + }, + ], + }, + doc: None, + location: Span::new((), 0..26), + name: "invalid_inputs".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 60, + can_error: true, + })], + ); +} + #[test] fn validator() { let code = indoc! {r#" @@ -54,6 +108,7 @@ fn validator() { doc: None, end_position: 54, fun: Function { + can_error: true, arguments: vec![ ast::Arg { arg_name: ast::ArgName::Named { @@ -128,6 +183,7 @@ fn double_validator() { doc: None, end_position: 90, fun: Function { + can_error: true, arguments: vec![ ast::Arg { arg_name: ast::ArgName::Named { @@ -176,6 +232,7 @@ fn double_validator() { end_position: 52, }, other_fun: Some(Function { + can_error: true, arguments: vec![ ast::Arg { arg_name: ast::ArgName::Named { @@ -487,6 +544,7 @@ fn empty_function() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Trace { kind: ast::TraceKind::Todo, @@ -522,6 +580,7 @@ fn expect() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 19..69), @@ -592,6 +651,7 @@ fn plus_binop() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { label: "a".to_string(), @@ -644,6 +704,7 @@ fn pipeline() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { name: "a".to_string(), @@ -715,6 +776,7 @@ fn if_expression() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::If { location: Span::new((), 13..106), @@ -813,6 +875,7 @@ fn let_bindings() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { label: "a".to_string(), @@ -940,6 +1003,7 @@ fn block() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { label: "a".to_string(), @@ -1034,6 +1098,7 @@ fn when() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { label: "a".to_string(), @@ -1159,6 +1224,7 @@ fn anonymous_function() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 25..83), @@ -1252,6 +1318,7 @@ fn field_access() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![ast::Arg { arg_name: ast::ArgName::Named { label: "user".to_string(), @@ -1302,6 +1369,7 @@ fn call() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 15..108), @@ -1461,6 +1529,7 @@ fn record_update() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![ ast::Arg { arg_name: ast::ArgName::Named { @@ -1555,6 +1624,7 @@ fn record_create_labeled() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(ast::Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Call { arguments: vec![ @@ -1612,6 +1682,7 @@ fn record_create_labeled_with_field_access() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(ast::Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Call { arguments: vec![ @@ -1673,6 +1744,7 @@ fn record_create_unlabeled() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(ast::Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Call { arguments: vec![ @@ -1726,6 +1798,7 @@ fn parse_tuple() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 13..85), @@ -1829,6 +1902,7 @@ fn parse_tuple2() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 13..38), @@ -1957,6 +2031,7 @@ fn base16_bytearray_literals() { tipo: (), }), ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::BinOp { location: Span::new((), 55..80), @@ -1992,6 +2067,7 @@ fn function_def() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, doc: None, arguments: vec![], body: expr::UntypedExpr::Trace { @@ -2026,6 +2102,7 @@ fn function_invoke() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, doc: None, arguments: vec![], body: expr::UntypedExpr::Assignment { @@ -2089,6 +2166,7 @@ fn function_ambiguous_sequence() { code, vec![ ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 15..32), @@ -2121,6 +2199,7 @@ fn function_ambiguous_sequence() { end_position: 34, }), ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 52..69), @@ -2153,6 +2232,7 @@ fn function_ambiguous_sequence() { end_position: 71, }), ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Assignment { location: Span::new((), 89..103), @@ -2184,6 +2264,7 @@ fn function_ambiguous_sequence() { end_position: 104, }), ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 122..153), @@ -2296,6 +2377,7 @@ fn tuple_pattern() { assert_definitions( code, vec![ast::UntypedDefinition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::When { location: Span::new((), 13..49), @@ -2349,6 +2431,7 @@ fn subtraction_vs_negate() { assert_definitions( code, vec![ast::Definition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 14..61), @@ -2465,6 +2548,7 @@ fn clause_guards() { assert_definitions( code, vec![ast::Definition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::When { location: Span::new((), 13..250), @@ -2719,6 +2803,7 @@ fn scope_logical_expression() { assert_definitions( code, vec![ast::Definition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 13..61), @@ -2817,6 +2902,7 @@ fn trace_expressions() { assert_definitions( code, vec![ast::Definition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Sequence { location: Span::new((), 13..131), @@ -2942,6 +3028,7 @@ fn parse_keyword_error() { code, vec![ ast::Definition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Trace { kind: ast::TraceKind::Error, @@ -2963,6 +3050,7 @@ fn parse_keyword_error() { end_position: 38, }), ast::Definition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::When { location: Span::new((), 54..110), @@ -3041,6 +3129,7 @@ fn parse_keyword_todo() { code, vec![ ast::Definition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::Trace { kind: ast::TraceKind::Todo, @@ -3062,6 +3151,7 @@ fn parse_keyword_todo() { end_position: 37, }), ast::Definition::Fn(Function { + can_error: true, arguments: vec![], body: expr::UntypedExpr::When { location: Span::new((), 53..121), diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index ebbe794a..aed4a266 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -197,6 +197,7 @@ impl<'a> Environment<'a> { return_annotation, return_type, end_position, + can_error, }) => { // Lookup the inferred function information let function = self @@ -241,6 +242,7 @@ impl<'a> Environment<'a> { return_type, body, end_position, + can_error, }) } diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index e63586e7..19288ac6 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -160,6 +160,7 @@ fn infer_definition( body, return_annotation, end_position, + can_error, .. }) => { if public && kind.is_validator() { @@ -246,6 +247,7 @@ fn infer_definition( .return_type() .expect("Could not find return type for fn"), body, + can_error, end_position, })) } diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index b97749a0..afa9edf4 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -670,7 +670,12 @@ where let mut programs = Vec::new(); for (input_path, module_name, func_def) in scripts { - let Function { name, body, .. } = func_def; + let Function { + name, + body, + can_error, + .. + } = func_def; if verbose { self.event_listener.handle_event(Event::GeneratingUPLCFor { @@ -711,6 +716,7 @@ where input_path, module_name, name.to_string(), + *can_error, program.try_into().unwrap(), evaluation_hint, ); @@ -737,7 +743,7 @@ where let mut eval_result = script.program.clone().eval(initial_budget); EvalInfo { - success: !eval_result.failed(), + success: !eval_result.failed(script.can_error), script, spent_budget: eval_result.cost(), logs: eval_result.logs(), diff --git a/crates/aiken-project/src/script.rs b/crates/aiken-project/src/script.rs index dd7539a6..58b83882 100644 --- a/crates/aiken-project/src/script.rs +++ b/crates/aiken-project/src/script.rs @@ -8,6 +8,7 @@ pub struct Script { pub input_path: PathBuf, pub module: String, pub name: String, + pub can_error: bool, pub program: Program, pub evaluation_hint: Option, } @@ -19,6 +20,7 @@ impl Script { input_path: PathBuf, module: String, name: String, + can_error: bool, program: Program, evaluation_hint: Option, ) -> Script { @@ -27,6 +29,7 @@ impl Script { module, name, program, + can_error, evaluation_hint, } } diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index abfba2c8..044b87ba 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -80,7 +80,7 @@ fn assert_uplc(source_code: &str, expected: Term, should_fail: bool) { let mut eval = debruijn_program.eval(ExBudget::default()); assert_eq!( - eval.failed(), + eval.failed(false), should_fail, "logs - {}\n", format!("{:#?}", eval.logs()) diff --git a/crates/uplc/src/machine/eval_result.rs b/crates/uplc/src/machine/eval_result.rs index 468ebce9..c35c860b 100644 --- a/crates/uplc/src/machine/eval_result.rs +++ b/crates/uplc/src/machine/eval_result.rs @@ -32,10 +32,14 @@ impl EvalResult { std::mem::take(&mut self.logs) } - pub fn failed(&self) -> bool { - matches!(self.result, Err(_)) - || matches!(self.result, Ok(Term::Error)) - || matches!(self.result, Ok(Term::Constant(ref con)) if matches!(con.as_ref(), Constant::Bool(false))) + pub fn failed(&self, can_error: bool) -> bool { + if can_error { + !matches!(self.result, Err(_)) + } else { + matches!(self.result, Err(_)) + || matches!(self.result, Ok(Term::Error)) + || matches!(self.result, Ok(Term::Constant(ref con)) if matches!(con.as_ref(), Constant::Bool(false))) + } } pub fn result(self) -> Result, Error> {