4282 lines
88 KiB
Rust
4282 lines
88 KiB
Rust
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<UntypedModule>,
|
|
kind: ModuleKind,
|
|
tracing: Tracing,
|
|
) -> 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 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<Warning>, TypedModule), (Vec<Warning>, Error)> {
|
|
check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::verbose())
|
|
}
|
|
|
|
fn check_with_verbosity(
|
|
ast: UntypedModule,
|
|
level: TraceLevel,
|
|
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
|
|
check_module(ast, Vec::new(), ModuleKind::Lib, Tracing::All(level))
|
|
}
|
|
|
|
fn check_with_deps(
|
|
ast: UntypedModule,
|
|
extra: Vec<UntypedModule>,
|
|
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, Error)> {
|
|
check_module(ast, extra, ModuleKind::Lib, Tracing::verbose())
|
|
}
|
|
|
|
fn check_validator(
|
|
ast: UntypedModule,
|
|
) -> Result<(Vec<Warning>, TypedModule), (Vec<Warning>, 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<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 illegal_generic_instantiation() {
|
|
let source_code = r#"
|
|
type Rec<t> {
|
|
get_t: t,
|
|
}
|
|
|
|
|
|
fn use_dict(dict: Rec<fn(Bool) -> 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<a> {
|
|
foo: a
|
|
}
|
|
|
|
fn main() -> Foo<fn(Int) -> 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<a> {
|
|
foo: a
|
|
}
|
|
|
|
fn main() -> Foo<MillerLoopResult> {
|
|
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<a>, 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<Data>, 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<Data>, _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<Data>, _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<Data>, 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<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 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<Int> { 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<Int> { 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<Int> { todo }
|
|
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { 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<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_partial_down_casting() {
|
|
let source_code = r#"
|
|
type Foo {
|
|
x: Int
|
|
}
|
|
|
|
fn bar(n: List<Foo>) {
|
|
expect a: List<Data> = 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<Data>) {
|
|
expect a: List<Foo> = 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<Int> = 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<Int> = 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<Int> = 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<Int> {
|
|
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<Int> {
|
|
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<Int> = 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>) -> 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<Int>)
|
|
}
|
|
"#;
|
|
|
|
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<Int>)
|
|
}
|
|
"#;
|
|
|
|
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<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())
|
|
}
|
|
|
|
#[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<a>, 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>) -> 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<a>, 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>) -> 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<Datum>, 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<Datum>, 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<a>, 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<a>, 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<Int> = 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<Int>, 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<a> = 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<a> = [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<a> = 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<a> = []"#;
|
|
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: ()
|
|
}
|
|
}
|
|
);
|
|
}
|