From 9bb1a88f235f647832180abee1c33242b39ee15c Mon Sep 17 00:00:00 2001 From: microproofs Date: Fri, 21 Apr 2023 17:21:08 -0400 Subject: [PATCH] fix: expect [] on a non-empty list now fails. --- crates/aiken-lang/src/gen_uplc.rs | 13 +- crates/aiken-lang/src/gen_uplc/air.rs | 6 + crates/aiken-lang/src/gen_uplc/stack.rs | 10 + .../aiken-project/src/blueprint/validator.rs | 453 +----------------- crates/aiken-project/src/tests/gen_uplc.rs | 76 ++- 5 files changed, 103 insertions(+), 455 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index c828994c..ab81d460 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -1824,7 +1824,7 @@ impl<'a> CodeGenerator<'a> { value_stack, ); } else { - pattern_stack.let_assignment("_", value_stack); + pattern_stack.list_empty(value_stack); } pattern_stack.merge_child(elements_stack); @@ -4696,6 +4696,17 @@ impl<'a> CodeGenerator<'a> { arg_stack.push(term); } + Air::ListEmpty { .. } => { + let value = arg_stack.pop().unwrap(); + let mut term = arg_stack.pop().unwrap(); + + term = value.delayed_choose_list( + term, + Term::Error.trace(Term::string("Expected no items for List")), + ); + + arg_stack.push(term); + } Air::Tuple { tipo, count, .. } => { let mut args = vec![]; diff --git a/crates/aiken-lang/src/gen_uplc/air.rs b/crates/aiken-lang/src/gen_uplc/air.rs index 6c16a91c..190432a4 100644 --- a/crates/aiken-lang/src/gen_uplc/air.rs +++ b/crates/aiken-lang/src/gen_uplc/air.rs @@ -219,6 +219,9 @@ pub enum Air { FieldsEmpty { scope: Scope, }, + ListEmpty { + scope: Scope, + }, } impl Air { @@ -257,6 +260,7 @@ impl Air { | Air::RecordAccess { scope, .. } | Air::FieldsExpose { scope, .. } | Air::FieldsEmpty { scope } + | Air::ListEmpty { scope } | Air::ListAccessor { scope, .. } | Air::ListExpose { scope, .. } | Air::TupleAccessor { scope, .. } @@ -301,6 +305,7 @@ impl Air { | Air::RecordAccess { scope, .. } | Air::FieldsExpose { scope, .. } | Air::FieldsEmpty { scope } + | Air::ListEmpty { scope } | Air::ListAccessor { scope, .. } | Air::ListExpose { scope, .. } | Air::TupleAccessor { scope, .. } @@ -398,6 +403,7 @@ impl Air { | Air::Finally { .. } | Air::FieldsExpose { .. } | Air::FieldsEmpty { .. } + | Air::ListEmpty { .. } | Air::NoOp { .. } => None, Air::UnOp { op, .. } => match op { UnOp::Not => Some( diff --git a/crates/aiken-lang/src/gen_uplc/stack.rs b/crates/aiken-lang/src/gen_uplc/stack.rs index e6192889..e568480a 100644 --- a/crates/aiken-lang/src/gen_uplc/stack.rs +++ b/crates/aiken-lang/src/gen_uplc/stack.rs @@ -781,6 +781,16 @@ impl AirStack { self.call(void(), expect_stack, vec![tail_stack, arg_stack2]) } + + pub fn list_empty(&mut self, value_stack: AirStack) { + self.new_scope(); + + self.air.push(Air::ListEmpty { + scope: self.scope.clone(), + }); + + self.merge_child(value_stack); + } } #[cfg(test)] diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 628c9135..e3a8f748 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -181,16 +181,8 @@ mod test { use serde_json::{self, json}; use std::collections::HashMap; - use aiken_lang::{ - self, - ast::{Definition, Function}, - builtins, - }; - use uplc::{ - ast::{self as uplc_ast, Constant, Name}, - machine::cost_model::ExBudget, - optimize, BigInt, Constr, PlutusData, - }; + use aiken_lang::{self, builtins}; + use uplc::ast::{self as uplc_ast}; use crate::tests::TestProject; @@ -235,59 +227,6 @@ mod test { assert_json_eq!(serde_json::to_value(validator).unwrap(), expected); } - fn assert_uplc(source_code: &str, expected: Term) { - let mut project = TestProject::new(); - - let modules = CheckedModules::singleton(project.check(project.parse(source_code))); - let mut generator = modules.new_generator( - &project.functions, - &project.data_types, - &project.module_types, - ); - - let Some(checked_module) = modules.values().next() - else { - unreachable!("There's got to be one right?") - }; - - let mut scripts = vec![]; - - for def in checked_module.ast.definitions() { - if let Definition::Test(func) = def { - scripts.push(( - checked_module.input_path.clone(), - checked_module.name.clone(), - func, - )); - } - } - - assert_eq!(scripts.len(), 1); - - let script = &scripts[0]; - - let Function { body, .. } = script.2; - - let program = generator.generate_test(body); - - let debruijn_program: Program = program.try_into().unwrap(); - - let expected = Program { - version: (1, 0, 0), - term: expected, - }; - - let expected = optimize::aiken_optimize_and_intern(expected); - - let expected: Program = expected.try_into().unwrap(); - - assert_eq!(debruijn_program.to_pretty(), expected.to_pretty()); - - let eval = debruijn_program.eval(ExBudget::default()); - - assert!(!eval.failed()) - } - fn fixture_definitions() -> Definitions> { let mut definitions = Definitions::new(); @@ -381,394 +320,6 @@ mod test { ); } - #[test] - fn acceptance_test_6_if_else() { - let src = r#" - test bar() { - let x = 1 - if x == 1 { - True - } else { - False - } - } - "#; - - assert_uplc( - src, - Term::equals_integer() - .apply(Term::integer(1.into())) - .apply(Term::integer(1.into())) - .delayed_if_else(Term::bool(true), Term::bool(false)), - ); - } - - #[test] - fn acceptance_test_1_length() { - let src = r#" - pub fn length(xs: List) -> Int { - when xs is { - [] -> - 0 - [_, ..rest] -> - 1 + length(rest) - } - } - - test length_1() { - length([1, 2, 3]) == 3 - } - "#; - - assert_uplc( - src, - Term::equals_integer() - .apply( - Term::var("length") - .lambda("length") - .apply(Term::var("length").apply(Term::var("length"))) - .lambda("length") - .apply( - Term::var("xs") - .delayed_choose_list( - Term::integer(0.into()), - Term::add_integer() - .apply(Term::integer(1.into())) - .apply( - Term::var("length") - .apply(Term::var("length")) - .apply(Term::var("rest")), - ) - .lambda("rest") - .apply(Term::tail_list().apply(Term::var("xs"))), - ) - .lambda("xs") - .lambda("length"), - ) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - ])), - ) - .apply(Term::integer(3.into())), - ); - } - - #[test] - fn acceptance_test_2_repeat() { - let src = r#" - pub fn repeat(x: a, n: Int) -> List { - if n <= 0 { - [] - } else { - [x, ..repeat(x, n - 1)] - } - } - - test repeat_1() { - repeat("aiken", 2) == ["aiken", "aiken"] - } - "#; - - assert_uplc( - src, - Term::equals_data() - .apply( - Term::list_data().apply( - Term::var("repeat") - .lambda("repeat") - .apply(Term::var("repeat").apply(Term::var("repeat"))) - .lambda("repeat") - .apply( - Term::less_than_equals_integer() - .apply(Term::var("n")) - .apply(Term::integer(0.into())) - .delayed_if_else( - Term::empty_list(), - Term::mk_cons() - .apply(Term::b_data().apply(Term::var("x"))) - .apply( - Term::var("repeat") - .apply(Term::var("repeat")) - .apply(Term::var("x")) - .apply( - Term::sub_integer() - .apply(Term::var("n")) - .apply(Term::integer(1.into())), - ), - ), - ) - .lambda("n") - .lambda("x") - .lambda("repeat"), - ) - .apply(Term::byte_string("aiken".as_bytes().to_vec())) - .apply(Term::integer(2.into())), - ), - ) - .apply(Term::list_data().apply(Term::list_values(vec![ - Constant::Data(PlutusData::BoundedBytes("aiken".as_bytes().to_vec().into())), - Constant::Data(PlutusData::BoundedBytes("aiken".as_bytes().to_vec().into())), - ]))), - ); - } - - #[test] - fn acceptance_test_3_concat() { - let src = r#" - pub fn foldr(xs: List, f: fn(a, b) -> b, zero: b) -> b { - when xs is { - [] -> - zero - [x, ..rest] -> - f(x, foldr(rest, f, zero)) - } - } - - pub fn concat(left: List, right: List) -> List { - foldr(left, fn(x, xs) { [x, ..xs] }, right) - } - - test concat_1() { - concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] - } - "#; - - assert_uplc( - src, - Term::equals_data() - .apply( - Term::list_data().apply( - Term::var("concat") - .lambda("concat") - .apply( - Term::var("foldr") - .apply(Term::var("left")) - .apply( - Term::mk_cons() - .apply(Term::i_data().apply(Term::var("x"))) - .apply(Term::var("xs")) - .lambda("xs") - .lambda("x"), - ) - .apply(Term::var("right")) - .lambda("right") - .lambda("left"), - ) - .lambda("foldr") - .apply(Term::var("foldr").apply(Term::var("foldr"))) - .lambda("foldr") - .apply( - Term::var("xs") - .delayed_choose_list( - Term::var("zero"), - Term::var("f") - .apply(Term::var("x")) - .apply( - Term::var("foldr") - .apply(Term::var("foldr")) - .apply(Term::var("rest")) - .apply(Term::var("f")) - .apply(Term::var("zero")), - ) - .lambda("rest") - .apply(Term::tail_list().apply(Term::var("xs"))) - .lambda("x") - .apply( - Term::un_i_data().apply( - Term::head_list().apply(Term::var("xs")), - ), - ), - ) - .lambda("zero") - .lambda("f") - .lambda("xs") - .lambda("foldr"), - ) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - ])) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), - ])), - ), - ) - .apply(Term::list_data().apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), - ]))), - ); - } - - #[test] - fn acceptance_test_4_concat_no_anon_func() { - let src = r#" - pub fn foldr(xs: List, f: fn(a, b) -> b, zero: b) -> b { - when xs is { - [] -> - zero - [x, ..rest] -> - f(x, foldr(rest, f, zero)) - } - } - - pub fn prepend(x: a, xs: List) -> List { - [x, ..xs] - } - - pub fn concat(left: List, right: List) -> List { - foldr(left, prepend, right) - } - - test concat_1() { - concat([1, 2, 3], [4, 5, 6]) == [1, 2, 3, 4, 5, 6] - } - "#; - - assert_uplc( - src, - Term::equals_data() - .apply( - Term::list_data().apply( - Term::var("concat") - .lambda("concat") - .apply( - Term::var("foldr") - .apply(Term::var("left")) - .apply(Term::var("prepend")) - .apply(Term::var("right")) - .lambda("right") - .lambda("left"), - ) - .lambda("prepend") - .apply( - Term::mk_cons() - .apply(Term::i_data().apply(Term::var("x"))) - .apply(Term::var("xs")) - .lambda("xs") - .lambda("x"), - ) - .lambda("foldr") - .apply(Term::var("foldr").apply(Term::var("foldr"))) - .lambda("foldr") - .apply( - Term::var("xs") - .delayed_choose_list( - Term::var("zero"), - Term::var("f") - .apply(Term::var("x")) - .apply( - Term::var("foldr") - .apply(Term::var("foldr")) - .apply(Term::var("rest")) - .apply(Term::var("f")) - .apply(Term::var("zero")), - ) - .lambda("rest") - .apply(Term::tail_list().apply(Term::var("xs"))) - .lambda("x") - .apply( - Term::un_i_data().apply( - Term::head_list().apply(Term::var("xs")), - ), - ), - ) - .lambda("zero") - .lambda("f") - .lambda("xs") - .lambda("foldr"), - ) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - ])) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), - ])), - ), - ) - .apply(Term::list_data().apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(4.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), - ]))), - ); - } - - #[test] - fn acceptance_test_5_head_not_empty() { - let src = r#" - use aiken/builtin.{head_list} - - pub fn head(xs: List) -> Option { - when xs is { - [] -> None - _ -> Some(head_list(xs)) - } - } - - test head_1() { - head([1, 2, 3]) == Some(1) - } - "#; - - assert_uplc( - src, - Term::equals_data() - .apply( - Term::var("head") - .lambda("head") - .apply( - Term::var("xs") - .delayed_choose_list( - Term::Constant( - Constant::Data(PlutusData::Constr(Constr { - tag: 122, - any_constructor: None, - fields: vec![], - })) - .into(), - ), - Term::constr_data().apply(Term::integer(0.into())).apply( - Term::mk_cons() - .apply(Term::head_list().apply(Term::var("xs"))) - .apply(Term::empty_list()), - ), - ) - .lambda("xs"), - ) - .apply(Term::list_values(vec![ - Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), - Constant::Data(PlutusData::BigInt(BigInt::Int(3.into()))), - ])), - ) - .apply(Term::Constant( - Constant::Data(PlutusData::Constr(Constr { - tag: 121, - any_constructor: None, - fields: vec![PlutusData::BigInt(BigInt::Int(1.into()))], - })) - .into(), - )), - ); - } - #[test] fn mint_parameterized() { assert_validator( diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index 51a18fde..76414ba0 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -11,7 +11,7 @@ use crate::module::CheckedModules; use super::TestProject; -fn assert_uplc(source_code: &str, expected: Term) { +fn assert_uplc(source_code: &str, expected: Term, should_fail: bool) { let mut project = TestProject::new(); let modules = CheckedModules::singleton(project.check(project.parse(source_code))); @@ -59,9 +59,14 @@ fn assert_uplc(source_code: &str, expected: Term) { assert_eq!(debruijn_program.to_pretty(), expected.to_pretty()); - let eval = debruijn_program.eval(ExBudget::default()); + let mut eval = debruijn_program.eval(ExBudget::default()); - assert!(!eval.failed()) + assert_eq!( + eval.failed(), + should_fail, + "logs - {}\n", + format!("{:#?}", eval.logs()) + ) } #[test] @@ -113,6 +118,7 @@ fn acceptance_test_1_length() { ])), ) .apply(Term::integer(3.into())), + false, ); } @@ -172,6 +178,7 @@ fn acceptance_test_2_repeat() { Constant::Data(PlutusData::BoundedBytes("aiken".as_bytes().to_vec().into())), Constant::Data(PlutusData::BoundedBytes("aiken".as_bytes().to_vec().into())), ]))), + false, ); } @@ -266,6 +273,7 @@ fn acceptance_test_3_concat() { Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), ]))), + false, ); } @@ -366,6 +374,7 @@ fn acceptance_test_4_concat_no_anon_func() { Constant::Data(PlutusData::BigInt(BigInt::Int(5.into()))), Constant::Data(PlutusData::BigInt(BigInt::Int(6.into()))), ]))), + false, ); } @@ -425,6 +434,7 @@ fn acceptance_test_5_head_not_empty() { })) .into(), )), + false, ); } @@ -480,6 +490,7 @@ fn acceptance_test_5_head_empty() { })) .into(), )), + false, ); } @@ -502,6 +513,7 @@ fn acceptance_test_6_if_else() { .apply(Term::integer(1.into())) .apply(Term::integer(1.into())) .delayed_if_else(Term::bool(true), Term::bool(false)), + false, ); } @@ -546,6 +558,7 @@ fn acceptance_test_6_equals() { .apply(Term::empty_map()), ), ), + false, ); } @@ -694,6 +707,7 @@ fn acceptance_test_7_unzip() { ) .into(), )), + false, ); } @@ -726,6 +740,7 @@ fn acceptance_test_8_is_empty() { Term::bool(true), Term::bool(true).if_else(Term::bool(false), Term::bool(true)), ), + false, ); } @@ -758,6 +773,7 @@ fn acceptance_test_8_is_not_empty() { Term::bool(false), Term::bool(false).if_else(Term::bool(false), Term::bool(true)), ), + false, ); } @@ -790,6 +806,7 @@ fn acceptance_test_9_is_empty() { Term::bool(true), Term::bool(true).if_else(Term::bool(false), Term::bool(true)), ), + false, ); } @@ -889,6 +906,7 @@ fn acceptance_test_10_map_none() { .into(), )) .constr_get_field(), + false, ); } @@ -988,5 +1006,57 @@ fn acceptance_test_10_map_some() { .into(), )) .constr_get_field(), + false, + ); +} + +#[test] +fn expect_empty_list_on_filled_list() { + let src = r#" + test empty_list1() { + let x = [1,2] + expect [] = x + + True + } + "#; + + assert_uplc( + src, + Term::var("x") + .delayed_choose_list( + Term::bool(true), + Term::Error.trace(Term::string("Expected no items for List")), + ) + .lambda("x") + .apply(Term::list_values(vec![ + Constant::Data(PlutusData::BigInt(BigInt::Int(1.into()))), + Constant::Data(PlutusData::BigInt(BigInt::Int(2.into()))), + ])), + true, + ); +} + +#[test] +fn expect_empty_list_on_new_list() { + let src = r#" + test empty_list1() { + let x = [] + expect [] = x + + True + } + "#; + + assert_uplc( + src, + Term::var("x") + .delayed_choose_list( + Term::bool(true), + Term::Error.trace(Term::string("Expected no items for List")), + ) + .lambda("x") + .apply(Term::list_values(vec![])), + false, ); }