mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 22:09:09 +00:00
4777 lines
142 KiB
Rust
4777 lines
142 KiB
Rust
#[macro_use]
|
||
extern crate pretty_assertions;
|
||
#[macro_use]
|
||
extern crate indoc;
|
||
extern crate bumpalo;
|
||
extern crate roc_reporting;
|
||
|
||
mod helpers;
|
||
|
||
#[cfg(test)]
|
||
mod test_reporting {
|
||
use crate::helpers::test_home;
|
||
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
||
use bumpalo::Bump;
|
||
use roc_module::symbol::{Interns, ModuleId};
|
||
use roc_mono::ir::{Procs, Stmt};
|
||
use roc_mono::layout::LayoutCache;
|
||
use roc_reporting::report::{
|
||
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
|
||
CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
|
||
WHITE_CODE, YELLOW_CODE,
|
||
};
|
||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||
use roc_solve::solve;
|
||
use roc_types::pretty_print::name_all_type_vars;
|
||
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<'b>(doc: RocDocBuilder<'b>) -> Report<'b> {
|
||
Report {
|
||
title: "".to_string(),
|
||
doc,
|
||
filename: filename_from_string(r"\code\proj\Main.roc"),
|
||
}
|
||
}
|
||
|
||
fn infer_expr_help<'a>(
|
||
arena: &'a Bump,
|
||
expr_src: &'a str,
|
||
) -> Result<
|
||
(
|
||
Vec<solve::TypeError>,
|
||
Vec<roc_problem::can::Problem>,
|
||
Vec<roc_mono::ir::MonoProblem>,
|
||
ModuleId,
|
||
Interns,
|
||
),
|
||
ParseErrOut<'a>,
|
||
> {
|
||
let CanExprOut {
|
||
loc_expr,
|
||
output,
|
||
var_store,
|
||
var,
|
||
constraint,
|
||
home,
|
||
mut interns,
|
||
problems: can_problems,
|
||
..
|
||
} = can_expr(arena, expr_src)?;
|
||
let mut subs = Subs::new(var_store.into());
|
||
|
||
for (var, name) in output.introduced_variables.name_by_var {
|
||
subs.rigid_var(var, name);
|
||
}
|
||
|
||
let mut unify_problems = Vec::new();
|
||
let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
||
|
||
name_all_type_vars(var, &mut subs);
|
||
|
||
let mut mono_problems = Vec::new();
|
||
|
||
// MONO
|
||
|
||
if unify_problems.is_empty() && can_problems.is_empty() {
|
||
let arena = Bump::new();
|
||
|
||
// Compile and add all the Procs before adding main
|
||
let mut procs = Procs::default();
|
||
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
|
||
|
||
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
||
let mut layout_cache = LayoutCache::default();
|
||
let mut mono_env = roc_mono::ir::Env {
|
||
arena: &arena,
|
||
subs: &mut subs,
|
||
problems: &mut mono_problems,
|
||
home,
|
||
ident_ids: &mut ident_ids,
|
||
ptr_bytes: 8,
|
||
};
|
||
let _mono_expr = Stmt::new(
|
||
&mut mono_env,
|
||
loc_expr.value,
|
||
var,
|
||
&mut procs,
|
||
&mut layout_cache,
|
||
);
|
||
}
|
||
|
||
Ok((unify_problems, can_problems, mono_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 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_parse_problem(filename.clone(), src.as_bytes());
|
||
let doc = parse_problem(&alloc, filename, 0, problem);
|
||
|
||
callback(doc.pretty(&alloc).append(alloc.line()), buf)
|
||
}
|
||
Ok((type_problems, can_problems, mono_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, filename.clone(), problem.clone());
|
||
reports.push(report);
|
||
}
|
||
|
||
for problem in type_problems {
|
||
let report = type_problem(&alloc, filename.clone(), problem.clone());
|
||
reports.push(report);
|
||
}
|
||
|
||
for problem in mono_problems {
|
||
let report = mono_problem(&alloc, 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 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::CiWrite::new(buf))
|
||
.expect("list_reports")
|
||
};
|
||
|
||
list_reports(&arena, src, &mut buf, callback);
|
||
|
||
// convenient to copy-paste the generated message
|
||
if true {
|
||
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);
|
||
}
|
||
|
||
fn human_readable(str: &str) -> String {
|
||
str.replace(RED_CODE, "<red>")
|
||
.replace(WHITE_CODE, "<white>")
|
||
.replace(BLUE_CODE, "<blue>")
|
||
.replace(YELLOW_CODE, "<yellow>")
|
||
.replace(GREEN_CODE, "<green>")
|
||
.replace(CYAN_CODE, "<cyan>")
|
||
.replace(MAGENTA_CODE, "<magenta>")
|
||
.replace(RESET_CODE, "<reset>")
|
||
.replace(BOLD_CODE, "<bold>")
|
||
.replace(UNDERLINE_CODE, "<underline>")
|
||
}
|
||
|
||
#[test]
|
||
fn value_not_exposed() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
List.foobar 1 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
The List module does not expose a foobar value:
|
||
|
||
1│ List.foobar 1 2
|
||
^^^^^^^^^^^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn report_unused_def() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = 1
|
||
y = 2
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
`y` is not used anywhere in your code.
|
||
|
||
2│ y = 2
|
||
^
|
||
|
||
If you didn't intend on using `y` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn report_shadowing() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
i = 1
|
||
|
||
s = \i ->
|
||
i + 1
|
||
|
||
s i
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
The `i` name is first defined here:
|
||
|
||
1│ i = 1
|
||
^
|
||
|
||
But then it's defined a second time here:
|
||
|
||
3│ s = \i ->
|
||
^
|
||
|
||
Since these variables have the same name, it's easy to use the wrong
|
||
one on accident. Give one of them a new name.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn report_shadowing_in_annotation() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Booly : [ Yes, No ]
|
||
|
||
Booly : [ Yes, No, Maybe ]
|
||
|
||
x =
|
||
No
|
||
|
||
x
|
||
"#
|
||
),
|
||
// Booly is called a "variable"
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
The `Booly` name is first defined here:
|
||
|
||
1│ Booly : [ Yes, No ]
|
||
^^^^^^^^^^^^^^^^^^^
|
||
|
||
But then it's defined a second time here:
|
||
|
||
3│ Booly : [ Yes, No, Maybe ]
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Since these variables have the same name, it's easy to use the wrong
|
||
one on accident. Give one of them a new name.
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
`Booly` is not used anywhere in your code.
|
||
|
||
1│ Booly : [ Yes, No ]
|
||
^^^^^^^^^^^^^^^^^^^
|
||
|
||
If you didn't intend on using `Booly` then remove it so future readers
|
||
of your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
// #[test]
|
||
// fn report_multi_line_shadowing_in_annotation() {
|
||
// report_problem_as(
|
||
// indoc!(
|
||
// r#"
|
||
// Booly :
|
||
// [
|
||
// Yes,
|
||
// No
|
||
// ]
|
||
//
|
||
// Booly :
|
||
// [
|
||
// Yes,
|
||
// No,
|
||
// Maybe
|
||
// ]
|
||
//
|
||
// x =
|
||
// No
|
||
//
|
||
// x
|
||
// "#
|
||
// ),
|
||
// indoc!(
|
||
// r#"
|
||
// Booly is first defined here:
|
||
//
|
||
// 1│> Booly :
|
||
// 2│> [
|
||
// 3│> Yes,
|
||
// 4│> No
|
||
// 5│> ]
|
||
//
|
||
// But then it's defined a second time here:
|
||
//
|
||
// 7 │> Booly :
|
||
// 8 │> [
|
||
// 9 │> Yes,
|
||
// 10│> No,
|
||
// 11│> Maybe
|
||
// 12│> ]
|
||
//
|
||
// Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#
|
||
// ),
|
||
// )
|
||
// }
|
||
|
||
// #[test]
|
||
// fn report_unsupported_top_level_def() {
|
||
// report_problem_as(
|
||
// indoc!(
|
||
// r#"
|
||
// x = 1
|
||
//
|
||
// 5 = 2 + 1
|
||
//
|
||
// x
|
||
// "#
|
||
// ),
|
||
// indoc!(r#" "#),
|
||
// )
|
||
// }
|
||
|
||
#[test]
|
||
fn report_precedence_problem_single_line() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"x = 1
|
||
y =
|
||
if selectedId != thisId == adminsId then
|
||
4
|
||
|
||
else
|
||
5
|
||
|
||
{ x, y }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
Using != and == together requires parentheses, to clarify how they
|
||
should be grouped.
|
||
|
||
3│ if selectedId != thisId == adminsId then
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn unused_undefined_argument() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
foo = { x: 1 == 1, y: 0x4 }
|
||
|
||
baz = 3
|
||
|
||
main : Str
|
||
main =
|
||
when foo.y is
|
||
4 -> bar baz "yay"
|
||
_ -> "nay"
|
||
|
||
main
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `bar` value
|
||
|
||
8│ 4 -> bar baz "yay"
|
||
^^^
|
||
|
||
these names seem close though:
|
||
|
||
baz
|
||
Nat
|
||
Str
|
||
U8
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn report_precedence_problem_multiline() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
if
|
||
1
|
||
== 2
|
||
== 3
|
||
then
|
||
2
|
||
|
||
else
|
||
3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
Using more than one == like this requires parentheses, to clarify how
|
||
things should be grouped.
|
||
|
||
2│> 1
|
||
3│> == 2
|
||
4│> == 3
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
// #[test]
|
||
// fn report_unused_argument() {
|
||
// report_problem_as(
|
||
// indoc!(r#"
|
||
// y = 9
|
||
//
|
||
// box = \class, htmlChildren ->
|
||
// div [ class ] []
|
||
//
|
||
// div = 4
|
||
//
|
||
// box "wizard" []
|
||
// "#),
|
||
// indoc!(
|
||
// r#"
|
||
// box doesn't use htmlChildren.
|
||
//
|
||
// 3│ box = \class, htmlChildren ->
|
||
//
|
||
// If you don't need htmlChildren, then you can just remove it. However, if you really do need htmlChildren as an argument of box, prefix it with an underscore, like this: "_htmlChildren". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."#
|
||
// ),
|
||
// );
|
||
// }
|
||
|
||
// #[test]
|
||
// fn report_unused_import() {
|
||
// report_problem_as(
|
||
// indoc!(r#"
|
||
// interface Report
|
||
// exposes [
|
||
// plainText,
|
||
// emText
|
||
// ]
|
||
// imports [
|
||
// Symbol.{ Interns }
|
||
// ]
|
||
//
|
||
// plainText = \str -> PlainText str
|
||
//
|
||
// emText = \str -> EmText str
|
||
// "#),
|
||
// indoc!(
|
||
// r#"
|
||
// Nothing from Symbol is used in this module.
|
||
//
|
||
// 6│ imports [
|
||
// 7│ Symbol.{ Interns }
|
||
// ^^^^^^
|
||
// 8│ ]
|
||
//
|
||
// Since Symbol isn't used, you don't need to import it."#
|
||
// ),
|
||
// );
|
||
// }
|
||
|
||
#[test]
|
||
fn report_value_color() {
|
||
let src: &str = indoc!(
|
||
r#"
|
||
activityIndicatorLarge = div
|
||
|
||
view activityIndicatorLarge
|
||
"#
|
||
);
|
||
|
||
let arena = Bump::new();
|
||
let (_type_problems, _can_problems, _mono_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, _mono_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>── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────<reset>
|
||
|
||
I cannot find a `theAdmin` value
|
||
|
||
<cyan>3<reset><cyan>│<reset> <white>theAdmin<reset>
|
||
<red>^^^^^^^^<reset>
|
||
|
||
these names seem close though:
|
||
|
||
Result
|
||
Num
|
||
Set
|
||
U8
|
||
"#
|
||
),
|
||
);
|
||
}
|
||
|
||
// #[test]
|
||
// fn shadowing_type_alias() {
|
||
// report_problem_as(
|
||
// indoc!(
|
||
// r#"
|
||
// foo : I64 as I64
|
||
// foo = 42
|
||
//
|
||
// foo
|
||
// "#
|
||
// ),
|
||
// indoc!(
|
||
// r#"
|
||
// You cannot mix (!=) and (==) without parentheses
|
||
//
|
||
// 3│ if selectedId != thisId == adminsId then
|
||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
//
|
||
// "#
|
||
// ),
|
||
// )
|
||
// }
|
||
|
||
// #[test]
|
||
// fn invalid_as_type_alias() {
|
||
// report_problem_as(
|
||
// indoc!(
|
||
// r#"
|
||
// foo : I64 as a
|
||
// foo = 42
|
||
//
|
||
// foo
|
||
// "#
|
||
// ),
|
||
// indoc!(
|
||
// r#"
|
||
// You cannot mix (!=) and (==) without parentheses
|
||
//
|
||
// 3│ if selectedId != thisId == adminsId then
|
||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
//
|
||
// "#
|
||
// ),
|
||
// )
|
||
// }
|
||
|
||
#[test]
|
||
fn if_condition_not_bool() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
if "foo" then 2 else 3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This `if` condition needs to be a Bool:
|
||
|
||
1│ if "foo" then 2 else 3
|
||
^^^^^
|
||
|
||
Right now it’s a string of type:
|
||
|
||
Str
|
||
|
||
But I need every `if` condition to evaluate to a Bool—either `True` or
|
||
`False`.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn when_if_guard() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
2 if 1 -> 0x0
|
||
_ -> 0x1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This `if` guard condition needs to be a Bool:
|
||
|
||
1│ when 1 is
|
||
2│> 2 if 1 -> 0x0
|
||
3│ _ -> 0x1
|
||
|
||
Right now it’s a number of type:
|
||
|
||
Num a
|
||
|
||
But I need every `if` guard condition to evaluate to a Bool—either
|
||
`True` or `False`.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn if_2_branch_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
if True then 2 else "foo"
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This `if` has an `else` branch with a different type from its `then` branch:
|
||
|
||
1│ if True then 2 else "foo"
|
||
^^^^^
|
||
|
||
The `else` branch is a string of type:
|
||
|
||
Str
|
||
|
||
but the `then` branch has the type:
|
||
|
||
Num a
|
||
|
||
I need all branches in an `if` to have the same type!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
// #[test]
|
||
// fn if_3_branch_mismatch() {
|
||
// report_problem_as(
|
||
// indoc!(
|
||
// r#"
|
||
// if True then 2 else if False then 2 else "foo"
|
||
// "#
|
||
// ),
|
||
// indoc!(
|
||
// r#"
|
||
// ── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
// The 2nd branch of this `if` does not match all the previous branches:
|
||
|
||
// 1│ if True then 2 else "foo"
|
||
// ^^^^^
|
||
|
||
// The 2nd branch is a string of type
|
||
|
||
// Str
|
||
|
||
// But all the previous branches have the type
|
||
|
||
// Num a
|
||
|
||
// "#
|
||
// ),
|
||
// )
|
||
// }
|
||
|
||
#[test]
|
||
fn when_branch_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
2 -> "foo"
|
||
3 -> {}
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd branch of this `when` does not match all the previous branches:
|
||
|
||
1│ when 1 is
|
||
2│ 2 -> "foo"
|
||
3│ 3 -> {}
|
||
^^
|
||
|
||
The 2nd branch is a record of type:
|
||
|
||
{}
|
||
|
||
But all the previous branches have type:
|
||
|
||
Str
|
||
|
||
I need all branches of a `when` to have the same type!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn elem_in_list() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
[ 1, 3, "foo" ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This list contains elements with different types:
|
||
|
||
1│ [ 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
|
||
|
||
I need every element in a list to have the same type!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_update_value() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : { foo : {} }
|
||
x = { foo: {} }
|
||
|
||
{ x & foo: "bar" }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
I cannot update the `.foo` field like this:
|
||
|
||
4│ { x & foo: "bar" }
|
||
^^^^^
|
||
|
||
You are trying to update `.foo` to be a string of type:
|
||
|
||
Str
|
||
|
||
But it should be:
|
||
|
||
{}
|
||
|
||
Record update syntax does not allow you to change the type of fields.
|
||
You can achieve that with record literal syntax.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn circular_type() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f = \g -> g g
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CIRCULAR TYPE ───────────────────────────────────────────────────────────────
|
||
|
||
I'm inferring a weird self-referential type for `g`:
|
||
|
||
1│ f = \g -> g g
|
||
^
|
||
|
||
Here is my best effort at writing down the type. You will see ∞ for
|
||
parts of the type that repeat something already printed out
|
||
infinitely.
|
||
|
||
∞ -> a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn polymorphic_recursion() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f = \x -> f [x]
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CIRCULAR TYPE ───────────────────────────────────────────────────────────────
|
||
|
||
I'm inferring a weird self-referential type for `f`:
|
||
|
||
1│ f = \x -> f [x]
|
||
^
|
||
|
||
Here is my best effort at writing down the type. You will see ∞ for
|
||
parts of the type that repeat something already printed out
|
||
infinitely.
|
||
|
||
List ∞ -> a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_field_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
bar = { bar : 0x3 }
|
||
|
||
f : { foo : Int * } -> Bool
|
||
f = \_ -> True
|
||
|
||
f bar
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `f` is not what I expect:
|
||
|
||
6│ f bar
|
||
^^^
|
||
|
||
This `bar` value is a:
|
||
|
||
{ bar : Int a }
|
||
|
||
But `f` needs the 1st argument to be:
|
||
|
||
{ foo : Int a }
|
||
|
||
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]
|
||
fn tag_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ Red, Green ] -> Bool
|
||
f = \_ -> True
|
||
|
||
f Blue
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `f` is not what I expect:
|
||
|
||
4│ f Blue
|
||
^^^^
|
||
|
||
This `Blue` global tag has the type:
|
||
|
||
[ Blue ]a
|
||
|
||
But `f` needs the 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]
|
||
fn tag_with_arguments_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ Red (Int *), Green Bool ] -> Bool
|
||
f = \_ -> True
|
||
|
||
f (Blue 3.14)
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `f` is not what I expect:
|
||
|
||
4│ f (Blue 3.14)
|
||
^^^^^^^^^
|
||
|
||
This `Blue` global tag application has the type:
|
||
|
||
[ Blue (Float a) ]b
|
||
|
||
But `f` needs the 1st argument to be:
|
||
|
||
[ Green Bool, Red (Int a) ]
|
||
|
||
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]
|
||
fn from_annotation_if() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : Int *
|
||
x = if True then 3.14 else 4
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the `then` branch of this `if` expression:
|
||
|
||
2│ x = if True then 3.14 else 4
|
||
^^^^
|
||
|
||
The 1st branch is a float of type:
|
||
|
||
Float a
|
||
|
||
But the type annotation on `x` says it should be:
|
||
|
||
Int a
|
||
|
||
Tip: You can convert between Int and Float using functions like
|
||
`Num.toFloat` and `Num.round`.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn from_annotation_when() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : Int *
|
||
x =
|
||
when True is
|
||
_ -> 3.14
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `x` definition:
|
||
|
||
1│ x : Int *
|
||
2│ x =
|
||
3│> when True is
|
||
4│> _ -> 3.14
|
||
|
||
This `when` expression produces:
|
||
|
||
Float a
|
||
|
||
But the type annotation on `x` says it should be:
|
||
|
||
Int a
|
||
|
||
Tip: You can convert between Int and Float using functions like
|
||
`Num.toFloat` and `Num.round`.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn from_annotation_function() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : Int * -> Int *
|
||
x = \_ -> 3.14
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `x` definition:
|
||
|
||
1│ x : Int * -> Int *
|
||
2│ x = \_ -> 3.14
|
||
^^^^
|
||
|
||
The body is a float of type:
|
||
|
||
Float a
|
||
|
||
But the type annotation on `x` says it should be:
|
||
|
||
Int a
|
||
|
||
Tip: You can convert between Int and Float using functions like
|
||
`Num.toFloat` and `Num.round`.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn fncall_value() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : I64
|
||
x = 42
|
||
|
||
x 3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY ARGS ───────────────────────────────────────────────────────────────
|
||
|
||
The `x` value is not a function, but it was given 1 argument:
|
||
|
||
4│ x 3
|
||
^
|
||
|
||
Are there any missing commas? Or missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn fncall_overapplied() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : I64 -> I64
|
||
f = \_ -> 42
|
||
|
||
f 1 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY ARGS ───────────────────────────────────────────────────────────────
|
||
|
||
The `f` function expects 1 argument, but it got 2 instead:
|
||
|
||
4│ f 1 2
|
||
^
|
||
|
||
Are there any missing commas? Or missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn fncall_underapplied() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : I64, I64 -> I64
|
||
f = \_, _ -> 42
|
||
|
||
f 1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO FEW ARGS ────────────────────────────────────────────────────────────────
|
||
|
||
The `f` function expects 2 arguments, but it got only 1:
|
||
|
||
4│ f 1
|
||
^
|
||
|
||
Roc does not allow functions to be partially applied. Use a closure to
|
||
make partial application explicit.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_when_condition() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
{} -> 42
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st pattern in this `when` is causing a mismatch:
|
||
|
||
2│ {} -> 42
|
||
^^
|
||
|
||
The first pattern is trying to match record values of type:
|
||
|
||
{}a
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
Num a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_when_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
2 -> 3
|
||
{} -> 42
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd pattern in this `when` does not match the previous ones:
|
||
|
||
3│ {} -> 42
|
||
^^
|
||
|
||
The 2nd pattern is trying to match record values of type:
|
||
|
||
{}a
|
||
|
||
But all the previous branches match:
|
||
|
||
Num a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_guard_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when { foo: 1 } is
|
||
{ foo: True } -> 42
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st pattern in this `when` is causing a mismatch:
|
||
|
||
2│ { foo: True } -> 42
|
||
^^^^^^^^^^^^^
|
||
|
||
The first pattern is trying to match record values of type:
|
||
|
||
{ foo : [ True ]a }
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
{ foo : Num a }
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_guard_does_not_bind_label() {
|
||
// needs some improvement, but the principle works
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when { foo: 1 } is
|
||
{ foo: 2 } -> foo
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `foo` value
|
||
|
||
2│ { foo: 2 } -> foo
|
||
^^^
|
||
|
||
these names seem close though:
|
||
|
||
Bool
|
||
U8
|
||
F64
|
||
Nat
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_guard_can_be_shadowed_above() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
foo = 3
|
||
|
||
when { foo: 1 } is
|
||
{ foo: 2 } -> foo
|
||
_ -> foo
|
||
"#
|
||
),
|
||
// should give no error
|
||
"",
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_guard_can_be_shadowed_below() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when { foo: 1 } is
|
||
{ foo: 2 } ->
|
||
foo = 3
|
||
|
||
foo
|
||
_ -> 3
|
||
"#
|
||
),
|
||
// should give no error
|
||
"",
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_or_pattern_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when { foo: 1 } is
|
||
{} | 1 -> 3
|
||
"#
|
||
),
|
||
// Just putting this here. We should probably handle or-patterns better
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st pattern in this `when` is causing a mismatch:
|
||
|
||
2│ {} | 1 -> 3
|
||
^^^^^^
|
||
|
||
The first pattern is trying to match numbers:
|
||
|
||
Num a
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
{ foo : Num a }
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_let_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
(Foo x) = 42
|
||
|
||
x
|
||
"#
|
||
),
|
||
// Maybe this should specifically say the pattern doesn't work?
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This expression is used in an unexpected way:
|
||
|
||
1│ (Foo x) = 42
|
||
^^
|
||
|
||
It is a number of type:
|
||
|
||
Num a
|
||
|
||
But you are trying to use it as:
|
||
|
||
[ Foo a ]b
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn from_annotation_complex_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
{ x } : { x : Int * }
|
||
{ x } = { x: 4.0 }
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of this definition:
|
||
|
||
1│ { x } : { x : Int * }
|
||
2│ { x } = { x: 4.0 }
|
||
^^^^^^^^^^
|
||
|
||
The body is a record of type:
|
||
|
||
{ x : Float a }
|
||
|
||
But the type annotation says it should be:
|
||
|
||
{ x : Int a }
|
||
|
||
Tip: You can convert between Int and Float using functions like
|
||
`Num.toFloat` and `Num.round`.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn malformed_int_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
100A -> 3
|
||
_ -> 4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer pattern is malformed:
|
||
|
||
2│ 100A -> 3
|
||
^^^^
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn malformed_float_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
2.X -> 3
|
||
_ -> 4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This float pattern is malformed:
|
||
|
||
2│ 2.X -> 3
|
||
^^^
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn malformed_hex_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
0xZ -> 3
|
||
_ -> 4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This hex integer pattern is malformed:
|
||
|
||
2│ 0xZ -> 3
|
||
^^^
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn malformed_oct_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
0o9 -> 3
|
||
_ -> 4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This octal integer pattern is malformed:
|
||
|
||
2│ 0o9 -> 3
|
||
^^^
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn malformed_bin_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 1 is
|
||
0b4 -> 3
|
||
_ -> 4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This binary integer pattern is malformed:
|
||
|
||
2│ 0b4 -> 3
|
||
^^^
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn missing_fields() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : { a : Int *, b : Float *, c : Bool }
|
||
x = { b: 4.0 }
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `x` definition:
|
||
|
||
1│ x : { a : Int *, b : Float *, c : Bool }
|
||
2│ x = { b: 4.0 }
|
||
^^^^^^^^^^
|
||
|
||
The body is a record of type:
|
||
|
||
{ b : Float a }
|
||
|
||
But the type annotation on `x` says it should be:
|
||
|
||
{ a : Int a, b : Float b, c : Bool }
|
||
|
||
Tip: Looks like the c and a fields are missing.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn bad_double_rigid() {
|
||
// 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
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : a, b -> a
|
||
f = \x, y -> if True then x else y
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the `else` branch of this `if` expression:
|
||
|
||
2│ 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 your type annotation? Maybe your code uses them in
|
||
a weird way?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn bad_rigid_function() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Bool -> msg
|
||
f = \_ -> Foo
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : Bool -> msg
|
||
2│ f = \_ -> Foo
|
||
^^^
|
||
|
||
This `Foo` global 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]
|
||
fn bad_rigid_value() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : msg
|
||
f = 0x3
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : msg
|
||
2│ 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?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn typo_lowercase_ok() {
|
||
// TODO improve tag suggestions
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Bool -> [ Ok I64, InvalidFoo ]
|
||
f = \_ -> ok 4
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `ok` value
|
||
|
||
2│ f = \_ -> ok 4
|
||
^^
|
||
|
||
these names seem close though:
|
||
|
||
U8
|
||
f
|
||
I8
|
||
F64
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn typo_uppercase_ok() {
|
||
// these error messages seem pretty helpful
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Bool -> I64
|
||
f = \_ ->
|
||
ok = 3
|
||
|
||
Ok
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
`ok` is not used anywhere in your code.
|
||
|
||
3│ 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 ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : Bool -> I64
|
||
2│ f = \_ ->
|
||
3│ ok = 3
|
||
4│
|
||
5│ Ok
|
||
^^
|
||
|
||
This `Ok` global tag has the type:
|
||
|
||
[ Ok ]a
|
||
|
||
But the type annotation on `f` says it should be:
|
||
|
||
I64
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn circular_definition_self() {
|
||
// invalid recursion
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f = f
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
The `f` value is defined directly in terms of itself, causing an
|
||
infinite loop.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn circular_definition() {
|
||
// invalid mutual recursion
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
foo = bar
|
||
|
||
bar = foo
|
||
|
||
foo
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
The `foo` definition is causing a very tricky infinite loop:
|
||
|
||
1│ foo = bar
|
||
^^^
|
||
|
||
The `foo` value depends on itself through the following chain of
|
||
definitions:
|
||
|
||
┌─────┐
|
||
│ foo
|
||
│ ↓
|
||
│ bar
|
||
└─────┘
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn update_empty_record() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = {}
|
||
|
||
{ x & foo: 3 }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The `x` record does not have a `.foo` field:
|
||
|
||
3│ { x & foo: 3 }
|
||
^^^^^^
|
||
|
||
In fact, `x` is a record with NO fields!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn update_record() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = { fo: 3, bar: 4 }
|
||
|
||
{ x & foo: 3 }
|
||
"#
|
||
),
|
||
// TODO also suggest fields with the correct type
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The `x` record does not have a `.foo` field:
|
||
|
||
3│ { x & foo: 3 }
|
||
^^^^^^
|
||
|
||
This is usually a typo. Here are the `x` fields that are most similar:
|
||
|
||
{ fo : Num b
|
||
, bar : Num a
|
||
}
|
||
|
||
So maybe `.foo` should be `.fo`?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn update_record_ext() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { fo: I64 }ext -> I64
|
||
f = \r ->
|
||
r2 = { r & foo: r.fo }
|
||
|
||
r2.fo
|
||
|
||
f
|
||
"#
|
||
),
|
||
// TODO also suggest fields with the correct type
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The `r` record does not have a `.foo` field:
|
||
|
||
3│ r2 = { r & foo: r.fo }
|
||
^^^^^^^^^
|
||
|
||
This is usually a typo. Here are the `r` fields that are most similar:
|
||
|
||
{ fo : I64
|
||
}ext
|
||
|
||
So maybe `.foo` should be `.fo`?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn update_record_snippet() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = { fo: 3, bar: 4, baz: 3, spam: 42, foobar: 3 }
|
||
|
||
{ x & foo: 3 }
|
||
"#
|
||
),
|
||
// TODO also suggest fields with the correct type
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The `x` record does not have a `.foo` field:
|
||
|
||
3│ { x & foo: 3 }
|
||
^^^^^^
|
||
|
||
This is usually a typo. Here are the `x` fields that are most similar:
|
||
|
||
{ fo : Num c
|
||
, foobar : Num a
|
||
, bar : Num e
|
||
, baz : Num b
|
||
, ...
|
||
}
|
||
|
||
So maybe `.foo` should be `.fo`?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn plus_on_str() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
0x4 + "foo"
|
||
"#
|
||
),
|
||
// TODO also suggest fields with the correct type
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `add` is not what I expect:
|
||
|
||
1│ 0x4 + "foo"
|
||
^^^^^
|
||
|
||
This argument is a string of type:
|
||
|
||
Str
|
||
|
||
But `add` needs the 2nd argument to be:
|
||
|
||
Num (Integer a)
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn int_float() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
0x4 + 3.14
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `add` is not what I expect:
|
||
|
||
1│ 0x4 + 3.14
|
||
^^^^
|
||
|
||
This argument is a float of type:
|
||
|
||
Float a
|
||
|
||
But `add` needs the 2nd argument to be:
|
||
|
||
Num (Integer a)
|
||
|
||
Tip: You can convert between Int and Float using functions like
|
||
`Num.toFloat` and `Num.round`.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn boolean_tag() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
42 + True
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `add` is not what I expect:
|
||
|
||
1│ 42 + True
|
||
^^^^
|
||
|
||
This `True` boolean has the type:
|
||
|
||
[ True ]a
|
||
|
||
But `add` needs the 2nd argument to be:
|
||
|
||
Num a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn tag_missing() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ A ] -> [ A, B ]
|
||
f = \a -> a
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : [ A ] -> [ A, B ]
|
||
2│ 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]
|
||
fn tags_missing() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ A ] -> [ A, B, C ]
|
||
f = \a -> a
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : [ A ] -> [ A, B, C ]
|
||
2│ 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 `C` and `B` tags.
|
||
|
||
Tip: Closed tag unions can't grow, because that might change the size
|
||
in memory. Can you use an open tag union?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
#[ignore]
|
||
fn open_tag_union_can_grow() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ A ]* -> [ A, B, C ]
|
||
f = \a -> a
|
||
|
||
f
|
||
"#
|
||
),
|
||
// should not give errors
|
||
indoc!(""),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_fn_not_exhaustive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Either : [ Left I64, Right Bool ]
|
||
|
||
x : Either
|
||
x = Left 42
|
||
|
||
f : Either -> I64
|
||
f = \Left v -> v
|
||
|
||
f x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This pattern does not cover all the possibilities:
|
||
|
||
7│ 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]
|
||
fn patterns_let_not_exhaustive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : [ Left I64, Right Bool ]
|
||
x = Left 42
|
||
|
||
|
||
(Left y) = x
|
||
|
||
y
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This pattern does not cover all the possibilities:
|
||
|
||
5│ (Left y) = x
|
||
^^^^^^
|
||
|
||
Other possibilities include:
|
||
|
||
Right _
|
||
|
||
I would have to crash if I saw one of those! You can use a binding to
|
||
deconstruct a value if there is only ONE possibility. Use a `when` to
|
||
account for all possibilities.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_when_not_exhaustive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 0x1 is
|
||
2 -> 0x3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
1│> when 0x1 is
|
||
2│> 2 -> 0x3
|
||
|
||
Other possibilities include:
|
||
|
||
_
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_bool_not_exhaustive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : Bool
|
||
x = True
|
||
|
||
when x is
|
||
False -> 3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
4│> when x is
|
||
5│> False -> 3
|
||
|
||
Other possibilities include:
|
||
|
||
True
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_enum_not_exhaustive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : [ Red, Green, Blue ]
|
||
x = Red
|
||
|
||
when x is
|
||
Red -> 0
|
||
Green -> 1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
4│> when x is
|
||
5│> Red -> 0
|
||
6│> Green -> 1
|
||
|
||
Other possibilities include:
|
||
|
||
Blue
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_remote_data_not_exhaustive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
RemoteData e a : [ NotAsked, Loading, Failure e, Success a ]
|
||
|
||
x : RemoteData I64 Str
|
||
|
||
when x is
|
||
NotAsked -> 3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
5│> when x is
|
||
6│> NotAsked -> 3
|
||
|
||
Other possibilities include:
|
||
|
||
Failure _
|
||
Loading
|
||
Success _
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_record_not_exhaustive() {
|
||
report_problem_as(
|
||
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.
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
3│> when x is
|
||
4│> { a: 4 } -> 4
|
||
|
||
Other possibilities include:
|
||
|
||
{ a }
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_record_guard_not_exhaustive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
y : [ Nothing, Just I64 ]
|
||
y = Just 4
|
||
x = { a: y, b: 42}
|
||
|
||
when x is
|
||
{ a: Nothing } -> 4
|
||
{ a: Just 3 } -> 4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
5│> when x is
|
||
6│> { a: Nothing } -> 4
|
||
7│> { a: Just 3 } -> 4
|
||
|
||
Other possibilities include:
|
||
|
||
{ a: Just _, b }
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_nested_tag_not_exhaustive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when Record Nothing 1 is
|
||
Record (Nothing) b -> b
|
||
Record (Just 3) b -> b
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
1│> when Record Nothing 1 is
|
||
2│> Record (Nothing) b -> b
|
||
3│> 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]
|
||
fn patterns_int_redundant() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 0x1 is
|
||
2 -> 3
|
||
2 -> 4
|
||
_ -> 5
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── REDUNDANT PATTERN ───────────────────────────────────────────────────────────
|
||
|
||
The 2nd pattern is redundant:
|
||
|
||
1│ when 0x1 is
|
||
2│ 2 -> 3
|
||
3│> 2 -> 4
|
||
4│ _ -> 5
|
||
|
||
Any value of this shape will be handled by a previous pattern, so this
|
||
one should be removed.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn unify_alias_other() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo : { x : Int * }
|
||
|
||
f : Foo -> Int *
|
||
f = \r -> r.x
|
||
|
||
f { y: 3.14 }
|
||
"#
|
||
),
|
||
// de-aliases the alias to give a better error message
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `f` is not what I expect:
|
||
|
||
6│ f { y: 3.14 }
|
||
^^^^^^^^^^^
|
||
|
||
This argument is a record of type:
|
||
|
||
{ y : Float a }
|
||
|
||
But `f` needs the 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]
|
||
#[ignore]
|
||
fn cyclic_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo : { x : Bar }
|
||
Bar : { y : Foo }
|
||
|
||
f : Foo
|
||
|
||
f
|
||
"#
|
||
),
|
||
// should not report Bar as unused!
|
||
indoc!(
|
||
r#"
|
||
── CYCLIC ALIAS ────────────────────────────────────────────────────────────────
|
||
|
||
The `Foo` alias is recursive in an invalid way:
|
||
|
||
1│ Foo : { x : Bar }
|
||
^^^^^^^^^^^
|
||
|
||
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
|
||
tag.
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
`Bar` is not used anywhere in your code.
|
||
|
||
2│ Bar : { y : Foo }
|
||
^^^^^^^^^^^^^^^^^
|
||
|
||
If you didn't intend on using `Bar` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn self_recursive_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo : { x : Foo }
|
||
|
||
f : Foo
|
||
f = 3
|
||
|
||
f
|
||
"#
|
||
),
|
||
// should not report Bar as unused!
|
||
indoc!(
|
||
r#"
|
||
── CYCLIC ALIAS ────────────────────────────────────────────────────────────────
|
||
|
||
The `Foo` alias is self-recursive in an invalid way:
|
||
|
||
1│ Foo : { x : Foo }
|
||
^^^
|
||
|
||
Recursion in aliases is only allowed if recursion happens behind a
|
||
tag.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_duplicate_field_same_type() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
{ x: 4, y: 3, x: 4 }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This record defines the `.x` field twice!
|
||
|
||
1│ { x: 4, y: 3, x: 4 }
|
||
^^^^ ^^^^
|
||
|
||
In the rest of the program, I will only use the latter definition:
|
||
|
||
1│ { x: 4, y: 3, x: 4 }
|
||
^^^^
|
||
|
||
For clarity, remove the previous `.x` definitions from this record.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_duplicate_field_different_types() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
{ x: 4, y: 3, x: "foo" }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This record defines the `.x` field twice!
|
||
|
||
1│ { x: 4, y: 3, x: "foo" }
|
||
^^^^ ^^^^^^^^
|
||
|
||
In the rest of the program, I will only use the latter definition:
|
||
|
||
1│ { x: 4, y: 3, x: "foo" }
|
||
^^^^^^^^
|
||
|
||
For clarity, remove the previous `.x` definitions from this record.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_duplicate_field_multiline() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
{
|
||
x: 4,
|
||
y: 3,
|
||
x: "foo"
|
||
}
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This record defines the `.x` field twice!
|
||
|
||
1│ {
|
||
2│> x: 4,
|
||
3│ y: 3,
|
||
4│> x: "foo"
|
||
5│ }
|
||
|
||
In the rest of the program, I will only use the latter definition:
|
||
|
||
1│ {
|
||
2│ x: 4,
|
||
3│ y: 3,
|
||
4│> x: "foo"
|
||
5│ }
|
||
|
||
For clarity, remove the previous `.x` definitions from this record.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_update_duplicate_field_multiline() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\r ->
|
||
{ r &
|
||
x: 4,
|
||
y: 3,
|
||
x: "foo"
|
||
}
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This record defines the `.x` field twice!
|
||
|
||
2│ { r &
|
||
3│> x: 4,
|
||
4│ y: 3,
|
||
5│> x: "foo"
|
||
6│ }
|
||
|
||
In the rest of the program, I will only use the latter definition:
|
||
|
||
2│ { r &
|
||
3│ x: 4,
|
||
4│ y: 3,
|
||
5│> x: "foo"
|
||
6│ }
|
||
|
||
For clarity, remove the previous `.x` definitions from this record.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_type_duplicate_field() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
a : { foo : I64, bar : F64, foo : Str }
|
||
a = { bar: 3.0, foo: "foo" }
|
||
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This record type defines the `.foo` field twice!
|
||
|
||
1│ a : { foo : I64, bar : F64, foo : Str }
|
||
^^^^^^^^^ ^^^^^^^^^
|
||
|
||
In the rest of the program, I will only use the latter definition:
|
||
|
||
1│ a : { foo : I64, bar : F64, foo : Str }
|
||
^^^^^^^^^
|
||
|
||
For clarity, remove the previous `.foo` definitions from this record
|
||
type.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn tag_union_duplicate_tag() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
a : [ Foo I64, Bar F64, Foo Str ]
|
||
a = Foo "foo"
|
||
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This tag union type defines the `Foo` tag twice!
|
||
|
||
1│ a : [ Foo I64, Bar F64, Foo Str ]
|
||
^^^^^^^ ^^^^^^^
|
||
|
||
In the rest of the program, I will only use the latter definition:
|
||
|
||
1│ a : [ Foo I64, Bar F64, Foo Str ]
|
||
^^^^^^^
|
||
|
||
For clarity, remove the previous `Foo` definitions from this tag union
|
||
type.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn annotation_definition_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
bar : I64
|
||
foo = \x -> x
|
||
|
||
# NOTE: neither bar or foo are defined at this point
|
||
4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This annotation does not match the definition immediately following
|
||
it:
|
||
|
||
1│> bar : I64
|
||
2│> foo = \x -> x
|
||
|
||
Is it a typo? If not, put either a newline or comment between them.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn annotation_newline_body_is_fine() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
bar : I64
|
||
|
||
foo = \x -> x
|
||
|
||
foo bar
|
||
"#
|
||
),
|
||
indoc!(""),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_alias_rigid_var_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
MyAlias 1 : I64
|
||
|
||
4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This pattern in the definition of `MyAlias` is not what I expect:
|
||
|
||
1│ MyAlias 1 : I64
|
||
^
|
||
|
||
Only type variables like `a` or `value` can occur in this position.
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
`MyAlias` is not used anywhere in your code.
|
||
|
||
1│ MyAlias 1 : 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]
|
||
fn invalid_num() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
a : Num I64 F64
|
||
a = 3
|
||
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY TYPE ARGUMENTS ─────────────────────────────────────────────────────
|
||
|
||
The `Num` alias expects 1 type argument, but it got 2 instead:
|
||
|
||
1│ a : Num I64 F64
|
||
^^^^^^^^^^^
|
||
|
||
Are there missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_num_fn() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Bool -> Num I64 F64
|
||
f = \_ -> 3
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY TYPE ARGUMENTS ─────────────────────────────────────────────────────
|
||
|
||
The `Num` alias expects 1 type argument, but it got 2 instead:
|
||
|
||
1│ f : Bool -> Num I64 F64
|
||
^^^^^^^^^^^
|
||
|
||
Are there missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn too_few_type_arguments() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Pair a b : [ Pair a b ]
|
||
|
||
x : Pair I64
|
||
x = Pair 2 3
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO FEW TYPE ARGUMENTS ──────────────────────────────────────────────────────
|
||
|
||
The `Pair` alias expects 2 type arguments, but it got 1 instead:
|
||
|
||
3│ x : Pair I64
|
||
^^^^^^^^
|
||
|
||
Are there missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn too_many_type_arguments() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Pair a b : [ Pair a b ]
|
||
|
||
x : Pair I64 I64 I64
|
||
x = 3
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY TYPE ARGUMENTS ─────────────────────────────────────────────────────
|
||
|
||
The `Pair` alias expects 2 type arguments, but it got 3 instead:
|
||
|
||
3│ x : Pair I64 I64 I64
|
||
^^^^^^^^^^^^^^^^
|
||
|
||
Are there missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn phantom_type_variable() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo a : [ Foo ]
|
||
|
||
f : Foo I64
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
The `a` type variable is not used in the `Foo` alias definition:
|
||
|
||
1│ Foo a : [ Foo ]
|
||
^
|
||
|
||
Roc does not allow unused type parameters!
|
||
|
||
Tip: If you want an unused type parameter (a so-called "phantom
|
||
type"), read the guide section on phantom data.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn elm_function_syntax() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f x y = x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── PARSE PROBLEM ───────────────────────────────────────────────────────────────
|
||
|
||
Unexpected tokens in front of the `=` symbol:
|
||
|
||
1│ f x y = x
|
||
^^^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn two_different_cons() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||
|
||
x : ConsList {}
|
||
x = Cons {} (Cons "foo" Nil)
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `x` definition:
|
||
|
||
3│ x : ConsList {}
|
||
4│ x = Cons {} (Cons "foo" Nil)
|
||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
This `Cons` global 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]
|
||
#[ignore]
|
||
fn mutually_recursive_types_with_type_error() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
AList a b : [ ACons a (BList a b), ANil ]
|
||
BList a b : [ BCons a (AList a b), BNil ]
|
||
|
||
x : AList I64 I64
|
||
x = ACons 0 (BCons 1 (ACons "foo" BNil ))
|
||
|
||
y : BList a a
|
||
y = BNil
|
||
|
||
{ x, y }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `x` definition:
|
||
|
||
4│ x : AList I64 I64
|
||
5│ x = ACons 0 (BCons 1 (ACons "foo" BNil ))
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
This `ACons` global tag application has the type:
|
||
|
||
[ ACons (Num Integer) [ BCons (Num Integer) [ ACons Str [
|
||
BCons I64 [ ACons I64 (BList I64 I64), ANil ] as a, BNil ], ANil
|
||
], BNil ], ANil ]
|
||
|
||
But the type annotation on `x` says it should be:
|
||
|
||
[ ACons I64 (BList I64 I64), ANil ] as a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn integer_out_of_range() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = 9_223_372_036_854_775_807_000
|
||
|
||
y = -9_223_372_036_854_775_807_000
|
||
|
||
h = 0x8FFF_FFFF_FFFF_FFFF
|
||
l = -0x8FFF_FFFF_FFFF_FFFF
|
||
|
||
minlit = -9_223_372_036_854_775_808
|
||
maxlit = 9_223_372_036_854_775_807
|
||
|
||
x + y + h + l + minlit + maxlit
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal is too big:
|
||
|
||
1│ x = 9_223_372_036_854_775_807_000
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Roc uses signed 64-bit integers, allowing values between
|
||
−9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal is too small:
|
||
|
||
3│ y = -9_223_372_036_854_775_807_000
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Roc uses signed 64-bit integers, allowing values between
|
||
−9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal is too big:
|
||
|
||
5│ h = 0x8FFF_FFFF_FFFF_FFFF
|
||
^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Roc uses signed 64-bit integers, allowing values between
|
||
−9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal is too small:
|
||
|
||
6│ l = -0x8FFF_FFFF_FFFF_FFFF
|
||
^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Roc uses signed 64-bit integers, allowing values between
|
||
−9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn float_out_of_range() {
|
||
// have to deal with some whitespace issues because of the format! macro
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
overflow = 11.7976931348623157e308
|
||
underflow = -11.7976931348623157e308
|
||
|
||
overflow + underflow
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This float literal is too big:
|
||
|
||
1│ 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 ──────────────────────────────────────────────────────────────
|
||
|
||
This float literal is too small:
|
||
|
||
2│ 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
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn integer_malformed() {
|
||
// 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
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
dec = 100A
|
||
|
||
hex = 0xZZZ
|
||
|
||
oct = 0o9
|
||
|
||
bin = 0b2
|
||
|
||
dec + hex + oct + bin
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal contains an invalid digit:
|
||
|
||
1│ dec = 100A
|
||
^^^^
|
||
|
||
Integer literals can only contain the digits 0-9.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This hex integer literal contains an invalid digit:
|
||
|
||
3│ hex = 0xZZZ
|
||
^^^^^
|
||
|
||
Hexadecimal (base-16) integer literals can only contain the digits
|
||
0-9, a-f and A-F.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This octal integer literal contains an invalid digit:
|
||
|
||
5│ oct = 0o9
|
||
^^^
|
||
|
||
Octal (base-8) integer literals can only contain the digits 0-7.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This binary integer literal contains an invalid digit:
|
||
|
||
7│ bin = 0b2
|
||
^^^
|
||
|
||
Binary (base-2) integer literals can only contain the digits 0 and 1.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn integer_empty() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
dec = 20
|
||
|
||
hex = 0x
|
||
|
||
oct = 0o
|
||
|
||
bin = 0b
|
||
|
||
dec + hex + oct + bin
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This hex integer literal contains no digits:
|
||
|
||
3│ hex = 0x
|
||
^^
|
||
|
||
Hexadecimal (base-16) integer literals must contain at least one of
|
||
the digits 0-9, a-f and A-F.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This octal integer literal contains no digits:
|
||
|
||
5│ oct = 0o
|
||
^^
|
||
|
||
Octal (base-8) integer literals must contain at least one of the
|
||
digits 0-7.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This binary integer literal contains no digits:
|
||
|
||
7│ bin = 0b
|
||
^^
|
||
|
||
Binary (base-2) integer literals must contain at least one of the
|
||
digits 0 and 1.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn float_malformed() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = 3.0A
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This float literal contains an invalid digit:
|
||
|
||
1│ x = 3.0A
|
||
^^^^
|
||
|
||
Floating point literals can only contain the digits 0-9, or use
|
||
scientific notation 10e4
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_record_update() {
|
||
report_problem_as(
|
||
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 }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This expression cannot be updated:
|
||
|
||
2│ updateNestedRecord = { foo.bar & x: 4 }
|
||
^^^^^^^
|
||
|
||
Only variables can be updated with record update syntax.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn module_not_imported() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo.test
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
The `Foo` module is not imported:
|
||
|
||
1│ Foo.test
|
||
^^^^^^^^
|
||
|
||
Is there an import missing? Perhaps there is a typo, these names seem
|
||
close:
|
||
|
||
Bool
|
||
Num
|
||
Set
|
||
Str
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn optional_record_default_type_error() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\{ x, y ? True } -> x + y
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `add` is not what I expect:
|
||
|
||
1│ \{ x, y ? True } -> x + y
|
||
^
|
||
|
||
This `y` value is a:
|
||
|
||
[ True ]a
|
||
|
||
But `add` needs the 2nd argument to be:
|
||
|
||
Num a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn optional_record_default_with_signature() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { x : I64, y ? I64 } -> I64
|
||
f = \{ x, y ? "foo" } -> (\g, _ -> g) x y
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `f` is weird:
|
||
|
||
2│ 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]
|
||
fn optional_record_invalid_let_binding() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\rec ->
|
||
{ x, y } : { x : I64, y ? Bool }
|
||
{ x, y } = rec
|
||
|
||
{ x, y }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of this definition:
|
||
|
||
2│> { x, y } : { x : I64, y ? Bool }
|
||
3│> { x, y } = rec
|
||
|
||
The body is a value of type:
|
||
|
||
{ x : I64, y : Bool }
|
||
|
||
But the type annotation says it should be:
|
||
|
||
{ x : I64, y ? Bool }
|
||
|
||
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]
|
||
fn optional_record_invalid_function() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { x : I64, y ? I64 } -> I64
|
||
f = \{ x, y } -> x + y
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `f` is weird:
|
||
|
||
2│ 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]
|
||
fn optional_record_invalid_when() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { x : I64, y ? I64 } -> I64
|
||
f = \r ->
|
||
when r is
|
||
{ x, y } -> x + y
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st pattern in this `when` is causing a mismatch:
|
||
|
||
4│ { x, y } -> x + y
|
||
^^^^^^^^
|
||
|
||
The first pattern is trying to match record values of type:
|
||
|
||
{ x : I64, y : I64 }
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
{ 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]
|
||
fn optional_record_invalid_access() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { x : I64, y ? I64 } -> I64
|
||
f = \r -> r.y
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This expression is used in an unexpected way:
|
||
|
||
2│ 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]
|
||
fn optional_record_invalid_accessor() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { x : I64, y ? I64 } -> I64
|
||
f = \r -> .y r
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to this function is not what I expect:
|
||
|
||
2│ f = \r -> .y r
|
||
^
|
||
|
||
This `r` value is a:
|
||
|
||
{ x : I64, y ? I64 }
|
||
|
||
But this function needs the 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]
|
||
fn guard_mismatch_with_annotation() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { x : I64, y : I64 } -> I64
|
||
f = \r ->
|
||
when r is
|
||
{ x, y : "foo" } -> x + 0
|
||
_ -> 0
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st pattern in this `when` is causing a mismatch:
|
||
|
||
4│ { x, y : "foo" } -> x + 0
|
||
^^^^^^^^^^^^^^^^
|
||
|
||
The first pattern is trying to match record values of type:
|
||
|
||
{ x : I64, y : Str }
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
{ x : I64, y : I64 }
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn optional_field_mismatch_with_annotation() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { x : I64, y ? I64 } -> I64
|
||
f = \r ->
|
||
when r is
|
||
{ x, y ? "foo" } -> (\g, _ -> g) x y
|
||
_ -> 0
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st pattern in this `when` is causing a mismatch:
|
||
|
||
4│ { x, y ? "foo" } -> (\g, _ -> g) x y
|
||
^^^^^^^^^^^^^^^^
|
||
|
||
The first pattern is trying to match record values of type:
|
||
|
||
{ x : I64, y ? Str }
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
{ x : I64, y ? I64 }
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn incorrect_optional_field() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
{ x: 5, y ? 42 }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This record uses an optional value for the `.y` field in an incorrect
|
||
context!
|
||
|
||
1│ { x: 5, y ? 42 }
|
||
^^^^^^
|
||
|
||
You can only use optional values in record destructuring, for example
|
||
in affectation:
|
||
|
||
{ answer ? 42, otherField } = myRecord
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
#[test]
|
||
fn first_wildcard_is_required() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when Foo 1 2 3 is
|
||
Foo _ 1 _ -> 1
|
||
_ -> 2
|
||
"#
|
||
),
|
||
"",
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn second_wildcard_is_redundant() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when Foo 1 2 3 is
|
||
Foo _ 1 _ -> 1
|
||
_ -> 2
|
||
_ -> 3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── REDUNDANT PATTERN ───────────────────────────────────────────────────────────
|
||
|
||
The 3rd pattern is redundant:
|
||
|
||
1│ when Foo 1 2 3 is
|
||
2│ Foo _ 1 _ -> 1
|
||
3│ _ -> 2
|
||
4│ _ -> 3
|
||
^
|
||
|
||
Any value of this shape will be handled by a previous pattern, so this
|
||
one should be removed.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn alias_using_alias() {
|
||
report_problem_as(
|
||
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]
|
||
fn unused_argument() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f = \foo -> 1
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
`f` doesn't use `foo`.
|
||
|
||
1│ 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]
|
||
fn qualified_global_tag() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo.Bar
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
The `Foo.Bar` identifier is malformed:
|
||
|
||
1│ Foo.Bar
|
||
^^^^^^^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
#[ignore]
|
||
fn type_annotation_double_colon() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f :: I64
|
||
f = 42
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── PARSE PROBLEM ───────────────────────────────────────────────────────────────
|
||
|
||
Unexpected token :
|
||
|
||
1│ f :: I64
|
||
^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn double_equals_in_def() {
|
||
// NOTE: VERY BAD ERROR MESSAGE
|
||
//
|
||
// looks like `x y` are considered argument to the add, even though they are
|
||
// on a lower indentation level
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = 3
|
||
y =
|
||
x == 5
|
||
Num.add 1 2
|
||
|
||
x y
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY ARGS ───────────────────────────────────────────────────────────────
|
||
|
||
The `add` function expects 2 arguments, but it got 4 instead:
|
||
|
||
4│ Num.add 1 2
|
||
^^^^^^^
|
||
|
||
Are there any missing commas? Or missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_operator() {
|
||
// NOTE: VERY BAD ERROR MESSAGE
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
main =
|
||
5 ** 3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── PARSE PROBLEM ───────────────────────────────────────────────────────────────
|
||
|
||
Unexpected token :
|
||
|
||
2│ 5 ** 3
|
||
^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn tag_union_open() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED TAG UNION TYPE ───────────────────────────────────────────────────
|
||
|
||
I just started parsing a tag union type, but I got stuck here:
|
||
|
||
1│ f : [
|
||
^
|
||
|
||
Tag unions look like [ Many I64, None ], so I was expecting to see a
|
||
tag name next.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn tag_union_end() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ Yes,
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED TAG UNION TYPE ───────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a tag union type, but I got stuck here:
|
||
|
||
1│ f : [ Yes,
|
||
^
|
||
|
||
I was expecting to see a closing square bracket before this, so try
|
||
adding a ] and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn tag_union_lowercase_tag_name() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ lowercase ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD TAG NAME ──────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a tag union type, but I got stuck here:
|
||
|
||
1│ f : [ lowercase ]
|
||
^
|
||
|
||
I was expecting to see a tag name.
|
||
|
||
Hint: Tag names start with an uppercase letter, like Err or Green.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn tag_union_second_lowercase_tag_name() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ Good, bad ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD TAG NAME ──────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a tag union type, but I got stuck here:
|
||
|
||
1│ f : [ Good, bad ]
|
||
^
|
||
|
||
I was expecting to see a tag name.
|
||
|
||
Hint: Tag names start with an uppercase letter, like Err or Green.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_type_open() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : {
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED RECORD TYPE ──────────────────────────────────────────────────────
|
||
|
||
I just started parsing a record type, but I got stuck here:
|
||
|
||
1│ f : {
|
||
^
|
||
|
||
Record types look like { name : String, age : Int }, so I was
|
||
expecting to see a field name next.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_type_open_indent() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : {
|
||
foo : I64,
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED RECORD TYPE ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a record type, but I got stuck here:
|
||
|
||
1│ 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]
|
||
fn record_type_end() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { a: Int,
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED RECORD TYPE ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a record type, but I got stuck here:
|
||
|
||
1│ f : { a: Int,
|
||
^
|
||
|
||
I was expecting to see a closing curly brace before this, so try
|
||
adding a } and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_type_indent_end() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { a: Int
|
||
}
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NEED MORE INDENTATION ───────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a record type, but I got stuck here:
|
||
|
||
1│ f : { a: Int
|
||
2│ }
|
||
^
|
||
|
||
I need this curly brace to be indented more. Try adding more spaces
|
||
before it!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_type_keyword_field_name() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { if : I64 }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED RECORD TYPE ──────────────────────────────────────────────────────
|
||
|
||
I just started parsing a record type, but I got stuck on this field
|
||
name:
|
||
|
||
1│ 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!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_type_missing_comma() {
|
||
// a case where the message cannot be as good as elm's
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { foo bar }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED RECORD TYPE ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a record type, but I got stuck here:
|
||
|
||
1│ f : { foo bar }
|
||
^
|
||
|
||
I was expecting to see a colon, question mark, comma or closing curly
|
||
brace.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_type_tab() {
|
||
// a case where the message cannot be as good as elm's
|
||
report_problem_as(
|
||
"f : { foo \t }",
|
||
indoc!(
|
||
r#"
|
||
── TAB CHARACTER ───────────────────────────────────────────────────────────────
|
||
|
||
I encountered a tab character
|
||
|
||
1│ f : { foo }
|
||
^
|
||
|
||
Tab characters are not allowed.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
#[ignore]
|
||
fn comment_with_tab() {
|
||
report_problem_as(
|
||
"# comment with a \t\n4",
|
||
indoc!(
|
||
r#"
|
||
── TAB CHARACTER ───────────────────────────────────────────────────────────────
|
||
|
||
I encountered a tab character
|
||
|
||
1│ f : { foo }
|
||
^
|
||
|
||
Tab characters are not allowed.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
#[ignore]
|
||
fn type_in_parens_start() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : (
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED RECORD TYPE ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a record type, but I got stuck here:
|
||
|
||
1│ f : { a: Int,
|
||
^
|
||
|
||
I was expecting to see a closing curly brace before this, so try
|
||
adding a } and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_in_parens_end() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : ( I64
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a type in parentheses, but I got stuck
|
||
here:
|
||
|
||
1│ f : ( I64
|
||
^
|
||
|
||
I was expecting to see a closing parenthesis before this, so try
|
||
adding a ) and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_apply_double_dot() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo..Bar
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── DOUBLE DOT ──────────────────────────────────────────────────────────────────
|
||
|
||
I encountered two dots in a row:
|
||
|
||
1│ f : Foo..Bar
|
||
^
|
||
|
||
Try removing one of them.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_apply_trailing_dot() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo.Bar.
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── 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]
|
||
#[ignore]
|
||
fn type_apply_stray_dot() {
|
||
// TODO good message
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : .
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a type in parentheses, but I got stuck
|
||
here:
|
||
|
||
1│ f : ( I64
|
||
^
|
||
|
||
I was expecting to see a closing parenthesis before this, so try
|
||
adding a ) and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_apply_start_with_number() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo.1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── 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]
|
||
fn type_apply_start_with_lowercase() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo.foo
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD QUALIFIED NAME ────────────────────────────────────────────────────────
|
||
|
||
I encountered a lowercase letter at the start of a qualified name
|
||
segment:
|
||
|
||
1│ f : Foo.foo
|
||
^
|
||
|
||
All parts of a qualified type name must start with an uppercase
|
||
letter, like Num.I64 or List.List a.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_inline_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : I64 as
|
||
f = 0
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED INLINE ALIAS ─────────────────────────────────────────────────────
|
||
|
||
I just started parsing an inline type alias, but I got stuck here:
|
||
|
||
1│ f : I64 as
|
||
^
|
||
|
||
Note: I may be confused by indentation
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_double_comma() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : I64,,I64 -> I64
|
||
f = 0
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── DOUBLE COMMA ────────────────────────────────────────────────────────────────
|
||
|
||
I just started parsing a function argument type, but I encounterd two
|
||
commas in a row:
|
||
|
||
1│ f : I64,,I64 -> I64
|
||
^
|
||
|
||
Try removing one of them.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_argument_no_arrow() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : I64, I64
|
||
f = 0
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED TYPE ─────────────────────────────────────────────────────────────
|
||
|
||
I just started parsing a type, but I got stuck here:
|
||
|
||
1│ f : I64, I64
|
||
^
|
||
|
||
Note: I may be confused by indentation
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_argument_arrow_then_nothing() {
|
||
// TODO could do better by pointing out we're parsing a function type
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : I64, I64 ->
|
||
f = 0
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED TYPE ─────────────────────────────────────────────────────────────
|
||
|
||
I just started parsing a type, but I got stuck here:
|
||
|
||
1│ f : I64, I64 ->
|
||
^
|
||
|
||
Note: I may be confused by indentation
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_private_tag_name() {
|
||
// TODO could do better by pointing out we're parsing a function type
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ @Foo Bool, @100 I64 ]
|
||
f = 0
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD TAG NAME ──────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a tag union type, but I got stuck here:
|
||
|
||
1│ f : [ @Foo Bool, @100 I64 ]
|
||
^
|
||
|
||
I was expecting to see a private tag name.
|
||
|
||
Hint: Private tag names start with an `@` symbol followed by an
|
||
uppercase letter, like @UID or @SecretKey.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn dict_type_formatting() {
|
||
// TODO could do better by pointing out we're parsing a function type
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
myDict : Dict I64 Str
|
||
myDict = Dict.insert Dict.empty "foo" 42
|
||
|
||
myDict
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `myDict` definition:
|
||
|
||
1│ myDict : Dict I64 Str
|
||
2│ 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]
|
||
fn alias_type_diff() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
HSet a : Set a
|
||
|
||
foo : Str -> HSet {}
|
||
|
||
myDict : HSet Str
|
||
myDict = foo "bar"
|
||
|
||
myDict
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `myDict` definition:
|
||
|
||
5│ myDict : HSet Str
|
||
6│ myDict = foo "bar"
|
||
^^^^^^^^^
|
||
|
||
This `foo` call produces:
|
||
|
||
HSet {}
|
||
|
||
But the type annotation on `myDict` says it should be:
|
||
|
||
HSet Str
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
}
|