use crate::{ ast::{ Definition, ModuleKind, Pattern, TraceLevel, Tracing, TypedModule, UntypedModule, UntypedPattern, }, builtins, expr::{CallArg, Span, TypedExpr}, parser, tipo::error::{Error, UnifyErrorSituation, Warning}, IdGenerator, }; use std::collections::HashMap; const DEFAULT_MODULE_NAME: &str = "my_module"; const DEFAULT_PACKAGE: &str = "test/project"; fn parse(source_code: &str) -> UntypedModule { parse_as(source_code, DEFAULT_MODULE_NAME) } fn parse_as(source_code: &str, name: &str) -> UntypedModule { let kind = ModuleKind::Lib; let (mut ast, _) = parser::module(source_code, kind).expect("Failed to parse module"); ast.name = name.to_string(); ast } fn check_module( ast: UntypedModule, extra: Vec, kind: ModuleKind, tracing: Tracing, ) -> Result<(Vec, TypedModule), (Vec, Error)> { let id_gen = IdGenerator::new(); let mut warnings = vec![]; let mut module_types = HashMap::new(); module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); for module in extra { let mut warnings = vec![]; if module.name == DEFAULT_MODULE_NAME { panic!("passed extra modules with default name! Use 'parse_as' to define tests instead of 'parse'."); } let typed_module = module .infer( &id_gen, kind, DEFAULT_PACKAGE, &module_types, Tracing::All(TraceLevel::Verbose), &mut warnings, None, ) .expect("extra dependency did not compile"); module_types.insert(typed_module.name.clone(), typed_module.type_info.clone()); } let result = ast.infer( &id_gen, kind, DEFAULT_PACKAGE, &module_types, tracing, &mut warnings, None, ); result .map(|o| (warnings.clone(), o)) .map_err(|e| (warnings, e)) } fn check(ast: UntypedModule) -> Result<(Vec, TypedModule), (Vec, Error)> { check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::verbose()) } fn check_with_verbosity( ast: UntypedModule, level: TraceLevel, ) -> Result<(Vec, TypedModule), (Vec, Error)> { check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::All(level)) } fn check_with_deps( ast: UntypedModule, extra: Vec, ) -> Result<(Vec, TypedModule), (Vec, Error)> { check_module(ast, extra, ModuleKind::Lib, Tracing::verbose()) } fn check_validator( ast: UntypedModule, ) -> Result<(Vec, TypedModule), (Vec, Error)> { check_module(ast, Vec::new(), ModuleKind::Validator, Tracing::verbose()) } #[test] fn bls12_381_elements_in_data_type() { let source_code = r#" type Datum { D0(G1Element) D1(G2Element) } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn bls12_381_ml_result_in_data_type() { let source_code = r#" type Datum { thing: MillerLoopResult } "#; let res = check(parse(source_code)); assert!(matches!(res, Err((_, Error::IllegalTypeInData { .. })))) } #[test] fn validator_illegal_return_type() { let source_code = r#" validator foo { spend(d, r, c) -> Int { 1 } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::ValidatorMustReturnBool { .. })) )) } #[test] fn implicitly_discard_void() { let source_code = r#" pub fn label(str: String) -> Void { trace str Void } "#; let (warnings, _) = check_validator(parse(source_code)).expect("should type-check"); assert!(warnings.is_empty(), "no warnings: {warnings:#?}"); } #[test] fn validator_illegal_arity() { let source_code = r#" validator foo { mint(c) { True } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::IncorrectValidatorArity { .. })) )) } #[test] fn list_illegal_inhabitants() { let source_code = r#" fn main() { [identity] } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::IllegalTypeInData { .. })) )) } #[test] fn tuple_illegal_inhabitants() { let source_code = r#" fn main() { (identity, always) } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::IllegalTypeInData { .. })) )) } #[test] fn illegal_inhabitants_nested() { let source_code = r#" fn main() { [(identity, always)] } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::IllegalTypeInData { .. })) )) } #[test] fn illegal_function_comparison() { let source_code = r#" fn not(x: Bool) -> Bool { todo } fn foo() -> Bool { not == not } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::IllegalComparison { .. })) )) } #[test] fn illegal_inhabitants_returned() { let source_code = r#" type Fuzzer = fn(PRNG) -> (a, PRNG) fn constant(a: a) -> Fuzzer { fn (prng) { (a, prng) } } fn main() -> Fuzzer> { constant(constant(42)) } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::IllegalTypeInData { .. })) )) } #[test] fn illegal_generic_instantiation() { let source_code = r#" type Rec { get_t: t, } fn use_dict(dict: Rec Bool>, b: Bool) -> Bool { let f = dict.get_t f(b) } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::IllegalTypeInData { .. })) )) } #[test] fn not_illegal_top_level_unserialisable() { let source_code = r#" fn foo() -> MillerLoopResult { todo } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn illegal_unserialisable_in_generic_fn() { let source_code = r#" type Foo { foo: a } fn main() -> Foo Bool> { todo } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::IllegalTypeInData { .. })) )) } #[test] fn illegal_unserialisable_in_generic_miller_loop() { let source_code = r#" type Foo { foo: a } fn main() -> Foo { todo } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::IllegalTypeInData { .. })) )) } #[test] fn mark_constructors_as_used_via_field_access() { let source_code = r#" type Datum { D0(D0Params) D1(D1Params) } type D0Params { foo: Int, } type D1Params { bar: Int, } fn spend(d: Datum, _r, _c) { when d is { D0(params) -> params.foo == 1 D1(_params) -> False } } "#; let (warnings, _) = check(parse(source_code)).unwrap(); assert_eq!(warnings.len(), 2) } #[test] fn expect_multi_patterns() { let source_code = r#" fn fold(list: List, initial: b, apply: fn(a, b) -> b) { when list is { [] -> initial [x, ..xs] -> fold(xs, apply(x, initial), apply) } } pub fn foo() { expect Some(x), acc <- fold([Some(1), None], 0) x + acc } "#; let (warnings, _) = check(parse(source_code)).unwrap(); assert_eq!(warnings.len(), 0) } #[test] fn validator_correct_form() { let source_code = r#" validator foo { spend(d: Option, r, oref, c) { True } } "#; assert!(check_validator(parse(source_code)).is_ok()) } #[test] fn validator_in_lib_warning() { let source_code = r#" validator foo { spend(c) { True } } "#; let (warnings, _) = check(parse(source_code)).unwrap(); assert!(matches!( warnings[0], Warning::ValidatorInLibraryModule { .. } )) } #[test] fn multi_validator() { let source_code = r#" validator foo(foo: ByteArray, bar: Int) { spend(_d: Option, _r, _oref, _c) { foo == #"aabb" } mint(_r, _p, _c) { bar == 0 } } "#; let (warnings, _) = check_validator(parse(source_code)).unwrap(); assert_eq!(warnings.len(), 0) } #[test] fn multi_validator_warning() { let source_code = r#" validator foo(foo: ByteArray, bar: Int) { spend(_d: Option, _r, _oref, _c) { foo == #"aabb" } mint(_r, _p, _c) { True } } "#; let (warnings, _) = check_validator(parse(source_code)).unwrap(); assert!(matches!( warnings[0], Warning::UnusedVariable { ref name, .. } if name == "bar" )) } #[test] fn exhaustiveness_simple() { let source_code = r#" type Foo { Bar Baz } fn foo() { let thing = Bar when thing is { Bar -> True } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "Baz" )) } #[test] fn validator_args_no_annotation() { let source_code = r#" validator hello(d) { spend(a: Option, b, oref, c) { True } } "#; let (_, module) = check_validator(parse(source_code)).unwrap(); module.definitions().for_each(|def| { let Definition::Validator(validator) = def else { unreachable!() }; validator.params.iter().for_each(|param| { assert!(param.tipo.is_data()); }); validator.handlers[0] .arguments .iter() .skip(1) .for_each(|arg| { assert!(arg.tipo.is_data()); }) }) } #[test] fn exhaustiveness_missing_empty_list() { let source_code = r#" fn foo() { let thing = [1, 2] when thing is { [a, ..] -> True } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "[]" )) } #[test] fn exhaustiveness_missing_list_wildcards() { let source_code = r#" fn foo() { let thing = [1, 2] when thing is { [] -> True } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "[_, ..]" )) } #[test] fn exhaustiveness_missing_list_wildcards_2() { let source_code = r#" fn foo() { let thing = [1, 2] when thing is { [] -> True [a] -> True } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "[_, _, ..]" )) } #[test] fn exhaustiveness_int() { let source_code = r#" fn foo() { let thing = 1 when thing is { 1 -> True } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "_" )) } #[test] fn exhaustiveness_int_redundant() { let source_code = r#" fn foo() { let thing = 1 when thing is { 1 -> True 1 -> True _ -> True } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::RedundantMatchClause { original: Some(_), .. } )) )) } #[test] fn exhaustiveness_let_binding() { let source_code = r#" fn foo() { let Some(x) = None True } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { is_let, unmatched, .. } )) if unmatched[0] == "None" && is_let )) } #[test] fn exhaustiveness_expect() { let source_code = r#" fn foo() { expect Some(x) = None True } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn exhaustiveness_expect_no_warning() { let source_code = r#" pub type A { int: Int, b: B, } pub type B { B0(Int) B1(Int) } pub fn bad_let(x: A, _: A) { expect A { b: B0(int), .. } = x int > 0 } "#; let (warnings, _) = check(parse(source_code)).unwrap(); assert_eq!(warnings.len(), 0) } #[test] fn exhaustiveness_expect_warning() { let source_code = r#" pub type A { int: Int, b: Int, } pub fn thing(x: A, _: A) { expect A { b, .. } = x b > 0 } "#; let (warnings, _) = check(parse(source_code)).unwrap(); assert!(matches!( warnings[0], Warning::SingleConstructorExpect { .. } )) } #[test] fn exhaustiveness_missing_constr_with_args() { let source_code = r#" type Foo { Bar Why(Int) Baz { other: Int } } fn foo() { let thing = Bar when thing is { Bar -> True } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "Why(_)" && unmatched[1] == "Baz { other }" )) } #[test] fn exhaustiveness_redundant_pattern() { let source_code = r#" type Foo { A B } fn foo(a: Foo) { when a is { A -> todo B -> todo _ -> todo } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::RedundantMatchClause { original: None, .. })) )) } #[test] fn exhaustiveness_redundant_pattern_2() { let source_code = r#" type Foo { A B } fn foo(a: Foo) { when a is { A -> todo B -> todo A -> todo } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::RedundantMatchClause { original: Some(_), .. } )) )) } #[test] fn exhaustiveness_complex() { let source_code = r#" type Foo { Bar Why(Int) Baz { other: Int } } type Hello { Yes No { idk: Int, thing: Foo } } fn foo() { let thing = ((Yes, 1), (Yes, [1, 2])) when thing is { ((Yes, _), (Yes, [])) -> True ((Yes, _), (No { .. }, _)) -> True ((No { .. }, _), (No { .. }, _)) -> True } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "((Yes, _), (Yes, [_, ..]))" && unmatched[1] == "((No { idk, thing }, _), (Yes, _))" )) } #[test] fn exhaustiveness_tuple() { let source_code = r#" fn foo() { when (14, True) is { (14, True) -> Void } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "(_, _)" )) } #[test] fn exhaustiveness_nested_list_and_tuples() { fn assert_step(step: &str, expected: &str) { let result = check(parse(step)); assert!(matches!( result, Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == expected )); } assert_step( r#" fn foo() { let xs : List<(List<(Int, Bool)>, Int)> = [([(14, True)], 42)] when xs is { [ ] -> Void [([(14, True)], 42), ..] -> Void } } "#, "[([], _), ..]", ); assert_step( r#" fn foo() { let xs : List<(List<(Int, Bool)>, Int)> = [([(14, True)], 42)] when xs is { [ ] -> Void [([(_, True)], 42), ..] -> Void [([ ], _), ..] -> Void } } "#, "[([(_, False), ..], _), ..]", ); assert_step( r#" fn foo() { let xs : List<(List<(Int, Bool)>, Int)> = [([(14, True)], 42)] when xs is { [ ] -> Void [([(_, True ) ], 42), ..] -> Void [([ ], _), ..] -> Void [([(_, False), ..], _), ..] -> Void } } "#, "[([(_, True), _, ..], _), ..]", ); assert_step( r#" fn foo() { let xs : List<(List<(Int, Bool)>, Int)> = [([(14, True)], 42)] when xs is { [ ] -> Void [([(_, True ) ], 42), ..] -> Void [([ ], _), ..] -> Void [([(_, False) , ..], _), ..] -> Void [([(_, True ), _, ..], _), ..] -> Void } } "#, "[([(_, True)], _), ..]", ); let source_code = r#" fn foo() { let xs : List<(List<(Int, Bool)>, Int)> = [([(14, True)], 42)] when xs is { [ ] -> Void [([(_, True ) ], 42), ..] -> Void [([ ], _), ..] -> Void [([(_, False) , ..], _), ..] -> Void [([(_, True ), _, ..], _), ..] -> Void [([(_, True ) ], _), ..] -> Void } } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn expect_sugar_correct_type() { let source_code = r#" fn foo() { expect 1 == 1 2 } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn expect_sugar_incorrect_type() { let source_code = r#" fn foo() { expect 1 2 } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn logical_op_chain_expressions_should_be_bool() { let source_code = r#" fn foo() { and { 1 == 1, False, or { 2 == 3, 1 } } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn anonymous_function_scoping() { let source_code = r#" fn reduce(list, f, i) { todo } pub fn foo() { let sum = reduce( [1, 2, 3], fn(acc: Int, n: Int) { acc + n }, 0, ) sum + acc } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::UnknownVariable { name, .. })) if name == "acc" )) } #[test] fn anonymous_function_dupicate_args() { let source_code = r#" fn reduce(list, f, i) { todo } pub fn foo() { let sum = reduce( [1, 2, 3], fn(acc: Int, acc: Int) { acc + acc }, 0, ) sum } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::DuplicateArgument { label, .. })) if label == "acc" )) } #[test] fn assignement_last_expr_when() { let source_code = r#" pub fn foo() { let bar = None when bar is { Some(_) -> { let wow = 1 } None -> { 2 } } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::LastExpressionIsAssignment { .. })) )) } #[test] fn assignement_last_expr_if_first_branch() { let source_code = r#" pub fn foo() { if True { let thing = 1 } else { 1 } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::LastExpressionIsAssignment { .. })) )) } #[test] fn assignement_last_expr_if_branches() { let source_code = r#" pub fn foo() { if True { 2 } else if False { let thing = 1 } else { 1 } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::LastExpressionIsAssignment { .. })) )) } #[test] fn assignement_last_expr_if_final_else() { let source_code = r#" pub fn foo() { if True { 1 } else { let thing = 1 } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::LastExpressionIsAssignment { .. })) )) } #[test] fn assignment_last_expr_logical_chain() { let source_code = r#" pub fn foo() -> Bool { and { expect 1 + 1 == 2, True, 2 > 0, or { expect True, False, } } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::LastExpressionIsAssignment { .. })) )) } #[test] fn if_scoping() { let source_code = r#" pub fn foo(c) { if c { let bar = 1 bar } else if !c { bar } else { bar } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::UnknownVariable { .. })) )) } #[test] fn list_pattern_1() { let source_code = r#" test foo() { let xs = [1, 2, 3] let [x] = xs x == 1 } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::NotExhaustivePatternMatch { .. })) )) } #[test] fn list_pattern_2() { let source_code = r#" test foo() { let xs = [1, 2, 3] let x = when xs is { [x] -> x } x == 1 } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::NotExhaustivePatternMatch { .. })) )) } #[test] fn list_pattern_3() { let source_code = r#" test foo() { let xs = [1, 2, 3] let x = when xs is { [x] -> x [x, ..] -> x } x == 1 } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::NotExhaustivePatternMatch { .. })) )) } #[test] fn list_pattern_4() { let source_code = r#" test foo() { let xs = [1, 2, 3] let x = when xs is { [] -> 1 [_, ..] -> 1 } x == 1 } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn list_pattern_5() { let source_code = r#" test foo() { let xs = [1, 2, 3] let x = when xs is { [x, ..] -> 1 _ -> 1 } x == 1 } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn spread_with_positional_constr_args() { let source_code = r#" type Redeemer { First(Int) Second } fn foo(redeemer: Redeemer) { when redeemer is { First(..) -> True Second -> True } } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn unnecessary_spread_with_positional_constr_args() { let source_code = r#" type Redeemer { First(Int) Second } fn foo(redeemer: Redeemer) { when redeemer is { First(x, ..) -> True Second -> True } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::UnnecessarySpreadOperator { .. })) )) } #[test] fn trace_strings() { let source_code = r#" fn bar() { @"BAR" } test foo() { let msg1 = @"FOO" trace(@"INLINE") trace(msg1) trace(bar()) True } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn trace_non_strings() { let source_code = r#" test foo() { trace(14 + 42) True } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn trace_string_label_compact() { let source_code = r#" test foo() { trace @"foo": [1,2,3] True } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn trace_non_string_label_compact() { let source_code = r#" test foo() { trace(14 + 42) True } "#; assert!(matches!( &check_with_verbosity(parse(source_code), TraceLevel::Compact), Ok((warnings, _)) if warnings == &[Warning::CompactTraceLabelIsNotstring { location: Span::create(40, 7) }], )) } #[test] fn trace_if_false_ok() { let source_code = r#" fn or_func(a: Bool, b: Bool) { (a || b)? } test foo() { or_func(True, False)? } test bar() { let must_be_signed = True must_be_signed? } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn backpassing_basic() { let source_code = r#" fn and_then(opt: Option, then: fn(a) -> Option) -> Option { when opt is { None -> None Some(a) -> then(a) } } fn backpassing(opt_i: Option, opt_j: Option) -> Option { let i <- and_then(opt_i) let j <- and_then(opt_j) Some(i + j) } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn backpassing_expect_simple() { let source_code = r#" fn and_then(opt: Option, then: fn(a) -> Option) -> Option { when opt is { None -> None Some(a) -> then(a) } } fn backpassing(opt_i: Option, opt_j: Option) -> Option { expect 42 <- and_then(opt_i) let j <- and_then(opt_j) Some(j + 42) } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn backpassing_expect_nested() { let source_code = r#" fn and_then(opt: Option, then: fn(Option) -> Option) -> Option { when opt is { None -> None Some(a) -> then(Some(a)) } } fn backpassing(opt_i: Option, opt_j: Option) -> Option { expect Some(i) <- and_then(opt_i) expect Some(j) <- and_then(opt_j) Some(i + j) } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn backpassing_interleaved_capture() { let source_code = r#" fn and_then(opt: Option, then: fn(a) -> Option) -> Option { when opt is { None -> None Some(a) -> then(a) } } fn backpassing(opt_i: Option, opt_j: Option) -> Option { let f = and_then(opt_i, _) let i <- f let g = and_then(opt_j, _) let j <- g Some(i + j) } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn backpassing_patterns() { let source_code = r#" fn and_then(opt: Option, then: fn(a) -> Option) -> Option { when opt is { None -> None Some(a) -> then(a) } } type Foo { foo: Int, } fn backpassing(opt_i: Option, opt_j: Option) -> Option { let Foo { foo: i } <- and_then(opt_i) let Foo { foo: j } <- and_then(opt_j) Some(i + j) } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn backpassing_not_a_function() { let source_code = r#" fn and_then(opt: Option, then: fn(a) -> Option) -> Option { when opt is { None -> None Some(a) -> then(a) } } fn backpassing(opt_i: Option, opt_j: Option) -> Option { let i <- opt_i let j <- and_then(opt_j) Some(i + j) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::NotFn { .. })) )) } #[test] fn backpassing_non_exhaustive_pattern() { let source_code = r#" fn and_then(opt: Option, then: fn(a) -> Option) -> Option { when opt is { None -> None Some(a) -> then(a) } } fn backpassing(opt_i: Option, opt_j: Option) -> Option { let 42 <- and_then(opt_i) let j <- and_then(opt_j) Some(i + j) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::NotExhaustivePatternMatch { .. })) )) } #[test] fn backpassing_unsaturated_fn() { let source_code = r#" fn and_then(opt: Option, then: fn(a) -> Option) -> Option { when opt is { None -> None Some(a) -> then(a) } } fn backpassing(opt_i: Option, opt_j: Option) -> Option { let i <- and_then let j <- and_then(opt_j) Some(i + j) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::IncorrectFieldsArity { .. })) )) } #[test] fn backpassing_expect_type_mismatch() { let source_code = r#" fn and_then(opt: Option, then: fn(a) -> Option) -> Option { when opt is { None -> None Some(a) -> then(a) } } fn backpassing(opt_i: Option, opt_j: Option) -> Option { expect Some(i) <- and_then(opt_i) let j <- and_then(opt_j) Some(i + j) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn backpassing_multi_args() { let source_code = r#" fn fold(list: List, init: b, then: fn(a, b) -> b) -> b { when list is { [] -> init [x, ..rest] -> fold(rest, then(x, init), then) } } fn backpassing() -> Int { let elem, acc <- fold([1, 2, 3], 0) elem + acc } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn backpassing_multi_args_expect() { let source_code = r#" pub type Bar { Foo(Int) Wow(Int) } fn fold(list: List, init: b, then: fn(a, b) -> b) -> b { when list is { [] -> init [x, ..rest] -> fold(rest, then(x, init), then) } } pub fn backpassing() -> Bar { expect Foo(elem), Wow(acc) <- fold([Foo(1), Foo(2), Foo(3)], Wow(0)) Wow(elem + acc) } "#; assert!(matches!(check(parse(source_code)), Ok((warnings, _)) if warnings.is_empty())) } #[test] fn backpassing_multi_args_using_equals() { let source_code = r#" fn fold(list: List, init: b, then: fn(a, b) -> b) -> b { when list is { [] -> init [x, ..rest] -> fold(rest, then(x, init), then) } } fn backpassing() -> Int { let elem, acc = fold([1, 2, 3], 0, fn(elem, acc) { elem + acc }) elem + acc } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::UnexpectedMultiPatternAssignment { .. })) )) } #[test] fn trace_if_false_ko() { let source_code = r#" fn add(a: Int, b: Int) { (a + b)? } test foo() { add(14, 42) == 12 } test bar() { let must_be_signed = #"FF00" must_be_signed? == #"FF00" } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn pipe_with_wrong_type() { let source_code = r#" test foo() { True |> bar } fn bar(n: Int) { n - 1 } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::CouldNotUnify { situation: Some(UnifyErrorSituation::PipeTypeMismatch), .. } )) )) } #[test] fn pipe_with_wrong_type_and_args() { let source_code = r#" test foo() { True |> bar(False) } fn bar(n: Int, l: Bool) { n - 1 } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::CouldNotUnify { situation: Some(UnifyErrorSituation::PipeTypeMismatch), .. } )) )) } #[test] fn pipe_with_right_type_and_wrong_args() { let source_code = r#" test foo() { 1 |> bar(1) } fn bar(n: Int, l: Bool) { n - 1 } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::CouldNotUnify { situation: None, .. } )) )) } #[test] fn pipe_with_wrong_type_and_full_args() { let source_code = r#" test foo() { True |> bar(False) } fn bar(l: Bool) -> fn(Int) -> Int { fn(n: Int) { n - 1 } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::CouldNotUnify { situation: Some(UnifyErrorSituation::PipeTypeMismatch), .. } )) )) } #[test] fn pipe_wrong_arity_partially_applied() { let source_code = r#" fn f(_a: Int, _b: Int, _c: Int) -> Int { todo } test foo() { 0 |> f(0) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::IncorrectFieldsArity { given, expected, .. })) if given == 2 && expected == 3 )) } #[test] fn pipe_wrong_arity_fully_saturated() { let source_code = r#" fn f(_a: Int, _b: Int, _c: Int) -> Int { todo } test foo() { 0 |> f(0, 0, 0) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::NotFn { .. })) )) } #[test] fn pipe_wrong_arity_fully_saturated_return_fn() { let source_code = r#" fn f(_a: Int, _b: Int, _c: Int) -> fn(Int) -> Int { todo } test foo() { (0 |> f(0, 0, 0)) == 0 } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn fuzzer_ok_basic() { let source_code = r#" fn int() -> Fuzzer { todo } test prop(n via int()) { True } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn sampler_ok_basic() { let source_code = r#" fn int() -> Sampler { todo } bench prop(n via int()) { True } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn fuzzer_ok_explicit() { let source_code = r#" fn int(prng: PRNG) -> Option<(PRNG, Int)> { todo } test prop(n via int) { Void } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn fuzzer_ok_list() { let source_code = r#" fn int() -> Fuzzer { todo } fn list(a: Fuzzer) -> Fuzzer> { todo } test prop(xs via list(int())) { True } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn fuzzer_err_unbound() { let source_code = r#" fn any() -> Fuzzer { todo } fn list(a: Fuzzer) -> Fuzzer> { todo } test prop(xs via list(any())) { todo } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::GenericLeftAtBoundary { .. })) )) } #[test] fn fuzzer_err_unify_1() { let source_code = r#" test prop(xs via Void) { todo } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::CouldNotUnify { situation: None, .. } )) )) } #[test] fn fuzzer_err_unify_2() { let source_code = r#" fn any() -> Fuzzer { todo } test prop(xs via any) { todo } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::CouldNotUnify { situation: None, .. } )) )) } #[test] fn fuzzer_err_unify_3() { let source_code = r#" fn list(a: Fuzzer) -> Fuzzer> { todo } fn int() -> Fuzzer { todo } test prop(xs: Int via list(int())) { todo } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::CouldNotUnify { situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch), .. } )) )) } #[test] fn utf8_hex_literal_warning() { let source_code = r#" pub const policy_id = "f43a62fdc3965df486de8a0d32fe800963589c41b38946602a0dc535" "#; let (warnings, _) = check(parse(source_code)).unwrap(); assert!(matches!( warnings[0], Warning::Utf8ByteArrayIsValidHexString { .. } )) } #[test] fn discarded_let_bindings() { let source_code = r#" fn foo() { let result = when 42 is { 1 -> { let unused = "foo" Void } _ -> { Void } } let _ = "foo" result } "#; let (warnings, ast) = check(parse(source_code)).unwrap(); assert!(matches!(warnings[0], Warning::UnusedVariable { ref name, .. } if name == "unused")); assert!(matches!(warnings[1], Warning::DiscardedLetAssignment { ref name, .. } if name == "_")); // Controls that unused let-bindings have been erased from the transformed AST. match ast.definitions.first() { Some(Definition::Fn(def)) => match &def.body { TypedExpr::Sequence { expressions, .. } => { assert_eq!(expressions.len(), 2); assert!( matches!(expressions[1], TypedExpr::Var { .. }), "last expression isn't return variable" ); match &expressions[0] { TypedExpr::Assignment { value, .. } => match **value { TypedExpr::When { ref clauses, .. } => { assert!( matches!(clauses[0].then, TypedExpr::Sequence { ref expressions, ..} if expressions.len() == 1) ) } _ => unreachable!("first expression isn't when/is"), }, _ => unreachable!("first expression isn't assignment"), } } _ => unreachable!("body isn't a Sequence"), }, _ => unreachable!("ast isn't a Fn"), } } #[test] fn backpassing_type_annotation() { let source_code = r#" pub type Foo { foo: Int, } fn transition_fold4( inputs, callback, ) { when inputs is { [] -> { (Foo(1), inputs) } [input, ..remaining_inputs] -> { callback(input)( fn(foo) { transition_fold4( remaining_inputs, callback, ) }, ) } } } pub fn backpassing(x) { let input: Foo <- transition_fold4( x, ) fn(g){ g(if input.foo == 1{ 1 } else { 2 }) } } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn forbid_expect_into_opaque_type_from_data() { let source_code = r#" opaque type Thing { inner: Int } fn bar(n: Data) { expect a: Thing = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::ExpectOnOpaqueType { .. })) )) } #[test] fn forbid_partial_down_casting() { let source_code = r#" type Foo { x: Int } fn bar(n: List) { expect a: List = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn forbid_partial_up_casting() { let source_code = r#" type Foo { x: Int } fn bar(n: List) { expect a: List = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn allow_expect_into_type_from_data() { let source_code = r#" fn bar(n: Data) { expect a: Option = n a } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn forbid_casting_into_type_from_data() { let source_code = r#" fn bar(n: Data) { let a: Option = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn forbid_casting_into_var_from_data_with_ann() { let source_code = r#" fn bar(n: Data) { let a: Option = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn allow_let_rebinding() { let source_code = r#" fn bar(n: Data) { let a = n a } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn expect_rebinding_requires_annotation() { let source_code = r#" fn bar(n: Data) -> Option { expect a = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CastDataNoAnn { .. })) )) } #[test] fn forbid_casting_into_var_from_data_with_ann_indirect() { let source_code = r#" fn bar(n: Data) -> Option { let a = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn forbid_casting_into_pattern_from_data() { let source_code = r#" fn bar(n: Data) { let Some(a) = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn allow_expect_into_monomorphic_type_from_data_with_pattern() { let source_code = r#" fn bar(n: Data) { expect Some(a): Option = n a } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn forbid_expect_into_generic_type_from_data_with_pattern() { let source_code = r#" fn bar(n: Data) { expect Some(a) = n a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CastDataNoAnn { .. })) )) } #[test] fn allow_generic_expect_without_typecast() { let source_code = r#" pub fn unwrap(opt: Option) -> a { expect Some(a) = opt a } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn allow_expect_into_custom_type_from_data_no_annotation() { let source_code = r#" type OrderDatum { requested_handle: ByteArray, amount: Int, other: Bool, } fn foo(datum: Data) { expect OrderDatum { requested_handle, .. } = datum requested_handle } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn forbid_expect_from_arbitrary_type() { let source_code = r#" type Foo { x: Int } type Bar { y: Int } fn bar(f: Foo) { expect b: Bar = f Void } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn allow_expect_into_opaque_type_constructor_without_typecasting_in_module() { let source_code = r#" opaque type Thing { Foo(Int) Bar(Int) } fn bar(thing: Thing) { expect Foo(a) = thing a } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn use_imported_type_as_namespace_for_patterns() { let dependency = r#" pub type Credential { VerificationKey(ByteArray) Script(ByteArray) } "#; let source_code = r#" use cardano/address.{Credential, Script, VerificationKey} pub fn compare(left: Credential, right: Credential) -> Ordering { when left is { Script(left) -> when right is { Script(right) -> Equal _ -> Less } VerificationKey(left) -> when right is { Script(_) -> Greater VerificationKey(right) -> Equal } } } "#; let result = check_with_deps( parse(source_code), vec![(parse_as(dependency, "cardano/address"))], ); assert!(matches!(result, Ok(..)), "{result:#?}"); } #[test] fn use_type_as_namespace_for_patterns() { let dependency = r#" pub type Foo { A { a: Int } B } "#; let source_code = r#" use thing.{Foo} fn bar(foo: Foo) { when foo is { Foo.A { a } -> a > 10 Foo.B -> False } } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]); assert!(matches!(result, Ok(..)), "{result:#?}"); } #[test] fn use_nested_type_as_namespace_for_patterns() { let dependency = r#" pub type Foo { A { a: Int } B } "#; let source_code = r#" use foo.{Foo} fn bar(x: Foo) { when x is { foo.Foo.A { a } -> a > 10 foo.Foo.B -> False } } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!(matches!(result, Ok(..)), "{result:#?}"); } #[test] fn use_opaque_type_as_namespace_for_patterns_fails() { let dependency = r#" pub opaque type Foo { A { a: Int } B } "#; let source_code = r#" use thing.{Foo} fn bar(foo: Foo) { when foo is { Foo.A { a } -> a > 10 Foo.B -> False } } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]); assert!( matches!( &result, Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "A" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn use_wrong_type_as_namespace_for_patterns_fails() { let dependency = r#" pub type Foo { A { a: Int } B } pub type Bar { C D } "#; let source_code = r#" use thing.{Foo} fn bar(foo: Foo) { when foo is { Foo.A { .. } -> True Foo.D -> False } } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]); assert!( matches!( &result, Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "D" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn use_type_as_namespace_for_constructors() { let dependency = r#" pub type Foo { I { i: Int } B(Bool) } "#; let source_code = r#" use foo.{Foo} test my_test() { and { Foo.I { i: 42 } == foo.I(42), foo.B(True) == Foo.B(True), } } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!(matches!(result, Ok(..)), "{result:#?}"); } #[test] fn use_type_as_nested_namespace_for_constructors() { let dependency = r#" pub type Foo { I { i: Int } B(Bool) } "#; let source_code = r#" use foo test my_test() { trace foo.Foo.I { i: 42 } trace foo.Foo.B(False) Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!(matches!(result, Ok(..)), "{result:#?}"); } #[test] fn use_type_as_nested_namespace_for_constructors_from_multi_level_module() { let dependency = r#" pub type Foo { I { i: Int } B(Bool) } "#; let source_code = r#" use foo/bar test my_test() { trace bar.Foo.I { i: 42 } trace bar.Foo.B(True) Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo/bar"))]); assert!(matches!(result, Ok(..)), "{result:#?}"); } #[test] fn use_type_as_nested_namespace_for_constructors_from_module_alias() { let dependency = r#" pub type Foo { I { i: Int } B(Bool) } "#; let source_code = r#" use foo as bar test my_test() { trace bar.Foo.I { i: 42 } trace bar.Foo.B(True) Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!(matches!(result, Ok(..)), "{result:#?}"); } #[test] fn use_type_as_namespace_unknown_constructor() { let dependency = r#" pub type Foo { I { i: Int } B(Bool) } "#; let source_code = r#" use foo.{Foo} test my_test() { Foo.I(42) == Foo.A } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "A" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn use_wrong_type_as_namespace() { let dependency = r#" pub type Foo { I { i: Int } B(Bool) } pub type Bar { S { s: String } L(List) } "#; let source_code = r#" use foo.{Foo} test my_test() { trace Foo.S(@"wut") Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "S" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn use_wrong_nested_type_as_namespace() { let dependency = r#" pub type Foo { I { i: Int } B(Bool) } pub type Bar { S { s: String } L(List) } "#; let source_code = r#" use foo test my_test() { trace foo.Foo.S(@"wut") Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "S" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn use_private_type_as_nested_namespace_fails() { let dependency = r#" type Foo { I { i: Int } B(Bool) } "#; let source_code = r#" use foo test my_test() { trace foo.Foo.I { i: 42 } Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::UnknownModuleType { name, .. })) if name == "Foo" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn use_opaque_type_as_namespace_for_constructors_fails() { let dependency = r#" pub opaque type Foo { I { i: Int } B(Bool) } "#; let source_code = r#" use foo.{Foo} test my_test() { trace Foo.I(42) Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "I" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn use_opaque_type_as_nested_namespace_for_constructors_fails() { let dependency = r#" pub opaque type Foo { I { i: Int } B(Bool) } "#; let source_code = r#" use foo test my_test() { trace foo.Foo.I(42) Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "I" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn use_non_imported_module_as_namespace() { let dependency = r#" pub type Foo { I(Int) B(Bool) } "#; let source_code = r#" test my_test() { trace foo.Foo.I(14) Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::UnknownModule { name, .. })) if name == "foo" && warnings.is_empty(), ), "{result:#?}" ); } #[test] fn invalid_type_field_access_chain() { let dependency = r#" pub type Foo { I(Int) B(Bool) } "#; let source_code = r#" use foo.{Foo} test my_test() { trace Foo.I.Int(42) Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::InvalidFieldAccess { .. })) if warnings.is_empty(), ), "{result:#?}" ); } #[test] fn invalid_type_field_access_chain_2() { let dependency = r#" pub type Foo { I(Int) B(Bool) } "#; let source_code = r#" use foo.{Foo} test my_test() { trace Foo.i(42) Void } "#; let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); assert!( matches!( &result, Err((warnings, Error::UnknownTypeConstructor { .. })) if warnings.is_empty(), ), "{result:#?}" ); } #[test] fn forbid_importing_or_using_opaque_constructors() { let dependency = r#" pub opaque type Thing { Foo(Int) Bar(Int) } "#; let source_code = r#" use thing.{Thing, Foo} fn bar(thing: Thing) { expect Foo(a) = thing a } "#; assert!(matches!( check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))],), Err((_, Error::UnknownModuleField { .. })), )); let source_code = r#" use thing.{Thing} fn bar(thing: Thing) { expect Foo(a) = thing a } "#; assert!(matches!( check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))],), Err((_, Error::UnknownTypeConstructor { .. })), )); } #[test] fn forbid_expect_into_opaque_type_constructor_with_typecasting() { let source_code = r#" opaque type Thing { Foo(Int) Bar(Int) } fn bar(data: Data) { expect Foo(a): Thing = data a } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::ExpectOnOpaqueType { .. })) )) } #[test] fn forbid_expect_into_nested_opaque_in_record_without_typecasting() { let source_code = r#" opaque type Thing { inner: Int } type Foo { foo: Thing } fn bar(f: Foo) { expect Foo { foo: Thing { inner } } : Foo = f Void } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn forbid_expect_into_nested_opaque_in_record_with_typecasting() { let source_code = r#" opaque type Thing { inner: Int } type Foo { foo: Thing } fn bar(a: Data) { expect Foo { foo: Thing { inner } } : Foo = a Void } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::ExpectOnOpaqueType { .. })) )) } #[test] fn forbid_expect_into_nested_opaque_in_list() { let source_code = r#" opaque type Thing { inner: Int } fn bar(a: Data) { expect [x]: List = [a] Void } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::ExpectOnOpaqueType { .. })) )) } #[test] fn allow_expect_on_var_patterns_that_are_opaque() { let source_code = r#" opaque type Thing { inner: Int } fn bar(a: Option) { expect Some(thing) = a thing.inner } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn can_down_cast_to_data_always() { let source_code = r#" pub opaque type Foo { x: Int } pub fn bar(a: Foo) { let b: Data = a b } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn can_down_cast_to_data_on_fn_call() { let source_code = r#" pub type Foo { Foo } pub fn serialise(data: Data) -> ByteArray { "" } test foo() { serialise(Foo) == "" } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn can_down_cast_to_data_on_pipe() { let source_code = r#" pub type Foo { Foo } pub fn serialise(data: Data) -> ByteArray { "" } test foo() { (Foo |> serialise) == "" } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn correct_span_for_backpassing_args() { let source_code = r#" fn fold(list: List, acc: b, f: fn(a, b) -> b) -> b { when list is { [] -> acc [x, ..xs] -> fold(xs, f(x, acc), f) } } pub fn sum(list: List) -> Int { let a, b <- fold(list, 0) a + 1 } "#; let (warnings, _ast) = check(parse(source_code)).unwrap(); assert!( matches!(&warnings[0], Warning::UnusedVariable { ref name, location } if name == "b" && location.start == 245 && location.end == 246) ); } #[test] fn allow_discard_for_backpassing_args() { let source_code = r#" fn fold(list: List, acc: b, f: fn(a, b) -> b) -> b { when list is { [] -> acc [x, ..xs] -> fold(xs, f(x, acc), f) } } pub fn sum(list: List) -> Int { let a, _b <- fold(list, 0) a + 1 } "#; let (warnings, _ast) = check(parse(source_code)).unwrap(); assert_eq!(warnings.len(), 0); } #[test] fn validator_private_type_leak() { let source_code = r#" type Datum { foo: Int, } type Redeemer { bar: Int, } validator bar { spend(datum: Option, redeemer: Redeemer, _oref, _ctx) { expect Some(d) = datum d.foo == redeemer.bar } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::PrivateTypeLeak { .. })) )) } #[test] fn validator_public() { let source_code = r#" pub type Datum { foo: Int, } pub type Redeemer { bar: Int, } validator bar { spend(datum: Option, redeemer: Redeemer, _oref, _ctx) { expect Some(d) = datum d.foo == redeemer.bar } } "#; assert!(check_validator(parse(source_code)).is_ok()) } #[test] fn tuple_access_on_call() { let source_code = r#" use aiken/builtin pub fn list_at(xs: List, index: Int) -> a { if index == 0 { builtin.head_list(xs) } else { list_at(builtin.tail_list(xs), index - 1) } } fn foo() { [list_at([(1, 2)], 0).2nd, ..[1, 2]] } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn partial_eq_call_args() { let source_code = r#" fn foo(a: Int, b: Int, c: Bool) -> Int { todo } fn main() -> Int { foo(14, 42) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::IncorrectFieldsArity { .. })) )); } #[test] fn partial_eq_callback_args() { let source_code = r#" fn foo(cb: fn(Int, Int, Bool) -> Int) -> Int { todo } fn main() -> Int { foo(fn(a, b) { a + b }) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )); } #[test] fn partial_eq_callback_return() { let source_code = r#" fn foo(cb: fn(Int, Int) -> (Int, Int, Bool)) -> Int { todo } fn main() -> Int { foo(fn(a, b) { (a, b) }) } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )); } #[test] fn pair_access_on_call() { let source_code = r#" use aiken/builtin pub fn list_at(xs: List, index: Int) -> a { if index == 0 { builtin.head_list(xs) } else { list_at(builtin.tail_list(xs), index - 1) } } fn foo() { [list_at([Pair(1, 2)], 0).2nd, ..[1, 2]] } "#; assert!(check(parse(source_code)).is_ok()) } #[test] fn pair_index_out_of_bound() { let source_code = r#" pub fn foo() { Pair(1, 2).3rd } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::PairIndexOutOfBound { .. })) )) } #[test] fn not_indexable() { let source_code = r#" pub fn foo() { "foo".1st } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::NotIndexable { .. })) )) } #[test] fn out_of_scope_access() { let source_code = r#" pub fn a(x: Int) { b(x) } fn b(y: Int) { x + y } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::UnknownVariable { .. })) )) } #[test] fn mutually_recursive_1() { let source_code = r#" pub fn foo(x) { bar(x) } pub fn bar(y) { foo(y) } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn fn_single_variant_pattern() { let source_code = r#" pub type Foo { a: Int } pub fn foo(Foo { a }) { a + 1 } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn fn_multi_variant_pattern() { let source_code = r#" type Foo { A { a: Int } B { b: Int } } pub fn foo(A { a }) { a + 1 } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::NotExhaustivePatternMatch { .. })) )) } #[test] fn if_soft_cast() { let source_code = r#" pub type Foo { a: Int } pub fn foo(foo: Data) -> Int { if foo is bar: Foo { bar.a } else { 0 } } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn if_soft_cast_sugar() { let source_code = r#" pub type Foo { a: Int } pub fn foo(foo: Data) -> Int { if foo is Foo { foo.a } else { 0 } } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn if_soft_cast_record() { let source_code = r#" pub type Foo { a: Int } pub fn foo(foo: Data) -> Int { if foo is Foo { a }: Foo { a } else { 0 } } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn if_soft_cast_no_scope_leak() { let source_code = r#" pub type Foo { a: Int } pub fn foo(foo: Data) -> Int { if foo is bar: Foo { bar.a } else { bar } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::UnknownVariable { name, .. })) if name == "bar" )) } #[test] fn if_soft_cast_no_scope_leak_2() { let source_code = r#" pub type Foo { a: Int } pub fn foo(foo: Data) -> Int { if foo is Foo { a }: Foo { a } else { a } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::UnknownVariable { name, .. })) if name == "a" )) } #[test] fn if_soft_cast_unused_pattern() { let source_code = r#" pub type Foo { a: Int } pub fn foo(foo: Data) -> Int { if foo is Foo { a }: Foo { 1 } else { 0 } } "#; let (warnings, _ast) = check(parse(source_code)).unwrap(); assert!(matches!( warnings[0], Warning::UnusedVariable { ref name, .. } if name == "a" )) } #[test] fn if_soft_cast_not_data() { let source_code = r#" pub type Foo { Bar { a: Int } Buzz { b: Int } } pub fn foo(foo: Foo) -> Int { if foo is Bar { a }: Foo { a } else { 0 } } "#; let (warnings, _ast) = check(parse(source_code)).unwrap(); assert!(matches!(warnings[0], Warning::UseWhenInstead { .. })) } #[test] fn side_effects() { let source_code = r#" pub fn side_effects() { trace "Aiken, rocks!" Void } pub fn foo() { expect _ = side_effects() True } "#; let (warnings, ast) = check(parse(source_code)).unwrap(); assert!(warnings.is_empty(), "no warnings: {warnings:#?}"); if let Some(Definition::Fn(ref foo)) = ast.definitions().last() { if let TypedExpr::Sequence { ref expressions, .. } = foo.body { matches!( expressions[..], [ TypedExpr::Assignment { pattern: Pattern::Discard { .. }, .. }, TypedExpr::Var { .. }, ], ); } else { unreachable!(); } } else { unreachable!(); } } #[test] fn pattern_bytearray() { let source_code = r#" pub fn main(foo: ByteArray) { when foo is { #[1, 2, 3] -> True #"00ff" -> True "Aiken, rocks!" -> True _ -> False } } "#; let result = check(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert!(warnings.is_empty(), "no warnings: {warnings:#?}"); } #[test] fn pattern_bytearray_not_unify_clause_list() { let source_code = r#" pub fn main(foo: ByteArray) { when foo is { [1, 2, 3] -> True _ -> False } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn pattern_bytearray_not_unify_clause_int() { let source_code = r#" pub fn main(foo: ByteArray) { when foo is { 42 -> True _ -> False } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn pattern_bytearray_not_unify_subject() { let source_code = r#" pub fn main(foo: String) { when foo is { "42" -> True _ -> False } } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn recover_no_assignment_sequence() { let source_code = r#" pub fn main() { let result = 42 expect result + 1 == 43 } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn recover_no_assignment_fn_body() { let source_code = r#" pub fn is_bool(foo: Data) -> Void { expect _: Bool = foo } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn recover_no_assignment_when_clause() { let source_code = r#" pub fn main(foo) { when foo is { [] -> Void [x, ..] -> expect _: Int = x } } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn recover_no_assignment_fn_if_then_else() { let source_code = r#" pub fn foo(weird_maths) -> Void { if weird_maths { expect 1 == 2 } else { expect 1 + 1 == 2 } } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn test_return_explicit_void() { let source_code = r#" test foo() { Void } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn test_return_implicit_void() { let source_code = r#" test foo() { let data: Data = 42 expect _: Int = data } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn test_return_illegal() { let source_code = r#" test foo() { 42 } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::IllegalTestType { .. })) )) } #[test] fn validator_by_name() { let source_code = r#" validator foo { mint(_redeemer: Data, policy_id: ByteArray, _self: Data) { policy_id == "foo" } } test test_1() { foo.mint(Void, "foo", Void) } "#; assert!(check_validator(parse(source_code)).is_ok()) } #[test] fn validator_by_name_with_params() { let source_code = r#" validator foo(_thing: Data) { mint(_redeemer: Data, policy_id: ByteArray, _self: Data) { policy_id == "foo" } } test test_1() { foo.mint(Void, Void, "foo", Void) } "#; assert!(check_validator(parse(source_code)).is_ok()) } #[test] fn validator_by_name_unknown_handler() { let source_code = r#" validator foo { mint(_redeemer: Data, policy_id: ByteArray, _self: Data) { policy_id == "foo" } } test foo() { foo.bar(Void, "foo", Void) } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::UnknownValidatorHandler { .. })) )) } #[test] fn validator_by_name_module_duplicate() { let source_code = r#" use aiken/builtin validator builtin { mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) { True } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::DuplicateName { .. })) )) } #[test] fn validator_by_name_validator_duplicate_1() { let source_code = r#" validator foo { mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) { True } } validator foo { mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) { True } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::DuplicateName { .. })) )) } #[test] fn validator_by_name_validator_duplicate_2() { let source_code = r#" validator foo { mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) { True } mint(_redeemer: Data, _policy_id: ByteArray, _self: Data) { True } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::DuplicateName { .. })) )) } #[test] fn exhaustive_handlers() { let source_code = r#" validator foo { mint(_redeemer, _policy_id, _self) { True } spend(_datum, _redeemer, _policy_id, _self) { True } withdraw(_redeemer, _account, _self) { True } publish(_redeemer, _certificate, _self) { True } vote(_redeemer, _voter, _self) { True } propose(_redeemer, _proposal, _self) { True } } "#; assert!(check_validator(parse(source_code)).is_ok()) } #[test] fn extraneous_fallback_on_exhaustive_handlers() { let source_code = r#" validator foo { mint(_redeemer, _policy_id, _self) { True } spend(_datum, _redeemer, _policy_id, _self) { True } withdraw(_redeemer, _account, _self) { True } publish(_redeemer, _certificate, _self) { True } vote(_redeemer, _voter, _self) { True } propose(_redeemer, _proposal, _self) { True } else (_) -> Bool { fail } } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::UnexpectedValidatorFallback { .. })) )) } #[test] fn constant_usage() { let source_code = r#" pub const some_bool_constant: Bool = True const some_int_constant: Int = 42 const some_string_constant: String = @"Aiken" test foo() { some_int_constant == 42 } "#; let result = check(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert!(matches!( &warnings[..], [Warning::UnusedPrivateModuleConstant { name, .. }] if name == "some_string_constant" )); } #[test] fn wrong_arity_on_known_builtin() { let source_code = r#" const foo: Option = Some() "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::IncorrectFunctionCallArity { .. })) )) } #[test] fn softcasting_unused_let_binding() { let source_code = r#" pub fn is_int(data: Data) -> Bool { if data is Int { True } else { False } } "#; let result = dbg!(check(parse(source_code))); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert!(warnings.is_empty(), "should not contain any warnings"); } #[test] fn dangling_let_in_block() { let source_code = r#" fn for_each(xs: List, with: fn(Int) -> a) -> a { todo } test foo() { { let _ <- for_each([1, 2, 3]) } } "#; let result = check_validator(parse(source_code)); assert!( matches!(result, Err((_, Error::LastExpressionIsAssignment { .. }))), "{result:?}" ) } #[test] fn default_trace_return() { let source_code = r#" fn debug() { trace @"patate": Void } test foo() { debug() True } "#; assert!(matches!(check_validator(parse(source_code)), Ok(..))) } #[test] fn dangling_trace_let_standalone() { let source_code = r#" test foo() { trace @"foo" let True = True } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::LastExpressionIsAssignment { .. })) )) } #[test] fn dangling_trace_let_in_sequence() { let source_code = r#" test foo() { let predicate = True trace @"foo" let result = predicate } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::LastExpressionIsAssignment { .. })) )) } #[test] fn dangling_trace_let_in_trace() { let source_code = r#" test foo() { trace @"foo" trace @"bar" let result = True } "#; assert!(matches!( check_validator(parse(source_code)), Err((_, Error::LastExpressionIsAssignment { .. })) )) } #[test] fn destructuring_validator_params_tuple() { let source_code = r#" validator foo((x, y): (Int, Int)) { mint(_redeemer, _policy_id, _self) { x + y > 42 } else(_) { fail } } "#; let result = check_validator(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert!( matches!(&warnings[..], &[]), "should be empty: {warnings:#?}" ); } #[test] fn destructuring_validator_params_record() { let source_code = r#" pub type Foo { Foo(Int, Int) } validator foo(Foo(x, y): Foo) { mint(_redeemer, _policy_id, _self) { x + y > 42 } else(_) { fail } } "#; let result = check_validator(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert!( matches!(&warnings[..], &[]), "should be empty: {warnings:#?}" ); } #[test] fn constant_generic_lambda() { let source_code = r#"const foo: fn(a) -> List = fn(x: a) { [x] }"#; assert!(matches!( check(parse(source_code)), Err((_, Error::GenericLeftAtBoundary { .. })) )) } #[test] fn constant_generic_mismatch() { let source_code = r#"const foo: List = [42]"#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn constant_generic_inferred_1() { let source_code = r#"const foo = [42]"#; assert!(check(parse(source_code)).is_ok()); } #[test] fn constant_generic_inferred_2() { let source_code = r#" const foo = fn(x) { [x] }(42) test my_test() { foo == [42] } "#; assert!(check(parse(source_code)).is_ok()); } #[test] fn constant_generic_inferred_3() { let source_code = r#"const foo: List = fn(x) { [x] }(42)"#; assert!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[test] fn constant_generic_empty() { let source_code = r#"const foo: List = []"#; assert!(check_validator(parse(source_code)).is_ok()); } #[test] fn unused_record_fields_1() { let source_code = r#" pub type Foo { Foo { field0: Bool, field1: Bool } Bar } test confusing() { let foo = Foo(True, True) when foo is { Foo(field1, field0) -> field0 || field1 _ -> False } } "#; let result = check_validator(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert_eq!( warnings[0], Warning::UnusedRecordFields { location: Span::create(193, 19), suggestion: UntypedPattern::Constructor { is_record: true, location: Span::create(193, 19), name: "Foo".to_string(), arguments: vec![ CallArg { label: Some("field0".to_string()), location: Span::create(197, 6), value: Pattern::Var { location: Span::create(197, 6), name: "field1".to_string() } }, CallArg { label: Some("field1".to_string()), location: Span::create(205, 6), value: Pattern::Var { location: Span::create(205, 6), name: "field0".to_string() } } ], module: None, constructor: (), spread_location: None, tipo: () } } ); } #[test] fn unused_record_fields_2() { let source_code = r#" pub type Foo { Foo { field0: Bool, field1: Bool } Bar } test confusing() { let foo = Foo(True, True) when foo is { Foo(field0, field1) -> field0 || field1 _ -> False } } "#; let result = check_validator(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert_eq!( warnings[0], Warning::UnusedRecordFields { location: Span::create(193, 19), suggestion: UntypedPattern::Constructor { is_record: true, location: Span::create(193, 19), name: "Foo".to_string(), arguments: vec![ CallArg { label: Some("field0".to_string()), location: Span::create(197, 6), value: Pattern::Var { location: Span::create(197, 6), name: "field0".to_string() } }, CallArg { label: Some("field1".to_string()), location: Span::create(205, 6), value: Pattern::Var { location: Span::create(205, 6), name: "field1".to_string() } } ], module: None, constructor: (), spread_location: None, tipo: () } } ); } #[test] fn unused_record_fields_3() { let source_code = r#" pub type Foo { Foo { field0: Bool, field1: Bool } Bar } test confusing() { let foo = Foo(True, True) when foo is { Foo(a, _) -> a _ -> False } } "#; let result = check_validator(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert_eq!( warnings[0], Warning::UnusedRecordFields { location: Span::create(193, 9), suggestion: UntypedPattern::Constructor { is_record: true, location: Span::create(193, 9), name: "Foo".to_string(), arguments: vec![CallArg { label: Some("field0".to_string()), location: Span::create(197, 6), value: Pattern::Var { location: Span::create(197, 1), name: "a".to_string() } },], module: None, constructor: (), spread_location: Some(Span::create(199, 2)), tipo: () } } ); } #[test] fn unused_record_fields_4() { let source_code = r#" pub type Foo { Foo { field0: Bool, field1: Bool } Bar } test confusing() { let foo = Foo(True, True) when foo is { Foo(a, ..) -> a _ -> False } } "#; let result = check_validator(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert_eq!( warnings[0], Warning::UnusedRecordFields { location: Span::create(193, 10), suggestion: UntypedPattern::Constructor { is_record: true, location: Span::create(193, 10), name: "Foo".to_string(), arguments: vec![CallArg { label: Some("field0".to_string()), location: Span::create(197, 6), value: Pattern::Var { location: Span::create(197, 1), name: "a".to_string() } },], module: None, constructor: (), spread_location: Some(Span::create(200, 2)), tipo: () } } ); } #[test] fn unused_record_fields_5() { let source_code = r#" pub type Foo { b_is_before_a: Bool, a_is_after_b: Bool, } test confusing() { let Foo(a, b) = Foo(True, True) a || b } "#; let result = check_validator(parse(source_code)); assert!(result.is_ok()); let (warnings, _) = result.unwrap(); assert_eq!( warnings[0], Warning::UnusedRecordFields { location: Span::create(137, 9), suggestion: UntypedPattern::Constructor { is_record: true, location: Span::create(137, 9), name: "Foo".to_string(), arguments: vec![ CallArg { label: Some("b_is_before_a".to_string()), location: Span::create(141, 13), value: Pattern::Var { location: Span::create(141, 1), name: "a".to_string() } }, CallArg { label: Some("a_is_after_b".to_string()), location: Span::create(144, 12), value: Pattern::Var { location: Span::create(144, 1), name: "b".to_string() } } ], module: None, constructor: (), spread_location: None, tipo: () } } ); }