roc/compiler/reporting/tests/test_reporting.rs
2020-04-09 01:08:09 +02:00

1832 lines
44 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate roc_reporting;
mod helpers;
#[cfg(test)]
mod test_reporting {
use crate::helpers::test_home;
use roc_module::symbol::{Interns, ModuleId};
use roc_reporting::report::{
can_problem, em_text, plain_text, url, Report, ReportText, BLUE_CODE, BOLD_CODE, CYAN_CODE,
DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
WHITE_CODE, YELLOW_CODE,
};
use roc_reporting::type_error::type_problem;
use roc_types::pretty_print::name_all_type_vars;
use roc_types::subs::Subs;
use std::path::PathBuf;
// use roc_region::all;
use crate::helpers::{can_expr, infer_expr, CanExprOut};
use roc_reporting::report::ReportText::{Concat, Module, Region, Type, Value};
use roc_solve::solve;
use roc_types::subs::Content::{FlexVar, RigidVar, Structure};
use roc_types::subs::FlatType::EmptyRecord;
fn filename_from_string(str: &str) -> PathBuf {
let mut filename = PathBuf::new();
filename.push(str);
return filename;
}
// use roc_problem::can;
fn to_simple_report(text: ReportText) -> Report {
Report {
title: "SYNTAX PROBLEM".to_string(),
text: text,
filename: filename_from_string(r"\code\proj\Main.roc"),
}
}
fn infer_expr_help(
expr_src: &str,
) -> (
Vec<solve::TypeError>,
Vec<roc_problem::can::Problem>,
Subs,
ModuleId,
Interns,
) {
let CanExprOut {
output,
var_store,
var,
constraint,
home,
interns,
problems: can_problems,
..
} = can_expr(expr_src);
let mut subs = Subs::new(var_store.into());
for (var, name) in output.introduced_variables.name_by_var {
subs.rigid_var(var, name);
}
let mut unify_problems = Vec::new();
let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
name_all_type_vars(var, &mut subs);
(unify_problems, can_problems, subs, home, interns)
}
fn report_renders_as_from_src(src: &str, report: Report, expected_rendering: &str) {
let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src);
let mut buf: String = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
report
.text
.render_ci(&mut buf, &mut subs, home, &src_lines, &interns);
assert_eq!(buf, expected_rendering);
}
fn report_problem_as(src: &str, expected_rendering: &str) {
let (type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src);
let mut buf: String = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
match can_problems.first() {
None => {}
Some(problem) => {
let report = can_problem(
filename_from_string(r"\code\proj\Main.roc"),
problem.clone(),
);
report
.text
.render_ci(&mut buf, &mut subs, home, &src_lines, &interns)
}
}
for problem in type_problems {
let report = type_problem(
filename_from_string(r"\code\proj\Main.roc"),
problem.clone(),
);
report
.text
.render_ci(&mut buf, &mut subs, home, &src_lines, &interns)
}
assert_eq!(buf, expected_rendering);
}
fn human_readable(str: &str) -> String {
return str
.replace(RED_CODE, "<red>")
.replace(WHITE_CODE, "<white>")
.replace(BLUE_CODE, "<blue>")
.replace(YELLOW_CODE, "<yellow>")
.replace(GREEN_CODE, "<green>")
.replace(CYAN_CODE, "<cyan>")
.replace(MAGENTA_CODE, "<magenta>")
.replace(RESET_CODE, "<reset>")
.replace(BOLD_CODE, "<bold>")
.replace(UNDERLINE_CODE, "<underline>");
}
fn report_renders_in_color_from_src(src: &str, report: Report, expected_rendering: &str) {
let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src);
let mut buf: String = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
report.text.render_color_terminal(
&mut buf,
&mut subs,
home,
&src_lines,
&interns,
&DEFAULT_PALETTE,
);
assert_eq!(human_readable(&buf), expected_rendering);
}
fn report_renders_in_color(report: Report, expected_rendering: &str) {
report_renders_in_color_from_src(
indoc!(
r#"
x = 1
y = 2
x
"#
),
report,
expected_rendering,
)
}
fn report_renders_as(report: Report, expected_rendering: &str) {
report_renders_as_from_src(
indoc!(
r#"
x = 1
y = 2
x
"#
),
report,
expected_rendering,
)
}
#[test]
fn report_plain() {
report_renders_as(to_simple_report(plain_text("y")), "y");
}
#[test]
fn report_emphasized_text() {
report_renders_as(to_simple_report(em_text("y")), "*y*");
}
#[test]
fn report_url() {
report_renders_as(
to_simple_report(url("package.roc.org")),
"<package.roc.org>",
);
}
#[test]
fn report_symbol() {
let src: &str = indoc!(
r#"
x = 1
y = 2
x
"#
);
let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src);
let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
to_simple_report(Value(interns.symbol(test_home(), "x".into())))
.text
.render_ci(&mut buf, &mut subs, home, &src_lines, &interns);
assert_eq!(buf, "`x`");
}
#[test]
fn report_module() {
let src: &str = indoc!(
r#"
x = 1
y = 2
x
"#
);
let (_type_problems, _can_problems, mut subs, home, mut interns) = infer_expr_help(src);
let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
let module_id = interns.module_id(&"Main".into());
to_simple_report(Module(module_id))
.text
.render_ci(&mut buf, &mut subs, home, &src_lines, &interns);
assert_eq!(buf, "Main");
}
#[test]
fn report_wildcard() {
report_renders_as(to_simple_report(Type(FlexVar(None))), "*");
}
#[test]
fn report_flex_var() {
report_renders_as(to_simple_report(Type(FlexVar(Some("msg".into())))), "msg");
}
#[test]
fn report_rigid_var() {
report_renders_as(to_simple_report(Type(RigidVar("Str".into()))), "Str");
}
#[test]
fn report_empty_record() {
report_renders_as(to_simple_report(Type(Structure(EmptyRecord))), "{}");
}
#[test]
fn report_batch_of_plain_text() {
let mut report_texts = Vec::new();
report_texts.push(plain_text("Wait a second. "));
report_texts.push(plain_text("There is a problem here. -> "));
report_texts.push(em_text("y"));
report_renders_as(
to_simple_report(Concat(report_texts)),
"Wait a second. There is a problem here. -> *y*",
);
}
#[test]
fn report_unused_def() {
report_problem_as(
indoc!(
r#"
x = 1
y = 2
x
"#
),
indoc!(
r#"
`y` is not used anywhere in your code.
2 ┆ y = 2
┆ ^
If you didn't intend on using `y` then remove it so future readers of your code don't wonder why it is there."#
),
)
}
#[test]
fn report_shadowing() {
report_problem_as(
indoc!(
r#"
i = 1
s = \i ->
i + 1
s i
"#
),
indoc!(
r#"
`i` is first defined here:
1 ┆ i = 1
┆ ^
But then it's defined a second time here:
3 ┆ s = \i ->
┆ ^
Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#
),
)
}
#[test]
fn report_shadowing_in_annotation() {
report_problem_as(
indoc!(
r#"
Booly : [ Yes, No ]
Booly : [ Yes, No, Maybe ]
x =
No
x
"#
),
indoc!(
r#"
`Booly` is first defined here:
1 ┆ Booly : [ Yes, No ]
┆ ^^^^^^^^^^^^^^^^^^^
But then it's defined a second time here:
3 ┆ Booly : [ Yes, No, Maybe ]
┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^
Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#
),
)
}
// #[test]
// fn report_multi_line_shadowing_in_annotation() {
// report_problem_as(
// indoc!(
// r#"
// Booly :
// [
// Yes,
// No
// ]
//
// Booly :
// [
// Yes,
// No,
// Maybe
// ]
//
// x =
// No
//
// x
// "#
// ),
// indoc!(
// r#"
// Booly is first defined here:
//
// 1 ┆> Booly :
// 2 ┆> [
// 3 ┆> Yes,
// 4 ┆> No
// 5 ┆> ]
//
// But then it's defined a second time here:
//
// 7 ┆> Booly :
// 8 ┆> [
// 9 ┆> Yes,
// 10 ┆> No,
// 11 ┆> Maybe
// 12 ┆> ]
//
// Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#
// ),
// )
// }
// #[test]
// fn report_unsupported_top_level_def() {
// report_problem_as(
// indoc!(
// r#"
// x = 1
//
// 5 = 2 + 1
//
// x
// "#
// ),
// indoc!(r#" "#),
// )
// }
#[test]
fn report_precedence_problem_single_line() {
report_problem_as(
indoc!(
r#"x = 1
y =
if selectedId != thisId == adminsId then
4
else
5
{ x, y }
"#
),
indoc!(
r#"
Using != and == together requires parentheses, to clarify how they should be grouped.
3 ┆ if selectedId != thisId == adminsId then
┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"#
),
)
}
#[test]
fn report_precedence_problem_multiline() {
report_problem_as(
indoc!(
r#"
if
1
== 2
== 3
then
2
else
3
"#
),
indoc!(
r#"
Using more than one == like this requires parentheses, to clarify how things should be grouped.
2 ┆> 1
3 ┆> == 2
4 ┆> == 3
"#
),
)
}
// #[test]
// fn report_unused_argument() {
// report_problem_as(
// indoc!(r#"
// y = 9
//
// box = \class, htmlChildren ->
// div [ class ] []
//
// div = 4
//
// box "wizard" []
// "#),
// indoc!(
// r#"
// box doesn't use htmlChildren.
//
// 3 ┆ box = \class, htmlChildren ->
//
// If you don't need htmlChildren, then you can just remove it. However, if you really do need htmlChildren as an argument of box, prefix it with an underscore, like this: "_htmlChildren". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."#
// ),
// );
// }
// #[test]
// fn report_unused_import() {
// report_problem_as(
// indoc!(r#"
// interface Report
// exposes [
// plainText,
// emText
// ]
// imports [
// Symbol.{ Interns }
// ]
//
// plainText = \str -> PlainText str
//
// emText = \str -> EmText str
// "#),
// indoc!(
// r#"
// Nothing from Symbol is used in this module.
//
// 6 ┆ imports [
// 7 ┆ Symbol.{ Interns }
// ┆ ^^^^^^
// 8 ┆ ]
//
// Since Symbol isn't used, you don't need to import it."#
// ),
// );
// }
#[test]
fn report_plain_text_color() {
report_renders_in_color(to_simple_report(plain_text("y")), "<white>y<reset>");
}
#[test]
fn report_em_text_color() {
report_renders_in_color(to_simple_report(em_text("HELLO!")), "<bold>HELLO!<reset>");
}
#[test]
fn report_url_color() {
report_renders_in_color(
to_simple_report(url("www.roc.com/blog")),
"<underline>www.roc.com/blog<reset>",
);
}
#[test]
fn report_value_color() {
let src: &str = indoc!(
r#"
activityIndicatorLarge = div
view activityIndicatorLarge
"#
);
let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src);
let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
to_simple_report(Value(
interns.symbol(test_home(), "activityIndicatorLarge".into()),
))
.text
.render_color_terminal(
&mut buf,
&mut subs,
home,
&src_lines,
&interns,
&DEFAULT_PALETTE,
);
assert_eq!(human_readable(&buf), "<blue>activityIndicatorLarge<reset>");
}
#[test]
fn report_module_color() {
let src: &str = indoc!(
r#"
x = 1
y = 2
x
"#
);
let (_type_problems, _can_problems, mut subs, home, mut interns) = infer_expr_help(src);
let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
let module_id = interns.module_id(&"Util.Int".into());
to_simple_report(Module(module_id))
.text
.render_color_terminal(
&mut buf,
&mut subs,
home,
&src_lines,
&interns,
&DEFAULT_PALETTE,
);
assert_eq!(human_readable(&buf), "<green>Util.Int<reset>");
}
#[test]
fn report_wildcard_in_color() {
report_renders_in_color(to_simple_report(Type(FlexVar(None))), "<yellow>*<reset>");
}
#[test]
fn report_flex_var_in_color() {
report_renders_in_color(
to_simple_report(Type(FlexVar(Some("msg".into())))),
"<yellow>msg<reset>",
);
}
#[test]
fn report_rigid_var_in_color() {
report_renders_in_color(
to_simple_report(Type(RigidVar("Str".into()))),
"<yellow>Str<reset>",
);
}
#[test]
fn report_empty_record_in_color() {
report_renders_in_color(
to_simple_report(Type(Structure(EmptyRecord))),
"<green>{}<reset>",
);
}
#[test]
fn report_batch_in_color() {
let mut report_texts = Vec::new();
report_texts.push(Type(RigidVar("List".into())));
report_texts.push(plain_text(" "));
report_texts.push(Type(Structure(EmptyRecord)));
report_renders_in_color(
to_simple_report(Concat(report_texts)),
"<yellow>List<reset><white> <reset><green>{}<reset>",
);
}
#[test]
fn report_region_in_color() {
report_renders_in_color_from_src(
indoc!(
r#"
isDisabled = \user -> user.isAdmin
theAdmin
|> isDisabled
"#
),
to_simple_report(Region(roc_region::all::Region {
start_line: 0,
end_line: 3,
start_col: 0,
end_col: 0,
})),
indoc!(
r#"
<cyan>1<reset><magenta> ┆<reset><red>><reset> <white>isDisabled = \user -> user.isAdmin<reset>
<cyan>2<reset><magenta> ┆<reset><red>><reset>
<cyan>3<reset><magenta> ┆<reset><red>><reset> <white>theAdmin<reset>
<cyan>4<reset><magenta> ┆<reset><red>><reset> <white> |> isDisabled<reset>
"#
),
);
}
#[test]
fn report_region() {
report_renders_as_from_src(
indoc!(
r#"
x = 1
y = 2
f = \a -> a + 4
f x
"#
),
to_simple_report(Region(roc_region::all::Region {
start_line: 1,
end_line: 3,
start_col: 0,
end_col: 0,
})),
indoc!(
r#"
2 ┆> y = 2
3 ┆> f = \a -> a + 4
4 ┆>
"#
),
);
}
#[test]
fn report_region_line_number_length_edge_case() {
// the highest line number is 9, but it's rendered as 10.
// Make sure that we render the line number as 2-wide
report_renders_as_from_src(
indoc!(
r#"
x = 1
y = 2
f = \a -> a + 4
f x
"#
),
to_simple_report(Region(roc_region::all::Region {
start_line: 7,
end_line: 9,
start_col: 0,
end_col: 0,
})),
indoc!(
r#"
8 ┆>
9 ┆> f x
10 ┆>
"#
),
);
}
#[test]
fn report_region_different_line_number_lengths() {
report_renders_as_from_src(
indoc!(
r#"
x = 1
y = 2
f = \a -> a + 4
f x
"#
),
to_simple_report(Region(roc_region::all::Region {
start_line: 8,
end_line: 10,
start_col: 0,
end_col: 0,
})),
indoc!(
r#"
9 ┆>
10 ┆> y = 2
11 ┆> f = \a -> a + 4
"#
),
);
}
// #[test]
// fn shadowing_type_alias() {
// report_problem_as(
// indoc!(
// r#"
// foo : Int as Int
// foo = 42
//
// foo
// "#
// ),
// indoc!(
// r#"
// You cannot mix (!=) and (==) without parentheses
//
// 3 ┆ if selectedId != thisId == adminsId then
// ┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// "#
// ),
// )
// }
// #[test]
// fn invalid_as_type_alias() {
// report_problem_as(
// indoc!(
// r#"
// foo : Int as a
// foo = 42
//
// foo
// "#
// ),
// indoc!(
// r#"
// You cannot mix (!=) and (==) without parentheses
//
// 3 ┆ if selectedId != thisId == adminsId then
// ┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// "#
// ),
// )
// }
#[test]
fn if_condition_not_bool() {
report_problem_as(
indoc!(
r#"
if "foo" then 2 else 3
"#
),
indoc!(
r#"
This `if` condition needs to be a Bool:
1 ┆ if "foo" then 2 else 3
┆ ^^^^^
Right now its a string of type:
Str
But I need every `if` condition to evaluate to a Bool—either `True` or `False`.
"#
),
)
}
#[test]
fn when_if_guard() {
report_problem_as(
indoc!(
r#"
when 1 is
2 if 1 -> 0x0
_ -> 0x1
"#
),
indoc!(
r#"
This `if` guard condition needs to be a Bool:
2 ┆ 2 if 1 -> 0x0
┆ ^
Right now its a number of type:
Num a
But I need every `if` guard condition to evaluate to a Bool—either `True` or `False`.
"#
),
)
}
#[test]
fn if_2_branch_mismatch() {
report_problem_as(
indoc!(
r#"
if True then 2 else "foo"
"#
),
indoc!(
r#"
This `if` has an `else` branch with a different type from its `then` branch:
1 ┆ if True then 2 else "foo"
┆ ^^^^^
The `else` branch is a string of type:
Str
but the `then` branch has the type:
Num a
I need all branches in an `if` to have the same type!
"#
),
)
}
// #[test]
// fn if_3_branch_mismatch() {
// report_problem_as(
// indoc!(
// r#"
// if True then 2 else if False then 2 else "foo"
// "#
// ),
// indoc!(
// r#"
// The 2nd branch of this `if` does not match all the previous branches:
// 1 ┆ if True then 2 else "foo"
// ┆ ^^^^^
// The 2nd branch is a string of type
// Str
// But all the previous branches have the type
// Num a
// "#
// ),
// )
// }
#[test]
fn when_branch_mismatch() {
report_problem_as(
indoc!(
r#"
when 1 is
2 -> "foo"
3 -> {}
"#
),
indoc!(
r#"
The 2nd branch of this `when` does not match all the previous branches:
3 ┆ 3 -> {}
┆ ^^
The 2nd branch is a record of type:
{}
But all the previous branches have type:
Str
I need all branches of a `when` to have the same type!
"#
),
)
}
#[test]
fn elem_in_list() {
report_problem_as(
indoc!(
r#"
[ 1, 3, "foo" ]
"#
),
indoc!(
r#"
The 3rd element of this list does not match all the previous elements:
1 ┆ [ 1, 3, "foo" ]
┆ ^^^^^
The 3rd element is a string of type:
Str
But all the previous elements in the list have type:
Num a
I need all elements of a list to have the same type!
"#
),
)
}
#[test]
fn record_update_value() {
report_problem_as(
indoc!(
r#"
x : { foo : {} }
x = { foo: {} }
{ x & foo: "bar" }
"#
),
indoc!(
r#"
I cannot update the `.foo` field like this:
4 ┆ { x & foo: "bar" }
┆ ^^^^^^^^^^^^^^^^^^
You are trying to update `.foo` to be a string of type:
Str
But it should be:
{}
Record update syntax does not allow you to change the type of fields. You can achieve that with record literal syntax.
"#
),
)
}
// needs a bit more infrastructure re. diffing records
// #[test]
// fn record_update_keys() {
// report_problem_as(
// indoc!(
// r#"
// x : { foo : {} }
// x = { foo: {} }
//
// { x & baz: "bar" }
// "#
// ),
// indoc!(
// r#"
// The `x` record does not have a `baz` field:
//
// 4 ┆ { x & baz: "bar" }
// ┆ ^^^
//
// This is usually a typo. Here are the `x` fields that are most similar:
//
// { foo : {}
// }
//
// So maybe `baz` should be `foo`?
// "#
// ),
// )
// }
// #[test]
// fn num_literal() {
// report_problem_as(
// indoc!(
// r#"
// x : Str
// x = 4
//
// x
// "#
// ),
// indoc!(
// r#"
// Something is off with the body of the `x` definition:
//
// 4 ┆ x = 4
// ┆ ^
//
// The body is a number of type:
//
// Num a
//
// But the type annotation on `x` says that it should be:
//
// Str
//
// "#
// ),
// )
// }
#[test]
fn circular_type() {
report_problem_as(
indoc!(
r#"
f = \g -> g g
f
"#
),
indoc!(
r#"
I'm inferring a weird self-referential type for `g`:
1 ┆ f = \g -> g g
┆ ^
Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely.
∞ -> a
"#
),
)
}
#[test]
fn polymorphic_recursion() {
report_problem_as(
indoc!(
r#"
f = \x -> f [x]
f
"#
),
indoc!(
r#"
I'm inferring a weird self-referential type for `f`:
1 ┆ f = \x -> f [x]
┆ ^
Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely.
List ∞ -> a
"#
),
)
}
#[test]
fn record_field_mismatch() {
report_problem_as(
indoc!(
r#"
bar = { bar : 0x3 }
f : { foo : Int } -> Bool
f = \_ -> True
f bar
"#
),
indoc!(
r#"
The 1st argument to `f` is not what I expect:
6 ┆ f bar
┆ ^^^
This `bar` value is a:
{ bar : Int }
But `f` needs the 1st argument to be:
{ foo : Int }
Hint: Seems like a record field typo. Maybe `bar` should be `foo`?
Hint: Can more type annotations be added? Type annotations always help
me give more specific messages, and I think they could help a lot in
this case
"#
),
)
}
#[test]
fn tag_mismatch() {
report_problem_as(
indoc!(
r#"
f : [ Red, Green ] -> Bool
f = \_ -> True
f Blue
"#
),
indoc!(
r#"
The 1st argument to `f` is not what I expect:
4 ┆ f Blue
┆ ^^^^
This `Blue` global tag application has the type:
[ Blue ]a
But `f` needs the 1st argument to be:
[ Green, Red ]
Hint: Seems like a tag typo. Maybe `Blue` should be `Red`?
Hint: Can more type annotations be added? Type annotations always help
me give more specific messages, and I think they could help a lot in
this case
"#
),
)
}
#[test]
fn tag_with_arguments_mismatch() {
report_problem_as(
indoc!(
r#"
f : [ Red Int, Green Bool ] -> Bool
f = \_ -> True
f (Blue 3.14)
"#
),
indoc!(
r#"
The 1st argument to `f` is not what I expect:
4 ┆ f (Blue 3.14)
┆ ^^^^^^^^^
This `Blue` global tag application has the type:
[ Blue Float ]a
But `f` needs the 1st argument to be:
[ Green Bool, Red Int ]
Hint: Seems like a tag typo. Maybe `Blue` should be `Red`?
Hint: Can more type annotations be added? Type annotations always help
me give more specific messages, and I think they could help a lot in
this case
"#
),
)
}
#[test]
fn from_annotation_if() {
report_problem_as(
indoc!(
r#"
x : Int
x = if True then 3.14 else 4
x
"#
),
indoc!(
r#"
Something is off with the 1st branch of this `if` expression:
2 ┆ x = if True then 3.14 else 4
┆ ^^^^
The 1st branch is a float of type:
Float
But the type annotation on `x` says it should be:
Int
"#
),
)
}
#[test]
fn from_annotation_when() {
report_problem_as(
indoc!(
r#"
x : Int
x =
when True is
_ -> 3.14
x
"#
),
indoc!(
r#"
Something is off with the 1st branch of this `when` expression:
4 ┆ _ -> 3.14
┆ ^^^^
The 1st branch is a float of type:
Float
But the type annotation on `x` says it should be:
Int
"#
),
)
}
#[test]
fn from_annotation_function() {
report_problem_as(
indoc!(
r#"
x : Int -> Int
x = \_ -> 3.14
x
"#
),
indoc!(
r#"
Something is off with the body of the `x` definition:
2 ┆ x = \_ -> 3.14
┆ ^^^^^^^^^^
The body is an anonymous function of type:
Int -> Float
But the type annotation on `x` says it should be:
Int -> Int
"#
),
)
}
#[test]
fn fncall_value() {
report_problem_as(
indoc!(
r#"
x : Int
x = 42
x 3
"#
),
indoc!(
r#"
The `x` value is not a function, but it was given 1 argument:
4 ┆ x 3
┆ ^
Are there any missing commas? Or missing parentheses?"#
),
)
}
#[test]
fn fncall_overapplied() {
report_problem_as(
indoc!(
r#"
f : Int -> Int
f = \_ -> 42
f 1 2
"#
),
indoc!(
r#"
The `f` function expects 1 argument, but it got 2 instead:
4 ┆ f 1 2
┆ ^
Are there any missing commas? Or missing parentheses?"#
),
)
}
#[test]
fn fncall_underapplied() {
report_problem_as(
indoc!(
r#"
f : Int, Int -> Int
f = \_, _ -> 42
f 1
"#
),
indoc!(
r#"
The `f` function expects 2 arguments, but it got only 1:
4 ┆ f 1
┆ ^
Roc does not allow functions to be partially applied. Use a closure to make partial application explicit."#
),
)
}
#[test]
fn pattern_when_condition() {
report_problem_as(
indoc!(
r#"
when 1 is
{} -> 42
"#
),
indoc!(
r#"
The 1st pattern in this `when` is causing a mismatch:
2 ┆ {} -> 42
┆ ^^
The first pattern is trying to match record values of type:
{}a
But the expression between `when` and `is` has the type:
Num a
"#
),
)
}
#[test]
fn pattern_when_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
2 -> 3
{} -> 42
"#
),
indoc!(
r#"
The 2nd pattern in this `when` does not match the previous ones:
3 ┆ {} -> 42
┆ ^^
The 2nd pattern is trying to match record values of type:
{}a
But all the previous branches match:
Num a
"#
),
)
}
#[test]
fn pattern_guard_mismatch() {
report_problem_as(
indoc!(
r#"
when { foo: 1 } is
{ foo: True } -> 42
"#
),
indoc!(
r#"
The 1st pattern in this `when` is causing a mismatch:
2 ┆ { foo: True } -> 42
┆ ^^^^^^^^^^^^^
The first pattern is trying to match record values of type:
{ foo : [ True ]a }
But the expression between `when` and `is` has the type:
{ foo : Num a }
"#
),
)
}
#[test]
fn pattern_guard_does_not_bind_label() {
// needs some improvement, but the principle works
report_problem_as(
indoc!(
r#"
when { foo: 1 } is
{ foo: 2 } -> foo
"#
),
indoc!(
r#"
I cannot find a `foo` value
2 ┆ { foo: 2 } -> foo
┆ ^^^
these names seem close though:
Bool
Int
Num
Map
"#
),
)
}
#[test]
fn pattern_guard_can_be_shadowed_above() {
report_problem_as(
indoc!(
r#"
foo = 3
when { foo: 1 } is
{ foo: 2 } -> foo
"#
),
// should give no error
"",
)
}
#[test]
fn pattern_guard_can_be_shadowed_below() {
report_problem_as(
indoc!(
r#"
when { foo: 1 } is
{ foo: 2 } ->
foo = 3
foo
"#
),
// should give no error
"",
)
}
#[test]
fn pattern_or_pattern_mismatch() {
report_problem_as(
indoc!(
r#"
when { foo: 1 } is
{} | 1 -> 3
"#
),
// Just putting this here. We should probably handle or-patterns better
indoc!(
r#"
The 1st pattern in this `when` is causing a mismatch:
2 ┆ {} | 1 -> 3
┆ ^^^^^^
The first pattern is trying to match numbers:
Num a
But the expression between `when` and `is` has the type:
{ foo : Num a }
"#
),
)
}
#[test]
fn pattern_let_mismatch() {
report_problem_as(
indoc!(
r#"
(Foo x) = 42
x
"#
),
// Maybe this should specifically say the pattern doesn't work?
indoc!(
r#"
This expression is used in an unexpected way:
1 ┆ (Foo x) = 42
┆ ^^
It is a number of type:
Num a
But you are trying to use it as:
[ Foo a ]b
"#
),
)
}
#[test]
fn from_annotation_complex_pattern() {
report_problem_as(
indoc!(
r#"
{ x } : { x : Int }
{ x } = { x: 4.0 }
x
"#
),
indoc!(
r#"
Something is off with the body of this definition:
2 ┆ { x } = { x: 4.0 }
┆ ^^^^^^^^^^
The body is a record of type:
{ x : Float }
But the type annotation says it should be:
{ x : Int }
"#
),
)
}
#[test]
fn missing_fields() {
report_problem_as(
indoc!(
r#"
x : { a : Int, b : Float, c : Bool }
x = { b: 4.0 }
x
"#
),
indoc!(
r#"
Something is off with the body of the `x` definition:
2 ┆ x = { b: 4.0 }
┆ ^^^^^^^^^^
The body is a record of type:
{ b : Float }
But the type annotation on `x` says it should be:
{ a : Int, b : Float, c : Bool }
Hint: Looks like the c and a fields are missing.
"#
),
)
}
#[test]
fn bad_double_rigid() {
report_problem_as(
indoc!(
r#"
f : a, b -> a
f = \x, y -> if True then x else y
f
"#
),
indoc!(
r#"
Something is off with the body of the `f` definition:
2 ┆ f = \x, y -> if True then x else y
┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The body is an anonymous function of type:
a, a -> a
But the type annotation on `f` says it should be:
a, b -> a
Hint: Your type annotation uses a and b as separate type variables.
Your code seems to be saying they are the same though. Maybe they
should be the same your type annotation? Maybe your code uses them in
a weird way?
"#
),
)
}
#[test]
fn bad_rigid_function() {
report_problem_as(
indoc!(
r#"
f : Bool -> msg
f = \_ -> Foo
f
"#
),
indoc!(
r#"
Something is off with the body of the `f` definition:
2 ┆ f = \_ -> Foo
┆ ^^^^^^^^^
The body is an anonymous function of type:
Bool -> [ Foo ]a
But the type annotation on `f` says it should be:
Bool -> msg
Hint: The type annotation uses the type variable `msg` to say that
this definition can produce any type of value. But in the body I see
that it will only produce a tag value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"#
),
)
}
#[test]
fn bad_rigid_value() {
report_problem_as(
indoc!(
r#"
f : msg
f = 0x3
f
"#
),
indoc!(
r#"
Something is off with the body of the `f` definition:
2 ┆ f = 0x3
┆ ^^^
The body is an integer of type:
Int
But the type annotation on `f` says it should be:
msg
Hint: The type annotation uses the type variable `msg` to say that
this definition can produce any type of value. But in the body I see
that it will only produce a Int value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"#
),
)
}
}