roc/crates/compiler/solve/tests/solve_expr.rs
Ayaz Hafiz 058644aa96
Implement weakening of variables introduced in branch patterns
Variables introduced in branch patterns should never be generalized in
the new weakening model. This implements that. The strategy is:

- when we have a let-binding that should be weakened, do not introduce
  its bound variables in a new (higher) rank
- instead, introduce them at the current rank, and also solve the
  let-binding at the current rank
- if any of those variables should then be generalized relative to the
  current rank, they will be so when the current rank is popped and
  generalized
2023-01-11 14:28:46 -06:00

8700 lines
211 KiB
Rust

#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
mod helpers;
#[cfg(test)]
mod solve_expr {
use crate::helpers::with_larger_debug_stack;
use lazy_static::lazy_static;
use regex::Regex;
use roc_can::{
abilities::ImplKey,
traverse::{find_ability_member_and_owning_type_at, find_type_at},
};
use roc_load::LoadedModule;
use roc_module::symbol::{Interns, ModuleId};
use roc_packaging::cache::RocCacheDir;
use roc_problem::can::Problem;
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Region};
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator};
use roc_solve_problem::TypeError;
use roc_types::{
pretty_print::{name_and_print_var, DebugPrint},
types::MemberImpl,
};
use std::path::PathBuf;
// HELPERS
lazy_static! {
static ref RE_TYPE_QUERY: Regex =
Regex::new(r#"(?P<where>\^+)(?:\{-(?P<sub>\d+)\})?"#).unwrap();
}
#[derive(Debug, Clone, Copy)]
struct TypeQuery(Region);
fn parse_queries(src: &str) -> Vec<TypeQuery> {
let line_info = LineInfo::new(src);
let mut queries = vec![];
let mut consecutive_query_lines = 0;
for (i, line) in src.lines().enumerate() {
let mut queries_on_line = RE_TYPE_QUERY.captures_iter(line).into_iter().peekable();
if queries_on_line.peek().is_none() {
consecutive_query_lines = 0;
continue;
} else {
consecutive_query_lines += 1;
}
for capture in queries_on_line {
let wher = capture.name("where").unwrap();
let subtract_col = capture
.name("sub")
.and_then(|m| str::parse(m.as_str()).ok())
.unwrap_or(0);
let (start, end) = (wher.start() as u32, wher.end() as u32);
let (start, end) = (start - subtract_col, end - subtract_col);
let last_line = i as u32 - consecutive_query_lines;
let start_lc = LineColumn {
line: last_line,
column: start,
};
let end_lc = LineColumn {
line: last_line,
column: end,
};
let lc_region = LineColumnRegion::new(start_lc, end_lc);
let region = line_info.convert_line_column_region(lc_region);
queries.push(TypeQuery(region));
}
}
queries
}
fn run_load_and_infer(src: &str) -> Result<(LoadedModule, String), std::io::Error> {
use bumpalo::Bump;
use tempfile::tempdir;
let arena = &Bump::new();
let module_src;
let temp;
if src.starts_with("app") {
// this is already a module
module_src = src;
} else {
// this is an expression, promote it to a module
temp = promote_expr_to_module(src);
module_src = &temp;
}
let loaded = {
let dir = tempdir()?;
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let result = roc_load::load_and_typecheck_str(
arena,
file_path,
module_src,
dir.path().to_path_buf(),
roc_target::TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::Generic,
RocCacheDir::Disallowed,
roc_reporting::report::DEFAULT_PALETTE,
);
dir.close()?;
result
};
let loaded = loaded.expect("failed to load module");
Ok((loaded, module_src.to_string()))
}
fn format_problems(
src: &str,
home: ModuleId,
interns: &Interns,
can_problems: Vec<Problem>,
type_problems: Vec<TypeError>,
) -> (String, String) {
let filename = PathBuf::from("test.roc");
let src_lines: Vec<&str> = src.split('\n').collect();
let lines = LineInfo::new(src);
let alloc = RocDocAllocator::new(&src_lines, home, interns);
let mut can_reports = vec![];
let mut type_reports = vec![];
for problem in can_problems {
let report = can_problem(&alloc, &lines, filename.clone(), problem.clone());
can_reports.push(report.pretty(&alloc));
}
for problem in type_problems {
if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) {
type_reports.push(report.pretty(&alloc));
}
}
let mut can_reports_buf = String::new();
let mut type_reports_buf = String::new();
use roc_reporting::report::CiWrite;
alloc
.stack(can_reports)
.1
.render_raw(70, &mut CiWrite::new(&mut can_reports_buf))
.unwrap();
alloc
.stack(type_reports)
.1
.render_raw(70, &mut CiWrite::new(&mut type_reports_buf))
.unwrap();
(can_reports_buf, type_reports_buf)
}
fn infer_eq_help(src: &str) -> Result<(String, String, String), std::io::Error> {
let (
LoadedModule {
module_id: home,
mut can_problems,
mut type_problems,
interns,
mut solved,
mut exposed_to_host,
abilities_store,
..
},
src,
) = run_load_and_infer(src)?;
let mut can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
// Disregard UnusedDef problems, because those are unavoidable when
// returning a function from the test expression.
can_problems.retain(|prob| {
!matches!(
prob,
roc_problem::can::Problem::UnusedDef(_, _)
| roc_problem::can::Problem::UnusedBranchDef(..)
)
});
let (can_problems, type_problems) =
format_problems(&src, home, &interns, can_problems, type_problems);
let subs = solved.inner_mut();
exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s));
debug_assert!(exposed_to_host.len() == 1, "{:?}", exposed_to_host);
let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap();
let actual_str = name_and_print_var(variable, subs, home, &interns, DebugPrint::NOTHING);
Ok((type_problems, can_problems, actual_str))
}
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from(indoc!(
r#"
app "test"
imports []
provides [main] to "./platform"
main =
"#
));
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
fn infer_eq(src: &str, expected: &str) {
let (_, can_problems, actual) = infer_eq_help(src).unwrap();
assert!(
can_problems.is_empty(),
"Canonicalization problems: {}",
can_problems
);
assert_eq!(actual, expected.to_string());
}
fn infer_eq_without_problem(src: &str, expected: &str) {
let (type_problems, can_problems, actual) = infer_eq_help(src).unwrap();
assert!(
can_problems.is_empty(),
"Canonicalization problems: {}",
can_problems
);
if !type_problems.is_empty() {
// fail with an assert, but print the problems normally so rust doesn't try to diff
// an empty vec with the problems.
panic!(
"expected:\n{:?}\ninferred:\n{:?}\nproblems:\n{}",
expected, actual, type_problems,
);
}
assert_eq!(actual, expected.to_string());
}
#[derive(Default)]
struct InferOptions {
print_can_decls: bool,
print_only_under_alias: bool,
allow_errors: bool,
}
fn infer_queries_help(src: &str, expected: impl FnOnce(&str), options: InferOptions) {
let (
LoadedModule {
module_id: home,
mut can_problems,
mut type_problems,
mut declarations_by_id,
mut solved,
interns,
abilities_store,
..
},
src,
) = run_load_and_infer(src).unwrap();
let decls = declarations_by_id.remove(&home).unwrap();
let subs = solved.inner_mut();
let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
let (can_problems, type_problems) =
format_problems(&src, home, &interns, can_problems, type_problems);
if !options.allow_errors {
assert!(
can_problems.is_empty(),
"Canonicalization problems: {}",
can_problems
);
assert!(type_problems.is_empty(), "Type problems: {}", type_problems);
}
let queries = parse_queries(&src);
assert!(!queries.is_empty(), "No queries provided!");
let mut output_parts = Vec::with_capacity(queries.len() + 2);
if options.print_can_decls {
use roc_can::debug::{pretty_print_declarations, PPCtx};
let ctx = PPCtx {
home,
interns: &interns,
print_lambda_names: true,
};
let pretty_decls = pretty_print_declarations(&ctx, &decls);
output_parts.push(pretty_decls);
output_parts.push("\n".to_owned());
}
for TypeQuery(region) in queries.into_iter() {
let start = region.start().offset;
let end = region.end().offset;
let text = &src[start as usize..end as usize];
let var = find_type_at(region, &decls)
.unwrap_or_else(|| panic!("No type for {:?} ({:?})!", &text, region));
let snapshot = subs.snapshot();
let actual_str = name_and_print_var(
var,
subs,
home,
&interns,
DebugPrint {
print_lambda_sets: true,
print_only_under_alias: options.print_only_under_alias,
ignore_polarity: true,
print_weakened_vars: true,
},
);
subs.rollback_to(snapshot);
let elaborated =
match find_ability_member_and_owning_type_at(region, &decls, &abilities_store) {
Some((spec_type, spec_symbol)) => {
format!(
"{}#{}({}) : {}",
spec_type.as_str(&interns),
text,
spec_symbol.ident_id().index(),
actual_str
)
}
None => {
format!("{} : {}", text, actual_str)
}
};
output_parts.push(elaborated);
}
let pretty_output = output_parts.join("\n");
expected(&pretty_output);
}
macro_rules! infer_queries {
($program:expr, @$queries:literal $($option:ident: $value:expr)*) => {
infer_queries_help($program, |golden| insta::assert_snapshot!(golden, @$queries), InferOptions {
$($option: $value,)* ..InferOptions::default()
})
};
}
fn check_inferred_abilities<'a, I>(src: &'a str, expected_specializations: I)
where
I: IntoIterator<Item = (&'a str, &'a str)>,
{
let LoadedModule {
module_id: home,
mut can_problems,
mut type_problems,
interns,
abilities_store,
..
} = run_load_and_infer(src).unwrap().0;
let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
assert_eq!(can_problems, Vec::new(), "Canonicalization problems: ");
if !type_problems.is_empty() {
eprintln!("{:?}", type_problems);
panic!();
}
let known_specializations = abilities_store.iter_declared_implementations().filter_map(
|(impl_key, member_impl)| match member_impl {
MemberImpl::Impl(impl_symbol) => {
let specialization = abilities_store.specialization_info(*impl_symbol).expect(
"declared implementations should be resolved conclusively after solving",
);
Some((impl_key, specialization.clone()))
}
MemberImpl::Error => None,
},
);
use std::collections::HashSet;
let pretty_specializations = known_specializations
.into_iter()
.map(|(impl_key, _)| {
let ImplKey {
opaque,
ability_member,
} = impl_key;
let member_data = abilities_store.member_def(ability_member).unwrap();
let member_str = ability_member.as_str(&interns);
let ability_str = member_data.parent_ability.as_str(&interns);
(
format!("{}:{}", ability_str, member_str),
opaque.as_str(&interns),
)
})
.collect::<HashSet<_>>();
for (parent, specialization) in expected_specializations.into_iter() {
let has_the_one = pretty_specializations
.iter()
// references are annoying so we do this
.any(|(p, s)| p == parent && s == &specialization);
assert!(
has_the_one,
"{:#?} not in {:#?}",
(parent, specialization),
pretty_specializations,
);
}
}
#[test]
fn int_literal() {
infer_eq("5", "Num *");
}
#[test]
fn float_literal() {
infer_eq("0.5", "Float *");
}
#[test]
fn dec_literal() {
infer_eq(
indoc!(
r#"
val : Dec
val = 1.2
val
"#
),
"Dec",
);
}
#[test]
fn string_literal() {
infer_eq(
indoc!(
r#"
"type inference!"
"#
),
"Str",
);
}
#[test]
fn empty_string() {
infer_eq(
indoc!(
r#"
""
"#
),
"Str",
);
}
#[test]
fn string_starts_with() {
infer_eq_without_problem(
indoc!(
r#"
Str.startsWith
"#
),
"Str, Str -> Bool",
);
}
#[test]
fn string_from_int() {
infer_eq_without_problem(
indoc!(
r#"
Num.toStr
"#
),
"Num * -> Str",
);
}
#[test]
fn string_from_utf8() {
infer_eq_without_problem(
indoc!(
r#"
Str.fromUtf8
"#
),
"List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]",
);
}
#[test]
fn choose_correct_recursion_var_under_record() {
infer_queries!(
indoc!(
r#"
Parser : [
Specialize Parser,
Record (List {parser: Parser}),
]
printCombinatorParser : Parser -> Str
printCombinatorParser = \parser ->
when parser is
# ^^^^^^
Specialize p ->
printed = printCombinatorParser p
if Bool.false then printed else "foo"
Record fields ->
fields
|> List.map \f ->
printed = printCombinatorParser f.parser
if Bool.false then printed else "foo"
|> List.first
|> Result.withDefault ("foo")
printCombinatorParser (Record [])
"#
),
@r###"
parser : [Record (List { parser : a }), Specialize a] as a
"###
print_only_under_alias: true
);
}
// #[test]
// fn block_string_literal() {
// infer_eq(
// indoc!(
// r#"
// """type
// inference!"""
// "#
// ),
// "Str",
// );
// }
// LIST
#[test]
fn empty_list() {
infer_eq(
indoc!(
r#"
[]
"#
),
"List *",
);
}
#[test]
fn list_of_lists() {
infer_eq(
indoc!(
r#"
[[]]
"#
),
"List (List *)",
);
}
#[test]
fn triple_nested_list() {
infer_eq(
indoc!(
r#"
[[[]]]
"#
),
"List (List (List *))",
);
}
#[test]
fn nested_empty_list() {
infer_eq(
indoc!(
r#"
[[], [[]]]
"#
),
"List (List (List *))",
);
}
#[test]
fn concat_different_types() {
infer_eq(
indoc!(
r#"
empty = []
one = List.concat [1] empty
str = List.concat ["blah"] empty
empty
"#
),
"List *",
);
}
#[test]
fn list_of_one_int() {
infer_eq(
indoc!(
r#"
[42]
"#
),
"List (Num *)",
);
}
#[test]
fn triple_nested_int_list() {
infer_eq(
indoc!(
r#"
[[[5]]]
"#
),
"List (List (List (Num *)))",
);
}
#[test]
fn list_of_ints() {
infer_eq(
indoc!(
r#"
[1, 2, 3]
"#
),
"List (Num *)",
);
}
#[test]
fn nested_list_of_ints() {
infer_eq(
indoc!(
r#"
[[1], [2, 3]]
"#
),
"List (List (Num *))",
);
}
#[test]
fn list_of_one_string() {
infer_eq(
indoc!(
r#"
["cowabunga"]
"#
),
"List Str",
);
}
#[test]
fn triple_nested_string_list() {
infer_eq(
indoc!(
r#"
[[["foo"]]]
"#
),
"List (List (List Str))",
);
}
#[test]
fn list_of_strings() {
infer_eq(
indoc!(
r#"
["foo", "bar"]
"#
),
"List Str",
);
}
// INTERPOLATED STRING
#[test]
fn infer_interpolated_string() {
infer_eq(
indoc!(
r#"
whatItIs = "great"
"type inference is \(whatItIs)!"
"#
),
"Str",
);
}
#[test]
fn infer_interpolated_var() {
infer_eq(
indoc!(
r#"
whatItIs = "great"
str = "type inference is \(whatItIs)!"
whatItIs
"#
),
"Str",
);
}
#[test]
fn infer_interpolated_field() {
infer_eq(
indoc!(
r#"
rec = { whatItIs: "great" }
str = "type inference is \(rec.whatItIs)!"
rec
"#
),
"{ whatItIs : Str }",
);
}
// LIST MISMATCH
#[test]
fn mismatch_heterogeneous_list() {
infer_eq(
indoc!(
r#"
["foo", 5]
"#
),
"List <type mismatch>",
);
}
#[test]
fn mismatch_heterogeneous_nested_list() {
infer_eq(
indoc!(
r#"
[["foo", 5]]
"#
),
"List (List <type mismatch>)",
);
}
#[test]
fn mismatch_heterogeneous_nested_empty_list() {
infer_eq(
indoc!(
r#"
[[1], [[]]]
"#
),
"List <type mismatch>",
);
}
// CLOSURE
#[test]
fn always_return_empty_record() {
infer_eq(
indoc!(
r#"
\_ -> {}
"#
),
"* -> {}",
);
}
#[test]
fn two_arg_return_int() {
infer_eq(
indoc!(
r#"
\_, _ -> 42
"#
),
"*, * -> Num *",
);
}
#[test]
fn three_arg_return_string() {
infer_eq(
indoc!(
r#"
\_, _, _ -> "test!"
"#
),
"*, *, * -> Str",
);
}
// DEF
#[test]
fn def_empty_record() {
infer_eq(
indoc!(
r#"
foo = {}
foo
"#
),
"{}",
);
}
#[test]
fn def_string() {
infer_eq(
indoc!(
r#"
str = "thing"
str
"#
),
"Str",
);
}
#[test]
fn def_1_arg_closure() {
infer_eq(
indoc!(
r#"
fn = \_ -> {}
fn
"#
),
"* -> {}",
);
}
#[test]
fn applied_tag() {
infer_eq_without_problem(
indoc!(
r#"
List.map ["a", "b"] \elem -> Foo elem
"#
),
"List [Foo Str]",
)
}
// Tests (TagUnion, Func)
#[test]
fn applied_tag_function() {
infer_eq_without_problem(
indoc!(
r#"
foo = Foo
foo "hi"
"#
),
"[Foo Str]",
)
}
// Tests (TagUnion, Func)
#[test]
fn applied_tag_function_list_map() {
infer_eq_without_problem(
indoc!(
r#"
List.map ["a", "b"] Foo
"#
),
"List [Foo Str]",
)
}
// Tests (TagUnion, Func)
#[test]
fn applied_tag_function_list() {
infer_eq_without_problem(
indoc!(
r#"
[\x -> Bar x, Foo]
"#
),
"List (a -> [Bar a, Foo a])",
)
}
// Tests (Func, TagUnion)
#[test]
fn applied_tag_function_list_other_way() {
infer_eq_without_problem(
indoc!(
r#"
[Foo, \x -> Bar x]
"#
),
"List (a -> [Bar a, Foo a])",
)
}
// Tests (Func, TagUnion)
#[test]
fn applied_tag_function_record() {
infer_eq_without_problem(
indoc!(
r#"
foo = Foo
{
x: [foo, Foo],
y: [foo, \x -> Foo x],
z: [foo, \x,y -> Foo x y]
}
"#
),
"{ x : List [Foo], y : List (a -> [Foo a]), z : List (b, c -> [Foo b c]) }",
)
}
// Tests (TagUnion, Func)
#[test]
fn applied_tag_function_with_annotation() {
infer_eq_without_problem(
indoc!(
r#"
x : List [Foo I64]
x = List.map [1, 2] Foo
x
"#
),
"List [Foo I64]",
)
}
#[test]
fn def_2_arg_closure() {
infer_eq(
indoc!(
r#"
func = \_, _ -> 42
func
"#
),
"*, * -> Num *",
);
}
#[test]
fn def_3_arg_closure() {
infer_eq(
indoc!(
r#"
f = \_, _, _ -> "test!"
f
"#
),
"*, *, * -> Str",
);
}
#[test]
fn def_multiple_functions() {
infer_eq(
indoc!(
r#"
a = \_, _, _ -> "test!"
b = a
b
"#
),
"*, *, * -> Str",
);
}
#[test]
fn def_multiple_strings() {
infer_eq(
indoc!(
r#"
a = "test!"
b = a
b
"#
),
"Str",
);
}
#[test]
fn def_multiple_ints() {
infer_eq(
indoc!(
r#"
c = b
b = a
a = 42
c
"#
),
"Num *",
);
}
#[test]
fn def_returning_closure() {
infer_eq(
indoc!(
r#"
f = \z -> z
g = \z -> z
(\x ->
a = f x
b = g x
x
)
"#
),
"a -> a",
);
}
// CALLING FUNCTIONS
#[test]
fn call_returns_int() {
infer_eq(
indoc!(
r#"
alwaysFive = \_ -> 5
alwaysFive "stuff"
"#
),
"Num *",
);
}
#[test]
fn identity_returns_given_type() {
infer_eq(
indoc!(
r#"
identity = \a -> a
identity "hi"
"#
),
"Str",
);
}
#[test]
fn identity_infers_principal_type() {
infer_eq(
indoc!(
r#"
identity = \x -> x
y = identity 5
identity
"#
),
"a -> a",
);
}
#[test]
fn identity_works_on_incompatible_types() {
infer_eq(
indoc!(
r#"
identity = \a -> a
x = identity 5
y = identity "hi"
x
"#
),
"Num *",
);
}
#[test]
fn call_returns_list() {
infer_eq(
indoc!(
r#"
enlist = \val -> [val]
enlist 5
"#
),
"List (Num *)",
);
}
#[test]
fn indirect_always() {
infer_eq(
indoc!(
r#"
always = \val -> (\_ -> val)
alwaysFoo = always "foo"
alwaysFoo 42
"#
),
"Str",
);
}
#[test]
fn pizza_desugar() {
infer_eq(
indoc!(
r#"
1 |> (\a -> a)
"#
),
"Num *",
);
}
#[test]
fn pizza_desugar_two_arguments() {
infer_eq(
indoc!(
r#"
always2 = \a, _ -> a
1 |> always2 "foo"
"#
),
"Num *",
);
}
#[test]
fn anonymous_identity() {
infer_eq(
indoc!(
r#"
(\a -> a) 3.14
"#
),
"Float *",
);
}
#[test]
fn identity_of_identity() {
infer_eq(
indoc!(
r#"
(\val -> val) (\val -> val)
"#
),
"a -> a",
);
}
#[test]
fn recursive_identity() {
infer_eq(
indoc!(
r#"
identity = \val -> val
identity identity
"#
),
"a -> a",
);
}
#[test]
fn identity_function() {
infer_eq(
indoc!(
r#"
\val -> val
"#
),
"a -> a",
);
}
#[test]
fn use_apply() {
infer_eq(
indoc!(
r#"
identity = \a -> a
apply = \f, x -> f x
apply identity 5
"#
),
"Num *",
);
}
#[test]
fn apply_function() {
infer_eq(
indoc!(
r#"
\f, x -> f x
"#
),
"(a -> b), a -> b",
);
}
// #[test]
// TODO FIXME this should pass, but instead fails to canonicalize
// fn use_flip() {
// infer_eq(
// indoc!(
// r#"
// flip = \f -> (\a b -> f b a)
// neverendingInt = \f int -> f int
// x = neverendingInt (\a -> a) 5
// flip neverendingInt
// "#
// ),
// "(Num *, (a -> a)) -> Num *",
// );
// }
#[test]
fn flip_function() {
infer_eq(
indoc!(
r#"
\f -> (\a, b -> f b a)
"#
),
"(a, b -> c) -> (b, a -> c)",
);
}
#[test]
fn always_function() {
infer_eq(
indoc!(
r#"
\val -> \_ -> val
"#
),
"a -> (* -> a)",
);
}
#[test]
fn pass_a_function() {
infer_eq(
indoc!(
r#"
\f -> f {}
"#
),
"({} -> a) -> a",
);
}
// OPERATORS
// #[test]
// fn div_operator() {
// infer_eq(
// indoc!(
// r#"
// \l r -> l / r
// "#
// ),
// "F64, F64 -> F64",
// );
// }
// #[test]
// fn basic_float_division() {
// infer_eq(
// indoc!(
// r#"
// 1 / 2
// "#
// ),
// "F64",
// );
// }
// #[test]
// fn basic_int_division() {
// infer_eq(
// indoc!(
// r#"
// 1 // 2
// "#
// ),
// "Num *",
// );
// }
// #[test]
// fn basic_addition() {
// infer_eq(
// indoc!(
// r#"
// 1 + 2
// "#
// ),
// "Num *",
// );
// }
// #[test]
// fn basic_circular_type() {
// infer_eq(
// indoc!(
// r#"
// \x -> x x
// "#
// ),
// "<Type Mismatch: Circular Type>",
// );
// }
// #[test]
// fn y_combinator_has_circular_type() {
// assert_eq!(
// infer(indoc!(r#"
// \f -> (\x -> f x x) (\x -> f x x)
// "#)),
// Erroneous(Problem::CircularType)
// );
// }
// #[test]
// fn no_higher_ranked_types() {
// // This should error because it can't type of alwaysFive
// infer_eq(
// indoc!(
// r#"
// \always -> [always [], always ""]
// "#
// ),
// "<type mismatch>",
// );
// }
#[test]
fn always_with_list() {
infer_eq(
indoc!(
r#"
alwaysFive = \_ -> 5
[alwaysFive "foo", alwaysFive []]
"#
),
"List (Num *)",
);
}
#[test]
fn if_with_int_literals() {
infer_eq(
indoc!(
r#"
if Bool.true then
42
else
24
"#
),
"Num *",
);
}
#[test]
fn when_with_int_literals() {
infer_eq(
indoc!(
r#"
when 1 is
1 -> 2
3 -> 4
"#
),
"Num *",
);
}
// RECORDS
#[test]
fn empty_record() {
infer_eq("{}", "{}");
}
#[test]
fn one_field_record() {
infer_eq("{ x: 5 }", "{ x : Num * }");
}
#[test]
fn two_field_record() {
infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float * }");
}
#[test]
fn record_literal_accessor() {
infer_eq("{ x: 5, y : 3.14 }.x", "Num *");
}
#[test]
fn record_arg() {
infer_eq("\\rec -> rec.x", "{ x : a }* -> a");
}
#[test]
fn record_with_bound_var() {
infer_eq(
indoc!(
r#"
fn = \rec ->
x = rec.x
rec
fn
"#
),
"{ x : a }b -> { x : a }b",
);
}
#[test]
fn using_type_signature() {
infer_eq(
indoc!(
r#"
bar : custom -> custom
bar = \x -> x
bar
"#
),
"custom -> custom",
);
}
#[test]
fn type_signature_without_body() {
infer_eq(
indoc!(
r#"
foo: Str -> {}
foo "hi"
"#
),
"{}",
);
}
#[test]
fn type_signature_without_body_rigid() {
infer_eq(
indoc!(
r#"
foo : Num * -> custom
foo 2
"#
),
"custom",
);
}
#[test]
fn accessor_function() {
infer_eq(".foo", "{ foo : a }* -> a");
}
#[test]
fn type_signature_without_body_record() {
infer_eq(
indoc!(
r#"
{ x, y } : { x : ({} -> custom), y : {} }
x
"#
),
"{} -> custom",
);
}
#[test]
fn empty_record_pattern() {
infer_eq(
indoc!(
r#"
# technically, an empty record can be destructured
thunk = \{} -> 42
xEmpty = if thunk {} == 42 then { x: {} } else { x: {} }
when xEmpty is
{ x: {} } -> {}
"#
),
"{}",
);
}
#[test]
fn record_type_annotation() {
// check that a closed record remains closed
infer_eq(
indoc!(
r#"
foo : { x : custom } -> custom
foo = \{ x } -> x
foo
"#
),
"{ x : custom } -> custom",
);
}
#[test]
fn record_update() {
infer_eq(
indoc!(
r#"
user = { year: "foo", name: "Sam" }
{ user & year: "foo" }
"#
),
"{ name : Str, year : Str }",
);
}
#[test]
fn bare_tag() {
infer_eq(
indoc!(
r#"
Foo
"#
),
"[Foo]",
);
}
#[test]
fn single_tag_pattern() {
infer_eq(
indoc!(
r#"
\Foo -> 42
"#
),
"[Foo] -> Num *",
);
}
#[test]
fn two_tag_pattern() {
infer_eq(
indoc!(
r#"
\x ->
when x is
True -> 1
False -> 0
"#
),
"[False, True] -> Num *",
);
}
#[test]
fn tag_application() {
infer_eq(
indoc!(
r#"
Foo "happy" 12
"#
),
"[Foo Str (Num *)]",
);
}
#[test]
fn record_extraction() {
infer_eq(
indoc!(
r#"
f = \x ->
when x is
{ a, b: _ } -> a
f
"#
),
"{ a : a, b : * }* -> a",
);
}
#[test]
fn record_field_pattern_match_with_guard() {
infer_eq(
indoc!(
r#"
when { x: 5 } is
{ x: 4 } -> 4
"#
),
"Num *",
);
}
#[test]
fn tag_union_pattern_match() {
infer_eq(
indoc!(
r#"
\Foo x -> Foo x
"#
),
"[Foo a] -> [Foo a]",
);
}
#[test]
fn tag_union_pattern_match_ignored_field() {
infer_eq(
indoc!(
r#"
\Foo x _ -> Foo x "y"
"#
),
"[Foo a *] -> [Foo a Str]",
);
}
#[test]
fn tag_with_field() {
infer_eq(
indoc!(
r#"
when Foo "blah" is
Foo x -> x
"#
),
"Str",
);
}
#[test]
fn qualified_annotation_num_integer() {
infer_eq(
indoc!(
r#"
int : Num.Num (Num.Integer Num.Signed64)
int
"#
),
"I64",
);
}
#[test]
fn qualified_annotated_num_integer() {
infer_eq(
indoc!(
r#"
int : Num.Num (Num.Integer Num.Signed64)
int = 5
int
"#
),
"I64",
);
}
#[test]
fn annotation_num_integer() {
infer_eq(
indoc!(
r#"
int : Num (Integer Signed64)
int
"#
),
"I64",
);
}
#[test]
fn annotated_num_integer() {
infer_eq(
indoc!(
r#"
int : Num (Integer Signed64)
int = 5
int
"#
),
"I64",
);
}
#[test]
fn qualified_annotation_using_i128() {
infer_eq(
indoc!(
r#"
int : Num.I128
int
"#
),
"I128",
);
}
#[test]
fn qualified_annotated_using_i128() {
infer_eq(
indoc!(
r#"
int : Num.I128
int = 5
int
"#
),
"I128",
);
}
#[test]
fn annotation_using_i128() {
infer_eq(
indoc!(
r#"
int : I128
int
"#
),
"I128",
);
}
#[test]
fn annotated_using_i128() {
infer_eq(
indoc!(
r#"
int : I128
int = 5
int
"#
),
"I128",
);
}
#[test]
fn qualified_annotation_using_u128() {
infer_eq(
indoc!(
r#"
int : Num.U128
int
"#
),
"U128",
);
}
#[test]
fn qualified_annotated_using_u128() {
infer_eq(
indoc!(
r#"
int : Num.U128
int = 5
int
"#
),
"U128",
);
}
#[test]
fn annotation_using_u128() {
infer_eq(
indoc!(
r#"
int : U128
int
"#
),
"U128",
);
}
#[test]
fn annotated_using_u128() {
infer_eq(
indoc!(
r#"
int : U128
int = 5
int
"#
),
"U128",
);
}
#[test]
fn qualified_annotation_using_i64() {
infer_eq(
indoc!(
r#"
int : Num.I64
int
"#
),
"I64",
);
}
#[test]
fn qualified_annotated_using_i64() {
infer_eq(
indoc!(
r#"
int : Num.I64
int = 5
int
"#
),
"I64",
);
}
#[test]
fn annotation_using_i64() {
infer_eq(
indoc!(
r#"
int : I64
int
"#
),
"I64",
);
}
#[test]
fn annotated_using_i64() {
infer_eq(
indoc!(
r#"
int : I64
int = 5
int
"#
),
"I64",
);
}
#[test]
fn qualified_annotation_using_u64() {
infer_eq(
indoc!(
r#"
int : Num.U64
int
"#
),
"U64",
);
}
#[test]
fn qualified_annotated_using_u64() {
infer_eq(
indoc!(
r#"
int : Num.U64
int = 5
int
"#
),
"U64",
);
}
#[test]
fn annotation_using_u64() {
infer_eq(
indoc!(
r#"
int : U64
int
"#
),
"U64",
);
}
#[test]
fn annotated_using_u64() {
infer_eq(
indoc!(
r#"
int : U64
int = 5
int
"#
),
"U64",
);
}
#[test]
fn qualified_annotation_using_i32() {
infer_eq(
indoc!(
r#"
int : Num.I32
int
"#
),
"I32",
);
}
#[test]
fn qualified_annotated_using_i32() {
infer_eq(
indoc!(
r#"
int : Num.I32
int = 5
int
"#
),
"I32",
);
}
#[test]
fn annotation_using_i32() {
infer_eq(
indoc!(
r#"
int : I32
int
"#
),
"I32",
);
}
#[test]
fn annotated_using_i32() {
infer_eq(
indoc!(
r#"
int : I32
int = 5
int
"#
),
"I32",
);
}
#[test]
fn qualified_annotation_using_u32() {
infer_eq(
indoc!(
r#"
int : Num.U32
int
"#
),
"U32",
);
}
#[test]
fn qualified_annotated_using_u32() {
infer_eq(
indoc!(
r#"
int : Num.U32
int = 5
int
"#
),
"U32",
);
}
#[test]
fn annotation_using_u32() {
infer_eq(
indoc!(
r#"
int : U32
int
"#
),
"U32",
);
}
#[test]
fn annotated_using_u32() {
infer_eq(
indoc!(
r#"
int : U32
int = 5
int
"#
),
"U32",
);
}
#[test]
fn qualified_annotation_using_i16() {
infer_eq(
indoc!(
r#"
int : Num.I16
int
"#
),
"I16",
);
}
#[test]
fn qualified_annotated_using_i16() {
infer_eq(
indoc!(
r#"
int : Num.I16
int = 5
int
"#
),
"I16",
);
}
#[test]
fn annotation_using_i16() {
infer_eq(
indoc!(
r#"
int : I16
int
"#
),
"I16",
);
}
#[test]
fn annotated_using_i16() {
infer_eq(
indoc!(
r#"
int : I16
int = 5
int
"#
),
"I16",
);
}
#[test]
fn qualified_annotation_using_u16() {
infer_eq(
indoc!(
r#"
int : Num.U16
int
"#
),
"U16",
);
}
#[test]
fn qualified_annotated_using_u16() {
infer_eq(
indoc!(
r#"
int : Num.U16
int = 5
int
"#
),
"U16",
);
}
#[test]
fn annotation_using_u16() {
infer_eq(
indoc!(
r#"
int : U16
int
"#
),
"U16",
);
}
#[test]
fn annotated_using_u16() {
infer_eq(
indoc!(
r#"
int : U16
int = 5
int
"#
),
"U16",
);
}
#[test]
fn qualified_annotation_using_i8() {
infer_eq(
indoc!(
r#"
int : Num.I8
int
"#
),
"I8",
);
}
#[test]
fn qualified_annotated_using_i8() {
infer_eq(
indoc!(
r#"
int : Num.I8
int = 5
int
"#
),
"I8",
);
}
#[test]
fn annotation_using_i8() {
infer_eq(
indoc!(
r#"
int : I8
int
"#
),
"I8",
);
}
#[test]
fn annotated_using_i8() {
infer_eq(
indoc!(
r#"
int : I8
int = 5
int
"#
),
"I8",
);
}
#[test]
fn qualified_annotation_using_u8() {
infer_eq(
indoc!(
r#"
int : Num.U8
int
"#
),
"U8",
);
}
#[test]
fn qualified_annotated_using_u8() {
infer_eq(
indoc!(
r#"
int : Num.U8
int = 5
int
"#
),
"U8",
);
}
#[test]
fn annotation_using_u8() {
infer_eq(
indoc!(
r#"
int : U8
int
"#
),
"U8",
);
}
#[test]
fn annotated_using_u8() {
infer_eq(
indoc!(
r#"
int : U8
int = 5
int
"#
),
"U8",
);
}
#[test]
fn qualified_annotation_num_floatingpoint() {
infer_eq(
indoc!(
r#"
float : Num.Num (Num.FloatingPoint Num.Binary64)
float
"#
),
"F64",
);
}
#[test]
fn qualified_annotated_num_floatingpoint() {
infer_eq(
indoc!(
r#"
float : Num.Num (Num.FloatingPoint Num.Binary64)
float = 5.5
float
"#
),
"F64",
);
}
#[test]
fn annotation_num_floatingpoint() {
infer_eq(
indoc!(
r#"
float : Num (FloatingPoint Binary64)
float
"#
),
"F64",
);
}
#[test]
fn annotated_num_floatingpoint() {
infer_eq(
indoc!(
r#"
float : Num (FloatingPoint Binary64)
float = 5.5
float
"#
),
"F64",
);
}
#[test]
fn qualified_annotation_f64() {
infer_eq(
indoc!(
r#"
float : Num.F64
float
"#
),
"F64",
);
}
#[test]
fn qualified_annotated_f64() {
infer_eq(
indoc!(
r#"
float : Num.F64
float = 5.5
float
"#
),
"F64",
);
}
#[test]
fn annotation_f64() {
infer_eq(
indoc!(
r#"
float : F64
float
"#
),
"F64",
);
}
#[test]
fn annotated_f64() {
infer_eq(
indoc!(
r#"
float : F64
float = 5.5
float
"#
),
"F64",
);
}
#[test]
fn qualified_annotation_f32() {
infer_eq(
indoc!(
r#"
float : Num.F32
float
"#
),
"F32",
);
}
#[test]
fn qualified_annotated_f32() {
infer_eq(
indoc!(
r#"
float : Num.F32
float = 5.5
float
"#
),
"F32",
);
}
#[test]
fn annotation_f32() {
infer_eq(
indoc!(
r#"
float : F32
float
"#
),
"F32",
);
}
#[test]
fn annotated_f32() {
infer_eq(
indoc!(
r#"
float : F32
float = 5.5
float
"#
),
"F32",
);
}
#[test]
fn fake_result_ok() {
infer_eq(
indoc!(
r#"
Res a e : [Okay a, Error e]
ok : Res I64 *
ok = Okay 5
ok
"#
),
"Res I64 *",
);
}
#[test]
fn fake_result_err() {
infer_eq(
indoc!(
r#"
Res a e : [Okay a, Error e]
err : Res * Str
err = Error "blah"
err
"#
),
"Res * Str",
);
}
#[test]
fn basic_result_ok() {
infer_eq(
indoc!(
r#"
ok : Result I64 *
ok = Ok 5
ok
"#
),
"Result I64 *",
);
}
#[test]
fn basic_result_err() {
infer_eq(
indoc!(
r#"
err : Result * Str
err = Err "blah"
err
"#
),
"Result * Str",
);
}
#[test]
fn basic_result_conditional() {
infer_eq(
indoc!(
r#"
ok : Result I64 *
ok = Ok 5
err : Result * Str
err = Err "blah"
if 1 > 0 then
ok
else
err
"#
),
"Result I64 Str",
);
}
// #[test]
// fn annotation_using_num_used() {
// // There was a problem where `I64`, because it is only an annotation
// // wasn't added to the vars_by_symbol.
// infer_eq_without_problem(
// indoc!(
// r#"
// int : I64
// p = (\x -> x) int
// p
// "#
// ),
// "I64",
// );
// }
#[test]
fn num_identity() {
infer_eq_without_problem(
indoc!(
r#"
numIdentity : Num.Num a -> Num.Num a
numIdentity = \x -> x
y = numIdentity 3.14
{ numIdentity, x : numIdentity 42, y }
"#
),
"{ numIdentity : Num a -> Num a, x : Num *, y : Float * }",
);
}
#[test]
fn when_with_annotation() {
infer_eq_without_problem(
indoc!(
r#"
x : Num.Num (Num.Integer Num.Signed64)
x =
when 2 is
3 -> 4
_ -> 5
x
"#
),
"I64",
);
}
// TODO add more realistic function when able
#[test]
fn integer_sum() {
infer_eq_without_problem(
indoc!(
r#"
f = \n ->
when n is
0 -> 0
_ -> f n
f
"#
),
"Num * -> Num *",
);
}
#[test]
fn identity_map() {
infer_eq_without_problem(
indoc!(
r#"
map : (a -> b), [Identity a] -> [Identity b]
map = \f, identity ->
when identity is
Identity v -> Identity (f v)
map
"#
),
"(a -> b), [Identity a] -> [Identity b]",
);
}
#[test]
fn to_bit() {
infer_eq_without_problem(
indoc!(
r#"
toBit = \bool ->
when bool is
True -> 1
False -> 0
toBit
"#
),
"[False, True] -> Num *",
);
}
// this test is related to a bug where ext_var would have an incorrect rank.
// This match has duplicate cases, but we ignore that.
#[test]
fn to_bit_record() {
infer_eq(
indoc!(
r#"
foo = \rec ->
when rec is
{ x: _ } -> "1"
{ y: _ } -> "2"
foo
"#
),
"{ x : *, y : * }* -> Str",
);
}
#[test]
fn from_bit() {
infer_eq_without_problem(
indoc!(
r#"
fromBit = \int ->
when int is
0 -> False
_ -> True
fromBit
"#
),
"Num * -> [False, True]",
);
}
#[test]
fn result_map_explicit() {
infer_eq_without_problem(
indoc!(
r#"
map : (a -> b), [Err e, Ok a] -> [Err e, Ok b]
map = \f, result ->
when result is
Ok v -> Ok (f v)
Err e -> Err e
map
"#
),
"(a -> b), [Err e, Ok a] -> [Err e, Ok b]",
);
}
#[test]
fn result_map_alias() {
infer_eq_without_problem(
indoc!(
r#"
Res e a : [Ok a, Err e]
map : (a -> b), Res e a -> Res e b
map = \f, result ->
when result is
Ok v -> Ok (f v)
Err e -> Err e
map
"#
),
"(a -> b), Res e a -> Res e b",
);
}
#[test]
fn record_from_load() {
infer_eq_without_problem(
indoc!(
r#"
foo = \{ x } -> x
foo { x: 5 }
"#
),
"Num *",
);
}
#[test]
fn defs_from_load() {
infer_eq_without_problem(
indoc!(
r#"
alwaysThreePointZero = \_ -> 3.0
answer = 42
identity = \a -> a
threePointZero = identity (alwaysThreePointZero {})
threePointZero
"#
),
"Float *",
);
}
#[test]
fn use_as_in_signature() {
infer_eq_without_problem(
indoc!(
r#"
foo : Str.Str as Foo -> Foo
foo = \_ -> "foo"
foo
"#
),
"Foo -> Foo",
);
}
#[test]
fn use_alias_in_let() {
infer_eq_without_problem(
indoc!(
r#"
Foo : Str.Str
foo : Foo -> Foo
foo = \_ -> "foo"
foo
"#
),
"Foo -> Foo",
);
}
#[test]
fn use_alias_with_argument_in_let() {
infer_eq_without_problem(
indoc!(
r#"
Foo a : { foo : a }
v : Foo (Num.Num (Num.Integer Num.Signed64))
v = { foo: 42 }
v
"#
),
"Foo I64",
);
}
#[test]
fn identity_alias() {
infer_eq_without_problem(
indoc!(
r#"
Foo a : { foo : a }
id : Foo a -> Foo a
id = \x -> x
id
"#
),
"Foo a -> Foo a",
);
}
#[test]
fn linked_list_empty() {
infer_eq_without_problem(
indoc!(
r#"
empty : [Cons a (ConsList a), Nil] as ConsList a
empty = Nil
empty
"#
),
"ConsList a",
);
}
#[test]
fn linked_list_singleton() {
infer_eq_without_problem(
indoc!(
r#"
singleton : a -> [Cons a (ConsList a), Nil] as ConsList a
singleton = \x -> Cons x Nil
singleton
"#
),
"a -> ConsList a",
);
}
#[test]
fn peano_length() {
infer_eq_without_problem(
indoc!(
r#"
Peano : [S Peano, Z]
length : Peano -> Num.Num (Num.Integer Num.Signed64)
length = \peano ->
when peano is
Z -> 0
S v -> length v
length
"#
),
"Peano -> I64",
);
}
#[test]
fn peano_map() {
infer_eq_without_problem(
indoc!(
r#"
map : [S Peano, Z] as Peano -> Peano
map = \peano ->
when peano is
Z -> Z
S v -> S (map v)
map
"#
),
"Peano -> Peano",
);
}
#[test]
fn infer_linked_list_map() {
infer_eq_without_problem(
indoc!(
r#"
map = \f, list ->
when list is
Nil -> Nil
Cons x xs ->
a = f x
b = map f xs
Cons a b
map
"#
),
"(a -> b), [Cons a c, Nil] as c -> [Cons b d, Nil] as d",
);
}
#[test]
fn typecheck_linked_list_map() {
infer_eq_without_problem(
indoc!(
r#"
ConsList a : [Cons a (ConsList a), Nil]
map : (a -> b), ConsList a -> ConsList b
map = \f, list ->
when list is
Nil -> Nil
Cons x xs ->
Cons (f x) (map f xs)
map
"#
),
"(a -> b), ConsList a -> ConsList b",
);
}
#[test]
fn mismatch_in_alias_args_gets_reported() {
infer_eq(
indoc!(
r#"
Foo a : a
r : Foo {}
r = {}
s : Foo Str.Str
s = "bar"
when {} is
_ -> s
_ -> r
"#
),
"<type mismatch>",
);
}
#[test]
fn mismatch_in_apply_gets_reported() {
infer_eq(
indoc!(
r#"
r : { x : (Num.Num (Num.Integer Signed64)) }
r = { x : 1 }
s : { left : { x : Num.Num (Num.FloatingPoint Num.Binary64) } }
s = { left: { x : 3.14 } }
when 0 is
1 -> s.left
0 -> r
"#
),
"<type mismatch>",
);
}
#[test]
fn mismatch_in_tag_gets_reported() {
infer_eq(
indoc!(
r#"
r : [Ok Str.Str]
r = Ok 1
s : { left: [Ok {}] }
s = { left: Ok 3.14 }
when 0 is
1 -> s.left
0 -> r
"#
),
"<type mismatch>",
);
}
// TODO As intended, this fails, but it fails with the wrong error!
//
// #[test]
// fn nums() {
// infer_eq_without_problem(
// indoc!(
// r#"
// s : Num *
// s = 3.1
// s
// "#
// ),
// "<Type Mismatch: _____________>",
// );
// }
#[test]
fn peano_map_alias() {
infer_eq(
indoc!(
r#"
app "test" provides [main] to "./platform"
Peano : [S Peano, Z]
map : Peano -> Peano
map = \peano ->
when peano is
Z -> Z
S rest -> S (map rest)
main =
map
"#
),
"Peano -> Peano",
);
}
#[test]
fn unit_alias() {
infer_eq(
indoc!(
r#"
Unit : [Unit]
unit : Unit
unit = Unit
unit
"#
),
"Unit",
);
}
#[test]
fn rigid_in_letnonrec() {
infer_eq_without_problem(
indoc!(
r#"
ConsList a : [Cons a (ConsList a), Nil]
toEmpty : ConsList a -> ConsList a
toEmpty = \_ ->
result : ConsList a
result = Nil
result
toEmpty
"#
),
"ConsList a -> ConsList a",
);
}
#[test]
fn rigid_in_letrec_ignored() {
// re-enable when we don't capture local things that don't need to be!
infer_eq_without_problem(
indoc!(
r#"
ConsList a : [Cons a (ConsList a), Nil]
toEmpty : ConsList a -> ConsList a
toEmpty = \_ ->
result : ConsList a
result = Nil
toEmpty result
toEmpty
"#
),
"ConsList a -> ConsList a",
);
}
#[test]
fn rigid_in_letrec() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
ConsList a : [Cons a (ConsList a), Nil]
toEmpty : ConsList a -> ConsList a
toEmpty = \_ ->
result : ConsList a
result = Nil
toEmpty result
main =
toEmpty
"#
),
"ConsList a -> ConsList a",
);
}
#[test]
fn let_record_pattern_with_annotation() {
infer_eq_without_problem(
indoc!(
r#"
{ x, y } : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) }
{ x, y } = { x : "foo", y : 3.14 }
x
"#
),
"Str",
);
}
#[test]
fn let_record_pattern_with_annotation_alias() {
infer_eq(
indoc!(
r#"
Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) }
{ x, y } : Foo
{ x, y } = { x : "foo", y : 3.14 }
x
"#
),
"Str",
);
}
#[test]
fn peano_map_infer() {
infer_eq(
indoc!(
r#"
app "test" provides [main] to "./platform"
map =
\peano ->
when peano is
Z -> Z
S rest -> map rest |> S
main =
map
"#
),
"[S a, Z] as a -> [S b, Z] as b",
);
}
#[test]
fn peano_map_infer_nested() {
infer_eq(
indoc!(
r#"
map = \peano ->
when peano is
Z -> Z
S rest ->
map rest |> S
map
"#
),
"[S a, Z] as a -> [S b, Z] as b",
);
}
#[test]
fn let_record_pattern_with_alias_annotation() {
infer_eq_without_problem(
indoc!(
r#"
Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) }
{ x, y } : Foo
{ x, y } = { x : "foo", y : 3.14 }
x
"#
),
"Str",
);
}
#[test]
fn let_tag_pattern_with_annotation() {
infer_eq_without_problem(
indoc!(
r#"
UserId x : [UserId I64]
UserId x = UserId 42
x
"#
),
"I64",
);
}
#[test]
fn typecheck_record_linked_list_map() {
infer_eq_without_problem(
indoc!(
r#"
ConsList q : [Cons { x: q, xs: ConsList q }, Nil]
map : (a -> b), ConsList a -> ConsList b
map = \f, list ->
when list is
Nil -> Nil
Cons { x, xs } ->
Cons { x: f x, xs : map f xs }
map
"#
),
"(a -> b), ConsList a -> ConsList b",
);
}
#[test]
fn infer_record_linked_list_map() {
infer_eq_without_problem(
indoc!(
r#"
map = \f, list ->
when list is
Nil -> Nil
Cons { x, xs } ->
Cons { x: f x, xs : map f xs }
map
"#
),
"(a -> b), [Cons { x : a, xs : c }*, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d",
);
}
#[test]
fn typecheck_mutually_recursive_tag_union_2() {
infer_eq_without_problem(
indoc!(
r#"
ListA a b : [Cons a (ListB b a), Nil]
ListB a b : [Cons a (ListA b a), Nil]
ConsList q : [Cons q (ConsList q), Nil]
toAs : (b -> a), ListA a b -> ConsList a
toAs = \f, lista ->
when lista is
Nil -> Nil
Cons a listb ->
when listb is
Nil -> Nil
Cons b newLista ->
Cons a (Cons (f b) (toAs f newLista))
toAs
"#
),
"(b -> a), ListA a b -> ConsList a",
);
}
#[test]
fn typecheck_mutually_recursive_tag_union_listabc() {
infer_eq_without_problem(
indoc!(
r#"
ListA a : [Cons a (ListB a)]
ListB a : [Cons a (ListC a)]
ListC a : [Cons a (ListA a), Nil]
val : ListC Num.I64
val = Cons 1 (Cons 2 (Cons 3 Nil))
val
"#
),
"ListC I64",
);
}
#[test]
fn infer_mutually_recursive_tag_union() {
infer_eq_without_problem(
indoc!(
r#"
toAs = \f, lista ->
when lista is
Nil -> Nil
Cons a listb ->
when listb is
Nil -> Nil
Cons b newLista ->
Cons a (Cons (f b) (toAs f newLista))
toAs
"#
),
"(a -> b), [Cons c [Cons a d, Nil], Nil] as d -> [Cons c [Cons b e], Nil] as e",
);
}
#[test]
fn solve_list_get() {
infer_eq_without_problem(
indoc!(
r#"
List.get ["a"] 0
"#
),
"Result Str [OutOfBounds]",
);
}
#[test]
fn type_more_general_than_signature() {
infer_eq_without_problem(
indoc!(
r#"
partition : Nat, Nat, List (Int a) -> [Pair Nat (List (Int a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok _ ->
Pair 0 []
Err _ ->
Pair (low - 1) initialList
partition
"#
),
"Nat, Nat, List (Int a) -> [Pair Nat (List (Int a))]",
);
}
#[test]
fn quicksort_partition() {
with_larger_debug_stack(|| {
infer_eq_without_problem(
indoc!(
r#"
swap : Nat, Nat, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
list
|> List.set i atJ
|> List.set j atI
_ ->
list
partition : Nat, Nat, List (Int a) -> [Pair Nat (List (Int a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
go = \i, j, list ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
go (i + 1) (j + 1) (swap (i + 1) j list)
else
go i (j + 1) list
Err _ ->
Pair i list
else
Pair i list
when go (low - 1) low initialList is
Pair newI newList ->
Pair (newI + 1) (swap (newI + 1) high newList)
Err _ ->
Pair (low - 1) initialList
partition
"#
),
"Nat, Nat, List (Int a) -> [Pair Nat (List (Int a))]",
);
});
}
#[test]
fn identity_list() {
infer_eq_without_problem(
indoc!(
r#"
idList : List a -> List a
idList = \list -> list
foo : List I64 -> List I64
foo = \initialList -> idList initialList
foo
"#
),
"List I64 -> List I64",
);
}
#[test]
fn list_get() {
infer_eq_without_problem(
indoc!(
r#"
List.get [10, 9, 8, 7] 1
"#
),
"Result (Num *) [OutOfBounds]",
);
infer_eq_without_problem(
indoc!(
r#"
List.get
"#
),
"List a, Nat -> Result a [OutOfBounds]",
);
}
#[test]
fn use_rigid_twice() {
infer_eq_without_problem(
indoc!(
r#"
id1 : q -> q
id1 = \x -> x
id2 : q -> q
id2 = \x -> x
{ id1, id2 }
"#
),
"{ id1 : q -> q, id2 : q1 -> q1 }",
);
}
#[test]
fn map_insert() {
infer_eq_without_problem(
indoc!(
r#"
Dict.insert
"#
),
"Dict k v, k, v -> Dict k v | k has Hash & Eq",
);
}
#[test]
fn num_to_frac() {
infer_eq_without_problem(
indoc!(
r#"
Num.toFrac
"#
),
"Num * -> Float a",
);
}
#[test]
fn pow() {
infer_eq_without_problem(
indoc!(
r#"
Num.pow
"#
),
"Float a, Float a -> Float a",
);
}
#[test]
fn ceiling() {
infer_eq_without_problem(
indoc!(
r#"
Num.ceiling
"#
),
"Float * -> Int a",
);
}
#[test]
fn floor() {
infer_eq_without_problem(
indoc!(
r#"
Num.floor
"#
),
"Float * -> Int a",
);
}
#[test]
fn div() {
infer_eq_without_problem(
indoc!(
r#"
Num.div
"#
),
"Float a, Float a -> Float a",
)
}
#[test]
fn div_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divChecked
"#
),
"Float a, Float a -> Result (Float a) [DivByZero]",
)
}
#[test]
fn div_ceil() {
infer_eq_without_problem(
indoc!(
r#"
Num.divCeil
"#
),
"Int a, Int a -> Int a",
);
}
#[test]
fn div_ceil_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divCeilChecked
"#
),
"Int a, Int a -> Result (Int a) [DivByZero]",
);
}
#[test]
fn div_trunc() {
infer_eq_without_problem(
indoc!(
r#"
Num.divTrunc
"#
),
"Int a, Int a -> Int a",
);
}
#[test]
fn div_trunc_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divTruncChecked
"#
),
"Int a, Int a -> Result (Int a) [DivByZero]",
);
}
#[test]
fn atan() {
infer_eq_without_problem(
indoc!(
r#"
Num.atan
"#
),
"Float a -> Float a",
);
}
#[test]
fn min_i128() {
infer_eq_without_problem(
indoc!(
r#"
Num.minI128
"#
),
"I128",
);
}
#[test]
fn max_i128() {
infer_eq_without_problem(
indoc!(
r#"
Num.maxI128
"#
),
"I128",
);
}
#[test]
fn min_i64() {
infer_eq_without_problem(
indoc!(
r#"
Num.minI64
"#
),
"I64",
);
}
#[test]
fn max_i64() {
infer_eq_without_problem(
indoc!(
r#"
Num.maxI64
"#
),
"I64",
);
}
#[test]
fn min_u64() {
infer_eq_without_problem(
indoc!(
r#"
Num.minU64
"#
),
"U64",
);
}
#[test]
fn max_u64() {
infer_eq_without_problem(
indoc!(
r#"
Num.maxU64
"#
),
"U64",
);
}
#[test]
fn min_i32() {
infer_eq_without_problem(
indoc!(
r#"
Num.minI32
"#
),
"I32",
);
}
#[test]
fn max_i32() {
infer_eq_without_problem(
indoc!(
r#"
Num.maxI32
"#
),
"I32",
);
}
#[test]
fn min_u32() {
infer_eq_without_problem(
indoc!(
r#"
Num.minU32
"#
),
"U32",
);
}
#[test]
fn max_u32() {
infer_eq_without_problem(
indoc!(
r#"
Num.maxU32
"#
),
"U32",
);
}
#[test]
fn reconstruct_path() {
infer_eq_without_problem(
indoc!(
r#"
reconstructPath : Dict position position, position -> List position | position has Hash & Eq
reconstructPath = \cameFrom, goal ->
when Dict.get cameFrom goal is
Err KeyNotFound ->
[]
Ok next ->
List.append (reconstructPath cameFrom next) goal
reconstructPath
"#
),
"Dict position position, position -> List position | position has Hash & Eq",
);
}
#[test]
fn use_correct_ext_record() {
// Related to a bug solved in 81fbab0b3fe4765bc6948727e603fc2d49590b1c
infer_eq_without_problem(
indoc!(
r#"
f = \r ->
g = r.q
h = r.p
42
f
"#
),
"{ p : *, q : * }* -> Num *",
);
}
#[test]
fn use_correct_ext_tag_union() {
// related to a bug solved in 08c82bf151a85e62bce02beeed1e14444381069f
infer_eq_without_problem(
indoc!(
r#"
app "test" imports [Result.{ Result }] provides [main] to "./platform"
boom = \_ -> boom {}
Model position : { openSet : Set position }
cheapestOpen : Model position -> Result position [KeyNotFound] | position has Hash & Eq
cheapestOpen = \model ->
folder = \resSmallestSoFar, position ->
when resSmallestSoFar is
Err _ -> resSmallestSoFar
Ok smallestSoFar ->
if position == smallestSoFar.position then resSmallestSoFar
else
Ok { position, cost: 0.0 }
Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder
|> Result.map (\x -> x.position)
astar : Model position -> Result position [KeyNotFound] | position has Hash & Eq
astar = \model -> cheapestOpen model
main =
astar
"#
),
"Model position -> Result position [KeyNotFound] | position has Hash & Eq",
);
}
#[test]
fn when_with_or_pattern_and_guard() {
infer_eq_without_problem(
indoc!(
r#"
\x ->
when x is
2 | 3 -> 0
a if a < 20 -> 1
3 | 4 if Bool.false -> 2
_ -> 3
"#
),
"Num * -> Num *",
);
}
#[test]
fn sorting() {
// based on https://github.com/elm/compiler/issues/2057
// Roc seems to do this correctly, tracking to make sure it stays that way
infer_eq_without_problem(
indoc!(
r#"
sort : ConsList cm -> ConsList cm
sort =
\xs ->
f : cm, cm -> Order
f = \_, _ -> LT
sortWith f xs
sortBy : (x -> cmpl), ConsList x -> ConsList x
sortBy =
\_, list ->
cmp : x, x -> Order
cmp = \_, _ -> LT
sortWith cmp list
always = \x, _ -> x
sortWith : (foobar, foobar -> Order), ConsList foobar -> ConsList foobar
sortWith =
\_, list ->
f = \arg ->
g arg
g = \bs ->
when bs is
bx -> f bx
always Nil (f list)
Order : [LT, GT, EQ]
ConsList a : [Nil, Cons a (ConsList a)]
{ x: sortWith, y: sort, z: sortBy }
"#
),
"{ x : (foobar, foobar -> Order), ConsList foobar -> ConsList foobar, y : ConsList cm -> ConsList cm, z : (x -> cmpl), ConsList x -> ConsList x }"
);
}
// Like in elm, this test now fails. Polymorphic recursion (even with an explicit signature)
// yields a type error.
//
// We should at some point investigate why that is. Elm did support polymorphic recursion in
// earlier versions.
//
// #[test]
// fn wrapper() {
// // based on https://github.com/elm/compiler/issues/1964
// // Roc seems to do this correctly, tracking to make sure it stays that way
// infer_eq_without_problem(
// indoc!(
// r#"
// Type a : [TypeCtor (Type (Wrapper a))]
//
// Wrapper a : [Wrapper a]
//
// Opaque : [Opaque]
//
// encodeType1 : Type a -> Opaque
// encodeType1 = \thing ->
// when thing is
// TypeCtor v0 ->
// encodeType1 v0
//
// encodeType1
// "#
// ),
// "Type a -> Opaque",
// );
// }
#[test]
fn rigids() {
infer_eq_without_problem(
indoc!(
r#"
f : List a -> List a
f = \input ->
# let-polymorphism at work
x : List b
x = []
when List.get input 0 is
Ok val -> List.append x val
Err _ -> input
f
"#
),
"List a -> List a",
);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn rigid_record_quantification() {
// the ext here is qualified on the outside (because we have rank 1 types, not rank 2).
// That means e.g. `f : { bar : String, foo : I64 } -> Bool }` is a valid argument, but
// that function could not be applied to the `{ foo : I64 }` list. Therefore, this function
// is not allowed.
//
// should hit a debug_assert! in debug mode, and produce a type error in release mode
infer_eq_without_problem(
indoc!(
r#"
test : ({ foo : I64 }ext -> Bool), { foo : I64 } -> Bool
test = \fn, a -> fn a
test
"#
),
"should fail",
);
}
// OPTIONAL RECORD FIELDS
#[test]
fn optional_field_unifies_with_missing() {
infer_eq_without_problem(
indoc!(
r#"
negatePoint : { x : I64, y : I64, z ? Num c } -> { x : I64, y : I64, z : Num c }
negatePoint { x: 1, y: 2 }
"#
),
"{ x : I64, y : I64, z : Num c }",
);
}
#[test]
fn open_optional_field_unifies_with_missing() {
infer_eq_without_problem(
indoc!(
r#"
negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r
a = negatePoint { x: 1, y: 2 }
b = negatePoint { x: 1, y: 2, blah : "hi" }
{ a, b }
"#
),
"{ a : { x : I64, y : I64, z : Num c }, b : { blah : Str, x : I64, y : I64, z : Num c1 } }",
);
}
#[test]
fn optional_field_unifies_with_present() {
infer_eq_without_problem(
indoc!(
r#"
negatePoint : { x : Num a, y : Num b, z ? c } -> { x : Num a, y : Num b, z : c }
negatePoint { x: 1, y: 2.1, z: 0x3 }
"#
),
"{ x : Num *, y : Float *, z : Int * }",
);
}
#[test]
fn open_optional_field_unifies_with_present() {
infer_eq_without_problem(
indoc!(
r#"
negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r
a = negatePoint { x: 1, y: 2.1 }
b = negatePoint { x: 1, y: 2.1, blah : "hi" }
{ a, b }
"#
),
"{ a : { x : Num *, y : Float *, z : c }, b : { blah : Str, x : Num *, y : Float *, z : c1 } }",
);
}
#[test]
fn optional_field_function() {
infer_eq_without_problem(
indoc!(
r#"
\{ x, y ? 0 } -> x + y
"#
),
"{ x : Num a, y ? Num a }* -> Num a",
);
}
#[test]
fn optional_field_let() {
infer_eq_without_problem(
indoc!(
r#"
{ x, y ? 0 } = { x: 32 }
x + y
"#
),
"Num *",
);
}
#[test]
fn optional_field_when() {
infer_eq_without_problem(
indoc!(
r#"
\r ->
when r is
{ x, y ? 0 } -> x + y
"#
),
"{ x : Num a, y ? Num a }* -> Num a",
);
}
#[test]
fn optional_field_let_with_signature() {
infer_eq_without_problem(
indoc!(
r#"
\rec ->
{ x, y } : { x : I64, y ? Bool }*
{ x, y ? Bool.false } = rec
{ x, y }
"#
),
"{ x : I64, y ? Bool }* -> { x : I64, y : Bool }",
);
}
#[test]
fn list_walk_backwards() {
infer_eq_without_problem(
indoc!(
r#"
List.walkBackwards
"#
),
"List elem, state, (state, elem -> state) -> state",
);
}
#[test]
fn list_walk_backwards_example() {
infer_eq_without_problem(
indoc!(
r#"
empty : List I64
empty =
[]
List.walkBackwards empty 0 (\a, b -> a + b)
"#
),
"I64",
);
}
#[test]
fn list_drop_at() {
infer_eq_without_problem(
indoc!(
r#"
List.dropAt
"#
),
"List elem, Nat -> List elem",
);
}
#[test]
fn str_trim() {
infer_eq_without_problem(
indoc!(
r#"
Str.trim
"#
),
"Str -> Str",
);
}
#[test]
fn str_trim_left() {
infer_eq_without_problem(
indoc!(
r#"
Str.trimLeft
"#
),
"Str -> Str",
);
}
#[test]
fn list_take_first() {
infer_eq_without_problem(
indoc!(
r#"
List.takeFirst
"#
),
"List elem, Nat -> List elem",
);
}
#[test]
fn list_take_last() {
infer_eq_without_problem(
indoc!(
r#"
List.takeLast
"#
),
"List elem, Nat -> List elem",
);
}
#[test]
fn list_sublist() {
infer_eq_without_problem(
indoc!(
r#"
List.sublist
"#
),
"List elem, { len : Nat, start : Nat } -> List elem",
);
}
#[test]
fn list_split() {
infer_eq_without_problem(
indoc!("List.split"),
"List elem, Nat -> { before : List elem, others : List elem }",
);
}
#[test]
fn list_drop_last() {
infer_eq_without_problem(
indoc!(
r#"
List.dropLast
"#
),
"List elem -> List elem",
);
}
#[test]
fn list_intersperse() {
infer_eq_without_problem(
indoc!(
r#"
List.intersperse
"#
),
"List elem, elem -> List elem",
);
}
#[test]
fn function_that_captures_nothing_is_not_captured() {
// we should make sure that a function that doesn't capture anything it not itself captured
// such functions will be lifted to the top-level, and are thus globally available!
infer_eq_without_problem(
indoc!(
r#"
f = \x -> x + 1
g = \y -> f y
g
"#
),
"Num a -> Num a",
);
}
#[test]
fn double_named_rigids() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
main : List x
main =
empty : List x
empty = []
empty
"#
),
"List x",
);
}
#[test]
fn double_tag_application() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
if 1 == 1 then
Foo (Bar) 1
else
Foo Bar 1
"#
),
"[Foo [Bar] (Num *)]",
);
infer_eq_without_problem("Foo Bar 1", "[Foo [Bar] (Num *)]");
}
#[test]
fn double_tag_application_pattern() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
Bar : [Bar]
Foo : [Foo Bar I64, Empty]
foo : Foo
foo = Foo Bar 1
main =
when foo is
Foo Bar 1 ->
Foo Bar 2
x ->
x
"#
),
"[Empty, Foo [Bar] I64]",
);
}
#[test]
fn recursive_function_with_rigid() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
State a : { count : I64, x : a }
foo : State a -> I64
foo = \state ->
if state.count == 0 then
0
else
1 + foo { count: state.count - 1, x: state.x }
main : I64
main =
foo { count: 3, x: {} }
"#
),
"I64",
);
}
#[test]
fn rbtree_empty() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
# The color of a node. Leaves are considered Black.
NodeColor : [Red, Black]
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
# Create an empty dictionary.
empty : RBTree k v
empty =
Empty
foo : RBTree I64 I64
foo = empty
main : RBTree I64 I64
main =
foo
"#
),
"RBTree I64 I64",
);
}
#[test]
fn rbtree_insert() {
// exposed an issue where pattern variables were not introduced
// at the correct level in the constraint
//
// see 22592eff805511fbe1da63849771ee5f367a6a16
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
RBTree k : [Node k (RBTree k), Empty]
balance : RBTree k -> RBTree k
balance = \left ->
when left is
Node _ Empty -> Empty
_ -> Empty
main : RBTree {}
main =
balance Empty
"#
),
"RBTree {}",
);
}
#[test]
fn rbtree_full_remove_min() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
NodeColor : [Red, Black]
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
moveRedLeft : RBTree k v -> RBTree k v
moveRedLeft = \dict ->
when dict is
# Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) ->
# Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
Node clr k v (Node _ lK lV lLeft lRight) (Node _ rK rV rLeft rRight) ->
when rLeft is
Node Red rlK rlV rlL rlR ->
Node
Red
rlK
rlV
(Node Black k v (Node Red lK lV lLeft lRight) rlL)
(Node Black rK rV rlR rRight)
_ ->
when clr is
Black ->
Node
Black
k
v
(Node Red lK lV lLeft lRight)
(Node Red rK rV rLeft rRight)
Red ->
Node
Black
k
v
(Node Red lK lV lLeft lRight)
(Node Red rK rV rLeft rRight)
_ ->
dict
balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v
balance = \color, key, value, left, right ->
when right is
Node Red rK rV rLeft rRight ->
when left is
Node Red lK lV lLeft lRight ->
Node
Red
key
value
(Node Black lK lV lLeft lRight)
(Node Black rK rV rLeft rRight)
_ ->
Node color rK rV (Node Red key value left rLeft) rRight
_ ->
when left is
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black key value lRight right)
_ ->
Node color key value left right
Key k : Num k
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Hash & Eq
removeHelpEQGT = \targetKey, dict ->
when dict is
Node color key value left right ->
if targetKey == key then
when getMin right is
Node _ minKey minValue _ _ ->
balance color minKey minValue left (removeMin right)
Empty ->
Empty
else
balance color key value left (removeHelp targetKey right)
Empty ->
Empty
getMin : RBTree k v -> RBTree k v
getMin = \dict ->
when dict is
# Node _ _ _ ((Node _ _ _ _ _) as left) _ ->
Node _ _ _ left _ ->
when left is
Node _ _ _ _ _ -> getMin left
_ -> dict
_ ->
dict
moveRedRight : RBTree k v -> RBTree k v
moveRedRight = \dict ->
when dict is
Node clr k v (Node lClr lK lV (Node Red llK llV llLeft llRight) lRight) (Node rClr rK rV rLeft rRight) ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black k v lRight (Node Red rK rV rLeft rRight))
Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
when clr is
Black ->
Node
Black
k
v
(Node Red lK lV lLeft lRight)
(Node Red rK rV rLeft rRight)
Red ->
Node
Black
k
v
(Node Red lK lV lLeft lRight)
(Node Red rK rV rLeft rRight)
_ ->
dict
removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v
removeHelpPrepEQGT = \_, dict, color, key, value, left, right ->
when left is
Node Red lK lV lLeft lRight ->
Node
color
lK
lV
lLeft
(Node Red key value lRight right)
_ ->
when right is
Node Black _ _ (Node Black _ _ _ _) _ ->
moveRedRight dict
Node Black _ _ Empty _ ->
moveRedRight dict
_ ->
dict
removeMin : RBTree k v -> RBTree k v
removeMin = \dict ->
when dict is
Node color key value left right ->
when left is
Node lColor _ _ lLeft _ ->
when lColor is
Black ->
when lLeft is
Node Red _ _ _ _ ->
Node color key value (removeMin left) right
_ ->
when moveRedLeft dict is # here 1
Node nColor nKey nValue nLeft nRight ->
balance nColor nKey nValue (removeMin nLeft) nRight
Empty ->
Empty
_ ->
Node color key value (removeMin left) right
_ ->
Empty
_ ->
Empty
removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Hash & Eq
removeHelp = \targetKey, dict ->
when dict is
Empty ->
Empty
Node color key value left right ->
if targetKey < key then
when left is
Node Black _ _ lLeft _ ->
when lLeft is
Node Red _ _ _ _ ->
Node color key value (removeHelp targetKey left) right
_ ->
when moveRedLeft dict is # here 2
Node nColor nKey nValue nLeft nRight ->
balance nColor nKey nValue (removeHelp targetKey nLeft) nRight
Empty ->
Empty
_ ->
Node color key value (removeHelp targetKey left) right
else
removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right)
main : RBTree I64 I64
main =
removeHelp 1i64 Empty
"#
),
"RBTree (Key (Integer Signed64)) I64",
);
}
#[test]
fn rbtree_remove_min_1() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
RBTree k : [Node k (RBTree k) (RBTree k), Empty]
removeHelp : Num k, RBTree (Num k) -> RBTree (Num k)
removeHelp = \targetKey, dict ->
when dict is
Empty ->
Empty
Node key left right ->
if targetKey < key then
when left is
Node _ lLeft _ ->
when lLeft is
Node _ _ _ ->
Empty
_ -> Empty
_ ->
Node key (removeHelp targetKey left) right
else
Empty
main : RBTree I64
main =
removeHelp 1 Empty
"#
),
"RBTree I64",
);
}
#[test]
fn rbtree_foobar() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
NodeColor : [Red, Black]
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v | k has Hash & Eq
removeHelp = \targetKey, dict ->
when dict is
Empty ->
Empty
Node color key value left right ->
if targetKey < key then
when left is
Node Black _ _ lLeft _ ->
when lLeft is
Node Red _ _ _ _ ->
Node color key value (removeHelp targetKey left) right
_ ->
when moveRedLeft dict is # here 2
Node nColor nKey nValue nLeft nRight ->
balance nColor nKey nValue (removeHelp targetKey nLeft) nRight
Empty ->
Empty
_ ->
Node color key value (removeHelp targetKey left) right
else
removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right)
Key k : Num k
balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v
moveRedLeft : RBTree k v -> RBTree k v
removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Hash & Eq
removeHelpEQGT = \targetKey, dict ->
when dict is
Node color key value left right ->
if targetKey == key then
when getMin right is
Node _ minKey minValue _ _ ->
balance color minKey minValue left (removeMin right)
Empty ->
Empty
else
balance color key value left (removeHelp targetKey right)
Empty ->
Empty
getMin : RBTree k v -> RBTree k v
removeMin : RBTree k v -> RBTree k v
main : RBTree I64 I64
main =
removeHelp 1i64 Empty
"#
),
"RBTree I64 I64",
);
}
#[test]
fn quicksort_partition_help() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [partitionHelp] to "./platform"
swap : Nat, Nat, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
list
|> List.set i atJ
|> List.set j atI
_ ->
[]
partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [Pair Nat (List (Num a))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
else
partitionHelp i (j + 1) list high pivot
Err _ ->
Pair i list
else
Pair i list
"#
),
"Nat, Nat, List (Num a), Nat, Num a -> [Pair Nat (List (Num a))]",
);
}
#[test]
fn rbtree_old_balance_simplified() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
RBTree k : [Node k (RBTree k) (RBTree k), Empty]
balance : k, RBTree k -> RBTree k
balance = \key, left ->
Node key left Empty
main : RBTree I64
main =
balance 0 Empty
"#
),
"RBTree I64",
);
}
#[test]
fn rbtree_balance_simplified() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
RBTree k : [Node k (RBTree k) (RBTree k), Empty]
node = \x,y,z -> Node x y z
balance : k, RBTree k -> RBTree k
balance = \key, left ->
node key left Empty
main : RBTree I64
main =
balance 0 Empty
"#
),
"RBTree I64",
);
}
#[test]
fn rbtree_balance() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
NodeColor : [Red, Black]
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v
balance = \color, key, value, left, right ->
when right is
Node Red rK rV rLeft rRight ->
when left is
Node Red lK lV lLeft lRight ->
Node
Red
key
value
(Node Black lK lV lLeft lRight)
(Node Black rK rV rLeft rRight)
_ ->
Node color rK rV (Node Red key value left rLeft) rRight
_ ->
when left is
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black key value lRight right)
_ ->
Node color key value left right
main : RBTree I64 I64
main =
balance Red 0 0 Empty Empty
"#
),
"RBTree I64 I64",
);
}
#[test]
fn pattern_rigid_problem() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
RBTree k : [Node k (RBTree k) (RBTree k), Empty]
balance : k, RBTree k -> RBTree k
balance = \key, left ->
when left is
Node _ _ lRight ->
Node key lRight Empty
_ ->
Empty
main : RBTree I64
main =
balance 0 Empty
"#
),
"RBTree I64",
);
}
#[test]
fn expr_to_str() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
Expr : [Add Expr Expr, Val I64, Var I64]
printExpr : Expr -> Str
printExpr = \e ->
when e is
Add a b ->
"Add ("
|> Str.concat (printExpr a)
|> Str.concat ") ("
|> Str.concat (printExpr b)
|> Str.concat ")"
Val v -> Num.toStr v
Var v -> "Var " |> Str.concat (Num.toStr v)
main : Str
main = printExpr (Var 3)
"#
),
"Str",
);
}
#[test]
fn int_type_let_polymorphism() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
x = 4
f : U8 -> U32
f = \z -> Num.intCast z
y = f x
main =
x
"#
),
"Num *",
);
}
#[test]
fn rigid_type_variable_problem() {
// see https://github.com/roc-lang/roc/issues/1162
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
RBTree k : [Node k (RBTree k) (RBTree k), Empty]
balance : a, RBTree a -> RBTree a
balance = \key, left ->
when left is
Node _ _ lRight ->
Node key lRight Empty
_ ->
Empty
main : RBTree {}
main =
balance {} Empty
"#
),
"RBTree {}",
);
}
#[test]
fn inference_var_inside_arrow() {
infer_eq_without_problem(
indoc!(
r#"
id : _ -> _
id = \x -> x
id
"#
),
"a -> a",
)
}
#[test]
fn inference_var_inside_ctor() {
infer_eq_without_problem(
indoc!(
r#"
canIGo : _ -> Result.Result _ _
canIGo = \color ->
when color is
"green" -> Ok "go!"
"yellow" -> Err (SlowIt "whoa, let's slow down!")
"red" -> Err (StopIt "absolutely not")
_ -> Err (UnknownColor "this is a weird stoplight")
canIGo
"#
),
"Str -> Result Str [SlowIt Str, StopIt Str, UnknownColor Str]",
)
}
#[test]
fn inference_var_inside_ctor_linked() {
infer_eq_without_problem(
indoc!(
r#"
swapRcd: {x: _, y: _} -> {x: _, y: _}
swapRcd = \{x, y} -> {x: y, y: x}
swapRcd
"#
),
"{ x : a, y : b } -> { x : b, y : a }",
)
}
#[test]
fn inference_var_link_with_rigid() {
infer_eq_without_problem(
indoc!(
r#"
swapRcd: {x: tx, y: ty} -> {x: _, y: _}
swapRcd = \{x, y} -> {x: y, y: x}
swapRcd
"#
),
"{ x : tx, y : ty } -> { x : ty, y : tx }",
)
}
#[test]
fn inference_var_inside_tag_ctor() {
infer_eq_without_problem(
indoc!(
r#"
badComics: [True, False] -> [CowTools _, Thagomizer _]
badComics = \c ->
when c is
True -> CowTools "The Far Side"
False -> Thagomizer "The Far Side"
badComics
"#
),
"[False, True] -> [CowTools Str, Thagomizer Str]",
)
}
#[test]
fn inference_var_tag_union_ext() {
// TODO: we should really be inferring [Blue, Orange]a -> [Lavender, Peach]a here.
// See https://github.com/roc-lang/roc/issues/2053
infer_eq_without_problem(
indoc!(
r#"
pastelize: _ -> [Lavender, Peach]_
pastelize = \color ->
when color is
Blue -> Lavender
Orange -> Peach
col -> col
pastelize
"#
),
"[Blue, Lavender, Orange, Peach]a -> [Blue, Lavender, Orange, Peach]a",
)
}
#[test]
fn inference_var_rcd_union_ext() {
infer_eq_without_problem(
indoc!(
r#"
setRocEmail : _ -> { name: Str, email: Str }_
setRocEmail = \person ->
{ person & email: "\(person.name)@roclang.com" }
setRocEmail
"#
),
"{ email : Str, name : Str }a -> { email : Str, name : Str }a",
)
}
#[test]
fn issue_2217() {
infer_eq_without_problem(
indoc!(
r#"
LinkedList elem : [Empty, Prepend (LinkedList elem) elem]
fromList : List elem -> LinkedList elem
fromList = \elems -> List.walk elems Empty Prepend
fromList
"#
),
"List elem -> LinkedList elem",
)
}
#[test]
fn issue_2217_inlined() {
infer_eq_without_problem(
indoc!(
r#"
fromList : List elem -> [Empty, Prepend (LinkedList elem) elem] as LinkedList elem
fromList = \elems -> List.walk elems Empty Prepend
fromList
"#
),
"List elem -> LinkedList elem",
)
}
#[test]
fn infer_union_input_position1() {
infer_eq_without_problem(
indoc!(
r#"
\tag ->
when tag is
A -> X
B -> Y
"#
),
"[A, B] -> [X, Y]",
)
}
#[test]
fn infer_union_input_position2() {
infer_eq_without_problem(
indoc!(
r#"
\tag ->
when tag is
A -> X
B -> Y
_ -> Z
"#
),
"[A, B]* -> [X, Y, Z]",
)
}
#[test]
fn infer_union_input_position3() {
infer_eq_without_problem(
indoc!(
r#"
\tag ->
when tag is
A M -> X
A N -> Y
"#
),
"[A [M, N]] -> [X, Y]",
)
}
#[test]
fn infer_union_input_position4() {
infer_eq_without_problem(
indoc!(
r#"
\tag ->
when tag is
A M -> X
A N -> Y
A _ -> Z
"#
),
"[A [M, N]*] -> [X, Y, Z]",
)
}
#[test]
fn infer_union_input_position5() {
infer_eq_without_problem(
indoc!(
r#"
\tag ->
when tag is
A (M J) -> X
A (N K) -> X
"#
),
"[A [M [J], N [K]]] -> [X]",
)
}
#[test]
fn infer_union_input_position6() {
infer_eq_without_problem(
indoc!(
r#"
\tag ->
when tag is
A M -> X
B -> X
A N -> X
"#
),
"[A [M, N], B] -> [X]",
)
}
#[test]
fn infer_union_input_position7() {
infer_eq_without_problem(
indoc!(
r#"
\tag ->
when tag is
A -> X
t -> t
"#
),
"[A, X]a -> [A, X]a",
)
}
#[test]
fn infer_union_input_position8() {
infer_eq_without_problem(
indoc!(
r#"
\opt ->
when opt is
Some ({tag: A}) -> 1
Some ({tag: B}) -> 1
None -> 0
"#
),
"[None, Some { tag : [A, B] }*] -> Num *",
)
}
#[test]
fn infer_union_input_position9() {
infer_eq_without_problem(
indoc!(
r#"
opt : [Some Str, None]
opt = Some ""
rcd = { opt }
when rcd is
{ opt: Some s } -> s
{ opt: None } -> "?"
"#
),
"Str",
)
}
#[test]
fn infer_union_input_position10() {
infer_eq_without_problem(
indoc!(
r#"
\r ->
when r is
{ x: Blue, y ? 3 } -> y
{ x: Red, y ? 5 } -> y
"#
),
"{ x : [Blue, Red], y ? Num a }* -> Num a",
)
}
#[test]
// Issue #2299
fn infer_union_argument_position() {
infer_eq_without_problem(
indoc!(
r#"
\UserId id -> id + 1
"#
),
"[UserId (Num a)] -> Num a",
)
}
#[test]
fn infer_union_def_position() {
infer_eq_without_problem(
indoc!(
r#"
\email ->
Email str = email
Str.isEmpty str
"#
),
"[Email Str] -> Bool",
)
}
#[test]
fn numeric_literal_suffixes() {
infer_eq_without_problem(
indoc!(
r#"
{
u8: 123u8,
u16: 123u16,
u32: 123u32,
u64: 123u64,
u128: 123u128,
i8: 123i8,
i16: 123i16,
i32: 123i32,
i64: 123i64,
i128: 123i128,
nat: 123nat,
bu8: 0b11u8,
bu16: 0b11u16,
bu32: 0b11u32,
bu64: 0b11u64,
bu128: 0b11u128,
bi8: 0b11i8,
bi16: 0b11i16,
bi32: 0b11i32,
bi64: 0b11i64,
bi128: 0b11i128,
bnat: 0b11nat,
dec: 123.0dec,
f32: 123.0f32,
f64: 123.0f64,
fdec: 123dec,
ff32: 123f32,
ff64: 123f64,
}
"#
),
r#"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bnat : Nat, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, nat : Nat, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }"#,
)
}
#[test]
fn numeric_literal_suffixes_in_pattern() {
infer_eq_without_problem(
indoc!(
r#"
{
u8: (\n ->
when n is
123u8 -> n
_ -> n),
u16: (\n ->
when n is
123u16 -> n
_ -> n),
u32: (\n ->
when n is
123u32 -> n
_ -> n),
u64: (\n ->
when n is
123u64 -> n
_ -> n),
u128: (\n ->
when n is
123u128 -> n
_ -> n),
i8: (\n ->
when n is
123i8 -> n
_ -> n),
i16: (\n ->
when n is
123i16 -> n
_ -> n),
i32: (\n ->
when n is
123i32 -> n
_ -> n),
i64: (\n ->
when n is
123i64 -> n
_ -> n),
i128: (\n ->
when n is
123i128 -> n
_ -> n),
nat: (\n ->
when n is
123nat -> n
_ -> n),
bu8: (\n ->
when n is
0b11u8 -> n
_ -> n),
bu16: (\n ->
when n is
0b11u16 -> n
_ -> n),
bu32: (\n ->
when n is
0b11u32 -> n
_ -> n),
bu64: (\n ->
when n is
0b11u64 -> n
_ -> n),
bu128: (\n ->
when n is
0b11u128 -> n
_ -> n),
bi8: (\n ->
when n is
0b11i8 -> n
_ -> n),
bi16: (\n ->
when n is
0b11i16 -> n
_ -> n),
bi32: (\n ->
when n is
0b11i32 -> n
_ -> n),
bi64: (\n ->
when n is
0b11i64 -> n
_ -> n),
bi128: (\n ->
when n is
0b11i128 -> n
_ -> n),
bnat: (\n ->
when n is
0b11nat -> n
_ -> n),
dec: (\n ->
when n is
123.0dec -> n
_ -> n),
f32: (\n ->
when n is
123.0f32 -> n
_ -> n),
f64: (\n ->
when n is
123.0f64 -> n
_ -> n),
fdec: (\n ->
when n is
123dec -> n
_ -> n),
ff32: (\n ->
when n is
123f32 -> n
_ -> n),
ff64: (\n ->
when n is
123f64 -> n
_ -> n),
}
"#
),
r#"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bnat : Nat -> Nat, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, nat : Nat -> Nat, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }"#,
)
}
#[test]
fn issue_2458() {
infer_eq_without_problem(
indoc!(
r#"
Foo a : [Blah (Result (Bar a) { val: a })]
Bar a : Foo a
v : Bar U8
v = Blah (Ok (Blah (Err { val: 1 })))
v
"#
),
"Bar U8",
)
}
#[test]
fn issue_2458_swapped_order() {
infer_eq_without_problem(
indoc!(
r#"
Bar a : Foo a
Foo a : [Blah (Result (Bar a) { val: a })]
v : Bar U8
v = Blah (Ok (Blah (Err { val: 1 })))
v
"#
),
"Bar U8",
)
}
// https://github.com/roc-lang/roc/issues/2379
#[test]
fn copy_vars_referencing_copied_vars() {
infer_eq_without_problem(
indoc!(
r#"
Job : [Job [Command] (List Job)]
job : Job
job
"#
),
"Job",
)
}
#[test]
fn generalize_and_specialize_recursion_var() {
infer_eq_without_problem(
indoc!(
r#"
Job a : [Job (List (Job a)) a]
job : Job Str
when job is
Job lst s -> P lst s
"#
),
"[P (List ([Job (List a) Str] as a)) Str]",
)
}
#[test]
fn to_int() {
infer_eq_without_problem(
indoc!(
r#"
{
toI8: Num.toI8,
toI16: Num.toI16,
toI32: Num.toI32,
toI64: Num.toI64,
toI128: Num.toI128,
toNat: Num.toNat,
toU8: Num.toU8,
toU16: Num.toU16,
toU32: Num.toU32,
toU64: Num.toU64,
toU128: Num.toU128,
}
"#
),
r#"{ toI128 : Int * -> I128, toI16 : Int a -> I16, toI32 : Int b -> I32, toI64 : Int c -> I64, toI8 : Int d -> I8, toNat : Int e -> Nat, toU128 : Int f -> U128, toU16 : Int g -> U16, toU32 : Int h -> U32, toU64 : Int i -> U64, toU8 : Int j -> U8 }"#,
)
}
#[test]
fn to_float() {
infer_eq_without_problem(
indoc!(
r#"
{
toF32: Num.toF32,
toF64: Num.toF64,
}
"#
),
r#"{ toF32 : Num * -> F32, toF64 : Num a -> F64 }"#,
)
}
#[test]
fn opaque_wrap_infer() {
infer_eq_without_problem(
indoc!(
r#"
Age := U32
@Age 21
"#
),
r#"Age"#,
)
}
#[test]
fn opaque_wrap_check() {
infer_eq_without_problem(
indoc!(
r#"
Age := U32
a : Age
a = @Age 21
a
"#
),
r#"Age"#,
)
}
#[test]
fn opaque_wrap_polymorphic_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
@Id (Id 21 "sasha")
"#
),
r#"Id Str"#,
)
}
#[test]
fn opaque_wrap_polymorphic_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
a : Id Str
a = @Id (Id 21 "sasha")
a
"#
),
r#"Id Str"#,
)
}
#[test]
fn opaque_wrap_polymorphic_from_multiple_branches_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
condition : Bool
if condition
then @Id (Id 21 (Y "sasha"))
else @Id (Id 21 (Z "felix"))
"#
),
r#"Id [Y Str, Z Str]"#,
)
}
#[test]
fn opaque_wrap_polymorphic_from_multiple_branches_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
condition : Bool
v : Id [Y Str, Z Str]
v =
if condition
then @Id (Id 21 (Y "sasha"))
else @Id (Id 21 (Z "felix"))
v
"#
),
r#"Id [Y Str, Z Str]"#,
)
}
#[test]
fn opaque_unwrap_infer() {
infer_eq_without_problem(
indoc!(
r#"
Age := U32
\@Age n -> n
"#
),
r#"Age -> U32"#,
)
}
#[test]
fn opaque_unwrap_check() {
infer_eq_without_problem(
indoc!(
r#"
Age := U32
v : Age -> U32
v = \@Age n -> n
v
"#
),
r#"Age -> U32"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
\@Id (Id _ n) -> n
"#
),
r#"Id a -> a"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
v : Id a -> a
v = \@Id (Id _ n) -> n
v
"#
),
r#"Id a -> a"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_specialized_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
strToBool : Str -> Bool
\@Id (Id _ n) -> strToBool n
"#
),
r#"Id Str -> Bool"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_specialized_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
strToBool : Str -> Bool
v : Id Str -> Bool
v = \@Id (Id _ n) -> strToBool n
v
"#
),
r#"Id Str -> Bool"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_from_multiple_branches_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
\id ->
when id is
@Id (Id _ A) -> ""
@Id (Id _ B) -> ""
@Id (Id _ (C { a: "" })) -> ""
@Id (Id _ (C { a: _ })) -> "" # any other string, for exhautiveness
"#
),
r#"Id [A, B, C { a : Str }*] -> Str"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_from_multiple_branches_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [Id U32 n]
f : Id [A, B, C { a : Str }e] -> Str
f = \id ->
when id is
@Id (Id _ A) -> ""
@Id (Id _ B) -> ""
@Id (Id _ (C { a: "" })) -> ""
@Id (Id _ (C { a: _ })) -> "" # any other string, for exhautiveness
f
"#
),
r#"Id [A, B, C { a : Str }e] -> Str"#,
)
}
#[test]
fn lambda_set_within_alias_is_quantified() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [effectAlways] to "./platform"
Effect a := {} -> a
effectAlways : a -> Effect a
effectAlways = \x ->
inner = \{} -> x
@Effect inner
"#
),
r#"a -> Effect a"#,
)
}
#[test]
fn generalized_accessor_function_applied() {
infer_eq_without_problem(
indoc!(
r#"
returnFoo = .foo
returnFoo { foo: "foo" }
"#
),
"Str",
)
}
#[test]
fn record_extension_variable_is_alias() {
infer_eq_without_problem(
indoc!(
r#"
Other a b : { y: a, z: b }
f : { x : Str }(Other Str Str)
f
"#
),
r#"{ x : Str, y : Str, z : Str }"#,
)
}
#[test]
fn tag_extension_variable_is_alias() {
infer_eq_without_problem(
indoc!(
r#"
Other : [B, C]
f : [A]Other
f
"#
),
r#"[A, B, C]"#,
)
}
#[test]
// https://github.com/roc-lang/roc/issues/2702
fn tag_inclusion_behind_opaque() {
infer_eq_without_problem(
indoc!(
r#"
Outer k := [Empty, Wrapped k]
insert : Outer k, k -> Outer k
insert = \m, var ->
when m is
@Outer Empty -> @Outer (Wrapped var)
@Outer (Wrapped _) -> @Outer (Wrapped var)
insert
"#
),
r#"Outer k, k -> Outer k"#,
)
}
#[test]
fn tag_inclusion_behind_opaque_infer() {
infer_eq_without_problem(
indoc!(
r#"
Outer k := [Empty, Wrapped k]
when (@Outer Empty) is
@Outer Empty -> @Outer (Wrapped "")
@Outer (Wrapped k) -> @Outer (Wrapped k)
"#
),
r#"Outer Str"#,
)
}
#[test]
fn tag_inclusion_behind_opaque_infer_single_ctor() {
infer_eq_without_problem(
indoc!(
r#"
Outer := [A, B]
when (@Outer A) is
@Outer A -> @Outer A
@Outer B -> @Outer B
"#
),
r#"Outer"#,
)
}
#[test]
fn issue_2583_specialize_errors_behind_unified_branches() {
infer_eq_without_problem(
indoc!(
r#"
if Bool.true then List.first [] else Str.toI64 ""
"#
),
"Result I64 [InvalidNumStr, ListWasEmpty]",
)
}
#[test]
fn lots_of_type_variables() {
infer_eq_without_problem(
indoc!(
r#"
fun = \a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,aa,bb -> {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,aa,bb}
fun
"#
),
"a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb -> { a : a, aa : aa, b : b, bb : bb, c : c, d : d, e : e, f : f, g : g, h : h, i : i, j : j, k : k, l : l, m : m, n : n, o : o, p : p, q : q, r : r, s : s, t : t, u : u, v : v, w : w, x : x, y : y, z : z }",
)
}
#[test]
fn exposed_ability_name() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [hash] to "./platform"
MHash has hash : a -> U64 | a has MHash
"#
),
"a -> U64 | a has MHash",
)
}
#[test]
fn single_ability_single_member_specializations() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [hash] to "./platform"
MHash has hash : a -> U64 | a has MHash
Id := U64 has [MHash {hash}]
hash = \@Id n -> n
"#
),
[("MHash:hash", "Id")],
)
}
#[test]
fn single_ability_multiple_members_specializations() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [hash, hash32] to "./platform"
MHash has
hash : a -> U64 | a has MHash
hash32 : a -> U32 | a has MHash
Id := U64 has [MHash {hash, hash32}]
hash = \@Id n -> n
hash32 = \@Id n -> Num.toU32 n
"#
),
[("MHash:hash", "Id"), ("MHash:hash32", "Id")],
)
}
#[test]
fn multiple_abilities_multiple_members_specializations() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [hash, hash32, eq, le] to "./platform"
MHash has
hash : a -> U64 | a has MHash
hash32 : a -> U32 | a has MHash
Ord has
eq : a, a -> Bool | a has Ord
le : a, a -> Bool | a has Ord
Id := U64 has [MHash {hash, hash32}, Ord {eq, le}]
hash = \@Id n -> n
hash32 = \@Id n -> Num.toU32 n
eq = \@Id m, @Id n -> m == n
le = \@Id m, @Id n -> m < n
"#
),
[
("MHash:hash", "Id"),
("MHash:hash32", "Id"),
("Ord:eq", "Id"),
("Ord:le", "Id"),
],
)
}
#[test]
fn ability_checked_specialization_with_typed_body() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [hash] to "./platform"
MHash has
hash : a -> U64 | a has MHash
Id := U64 has [MHash {hash}]
hash : Id -> U64
hash = \@Id n -> n
"#
),
[("MHash:hash", "Id")],
)
}
#[test]
fn ability_checked_specialization_with_annotation_only() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [hash] to "./platform"
MHash has
hash : a -> U64 | a has MHash
Id := U64 has [MHash {hash}]
hash : Id -> U64
"#
),
[("MHash:hash", "Id")],
)
}
#[test]
fn ability_specialization_called() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [zero] to "./platform"
MHash has
hash : a -> U64 | a has MHash
Id := U64 has [MHash {hash}]
hash = \@Id n -> n
zero = hash (@Id 0)
"#
),
"U64",
)
}
#[test]
fn alias_ability_member() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [thething] to "./platform"
MHash has
hash : a -> U64 | a has MHash
thething =
itis = hash
itis
"#
),
"a -> U64 | a has MHash",
)
}
#[test]
fn when_branch_and_body_flipflop() {
infer_eq_without_problem(
indoc!(
r#"
func = \record ->
when record.tag is
A -> { record & tag: B }
B -> { record & tag: A }
func
"#
),
"{ tag : [A, B] }a -> { tag : [A, B] }a",
)
}
#[test]
fn ability_constrained_in_non_member_check() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [hashEq] to "./platform"
MHash has
hash : a -> U64 | a has MHash
hashEq : a, a -> Bool | a has MHash
hashEq = \x, y -> hash x == hash y
"#
),
"a, a -> Bool | a has MHash",
)
}
#[test]
fn ability_constrained_in_non_member_infer() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [hashEq] to "./platform"
MHash has
hash : a -> U64 | a has MHash
hashEq = \x, y -> hash x == hash y
"#
),
"a, a1 -> Bool | a has MHash, a1 has MHash",
)
}
#[test]
fn ability_constrained_in_non_member_infer_usage() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [result] to "./platform"
MHash has
hash : a -> U64 | a has MHash
hashEq = \x, y -> hash x == hash y
Id := U64 has [MHash {hash}]
hash = \@Id n -> n
result = hashEq (@Id 100) (@Id 101)
"#
),
"Bool",
)
}
#[test]
fn ability_constrained_in_non_member_multiple_specializations() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [result] to "./platform"
MHash has
hash : a -> U64 | a has MHash
mulMHashes = \x, y -> hash x * hash y
Id := U64 has [MHash { hash: hashId }]
hashId = \@Id n -> n
Three := {} has [MHash { hash: hashThree }]
hashThree = \@Three _ -> 3
result = mulMHashes (@Id 100) (@Three {})
"#
),
"U64",
)
}
#[test]
fn intermediate_branch_types() {
infer_queries!(
indoc!(
r#"
app "test" provides [foo] to "./platform"
foo : [True, False] -> Str
foo = \ob ->
# ^^
when ob is
# ^^
True -> "A"
# ^^^^
False -> "B"
# ^^^^^
"#
),
@r###"
ob : [False, True]
ob : [False, True]
True : [False, True]
False : [False, True]
"###
)
}
#[test]
fn nested_open_tag_union() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [go] to "./platform"
Expr : [
Wrap Expr,
Val I64,
]
go : Expr -> Expr
go = \e ->
when P e is
P (Wrap (Val _)) -> Wrap e
# This branch should force the first argument to `P` and
# the first argument to `Wrap` to be an open tag union.
# This tests checks that we don't regress on that.
P y1 -> Wrap y1
"#
),
indoc!(r#"Expr -> Expr"#),
)
}
#[test]
fn opaque_and_alias_unify() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [always] to "./platform"
Effect a := {} -> a
Task a err : Effect (Result a err)
always : a -> Task a *
always = \x -> @Effect (\{} -> Ok x)
"#
),
"a -> Task a *",
);
}
#[test]
fn export_rigid_to_lower_rank() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [foo] to "./platform"
F a : { foo : a }
foo = \arg ->
x : F b
x = arg
x.foo
"#
),
"F b -> b",
);
}
#[test]
fn alias_in_opaque() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [foo] to "./platform"
MyError : [Error]
MyResult := Result U8 MyError
foo = @MyResult (Err Error)
"#
),
"MyResult",
)
}
#[test]
fn alias_propagates_able_var() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [zeroEncoder] to "./platform"
MEncoder fmt := List U8, fmt -> List U8 | fmt has Format
Format has it : fmt -> {} | fmt has Format
zeroEncoder = @MEncoder \lst, _ -> lst
"#
),
"MEncoder a | a has Format",
)
}
#[test]
fn encoder() {
infer_queries!(
indoc!(
r#"
app "test" provides [myU8Bytes] to "./platform"
MEncoder fmt := List U8, fmt -> List U8 | fmt has Format
MEncoding has
toEncoder : val -> MEncoder fmt | val has MEncoding, fmt has Format
Format has
u8 : U8 -> MEncoder fmt | fmt has Format
appendWith : List U8, MEncoder fmt, fmt -> List U8 | fmt has Format
appendWith = \lst, (@MEncoder doFormat), fmt -> doFormat lst fmt
toBytes : val, fmt -> List U8 | val has MEncoding, fmt has Format
toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt
Linear := {} has [Format {u8}]
u8 = \n -> @MEncoder (\lst, @Linear {} -> List.append lst n)
#^^{-1}
MyU8 := U8 has [MEncoding {toEncoder}]
toEncoder = \@MyU8 n -> u8 n
#^^^^^^^^^{-1}
myU8Bytes = toBytes (@MyU8 15) (@Linear {})
#^^^^^^^^^{-1}
"#
),
@r###"
Linear#u8(10) : U8 -[[u8(10)]]-> MEncoder Linear
MyU8#toEncoder(11) : MyU8 -[[toEncoder(11)]]-> MEncoder fmt | fmt has Format
myU8Bytes : List U8
"###
)
}
#[test]
fn decoder() {
infer_queries!(
indoc!(
r#"
app "test" provides [myU8] to "./platform"
MDecodeError : [TooShort, Leftover (List U8)]
MDecoder val fmt := List U8, fmt -> { result: Result val MDecodeError, rest: List U8 } | fmt has MDecoderFormatting
MDecoding has
decoder : MDecoder val fmt | val has MDecoding, fmt has MDecoderFormatting
MDecoderFormatting has
u8 : MDecoder U8 fmt | fmt has MDecoderFormatting
decodeWith : List U8, MDecoder val fmt, fmt -> { result: Result val MDecodeError, rest: List U8 } | fmt has MDecoderFormatting
decodeWith = \lst, (@MDecoder doDecode), fmt -> doDecode lst fmt
fromBytes : List U8, fmt -> Result val MDecodeError
| fmt has MDecoderFormatting, val has MDecoding
fromBytes = \lst, fmt ->
when decodeWith lst decoder fmt is
{ result, rest } ->
when result is
Ok val -> if List.isEmpty rest then Ok val else Err (Leftover rest)
Err e -> Err e
Linear := {} has [MDecoderFormatting {u8}]
u8 = @MDecoder \lst, @Linear {} ->
#^^{-1}
when List.first lst is
Ok n -> { result: Ok n, rest: List.dropFirst lst }
Err _ -> { result: Err TooShort, rest: [] }
MyU8 := U8 has [MDecoding {decoder}]
decoder = @MDecoder \lst, fmt ->
#^^^^^^^{-1}
when decodeWith lst u8 fmt is
{ result, rest } ->
{ result: Result.map result (\n -> @MyU8 n), rest }
myU8 : Result MyU8 _
myU8 = fromBytes [15] (@Linear {})
#^^^^{-1}
"#
),
@r###"
Linear#u8(11) : MDecoder U8 Linear
MyU8#decoder(12) : MDecoder MyU8 fmt | fmt has MDecoderFormatting
myU8 : Result MyU8 MDecodeError
"###
)
}
#[test]
fn task_wildcard_wildcard() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [tforever] to "./platform"
Effect a := {} -> a
eforever : Effect a -> Effect b
Task a err : Effect (Result a err)
tforever : Task val err -> Task * *
tforever = \task -> eforever task
"#
),
"Task val err -> Task * *",
);
}
#[test]
fn static_specialization() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Default has default : {} -> a | a has Default
A := {} has [Default {default}]
default = \{} -> @A {}
main =
a : A
a = default {}
# ^^^^^^^
a
"#
),
@"A#default(4) : {} -[[default(4)]]-> A"
)
}
#[test]
fn stdlib_encode_json() {
infer_eq_without_problem(
indoc!(
r#"
app "test"
imports [Json]
provides [main] to "./platform"
HelloWorld := {} has [Encoding {toEncoder}]
toEncoder = \@HelloWorld {} ->
Encode.custom \bytes, fmt ->
bytes
|> Encode.appendWith (Encode.string "Hello, World!\n") fmt
main =
when Str.fromUtf8 (Encode.toBytes (@HelloWorld {}) Json.toUtf8) is
Ok s -> s
_ -> "<bad>"
"#
),
"Str",
)
}
#[test]
fn encode_record() {
infer_queries!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }]
provides [main] to "./platform"
main = toEncoder { a: "" }
# ^^^^^^^^^
"#
),
@"Encoding#toEncoder(2) : { a : Str } -[[#Derived.toEncoder_{a}(0)]]-> Encoder fmt | fmt has EncoderFormatting"
)
}
#[test]
fn encode_record_with_nested_custom_impl() {
infer_queries!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder, custom }]
provides [main] to "./platform"
A := {} has [Encoding {toEncoder}]
toEncoder = \@A _ -> custom \b, _ -> b
main = toEncoder { a: @A {} }
# ^^^^^^^^^
"#
),
@"Encoding#toEncoder(2) : { a : A } -[[#Derived.toEncoder_{a}(0)]]-> Encoder fmt | fmt has EncoderFormatting"
)
}
#[test]
fn resolve_lambda_set_generalized_ability_alias() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Id has id : a -> a | a has Id
A := {} has [Id {id}]
id = \@A {} -> @A {}
#^^{-1}
main =
alias1 = id
# ^^
alias2 = alias1
# ^^^^^^
a : A
a = alias2 (@A {})
# ^^^^^^
a
"#
),
@r###"
A#id(4) : A -[[id(4)]]-> A
Id#id(2) : a -[[] + a:id(2):1]-> a | a has Id
alias1 : a -[[] + a:id(2):1]-> a | a has Id
alias2 : A -[[id(4)]]-> A
"###
)
}
#[test]
fn resolve_lambda_set_ability_chain() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Id1 has id1 : a -> a | a has Id1
Id2 has id2 : a -> a | a has Id2
A := {} has [Id1 {id1}, Id2 {id2}]
id1 = \@A {} -> @A {}
#^^^{-1}
id2 = \@A {} -> id1 (@A {})
#^^^{-1} ^^^
main =
a : A
a = id2 (@A {})
# ^^^
a
"#
),
@r###"
A#id1(6) : A -[[id1(6)]]-> A
A#id2(7) : A -[[id2(7)]]-> A
A#id1(6) : A -[[id1(6)]]-> A
A#id2(7) : A -[[id2(7)]]-> A
"###
)
}
#[test]
fn resolve_lambda_set_branches_ability_vs_non_ability() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Id has id : a -> a | a has Id
A := {} has [Id {id}]
id = \@A {} -> @A {}
#^^{-1}
idNotAbility = \x -> x
#^^^^^^^^^^^^{-1}
main =
choice : [T, U]
idChoice =
#^^^^^^^^{-1}
when choice is
T -> id
U -> idNotAbility
idChoice (@A {})
#^^^^^^^^{-1}
"#
),
@r###"
A#id(4) : A -[[id(4)]]-> A
idNotAbility : a -[[idNotAbility(5)]]-> a
idChoice : a -[[idNotAbility(5)] + a:id(2):1]-> a | a has Id
idChoice : A -[[id(4), idNotAbility(5)]]-> A
"###
)
}
#[test]
fn resolve_lambda_set_branches_same_ability() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Id has id : a -> a | a has Id
A := {} has [Id {id}]
id = \@A {} -> @A {}
#^^{-1}
main =
choice : [T, U]
idChoice =
#^^^^^^^^{-1}
when choice is
T -> id
U -> id
idChoice (@A {})
#^^^^^^^^{-1}
"#
),
@r#"
A#id(4) : A -[[id(4)]]-> A
idChoice : a -[[] + a:id(2):1]-> a | a has Id
idChoice : A -[[id(4)]]-> A
"#
)
}
#[test]
fn resolve_unspecialized_lambda_set_behind_alias() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Thunk a : {} -> a
Id has id : a -> Thunk a | a has Id
A := {} has [Id {id}]
id = \@A {} -> \{} -> @A {}
#^^{-1}
main =
alias = id
# ^^
a : A
a = (alias (@A {})) {}
# ^^^^^
a
"#
),
@r#"
A#id(5) : {} -[[id(5)]]-> ({} -[[8]]-> {})
Id#id(3) : a -[[] + a:id(3):1]-> ({} -[[] + a:id(3):2]-> a) | a has Id
alias : {} -[[id(5)]]-> ({} -[[8]]-> {})
"#
print_only_under_alias: true
)
}
#[test]
fn resolve_unspecialized_lambda_set_behind_opaque() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Thunk a := {} -> a
Id has id : a -> Thunk a | a has Id
A := {} has [Id {id}]
id = \@A {} -> @Thunk (\{} -> @A {})
#^^{-1}
main =
thunk = id (@A {})
@Thunk it = thunk
it {}
#^^{-1}
"#
),
@r#"
A#id(5) : {} -[[id(5)]]-> ({} -[[8]]-> {})
it : {} -[[8]]-> {}
"#
print_only_under_alias: true
)
}
#[test]
fn resolve_two_unspecialized_lambda_sets_in_one_lambda_set() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Thunk a : {} -> a
Id has id : a -> Thunk a | a has Id
A := {} has [Id {id}]
id = \@A {} -> \{} -> @A {}
#^^{-1}
main =
a : A
a = (id (@A {})) {}
# ^^
a
"#
),
@r#"
A#id(5) : {} -[[id(5)]]-> ({} -[[8]]-> {})
A#id(5) : {} -[[id(5)]]-> ({} -[[8]]-> {})
"#
print_only_under_alias: true
)
}
#[test]
fn resolve_recursive_ability_lambda_set() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Diverge has diverge : a -> a | a has Diverge
A := {} has [Diverge {diverge}]
diverge : A -> A
diverge = \@A {} -> diverge (@A {})
#^^^^^^^{-1} ^^^^^^^
main =
a : A
a = diverge (@A {})
# ^^^^^^^
a
"#
),
@r###"
A#diverge(4) : A -[[diverge(4)]]-> A
A#diverge(4) : A -[[diverge(4)]]-> A
A#diverge(4) : A -[[diverge(4)]]-> A
"###
)
}
#[test]
fn resolve_mutually_recursive_ability_lambda_sets() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Bounce has
ping : a -> a | a has Bounce
pong : a -> a | a has Bounce
A := {} has [Bounce {ping: pingA, pong: pongA}]
pingA = \@A {} -> pong (@A {})
#^^^^^{-1} ^^^^
pongA = \@A {} -> ping (@A {})
#^^^^^{-1} ^^^^
main =
a : A
a = ping (@A {})
# ^^^^
a
"#
),
@r###"
pingA : A -[[pingA(5)]]-> A
A#pong(6) : A -[[pongA(6)]]-> A
pongA : A -[[pongA(6)]]-> A
A#ping(5) : A -[[pingA(5)]]-> A
A#ping(5) : A -[[pingA(5)]]-> A
"###
)
}
#[test]
fn resolve_mutually_recursive_ability_lambda_sets_inferred() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Bounce has
ping : a -> a | a has Bounce
pong : a -> a | a has Bounce
A := {} has [Bounce {ping, pong}]
ping = \@A {} -> pong (@A {})
#^^^^{-1} ^^^^
pong = \@A {} -> ping (@A {})
#^^^^{-1} ^^^^
main =
a : A
a = ping (@A {})
# ^^^^
a
"#
),
@r###"
A#ping(5) : A -[[ping(5)]]-> A
A#pong(6) : A -[[pong(6)]]-> A
A#pong(6) : A -[[pong(6)]]-> A
A#ping(5) : A -[[ping(5)]]-> A
A#ping(5) : A -[[ping(5)]]-> A
"###
)
}
#[test]
fn list_of_lambdas() {
infer_queries!(
indoc!(
r#"
[\{} -> {}, \{} -> {}]
#^^^^^^^^^^^^^^^^^^^^^^{-1}
"#
),
@r###"[\{} -> {}, \{} -> {}] : List ({}* -[[1, 2]]-> {})"###
)
}
#[test]
fn self_recursion_with_inference_var() {
infer_eq_without_problem(
indoc!(
r#"
f : _ -> _
f = \_ -> if Bool.false then "" else f ""
f
"#
),
"Str -> Str",
)
}
#[test]
fn mutual_recursion_with_inference_var() {
infer_eq_without_problem(
indoc!(
r#"
f : _ -> Str
f = \s -> g s
g = \s -> if Bool.true then s else f s
g
"#
),
"Str -> Str",
)
}
#[test]
fn issue_3261() {
infer_queries!(
indoc!(
r#"
Named : [Named Str (List Named)]
foo : Named
foo = Named "outer" [Named "inner" []]
#^^^{-1}
Named name outerList = foo
#^^^^^^^^^^^^^^^^^^^^{-1}
# ^^^^ ^^^^^^^^^
{name, outerList}
"#
),
@r#"
foo : [Named Str (List a)]* as a
Named name outerList : [Named Str (List a)] as a
name : Str
outerList : List ([Named Str (List a)] as a)
"#
print_only_under_alias: true
)
}
#[test]
fn function_alias_in_signature() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
Parser a : List U8 -> List [Pair a (List U8)]
any: Parser U8
any = \inp ->
when List.first inp is
Ok u -> [Pair u (List.drop inp 1)]
_ -> []
main = any
"#
),
"Parser U8",
);
}
#[test]
fn infer_variables_in_value_def_signature() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [a] to "./platform"
a : {a: _}
a = {a: ""}
"#
),
"{ a : Str }",
);
}
#[test]
fn infer_variables_in_destructure_def_signature() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [a] to "./platform"
{a} : {a: _}
{a} = {a: ""}
"#
),
"Str",
)
}
#[test]
fn lambda_sets_collide_with_captured_var() {
infer_queries!(
indoc!(
r#"
capture : a -> ({} -> Str)
capture = \val ->
thunk =
\{} ->
when val is
_ -> ""
thunk
x : [True, False]
fun =
when x is
True -> capture ""
# ^^^^^^^
False -> capture {}
# ^^^^^^^
fun
#^^^{-1}
"#
),
@r#"
capture : Str -[[capture(1)]]-> ({} -[[thunk(5) {}, thunk(5) Str]]-> Str)
capture : {} -[[capture(1)]]-> ({} -[[thunk(5) {}, thunk(5) Str]]-> Str)
fun : {} -[[thunk(5) {}, thunk(5) Str]]-> Str
"#
);
}
#[test]
fn lambda_sets_collide_with_captured_function() {
infer_queries!(
indoc!(
r#"
Lazy a : {} -> a
after : Lazy a, (a -> Lazy b) -> Lazy b
after = \effect, map ->
thunk = \{} ->
when map (effect {}) is
b -> b {}
thunk
f = \_ -> \_ -> ""
g = \{ s1 } -> \_ -> s1
x : [True, False]
fun =
when x is
True -> after (\{} -> "") f
False -> after (\{} -> {s1: "s1"}) g
fun
#^^^{-1}
"#
),
@r#"fun : {} -[[thunk(9) (({} -[[15]]-> { s1 : Str })) ({ s1 : Str } -[[g(4)]]-> ({} -[[13 Str]]-> Str)), thunk(9) (({} -[[14]]-> Str)) (Str -[[f(3)]]-> ({} -[[11]]-> Str))]]-> Str"#
print_only_under_alias: true
);
}
#[test]
fn lambda_set_niche_same_layout_different_constructor() {
infer_queries!(
indoc!(
r#"
capture : a -> ({} -> Str)
capture = \val ->
thunk =
\{} ->
when val is
_ -> ""
thunk
x : [True, False]
fun =
when x is
True -> capture {a: ""}
False -> capture (A "")
fun
#^^^{-1}
"#
),
@r#"fun : {} -[[thunk(5) [A Str]*, thunk(5) { a : Str }]]-> Str"#
);
}
#[test]
fn check_phantom_type() {
infer_eq_without_problem(
indoc!(
r#"
F a b := b
foo : F Str Str -> F U8 Str
x : F Str Str
foo x
"#
),
"F U8 Str",
)
}
#[test]
fn infer_phantom_type_flow() {
infer_eq_without_problem(
indoc!(
r#"
F a b := b
foo : _ -> F U8 Str
foo = \it -> it
foo
"#
),
"F U8 Str -> F U8 Str",
)
}
#[test]
fn infer_unbound_phantom_type_star() {
infer_eq_without_problem(
indoc!(
r#"
F a b := b
foo = \@F {} -> @F ""
foo
"#
),
"F * {}* -> F * Str",
)
}
#[test]
fn polymorphic_lambda_set_specialization() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {} has [F {f}]
f = \@Fo {} -> g
#^{-1}
Go := {} has [G {g}]
g = \@Go {} -> {}
#^{-1}
main = (f (@Fo {})) (@Go {})
# ^
# ^^^^^^^^^^
"#
),
@r###"
Fo#f(7) : Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
Go#g(8) : Go -[[g(8)]]-> {}
Fo#f(7) : Fo -[[f(7)]]-> (Go -[[g(8)]]-> {})
f (@Fo {}) : Go -[[g(8)]]-> {}
"###
);
}
#[test]
fn polymorphic_lambda_set_specialization_bound_output() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a -> ({} -> b) | a has F, b has G
G has g : {} -> b | b has G
Fo := {} has [F {f}]
f = \@Fo {} -> g
#^{-1}
Go := {} has [G {g}]
g = \{} -> @Go {}
#^{-1}
main =
foo = 1
@Go it = (f (@Fo {})) {}
# ^
# ^^^^^^^^^^
{foo, it}
"#
),
@r###"
Fo#f(7) : Fo -[[f(7)]]-> ({} -[[] + b:g(4):1]-> b) | b has G
Go#g(8) : {} -[[g(8)]]-> Go
Fo#f(7) : Fo -[[f(7)]]-> ({} -[[g(8)]]-> Go)
f (@Fo {}) : {} -[[g(8)]]-> Go
"###
);
}
#[test]
fn polymorphic_lambda_set_specialization_with_let_generalization() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {} has [F {f}]
f = \@Fo {} -> g
#^{-1}
Go := {} has [G {g}]
g = \@Go {} -> {}
#^{-1}
main =
h = f (@Fo {})
# ^ ^
h (@Go {})
# ^
"#
),
@r###"
Fo#f(7) : Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
Go#g(8) : Go -[[g(8)]]-> {}
h : b -[[] + b:g(4):1]-> {} | b has G
Fo#f(7) : Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
h : Go -[[g(8)]]-> {}
"###
);
}
#[test]
fn polymorphic_lambda_set_specialization_with_let_generalization_unapplied() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {} has [F {f}]
f = \@Fo {} -> g
#^{-1}
Go := {} has [G {g}]
g = \@Go {} -> {}
#^{-1}
main =
#^^^^{-1}
h = f (@Fo {})
# ^ ^
h
"#
),
@r###"
Fo#f(7) : Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
Go#g(8) : Go -[[g(8)]]-> {}
main : b -[[] + b:g(4):1]-> {} | b has G
h : b -[[] + b:g(4):1]-> {} | b has G
Fo#f(7) : Fo -[[f(7)]]-> (b -[[] + b:g(4):1]-> {}) | b has G
"###
);
}
#[test]
fn polymorphic_lambda_set_specialization_with_deep_specialization_and_capture() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a, b -> ({} -> ({} -> {})) | a has F, b has G
G has g : b -> ({} -> {}) | b has G
Fo := {} has [F {f}]
f = \@Fo {}, b -> \{} -> g b
#^{-1}
Go := {} has [G {g}]
g = \@Go {} -> \{} -> {}
#^{-1}
main =
(f (@Fo {}) (@Go {})) {}
# ^
"#
),
@r###"
Fo#f(7) : Fo, b -[[f(7)]]-> ({} -[[13 b]]-> ({} -[[] + b:g(4):2]-> {})) | b has G
Go#g(8) : Go -[[g(8)]]-> ({} -[[14]]-> {})
Fo#f(7) : Fo, Go -[[f(7)]]-> ({} -[[13 Go]]-> ({} -[[14]]-> {}))
"###
);
}
#[test]
fn polymorphic_lambda_set_specialization_varying_over_multiple_variables() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
J has j : j -> (k -> {}) | j has J, k has K
K has k : k -> {} | k has K
C := {} has [J {j: jC}]
jC = \@C _ -> k
#^^{-1}
D := {} has [J {j: jD}]
jD = \@D _ -> k
#^^{-1}
E := {} has [K {k}]
k = \@E _ -> {}
#^{-1}
f = \flag, a, b ->
# ^ ^
it =
# ^^
when flag is
A -> j a
# ^
B -> j b
# ^
it
# ^^
main = (f A (@C {}) (@D {})) (@E {})
# ^
# ^^^^^^^^^^^^^^^^^^^
#^^^^{-1}
"#
),
@r###"
jC : C -[[jC(8)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
jD : D -[[jD(9)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
E#k(10) : E -[[k(10)]]-> {}
a : j | j has J
b : j | j has J
it : k -[[] + j:j(2):2 + j1:j(2):2]-> {} | j has J, j1 has J, k has K
J#j(2) : j -[[] + j:j(2):1]-> (k -[[] + j:j(2):2 + j1:j(2):2]-> {}) | j has J, j1 has J, k has K
J#j(2) : j -[[] + j:j(2):1]-> (k -[[] + j1:j(2):2 + j:j(2):2]-> {}) | j has J, j1 has J, k has K
it : k -[[] + j:j(2):2 + j1:j(2):2]-> {} | j has J, j1 has J, k has K
f : [A, B], C, D -[[f(11)]]-> (E -[[k(10)]]-> {})
f A (@C {}) (@D {}) : E -[[k(10)]]-> {}
main : {}
"###
);
}
#[test]
fn polymorphic_lambda_set_specialization_varying_over_multiple_variables_two_results() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
J has j : j -> (k -> {}) | j has J, k has K
K has k : k -> {} | k has K
C := {} has [J {j: jC}]
jC = \@C _ -> k
#^^{-1}
D := {} has [J {j: jD}]
jD = \@D _ -> k
#^^{-1}
E := {} has [K {k: kE}]
kE = \@E _ -> {}
#^^{-1}
F := {} has [K {k: kF}]
kF = \@F _ -> {}
#^^{-1}
f = \flag, a, b ->
# ^ ^
it =
# ^^
when flag is
A -> j a
# ^
B -> j b
# ^
it
# ^^
main =
#^^^^{-1}
it =
# ^^
(f A (@C {}) (@D {}))
# ^
if Bool.true
then it (@E {})
# ^^
else it (@F {})
# ^^
"#
),
@r###"
jC : C -[[jC(9)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
jD : D -[[jD(10)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
kE : E -[[kE(11)]]-> {}
kF : F -[[kF(12)]]-> {}
a : j | j has J
b : j | j has J
it : k -[[] + j:j(2):2 + j1:j(2):2]-> {} | j has J, j1 has J, k has K
J#j(2) : j -[[] + j:j(2):1]-> (k -[[] + j:j(2):2 + j1:j(2):2]-> {}) | j has J, j1 has J, k has K
J#j(2) : j -[[] + j:j(2):1]-> (k -[[] + j1:j(2):2 + j:j(2):2]-> {}) | j has J, j1 has J, k has K
it : k -[[] + j:j(2):2 + j1:j(2):2]-> {} | j has J, j1 has J, k has K
main : {}
it : k -[[] + k:k(4):1]-> {} | k has K
f : [A, B], C, D -[[f(13)]]-> (k -[[] + k:k(4):1]-> {}) | k has K
it : E -[[kE(11)]]-> {}
it : F -[[kF(12)]]-> {}
"###
);
}
#[test]
fn polymorphic_lambda_set_specialization_branching_over_single_variable() {
infer_queries!(
indoc!(
r#"
app "test" provides [f] to "./platform"
J has j : j -> (k -> {}) | j has J, k has K
K has k : k -> {} | k has K
C := {} has [J {j: jC}]
jC = \@C _ -> k
D := {} has [J {j: jD}]
jD = \@D _ -> k
E := {} has [K {k}]
k = \@E _ -> {}
f = \flag, a, c ->
it =
when flag is
A -> j a
B -> j a
it c
# ^^ ^
"#
),
@r###"
it : k -[[] + j:j(2):2]-> {} | j has J, k has K
c : k | k has K
"###
);
}
#[test]
fn wrap_recursive_opaque_negative_position() {
infer_eq_without_problem(
indoc!(
r#"
OList := [Nil, Cons {} OList]
lst : [Cons {} OList]
olist : OList
olist = (\l -> @OList l) lst
olist
"#
),
"OList",
);
}
#[test]
fn wrap_recursive_opaque_positive_position() {
infer_eq_without_problem(
indoc!(
r#"
OList := [Nil, Cons {} OList]
lst : [Cons {} OList]
olist : OList
olist = @OList lst
olist
"#
),
"OList",
);
}
#[test]
fn rosetree_with_result_is_legal_recursive_type() {
infer_eq_without_problem(
indoc!(
r#"
Rose a : [Rose (Result (List (Rose a)) I64)]
x : Rose I64
x = Rose (Ok [])
x
"#
),
"Rose I64",
);
}
#[test]
fn opaque_wrap_function() {
infer_eq_without_problem(
indoc!(
r#"
A := U8
List.map [1, 2, 3] @A
"#
),
"List A",
);
}
#[test]
fn opaque_wrap_function_with_inferred_arg() {
infer_eq_without_problem(
indoc!(
r#"
A a := a
List.map [1u8, 2u8, 3u8] @A
"#
),
"List (A U8)",
);
}
#[test]
fn shared_pattern_variable_in_when_patterns() {
infer_queries!(
indoc!(
r#"
when A "" is
# ^^^^
A x | B x -> x
# ^ ^ ^
"#
),
@r###"
A "" : [A Str, B Str]
x : Str
x : Str
x : Str
"###
);
}
#[test]
fn shared_pattern_variable_in_multiple_branch_when_patterns() {
infer_queries!(
indoc!(
r#"
when A "" is
# ^^^^
A x | B x -> x
# ^ ^ ^
C x | D x -> x
# ^ ^ ^
"#
),
@r###"
A "" : [A Str, B Str, C Str, D Str]
x : Str
x : Str
x : Str
x : Str
x : Str
x : Str
"###
);
}
#[test]
fn catchall_branch_for_pattern_not_last() {
infer_queries!(
indoc!(
r#"
\x -> when x is
#^
A B _ -> ""
A _ C -> ""
"#
),
@r#"x : [A [B]* [C]*]"#
allow_errors: true
);
}
#[test]
fn catchall_branch_walk_into_nested_types() {
infer_queries!(
indoc!(
r#"
\x -> when x is
#^
{ a: A { b: B } } -> ""
_ -> ""
"#
),
@r#"x : { a : [A { b : [B]* }*]* }*"#
);
}
#[test]
fn infer_type_with_underscore_destructure_assignment() {
infer_eq_without_problem(
indoc!(
r#"
Pair x _ = Pair 0 1
x
"#
),
"Num *",
);
}
#[test]
fn issue_3444() {
infer_queries!(
indoc!(
r#"
compose = \f, g ->
closCompose = \x -> g (f x)
closCompose
const = \x ->
closConst = \_ -> x
closConst
list = []
res : Str -> Str
res = List.walk list (const "z") (\c1, c2 -> compose c1 c2)
# ^^^^^ ^^^^^^^
# ^^^^^^^^^^^^^^^^^^^^^^^^
#^^^{-1}
res "hello"
#^^^{-1}
"#
),
@r###"
const : Str -[[const(2)]]-> (Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str)
compose : (Str -a-> Str), (Str -[[]]-> Str) -[[compose(1)]]-> (Str -a-> Str)
\c1, c2 -> compose c1 c2 : (Str -a-> Str), (Str -[[]]-> Str) -[[11]]-> (Str -a-> Str)
res : Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str
res : Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str
"###
);
}
#[test]
fn transient_captures() {
infer_queries!(
indoc!(
r#"
x = "abc"
getX = \{} -> x
h = \{} -> (getX {})
#^{-1}
h {}
"#
),
@"h : {}* -[[h(3) Str]]-> Str"
);
}
#[test]
fn transient_captures_after_def_ordering() {
infer_queries!(
indoc!(
r#"
h = \{} -> (getX {})
#^{-1}
getX = \{} -> x
x = "abc"
h {}
"#
),
@"h : {}* -[[h(1) Str]]-> Str"
);
}
#[test]
fn mutually_recursive_captures() {
infer_queries!(
indoc!(
r#"
x = Bool.true
y = Bool.false
a = "foo"
b = "bar"
foo = \{} -> if x then a else bar {}
#^^^{-1}
bar = \{} -> if y then b else foo {}
#^^^{-1}
bar {}
"#
),
@r###"
foo : {} -[[foo(5) Bool Bool Str Str]]-> Str
bar : {} -[[bar(6) Bool Bool Str Str]]-> Str
"###
);
}
#[test]
fn unify_optional_record_fields_in_two_closed_records() {
infer_eq_without_problem(
indoc!(
r#"
f : { x ? Str, y ? Str } -> {}
f {x : ""}
"#
),
"{}",
);
}
#[test]
fn match_on_result_with_uninhabited_error_branch() {
infer_eq_without_problem(
indoc!(
r#"
x : Result Str []
x = Ok "abc"
when x is
Ok s -> s
"#
),
"Str",
);
}
#[test]
fn match_on_result_with_uninhabited_error_destructuring() {
infer_eq_without_problem(
indoc!(
r#"
x : Result Str []
x = Ok "abc"
Ok str = x
str
"#
),
"Str",
);
}
#[test]
fn match_on_result_with_uninhabited_error_destructuring_in_lambda_syntax() {
infer_eq_without_problem(
indoc!(
r#"
x : Result Str [] -> Str
x = \Ok s -> s
x
"#
),
"Result Str [] -> Str",
);
}
#[test]
fn custom_implement_hash() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
Noop := {} has [Hash {hash}]
hash = \hasher, @Noop {} -> hasher
main = \hasher -> hash hasher (@Noop {})
"#
),
"hasher -> hasher | hasher has Hasher",
);
}
#[test]
fn dispatch_tag_union_function_inferred() {
infer_eq_without_problem(
indoc!(
r#"
g = if Bool.true then A else B
g ""
"#
),
"[A Str, B Str]",
);
}
#[test]
fn check_char_as_u8() {
infer_eq_without_problem(
indoc!(
r#"
x : U8
x = '.'
x
"#
),
"U8",
);
}
#[test]
fn check_char_as_u16() {
infer_eq_without_problem(
indoc!(
r#"
x : U16
x = '.'
x
"#
),
"U16",
);
}
#[test]
fn check_char_as_u32() {
infer_eq_without_problem(
indoc!(
r#"
x : U32
x = '.'
x
"#
),
"U32",
);
}
#[test]
fn check_char_pattern_as_u8() {
infer_eq_without_problem(
indoc!(
r#"
f : U8 -> _
f = \c ->
when c is
'.' -> 'A'
c1 -> c1
f
"#
),
"U8 -> U8",
);
}
#[test]
fn check_char_pattern_as_u16() {
infer_eq_without_problem(
indoc!(
r#"
f : U16 -> _
f = \c ->
when c is
'.' -> 'A'
c1 -> c1
f
"#
),
"U16 -> U16",
);
}
#[test]
fn check_char_pattern_as_u32() {
infer_eq_without_problem(
indoc!(
r#"
f : U32 -> _
f = \c ->
when c is
'.' -> 'A'
c1 -> c1
f
"#
),
"U32 -> U32",
);
}
#[test]
fn issue_4246_admit_recursion_between_opaque_functions() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [b] to "./platform"
O := {} -> {}
a = @O \{} -> ((\@O f -> f {}) b)
b = a
"#
),
"O",
);
}
#[test]
fn custom_implement_eq() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
Trivial := {} has [Eq {isEq}]
isEq = \@Trivial {}, @Trivial {} -> Bool.true
main = Bool.isEq (@Trivial {}) (@Trivial {})
"#
),
"Bool",
);
}
#[test]
fn expand_able_variables_in_type_alias() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F a : a | a has Hash
main : F a -> F a
#^^^^{-1}
"#
),
@"main : a -[[main(0)]]-> a | a has Hash"
print_only_under_alias: true
);
}
#[test]
fn self_recursive_function_not_syntactically_a_function() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [fx] to "./platform"
after : ({} -> a), ({} -> b) -> ({} -> b)
fx = after (\{} -> {}) \{} -> if Bool.true then fx {} else {}
"#
),
"{} -> {}",
);
}
#[test]
fn self_recursive_function_not_syntactically_a_function_nested() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
after : ({} -> a), ({} -> b) -> ({} -> b)
fx = after (\{} -> {}) \{} -> if Bool.true then fx {} else {}
fx
"#
),
"{} -> {}",
);
}
#[test]
fn derive_to_encoder_for_opaque() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
N := U8 has [Encoding]
main = Encode.toEncoder (@N 15)
# ^^^^^^^^^^^^^^^^
"#
),
@"N#Encode.toEncoder(3) : N -[[#N_toEncoder(3)]]-> Encoder fmt | fmt has EncoderFormatting"
);
}
#[test]
fn derive_decoder_for_opaque() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
N := U8 has [Decoding]
main : Decoder N _
main = Decode.custom \bytes, fmt ->
Decode.decodeWith bytes Decode.decoder fmt
# ^^^^^^^^^^^^^^
"#
),
@"N#Decode.decoder(3) : List U8, fmt -[[7]]-> { rest : List U8, result : [Err [TooShort], Ok U8] } | fmt has DecoderFormatting"
print_only_under_alias: true
);
}
#[test]
fn derive_hash_for_opaque() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
N := U8 has [Hash]
main = \hasher, @N n -> Hash.hash hasher (@N n)
# ^^^^^^^^^
"#
),
@"N#Hash.hash(3) : a, N -[[#N_hash(3)]]-> a | a has Hasher"
);
}
#[test]
fn derive_eq_for_opaque() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
N := U8 has [Eq]
main = Bool.isEq (@N 15) (@N 23)
# ^^^^^^^^^
"#
),
@"N#Bool.isEq(3) : N, N -[[#N_isEq(3)]]-> Bool"
);
}
#[test]
fn multiple_variables_bound_to_an_ability_from_type_def() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F a : a | a has Hash & Eq & Decoding
main : F a -> F a
#^^^^{-1}
"#
),
@"main : a -[[main(0)]]-> a | a has Hash & Decoding & Eq"
print_only_under_alias: true
);
}
#[test]
fn rigid_able_bounds_are_superset_of_flex_bounds_admitted() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
f : x -> x | x has Hash
g : x -> x | x has Decoding & Encoding
main : x -> x | x has Hash & Decoding & Encoding
main = \x -> x |> f |> g
"#
),
"x -> x | x has Hash & Encoding & Decoding",
);
}
#[test]
fn extend_uninhabited_without_opening_union() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
walkHelp : {} -> [Continue {}, Break []]
main = when walkHelp {} is
# ^^^^^^^^^^^
Continue {} -> {}
"#
),
@"walkHelp {} : [Break [], Continue {}]"
);
}
#[test]
fn contextual_openness_for_type_alias() {
infer_queries!(
indoc!(
r#"
app "test" provides [accum] to "./platform"
Q : [Green, Blue]
f : Q -> Q
f = \q -> when q is
#^{-1}
Green -> Green
Blue -> Blue
accum = \q -> when q is
#^^^^^{-1}
A -> f Green
B -> Yellow
C -> Orange
"#
),
@r###"
f : Q -[[f(2)]]-> Q
accum : [A, B, C] -[[accum(0)]]-> [Blue, Green, Orange, Yellow]*
"###
);
}
#[test]
fn inferred_fixed_fixpoints() {
infer_queries!(
indoc!(
r#"
app "test" provides [job] to "./platform"
F : [Bar, FromG G]
G : [G {lst : List F}]
job : { lst : List F } -> G
job = \config -> G config
#^^^{-1}
# ^^^^^^ ^^^^^^^^
"#
),
@r###"
job : { lst : List [Bar, FromG a] } -[[job(0)]]-> [G { lst : List [Bar, FromG a] }] as a
config : { lst : List [Bar, FromG ([G { lst : List [Bar, FromG a] }] as a)] }
G config : [G { lst : List [Bar, FromG a] }] as a
"###
print_only_under_alias: true
);
}
#[test]
fn fix_recursion_under_alias_issue_4368() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [doIt] to "./platform"
Effect : [
DoIt {} ({} -> Effect),
]
Task := ({} -> Effect) -> Effect
doIt : {} -> Task
doIt = \{} ->
@Task \toNext ->
DoIt {} \{} -> (toNext {})
"#
),
"{} -> Task",
);
}
#[test]
fn choose_ranged_num_for_hash() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
\h -> Hash.hash h 7
# ^^^^^^^^^
"#
),
@"Hash#Hash.hash(1) : a, I64 -[[Hash.hashI64(12)]]-> a | a has Hasher"
)
}
#[test]
fn generalize_inferred_opaque_variable_bound_to_ability_issue_4408() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [top] to "./platform"
MDict u := (List u) | u has Hash & Eq
bot : MDict k -> MDict k
bot = \@MDict data ->
when {} is
{} -> @MDict data
top : MDict v -> MDict v
top = \x -> bot x
"#
),
"MDict v -> MDict v | v has Hash & Eq",
);
}
#[test]
fn unify_types_with_fixed_fixpoints_outside_fixing_region() {
infer_queries!(indoc!(
r#"
app "test" provides [main] to "./platform"
Input := [
FromJob Job
]
Job := [
Job (List Input)
]
job : List Input -> Job
job = \inputs ->
@Job (Job inputs)
helloWorld : Job
helloWorld =
@Job ( Job [ @Input (FromJob greeting) ] )
# ^^^^^^^^^^^^^^^^^^^^^^^^^
greeting : Job
greeting =
job []
main = (\_ -> "Which platform am I running on now?\n") helloWorld
"#
),
@r###"
@Input (FromJob greeting) : [FromJob ([Job (List [FromJob a])] as a)]
"###
print_only_under_alias: true
)
}
#[test]
fn impl_ability_for_opaque_with_lambda_sets() {
infer_queries!(
indoc!(
r#"
app "test" provides [isEqQ] to "./platform"
Q := [ F (Str -> Str), G ] has [Eq { isEq: isEqQ }]
isEqQ = \@Q q1, @Q q2 -> when T q1 q2 is
#^^^^^{-1}
T (F _) (F _) -> Bool.true
T G G -> Bool.true
_ -> Bool.false
"#
),
@"isEqQ : Q, Q -[[isEqQ(0)]]-> Bool"
);
}
#[test]
fn impl_ability_for_opaque_with_lambda_sets_material() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Q := ({} -> Str) has [Eq {isEq: isEqQ}]
isEqQ = \@Q f1, @Q f2 -> (f1 {} == f2 {})
#^^^^^{-1}
main = isEqQ (@Q \{} -> "a") (@Q \{} -> "a")
# ^^^^^
"#
),
@r###"
isEqQ : ({} -[[]]-> Str), ({} -[[]]-> Str) -[[isEqQ(2)]]-> [False, True]
isEqQ : ({} -[[6, 7]]-> Str), ({} -[[6, 7]]-> Str) -[[isEqQ(2)]]-> [False, True]
"###
print_only_under_alias: true
);
}
#[test]
fn infer_concrete_type_with_inference_var() {
infer_queries!(indoc!(
r#"
app "test" provides [f] to "./platform"
f : _ -> {}
f = \_ -> f {}
#^{-1}
"#
),
@r###"
f : {} -[[f(0)]]-> {}
"###
)
}
#[test]
fn solve_inference_var_in_annotation_requiring_recursion_fix() {
infer_queries!(indoc!(
r#"
app "test" provides [translateStatic] to "./platform"
translateStatic : _ -> _
translateStatic = \Element c ->
#^^^^^^^^^^^^^^^{-1}
Element (List.map c translateStatic)
"#
),
@"translateStatic : [Element (List a)] as a -[[translateStatic(0)]]-> [Element (List b)]* as b"
)
}
#[test]
fn infer_contextual_crash() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [getInfallible] to "./platform"
getInfallible = \result -> when result is
Ok x -> x
_ -> crash "turns out this was fallible"
"#
),
"[Ok a]* -> a",
);
}
#[test]
fn resolve_eq_for_float_forces_dec() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
n : Num *
main = n == 1.
# ^
"#
),
@"n : Dec"
);
}
#[test]
fn resolve_set_eq_issue_4671() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
s1 : Set U8
s1 = Set.empty
s2 : Set Str
s2 = Set.empty
Bool.isEq s1 s1 && Bool.isEq s2 s2
# ^^^^^^^^^ ^^^^^^^^^
"#
),
@r###"
Set#Bool.isEq(17) : Set U8, Set U8 -[[Set.isEq(17)]]-> Bool
Set#Bool.isEq(17) : Set Str, Set Str -[[Set.isEq(17)]]-> Bool
"###
);
}
#[test]
fn disjoint_nested_lambdas_result_in_disjoint_parents_issue_4712() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Parser a : {} -> a
v1 : {}
v1 = {}
v2 : Str
v2 = ""
apply : Parser (a -> Str), a -> Parser Str
apply = \fnParser, valParser ->
\{} ->
(fnParser {}) (valParser)
map : a, (a -> Str) -> Parser Str
map = \simpleParser, transform ->
apply (\{} -> transform) simpleParser
parseInput = \{} ->
when [ map v1 (\{} -> ""), map v2 (\s -> s) ] is
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ -> ""
main = parseInput {} == ""
"#
),
@r###"
v1 = {}
v2 = ""
apply = \fnParser, valParser-> \{} -[9]-> (fnParser {}) valParser
map = \simpleParser, transform-> apply \{} -[12]-> transform simpleParser
parseInput =
\{}->
when [
map v1 \{} -[13]-> "",
map v2 \s -[14]-> s,
] is
_ -> ""
main = Bool.isEq (parseInput {}) ""
[ map v1 (\{} -> ""), map v2 (\s -> s) ] : List (({} -[[9 (({} -[[12 (Str -[[14]]-> Str)]]-> (Str -[[14]]-> Str))) Str, 9 (({} -[[12 ({} -[[13]]-> Str)]]-> ({} -[[13]]-> Str))) {}]]-> Str))
"###
print_only_under_alias: true
print_can_decls: true
);
}
#[test]
fn constrain_dbg_flex_var() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
polyDbg = \x ->
#^^^^^^^{-1}
dbg x
x
main = polyDbg ""
"#
),
@"polyDbg : a -[[polyDbg(1)]]-> a"
);
}
#[test]
fn pattern_as_uses_inferred_type() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main = when A "foo" is
A _ as a -> a
# ^
b -> b
# ^
"#
),
@r###"
a : [A Str]*
b : [A Str]*
"###
);
}
#[test]
fn pattern_as_does_not_narrow() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
input : [A Str, B Str]
input = A "foo"
drop : a -> {}
drop = \_ -> {}
main = when input is
# ^^^^^
A _ as a -> drop a
# ^
B _ as b -> drop b
# ^
"#
),
@r###"
input : [A Str, B Str]
a : [A Str, B Str]
b : [A Str, B Str]
"###
);
}
#[test]
fn pattern_as_list() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
input : List Str
input = [ "foo", "bar" ]
main = when input is
# ^^^^^
[ _first, .. as rest ] -> 1 + List.len rest
# ^^^^
[] -> 0
"#
),
@r###"
input : List Str
rest : List Str
"###
);
}
#[test]
fn rank_no_overgeneralization() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
#^^^^{-1}
\x ->
y = \z -> x z
y
"#
),
@"main : (a -[[]]-> b) -[[main(0)]]-> (a -[[y(2) (a -[[]]-> b)]]-> b)"
);
}
#[test]
fn when_branch_variables_not_generalized() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main = \{} -> when Red is
#^^^^{-1}
x ->
y : [Red]_
y = x
z : [Red, Green]_
z = x
{y, z}
"#
),
@"main : {}* -[[main(0)]]-> { y : [Green, Red]a, z : [Green, Red]a }"
);
}
}