1987 lines
41 KiB
Rust
1987 lines
41 KiB
Rust
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<Warning>, TypedModule), (Vec<Warning>, 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<Warning>, TypedModule), (Vec<Warning>, Error)> {
|
|
check_module(ast, Vec::new(), ModuleKind::Lib)
|
|
}
|
|
|
|
fn check_with_deps(
|
|
ast: UntypedModule,
|
|
extra: Vec<(String, UntypedModule)>,
|
|
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
|
|
check_module(ast, extra, ModuleKind::Lib)
|
|
}
|
|
|
|
fn check_validator(
|
|
ast: UntypedModule,
|
|
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, 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<a> = fn(PRNG) -> (a, PRNG)
|
|
|
|
fn constant(a: a) -> Fuzzer<a> {
|
|
fn (prng) {
|
|
(a, prng)
|
|
}
|
|
}
|
|
|
|
fn main() -> Fuzzer<Fuzzer<Int>> {
|
|
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(a)
|
|
}
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
|
|
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(a)
|
|
}
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
|
|
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<a>, then: fn(Option<a>) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(Some(a))
|
|
}
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
|
|
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(a)
|
|
}
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
|
|
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(a)
|
|
}
|
|
}
|
|
|
|
type Foo {
|
|
foo: Int,
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Foo>, opt_j: Option<Foo>) -> Option<Int> {
|
|
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(a)
|
|
}
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
|
|
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(a)
|
|
}
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
|
|
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(a)
|
|
}
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
|
|
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
|
|
when opt is {
|
|
None -> None
|
|
Some(a) -> then(a)
|
|
}
|
|
}
|
|
|
|
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
|
|
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<a>, 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<a>, 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<a>, 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<Int> { 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<Int> { todo }
|
|
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { 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<a> { todo }
|
|
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { 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<a> { 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<a>) -> Fuzzer<List<a>> { todo }
|
|
fn int() -> Fuzzer<Int> { 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<Thing> = [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<Thing>) {
|
|
expect Some(thing) = a
|
|
thing.inner
|
|
}
|
|
"#;
|
|
|
|
assert!(check(parse(source_code)).is_ok())
|
|
}
|