roc/crates/reporting/tests/test_reporting.rs
Ayaz Hafiz 4657a957f7
When storing variables, merge them directly with the target rather than unifying
When we unify two variables that end up merged, the rank of the
resulting content is the lower of the two variables being merged. But
during storage, we really do mean, take the target descriptor of the
type we're merging against, and don't try to lower to a
possibly-generalized rank! This fixes a couple bugs I didn't even
realize were present!
2022-07-29 14:53:14 -04:00

10134 lines
275 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;
extern crate bumpalo;
extern crate indoc;
extern crate roc_reporting;
mod helpers;
#[cfg(test)]
mod test_reporting {
use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut, ParseErrOut};
use bumpalo::Bump;
use indoc::indoc;
use roc_can::abilities::AbilitiesStore;
use roc_can::expr::PendingDerives;
use roc_load::{self, LoadedModule, LoadingProblem, Threading};
use roc_module::symbol::{Interns, ModuleId};
use roc_region::all::LineInfo;
use roc_reporting::report::{
can_problem, parse_problem, type_problem, RenderTarget, Report, Severity, ANSI_STYLE_CODES,
DEFAULT_PALETTE,
};
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
use roc_solve_problem::TypeError;
use roc_types::subs::Subs;
use std::path::PathBuf;
fn filename_from_string(str: &str) -> PathBuf {
let mut filename = PathBuf::new();
filename.push(str);
filename
}
fn to_simple_report(doc: RocDocBuilder) -> Report {
Report {
title: "".to_string(),
doc,
filename: filename_from_string(r"/code/proj/Main.roc"),
severity: Severity::RuntimeError,
}
}
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [main] to \"./platform\"\n\nmain =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
fn run_load_and_infer<'a>(
subdir: &str,
arena: &'a Bump,
src: &'a str,
) -> (String, Result<LoadedModule, LoadingProblem<'a>>) {
use std::fs::File;
use std::io::Write;
let module_src = if src.starts_with("app") {
// this is already a module
src.to_string()
} else {
// this is an expression, promote it to a module
promote_expr_to_module(src)
};
let exposed_types = Default::default();
let loaded = {
// Use a deterministic temporary directory.
// We can't have all tests use "tmp" because tests run in parallel,
// so append the test name to the tmp path.
let tmp = format!("tmp/{}", subdir);
let dir = roc_test_utils::TmpDir::new(&tmp);
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let full_file_path = file_path.clone();
let mut file = File::create(file_path).unwrap();
writeln!(file, "{}", module_src).unwrap();
let result = roc_load::load_and_typecheck(
arena,
full_file_path,
exposed_types,
roc_target::TargetInfo::default_x86_64(),
RenderTarget::Generic,
Threading::Single,
);
drop(file);
result
};
(module_src, loaded)
}
#[allow(clippy::type_complexity)]
fn infer_expr_help_new<'a>(
subdir: &str,
arena: &'a Bump,
expr_src: &'a str,
) -> Result<
(
String,
Vec<TypeError>,
Vec<roc_problem::can::Problem>,
ModuleId,
Interns,
),
LoadingProblem<'a>,
> {
let (module_src, result) = run_load_and_infer(subdir, arena, expr_src);
let LoadedModule {
module_id: home,
mut can_problems,
mut type_problems,
interns,
..
} = result?;
let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
Ok((module_src, type_problems, can_problems, home, interns))
}
fn list_reports_new<F>(subdir: &str, arena: &Bump, src: &str, finalize_render: F) -> String
where
F: FnOnce(RocDocBuilder<'_>, &mut String),
{
use ven_pretty::DocAllocator;
let filename = filename_from_string(r"/code/proj/Main.roc");
let mut buf = String::new();
match infer_expr_help_new(subdir, arena, src) {
Err(LoadingProblem::FormattedReport(fail)) => fail,
Ok((module_src, type_problems, can_problems, home, interns)) => {
let lines = LineInfo::new(&module_src);
let src_lines: Vec<&str> = module_src.split('\n').collect();
let mut reports = Vec::new();
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
for problem in can_problems {
let report = can_problem(&alloc, &lines, filename.clone(), problem.clone());
reports.push(report);
}
for problem in type_problems {
if let Some(report) =
type_problem(&alloc, &lines, filename.clone(), problem.clone())
{
reports.push(report);
}
}
let has_reports = !reports.is_empty();
let doc = alloc
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
.append(if has_reports {
alloc.line()
} else {
alloc.nil()
});
finalize_render(doc, &mut buf);
buf
}
Err(other) => {
panic!("failed to load: {:?}", other);
}
}
}
fn infer_expr_help<'a>(
arena: &'a Bump,
expr_src: &'a str,
) -> Result<
(
Vec<TypeError>,
Vec<roc_problem::can::Problem>,
ModuleId,
Interns,
),
ParseErrOut<'a>,
> {
let CanExprOut {
loc_expr: _,
output,
var_store,
var,
constraints,
constraint,
home,
interns,
problems: can_problems,
..
} = can_expr(arena, expr_src)?;
let mut subs = Subs::new_from_varstore(var_store);
for named in output.introduced_variables.named {
subs.rigid_var(named.variable, named.name);
}
for var in output.introduced_variables.wildcards {
subs.rigid_var(var.value, "*".into());
}
let mut solve_aliases = roc_solve::solve::Aliases::default();
for (name, alias) in output.aliases {
solve_aliases.insert(name, alias);
}
let mut unify_problems = Vec::new();
let mut abilities_store = AbilitiesStore::default();
let (_content, _subs) = infer_expr(
subs,
&mut unify_problems,
&constraints,
&constraint,
// Use `new_report_problem_as` in order to get proper derives.
// TODO: remove the non-new reporting test infra.
PendingDerives::default(),
&mut solve_aliases,
&mut abilities_store,
Default::default(),
var,
);
Ok((unify_problems, can_problems, home, interns))
}
fn list_reports<F>(arena: &Bump, src: &str, buf: &mut String, callback: F)
where
F: FnOnce(RocDocBuilder<'_>, &mut String),
{
use ven_pretty::DocAllocator;
let src_lines: Vec<&str> = src.split('\n').collect();
let lines = LineInfo::new(src);
let filename = filename_from_string(r"/code/proj/Main.roc");
match infer_expr_help(arena, src) {
Err(parse_err) => {
let ParseErrOut {
fail,
home,
interns,
} = parse_err;
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let problem = fail.into_file_error(filename.clone());
let doc = parse_problem(&alloc, &lines, filename, 0, problem);
callback(doc.pretty(&alloc).append(alloc.line()), buf)
}
Ok((type_problems, can_problems, home, interns)) => {
let mut reports = Vec::new();
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
for problem in can_problems {
let report = can_problem(&alloc, &lines, filename.clone(), problem.clone());
reports.push(report);
}
for problem in type_problems {
if let Some(report) =
type_problem(&alloc, &lines, filename.clone(), problem.clone())
{
reports.push(report);
}
}
let has_reports = !reports.is_empty();
let doc = alloc
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
.append(if has_reports {
alloc.line()
} else {
alloc.nil()
});
callback(doc, buf)
}
}
}
fn list_header_reports<F>(arena: &Bump, src: &str, buf: &mut String, callback: F)
where
F: FnOnce(RocDocBuilder<'_>, &mut String),
{
use ven_pretty::DocAllocator;
use roc_parse::state::State;
let state = State::new(src.as_bytes());
let filename = filename_from_string(r"/code/proj/Main.roc");
let src_lines: Vec<&str> = src.split('\n').collect();
let lines = LineInfo::new(src);
match roc_parse::module::parse_header(arena, state) {
Err(fail) => {
let interns = Interns::default();
let home = crate::helpers::test_home();
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
use roc_parse::parser::SyntaxError;
let problem = fail
.map_problem(SyntaxError::Header)
.into_file_error(filename.clone());
let doc = parse_problem(&alloc, &lines, filename, 0, problem);
callback(doc.pretty(&alloc).append(alloc.line()), buf)
}
Ok(_) => todo!(),
}
}
fn report_header_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1
.render_raw(70, &mut roc_reporting::report::CiWrite::new(buf))
.expect("list_reports")
};
list_header_reports(&arena, src, &mut buf, callback);
// convenient to copy-paste the generated message
if buf != expected_rendering {
for line in buf.split('\n') {
println!(" {}", line);
}
}
assert_eq!(buf, expected_rendering);
}
fn color_report_problem_as(src: &str, expected_rendering: &str) {
let mut buf: String = String::new();
let arena = Bump::new();
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1
.render_raw(
70,
&mut roc_reporting::report::ColorWrite::new(
&roc_reporting::report::DEFAULT_PALETTE,
buf,
),
)
.expect("list_reports")
};
list_reports(&arena, src, &mut buf, callback);
let readable = human_readable(&buf);
assert_eq!(readable, expected_rendering);
}
/// Do not call this directly! Use the test_report macro below!
fn __new_report_problem_as(subdir: &str, src: &str, check_render: impl FnOnce(&str)) {
let arena = Bump::new();
let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| {
doc.1
.render_raw(70, &mut roc_reporting::report::CiWrite::new(buf))
.expect("list_reports")
};
let buf = list_reports_new(subdir, &arena, src, finalize_render);
check_render(buf.as_str());
}
macro_rules! test_report {
($(#[$meta:meta])* $test_name:ident, $program:expr, @$output:literal) => {
test_report!($(#[$meta])* $test_name, $program, |golden| insta::assert_snapshot!(golden, @$output) );
};
($(#[$meta:meta])* $test_name: ident, $program:expr, $expecting:expr) => {
#[test]
$(#[$meta])*
fn $test_name() {
__new_report_problem_as(std::stringify!($test_name), $program, $expecting)
}
}
}
fn human_readable(str: &str) -> String {
str.replace(ANSI_STYLE_CODES.red, "<red>")
.replace(ANSI_STYLE_CODES.white, "<white>")
.replace(ANSI_STYLE_CODES.blue, "<blue>")
.replace(ANSI_STYLE_CODES.yellow, "<yellow>")
.replace(ANSI_STYLE_CODES.green, "<green>")
.replace(ANSI_STYLE_CODES.cyan, "<cyan>")
.replace(ANSI_STYLE_CODES.magenta, "<magenta>")
.replace(ANSI_STYLE_CODES.reset, "<reset>")
.replace(ANSI_STYLE_CODES.bold, "<bold>")
.replace(ANSI_STYLE_CODES.underline, "<underline>")
}
test_report!(
value_not_exposed,
indoc!(
r#"
List.isempty 1 2
"#
),
@r###"
── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─
The List module does not expose `isempty`:
4│ List.isempty 1 2
^^^^^^^^^^^^
Did you mean one of these?
List.isEmpty
List.set
List.get
List.keepIf
"###
);
test_report!(
report_unused_def,
indoc!(
r#"
x = 1
y = 2
x
"#
),
@r###"
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`y` is not used anywhere in your code.
5│ 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_report!(
report_shadowing,
indoc!(
r#"
i = 1
s = \i ->
i + 1
s i
"#
),
@r###"
── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─
The `i` name is first defined here:
4│ i = 1
^
But then it's defined a second time here:
6│ 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_report!(
report_shadowing_in_annotation,
indoc!(
r#"
Booly : [Yes, No]
Booly : [Yes, No, Maybe]
x : List Booly
x = []
x
"#
),
@r###"
── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─
The `Booly` name is first defined here:
4│ Booly : [Yes, No]
^^^^^^^^^^^^^^^^^
But then it's defined a second time here:
6│ Booly : [Yes, No, Maybe]
^^^^^^^^^^^^^^^^^^^^^^^^
Since these aliases have the same name, it's easy to use the wrong one
on accident. Give one of them a new name.
"###
);
test_report!(
report_precedence_problem_single_line,
indoc!(
r#"x = 1
y =
if selectedId != thisId == adminsId then
4
else
5
{ x, y }
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
Using != and == together requires parentheses, to clarify how they
should be grouped.
6│ if selectedId != thisId == adminsId then
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"###
);
test_report!(
#[ignore = "Blocked on https://github.com/rtfeldman/roc/issues/3385"]
unrecognized_name,
indoc!(
r#"
foo = { x: 1 == 1, y: 0x4 }
baz = 3
main : Str
main =
when foo.y is
4 -> bar baz "yay"
_ -> "nay"
main
"#
),
@r#"
── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─
Nothing is named `bar` in this scope.
8│ 4 -> bar baz "yay"
^^^
Did you mean one of these?
baz
Str
Err
main
"#
);
test_report!(
lowercase_primitive_tag_bool,
indoc!(
r#"
if true then 1 else 2
"#
),
@r###"
── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─
Nothing is named `true` in this scope.
4│ if true then 1 else 2
^^^^
Did you mean one of these?
True
Str
Frac
Num
"###
);
test_report!(
report_precedence_problem_multiline,
indoc!(
r#"
if
1
== 2
== 3
then
2
else
3
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
Using more than one == like this requires parentheses, to clarify how
things should be grouped.
5│> 1
6│> == 2
7│> == 3
"###
);
test_report!(
unused_arg_and_unused_def,
indoc!(
r#"
y = 9
box = \class, htmlChildren ->
div [class] []
div = \_, _ -> 4
box "wizard" []
"#
),
@r###"
── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─
`box` doesn't use `htmlChildren`.
6│ 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.
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`y` is not used anywhere in your code.
4│ y = 9
^
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_value_color() {
let src: &str = indoc!(
r#"
activityIndicatorLarge = div
view activityIndicatorLarge
"#
);
let arena = Bump::new();
let (_type_problems, _can_problems, home, interns) =
infer_expr_help(&arena, src).expect("parse error");
let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
let symbol = interns.symbol(test_home(), "activityIndicatorLarge".into());
to_simple_report(alloc.symbol_unqualified(symbol)).render_color_terminal(
&mut buf,
&alloc,
&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 arena = Bump::new();
let (_type_problems, _can_problems, home, mut interns) =
infer_expr_help(&arena, src).expect("parse error");
let mut buf = String::new();
let src_lines: Vec<&str> = src.split('\n').collect();
let module_id = interns.module_id(&"Util.Int".into());
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
to_simple_report(alloc.module(module_id)).render_color_terminal(
&mut buf,
&alloc,
&DEFAULT_PALETTE,
);
assert_eq!(human_readable(&buf), "<green>Util.Int<reset>");
}
#[test]
fn report_region_in_color() {
color_report_problem_as(
indoc!(
r#"
isDisabled = \user -> user.isAdmin
theAdmin
|> isDisabled
"#
),
indoc!(
r#"
<cyan>── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─<reset>
Nothing is named `theAdmin` in this scope.
<cyan>3<reset><cyan>│<reset> <white>theAdmin<reset>
<red>^^^^^^^^<reset>
Did you mean one of these?
List
True
Box
Str
"#
),
);
}
test_report!(
if_condition_not_bool,
indoc!(
r#"
if "foo" then 2 else 3
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `if` condition needs to be a Bool:
4│ 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_report!(
when_if_guard,
indoc!(
r#"
when 1 is
2 if 1 -> 0x0
_ -> 0x1
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `if` guard condition needs to be a Bool:
4│ when 1 is
5│> 2 if 1 -> 0x0
6│ _ -> 0x1
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_report!(
if_2_branch_mismatch,
indoc!(
r#"
if True then 2 else "foo"
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `if` has an `else` branch with a different type from its `then` branch:
4│ if True then 2 else "foo"
^^^^^
The `else` branch is a string of type:
Str
but the `then` branch has the type:
Num a
All branches in an `if` must have the same type!
"###
);
test_report!(
if_3_branch_mismatch,
indoc!(
r#"
if True then 2 else if False then 2 else "foo"
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 3rd branch of this `if` does not match all the previous branches:
4│ if True then 2 else if False then 2 else "foo"
^^^^^
The 3rd branch is a string of type:
Str
But all the previous branches have type:
Num a
All branches in an `if` must have the same type!
"###
);
test_report!(
when_branch_mismatch,
indoc!(
r#"
when 1 is
2 -> "foo"
3 -> {}
_ -> ""
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 2nd branch of this `when` does not match all the previous branches:
4│ when 1 is
5│ 2 -> "foo"
6│> 3 -> {}
7│ _ -> ""
The 2nd branch is a record of type:
{}
But all the previous branches have type:
Str
All branches of a `when` must have the same type!
"###
);
test_report!(
elem_in_list,
indoc!(
r#"
[1, 3, "foo"]
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This list contains elements with different types:
4│ [1, 3, "foo"]
^^^^^
Its 3rd element is a string of type:
Str
However, the preceding elements in the list all have the type:
Num a
Every element in a list must have the same type!
"###
);
test_report!(
unwrap_num_elem_in_list,
indoc!(
r#"
[1, 2.2, 0x3]
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This list contains elements with different types:
4│ [1, 2.2, 0x3]
^^^
Its 3rd element is an integer of type:
Int a
However, the preceding elements in the list all have the type:
Frac a
Every element in a list must have the same type!
Tip: You can convert between Int and Frac using functions like
`Num.toFrac` and `Num.round`.
"###
);
test_report!(
record_update_value,
indoc!(
r#"
x : { foo : {} }
x = { foo: {} }
{ x & foo: "bar" }
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
I cannot update the `.foo` field like this:
7│ { 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.
"###
);
test_report!(
circular_type,
indoc!(
r#"
f = \g -> g g
f
"#
),
@r###"
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `g`:
4│ 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_report!(
polymorphic_recursion,
indoc!(
r#"
f = \x -> f [x]
f
"#
),
@r###"
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `f`:
4│ 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_report!(
polymorphic_mutual_recursion,
indoc!(
r#"
f = \x -> g x
g = \x -> f [x]
f
"#
),
@r###"
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `f`:
4│ f = \x -> g 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
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `g`:
5│ g = \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_report!(
polymorphic_mutual_recursion_annotated,
indoc!(
r#"
f : a -> List a
f = \x -> g x
g = \x -> f [x]
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression is used in an unexpected way:
5│ f = \x -> g x
^^^
This `g` call produces:
List List a
But you are trying to use it as:
List a
Tip: The type annotation uses the type variable `a` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `List` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"###
);
test_report!(
polymorphic_mutual_recursion_dually_annotated_lie,
indoc!(
r#"
f : a -> List a
f = \x -> g x
g : b -> List b
g = \x -> f [x]
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression is used in an unexpected way:
7│ g = \x -> f [x]
^^^^^
This `f` call produces:
List List b
But you are trying to use it as:
List b
Tip: The type annotation uses the type variable `b` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `List` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"###
);
test_report!(
polymorphic_recursion_inference_var,
indoc!(
r#"
f : _
f = \x -> f [x]
f
"#
),
@r#"
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `f`:
5│ 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_report!(
polymorphic_recursion_with_deep_inference_var,
indoc!(
r#"
f : _ -> List _
f = \x -> f [x]
f
"#
),
@r#"
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `x`:
5│ 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 ∞
"#
);
test_report!(
mutual_polymorphic_recursion_with_inference_var,
indoc!(
r#"
f : _ -> List _
f = \x -> g x
g = \x -> f [x]
f
"#
),
// TODO: the second error is duplicated because when solving `f : _ -> List _`, we
// introduce the variable for `f` twice: once to solve `f` without generalization,
// and then a second time to properly generalize it. When a def is unannotated
// (like in `g`) the same variable gets used both times, because the type of `g` is
// only an unbound type variable. However, for `f`, we run `type_to_var` twice,
// receiving two separate variables, and the second variable doesn't have the cycle
// error already recorded for the first.
// The way to resolve this is to always give type annotation signatures an extra
// variables they can put themselves in, and to run the constraint algorithm
// against that extra variable, rather than possibly having to translate a `Type`
// again.
@r###"
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `f`:
5│ f = \x -> g 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 ∞ -> List a
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `g`:
6│ g = \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 ∞ -> List a
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `f`:
5│ f = \x -> g 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 ∞ -> List a
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `main`:
3│ main =
^^^^
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 ∞ -> List a
"###
);
test_report!(
mutual_polymorphic_recursion_with_inference_var_second,
indoc!(
r#"
f = \x -> g x
g : _ -> List _
g = \x -> f [x]
f
"#
),
@r#"
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `x`:
6│ g = \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 ∞
"#
);
test_report!(
record_field_mismatch,
indoc!(
r#"
bar = { bar : 0x3 }
f : { foo : Num.Int * } -> [Yes, No]
f = \_ -> Yes
f bar
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `f` has an unexpected type:
9│ f bar
^^^
This `bar` value is a:
{ bar : Int a }
But `f` needs its 1st argument to be:
{ foo : Int * }
Tip: Seems like a record field typo. Maybe `bar` should be `foo`?
Tip: 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_report!(
tag_mismatch,
indoc!(
r#"
f : [Red, Green] -> [Yes, No]
f = \_ -> Yes
f Blue
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `f` has an unexpected type:
7│ f Blue
^^^^
This `Blue` tag has the type:
[Blue]a
But `f` needs its 1st argument to be:
[Green, Red]
Tip: Seems like a tag typo. Maybe `Blue` should be `Red`?
Tip: 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_report!(
tag_with_arguments_mismatch,
indoc!(
r#"
f : [Red (Num.Int *), Green Str] -> Str
f = \_ -> "yes"
f (Blue 3.14)
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `f` has an unexpected type:
7│ f (Blue 3.14)
^^^^^^^^^
This `Blue` tag application has the type:
[Blue (Frac a)]b
But `f` needs its 1st argument to be:
[Green Str, Red (Int *)]
Tip: Seems like a tag typo. Maybe `Blue` should be `Red`?
Tip: 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_report!(
from_annotation_if,
indoc!(
r#"
x : Num.Int *
x = if True then 3.14 else 4
x
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the `then` branch of this `if` expression:
4│ x : Num.Int *
5│ x = if True then 3.14 else 4
^^^^
The 1st branch is a fraction of type:
Frac a
But the type annotation on `x` says it should be:
Int *
Tip: You can convert between Int and Frac using functions like
`Num.toFrac` and `Num.round`.
"###
);
test_report!(
from_annotation_when,
indoc!(
r#"
x : Num.Int *
x =
when True is
_ -> 3.14
x
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `x` definition:
4│ x : Num.Int *
5│ x =
6│> when True is
7│> _ -> 3.14
This `when` expression produces:
Frac a
But the type annotation on `x` says it should be:
Int *
Tip: You can convert between Int and Frac using functions like
`Num.toFrac` and `Num.round`.
"###
);
test_report!(
from_annotation_function,
indoc!(
r#"
x : Num.Int * -> Num.Int *
x = \_ -> 3.14
x
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `x` definition:
4│ x : Num.Int * -> Num.Int *
5│ x = \_ -> 3.14
^^^^
The body is a fraction of type:
Frac a
But the type annotation on `x` says it should be:
Int *
Tip: You can convert between Int and Frac using functions like
`Num.toFrac` and `Num.round`.
"###
);
test_report!(
fncall_value,
indoc!(
r#"
x : Num.I64
x = 42
x 3
"#
),
@r###"
── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─
The `x` value is not a function, but it was given 1 argument:
7│ x 3
^
Are there any missing commas? Or missing parentheses?
"###
);
test_report!(
fncall_overapplied,
indoc!(
r#"
f : Num.I64 -> Num.I64
f = \_ -> 42
f 1 2
"#
),
@r###"
── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─
The `f` function expects 1 argument, but it got 2 instead:
7│ f 1 2
^
Are there any missing commas? Or missing parentheses?
"###
);
test_report!(
fncall_underapplied,
indoc!(
r#"
f : Num.I64, Num.I64 -> Num.I64
f = \_, _ -> 42
f 1
"#
),
@r###"
── TOO FEW ARGS ────────────────────────────────────────── /code/proj/Main.roc ─
The `f` function expects 2 arguments, but it got only 1:
7│ f 1
^
Roc does not allow functions to be partially applied. Use a closure to
make partial application explicit.
"###
);
test_report!(
pattern_when_condition,
indoc!(
r#"
when 1 is
{} -> 42
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
4│> when 1 is
5│ {} -> 42
The `when` condition is a number of type:
Num a
But the branch patterns have type:
{}a
The branches must be cases of the `when` condition's type!
"###
);
test_report!(
pattern_when_pattern,
indoc!(
r#"
when 1 is
2 -> 3
{} -> 42
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 2nd pattern in this `when` does not match the previous ones:
6│ {} -> 42
^^
The 2nd pattern is trying to match record values of type:
{}a
But all the previous branches match:
Num a
"###
);
test_report!(
pattern_guard_mismatch_alias,
indoc!(
r#"
when { foo: 1 } is
{ foo: True } -> 42
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
4│> when { foo: 1 } is
5│ { foo: True } -> 42
The `when` condition is a record of type:
{ foo : Num a }
But the branch patterns have type:
{ foo : [True] }
The branches must be cases of the `when` condition's type!
"###
);
test_report!(
pattern_guard_mismatch,
indoc!(
r#"
when { foo: "" } is
{ foo: True } -> 42
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
4│> when { foo: "" } is
5│ { foo: True } -> 42
The `when` condition is a record of type:
{ foo : Str }
But the branch patterns have type:
{ foo : [True] }
The branches must be cases of the `when` condition's type!
"###
);
// needs some improvement, but the principle works
test_report!(
pattern_guard_does_not_bind_label,
indoc!(
r#"
when { foo: 1 } is
{ foo: _ } -> foo
"#
),
@r###"
── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─
Nothing is named `foo` in this scope.
5│ { foo: _ } -> foo
^^^
Did you mean one of these?
Box
Bool
U8
F64
"###
);
test_report! {
pattern_guard_can_be_shadowed_above,
indoc!(
r#"
foo = 3
when { foo: 1 } is
{ foo: 2 } -> foo
_ -> foo
"#
),
@"" // should give no error
}
test_report! {
pattern_guard_can_be_shadowed_below,
indoc!(
r#"
when { foo: 1 } is
{ foo: 2 } ->
foo = 3
foo
_ -> 3
"#
),
// should give no error
@""
}
test_report!(
pattern_or_pattern_mismatch,
indoc!(
r#"
when { foo: 1 } is
{} | 1 -> 3
"#
),
// Just putting this here. We should probably handle or-patterns better
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 2nd pattern in this branch does not match the previous ones:
5│ {} | 1 -> 3
^
The 2nd pattern is trying to match numbers:
Num a
But all the previous branches match:
{}a
"###
);
test_report!(
pattern_let_mismatch,
indoc!(
r#"
(Foo x) = 42
x
"#
),
// Maybe this should specifically say the pattern doesn't work?
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression is used in an unexpected way:
4│ (Foo x) = 42
^^
It is a number of type:
Num a
But you are trying to use it as:
[Foo a]
"###
);
test_report!(
from_annotation_complex_pattern,
indoc!(
r#"
{ x } : { x : Num.Int * }
{ x } = { x: 4.0 }
x
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of this definition:
4│ { x } : { x : Num.Int * }
5│ { x } = { x: 4.0 }
^^^^^^^^^^
The body is a record of type:
{ x : Frac a }
But the type annotation says it should be:
{ x : Int * }
Tip: You can convert between Int and Frac using functions like
`Num.toFrac` and `Num.round`.
"###
);
test_report!(
malformed_int_pattern,
indoc!(
r#"
when 1 is
100A -> 3
_ -> 4
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This integer pattern is malformed:
5│ 100A -> 3
^^^^
Tip: Learn more about number literals at TODO
"###
);
test_report!(
malformed_float_pattern,
indoc!(
r#"
when 1 is
2.X -> 3
_ -> 4
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This float pattern is malformed:
5│ 2.X -> 3
^^^
Tip: Learn more about number literals at TODO
"###
);
test_report!(
malformed_hex_pattern,
indoc!(
r#"
when 1 is
0xZ -> 3
_ -> 4
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This hex integer pattern is malformed:
5│ 0xZ -> 3
^^^
Tip: Learn more about number literals at TODO
"###
);
test_report!(
malformed_oct_pattern,
indoc!(
r#"
when 1 is
0o9 -> 3
_ -> 4
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This octal integer pattern is malformed:
5│ 0o9 -> 3
^^^
Tip: Learn more about number literals at TODO
"###
);
test_report!(
malformed_bin_pattern,
indoc!(
r#"
when 1 is
0b4 -> 3
_ -> 4
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This binary integer pattern is malformed:
5│ 0b4 -> 3
^^^
Tip: Learn more about number literals at TODO
"###
);
test_report!(
missing_fields,
indoc!(
r#"
x : { a : Num.Int *, b : Num.Frac *, c : Str }
x = { b: 4.0 }
x
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `x` definition:
4│ x : { a : Num.Int *, b : Num.Frac *, c : Str }
5│ x = { b: 4.0 }
^^^^^^^^^^
The body is a record of type:
{ b : Frac a }
But the type annotation on `x` says it should be:
{ a : Int *, b : Frac *, c : Str }
Tip: Looks like the c and a fields are missing.
"###
);
// this previously reported the message below, not sure which is better
//
// Something is off with the body of the `f` definition:
//
// 1│ f : a, b -> a
// 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
test_report!(
bad_double_rigid,
indoc!(
r#"
f : a, b -> a
f = \x, y -> if True then x else y
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the `else` branch of this `if` expression:
4│ f : a, b -> a
5│ f = \x, y -> if True then x else y
^
This `y` value is a:
b
But the type annotation on `f` says it should be:
a
Tip: Your type annotation uses `b` and `a` as separate type variables.
Your code seems to be saying they are the same though. Maybe they
should be the same in your type annotation? Maybe your code uses them
in a weird way?
"###
);
test_report!(
bad_rigid_function,
indoc!(
r#"
f : Str -> msg
f = \_ -> Foo
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `f` definition:
4│ f : Str -> msg
5│ f = \_ -> Foo
^^^
This `Foo` tag has the type:
[Foo]a
But the type annotation on `f` says it should be:
msg
Tip: 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_report!(
bad_rigid_value,
indoc!(
r#"
f : msg
f = 0x3
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `f` definition:
4│ f : msg
5│ f = 0x3
^^^
The body is an integer of type:
Int a
But the type annotation on `f` says it should be:
msg
Tip: 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?
"###
);
// TODO improve tag suggestions
test_report!(
typo_lowercase_ok,
indoc!(
r#"
f : Str -> [Ok Num.I64, InvalidFoo]
f = \_ -> ok 4
f
"#
),
@r###"
── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─
Nothing is named `ok` in this scope.
5│ f = \_ -> ok 4
^^
Did you mean one of these?
Ok
U8
Box
f
"###
);
// these error messages seem pretty helpful
test_report!(
typo_uppercase_ok,
indoc!(
r#"
f : Str -> Num.I64
f = \_ ->
ok = 3
Ok
f
"#
),
@r###"
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`ok` is not used anywhere in your code.
6│ ok = 3
^^
If you didn't intend on using `ok` then remove it so future readers of
your code don't wonder why it is there.
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `f` definition:
4│ f : Str -> Num.I64
5│ f = \_ ->
6│ ok = 3
7│
8│ Ok
^^
This `Ok` tag has the type:
[Ok]a
But the type annotation on `f` says it should be:
I64
"###
);
// invalid recursion
test_report!(
circular_definition_self,
indoc!(
r#"
f = f
f
"#
),
@r#"
── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─
The `f` value is defined directly in terms of itself, causing an
infinite loop.
"#
);
// invalid mutual recursion
test_report!(
circular_definition,
indoc!(
r#"
foo = bar
bar = foo
foo
"#
),
@r###"
── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─
The `foo` definition is causing a very tricky infinite loop:
4│ foo = bar
^^^
The `foo` value depends on itself through the following chain of
definitions:
┌─────┐
│ foo
│ ↓
│ bar
└─────┘
"###
);
test_report!(
update_empty_record,
indoc!(
r#"
x = {}
{ x & foo: 3 }
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `x` record doesnt have a `foo` field:
6│ { x & foo: 3 }
^^^^^^
In fact, `x` is a record with no fields at all!
"###
);
test_report!(
update_record,
indoc!(
r#"
x = { fo: 3, bar: 4 }
{ x & foo: 3 }
"#
),
// TODO also suggest fields with the correct type
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `x` record doesnt have a `foo` field:
6│ { x & foo: 3 }
^^^^^^
There may be a typo. These `x` fields are the most similar:
{
fo : Num b,
bar : Num a,
}
Maybe `foo:` should be `fo:` instead?
"###
);
test_report!(
update_record_ext,
indoc!(
r#"
f : { fo: Num.I64 }ext -> Num.I64
f = \r ->
r2 = { r & foo: r.fo }
r2.fo
f
"#
),
// TODO also suggest fields with the correct type
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `r` record doesnt have a `foo` field:
6│ r2 = { r & foo: r.fo }
^^^^^^^^^
There may be a typo. These `r` fields are the most similar:
{
fo : I64,
}ext
Maybe `foo:` should be `fo:` instead?
"###
);
test_report!(
update_record_snippet,
indoc!(
r#"
x = { fo: 3, bar: 4, baz: 3, spam: 42, foobar: 3 }
{ x & foo: 3 }
"#
),
// TODO also suggest fields with the correct type
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `x` record doesnt have a `foo` field:
6│ { x & foo: 3 }
^^^^^^
There may be a typo. These `x` fields are the most similar:
{
fo : Num c,
foobar : Num d,
bar : Num a,
baz : Num b,
}
Maybe `foo:` should be `fo:` instead?
"###
);
test_report!(
plus_on_str,
indoc!(
r#"
0x4 + "foo"
"#
),
// TODO also suggest fields with the correct type
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `add` has an unexpected type:
4│ 0x4 + "foo"
^^^^^
The argument is a string of type:
Str
But `add` needs its 2nd argument to be:
Int a
"###
);
test_report!(
int_frac,
indoc!(
r#"
0x4 + 3.14
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `add` has an unexpected type:
4│ 0x4 + 3.14
^^^^
The argument is a fraction of type:
Frac a
But `add` needs its 2nd argument to be:
Int a
Tip: You can convert between Int and Frac using functions like
`Num.toFrac` and `Num.round`.
"###
);
test_report!(
boolean_tag,
indoc!(
r#"
42 + True
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `add` has an unexpected type:
4│ 42 + True
^^^^
This `True` boolean has the type:
[True]a
But `add` needs its 2nd argument to be:
Num a
"###
);
test_report!(
tag_missing,
indoc!(
r#"
f : [A] -> [A, B]
f = \a -> a
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `f` definition:
4│ f : [A] -> [A, B]
5│ f = \a -> a
^
This `a` value is a:
[A]
But the type annotation on `f` says it should be:
[A, B]
Tip: Looks like a closed tag union does not have the `B` tag.
Tip: Closed tag unions can't grow, because that might change the size
in memory. Can you use an open tag union?
"###
);
test_report!(
tags_missing,
indoc!(
r#"
f : [A] -> [A, B, C]
f = \a -> a
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `f` definition:
4│ f : [A] -> [A, B, C]
5│ f = \a -> a
^
This `a` value is a:
[A]
But the type annotation on `f` says it should be:
[A, B, C]
Tip: Looks like a closed tag union does not have the `B` and `C` tags.
Tip: Closed tag unions can't grow, because that might change the size
in memory. Can you use an open tag union?
"###
);
test_report!(
patterns_fn_not_exhaustive,
indoc!(
r#"
Either : [Left {}, Right Str]
x : Either
x = Left {}
f : Either -> {}
f = \Left v -> v
f x
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This pattern does not cover all the possibilities:
10│ f = \Left v -> v
^^^^^^
Other possibilities include:
Right _
I would have to crash if I saw one of those! So rather than pattern
matching in function arguments, put a `when` in the function body to
account for all possibilities.
"###
);
test_report!(
patterns_let_not_exhaustive,
indoc!(
r#"
x : [Left {}, Right Str]
x = Left {}
(Left y) = x
y
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression is used in an unexpected way:
8│ (Left y) = x
^
This `x` value is a:
[Left {}, Right Str]
But you are trying to use it as:
[Left a]
Tip: Looks like a closed tag union does not have the `Right` tag.
Tip: Closed tag unions can't grow, because that might change the size
in memory. Can you use an open tag union?
"###
);
test_report!(
patterns_when_not_exhaustive,
indoc!(
r#"
when 0x1 is
2 -> 0x3
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
4│> when 0x1 is
5│> 2 -> 0x3
Other possibilities include:
_
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
patterns_bool_not_exhaustive,
indoc!(
r#"
x : [Red, Green]
x = Green
when x is
Red -> 3
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
7│> when x is
8│> Red -> 3
Other possibilities include:
Green
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
patterns_enum_not_exhaustive,
indoc!(
r#"
x : [Red, Green, Blue]
x = Red
when x is
Red -> 0
Green -> 1
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
7│> when x is
8│> Red -> 0
9│> Green -> 1
Other possibilities include:
Blue
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
patterns_remote_data_not_exhaustive,
indoc!(
r#"
RemoteData e a : [NotAsked, Loading, Failure e, Success a]
x : RemoteData Num.I64 Str
when x is
NotAsked -> 3
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
8│> when x is
9│> NotAsked -> 3
Other possibilities include:
Failure _
Loading
Success _
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
patterns_record_not_exhaustive,
indoc!(
r#"
x = { a: 3 }
when x is
{ a: 4 } -> 4
"#
),
// Tip: Looks like a record field guard is not exhaustive. Learn more about record pattern matches at TODO.
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
6│> when x is
7│> { a: 4 } -> 4
Other possibilities include:
{ a }
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
patterns_record_guard_not_exhaustive,
indoc!(
r#"
y : [Nothing, Just Num.I64]
y = Just 4
x = { a: y, b: 42}
when x is
{ a: Nothing } -> 4
{ a: Just 3 } -> 4
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
8│> when x is
9│> { a: Nothing } -> 4
10│> { a: Just 3 } -> 4
Other possibilities include:
{ a: Just _ }
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
patterns_nested_tag_not_exhaustive,
indoc!(
r#"
when Record Nothing 1 is
Record (Nothing) b -> b
Record (Just 3) b -> b
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
4│> when Record Nothing 1 is
5│> Record (Nothing) b -> b
6│> Record (Just 3) b -> b
Other possibilities include:
Record (Just _) _
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
patterns_int_redundant,
indoc!(
r#"
when 0x1 is
2 -> 3
2 -> 4
_ -> 5
"#
),
@r###"
── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─
The 2nd pattern is redundant:
4│ when 0x1 is
5│ 2 -> 3
6│> 2 -> 4
7│ _ -> 5
Any value of this shape will be handled by a previous pattern, so this
one should be removed.
"###
);
test_report!(
unify_alias_other,
indoc!(
r#"
Foo a : { x : Num.Int a }
f : Foo a -> Num.Int a
f = \r -> r.x
f { y: 3.14 }
"#
),
// de-aliases the alias to give a better error message
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `f` has an unexpected type:
9│ f { y: 3.14 }
^^^^^^^^^^^
The argument is a record of type:
{ y : Frac a }
But `f` needs its 1st argument to be:
{ x : Int a }
Tip: Seems like a record field typo. Maybe `y` should be `x`?
Tip: 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_report!(
#[ignore]
cyclic_alias,
indoc!(
r#"
Foo : { x : Bar }
Bar : { y : Foo }
f : Foo
f
"#
),
// should not report Bar as unused!
@r###"
── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─
The `Foo` alias is self-recursive in an invalid way:
4│ Foo : { x : Bar }
^^^
Recursion in aliases is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
"###
);
test_report!(
self_recursive_alias,
indoc!(
r#"
Foo : { x : Foo }
f : Foo
f = 3
f
"#
),
// should not report Bar as unused!
@r###"
── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─
The `Foo` alias is self-recursive in an invalid way:
4│ Foo : { x : Foo }
^^^
Recursion in aliases is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
"###
);
test_report!(
record_duplicate_field_same_type,
indoc!(
r#"
{ x: 4, y: 3, x: 4 }
"#
),
@r###"
── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─
This record defines the `.x` field twice!
4│ { x: 4, y: 3, x: 4 }
^^^^ ^^^^
In the rest of the program, I will only use the latter definition:
4│ { x: 4, y: 3, x: 4 }
^^^^
For clarity, remove the previous `.x` definitions from this record.
"###
);
test_report!(
record_duplicate_field_different_types,
indoc!(
r#"
{ x: 4, y: 3, x: "foo" }
"#
),
@r###"
── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─
This record defines the `.x` field twice!
4│ { x: 4, y: 3, x: "foo" }
^^^^ ^^^^^^^^
In the rest of the program, I will only use the latter definition:
4│ { x: 4, y: 3, x: "foo" }
^^^^^^^^
For clarity, remove the previous `.x` definitions from this record.
"###
);
test_report!(
record_duplicate_field_multiline,
indoc!(
r#"
{
x: 4,
y: 3,
x: "foo"
}
"#
),
@r###"
── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─
This record defines the `.x` field twice!
4│ {
5│> x: 4,
6│ y: 3,
7│> x: "foo"
8│ }
In the rest of the program, I will only use the latter definition:
4│ {
5│ x: 4,
6│ y: 3,
7│> x: "foo"
8│ }
For clarity, remove the previous `.x` definitions from this record.
"###
);
test_report!(
record_update_duplicate_field_multiline,
indoc!(
r#"
\r ->
{ r &
x: 4,
y: 3,
x: "foo"
}
"#
),
@r###"
── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─
This record defines the `.x` field twice!
5│ { r &
6│> x: 4,
7│ y: 3,
8│> x: "foo"
9│ }
In the rest of the program, I will only use the latter definition:
5│ { r &
6│ x: 4,
7│ y: 3,
8│> x: "foo"
9│ }
For clarity, remove the previous `.x` definitions from this record.
"###
);
test_report!(
record_type_duplicate_field,
indoc!(
r#"
a : { foo : Num.I64, bar : {}, foo : Str }
a = { bar: {}, foo: "foo" }
a
"#
),
@r###"
── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─
This record type defines the `.foo` field twice!
4│ a : { foo : Num.I64, bar : {}, foo : Str }
^^^^^^^^^^^^^ ^^^^^^^^^
In the rest of the program, I will only use the latter definition:
4│ a : { foo : Num.I64, bar : {}, foo : Str }
^^^^^^^^^
For clarity, remove the previous `.foo` definitions from this record
type.
"###
);
test_report!(
tag_union_duplicate_tag,
indoc!(
r#"
a : [Foo Num.I64, Bar {}, Foo Str]
a = Foo "foo"
a
"#
),
@r###"
── DUPLICATE TAG NAME ──────────────────────────────────── /code/proj/Main.roc ─
This tag union type defines the `Foo` tag twice!
4│ a : [Foo Num.I64, Bar {}, Foo Str]
^^^^^^^^^^^ ^^^^^^^
In the rest of the program, I will only use the latter definition:
4│ a : [Foo Num.I64, Bar {}, Foo Str]
^^^^^^^
For clarity, remove the previous `Foo` definitions from this tag union
type.
"###
);
test_report!(
annotation_definition_mismatch,
indoc!(
r#"
bar : Num.I64
foo = \x -> x
# NOTE: neither bar or foo are defined at this point
4
"#
),
@r###"
── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This annotation does not match the definition immediately following
it:
4│> bar : Num.I64
5│> foo = \x -> x
Is it a typo? If not, put either a newline or comment between them.
"###
);
test_report!(
annotation_newline_body_is_fine,
indoc!(
r#"
bar : Num.I64
foo = \x -> x
foo bar
"#
),
@""
);
test_report!(
invalid_alias_rigid_var_pattern,
indoc!(
r#"
MyAlias 1 : Num.I64
4
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This definition of `MyAlias` has an unexpected pattern:
4│ MyAlias 1 : Num.I64
^
Only type variables like `a` or `value` can occur in this position.
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`MyAlias` is not used anywhere in your code.
4│ MyAlias 1 : Num.I64
^^^^^^^^^^^^^^^^^^^
If you didn't intend on using `MyAlias` then remove it so future readers
of your code don't wonder why it is there.
"###
);
test_report!(
invalid_opaque_rigid_var_pattern,
indoc!(
r#"
Age 1 := Num.I64
a : Age
a
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This definition of `Age` has an unexpected pattern:
4│ Age 1 := Num.I64
^
Only type variables like `a` or `value` can occur in this position.
"###
);
test_report!(
invalid_num,
indoc!(
r#"
a : Num.Num Num.I64 Num.F64
a = 3
a
"#
),
@r###"
── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─
The `Num` alias expects 1 type argument, but it got 2 instead:
4│ a : Num.Num Num.I64 Num.F64
^^^^^^^^^^^^^^^^^^^^^^^
Are there missing parentheses?
"###
);
test_report!(
invalid_num_fn,
indoc!(
r#"
f : Str -> Num.Num Num.I64 Num.F64
f = \_ -> 3
f
"#
),
@r###"
── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─
The `Num` alias expects 1 type argument, but it got 2 instead:
4│ f : Str -> Num.Num Num.I64 Num.F64
^^^^^^^^^^^^^^^^^^^^^^^
Are there missing parentheses?
"###
);
test_report!(
too_few_type_arguments,
indoc!(
r#"
Pair a b : [Pair a b]
x : Pair Num.I64
x = Pair 2 3
x
"#
),
@r###"
── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─
The `Pair` alias expects 2 type arguments, but it got 1 instead:
6│ x : Pair Num.I64
^^^^^^^^^^^^
Are there missing parentheses?
"###
);
test_report!(
too_many_type_arguments,
indoc!(
r#"
Pair a b : [Pair a b]
x : Pair Num.I64 Num.I64 Num.I64
x = 3
x
"#
),
@r###"
── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─
The `Pair` alias expects 2 type arguments, but it got 3 instead:
6│ x : Pair Num.I64 Num.I64 Num.I64
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Are there missing parentheses?
"###
);
test_report!(
phantom_type_variable,
indoc!(
r#"
Foo a : [Foo]
f : Foo Num.I64
f
"#
),
@r###"
── UNUSED TYPE ALIAS PARAMETER ─────────────────────────── /code/proj/Main.roc ─
The `a` type parameter is not used in the `Foo` alias definition:
4│ Foo a : [Foo]
^
Roc does not allow unused type alias parameters!
Tip: If you want an unused type parameter (a so-called "phantom
type"), read the guide section on phantom values.
"###
);
test_report!(
elm_function_syntax,
indoc!(
r#"
f x y = x
"#
),
@r###"
── ARGUMENTS BEFORE EQUALS ────────────────── tmp/elm_function_syntax/Test.roc ─
I am partway through parsing a definition, but I got stuck here:
1│ app "test" provides [main] to "./platform"
2│
3│ main =
4│ f x y = x
^^^
Looks like you are trying to define a function. In roc, functions are
always written as a lambda, like increment = \n -> n + 1.
"###
);
test_report!(
two_different_cons,
indoc!(
r#"
ConsList a : [Cons a (ConsList a), Nil]
x : ConsList {}
x = Cons {} (Cons "foo" Nil)
x
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `x` definition:
6│ x : ConsList {}
7│ x = Cons {} (Cons "foo" Nil)
^^^^^^^^^^^^^^^^^^^^^^^^
This `Cons` tag application has the type:
[Cons {} [Cons Str [Cons {} a, Nil] as a, Nil], Nil]
But the type annotation on `x` says it should be:
[Cons {} a, Nil] as a
"###
);
test_report!(
mutually_recursive_types_with_type_error,
indoc!(
r#"
AList a b : [ACons a (BList a b), ANil]
BList a b : [BCons a (AList a b), BNil]
x : AList Num.I64 Num.I64
x = ACons 0 (BCons 1 (ACons "foo" BNil ))
y : BList a a
y = BNil
{ x, y }
"#
),
// TODO render tag unions across multiple lines
// TODO do not show recursion var if the recursion var does not render on the surface of a type
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `x` definition:
7│ x : AList Num.I64 Num.I64
8│ x = ACons 0 (BCons 1 (ACons "foo" BNil ))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `ACons` tag application has the type:
[ACons (Int Signed64) [BCons (Int Signed64) [ACons Str [BCons I64 [ACons I64 (BList I64 I64),
ANil] as ∞, BNil], ANil], BNil], ANil]
But the type annotation on `x` says it should be:
[ACons I64 (BList I64 I64), ANil] as a
"###
);
test_report!(
integer_out_of_range,
indoc!(
r#"
x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000
y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000
h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728
maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455
x + y + h + l + minlit + maxlit
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This integer literal is too big:
4│ x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The largest number representable in Roc is the maximum U128 value,
340_282_366_920_938_463_463_374_607_431_768_211_455.
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This integer literal is too small:
6│ y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The smallest number representable in Roc is the minimum I128 value,
-170_141_183_460_469_231_731_687_303_715_884_105_728.
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This integer literal is too big:
8│ h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The largest number representable in Roc is the maximum U128 value,
340_282_366_920_938_463_463_374_607_431_768_211_455.
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This integer literal is too small:
9│ l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The smallest number representable in Roc is the minimum I128 value,
-170_141_183_460_469_231_731_687_303_715_884_105_728.
Tip: Learn more about number literals at TODO
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `add` has an unexpected type:
14│ x + y + h + l + minlit + maxlit
^^^^^^
This `maxlit` value is a:
U128
But `add` needs its 2nd argument to be:
I128 or Dec
"###
);
// have to deal with some whitespace issues because of the format! macro
test_report!(
float_out_of_range,
indoc!(
r#"
overflow = 11.7976931348623157e308
underflow = -11.7976931348623157e308
overflow + underflow
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This float literal is too big:
4│ overflow = 11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit floating points, allowing values between
-1.7976931348623157e308 and 1.7976931348623157e308
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This float literal is too small:
5│ underflow = -11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit floating points, allowing values between
-1.7976931348623157e308 and 1.7976931348623157e308
Tip: Learn more about number literals at TODO
"###
);
// the generated messages here are incorrect. Waiting for a rust nightly feature to land,
// see https://github.com/rust-lang/rust/issues/22639
// this test is here to spot regressions in error reporting
test_report!(
integer_malformed,
indoc!(
r#"
dec = 100A
hex = 0xZZZ
oct = 0o9
bin = 0b2
dec + hex + oct + bin
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This integer literal contains an invalid digit:
4│ dec = 100A
^^^^
Integer literals can only contain the digits
0-9, or have an integer suffix.
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This hex integer literal contains an invalid digit:
6│ hex = 0xZZZ
^^^^^
Hexadecimal (base-16) integer literals can only contain the digits
0-9, a-f and A-F, or have an integer suffix.
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This octal integer literal contains an invalid digit:
8│ oct = 0o9
^^^
Octal (base-8) integer literals can only contain the digits
0-7, or have an integer suffix.
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This binary integer literal contains an invalid digit:
10│ bin = 0b2
^^^
Binary (base-2) integer literals can only contain the digits
0 and 1, or have an integer suffix.
Tip: Learn more about number literals at TODO
"###
);
test_report!(
integer_empty,
indoc!(
r#"
dec = 20
hex = 0x
oct = 0o
bin = 0b
dec + hex + oct + bin
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This hex integer literal contains no digits:
6│ hex = 0x
^^
Hexadecimal (base-16) integer literals must contain at least one of
the digits 0-9, a-f and A-F, or have an integer suffix.
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This octal integer literal contains no digits:
8│ oct = 0o
^^
Octal (base-8) integer literals must contain at least one of the
digits 0-7, or have an integer suffix.
Tip: Learn more about number literals at TODO
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This binary integer literal contains no digits:
10│ bin = 0b
^^
Binary (base-2) integer literals must contain at least one of the
digits 0 and 1, or have an integer suffix.
Tip: Learn more about number literals at TODO
"###
);
test_report!(
float_malformed,
indoc!(
r#"
x = 3.0A
x
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This float literal contains an invalid digit:
4│ x = 3.0A
^^^^
Floating point literals can only contain the digits 0-9, or use
scientific notation 10e4, or have a float suffix.
Tip: Learn more about number literals at TODO
"###
);
test_report!(
invalid_record_update,
indoc!(
r#"
foo = { bar: 3 }
updateNestedRecord = { foo.bar & x: 4 }
example = { age: 42 }
# these should work
y = { Test.example & age: 3 }
x = { example & age: 4 }
{ updateNestedRecord, foo, x, y }
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This expression cannot be updated:
5│ updateNestedRecord = { foo.bar & x: 4 }
^^^^^^^
Only variables can be updated with record update syntax.
── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─
The `Test` module is not imported:
10│ y = { Test.example & age: 3 }
^^^^^^^^^^^^
Is there an import missing? Perhaps there is a typo. Did you mean one
of these?
List
Set
Dict
Result
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This expression cannot be updated:
10│ y = { Test.example & age: 3 }
^^^^^^^^^^^^
Only variables can be updated with record update syntax.
"###
);
test_report!(
module_not_imported,
indoc!(
r#"
Foo.test
"#
),
@r###"
── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─
The `Foo` module is not imported:
4│ Foo.test
^^^^^^^^
Is there an import missing? Perhaps there is a typo. Did you mean one
of these?
Box
Bool
Num
Set
"###
);
test_report!(
optional_record_default_type_error,
indoc!(
r#"
\{ x, y ? True } -> x + y
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `add` has an unexpected type:
4│ \{ x, y ? True } -> x + y
^
This `y` value is a:
[True]a
But `add` needs its 2nd argument to be:
Num a
"###
);
test_report!(
optional_record_default_with_signature,
indoc!(
r#"
f : { x : Num.I64, y ? Num.I64 } -> Num.I64
f = \{ x, y ? "foo" } -> (\g, _ -> g) x y
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 1st argument to `f` is weird:
5│ f = \{ x, y ? "foo" } -> (\g, _ -> g) x y
^^^^^^^^^^^^^^^^
The argument is a pattern that matches record values of type:
{ x : I64, y ? Str }
But the annotation on `f` says the 1st argument should be:
{ x : I64, y ? I64 }
"###
);
test_report!(
optional_record_invalid_let_binding,
indoc!(
r#"
\rec ->
{ x, y } : { x : Num.I64, y ? Str }
{ x, y } = rec
{ x, y }
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of this definition:
5│> { x, y } : { x : Num.I64, y ? Str }
6│> { x, y } = rec
The body is a value of type:
{ x : I64, y : Str }
But the type annotation says it should be:
{ x : I64, y ? Str }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
"###
);
test_report!(
optional_record_invalid_function,
indoc!(
r#"
f : { x : Num.I64, y ? Num.I64 } -> Num.I64
f = \{ x, y } -> x + y
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 1st argument to `f` is weird:
5│ f = \{ x, y } -> x + y
^^^^^^^^
The argument is a pattern that matches record values of type:
{ x : I64, y : I64 }
But the annotation on `f` says the 1st argument should be:
{ x : I64, y ? I64 }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
"###
);
test_report!(
optional_record_invalid_when,
indoc!(
r#"
f : { x : Num.I64, y ? Num.I64 } -> Num.I64
f = \r ->
when r is
{ x, y } -> x + y
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
6│> when r is
7│ { x, y } -> x + y
This `r` value is a:
{ x : I64, y ? I64 }
But the branch patterns have type:
{ x : I64, y : I64 }
The branches must be cases of the `when` condition's type!
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
"###
);
test_report!(
optional_record_invalid_access,
indoc!(
r#"
f : { x : Num.I64, y ? Num.I64 } -> Num.I64
f = \r -> r.y
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression is used in an unexpected way:
5│ f = \r -> r.y
^^^
This `r` value is a:
{ x : I64, y ? I64 }
But you are trying to use it as:
{ x : I64, y : I64 }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
"###
);
test_report!(
optional_record_invalid_accessor,
indoc!(
r#"
f : { x : Num.I64, y ? Num.I64 } -> Num.I64
f = \r -> .y r
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to this function has an unexpected type:
5│ f = \r -> .y r
^
This `r` value is a:
{ x : I64, y ? I64 }
But this function needs its 1st argument to be:
{ x : I64, y : I64 }
Tip: To extract the `.y` field it must be non-optional, but the type
says this field is optional. Learn more about optional fields at TODO.
"###
);
test_report!(
guard_mismatch_with_annotation,
indoc!(
r#"
f : { x : Num.I64, y : Num.I64 } -> Num.I64
f = \r ->
when r is
{ x, y : "foo" } -> x + 0
_ -> 0
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
6│> when r is
7│ { x, y : "foo" } -> x + 0
8│ _ -> 0
This `r` value is a:
{ x : I64, y : I64 }
But the branch patterns have type:
{ x : I64, y : Str }
The branches must be cases of the `when` condition's type!
"###
);
test_report!(
optional_field_mismatch_with_annotation,
indoc!(
r#"
f : { x : Num.I64, y ? Num.I64 } -> Num.I64
f = \r ->
when r is
{ x, y ? "foo" } -> (\g, _ -> g) x y
_ -> 0
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
6│> when r is
7│ { x, y ? "foo" } -> (\g, _ -> g) x y
8│ _ -> 0
This `r` value is a:
{ x : I64, y ? I64 }
But the branch patterns have type:
{ x : I64, y ? Str }
The branches must be cases of the `when` condition's type!
"###
);
test_report!(
incorrect_optional_field,
indoc!(
r#"
{ x: 5, y ? 42 }
"#
),
@r###"
── BAD OPTIONAL VALUE ──────────────────────────────────── /code/proj/Main.roc ─
This record uses an optional value for the `.y` field in an incorrect
context!
4│ { x: 5, y ? 42 }
^^^^^^
You can only use optional values in record destructuring, like:
{ answer ? 42, otherField } = myRecord
"###
);
test_report!(
first_wildcard_is_required,
indoc!(
r#"
when Foo 1 2 3 is
Foo _ 1 _ -> 1
_ -> 2
"#
),
@""
);
test_report!(
second_wildcard_is_redundant,
indoc!(
r#"
when Foo 1 2 3 is
Foo _ 1 _ -> 1
_ -> 2
_ -> 3
"#
),
@r###"
── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─
The 3rd pattern is redundant:
4│ when Foo 1 2 3 is
5│ Foo _ 1 _ -> 1
6│ _ -> 2
7│ _ -> 3
^
Any value of this shape will be handled by a previous pattern, so this
one should be removed.
"###
);
test_report!(
alias_using_alias,
indoc!(
r#"
# 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
empty
"#
),
@""
);
test_report!(
unused_argument,
indoc!(
r#"
f = \foo -> 1
f
"#
),
@r###"
── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─
`f` doesn't use `foo`.
4│ f = \foo -> 1
^^^
If you don't need `foo`, then you can just remove it. However, if you
really do need `foo` as an argument of `f`, prefix it with an underscore,
like this: "_`foo`". Adding an underscore at the start of a variable
name is a way of saying that the variable is not used.
"###
);
test_report!(
qualified_tag,
indoc!(
r#"
Foo.Bar
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am trying to parse a qualified name here:
4│ Foo.Bar
^
This looks like a qualified tag name to me, but tags cannot be
qualified! Maybe you wanted a qualified name, something like
Json.Decode.string?
"###
);
test_report!(
module_ident_ends_with_dot,
indoc!(
r#"
Foo.Bar.
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am trying to parse a qualified name here:
4│ Foo.Bar.
^
I was expecting to see an identifier next, like height. A complete
qualified name looks something like Json.Decode.string.
"###
);
test_report!(
record_access_ends_with_dot,
indoc!(
r#"
foo.bar.
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I trying to parse a record field access here:
4│ foo.bar.
^
So I expect to see a lowercase letter next, like .name or .height.
"###
);
test_report!(
type_annotation_double_colon,
indoc!(
r#"
f :: I64
f = 42
f
"#
),
@r###"
── UNKNOWN OPERATOR ──────────────── tmp/type_annotation_double_colon/Test.roc ─
This looks like an operator, but it's not one I recognize!
1│ app "test" provides [main] to "./platform"
2│
3│ main =
4│ f :: I64
^^
I have no specific suggestion for this operator, see TODO for the full
list of operators in Roc.
"###
);
// NOTE: VERY BAD ERROR MESSAGE
//
// looks like `x y` are considered argument to the add, even though they are
// on a lower indentation level
test_report!(
double_equals_in_def,
indoc!(
r#"
x = 3
y =
x == 5
Num.add 1 2
{ x, y }
"#
),
@r###"
── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─
This value is not a function, but it was given 3 arguments:
6│ x == 5
^
Are there any missing commas? Or missing parentheses?
"###
);
test_report!(
tag_union_open,
indoc!(
r#"
f : [
"#
),
@r###"
── UNFINISHED TAG UNION TYPE ───────────────────── tmp/tag_union_open/Test.roc ─
I am partway through parsing a tag union type, but I got stuck here:
4│ f : [
^
I was expecting to see a closing square bracket before this, so try
adding a ] and see if that helps?
Note: I may be confused by indentation
"###
);
test_report!(
tag_union_end,
indoc!(
r#"
f : [Yes,
"#
),
@r###"
── UNFINISHED TAG UNION TYPE ────────────────────── tmp/tag_union_end/Test.roc ─
I am partway through parsing a tag union type, but I got stuck here:
4│ f : [Yes,
5│
6│
^
I was expecting to see a closing square bracket before this, so try
adding a ] and see if that helps?
"###
);
test_report!(
tag_union_lowercase_tag_name,
indoc!(
r#"
f : [lowercase]
"#
),
@r###"
── WEIRD TAG NAME ────────────────── tmp/tag_union_lowercase_tag_name/Test.roc ─
I am partway through parsing a tag union type, but I got stuck here:
4│ f : [lowercase]
^
I was expecting to see a tag name.
Hint: Tag names start with an uppercase letter, like Err or Green.
"###
);
test_report!(
tag_union_second_lowercase_tag_name,
indoc!(
r#"
f : [Good, bad]
"#
),
@r###"
── WEIRD TAG NAME ─────────── tmp/tag_union_second_lowercase_tag_name/Test.roc ─
I am partway through parsing a tag union type, but I got stuck here:
4│ f : [Good, bad]
^
I was expecting to see a tag name.
Hint: Tag names start with an uppercase letter, like Err or Green.
"###
);
test_report!(
record_type_open,
indoc!(
r#"
f : {
"#
),
@r###"
── UNFINISHED RECORD TYPE ────────────────────── tmp/record_type_open/Test.roc ─
I am partway through parsing a record type, but I got stuck here:
4│ f : {
^
I was expecting to see a closing curly brace before this, so try
adding a } and see if that helps?
Note: I may be confused by indentation
"###
);
test_report!(
record_type_open_indent,
indoc!(
r#"
f : {
foo : I64,
"#
),
@r###"
── UNFINISHED RECORD TYPE ─────────────── tmp/record_type_open_indent/Test.roc ─
I am partway through parsing a record type, but I got stuck here:
4│ f : {
^
I was expecting to see a closing curly brace before this, so try
adding a } and see if that helps?
Note: I may be confused by indentation
"###
);
test_report!(
record_type_end,
indoc!(
r#"
f : { a: Int,
"#
),
@r###"
── UNFINISHED RECORD TYPE ─────────────────────── tmp/record_type_end/Test.roc ─
I am partway through parsing a record type, but I got stuck here:
4│ f : { a: Int,
5│
6│
^
I was expecting to see a closing curly brace before this, so try
adding a } and see if that helps?
"###
);
test_report!(
record_type_keyword_field_name,
indoc!(
r#"
f : { if : I64 }
"#
),
@r###"
── UNFINISHED RECORD TYPE ──────── tmp/record_type_keyword_field_name/Test.roc ─
I just started parsing a record type, but I got stuck on this field
name:
4│ f : { if : I64 }
^^
Looks like you are trying to use `if` as a field name, but that is a
reserved word. Try using a different name!
"###
);
// a case where the message cannot be as good as elm's
test_report!(
record_type_missing_comma,
indoc!(
r#"
f : { foo bar }
"#
),
@r###"
── UNFINISHED RECORD TYPE ───────────── tmp/record_type_missing_comma/Test.roc ─
I am partway through parsing a record type, but I got stuck here:
4│ f : { foo bar }
^
I was expecting to see a colon, question mark, comma or closing curly
brace.
"###
);
// a case where the message cannot be as good as elm's
test_report!(
record_type_tab,
"f : { foo \t }",
@r###"
── TAB CHARACTER ──────────────────────────────── tmp/record_type_tab/Test.roc ─
I encountered a tab character
4│ f : { foo }
^
Tab characters are not allowed.
"###
);
test_report!(
comment_with_tab,
"# comment with a \t\n4",
@r###"
── TAB CHARACTER ─────────────────────────────── tmp/comment_with_tab/Test.roc ─
I encountered a tab character
4│ # comment with a
^
Tab characters are not allowed.
"###
);
// TODO bad error message
test_report!(
type_in_parens_start,
indoc!(
r#"
f : (
"#
),
@r###"
── UNFINISHED PARENTHESES ────────────────── tmp/type_in_parens_start/Test.roc ─
I just started parsing a type in parentheses, but I got stuck here:
4│ f : (
^
Tag unions look like [Many I64, None], so I was expecting to see a tag
name next.
Note: I may be confused by indentation
"###
);
test_report!(
type_in_parens_end,
indoc!(
r#"
f : ( I64
"#
),
@r###"
── UNFINISHED PARENTHESES ──────────────────── tmp/type_in_parens_end/Test.roc ─
I am partway through parsing a type in parentheses, but I got stuck
here:
4│ f : ( I64
^
I was expecting to see a parenthesis before this, so try adding a )
and see if that helps?
Note: I may be confused by indentation
"###
);
test_report!(
type_apply_double_dot,
indoc!(
r#"
f : Foo..Bar
f
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am confused by this type name:
4│ f : Foo..Bar
^^^^^^^^
Type names start with an uppercase letter, and can optionally be
qualified by a module name, like Bool or Http.Request.Request.
"###
);
// ── DOUBLE DOT ──────────────────────────────────────────────────────────────────
//
// I encountered two dots in a row:
//
// 1│ f : Foo..Bar
// ^
//
// Try removing one of them.
test_report!(
type_apply_trailing_dot,
indoc!(
r#"
f : Foo.Bar.
f
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am confused by this type name:
4│ f : Foo.Bar.
^^^^^^^^
Type names start with an uppercase letter, and can optionally be
qualified by a module name, like Bool or Http.Request.Request.
"###
);
// ── TRAILING DOT ────────────────────────────────────────────────────────────────
//
// I encountered a dot with nothing after it:
//
// 1│ f : Foo.Bar.
// ^
//
// Dots are used to refer to a type in a qualified way, like
// Num.I64 or List.List a. Try adding a type name next.
test_report!(
type_apply_stray_dot,
indoc!(
r#"
f : .
"#
),
@r###"
── UNFINISHED TYPE ───────────────────────── tmp/type_apply_stray_dot/Test.roc ─
I just started parsing a type, but I got stuck here:
4│ f : .
^
I am expecting a type next, like Bool or List a.
"###
);
test_report!(
type_apply_start_with_number,
indoc!(
r#"
f : Foo.1
f
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am confused by this type name:
4│ f : Foo.1
^^^^^
Type names start with an uppercase letter, and can optionally be
qualified by a module name, like Bool or Http.Request.Request.
"###
);
// ── WEIRD QUALIFIED NAME ────────────────────────────────────────────────────────
//
// I encountered a number at the start of a qualified name segment:
//
// 1│ f : Foo.1
// ^
//
// All parts of a qualified type name must start with an uppercase
// letter, like Num.I64 or List.List a.
test_report!(
type_apply_start_with_lowercase,
indoc!(
r#"
f : Foo.foo
f
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am confused by this type name:
4│ f : Foo.foo
^^^^^^^
Type names start with an uppercase letter, and can optionally be
qualified by a module name, like Bool or Http.Request.Request.
"###
);
test_report!(
def_missing_final_expression,
indoc!(
r#"
f : Foo.foo
"#
),
@r###"
── MISSING FINAL EXPRESSION ──────── tmp/def_missing_final_expression/Test.roc ─
I am partway through parsing a definition, but I got stuck here:
1│ app "test" provides [main] to "./platform"
2│
3│ main =
4│ f : Foo.foo
^
This definition is missing a final expression. A nested definition
must be followed by either another definition, or an expression
x = 4
y = 2
x + y
"###
);
test_report!(
type_inline_alias,
indoc!(
r#"
f : I64 as
f = 0
f
"#
),
@r###"
── UNFINISHED INLINE ALIAS ──────────────────── tmp/type_inline_alias/Test.roc ─
I just started parsing an inline type alias, but I got stuck here:
4│ f : I64 as
^
Note: I may be confused by indentation
"###
);
test_report!(
type_double_comma,
indoc!(
r#"
f : I64,,I64 -> I64
f = 0
f
"#
),
@r###"
── DOUBLE COMMA ─────────────────────────────── tmp/type_double_comma/Test.roc ─
I just started parsing a function argument type, but I encountered two
commas in a row:
4│ f : I64,,I64 -> I64
^
Try removing one of them.
"###
);
test_report!(
type_argument_no_arrow,
indoc!(
r#"
f : I64, I64
f = 0
f
"#
),
@r###"
── UNFINISHED TYPE ─────────────────────── tmp/type_argument_no_arrow/Test.roc ─
I am partway through parsing a type, but I got stuck here:
4│ f : I64, I64
^
Note: I may be confused by indentation
"###
);
// TODO could do better by pointing out we're parsing a function type
test_report!(
type_argument_arrow_then_nothing,
indoc!(
r#"
f : I64, I64 ->
f = 0
f
"#
),
@r###"
── UNFINISHED TYPE ───────────── tmp/type_argument_arrow_then_nothing/Test.roc ─
I just started parsing a type, but I got stuck here:
4│ f : I64, I64 ->
^
Note: I may be confused by indentation
"###
);
// TODO could do better by pointing out we're parsing a function type
test_report!(
dict_type_formatting,
indoc!(
r#"
app "dict" imports [ Dict ] provides [main] to "./platform"
myDict : Dict.Dict Num.I64 Str
myDict = Dict.insert Dict.empty "foo" 42
main = myDict
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `myDict` definition:
3│ myDict : Dict.Dict Num.I64 Str
4│ myDict = Dict.insert Dict.empty "foo" 42
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `insert` call produces:
Dict Str (Num a)
But the type annotation on `myDict` says it should be:
Dict I64 Str
"###
);
test_report!(
alias_type_diff,
indoc!(
r#"
app "test" imports [Set.{ Set }] provides [main] to "./platform"
HSet a : Set a
foo : Str -> HSet {}
myDict : HSet Str
myDict = foo "bar"
main = myDict
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `myDict` definition:
7│ myDict : HSet Str
8│ myDict = foo "bar"
^^^^^^^^^
This `foo` call produces:
HSet {}
But the type annotation on `myDict` says it should be:
HSet Str
"###
);
// this should get better with time
test_report!(
if_guard_without_condition,
indoc!(
r#"
when Just 4 is
Just if ->
4
_ ->
2
"#
),
@r###"
── IF GUARD NO CONDITION ───────────── tmp/if_guard_without_condition/Test.roc ─
I just started parsing an if guard, but there is no guard condition:
4│ when Just 4 is
5│ Just if ->
^
Try adding an expression before the arrow!
"###
);
test_report!(
empty_or_pattern,
indoc!(
r#"
when Just 4 is
Just 4 | ->
4
_ ->
2
"#
),
@r###"
── UNFINISHED PATTERN ────────────────────────── tmp/empty_or_pattern/Test.roc ─
I just started parsing a pattern, but I got stuck here:
5│ Just 4 | ->
^
Note: I may be confused by indentation
"###
);
// TODO check if "what_is_next" is a keyword
test_report!(
pattern_binds_keyword,
indoc!(
r#"
when Just 4 is
Just when ->
4
_ ->
2
"#
),
@r###"
── MISSING EXPRESSION ───────────────────── tmp/pattern_binds_keyword/Test.roc ─
I am partway through parsing a `when` expression, but I got stuck here:
5│ Just when ->
^
I was expecting to see an expression like 42 or "hello".
"###
);
// this should get better with time
test_report!(
when_missing_arrow,
indoc!(
r#"
when 5 is
1 -> 2
_
"#
),
@r###"
── UNFINISHED WHEN ─────────────────────────── tmp/when_missing_arrow/Test.roc ─
I was partway through parsing a `when` expression, but I got stuck here:
6│ _
^
I was expecting to see a pattern next
Note: Here is an example of a valid `when` expression for reference.
when List.first plants is
Ok n ->
n
Err _ ->
200
Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important!
"###
);
test_report!(
lambda_double_comma,
indoc!(
r#"
\a,,b -> 1
"#
),
@r###"
── UNFINISHED ARGUMENT LIST ───────────────── tmp/lambda_double_comma/Test.roc ─
I am partway through parsing a function argument list, but I got stuck
at this comma:
4│ \a,,b -> 1
^
I was expecting an argument pattern before this, so try adding an
argument before the comma and see if that helps?
"###
);
test_report!(
lambda_leading_comma,
indoc!(
r#"
\,b -> 1
"#
),
@r###"
── UNFINISHED ARGUMENT LIST ──────────────── tmp/lambda_leading_comma/Test.roc ─
I am partway through parsing a function argument list, but I got stuck
at this comma:
4│ \,b -> 1
^
I was expecting an argument pattern before this, so try adding an
argument before the comma and see if that helps?
"###
);
// this should get better with time
// TODO this formerly gave
//
// ── UNFINISHED WHEN ─────────────────────────────────────────────────────────────
//
// I was partway through parsing a `when` expression, but I got stuck here:
//
// 3│ _ -> 2
// ^
//
// I suspect this is a pattern that is not indented enough? (by 2 spaces)
//
// but that requires parsing the next pattern blindly, irrespective of indentation. Can
// we find an efficient solution that doesn't require parsing an extra pattern for
// every `when`, i.e. we want a good error message for the test case above, but for
// a valid `when`, we don't want to do extra work, e.g. here
//
// x
// when x is
// n -> n
//
// 4
//
// We don't want to parse the `4` and say it's an outdented pattern!
test_report!(
when_outdented_branch,
indoc!(
r#"
when 4 is
5 -> 2
2 -> 2
"#
),
@r###"
── NOT END OF FILE ──────────────────────── tmp/when_outdented_branch/Test.roc ─
I expected to reach the end of the file, but got stuck here:
6│ 2 -> 2
^
"###
);
test_report!(
when_over_indented_underscore,
indoc!(
r#"
when 4 is
5 -> 2
_ -> 2
"#
),
@r###"
── UNEXPECTED ARROW ─────────────── tmp/when_over_indented_underscore/Test.roc ─
I am parsing a `when` expression right now, but this arrow is confusing
me:
6│ _ -> 2
^^
It makes sense to see arrows around here, so I suspect it is something
earlier.Maybe this pattern is indented a bit farther from the previous
patterns?
Note: Here is an example of a valid `when` expression for reference.
when List.first plants is
Ok n ->
n
Err _ ->
200
Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important!
"###
);
test_report!(
when_over_indented_int,
indoc!(
r#"
when 4 is
5 -> Num.neg
2 -> 2
"#
),
@r###"
── UNEXPECTED ARROW ────────────────────── tmp/when_over_indented_int/Test.roc ─
I am parsing a `when` expression right now, but this arrow is confusing
me:
6│ 2 -> 2
^^
It makes sense to see arrows around here, so I suspect it is something
earlier.Maybe this pattern is indented a bit farther from the previous
patterns?
Note: Here is an example of a valid `when` expression for reference.
when List.first plants is
Ok n ->
n
Err _ ->
200
Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important!
"###
);
// TODO I think we can do better here
test_report!(
if_outdented_then,
indoc!(
r#"
x =
if 5 == 5
then 2 else 3
x
"#
),
@r###"
── UNFINISHED IF ────────────────────────────── tmp/if_outdented_then/Test.roc ─
I was partway through parsing an `if` expression, but I got stuck here:
5│ if 5 == 5
^
I was expecting to see the `then` keyword next.
"###
);
// this should get better with time
test_report!(
if_missing_else,
indoc!(
r#"
if 5 == 5 then 2
"#
),
@r###"
── UNFINISHED IF ──────────────────────────────── tmp/if_missing_else/Test.roc ─
I was partway through parsing an `if` expression, but I got stuck here:
4│ if 5 == 5 then 2
^
I was expecting to see the `else` keyword next.
"###
);
test_report!(
list_double_comma,
indoc!(
r#"
[1, 2, , 3]
"#
),
@r###"
── UNFINISHED LIST ──────────────────────────── tmp/list_double_comma/Test.roc ─
I am partway through started parsing a list, but I got stuck here:
4│ [1, 2, , 3]
^
I was expecting to see a list entry before this comma, so try adding a
list entry and see if that helps?
"###
);
test_report!(
list_without_end,
indoc!(
r#"
[1, 2,
"#
),
@r###"
── UNFINISHED LIST ───────────────────────────── tmp/list_without_end/Test.roc ─
I am partway through started parsing a list, but I got stuck here:
4│ [1, 2,
5│
6│
^
I was expecting to see a closing square bracket before this, so try
adding a ] and see if that helps?
Note: When I get stuck like this, it usually means that there is a
missing parenthesis or bracket somewhere earlier. It could also be a
stray keyword or operator.
"###
);
test_report!(
number_double_dot,
indoc!(
r#"
1.1.1
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This float literal contains an invalid digit:
4│ 1.1.1
^^^^^
Floating point literals can only contain the digits 0-9, or use
scientific notation 10e4, or have a float suffix.
Tip: Learn more about number literals at TODO
"###
);
test_report!(
unicode_not_hex,
r#""abc\u(zzzz)def""#,
@r###"
── WEIRD CODE POINT ───────────────────────────── tmp/unicode_not_hex/Test.roc ─
I am partway through parsing a unicode code point, but I got stuck
here:
4│ "abc\u(zzzz)def"
^
I was expecting a hexadecimal number, like \u(1100) or \u(00FF).
Learn more about working with unicode in roc at TODO
"###
);
test_report!(
interpolate_not_identifier,
r#""abc\(32)def""#,
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This string interpolation is invalid:
4│ "abc\(32)def"
^^
I was expecting an identifier, like \u(message) or
\u(LoremIpsum.text).
Learn more about string interpolation at TODO
"###
);
test_report!(
unicode_too_large,
r#""abc\u(110000)def""#,
@r###"
── INVALID UNICODE ─────────────────────────────────────── /code/proj/Main.roc ─
This unicode code point is invalid:
4│ "abc\u(110000)def"
^^^^^^
Learn more about working with unicode in roc at TODO
"###
);
test_report!(
weird_escape,
r#""abc\qdef""#,
@r###"
── WEIRD ESCAPE ──────────────────────────────────── tmp/weird_escape/Test.roc ─
I was partway through parsing a string literal, but I got stuck here:
4│ "abc\qdef"
^^
This is not an escape sequence I recognize. After a backslash, I am
looking for one of these:
- A newline: \n
- A caret return: \r
- A tab: \t
- An escaped quote: \"
- An escaped backslash: \\
- A unicode code point: \u(00FF)
- An interpolated string: \(myVariable)
"###
);
test_report!(
single_no_end,
r#""there is no end"#,
@r###"
── ENDLESS STRING ───────────────────────────────── tmp/single_no_end/Test.roc ─
I cannot find the end of this string:
4│ "there is no end
^
You could change it to something like "to be or not to be" or even
just "".
"###
);
test_report!(
multi_no_end,
r#""""there is no end"#,
@r###"
── ENDLESS STRING ────────────────────────────────── tmp/multi_no_end/Test.roc ─
I cannot find the end of this block string:
4│ """there is no end
^
You could change it to something like """to be or not to be""" or even
just """""".
"###
);
test_report!(
multi_insufficient_indent,
" \"\"\"\n testing\n \"\"\"", // 4 space indent on the start, 2 space on the `testing` line
@r###"
── INSUFFICIENT INDENT IN MULTI-LINE STRING ─ ..._insufficient_indent/Test.roc ─
This multiline string is not sufficiently indented:
5│ testing
^
Lines in a multi-line string must be indented at least as much as the
beginning """. This extra indentation is automatically removed from
the string during compilation.
"###
);
// https://github.com/rtfeldman/roc/issues/1714
test_report!(
interpolate_concat_is_transparent_1714,
indoc!(
r#"
greeting = "Privet"
if True then 1 else "\(greeting), World!"
"#,
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `if` has an `else` branch with a different type from its `then` branch:
6│ if True then 1 else "\(greeting), World!"
^^^^^^^^^^^^^^^^^^^^^
The `else` branch is a string of type:
Str
but the `then` branch has the type:
Num a
All branches in an `if` must have the same type!
"###
);
macro_rules! comparison_binop_transparency_tests {
($($op:expr, $name:ident),* $(,)?) => {
$(
test_report!(
$name,
&format!(r#"if True then "abc" else 1 {} 2"#, $op),
|golden| assert_eq!(golden, format!(
r#"── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `if` has an `else` branch with a different type from its `then` branch:
4│ if True then "abc" else 1 {} 2
^^{}^^
This comparison produces:
Bool
but the `then` branch has the type:
Str
All branches in an `if` must have the same type!
"#,
$op, "^".repeat($op.len())
))
);
)*
}
}
comparison_binop_transparency_tests! {
"<", lt_binop_is_transparent,
">", gt_binop_is_transparent,
"==", eq_binop_is_transparent,
"!=", neq_binop_is_transparent,
"<=", leq_binop_is_transparent,
">=", geq_binop_is_transparent,
}
test_report!(
keyword_record_field_access,
indoc!(
r#"
foo = {}
foo.if
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `foo` record doesnt have a `if` field:
6│ foo.if
^^^^^^
In fact, `foo` is a record with no fields at all!
"###
);
test_report!(
keyword_qualified_import,
indoc!(
r#"
Num.if
"#
),
@r###"
── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─
The Num module does not expose `if`:
4│ Num.if
^^^^^^
Did you mean one of these?
Num.sin
Num.div
Num.abs
Num.neg
"###
);
test_report!(
stray_dot_expr,
indoc!(
r#"
Num.add . 23
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I trying to parse a record field access here:
4│ Num.add . 23
^
So I expect to see a lowercase letter next, like .name or .height.
"###
);
test_report!(
opaque_ref_field_access,
indoc!(
r#"
@UUID.bar
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am very confused by this field access:
4│ @UUID.bar
^^^^
It looks like a record field access on an opaque reference.
"###
);
test_report!(
weird_accessor,
indoc!(
r#"
.foo.bar
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am very confused by this field access
4│ .foo.bar
^^^^^^^^
It looks like a field access on an accessor. I parse.client.name as
(.client).name. Maybe use an anonymous function like
(\r -> r.client.name) instead?
"###
);
test_report!(
part_starts_with_number,
indoc!(
r#"
foo.100
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I trying to parse a record field access here:
4│ foo.100
^
So I expect to see a lowercase letter next, like .name or .height.
"###
);
test_report!(
closure_underscore_ident,
indoc!(
r#"
\the_answer -> 100
"#
),
@r###"
── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am trying to parse an identifier here:
4│ \the_answer -> 100
^
Underscores are not allowed in identifiers. Use camelCase instead!
"###
);
test_report!(
#[ignore]
double_binop,
indoc!(
r#"
key >= 97 && <= 122
"#
),
@r#"
"#
);
test_report!(
#[ignore]
case_of,
indoc!(
r#"
case 1 of
1 -> True
_ -> False
"#
),
@r###"
── UNKNOWN OPERATOR ───────────────────────────────────── tmp/case_of/Test.roc ─
This looks like an operator, but it's not one I recognize!
1│ app "test" provides [main] to "./platform"
2│
3│ main =
4│ case 1 of
5│ 1 -> True
^^
The arrow -> is only used to define cases in a `when`.
when color is
Red -> "stop!"
Green -> "go!"
"###
);
test_report!(
argument_without_space,
indoc!(
r#"
["foo", bar("")]
"#
),
@r###"
── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─
Nothing is named `bar` in this scope.
4│ ["foo", bar("")]
^^^
Did you mean one of these?
Nat
Str
Err
U8
"###
);
test_report!(
invalid_operator,
indoc!(
r#"
main =
5 ** 3
"#
),
@r###"
── UNKNOWN OPERATOR ──────────────────────────── tmp/invalid_operator/Test.roc ─
This looks like an operator, but it's not one I recognize!
1│ app "test" provides [main] to "./platform"
2│
3│ main =
4│ main =
5│ 5 ** 3
^^
I have no specific suggestion for this operator, see TODO for the full
list of operators in Roc.
"###
);
test_report!(
double_plus,
indoc!(
r#"
main =
[] ++ []
"#
),
@r###"
── UNKNOWN OPERATOR ───────────────────────────────── tmp/double_plus/Test.roc ─
This looks like an operator, but it's not one I recognize!
1│ app "test" provides [main] to "./platform"
2│
3│ main =
4│ main =
5│ [] ++ []
^^
To concatenate two lists or strings, try using List.concat or
Str.concat instead.
"###
);
test_report!(
inline_hastype,
indoc!(
r#"
main =
(\x -> x) : I64
3
"#
),
@r###"
── UNKNOWN OPERATOR ────────────────────────────── tmp/inline_hastype/Test.roc ─
This looks like an operator, but it's not one I recognize!
1│ app "test" provides [main] to "./platform"
2│
3│ main =
4│ main =
5│ (\x -> x) : I64
^
The has-type operator : can only occur in a definition's type
signature, like
increment : I64 -> I64
increment = \x -> x + 1
"###
);
// this is still bad, but changing the order and progress of other parsers should improve it
// down the line
test_report!(
wild_case_arrow,
indoc!(
r#"
main = 5 -> 3
"#
),
@r###"
── UNKNOWN OPERATOR ───────────────────────────── tmp/wild_case_arrow/Test.roc ─
This looks like an operator, but it's not one I recognize!
1│ app "test" provides [main] to "./platform"
2│
3│ main =
4│ main = 5 -> 3
^^
The arrow -> is only used to define cases in a `when`.
when color is
Red -> "stop!"
Green -> "go!"
"###
);
#[test]
fn provides_to_identifier() {
report_header_problem_as(
indoc!(
r#"
app "test-base64"
packages { pf: "platform/main.roc" }
imports [pf.Task, Base64]
provides [main, @Foo] to pf
"#
),
indoc!(
r#"
── WEIRD PROVIDES ──────────────────────────────────────── /code/proj/Main.roc ─
I am partway through parsing a provides list, but I got stuck here:
3│ imports [pf.Task, Base64]
4│ provides [main, @Foo] to pf
^
I was expecting a type name, value name or function name next, like
provides [Animal, default, tame]
"#
),
)
}
#[test]
fn platform_requires_rigids() {
report_header_problem_as(
indoc!(
r#"
platform "folkertdev/foo"
requires { main : Effect {} }
exposes []
packages {}
imports [Task]
provides [mainForHost]
effects fx.Effect
{
putChar : I64 -> Effect {},
putLine : Str -> Effect {},
getLine : Effect Str
}
"#
),
indoc!(
r#"
── BAD REQUIRES ────────────────────────────────────────── /code/proj/Main.roc ─
I am partway through parsing a header, but I got stuck here:
1│ platform "folkertdev/foo"
2│ requires { main : Effect {} }
^
I am expecting a list of type names like `{}` or `{ Model }` next. A full
`requires` definition looks like
requires { Model, Msg } {main : Effect {}}
"#
),
)
}
#[test]
fn missing_imports() {
report_header_problem_as(
indoc!(
r#"
interface Foobar
exposes [main, Foo]
"#
),
indoc!(
r#"
── WEIRD IMPORTS ───────────────────────────────────────── /code/proj/Main.roc ─
I am partway through parsing a header, but I got stuck here:
2│ exposes [main, Foo]
^
I am expecting the `imports` keyword next, like
imports [Animal, default, tame]
"#
),
)
}
#[test]
fn exposes_identifier() {
report_header_problem_as(
indoc!(
r#"
interface Foobar
exposes [main, @Foo]
imports [pf.Task, Base64]
"#
),
indoc!(
r#"
── WEIRD EXPOSES ───────────────────────────────────────── /code/proj/Main.roc ─
I am partway through parsing an `exposes` list, but I got stuck here:
1│ interface Foobar
2│ exposes [main, @Foo]
^
I was expecting a type name, value name or function name next, like
exposes [Animal, default, tame]
"#
),
)
}
#[test]
fn invalid_module_name() {
report_header_problem_as(
indoc!(
r#"
interface foobar
exposes [main, @Foo]
imports [pf.Task, Base64]
"#
),
indoc!(
r#"
── WEIRD MODULE NAME ───────────────────────────────────── /code/proj/Main.roc ─
I am partway through parsing a header, but got stuck here:
1│ interface foobar
^
I am expecting a module name next, like BigNum or Main. Module names
must start with an uppercase letter.
"#
),
)
}
#[test]
fn invalid_app_name() {
report_header_problem_as(
indoc!(
r#"
app foobar
exposes [main, @Foo]
imports [pf.Task, Base64]
"#
),
indoc!(
r#"
── WEIRD APP NAME ──────────────────────────────────────── /code/proj/Main.roc ─
I am partway through parsing a header, but got stuck here:
1│ app foobar
^
I am expecting an application name next, like app "main" or
app "editor". App names are surrounded by quotation marks.
"#
),
)
}
test_report!(
apply_unary_negative,
indoc!(
r#"
foo = 3
-foo 1 2
"#
),
@r###"
── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─
This value is not a function, but it was given 2 arguments:
6│ -foo 1 2
^^^^
Are there any missing commas? Or missing parentheses?
"###
);
test_report!(
apply_unary_not,
indoc!(
r#"
foo = True
!foo 1 2
"#
),
@r###"
── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─
This value is not a function, but it was given 2 arguments:
6│ !foo 1 2
^^^^
Are there any missing commas? Or missing parentheses?
"###
);
test_report!(
applied_tag_function,
indoc!(
r#"
x : List [Foo Str]
x = List.map [1, 2] Foo
x
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `x` definition:
4│ x : List [Foo Str]
5│ x = List.map [1, 2] Foo
^^^^^^^^^^^^^^^^^^^
This `map` call produces:
List [Foo Num a]
But the type annotation on `x` says it should be:
List [Foo Str]
"###
);
test_report!(
pattern_in_parens_open,
indoc!(
r#"
\( a
"#
),
@r###"
── UNFINISHED PARENTHESES ──────────────── tmp/pattern_in_parens_open/Test.roc ─
I am partway through parsing a pattern in parentheses, but I got stuck
here:
4│ \( a
^
I was expecting to see a closing parenthesis before this, so try
adding a ) and see if that helps?
Note: I may be confused by indentation
"###
);
test_report!(
pattern_in_parens_end_comma,
indoc!(
r#"
\( a,
"#
),
@r###"
── UNFINISHED PARENTHESES ─────────── tmp/pattern_in_parens_end_comma/Test.roc ─
I am partway through parsing a pattern in parentheses, but I got stuck
here:
4│ \( a,
^
I was expecting to see a closing parenthesis before this, so try
adding a ) and see if that helps?
"###
);
test_report!(
pattern_in_parens_end,
indoc!(
r#"
\( a
"#
),
@r###"
── UNFINISHED PARENTHESES ───────────────── tmp/pattern_in_parens_end/Test.roc ─
I am partway through parsing a pattern in parentheses, but I got stuck
here:
4│ \( a
^
I was expecting to see a closing parenthesis before this, so try
adding a ) and see if that helps?
Note: I may be confused by indentation
"###
);
test_report!(
pattern_in_parens_indent_end,
indoc!(
r#"
x = \( a
)
"#
),
@r###"
── NEED MORE INDENTATION ─────────── tmp/pattern_in_parens_indent_end/Test.roc ─
I am partway through parsing a pattern in parentheses, but I got stuck
here:
4│ x = \( a
5│ )
^
I need this parenthesis to be indented more. Try adding more spaces
before it!
"###
);
test_report!(
pattern_in_parens_indent_open,
indoc!(
r#"
\(
"#
),
@r###"
── UNFINISHED PARENTHESES ───────── tmp/pattern_in_parens_indent_open/Test.roc ─
I just started parsing a pattern in parentheses, but I got stuck here:
4│ \(
^
Record pattern look like { name, age: currentAge }, so I was expecting
to see a field name next.
Note: I may be confused by indentation
"###
);
test_report!(
outdented_in_parens,
indoc!(
r#"
Box : (
Str
)
4
"#
),
@r###"
── NEED MORE INDENTATION ──────────────────── tmp/outdented_in_parens/Test.roc ─
I am partway through parsing a type in parentheses, but I got stuck
here:
4│ Box : (
5│ Str
6│ )
^
I need this parenthesis to be indented more. Try adding more spaces
before it!
"###
);
test_report!(
backpassing_type_error,
indoc!(
r#"
x <- List.map ["a", "b"]
x + 1
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `map` has an unexpected type:
4│> x <- List.map ["a", "b"]
5│>
6│> x + 1
The argument is an anonymous function of type:
Num a -> Num a
But `map` needs its 2nd argument to be:
Str -> Num a
"###
);
test_report!(
expect_expr_type_error,
indoc!(
r#"
expect "foobar"
4
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This `expect` condition needs to be a Bool:
4│ expect "foobar"
^^^^^^^^
Right now its a string of type:
Str
But I need every `expect` condition to evaluate to a Bool—either `True`
or `False`.
"###
);
test_report!(
num_too_general_wildcard,
indoc!(
r#"
mult : Num.Num *, Num.F64 -> Num.F64
mult = \a, b -> a * b
mult 0 0
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `mul` has an unexpected type:
5│ mult = \a, b -> a * b
^
This `b` value is a:
F64
But `mul` needs its 2nd argument to be:
Num *
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `mult` definition:
4│ mult : Num.Num *, Num.F64 -> Num.F64
5│ mult = \a, b -> a * b
^^^^^
This `mul` call produces:
Num *
But the type annotation on `mult` says it should be:
F64
"###
);
test_report!(
num_too_general_named,
indoc!(
r#"
mult : Num.Num a, Num.F64 -> Num.F64
mult = \a, b -> a * b
mult 0 0
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `mul` has an unexpected type:
5│ mult = \a, b -> a * b
^
This `b` value is a:
F64
But `mul` needs its 2nd argument to be:
Num a
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `mult` definition:
4│ mult : Num.Num a, Num.F64 -> Num.F64
5│ mult = \a, b -> a * b
^^^^^
This `mul` call produces:
Num a
But the type annotation on `mult` says it should be:
F64
"###
);
test_report!(
inference_var_not_enough_in_alias,
indoc!(
r#"
Result a b : [Ok a, Err b]
canIGo : _ -> 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
"#
),
@r###"
── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─
The `Result` name is first defined here:
1│ app "test" provides [main] to "./platform"
But then it's defined a second time here:
4│ Result a b : [Ok a, Err b]
^^^^^^^^^^^^^^^^^^^^^^^^^^
Since these aliases have the same name, it's easy to use the wrong one
on accident. Give one of them a new name.
── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─
The `Result` alias expects 2 type arguments, but it got 1 instead:
6│ canIGo : _ -> Result _
^^^^^^^^
Are there missing parentheses?
"###
);
test_report!(
inference_var_too_many_in_alias,
indoc!(
r#"
Result a b : [Ok a, Err b]
canIGo : _ -> 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
"#
),
@r###"
── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─
The `Result` name is first defined here:
1│ app "test" provides [main] to "./platform"
But then it's defined a second time here:
4│ Result a b : [Ok a, Err b]
^^^^^^^^^^^^^^^^^^^^^^^^^^
Since these aliases have the same name, it's easy to use the wrong one
on accident. Give one of them a new name.
── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─
The `Result` alias expects 2 type arguments, but it got 3 instead:
6│ canIGo : _ -> Result _ _ _
^^^^^^^^^^^^
Are there missing parentheses?
"###
);
test_report!(
inference_var_conflict_in_rigid_links,
indoc!(
r#"
f : a -> (_ -> b)
f = \x -> \y -> if x == y then x else y
f
"#
),
// TODO: We should tell the user that we inferred `_` as `a`
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `f` definition:
4│ f : a -> (_ -> b)
5│ f = \x -> \y -> if x == y then x else y
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The body is an anonymous function of type:
a -> a
But the type annotation on `f` says it should be:
a -> b
Tip: 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 in your type annotation? Maybe your code uses them
in a weird way?
"###
);
test_report!(
error_wildcards_are_related,
indoc!(
r#"
f : * -> *
f = \x -> x
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `f` definition:
4│ f : * -> *
5│ f = \x -> x
^
The type annotation on `f` says this `x` value should have the type:
*
However, the type of this `x` value is connected to another type in a
way that isn't reflected in this annotation.
Tip: Any connection between types must use a named type variable, not
a `*`! Maybe the annotation on `f` should have a named type variable in
place of the `*`?
"###
);
test_report!(
error_nested_wildcards_are_related,
indoc!(
r#"
f : a, b, * -> {x: a, y: b, z: *}
f = \x, y, z -> {x, y, z}
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `f` definition:
4│ f : a, b, * -> {x: a, y: b, z: *}
5│ f = \x, y, z -> {x, y, z}
^^^^^^^^^
The type annotation on `f` says the body is a record should have the
type:
{ x : a, y : b, z : * }
However, the type of the body is a record is connected to another type
in a way that isn't reflected in this annotation.
Tip: Any connection between types must use a named type variable, not
a `*`! Maybe the annotation on `f` should have a named type variable in
place of the `*`?
"###
);
test_report!(
error_wildcards_are_related_in_nested_defs,
indoc!(
r#"
f : a, b, * -> *
f = \_, _, x2 ->
inner : * -> *
inner = \y -> y
inner x2
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `inner` definition:
6│ inner : * -> *
7│ inner = \y -> y
^
The type annotation on `inner` says this `y` value should have the type:
*
However, the type of this `y` value is connected to another type in a
way that isn't reflected in this annotation.
Tip: Any connection between types must use a named type variable, not
a `*`! Maybe the annotation on `inner` should have a named type variable
in place of the `*`?
"###
);
test_report!(
error_inline_alias_not_an_alias,
indoc!(
r#"
f : List elem -> [Nil, Cons elem a] as a
"#
),
@r###"
── NOT AN INLINE ALIAS ────────── tmp/error_inline_alias_not_an_alias/Test.roc ─
The inline type after this `as` is not a type alias:
4│ f : List elem -> [Nil, Cons elem a] as a
^
Inline alias types must start with an uppercase identifier and be
followed by zero or more type arguments, like Point or List a.
"###
);
test_report!(
error_inline_alias_qualified,
indoc!(
r#"
f : List elem -> [Nil, Cons elem a] as Module.LinkedList a
"#
),
@r###"
── QUALIFIED ALIAS NAME ──────────── tmp/error_inline_alias_qualified/Test.roc ─
This type alias has a qualified name:
4│ f : List elem -> [Nil, Cons elem a] as Module.LinkedList a
^
An alias introduces a new name to the current scope, so it must be
unqualified.
"###
);
test_report!(
error_inline_alias_argument_uppercase,
indoc!(
r#"
f : List elem -> [Nil, Cons elem a] as LinkedList U
"#
),
@r###"
── TYPE ARGUMENT NOT LOWERCASE ─ ...r_inline_alias_argument_uppercase/Test.roc ─
This alias type argument is not lowercase:
4│ f : List elem -> [Nil, Cons elem a] as LinkedList U
^
All type arguments must be lowercase.
"###
);
test_report!(
mismatched_single_tag_arg,
indoc!(
r#"
isEmpty =
\email ->
Email str = email
Str.isEmpty str
isEmpty (Name "boo")
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `isEmpty` has an unexpected type:
9│ isEmpty (Name "boo")
^^^^^^^^^^
This `Name` tag application has the type:
[Name Str]a
But `isEmpty` needs its 1st argument to be:
[Email Str]
Tip: Seems like a tag typo. Maybe `Name` should be `Email`?
Tip: 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_report!(
issue_2326,
indoc!(
r#"
C a b : a -> D a b
D a b : { a, b }
f : C a Num.Nat -> D a Num.Nat
f = \c -> c 6
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `c` has an unexpected type:
8│ f = \c -> c 6
^
The argument is a number of type:
Num a
But `c` needs its 1st argument to be:
a
Tip: The type annotation uses the type variable `a` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `Num` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"###
);
test_report!(
issue_2380_annotations_only,
indoc!(
r#"
F : F
a : F
a
"#
),
@r###"
── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─
The `F` alias is self-recursive in an invalid way:
4│ F : F
^
Recursion in aliases is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
"###
);
test_report!(
issue_2380_typed_body,
indoc!(
r#"
F : F
a : F
a = 1
a
"#
),
@r###"
── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─
The `F` alias is self-recursive in an invalid way:
4│ F : F
^
Recursion in aliases is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
"###
);
test_report!(
issue_2380_alias_with_vars,
indoc!(
r#"
F a b : F a b
a : F Str Str
a
"#
),
@r###"
── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─
The `F` alias is self-recursive in an invalid way:
4│ F a b : F a b
^
Recursion in aliases is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
"###
);
test_report!(
issue_2167_record_field_optional_and_required_mismatch,
indoc!(
r#"
Job : [Job { inputs : List Str }]
job : { inputs ? List Str } -> Job
job = \{ inputs } ->
Job { inputs }
job { inputs: ["build", "test"] }
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 1st argument to `job` is weird:
6│ job = \{ inputs } ->
^^^^^^^^^^
The argument is a pattern that matches record values of type:
{ inputs : List Str }
But the annotation on `job` says the 1st argument should be:
{ inputs ? List Str }
Tip: To extract the `.inputs` field it must be non-optional, but the
type says this field is optional. Learn more about optional fields at
TODO.
"###
);
test_report!(
unify_recursive_with_nonrecursive,
indoc!(
r#"
Job : [Job { inputs : List Job }]
job : { inputs : List Str } -> Job
job = \{ inputs } ->
Job { inputs }
job { inputs: ["build", "test"] }
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `job` definition:
6│ job : { inputs : List Str } -> Job
7│ job = \{ inputs } ->
8│ Job { inputs }
^^^^^^^^^^^^^^
This `Job` tag application has the type:
[Job { inputs : List Str }]
But the type annotation on `job` says it should be:
[Job { inputs : List a }] as a
"###
);
test_report!(
nested_datatype,
indoc!(
r#"
Nested a : [Chain a (Nested (List a)), Term]
s : Nested Str
s
"#
),
@r###"
── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─
`Nested` is a nested datatype. Here is one recursive usage of it:
4│ Nested a : [Chain a (Nested (List a)), Term]
^^^^^^^^^^^^^^^
But recursive usages of `Nested` must match its definition:
4│ Nested a : [Chain a (Nested (List a)), Term]
^^^^^^^^
Nested datatypes are not supported in Roc.
Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments.
"###
);
test_report!(
nested_datatype_inline,
indoc!(
r#"
f : {} -> [Chain a (Nested (List a)), Term] as Nested a
f
"#
),
@r###"
── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─
`Nested` is a nested datatype. Here is one recursive usage of it:
4│ f : {} -> [Chain a (Nested (List a)), Term] as Nested a
^^^^^^^^^^^^^^^
But recursive usages of `Nested` must match its definition:
4│ f : {} -> [Chain a (Nested (List a)), Term] as Nested a
^^^^^^^^
Nested datatypes are not supported in Roc.
Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments.
"###
);
macro_rules! mismatched_suffix_tests {
($($number:expr, $suffix:expr, $name:ident)*) => {$(
test_report!(
$name,
&{
let number = $number.to_string();
let mut typ = $suffix.to_string();
typ.get_mut(0..1).unwrap().make_ascii_uppercase();
let bad_type = if $suffix == "u8" { "I8" } else { "U8" };
format!(indoc!(
r#"
use : Num.{} -> Num.U8
use {}{}
"#
), bad_type, number, $suffix)
},
|golden| {
let number = $number.to_string();
let mut typ = $suffix.to_string();
typ.get_mut(0..1).unwrap().make_ascii_uppercase();
let bad_type = if $suffix == "u8" { "I8" } else { "U8" };
let carets = "^".repeat(number.len() + $suffix.len());
let kind = match $suffix {
"dec"|"f32"|"f64" => "a fraction",
_ => "an integer",
};
let real = format!(indoc!(
r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `use` has an unexpected type:
5│ use {}{}
{}
The argument is {} of type:
{}
But `use` needs its 1st argument to be:
{}
"#
), number, $suffix, carets, kind, typ, bad_type);
assert_eq!(golden, real);
}
);
)*}
}
mismatched_suffix_tests! {
1, "u8", mismatched_suffix_u8
1, "u16", mismatched_suffix_u16
1, "u32", mismatched_suffix_u32
1, "u64", mismatched_suffix_u64
1, "u128", mismatched_suffix_u128
1, "i8", mismatched_suffix_i8
1, "i16", mismatched_suffix_i16
1, "i32", mismatched_suffix_i32
1, "i64", mismatched_suffix_i64
1, "i128", mismatched_suffix_i128
1, "nat", mismatched_suffix_nat
1, "dec", mismatched_suffix_dec
1, "f32", mismatched_suffix_f32
1, "f64", mismatched_suffix_f64
}
macro_rules! mismatched_suffix_tests_in_pattern {
($($number:expr, $suffix:expr, $name:ident)*) => {$(
test_report!(
$name,
&{
let number = $number.to_string();
let mut typ = $suffix.to_string();
typ.get_mut(0..1).unwrap().make_ascii_uppercase();
let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" };
format!(indoc!(
r#"
when {}{} is
{}{} -> 1
_ -> 1
"#
), number, bad_suffix, number, $suffix)
},
|golden| {
let number = $number.to_string();
let mut typ = $suffix.to_string();
typ.get_mut(0..1).unwrap().make_ascii_uppercase();
let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" };
let bad_type = if $suffix == "u8" { "I8" } else { "U8" };
let real = format!(indoc!(
r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
4│> when {}{} is
5│ {}{} -> 1
6│ _ -> 1
The `when` condition is an integer of type:
{}
But the branch patterns have type:
{}
The branches must be cases of the `when` condition's type!
"#
), number, bad_suffix, number, $suffix, bad_type, typ);
assert_eq!(golden, real);
}
);
)*}
}
mismatched_suffix_tests_in_pattern! {
1, "u8", mismatched_suffix_u8_pattern
1, "u16", mismatched_suffix_u16_pattern
1, "u32", mismatched_suffix_u32_pattern
1, "u64", mismatched_suffix_u64_pattern
1, "u128", mismatched_suffix_u128_pattern
1, "i8", mismatched_suffix_i8_pattern
1, "i16", mismatched_suffix_i16_pattern
1, "i32", mismatched_suffix_i32_pattern
1, "i64", mismatched_suffix_i64_pattern
1, "i128", mismatched_suffix_i128_pattern
1, "nat", mismatched_suffix_nat_pattern
1, "dec", mismatched_suffix_dec_pattern
1, "f32", mismatched_suffix_f32_pattern
1, "f64", mismatched_suffix_f64_pattern
}
test_report!(
bad_numeric_literal_suffix,
indoc!(
r#"
1u256
"#
),
// TODO: link to number suffixes
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This integer literal contains an invalid digit:
4│ 1u256
^^^^^
Integer literals can only contain the digits
0-9, or have an integer suffix.
Tip: Learn more about number literals at TODO
"###
);
test_report!(
numer_literal_multi_suffix,
indoc!(
r#"
1u8u8
"#
),
// TODO: link to number suffixes
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
This integer literal contains an invalid digit:
4│ 1u8u8
^^^^^
Integer literals can only contain the digits
0-9, or have an integer suffix.
Tip: Learn more about number literals at TODO
"###
);
test_report!(
int_literal_has_float_suffix,
indoc!(
r#"
0b1f32
"#
),
@r###"
── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─
This number literal is an integer, but it has a float suffix:
4│ 0b1f32
^^^^^^
"###
);
test_report!(
float_literal_has_int_suffix,
indoc!(
r#"
1.0u8
"#
),
@r###"
── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─
This number literal is a float, but it has an integer suffix:
4│ 1.0u8
^^^^^
"###
);
test_report!(
u8_overflow,
"256u8",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 256u8
^^^^^
Tip: The suffix indicates this integer is a U8, whose maximum value is
255.
"###
);
test_report!(
negative_u8,
"-1u8",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -1u8
^^^^
Tip: The suffix indicates this integer is a U8, whose minimum value is
0.
"###
);
test_report!(
u16_overflow,
"65536u16",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 65536u16
^^^^^^^^
Tip: The suffix indicates this integer is a U16, whose maximum value
is 65535.
"###
);
test_report!(
negative_u16,
"-1u16",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -1u16
^^^^^
Tip: The suffix indicates this integer is a U16, whose minimum value
is 0.
"###
);
test_report!(
u32_overflow,
"4_294_967_296u32",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 4_294_967_296u32
^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a U32, whose maximum value
is 4_294_967_295.
"###
);
test_report!(
negative_u32,
"-1u32",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -1u32
^^^^^
Tip: The suffix indicates this integer is a U32, whose minimum value
is 0.
"###
);
test_report!(
u64_overflow,
"18_446_744_073_709_551_616u64",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 18_446_744_073_709_551_616u64
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a U64, whose maximum value
is 18_446_744_073_709_551_615.
"###
);
test_report!(
negative_u64,
"-1u64",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -1u64
^^^^^
Tip: The suffix indicates this integer is a U64, whose minimum value
is 0.
"###
);
test_report!(
negative_u128,
"-1u128",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -1u128
^^^^^^
Tip: The suffix indicates this integer is a U128, whose minimum value
is 0.
"###
);
test_report!(
i8_overflow,
"128i8",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 128i8
^^^^^
Tip: The suffix indicates this integer is a I8, whose maximum value is
127.
"###
);
test_report!(
i8_underflow,
"-129i8",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -129i8
^^^^^^
Tip: The suffix indicates this integer is a I8, whose minimum value is
-128.
"###
);
test_report!(
i16_overflow,
"32768i16",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 32768i16
^^^^^^^^
Tip: The suffix indicates this integer is a I16, whose maximum value
is 32767.
"###
);
test_report!(
i16_underflow,
"-32769i16",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -32769i16
^^^^^^^^^
Tip: The suffix indicates this integer is a I16, whose minimum value
is -32768.
"###
);
test_report!(
i32_overflow,
"2_147_483_648i32",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 2_147_483_648i32
^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I32, whose maximum value
is 2_147_483_647.
"###
);
test_report!(
i32_underflow,
"-2_147_483_649i32",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -2_147_483_649i32
^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I32, whose minimum value
is -2_147_483_648.
"###
);
test_report!(
i64_overflow,
"9_223_372_036_854_775_808i64",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 9_223_372_036_854_775_808i64
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I64, whose maximum value
is 9_223_372_036_854_775_807.
"###
);
test_report!(
i64_underflow,
"-9_223_372_036_854_775_809i64",
@r###"
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─
This integer literal underflows the type indicated by its suffix:
4│ -9_223_372_036_854_775_809i64
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I64, whose minimum value
is -9_223_372_036_854_775_808.
"###
);
test_report!(
i128_overflow,
"170_141_183_460_469_231_731_687_303_715_884_105_728i128",
@r###"
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─
This integer literal overflows the type indicated by its suffix:
4│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I128, whose maximum value
is 170_141_183_460_469_231_731_687_303_715_884_105_727.
"###
);
test_report!(
list_get_negative_number,
indoc!(
r#"
List.get [1,2,3] -1
"#
),
// TODO: this error message could be improved, e.g. something like "This argument can
// be used as ... because of its literal value"
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `get` has an unexpected type:
4│ List.get [1,2,3] -1
^^
The argument is a number of type:
I8, I16, F32, I32, F64, I64, I128, or Dec
But `get` needs its 2nd argument to be:
Nat
"###
);
test_report!(
list_get_negative_number_indirect,
indoc!(
r#"
a = -9_223_372_036_854
List.get [1,2,3] a
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `get` has an unexpected type:
5│ List.get [1,2,3] a
^
This `a` value is a:
F64, I64, I128, or Dec
But `get` needs its 2nd argument to be:
Nat
"###
);
test_report!(
list_get_negative_number_double_indirect,
indoc!(
r#"
a = -9_223_372_036_854
b = a
List.get [1,2,3] b
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `get` has an unexpected type:
6│ List.get [1,2,3] b
^
This `b` value is a:
F64, I64, I128, or Dec
But `get` needs its 2nd argument to be:
Nat
"###
);
test_report!(
compare_unsigned_to_signed,
indoc!(
r#"
when -1 is
1u8 -> 1
_ -> 1
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
4│> when -1 is
5│ 1u8 -> 1
6│ _ -> 1
The `when` condition is a number of type:
I8, I16, F32, I32, F64, I64, I128, or Dec
But the branch patterns have type:
U8
The branches must be cases of the `when` condition's type!
"###
);
test_report!(
recursive_type_alias_is_newtype,
indoc!(
r#"
R a : [Only (R a)]
v : R Str
v
"#
),
@r###"
── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─
The `R` alias is self-recursive in an invalid way:
4│ R a : [Only (R a)]
^
Recursion in aliases is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
"###
);
test_report!(
recursive_type_alias_is_newtype_deep,
indoc!(
r#"
R a : [Only { very: [Deep (R a)] }]
v : R Str
v
"#
),
@r###"
── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─
The `R` alias is self-recursive in an invalid way:
4│ R a : [Only { very: [Deep (R a)] }]
^
Recursion in aliases is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
"###
);
test_report!(
recursive_type_alias_is_newtype_mutual,
indoc!(
r#"
Foo a : [Thing (Bar a)]
Bar a : [Stuff (Foo a)]
v : Bar Str
v
"#
),
@r###"
── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─
The `Foo` alias is recursive in an invalid way:
4│ Foo a : [Thing (Bar a)]
^^^
The `Foo` alias depends on itself through the following chain of
definitions:
┌─────┐
│ Foo
│ ↓
│ Bar
└─────┘
Recursion in aliases is only allowed if recursion happens behind a
tagged union, at least one variant of which is not recursive.
"###
);
test_report!(
issue_2458,
indoc!(
r#"
Result a b : [Ok a, Err b]
Foo a : [Blah (Result (Bar a) [])]
Bar a : Foo a
v : Bar Str
v
"#
),
@r###"
── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─
The `Result` name is first defined here:
1│ app "test" provides [main] to "./platform"
But then it's defined a second time here:
4│ Result a b : [Ok a, Err b]
^^^^^^^^^^^^^^^^^^^^^^^^^^
Since these aliases have the same name, it's easy to use the wrong one
on accident. Give one of them a new name.
"###
);
test_report!(
opaque_type_not_in_scope,
indoc!(
r#"
@Age 21
"#
),
@r###"
── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─
The opaque type Age referenced here is not defined:
4│ @Age 21
^^^^
Note: It looks like there are no opaque types declared in this scope yet!
"###
);
test_report!(
opaque_reference_not_opaque_type,
indoc!(
r#"
Age : Num.U8
@Age 21
"#
),
@r###"
── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─
The opaque type Age referenced here is not defined:
6│ @Age 21
^^^^
Note: There is an alias of the same name:
4│ Age : Num.U8
^^^
Note: It looks like there are no opaque types declared in this scope yet!
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`Age` is not used anywhere in your code.
4│ Age : Num.U8
^^^^^^^^^^^^
If you didn't intend on using `Age` then remove it so future readers of
your code don't wonder why it is there.
"###
);
test_report!(
qualified_opaque_reference,
indoc!(
r#"
OtherModule.@Age 21
"#
),
// TODO: get rid of the first error. Consider parsing OtherModule.@Age to completion
// and checking it during can. The reason the error appears is because it is parsed as
// Apply(Error(OtherModule), [@Age, 21])
@r###"
── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─
The opaque type Age referenced here is not defined:
4│ OtherModule.@Age 21
^^^^
Note: It looks like there are no opaque types declared in this scope yet!
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
I am trying to parse a qualified name here:
4│ OtherModule.@Age 21
^
I was expecting to see an identifier next, like height. A complete
qualified name looks something like Json.Decode.string.
"###
);
test_report!(
opaque_used_outside_declaration_scope,
indoc!(
r#"
age =
Age := Num.U8
21u8
@Age age
"#
),
// TODO(opaques): there is a potential for a better error message here, if the usage of
// `@Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to
// raise that declaration to the outer scope.
@r###"
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`Age` is not used anywhere in your code.
5│ Age := Num.U8
^^^^^^^^^^^^^
If you didn't intend on using `Age` then remove it so future readers of
your code don't wonder why it is there.
── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─
The opaque type Age referenced here is not defined:
8│ @Age age
^^^^
Note: It looks like there are no opaque types declared in this scope yet!
"###
);
test_report!(
#[ignore = "Blocked on https://github.com/rtfeldman/roc/issues/3385"]
unimported_modules_reported,
indoc!(
r#"
main : Task.Task {} []
main = "whatever man you don't even know my type"
main
"#
),
@r#"
── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─
The `Task` module is not imported:
1│ main : Task.Task {} []
^^^^^^^^^^^^^^^
Is there an import missing? Perhaps there is a typo. Did you mean one
of these?
Test
List
Num
Box
"#
);
test_report!(
opaque_mismatch_check,
indoc!(
r#"
Age := Num.U8
n : Age
n = @Age ""
n
"#
),
// TODO(opaques): error could be improved by saying that the opaque definition demands
// that the argument be a U8, and linking to the definitin!
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression is used in an unexpected way:
7│ n = @Age ""
^^
This argument to an opaque type has type:
Str
But you are trying to use it as:
U8
"###
);
test_report!(
opaque_mismatch_infer,
indoc!(
r#"
F n := n
if True
then @F ""
else @F {}
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression is used in an unexpected way:
8│ else @F {}
^^
This argument to an opaque type has type:
{}
But you are trying to use it as:
Str
"###
);
test_report!(
opaque_creation_is_not_wrapped,
indoc!(
r#"
F n := n
v : F Str
v = ""
v
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `v` definition:
6│ v : F Str
7│ v = ""
^^
The body is a string of type:
Str
But the type annotation on `v` says it should be:
F Str
Tip: Type comparisons between an opaque type are only ever equal if
both types are the same opaque type. Did you mean to create an opaque
type by wrapping it? If I have an opaque type Age := U32 I can create
an instance of this opaque type by doing @Age 23.
"###
);
test_report!(
opaque_mismatch_pattern_check,
indoc!(
r#"
Age := Num.U8
f : Age -> Num.U8
f = \Age n -> n
f
"#
),
// TODO(opaques): error could be improved by saying that the user-provided pattern
// probably wants to change "Age" to "@Age"!
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 1st argument to `f` is weird:
7│ f = \Age n -> n
^^^^^
The argument is a pattern that matches a `Age` tag of type:
[Age a]
But the annotation on `f` says the 1st argument should be:
Age
Tip: Type comparisons between an opaque type are only ever equal if
both types are the same opaque type. Did you mean to create an opaque
type by wrapping it? If I have an opaque type Age := U32 I can create
an instance of this opaque type by doing @Age 23.
"###
);
test_report!(
opaque_mismatch_pattern_infer,
indoc!(
r#"
F n := n
\x ->
when x is
@F A -> ""
@F {} -> ""
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The 2nd pattern in this `when` does not match the previous ones:
9│ @F {} -> ""
^^^^^
The 2nd pattern is trying to matchF unwrappings of type:
F {}a
But all the previous branches match:
F [A]
"###
);
test_report!(
opaque_pattern_match_not_exhaustive_tag,
indoc!(
r#"
F n := n
v : F [A, B, C]
when v is
@F A -> ""
@F B -> ""
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
8│> when v is
9│ @F A -> ""
10│ @F B -> ""
This `v` value is a:
F [A, B, C]
But the branch patterns have type:
F [A, B]
The branches must be cases of the `when` condition's type!
Tip: Looks like the branches are missing coverage of the `C` tag.
Tip: Maybe you need to add a catch-all branch, like `_`?
"###
);
test_report!(
opaque_pattern_match_not_exhaustive_int,
indoc!(
r#"
F n := n
v : F Num.U8
when v is
@F 1 -> ""
@F 2 -> ""
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
8│> when v is
9│> @F 1 -> ""
10│> @F 2 -> ""
Other possibilities include:
@F _
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
let_polymorphism_with_scoped_type_variables,
indoc!(
r#"
f : a -> a
f = \x ->
y : a -> a
y = \z -> z
n = y 1u8
x1 = y x
(\_ -> x1) n
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `y` has an unexpected type:
9│ n = y 1u8
^^^
The argument is an integer of type:
U8
But `y` needs its 1st argument to be:
a
Tip: The type annotation uses the type variable `a` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `U8` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"###
);
test_report!(
non_exhaustive_with_guard,
indoc!(
r#"
x : [A]
when x is
A if True -> ""
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
5│> when x is
6│> A if True -> ""
Other possibilities include:
A (note the lack of an if clause)
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
invalid_record_extension_type,
indoc!(
r#"
f : { x : Num.Nat }[]
f
"#
),
@r###"
── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─
This record extension type is invalid:
4│ f : { x : Num.Nat }[]
^^
Note: A record extension variable can only contain a type variable or
another record.
"###
);
test_report!(
invalid_tag_extension_type,
indoc!(
r#"
f : [A]Str
f
"#
),
@r###"
── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─
This tag union extension type is invalid:
4│ f : [A]Str
^^^
Note: A tag union extension variable can only contain a type variable
or another tag union.
"###
);
test_report!(
unknown_type,
indoc!(
r#"
Type : [Constructor UnknownType]
insertHelper : UnknownType, Type -> Type
insertHelper = \h, m ->
when m is
Constructor _ -> Constructor h
insertHelper
"#
),
@r###"
── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─
Nothing is named `UnknownType` in this scope.
4│ Type : [Constructor UnknownType]
^^^^^^^^^^^
Did you mean one of these?
Type
Unsigned8
Unsigned32
Unsigned16
── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─
Nothing is named `UnknownType` in this scope.
6│ insertHelper : UnknownType, Type -> Type
^^^^^^^^^^^
Did you mean one of these?
Type
Unsigned8
Unsigned32
Unsigned16
"###
);
test_report!(
ability_first_demand_not_indented_enough,
indoc!(
r#"
Eq has
eq : a, a -> U64 | a has Eq
1
"#
),
@r###"
── UNFINISHED ABILITY ── tmp/ability_first_demand_not_indented_enough/Test.roc ─
I was partway through parsing an ability definition, but I got stuck
here:
4│ Eq has
5│ eq : a, a -> U64 | a has Eq
^
I suspect this line is not indented enough (by 1 spaces)
"###
);
test_report!(
ability_demands_not_indented_with_first,
indoc!(
r#"
Eq has
eq : a, a -> U64 | a has Eq
neq : a, a -> U64 | a has Eq
1
"#
),
@r#"
── UNFINISHED ABILITY ─── tmp/ability_demands_not_indented_with_first/Test.roc ─
I was partway through parsing an ability definition, but I got stuck
here:
5│ eq : a, a -> U64 | a has Eq
6│ neq : a, a -> U64 | a has Eq
^
I suspect this line is indented too much (by 4 spaces)"#
);
test_report!(
ability_demand_value_has_args,
indoc!(
r#"
Eq has
eq b c : a, a -> U64 | a has Eq
1
"#
),
@r#"
── UNFINISHED ABILITY ───────────── tmp/ability_demand_value_has_args/Test.roc ─
I was partway through parsing an ability definition, but I got stuck
here:
5│ eq b c : a, a -> U64 | a has Eq
^
I was expecting to see a : annotating the signature of this value
next."#
);
test_report!(
ability_non_signature_expression,
indoc!(
r#"
Eq has
123
1
"#
),
@r###"
── UNFINISHED ABILITY ────────── tmp/ability_non_signature_expression/Test.roc ─
I was partway through parsing an ability definition, but I got stuck
here:
4│ Eq has
5│ 123
^
I was expecting to see a value signature next.
"###
);
test_report!(
wildcard_in_alias,
indoc!(
r#"
I : Num.Int *
a : I
a
"#
),
@r###"
── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─
The definition of `I` has an unbound type variable:
4│ I : Num.Int *
^
Tip: Type variables must be bound before the `:`. Perhaps you intended
to add a type parameter to this type?
"###
);
test_report!(
wildcard_in_opaque,
indoc!(
r#"
I := Num.Int *
a : I
a
"#
),
@r###"
── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─
The definition of `I` has an unbound type variable:
4│ I := Num.Int *
^
Tip: Type variables must be bound before the `:=`. Perhaps you intended
to add a type parameter to this type?
"###
);
test_report!(
multiple_wildcards_in_alias,
indoc!(
r#"
I : [A (Num.Int *), B (Num.Int *)]
a : I
a
"#
),
@r###"
── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─
The definition of `I` has 2 unbound type variables.
Here is one occurrence:
4│ I : [A (Num.Int *), B (Num.Int *)]
^
Tip: Type variables must be bound before the `:`. Perhaps you intended
to add a type parameter to this type?
"###
);
test_report!(
inference_var_in_alias,
indoc!(
r#"
I : Num.Int _
a : I
a
"#
),
@r###"
── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─
The definition of `I` has an unbound type variable:
4│ I : Num.Int _
^
Tip: Type variables must be bound before the `:`. Perhaps you intended
to add a type parameter to this type?
"###
);
test_report!(
unbound_var_in_alias,
indoc!(
r#"
I : Num.Int a
a : I
a
"#
),
@r###"
── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─
The definition of `I` has an unbound type variable:
4│ I : Num.Int a
^
Tip: Type variables must be bound before the `:`. Perhaps you intended
to add a type parameter to this type?
"###
);
test_report!(
ability_bad_type_parameter,
indoc!(
r#"
app "test" provides [] to "./platform"
Hash a b c has
hash : a -> U64 | a has Hash
"#
),
@r#"
── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─
The definition of the `Hash` ability includes type variables:
3│ Hash a b c has
^^^^^
Abilities cannot depend on type variables, but their member values
can!
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`Hash` is not used anywhere in your code.
3│ Hash a b c has
^^^^
If you didn't intend on using `Hash` then remove it so future readers of
your code don't wonder why it is there.
"#
);
test_report!(
alias_in_has_clause,
indoc!(
r#"
app "test" provides [hash] to "./platform"
Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
"#
),
@r#"
── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─
The type referenced in this "has" clause is not an ability:
3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
^^^^^^^^^
"#
);
test_report!(
shadowed_type_variable_in_has_clause,
indoc!(
r#"
app "test" provides [ab1] to "./platform"
Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
"#
),
@r#"
── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─
The `a` name is first defined here:
3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
^^^^^^^^^
But then it's defined a second time here:
3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
^^^^^^^^^
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_report!(
ability_shadows_ability,
indoc!(
r#"
app "test" provides [ab] to "./platform"
Ability has ab : a -> U64 | a has Ability
Ability has ab1 : a -> U64 | a has Ability
"#
),
@r#"
── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─
The `Ability` name is first defined here:
3│ Ability has ab : a -> U64 | a has Ability
^^^^^^^
But then it's defined a second time here:
5│ Ability has ab1 : a -> U64 | a has Ability
^^^^^^^
Since these abilities have the same name, it's easy to use the wrong
one on accident. Give one of them a new name.
"#
);
test_report!(
ability_member_does_not_bind_ability,
indoc!(
r#"
app "test" provides [] to "./platform"
Ability has ab : {} -> {}
"#
),
@r#"
── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─
The definition of the ability member `ab` does not include a `has` clause
binding a type variable to the ability `Ability`:
3│ Ability has ab : {} -> {}
^^
Ability members must include a `has` clause binding a type variable to
an ability, like
a has Ability
Otherwise, the function does not need to be part of the ability!
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`Ability` is not used anywhere in your code.
3│ Ability has ab : {} -> {}
^^^^^^^
If you didn't intend on using `Ability` then remove it so future readers
of your code don't wonder why it is there.
"#
);
test_report!(
ability_member_binds_parent_twice,
indoc!(
r#"
app "test" provides [] to "./platform"
Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq
"#
),
@r#"
── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────── /code/proj/Main.roc ─
The definition of the ability member `eq` includes multiple variables
bound to the `Eq`` ability:`
3│ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq
^^^^^^^^^^^^^^^^^^
Ability members can only bind one type variable to their parent
ability. Otherwise, I wouldn't know what type implements an ability by
looking at specializations!
Hint: Did you mean to only bind `a` to `Eq`?
"#
);
test_report!(
has_clause_not_on_toplevel,
indoc!(
r#"
app "test" provides [f] to "./platform"
Hash has hash : (a | a has Hash) -> Num.U64
f : a -> Num.U64 | a has Hash
"#
),
@r#"
── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─
A `has` clause is not allowed here:
3│ Hash has hash : (a | a has Hash) -> Num.U64
^^^^^^^^^^
`has` clauses can only be specified on the top-level type annotations.
── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─
The definition of the ability member `hash` does not include a `has`
clause binding a type variable to the ability `Hash`:
3│ Hash has hash : (a | a has Hash) -> Num.U64
^^^^
Ability members must include a `has` clause binding a type variable to
an ability, like
a has Hash
Otherwise, the function does not need to be part of the ability!
"#
);
test_report!(
ability_specialization_does_not_match_type,
indoc!(
r#"
app "test" provides [hash] to "./platform"
Hash has hash : a -> U64 | a has Hash
Id := U32 has [Hash {hash}]
hash = \@Id n -> n
"#
),
@r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with this specialization of `hash`:
7│ hash = \@Id n -> n
^^^^
This value is a declared specialization of type:
Id -> U32
But the type annotation on `hash` says it must match:
Id -> U64
"#
);
test_report!(
ability_specialization_is_incomplete,
indoc!(
r#"
app "test" provides [eq, le] to "./platform"
Eq has
eq : a, a -> Bool | a has Eq
le : a, a -> Bool | a has Eq
Id := U64 has [Eq {eq}]
eq = \@Id m, @Id n -> m == n
"#
),
@r###"
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
This type does not fully implement the `Eq` ability:
7│ Id := U64 has [Eq {eq}]
^^^^^^^
The following necessary members are missing implementations:
le
"###
);
test_report!(
ability_specialization_is_unused,
indoc!(
r#"
app "test" provides [hash] to "./platform"
Hash has
hash : a -> U64 | a has Hash
hash = \_ -> 0u64
"#
),
@r###"
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`hash` is not used anywhere in your code.
6│ hash = \_ -> 0u64
^^^^
If you didn't intend on using `hash` then remove it so future readers of
your code don't wonder why it is there.
"###
);
test_report!(
ability_specialization_is_duplicated,
indoc!(
r#"
app "test" provides [hash, One, Two] to "./platform"
Hash has
hash : a -> U64 | a has Hash
One := {} has [Hash {hash}]
Two := {} has [Hash {hash}]
hash = \_ -> 0u64
"#
),
// TODO: the error message here could be seriously improved!
@r###"
── OVERLOADED SPECIALIZATION ───────────────────────────── /code/proj/Main.roc ─
This ability member specialization is already claimed to specialize
another opaque type:
7│ Two := {} has [Hash {hash}]
^^^^
Previously, we found it to specialize `hash` for `One`.
Ability specializations can only provide implementations for one
opauqe type, since all opaque types are different!
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This specialization of `hash` is overly general:
9│ hash = \_ -> 0u64
^^^^
This value is a declared specialization of type:
a -> U64
But the type annotation on `hash` says it must match:
a -> U64 | a has Hash
Note: The specialized type is too general, and does not provide a
concrete type where a type variable is bound to an ability.
Specializations can only be made for concrete types. If you have a
generic implementation for this value, perhaps you don't need an
ability?
"###
);
test_report!(
#[ignore = "TODO does not error yet"]
ability_specialization_is_duplicated_with_type_mismatch,
indoc!(
r#"
app "test" provides [hash, One, Two] to "./platform"
Hash has
hash : a -> U64 | a has Hash
One := {} has [Hash {hash}]
Two := {} has [Hash {hash}]
hash = \@One _ -> 0u64
"#
),
@r###"
"###
);
test_report!(
ability_specialization_conflicting_specialization_types,
indoc!(
r#"
app "test" provides [eq] to "./platform"
Eq has
eq : a, a -> Bool | a has Eq
You := {} has [Eq {eq}]
AndI := {}
eq = \@You {}, @AndI {} -> False
"#
),
@r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with this specialization of `eq`:
9│ eq = \@You {}, @AndI {} -> False
^^
This value is a declared specialization of type:
You, AndI -> [False, True]
But the type annotation on `eq` says it must match:
You, You -> Bool
Tip: Type comparisons between an opaque type are only ever equal if
both types are the same opaque type. Did you mean to create an opaque
type by wrapping it? If I have an opaque type Age := U32 I can create
an instance of this opaque type by doing @Age 23.
"#
);
test_report!(
ability_specialization_checked_against_annotation,
indoc!(
r#"
app "test" provides [hash] to "./platform"
Hash has
hash : a -> U64 | a has Hash
Id := U64 has [Hash {hash}]
hash : Id -> U32
hash = \@Id n -> n
"#
),
@r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `hash` definition:
8│ hash : Id -> U32
9│ hash = \@Id n -> n
^
This `n` value is a:
U64
But the type annotation on `hash` says it should be:
U32
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with this specialization of `hash`:
9│ hash = \@Id n -> n
^^^^^^^^^^^
This value is a declared specialization of type:
Id -> U32
But the type annotation on `hash` says it must match:
Id -> U64
"#
);
test_report!(
ability_specialization_called_with_non_specializing,
indoc!(
r#"
app "test" provides [noGoodVeryBadTerrible] to "./platform"
Hash has
hash : a -> U64 | a has Hash
Id := U64 has [Hash {hash}]
hash = \@Id n -> n
User := {}
noGoodVeryBadTerrible =
{
nope: hash (@User {}),
notYet: hash (A 1),
}
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression has a type that does not implement the abilities it's expected to:
15│ notYet: hash (A 1),
^^^
Roc can't generate an implementation of the `#UserApp.Hash` ability for
[A (Num a)]b
Only builtin abilities can have generated implementations!
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression has a type that does not implement the abilities it's expected to:
14│ nope: hash (@User {}),
^^^^^^^^
The type `User` does not fully implement the ability `Hash`.
"###
);
test_report!(
ability_not_on_toplevel,
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
Hash has
hash : a -> U64 | a has Hash
123
"#
),
@r#"
── ABILITY NOT ON TOP-LEVEL ────────────────────────────── /code/proj/Main.roc ─
This ability definition is not on the top-level of a module:
4│> Hash has
5│> hash : a -> U64 | a has Hash
Abilities can only be defined on the top-level of a Roc module.
"#
);
test_report!(
expression_generalization_to_ability_is_an_error,
indoc!(
r#"
app "test" provides [hash, hashable] to "./platform"
Hash has
hash : a -> U64 | a has Hash
Id := U64 has [Hash {hash}]
hash = \@Id n -> n
hashable : a | a has Hash
hashable = @Id 15
"#
),
@r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `hashable` definition:
9│ hashable : a | a has Hash
10│ hashable = @Id 15
^^^^^^
This Id opaque wrapping has the type:
Id
But the type annotation on `hashable` says it should be:
a | a has Hash
Tip: The type annotation uses the type variable `a` to say that this
definition can produce any value implementing the `Hash` ability. But in
the body I see that it will only produce a `Id` value of a single
specific type. Maybe change the type annotation to be more specific?
Maybe change the code to be more general?
"#
);
test_report!(
ability_value_annotations_are_an_error,
indoc!(
r#"
app "test" provides [result] to "./platform"
Hash has
hash : a -> U64 | a has Hash
mulHashes : Hash, Hash -> U64
mulHashes = \x, y -> hash x * hash y
Id := U64 has [Hash {hash: hashId}]
hashId = \@Id n -> n
Three := {} has [Hash {hash: hashThree}]
hashThree = \@Three _ -> 3
result = mulHashes (@Id 100) (@Three {})
"#
),
@r#"
── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─
You are attempting to use the ability `Hash` as a type directly:
6│ mulHashes : Hash, Hash -> U64
^^^^
Abilities can only be used in type annotations to constrain type
variables.
Hint: Perhaps you meant to include a `has` annotation, like
a has Hash
── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─
You are attempting to use the ability `Hash` as a type directly:
6│ mulHashes : Hash, Hash -> U64
^^^^
Abilities can only be used in type annotations to constrain type
variables.
Hint: Perhaps you meant to include a `has` annotation, like
b has Hash
"#
);
test_report!(
branches_have_more_cases_than_condition,
indoc!(
r#"
foo : Bool -> Str
foo = \bool ->
when bool is
True -> "true"
False -> "false"
Wat -> "surprise!"
foo
"#
),
@r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
The branches of this `when` expression don't match the condition:
6│> when bool is
7│ True -> "true"
8│ False -> "false"
9│ Wat -> "surprise!"
This `bool` value is a:
Bool
But the branch patterns have type:
[False, True, Wat]
The branches must be cases of the `when` condition's type!
"#
);
// from https://github.com/rtfeldman/roc/commit/1372737f5e53ee5bb96d7e1b9593985e5537023a
// There was a bug where this reported UnusedArgument("val")
// since it was used only in the returned function only.
//
// we want this to not give any warnings/errors!
test_report!(
always_function,
indoc!(
r#"
always = \val -> \_ -> val
always
"#
),
@""
);
test_report!(
imports_missing_comma,
indoc!(
r#"
app "test-missing-comma"
packages { pf: "platform/main.roc" }
imports [pf.Task Base64]
provides [main, @Foo] to pf
"#
),
@r#"
── WEIRD IMPORTS ────────────────────────── tmp/imports_missing_comma/Test.roc ─
I am partway through parsing a imports list, but I got stuck here:
2│ packages { pf: "platform/main.roc" }
3│ imports [pf.Task Base64]
^
I am expecting a comma or end of list, like
imports [Shape, Vector]"#
);
test_report!(
not_enough_cases_for_open_union,
indoc!(
r#"
foo : [A, B]a -> Str
foo = \it ->
when it is
A -> ""
foo
"#
),
@r#"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
6│> when it is
7│> A -> ""
Other possibilities include:
B
_
I would have to crash if I saw one of those! Add branches for them!
"#
);
test_report!(
issue_2778_specialization_is_not_a_redundant_pattern,
indoc!(
r#"
formatColor = \color ->
when color is
Red -> "red"
Yellow -> "yellow"
_ -> "unknown"
Red |> formatColor |> Str.concat (formatColor Orange)
"#
),
@"" // no problem
);
test_report!(
nested_specialization,
indoc!(
r#"
app "test" provides [main] to "./platform"
Default has default : {} -> a | a has Default
main =
A := {} has [Default {default}]
default = \{} -> @A {}
default {}
"#
),
@r#"
── SPECIALIZATION NOT ON TOP-LEVEL ─────────────────────── /code/proj/Main.roc ─
This specialization of the `default` ability member is in a nested
scope:
7│ default = \{} -> @A {}
^^^^^^^
Specializations can only be defined on the top-level of a module.
"#
);
test_report!(
recursion_var_specialization_error,
indoc!(
r#"
Job a : [Job (List (Job a))]
job : Job Str
when job is
Job lst -> lst == ""
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `isEq` has an unexpected type:
9│ Job lst -> lst == ""
^^
The argument is a string of type:
Str
But `isEq` needs its 2nd argument to be:
List [Job ∞] as ∞
"###
);
test_report!(
#[ignore]
type_error_in_apply_is_circular,
indoc!(
r#"
app "test" imports [Set] provides [go] to "./platform"
S a : { set : Set.Set a }
go : a, S a -> Result (List a) *
go = \goal, model ->
if goal == goal
then Ok []
else
new = { model & set : Set.remove goal model.set }
go goal new
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `remove` has an unexpected type:
10│ new = { model & set : Set.remove goal model.set }
^^^^
This `goal` value is a:
a
But `remove` needs the 1st argument to be:
Set k
Tip: The type annotation uses the type variable `a` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `Set` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `new`:
10│ new = { model & set : Set.remove goal model.set }
^^^
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.
{ set : Set ∞ }
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
I'm inferring a weird self-referential type for `goal`:
6│ go = \goal, model ->
^^^^
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.
Set ∞
"###
);
test_report!(
cycle_through_non_function,
indoc!(
r#"
force : ({} -> I64) -> I64
force = \eval -> eval {}
t1 = \_ -> force (\_ -> t2)
t2 = t1 {}
t2
"#
),
@r#"
── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─
The `t1` definition is causing a very tricky infinite loop:
7│ t1 = \_ -> force (\_ -> t2)
^^
The `t1` value depends on itself through the following chain of
definitions:
┌─────┐
│ t1
│ ↓
│ t2
└─────┘
"#
);
test_report!(
function_does_not_implement_encoding,
indoc!(
r#"
app "test" imports [Encode] provides [main] to "./platform"
main = Encode.toEncoder \x -> x
"#
),
@r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression has a type that does not implement the abilities it's expected to:
3│ main = Encode.toEncoder \x -> x
^^^^^^^
Roc can't generate an implementation of the `Encode.Encoding` ability
for
a -> a
Note: `Encoding` cannot be generated for functions.
"#
);
test_report!(
nested_opaque_does_not_implement_encoding,
indoc!(
r#"
app "test" imports [Encode] provides [main] to "./platform"
A := {}
main = Encode.toEncoder { x: @A {} }
"#
),
// TODO: this error message is quite unfortunate. We should remove the duplication, and
// also support regions that point to things in other modules. See also https://github.com/rtfeldman/roc/issues/3056.
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression has a type that does not implement the abilities it's expected to:
4│ main = Encode.toEncoder { x: @A {} }
^^^^^^^^^^^^
Roc can't generate an implementation of the `Encode.Encoding` ability
for
{ x : A }
In particular, an implementation for
A
cannot be generated.
Tip: `A` does not implement `Encoding`. Consider adding a custom
implementation or `has Encode.Encoding` to the definition of `A`.
"###
);
test_report!(
cycle_through_non_function_top_level,
indoc!(
r#"
app "test" provides [t2] to "./platform"
force : ({} -> I64) -> I64
force = \eval -> eval {}
t1 = \_ -> force (\_ -> t2)
t2 = t1 {}
"#
),
@r#"
── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─
The `t1` definition is causing a very tricky infinite loop:
6│ t1 = \_ -> force (\_ -> t2)
^^
The `t1` value depends on itself through the following chain of
definitions:
┌─────┐
│ t1
│ ↓
│ t2
└─────┘
"#
);
test_report!(
opaque_ability_impl_not_found_shorthand_syntax,
indoc!(
r#"
app "test" provides [A] to "./platform"
Eq has eq : a, a -> U64 | a has Eq
A := U8 has [Eq {eq}]
"#
),
@r###"
── IMPLEMENTATION NOT FOUND ────────────────────────────── /code/proj/Main.roc ─
An implementation of `eq` could not be found in this scope:
5│ A := U8 has [Eq {eq}]
^^
Tip: consider adding a value of name `eq` in this scope, or using
another variable that implements this ability member, like
{ eq: myeq }
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
This type does not fully implement the `Eq` ability:
5│ A := U8 has [Eq {eq}]
^^^^^^^
The following necessary members are missing implementations:
eq
"###
);
test_report!(
opaque_ability_impl_not_found,
indoc!(
r#"
app "test" provides [A, myEq] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
A := U8 has [ Eq {eq: aEq} ]
myEq = \m, n -> m == n
"#
),
@r###"
── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─
Nothing is named `aEq` in this scope.
5│ A := U8 has [ Eq {eq: aEq} ]
^^^
Did you mean one of these?
Eq
myEq
eq
U8
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
This type does not fully implement the `Eq` ability:
5│ A := U8 has [ Eq {eq: aEq} ]
^^^^^^^^^^^^
The following necessary members are missing implementations:
eq
"###
);
test_report!(
opaque_ability_impl_optional,
indoc!(
r#"
app "test" provides [A, myEq] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
A := U8 has [ Eq {eq ? aEq} ]
myEq = \m, n -> m == n
"#
),
@r###"
── OPTIONAL ABILITY IMPLEMENTATION ─────────────────────── /code/proj/Main.roc ─
Ability implementations cannot be optional:
5│ A := U8 has [ Eq {eq ? aEq} ]
^^^^^^^^
Custom implementations must be supplied fully.
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
This type does not fully implement the `Eq` ability:
5│ A := U8 has [ Eq {eq ? aEq} ]
^^^^^^^^^^^^^
The following necessary members are missing implementations:
eq
"###
);
test_report!(
opaque_builtin_ability_impl_optional,
indoc!(
r#"
app "test"
imports [Encode.{ Encoding }]
provides [A, myEncoder] to "./platform"
A := U8 has [ Encoding {toEncoder ? myEncoder} ]
myEncoder = 1
"#
),
@r###"
── OPTIONAL ABILITY IMPLEMENTATION ─────────────────────── /code/proj/Main.roc ─
Ability implementations cannot be optional:
5│ A := U8 has [ Encoding {toEncoder ? myEncoder} ]
^^^^^^^^^^^^^^^^^^^^^
Custom implementations must be supplied fully.
Hint: if you want this implementation to be derived, don't include a
record of implementations. For example, has [Encoding] will attempt
to derive `Encoding`
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
This type does not fully implement the `Encoding` ability:
5│ A := U8 has [ Encoding {toEncoder ? myEncoder} ]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following necessary members are missing implementations:
toEncoder
"###
);
test_report!(
opaque_ability_impl_qualified,
indoc!(
r#"
app "test" provides [A] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
A := U8 has [ Eq {eq : Bool.eq} ]
"#
),
@r###"
── QUALIFIED ABILITY IMPLEMENTATION ────────────────────── /code/proj/Main.roc ─
This ability implementation is qualified:
5│ A := U8 has [ Eq {eq : Bool.eq} ]
^^^^^^^
Custom implementations must be defined in the local scope, and
unqualified.
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
This type does not fully implement the `Eq` ability:
5│ A := U8 has [ Eq {eq : Bool.eq} ]
^^^^^^^^^^^^^^^^^
The following necessary members are missing implementations:
eq
"###
);
test_report!(
opaque_ability_impl_not_identifier,
indoc!(
r#"
app "test" provides [A] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
A := U8 has [ Eq {eq : \m, n -> m == n} ]
"#
),
@r###"
── ABILITY IMPLEMENTATION NOT IDENTIFIER ───────────────── /code/proj/Main.roc ─
This ability implementation is not an identifier:
5│ A := U8 has [ Eq {eq : \m, n -> m == n} ]
^^^^^^^^^^^^^^^
Custom ability implementations defined in this position can only be
unqualified identifiers, not arbitrary expressions.
Tip: consider defining this expression as a variable.
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
This type does not fully implement the `Eq` ability:
5│ A := U8 has [ Eq {eq : \m, n -> m == n} ]
^^^^^^^^^^^^^^^^^^^^^^^^^
The following necessary members are missing implementations:
eq
"###
);
test_report!(
opaque_ability_impl_duplicate,
indoc!(
r#"
app "test" provides [A] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
A := U8 has [ Eq {eq: eqA, eq: eqA} ]
eqA = \@A m, @A n -> m == n
"#
),
@r###"
── DUPLICATE IMPLEMENTATION ────────────────────────────── /code/proj/Main.roc ─
This ability member implementation is duplicate:
5│ A := U8 has [ Eq {eq: eqA, eq: eqA} ]
^^^^^^^
The first implementation was defined here:
5│ A := U8 has [ Eq {eq: eqA, eq: eqA} ]
^^^^^^^
Only one custom implementation can be defined for an ability member.
"###
);
test_report!(
implements_type_not_ability,
indoc!(
r#"
app "test" provides [A, Foo] to "./platform"
Foo := {}
A := U8 has [ Foo {} ]
"#
),
@r###"
── NOT AN ABILITY ──────────────────────────────────────── /code/proj/Main.roc ─
This identifier is not an ability in scope:
5│ A := U8 has [ Foo {} ]
^^^
Only abilities can be implemented.
"###
);
test_report!(
derive_non_builtin_ability,
indoc!(
r#"
app "test" provides [A] to "./platform"
Ab has ab : a -> a | a has Ab
A := {} has [Ab]
"#
),
@r#"
── ILLEGAL DERIVE ──────────────────────────────────────── /code/proj/Main.roc ─
This ability cannot be derived:
5│ A := {} has [Ab]
^^
Only builtin abilities can be derived.
Note: The builtin abilities are `Encode.Encoding`
"#
);
test_report!(
has_encoding_for_function,
indoc!(
r#"
app "test" imports [Encode] provides [A] to "./platform"
A a := a -> a has [Encode.Encoding]
"#
),
@r#"
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
Roc can't derive an implementation of the `Encode.Encoding` for `A`:
3│ A a := a -> a has [Encode.Encoding]
^^^^^^^^^^^^^^^
Note: `Encoding` cannot be generated for functions.
Tip: You can define a custom implementation of `Encode.Encoding` for `A`.
"#
);
test_report!(
has_encoding_for_non_encoding_alias,
indoc!(
r#"
app "test" imports [Encode] provides [A] to "./platform"
A := B has [Encode.Encoding]
B := {}
"#
),
@r#"
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─
Roc can't derive an implementation of the `Encode.Encoding` for `A`:
3│ A := B has [Encode.Encoding]
^^^^^^^^^^^^^^^
Tip: `B` does not implement `Encoding`. Consider adding a custom
implementation or `has Encode.Encoding` to the definition of `B`.
Tip: You can define a custom implementation of `Encode.Encoding` for `A`.
"#
);
test_report!(
has_encoding_for_other_has_encoding,
indoc!(
r#"
app "test" imports [Encode] provides [A] to "./platform"
A := B has [Encode.Encoding]
B := {} has [Encode.Encoding]
"#
),
@"" // no error
);
test_report!(
has_encoding_for_recursive_deriving,
indoc!(
r#"
app "test" imports [Encode] provides [MyNat] to "./platform"
MyNat := [S MyNat, Z] has [Encode.Encoding]
"#
),
@"" // no error
);
test_report!(
shadowing_top_level_scope,
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main = 1
main = \n -> n + 2
"#
),
@r###"
── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─
The `main` name is first defined here:
3│ main = 1
^^^^
But then it's defined a second time here:
5│ main = \n -> n + 2
^^^^
Since these variables have the same name, it's easy to use the wrong
one on accident. Give one of them a new name.
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
5│ main = \n -> n + 2
^^^^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
"###
);
test_report!(
issue_1755,
indoc!(
r#"
Handle := {}
await : Result a err, (a -> Result b err) -> Result b err
open : {} -> Result Handle *
close : Handle -> Result {} *
withOpen : (Handle -> Result {} *) -> Result {} *
withOpen = \callback ->
handle <- await (open {})
{} <- await (callback handle)
close handle
withOpen
"#
),
@r#"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the body of the `withOpen` definition:
10│ withOpen : (Handle -> Result {} *) -> Result {} *
11│ withOpen = \callback ->
12│> handle <- await (open {})
13│> {} <- await (callback handle)
14│> close handle
The type annotation on `withOpen` says this `await` call should have the
type:
Result {} *
However, the type of this `await` call is connected to another type in a
way that isn't reflected in this annotation.
Tip: Any connection between types must use a named type variable, not
a `*`! Maybe the annotation on `withOpen` should have a named type
variable in place of the `*`?
"#
);
test_report!(
recursive_body_and_annotation_with_inference_disagree,
indoc!(
r#"
f : _ -> (_ -> Str)
f = \_ -> if True then {} else f {}
f
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This expression is used in an unexpected way:
5│ f = \_ -> if True then {} else f {}
^^^^^^^^^^^^^^^^^^^^^^^^^
It is a value of type:
{}
But you are trying to use it as:
a -> Str
"###
);
test_report!(
same_phantom_types_unify,
indoc!(
r#"
F a b := b
foo : F Str Str -> {}
x : F Str Str
foo x
"#
),
@r"" // okay
);
test_report!(
different_phantom_types,
indoc!(
r#"
F a b := b
foo : F Str Str -> {}
x : F U8 Str
foo x
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 1st argument to `foo` has an unexpected type:
10│ foo x
^
This `x` value is a:
F U8 Str
But `foo` needs its 1st argument to be:
F Str Str
"###
);
test_report!(
#[ignore = "TODO This should be a type error"]
phantom_type_bound_to_ability_not_implementing,
indoc!(
r#"
app "test" provides [x] to "./platform"
Foo has foo : a -> a | a has Foo
F a b := b | a has Foo
Hash := {}
x : F Hash {}
"#
),
@r###"
"###
);
test_report!(
int_literals_cannot_fit_in_same_type,
indoc!(
r#"
0x80000000000000000000000000000000 == -0x80000000000000000000000000000000
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `isEq` has an unexpected type:
4│ 0x80000000000000000000000000000000 == -0x80000000000000000000000000000000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The argument is an integer of type:
I128
But `isEq` needs its 2nd argument to be:
U128
"###
);
test_report!(
num_literals_cannot_fit_in_same_type,
indoc!(
r#"
170141183460469231731687303715884105728 == -170141183460469231731687303715884105728
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `isEq` has an unexpected type:
4│ 170141183460469231731687303715884105728 == -170141183460469231731687303715884105728
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The argument is a number of type:
I128 or Dec
But `isEq` needs its 2nd argument to be:
U128
"###
);
test_report!(
recursive_alias_cannot_leak_into_recursive_opaque,
indoc!(
r#"
OList := [Nil, Cons {} OList]
AList : [Nil, Cons {} AList]
alist : AList
olist : OList
olist =
when alist is
Nil -> @OList Nil
Cons _ lst -> lst
olist
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
Something is off with the 2nd branch of this `when` expression:
10│ olist : OList
11│ olist =
12│> when alist is
13│> Nil -> @OList Nil
14│> Cons _ lst -> lst
This `lst` value is a:
[Cons {} ∞, Nil] as ∞
But the type annotation on `olist` says it should be:
OList
Tip: Type comparisons between an opaque type are only ever equal if
both types are the same opaque type. Did you mean to create an opaque
type by wrapping it? If I have an opaque type Age := U32 I can create
an instance of this opaque type by doing @Age 23.
"###
);
test_report!(
opaque_wrap_function_mismatch,
indoc!(
r#"
A := U8
List.map [1u16, 2u16, 3u16] @A
"#
),
@r###"
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
This 2nd argument to `map` has an unexpected type:
5│ List.map [1u16, 2u16, 3u16] @A
^^
This A opaque wrapping has the type:
U8 -> A
But `map` needs its 2nd argument to be:
U16 -> A
"###
);
test_report!(
symbols_not_bound_in_all_patterns,
indoc!(
r#"
when A "" is
A x | B y -> x
"#
),
@r###"
── NAME NOT BOUND IN ALL PATTERNS ──────────────────────── /code/proj/Main.roc ─
`x` is not bound in all patterns of this `when` branch
5│ A x | B y -> x
^
Identifiers introduced in a `when` branch must be bound in all patterns
of the branch. Otherwise, the program would crash when it tries to use
an identifier that wasn't bound!
── NAME NOT BOUND IN ALL PATTERNS ──────────────────────── /code/proj/Main.roc ─
`y` is not bound in all patterns of this `when` branch
5│ A x | B y -> x
^
Identifiers introduced in a `when` branch must be bound in all patterns
of the branch. Otherwise, the program would crash when it tries to use
an identifier that wasn't bound!
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`y` is not used anywhere in your code.
5│ A x | B y -> x
^
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_report!(
flip_flop_catch_all_branches_not_exhaustive,
indoc!(
r#"
\x -> when x is
A B _ -> ""
A _ C -> ""
"#
),
@r###"
── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─
This `when` does not cover all the possibilities:
4│> \x -> when x is
5│> A B _ -> ""
6│> A _ C -> ""
Other possibilities include:
A _ _
I would have to crash if I saw one of those! Add branches for them!
"###
);
test_report!(
call_with_underscore_identifier,
indoc!(
r#"
f = \x, y, z -> x + y + z
f 1 _ 1
"#
),
@r###"
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
Underscores are not allowed in identifier names:
6│ f 1 _ 1
I recommend using camelCase, it is the standard in the Roc ecosystem.
"###
);
test_report!(
destructure_assignment_introduces_no_variables_nested,
indoc!(
r#"
Pair _ _ = Pair 0 1
_ = Pair 0 1
{} = {}
Foo = Foo
0
"#
),
@r###"
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
4│ Pair _ _ = Pair 0 1
^^^^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
6│ _ = Pair 0 1
^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
8│ {} = {}
^^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
10│ Foo = Foo
^^^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
"###
);
test_report!(
destructure_assignment_introduces_no_variables_nested_toplevel,
indoc!(
r#"
app "test" provides [] to "./platform"
Pair _ _ = Pair 0 1
_ = Pair 0 1
{} = {}
Foo = Foo
"#
),
@r###"
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
3│ Pair _ _ = Pair 0 1
^^^^^^^^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
5│ _ = Pair 0 1
^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
7│ {} = {}
^^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─
This destructure assignment doesn't introduce any new variables:
9│ Foo = Foo
^^^
If you don't need to use the value on the right-hand-side of this
assignment, consider removing the assignment. Since Roc is purely
functional, assignments that don't introduce variables cannot affect a
program's behavior!
"###
);
test_report!(
unused_shadow_specialization,
indoc!(
r#"
app "test" provides [hash, Id] to "./platform"
Hash has hash : a -> U64 | a has Hash
Id := {}
hash = \@Id _ -> 0
"#
),
@r###"
── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─
`hash` is not used anywhere in your code.
7│ hash = \@Id _ -> 0
^^^^
If you didn't intend on using `hash` then remove it so future readers of
your code don't wonder why it is there.
"###
);
test_report!(
specialization_for_wrong_type,
indoc!(
r#"
app "test" provides [hash, Id, Id2] to "./platform"
Hash has hash : a -> U64 | a has Hash
Id := {} has [Hash {hash}]
Id2 := {}
hash = \@Id2 _ -> 0
"#
),
@r###"
── WRONG SPECIALIZATION TYPE ───────────────────────────── /code/proj/Main.roc ─
This specialization of `hash` is not for the expected type:
8│ hash = \@Id2 _ -> 0
^^^^
It was previously claimed to be a specialization for `Id`, but was
determined to actually specialize `Id2`!
"###
);
}