use crate::{ ast::{Definition, ModuleKind, TraceLevel, Tracing, TypedModule, UntypedModule}, builtins, expr::TypedExpr, parser, tipo::error::{Error, UnifyErrorSituation, Warning}, IdGenerator, }; use std::collections::HashMap; fn parse(source_code: &str) -> UntypedModule { let kind = ModuleKind::Lib; let (ast, _) = parser::module(source_code, kind).expect("Failed to parse module"); ast } fn check_module( ast: UntypedModule, extra: Vec<(String, UntypedModule)>, kind: ModuleKind, ) -> 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 (package, module) in extra { let mut warnings = vec![]; let typed_module = module .infer( &id_gen, kind, &package, &module_types, Tracing::All(TraceLevel::Verbose), &mut warnings, ) .expect("extra dependency did not compile"); module_types.insert(package.clone(), typed_module.type_info.clone()); } let result = ast.infer( &id_gen, kind, "test/project", &module_types, Tracing::All(TraceLevel::Verbose), &mut warnings, ); 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) } fn check_with_deps( ast: UntypedModule, extra: Vec<(String, UntypedModule)>, ) -> Result<(Vec, TypedModule), (Vec, Error)> { check_module(ast, extra, ModuleKind::Lib) } fn check_validator( ast: UntypedModule, ) -> Result<(Vec, TypedModule), (Vec, Error)> { check_module(ast, Vec::new(), ModuleKind::Validator) } #[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)); dbg!(&res); assert!(matches!(res, Err((_, Error::IllegalTypeInData { .. })))) } #[test] fn validator_illegal_return_type() { let source_code = r#" validator { fn foo(d, r, c) { 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 { fn foo(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_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 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, } validator { fn foo(d: Datum, _r, _c) { when d is { D0(params) -> params.foo == 1 D1(_params) -> False } } } "#; let (warnings, _) = check_validator(parse(source_code)).unwrap(); assert_eq!(warnings.len(), 1) } #[test] fn validator_correct_form() { let source_code = r#" validator { fn foo(d, r, c) { True } } "#; assert!(check_validator(parse(source_code)).is_ok()) } #[test] fn validator_in_lib_warning() { let source_code = r#" validator { fn foo(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: ByteArray, bar: Int) { fn spend(_d, _r, _c) { foo == #"aabb" } fn mint(_r, _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: ByteArray, bar: Int) { fn spend(_d, _r, _c) { foo == #"aabb" } fn mint(_r, _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(d) { fn foo(a, b, 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.fun.arguments.iter().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 exhaustiveness_guard() { let source_code = r#" fn foo() { when [(True, 42)] is { [(True, x), ..] if x == 42 -> Void [(False, x), ..] -> Void [] -> Void } } "#; assert!(matches!( check(parse(source_code)), Err(( _, Error::NotExhaustivePatternMatch { unmatched, .. } )) if unmatched[0] == "[(True, _), ..]" )); } #[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 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 [x] -> x [x, ..] if x > 10 -> x } x == 1 } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::NotExhaustivePatternMatch { .. })) )) } #[test] fn list_pattern_5() { 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_6() { 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!(matches!( check(parse(source_code)), Err((_, Error::CouldNotUnify { .. })) )) } #[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 fuzzer_ok_basic() { let source_code = r#" fn int() -> Fuzzer { todo } test prop(n via int()) { todo } "#; 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) { todo } "#; 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())) { todo } "#; 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_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 forbid_importing_or_using_opaque_constructors() { let dependency = r#" pub opaque type Thing { Foo(Int) Bar(Int) } "#; let source_code = r#" use foo/thing.{Thing, Foo} fn bar(thing: Thing) { expect Foo(a) = thing a } "#; assert!(matches!( check_with_deps( parse(source_code), vec![("foo/thing".to_string(), parse(dependency))], ), Err((_, Error::UnknownModuleField { .. })), )); let source_code = r#" use foo/thing.{Thing} fn bar(thing: Thing) { expect Foo(a) = thing a } "#; assert!(matches!( check_with_deps( parse(source_code), vec![("foo/thing".to_string(), parse(dependency))], ), 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(thing: Foo) { expect Foo { foo: Thing { inner } } : Foo = thing Void } "#; assert!(matches!( check(parse(source_code)), Err((_, Error::ExpectOnOpaqueType { .. })) )) } #[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()) }