mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
9662 lines
297 KiB
Rust
9662 lines
297 KiB
Rust
#[macro_use]
|
||
extern crate pretty_assertions;
|
||
extern crate bumpalo;
|
||
extern crate indoc;
|
||
extern crate roc_reporting;
|
||
|
||
mod helpers;
|
||
|
||
#[cfg(test)]
|
||
mod test_reporting {
|
||
use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut, ParseErrOut};
|
||
use bumpalo::Bump;
|
||
use indoc::indoc;
|
||
use roc_can::abilities::AbilitiesStore;
|
||
use roc_can::def::Declaration;
|
||
use roc_can::pattern::Pattern;
|
||
use roc_load::{self, LoadedModule, LoadingProblem};
|
||
use roc_module::symbol::{Interns, ModuleId};
|
||
use roc_mono::ir::{Procs, Stmt, UpdateModeIds};
|
||
use roc_mono::layout::LayoutCache;
|
||
use roc_region::all::LineInfo;
|
||
use roc_reporting::report::{
|
||
can_problem, mono_problem, parse_problem, type_problem, RenderTarget, Report, Severity,
|
||
ANSI_STYLE_CODES, DEFAULT_PALETTE,
|
||
};
|
||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||
use roc_solve::solve;
|
||
use roc_test_utils::assert_multiline_str_eq;
|
||
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(doc: RocDocBuilder) -> Report {
|
||
Report {
|
||
title: "".to_string(),
|
||
doc,
|
||
filename: filename_from_string(r"\code\proj\Main.roc"),
|
||
severity: Severity::RuntimeError,
|
||
}
|
||
}
|
||
|
||
fn promote_expr_to_module(src: &str) -> String {
|
||
let mut buffer =
|
||
String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||
|
||
for line in src.lines() {
|
||
// indent the body!
|
||
buffer.push_str(" ");
|
||
buffer.push_str(line);
|
||
buffer.push('\n');
|
||
}
|
||
|
||
buffer
|
||
}
|
||
|
||
fn run_load_and_infer<'a>(
|
||
arena: &'a Bump,
|
||
src: &'a str,
|
||
) -> (String, Result<LoadedModule, LoadingProblem<'a>>) {
|
||
use std::fs::File;
|
||
use std::io::Write;
|
||
use tempfile::tempdir;
|
||
|
||
let module_src = if src.starts_with("app") {
|
||
// this is already a module
|
||
src.to_string()
|
||
} else {
|
||
// this is an expression, promote it to a module
|
||
promote_expr_to_module(src)
|
||
};
|
||
|
||
let exposed_types = Default::default();
|
||
let loaded = {
|
||
let dir = tempdir().unwrap();
|
||
let filename = PathBuf::from("Test.roc");
|
||
let file_path = dir.path().join(filename);
|
||
let full_file_path = file_path.clone();
|
||
let mut file = File::create(file_path).unwrap();
|
||
writeln!(file, "{}", module_src).unwrap();
|
||
let result = roc_load::load_and_typecheck(
|
||
arena,
|
||
full_file_path,
|
||
dir.path(),
|
||
exposed_types,
|
||
roc_target::TargetInfo::default_x86_64(),
|
||
RenderTarget::Generic,
|
||
);
|
||
drop(file);
|
||
|
||
dir.close().unwrap();
|
||
|
||
result
|
||
};
|
||
|
||
(module_src, loaded)
|
||
}
|
||
|
||
fn infer_expr_help_new<'a>(
|
||
arena: &'a Bump,
|
||
expr_src: &'a str,
|
||
) -> Result<
|
||
(
|
||
String,
|
||
Vec<solve::TypeError>,
|
||
Vec<roc_problem::can::Problem>,
|
||
Vec<roc_mono::ir::MonoProblem>,
|
||
ModuleId,
|
||
Interns,
|
||
),
|
||
LoadingProblem<'a>,
|
||
> {
|
||
let (module_src, result) = run_load_and_infer(arena, expr_src);
|
||
let LoadedModule {
|
||
module_id: home,
|
||
mut can_problems,
|
||
mut type_problems,
|
||
interns,
|
||
mut solved,
|
||
exposed_to_host,
|
||
mut declarations_by_id,
|
||
..
|
||
} = result?;
|
||
|
||
let can_problems = can_problems.remove(&home).unwrap_or_default();
|
||
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
||
|
||
let subs = solved.inner_mut();
|
||
|
||
for var in exposed_to_host.values() {
|
||
name_all_type_vars(*var, subs);
|
||
}
|
||
|
||
let mut mono_problems = Vec::new();
|
||
|
||
// MONO
|
||
|
||
if type_problems.is_empty() && can_problems.is_empty() {
|
||
let arena = Bump::new();
|
||
|
||
assert!(exposed_to_host.len() == 1);
|
||
let (sym, _var) = exposed_to_host.into_iter().next().unwrap();
|
||
|
||
let home_decls = declarations_by_id.remove(&home).unwrap();
|
||
let (loc_expr, var) = home_decls
|
||
.into_iter()
|
||
.find_map(|decl| match decl {
|
||
Declaration::Declare(def) => match def.loc_pattern.value {
|
||
Pattern::Identifier(s) if s == sym => Some((def.loc_expr, def.expr_var)),
|
||
_ => None,
|
||
},
|
||
_ => None,
|
||
})
|
||
.expect("No expression to monomorphize found!");
|
||
|
||
// Compile and add all the Procs before adding main
|
||
let mut procs = Procs::new_in(&arena);
|
||
let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone();
|
||
let mut update_mode_ids = UpdateModeIds::new();
|
||
|
||
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
||
let target_info = roc_target::TargetInfo::default_x86_64();
|
||
let mut layout_cache = LayoutCache::new(target_info);
|
||
let mut mono_env = roc_mono::ir::Env {
|
||
arena: &arena,
|
||
subs,
|
||
problems: &mut mono_problems,
|
||
home,
|
||
ident_ids: &mut ident_ids,
|
||
update_mode_ids: &mut update_mode_ids,
|
||
target_info,
|
||
// call_specialization_counter=0 is reserved
|
||
call_specialization_counter: 1,
|
||
};
|
||
let _mono_expr = Stmt::new(
|
||
&mut mono_env,
|
||
loc_expr.value,
|
||
var,
|
||
&mut procs,
|
||
&mut layout_cache,
|
||
);
|
||
}
|
||
|
||
Ok((
|
||
module_src,
|
||
type_problems,
|
||
can_problems,
|
||
mono_problems,
|
||
home,
|
||
interns,
|
||
))
|
||
}
|
||
|
||
fn list_reports_new<F>(arena: &Bump, src: &str, finalize_render: F) -> String
|
||
where
|
||
F: FnOnce(RocDocBuilder<'_>, &mut String),
|
||
{
|
||
use ven_pretty::DocAllocator;
|
||
|
||
let filename = filename_from_string(r"\code\proj\Main.roc");
|
||
|
||
let mut buf = String::new();
|
||
|
||
match infer_expr_help_new(arena, src) {
|
||
Err(LoadingProblem::FormattedReport(fail)) => fail,
|
||
Ok((module_src, type_problems, can_problems, mono_problems, home, interns)) => {
|
||
let lines = LineInfo::new(&module_src);
|
||
let src_lines: Vec<&str> = module_src.split('\n').collect();
|
||
let mut reports = Vec::new();
|
||
|
||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||
|
||
for problem in can_problems {
|
||
let report = can_problem(&alloc, &lines, filename.clone(), problem.clone());
|
||
reports.push(report);
|
||
}
|
||
|
||
for problem in type_problems {
|
||
if let Some(report) =
|
||
type_problem(&alloc, &lines, filename.clone(), problem.clone())
|
||
{
|
||
reports.push(report);
|
||
}
|
||
}
|
||
|
||
for problem in mono_problems {
|
||
let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone());
|
||
reports.push(report);
|
||
}
|
||
|
||
let has_reports = !reports.is_empty();
|
||
|
||
let doc = alloc
|
||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||
.append(if has_reports {
|
||
alloc.line()
|
||
} else {
|
||
alloc.nil()
|
||
});
|
||
|
||
finalize_render(doc, &mut buf);
|
||
buf
|
||
}
|
||
Err(other) => {
|
||
assert!(false, "failed to load: {:?}", other);
|
||
unreachable!()
|
||
}
|
||
}
|
||
}
|
||
|
||
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,
|
||
constraints,
|
||
constraint,
|
||
home,
|
||
interns,
|
||
problems: can_problems,
|
||
..
|
||
} = can_expr(arena, expr_src)?;
|
||
let mut subs = Subs::new_from_varstore(var_store);
|
||
|
||
for named in output.introduced_variables.named {
|
||
subs.rigid_var(named.variable, named.name);
|
||
}
|
||
|
||
for var in output.introduced_variables.wildcards {
|
||
subs.rigid_var(var.value, "*".into());
|
||
}
|
||
|
||
let mut solve_aliases = roc_solve::solve::Aliases::default();
|
||
|
||
for (name, alias) in output.aliases {
|
||
solve_aliases.insert(name, alias);
|
||
}
|
||
|
||
let mut unify_problems = Vec::new();
|
||
let mut abilities_store = AbilitiesStore::default();
|
||
let (_content, mut subs) = infer_expr(
|
||
subs,
|
||
&mut unify_problems,
|
||
&constraints,
|
||
&constraint,
|
||
&mut solve_aliases,
|
||
&mut abilities_store,
|
||
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::new_in(&arena);
|
||
let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone();
|
||
let mut update_mode_ids = UpdateModeIds::new();
|
||
|
||
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
||
let target_info = roc_target::TargetInfo::default_x86_64();
|
||
let mut layout_cache = LayoutCache::new(target_info);
|
||
let mut mono_env = roc_mono::ir::Env {
|
||
arena: &arena,
|
||
subs: &mut subs,
|
||
problems: &mut mono_problems,
|
||
home,
|
||
ident_ids: &mut ident_ids,
|
||
update_mode_ids: &mut update_mode_ids,
|
||
target_info,
|
||
// call_specialization_counter=0 is reserved
|
||
call_specialization_counter: 1,
|
||
};
|
||
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 lines = LineInfo::new(src);
|
||
|
||
let filename = filename_from_string(r"\code\proj\Main.roc");
|
||
|
||
match infer_expr_help(arena, src) {
|
||
Err(parse_err) => {
|
||
let ParseErrOut {
|
||
fail,
|
||
home,
|
||
interns,
|
||
} = parse_err;
|
||
|
||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||
|
||
let problem = fail.into_file_error(filename.clone());
|
||
let doc = parse_problem(&alloc, &lines, filename, 0, problem);
|
||
|
||
callback(doc.pretty(&alloc).append(alloc.line()), buf)
|
||
}
|
||
Ok((type_problems, can_problems, 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, &lines, filename.clone(), problem.clone());
|
||
reports.push(report);
|
||
}
|
||
|
||
for problem in type_problems {
|
||
if let Some(report) =
|
||
type_problem(&alloc, &lines, filename.clone(), problem.clone())
|
||
{
|
||
reports.push(report);
|
||
}
|
||
}
|
||
|
||
for problem in mono_problems {
|
||
let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone());
|
||
reports.push(report);
|
||
}
|
||
|
||
let has_reports = !reports.is_empty();
|
||
|
||
let doc = alloc
|
||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||
.append(if has_reports {
|
||
alloc.line()
|
||
} else {
|
||
alloc.nil()
|
||
});
|
||
|
||
callback(doc, buf)
|
||
}
|
||
}
|
||
}
|
||
|
||
fn list_header_reports<F>(arena: &Bump, src: &str, buf: &mut String, callback: F)
|
||
where
|
||
F: FnOnce(RocDocBuilder<'_>, &mut String),
|
||
{
|
||
use ven_pretty::DocAllocator;
|
||
|
||
use roc_parse::state::State;
|
||
|
||
let state = State::new(src.as_bytes());
|
||
|
||
let filename = filename_from_string(r"\code\proj\Main.roc");
|
||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||
let lines = LineInfo::new(src);
|
||
|
||
match roc_parse::module::parse_header(arena, state) {
|
||
Err(fail) => {
|
||
let interns = Interns::default();
|
||
let home = crate::helpers::test_home();
|
||
|
||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||
|
||
use roc_parse::parser::SyntaxError;
|
||
let problem = fail
|
||
.map_problem(SyntaxError::Header)
|
||
.into_file_error(filename.clone());
|
||
let doc = parse_problem(&alloc, &lines, filename, 0, problem);
|
||
|
||
callback(doc.pretty(&alloc).append(alloc.line()), buf)
|
||
}
|
||
Ok(_) => todo!(),
|
||
}
|
||
}
|
||
|
||
fn report_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 && buf != expected_rendering {
|
||
for line in buf.split('\n') {
|
||
println!(" {}", line);
|
||
}
|
||
}
|
||
|
||
assert_multiline_str_eq!(expected_rendering, buf.as_str());
|
||
}
|
||
|
||
fn report_header_problem_as(src: &str, expected_rendering: &str) {
|
||
let mut buf: String = String::new();
|
||
let arena = Bump::new();
|
||
|
||
let callback = |doc: RocDocBuilder<'_>, buf: &mut String| {
|
||
doc.1
|
||
.render_raw(70, &mut roc_reporting::report::CiWrite::new(buf))
|
||
.expect("list_reports")
|
||
};
|
||
|
||
list_header_reports(&arena, src, &mut buf, callback);
|
||
|
||
// convenient to copy-paste the generated message
|
||
if true && 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 new_report_problem_as(src: &str, expected_rendering: &str) {
|
||
let arena = Bump::new();
|
||
|
||
let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| {
|
||
doc.1
|
||
.render_raw(70, &mut roc_reporting::report::CiWrite::new(buf))
|
||
.expect("list_reports")
|
||
};
|
||
|
||
let buf = list_reports_new(&arena, src, finalize_render);
|
||
|
||
// convenient to copy-paste the generated message
|
||
if buf != expected_rendering {
|
||
for line in buf.split('\n') {
|
||
println!(" {}", line);
|
||
}
|
||
}
|
||
|
||
assert_multiline_str_eq!(expected_rendering, buf.as_str());
|
||
}
|
||
|
||
fn human_readable(str: &str) -> String {
|
||
str.replace(ANSI_STYLE_CODES.red, "<red>")
|
||
.replace(ANSI_STYLE_CODES.white, "<white>")
|
||
.replace(ANSI_STYLE_CODES.blue, "<blue>")
|
||
.replace(ANSI_STYLE_CODES.yellow, "<yellow>")
|
||
.replace(ANSI_STYLE_CODES.green, "<green>")
|
||
.replace(ANSI_STYLE_CODES.cyan, "<cyan>")
|
||
.replace(ANSI_STYLE_CODES.magenta, "<magenta>")
|
||
.replace(ANSI_STYLE_CODES.reset, "<reset>")
|
||
.replace(ANSI_STYLE_CODES.bold, "<bold>")
|
||
.replace(ANSI_STYLE_CODES.underline, "<underline>")
|
||
}
|
||
|
||
#[test]
|
||
fn value_not_exposed() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
List.isempty 1 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NOT EXPOSED ─────────────────────────────────────────────────────────────────
|
||
|
||
The List module does not expose `isempty`:
|
||
|
||
1│ List.isempty 1 2
|
||
^^^^^^^^^^^^
|
||
|
||
Did you mean one of these?
|
||
|
||
List.isEmpty
|
||
List.set
|
||
List.get
|
||
List.keepIf
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn report_unused_def() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = 1
|
||
y = 2
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`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#"
|
||
── DUPLICATE NAME ──────────────────────────────────────────────────────────────
|
||
|
||
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#"
|
||
── DUPLICATE NAME ──────────────────────────────────────────────────────────────
|
||
|
||
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 aliases have the same name, it's easy to use the wrong one
|
||
on accident. Give one of them a new name.
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`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.
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`Booly` is not used anywhere in your code.
|
||
|
||
3│ Booly : [ Yes, No, Maybe ]
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
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 unrecognized_name() {
|
||
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#"
|
||
── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `bar` value
|
||
|
||
8│ 4 -> bar baz "yay"
|
||
^^^
|
||
|
||
Did you mean one of these?
|
||
|
||
baz
|
||
Nat
|
||
Str
|
||
Err
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn lowercase_primitive_tag_bool() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
if true then 1 else 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `true` value
|
||
|
||
1│ if true then 1 else 2
|
||
^^^^
|
||
|
||
Did you mean one of these?
|
||
|
||
True
|
||
Str
|
||
Num
|
||
Err
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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 unused_arg_and_unused_def() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
y = 9
|
||
|
||
box = \class, htmlChildren ->
|
||
div [ class ] []
|
||
|
||
div = \_, _ -> 4
|
||
|
||
box "wizard" []
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNUSED ARGUMENT ─────────────────────────────────────────────────────────────
|
||
|
||
`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.
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`y` is not used anywhere in your code.
|
||
|
||
1│ y = 9
|
||
^
|
||
|
||
If you didn't intend on using `y` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn report_value_color() {
|
||
let src: &str = indoc!(
|
||
r#"
|
||
activityIndicatorLarge = div
|
||
|
||
view activityIndicatorLarge
|
||
"#
|
||
);
|
||
|
||
let arena = Bump::new();
|
||
let (_type_problems, _can_problems, _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>── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────<reset>
|
||
|
||
I cannot find a `theAdmin` value
|
||
|
||
<cyan>3<reset><cyan>│<reset> <white>theAdmin<reset>
|
||
<red>^^^^^^^^<reset>
|
||
|
||
Did you mean one of these?
|
||
|
||
Decimal
|
||
Dec
|
||
Result
|
||
Num
|
||
"#
|
||
),
|
||
);
|
||
}
|
||
|
||
// #[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 3rd branch of this `if` does not match all the previous branches:
|
||
|
||
1│ if True then 2 else if False then 2 else "foo"
|
||
^^^^^
|
||
|
||
The 3rd branch is a string of type:
|
||
|
||
Str
|
||
|
||
But all the previous branches have type:
|
||
|
||
Num a
|
||
|
||
I need all branches in an `if` to have the same type!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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 * }
|
||
|
||
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 *) ]
|
||
|
||
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:
|
||
|
||
1│ x : Int *
|
||
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 *
|
||
|
||
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 *
|
||
|
||
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 *
|
||
|
||
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_alias() {
|
||
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 ] }
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
{ foo : Num a }
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_guard_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when { foo: "" } 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 ] }
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
{ foo : Str }
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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#"
|
||
── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `foo` value
|
||
|
||
2│ { foo: 2 } -> foo
|
||
^^^
|
||
|
||
Did you mean one of these?
|
||
|
||
Box
|
||
Bool
|
||
U8
|
||
F64
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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 ]
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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 * }
|
||
|
||
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 *, b : Float *, 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:
|
||
|
||
1│ f : a, b -> a
|
||
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 in 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#"
|
||
── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `ok` value
|
||
|
||
2│ f = \_ -> ok 4
|
||
^^
|
||
|
||
Did you mean one of these?
|
||
|
||
Ok
|
||
U8
|
||
Box
|
||
f
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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#"
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`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#"
|
||
── CIRCULAR DEFINITION ─────────────────────────────────────────────────────────
|
||
|
||
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#"
|
||
── CIRCULAR DEFINITION ─────────────────────────────────────────────────────────
|
||
|
||
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 d
|
||
, bar : Num a
|
||
, 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]
|
||
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#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This expression is used in an unexpected way:
|
||
|
||
5│ (Left y) = x
|
||
^
|
||
|
||
This `x` value is a:
|
||
|
||
[ Left I64, Right Bool ]
|
||
|
||
But you are trying to use it as:
|
||
|
||
[ Left a ]
|
||
|
||
Tip: Seems like a tag typo. Maybe `Right` should be `Left`?
|
||
|
||
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 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 a : { x : Int a }
|
||
|
||
f : Foo a -> Int a
|
||
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
|
||
tagged union, at least one variant of which is not recursive.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_duplicate_field_same_type() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
{ x: 4, y: 3, x: 4 }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── DUPLICATE FIELD NAME ────────────────────────────────────────────────────────
|
||
|
||
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#"
|
||
── DUPLICATE FIELD NAME ────────────────────────────────────────────────────────
|
||
|
||
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#"
|
||
── DUPLICATE FIELD NAME ────────────────────────────────────────────────────────
|
||
|
||
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#"
|
||
── DUPLICATE FIELD NAME ────────────────────────────────────────────────────────
|
||
|
||
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#"
|
||
── DUPLICATE FIELD NAME ────────────────────────────────────────────────────────
|
||
|
||
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#"
|
||
── DUPLICATE TAG NAME ──────────────────────────────────────────────────────────
|
||
|
||
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#"
|
||
── NAMING 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.
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`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_opaque_rigid_var_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Age 1 := I64
|
||
|
||
a : Age
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This pattern in the definition of `Age` is not what I expect:
|
||
|
||
1│ Age 1 := I64
|
||
^
|
||
|
||
Only type variables like `a` or `value` can occur in this position.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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#"
|
||
── UNUSED TYPE ALIAS PARAMETER ─────────────────────────────────────────────────
|
||
|
||
The `a` type parameter is not used in the `Foo` alias definition:
|
||
|
||
1│ Foo a : [ Foo ]
|
||
^
|
||
|
||
Roc does not allow unused type alias parameters!
|
||
|
||
Tip: If you want an unused type parameter (a so-called "phantom
|
||
type"), read the guide section on phantom values.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn elm_function_syntax() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f x y = x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── ARGUMENTS BEFORE EQUALS ─────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a definition, but I got stuck here:
|
||
|
||
1│ f x y = x
|
||
^^^
|
||
|
||
Looks like you are trying to define a function. In roc, functions are
|
||
always written as a lambda, like increment = \n -> n + 1.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
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]
|
||
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 }
|
||
"#
|
||
),
|
||
// TODO render tag unions across multiple lines
|
||
// TODO do not show recursion var if the recursion var does not render on the surface of a type
|
||
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 Signed64)) [
|
||
BCons (Num (Integer Signed64)) [ ACons Str [ BCons I64 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 = 170_141_183_460_469_231_731_687_303_715_884_105_728_000
|
||
|
||
y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000
|
||
|
||
h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
|
||
l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
|
||
|
||
minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728
|
||
maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455
|
||
|
||
x + y + h + l + minlit + maxlit
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal is too big:
|
||
|
||
1│ x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
The largest number representable in Roc is the maximum U128 value,
|
||
340_282_366_920_938_463_463_374_607_431_768_211_455.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal is too small:
|
||
|
||
3│ y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
The smallest number representable in Roc is the minimum I128 value,
|
||
-170_141_183_460_469_231_731_687_303_715_884_105_728.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal is too big:
|
||
|
||
5│ h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
The largest number representable in Roc is the maximum U128 value,
|
||
340_282_366_920_938_463_463_374_607_431_768_211_455.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal is too small:
|
||
|
||
6│ l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
The smallest number representable in Roc is the minimum I128 value,
|
||
-170_141_183_460_469_231_731_687_303_715_884_105_728.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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, or have an integer suffix.
|
||
|
||
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, or have an integer suffix.
|
||
|
||
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, or have an integer suffix.
|
||
|
||
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, or have an integer suffix.
|
||
|
||
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, or have an integer suffix.
|
||
|
||
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, or have an integer suffix.
|
||
|
||
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, or have an integer suffix.
|
||
|
||
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, or have a float suffix.
|
||
|
||
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#"
|
||
── MODULE NOT IMPORTED ─────────────────────────────────────────────────────────
|
||
|
||
The `Foo` module is not imported:
|
||
|
||
1│ Foo.test
|
||
^^^^^^^^
|
||
|
||
Is there an import missing? Perhaps there is a typo. Did you mean one
|
||
of these?
|
||
|
||
Box
|
||
Bool
|
||
Num
|
||
Set
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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#"
|
||
── BAD OPTIONAL VALUE ──────────────────────────────────────────────────────────
|
||
|
||
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, like:
|
||
|
||
{ 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#"
|
||
── UNUSED ARGUMENT ─────────────────────────────────────────────────────────────
|
||
|
||
`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 ──────────────────────────────────────────────────────────────
|
||
|
||
I am trying to parse a qualified name here:
|
||
|
||
1│ Foo.Bar
|
||
^
|
||
|
||
This looks like a qualified tag name to me, but tags cannot be
|
||
qualified! Maybe you wanted a qualified name, something like
|
||
Json.Decode.string?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn module_ident_ends_with_dot() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo.Bar.
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am trying to parse a qualified name here:
|
||
|
||
1│ Foo.Bar.
|
||
^
|
||
|
||
I was expecting to see an identifier next, like height. A complete
|
||
qualified name looks something like Json.Decode.string.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn record_access_ends_with_dot() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
foo.bar.
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I trying to parse a record field access here:
|
||
|
||
1│ foo.bar.
|
||
^
|
||
|
||
So I expect to see a lowercase letter next, like .name or .height.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn qualified_private_tag() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
@Foo.Bar
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am very confused by this expression:
|
||
|
||
1│ @Foo.Bar
|
||
^^^^
|
||
|
||
Looks like a private tag is treated like a module name. Maybe you
|
||
wanted a qualified name, like Json.Decode.string?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_annotation_double_colon() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f :: I64
|
||
f = 42
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNKNOWN OPERATOR ────────────────────────────────────────────────────────────
|
||
|
||
This looks like an operator, but it's not one I recognize!
|
||
|
||
1│ f :: I64
|
||
^^
|
||
|
||
I have no specific suggestion for this operator, see TODO for the full
|
||
list of operators in Roc.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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 ───────────────────────────────────────────────────────────────
|
||
|
||
This value is not a function, but it was given 3 arguments:
|
||
|
||
3│ x == 5
|
||
^
|
||
|
||
Are there any missing commas? Or missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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_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]
|
||
fn comment_with_tab() {
|
||
report_problem_as(
|
||
"# comment with a \t\n4",
|
||
indoc!(
|
||
"
|
||
── TAB CHARACTER ───────────────────────────────────────────────────────────────
|
||
|
||
I encountered a tab character
|
||
|
||
1│ # comment with a \t
|
||
^
|
||
|
||
Tab characters are not allowed.
|
||
"
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_in_parens_start() {
|
||
// TODO bad error message
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : (
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED TYPE ─────────────────────────────────────────────────────────────
|
||
|
||
I just started parsing a type, but I got stuck here:
|
||
|
||
1│ f : (
|
||
^
|
||
|
||
I am expecting a type next, like Bool or List a.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[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 parenthesis before this, so try adding a )
|
||
and see if that helps?
|
||
|
||
Note: I may be confused by indentation
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_apply_double_dot() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo..Bar
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am confused by this type name:
|
||
|
||
1│ f : Foo..Bar
|
||
^^^^^^^^
|
||
|
||
Type names start with an uppercase letter, and can optionally be
|
||
qualified by a module name, like Bool or Http.Request.Request.
|
||
"#
|
||
),
|
||
)
|
||
|
||
// ── DOUBLE DOT ──────────────────────────────────────────────────────────────────
|
||
//
|
||
// I encountered two dots in a row:
|
||
//
|
||
// 1│ f : Foo..Bar
|
||
// ^
|
||
//
|
||
// Try removing one of them.
|
||
}
|
||
|
||
#[test]
|
||
fn type_apply_trailing_dot() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo.Bar.
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am confused by this type name:
|
||
|
||
1│ f : Foo.Bar.
|
||
^^^^^^^^
|
||
|
||
Type names start with an uppercase letter, and can optionally be
|
||
qualified by a module name, like Bool or Http.Request.Request.
|
||
"#
|
||
),
|
||
)
|
||
|
||
// ── TRAILING DOT ────────────────────────────────────────────────────────────────
|
||
//
|
||
// I encountered a dot with nothing after it:
|
||
//
|
||
// 1│ f : Foo.Bar.
|
||
// ^
|
||
//
|
||
// Dots are used to refer to a type in a qualified way, like
|
||
// Num.I64 or List.List a. Try adding a type name next.
|
||
}
|
||
|
||
#[test]
|
||
fn type_apply_stray_dot() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : .
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED TYPE ─────────────────────────────────────────────────────────────
|
||
|
||
I just started parsing a type, but I got stuck here:
|
||
|
||
1│ f : .
|
||
^
|
||
|
||
I am expecting a type next, like Bool or List a.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn type_apply_start_with_number() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo.1
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am confused by this type name:
|
||
|
||
1│ f : Foo.1
|
||
^^^^^
|
||
|
||
Type names start with an uppercase letter, and can optionally be
|
||
qualified by a module name, like Bool or Http.Request.Request.
|
||
"#
|
||
),
|
||
)
|
||
|
||
// ── WEIRD QUALIFIED NAME ────────────────────────────────────────────────────────
|
||
//
|
||
// I encountered a number at the start of a qualified name segment:
|
||
//
|
||
// 1│ f : Foo.1
|
||
// ^
|
||
//
|
||
// All parts of a qualified type name must start with an uppercase
|
||
// letter, like Num.I64 or List.List a.
|
||
}
|
||
|
||
#[test]
|
||
fn type_apply_start_with_lowercase() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo.foo
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am confused by this type name:
|
||
|
||
1│ f : Foo.foo
|
||
^^^^^^^
|
||
|
||
Type names start with an uppercase letter, and can optionally be
|
||
qualified by a module name, like Bool or Http.Request.Request.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn def_missing_final_expression() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : Foo.foo
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── MISSING FINAL EXPRESSION ────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a definition's final expression, but I
|
||
got stuck here:
|
||
|
||
1│ f : Foo.foo
|
||
^
|
||
|
||
This definition is missing a final expression. A nested definition
|
||
must be followed by either another definition, or an expression
|
||
|
||
x = 4
|
||
y = 2
|
||
|
||
x + y
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
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 encountered 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 am partway through 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
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn if_guard_without_condition() {
|
||
// this should get better with time
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when Just 4 is
|
||
Just if ->
|
||
4
|
||
|
||
_ ->
|
||
2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── IF GUARD NO CONDITION ───────────────────────────────────────────────────────
|
||
|
||
I just started parsing an if guard, but there is no guard condition:
|
||
|
||
1│ when Just 4 is
|
||
2│ Just if ->
|
||
^
|
||
|
||
Try adding an expression before the arrow!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn empty_or_pattern() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when Just 4 is
|
||
Just 4 | ->
|
||
4
|
||
|
||
_ ->
|
||
2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED PATTERN ──────────────────────────────────────────────────────────
|
||
|
||
I just started parsing a pattern, but I got stuck here:
|
||
|
||
2│ Just 4 | ->
|
||
^
|
||
|
||
Note: I may be confused by indentation
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_binds_keyword() {
|
||
// TODO check if "what_is_next" is a keyword
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when Just 4 is
|
||
Just when ->
|
||
4
|
||
|
||
_ ->
|
||
2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── MISSING EXPRESSION ──────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a definition, but I got stuck here:
|
||
|
||
1│ when Just 4 is
|
||
2│ Just when ->
|
||
^
|
||
|
||
I was expecting to see an expression like 42 or "hello".
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn when_missing_arrow() {
|
||
// this should get better with time
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 5 is
|
||
1 -> 2
|
||
_
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── MISSING ARROW ───────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a `when` expression, but got stuck here:
|
||
|
||
2│ 1 -> 2
|
||
3│ _
|
||
^
|
||
|
||
I was expecting to see an arrow next.
|
||
|
||
Note: Sometimes I get confused by indentation, so try to make your `when`
|
||
look something like this:
|
||
|
||
when List.first plants is
|
||
Ok n ->
|
||
n
|
||
|
||
Err _ ->
|
||
200
|
||
|
||
Notice the indentation. All patterns are aligned, and each branch is
|
||
indented a bit more than the corresponding pattern. That is important!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn lambda_double_comma() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\a,,b -> 1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED ARGUMENT LIST ────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a function argument list, but I got stuck
|
||
at this comma:
|
||
|
||
1│ \a,,b -> 1
|
||
^
|
||
|
||
I was expecting an argument pattern before this, so try adding an
|
||
argument before the comma and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn lambda_leading_comma() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\,b -> 1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED ARGUMENT LIST ────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a function argument list, but I got stuck
|
||
at this comma:
|
||
|
||
1│ \,b -> 1
|
||
^
|
||
|
||
I was expecting an argument pattern before this, so try adding an
|
||
argument before the comma and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn when_outdented_branch() {
|
||
// this should get better with time
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 4 is
|
||
5 -> 2
|
||
2 -> 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I got stuck here:
|
||
|
||
1│ when 4 is
|
||
2│ 5 -> 2
|
||
^
|
||
|
||
Whatever I am running into is confusing me a lot! Normally I can give
|
||
fairly specific hints, but something is really tripping me up this
|
||
time.
|
||
"#
|
||
),
|
||
// TODO this formerly gave
|
||
//
|
||
// ── UNFINISHED WHEN ─────────────────────────────────────────────────────────────
|
||
//
|
||
// I was partway through parsing a `when` expression, but I got stuck here:
|
||
//
|
||
// 3│ _ -> 2
|
||
// ^
|
||
//
|
||
// I suspect this is a pattern that is not indented enough? (by 2 spaces)
|
||
//
|
||
// but that requires parsing the next pattern blindly, irrespective of indentation. Can
|
||
// we find an efficient solution that doesn't require parsing an extra pattern for
|
||
// every `when`, i.e. we want a good error message for the test case above, but for
|
||
// a valid `when`, we don't want to do extra work, e.g. here
|
||
//
|
||
// x
|
||
// when x is
|
||
// n -> n
|
||
//
|
||
// 4
|
||
//
|
||
// We don't want to parse the `4` and say it's an outdented pattern!
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn when_over_indented_underscore() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 4 is
|
||
5 -> 2
|
||
_ -> 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I got stuck here:
|
||
|
||
1│ when 4 is
|
||
2│ 5 -> 2
|
||
^
|
||
|
||
Whatever I am running into is confusing me a lot! Normally I can give
|
||
fairly specific hints, but something is really tripping me up this
|
||
time.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn when_over_indented_int() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when 4 is
|
||
5 -> Num.neg
|
||
2 -> 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNEXPECTED ARROW ────────────────────────────────────────────────────────────
|
||
|
||
I am parsing a `when` expression right now, but this arrow is confusing
|
||
me:
|
||
|
||
3│ 2 -> 2
|
||
^^
|
||
|
||
It makes sense to see arrows around here, so I suspect it is something
|
||
earlier.Maybe this pattern is indented a bit farther from the previous
|
||
patterns?
|
||
|
||
Note: Here is an example of a valid `when` expression for reference.
|
||
|
||
when List.first plants is
|
||
Ok n ->
|
||
n
|
||
|
||
Err _ ->
|
||
200
|
||
|
||
Notice the indentation. All patterns are aligned, and each branch is
|
||
indented a bit more than the corresponding pattern. That is important!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn if_outdented_then() {
|
||
// TODO I think we can do better here
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x =
|
||
if 5 == 5
|
||
then 2 else 3
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED IF ───────────────────────────────────────────────────────────────
|
||
|
||
I was partway through parsing an `if` expression, but I got stuck here:
|
||
|
||
2│ if 5 == 5
|
||
^
|
||
|
||
I was expecting to see the `then` keyword next.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn if_missing_else() {
|
||
// this should get better with time
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
if 5 == 5 then 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED IF ───────────────────────────────────────────────────────────────
|
||
|
||
I was partway through parsing an `if` expression, but I got stuck here:
|
||
|
||
1│ if 5 == 5 then 2
|
||
^
|
||
|
||
I was expecting to see the `else` keyword next.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn list_double_comma() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
[ 1, 2, , 3 ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED LIST ─────────────────────────────────────────────────────────────
|
||
|
||
I am partway through started parsing a list, but I got stuck here:
|
||
|
||
1│ [ 1, 2, , 3 ]
|
||
^
|
||
|
||
I was expecting to see a list entry before this comma, so try adding a
|
||
list entry and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn list_without_end() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
[ 1, 2,
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED LIST ─────────────────────────────────────────────────────────────
|
||
|
||
I am partway through started parsing a list, but I got stuck here:
|
||
|
||
1│ [ 1, 2,
|
||
^
|
||
|
||
I was expecting to see a closing square bracket before this, so try
|
||
adding a ] and see if that helps?
|
||
|
||
Note: When I get stuck like this, it usually means that there is a
|
||
missing parenthesis or bracket somewhere earlier. It could also be a
|
||
stray keyword or operator.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn number_double_dot() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
1.1.1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This float literal contains an invalid digit:
|
||
|
||
1│ 1.1.1
|
||
^^^^^
|
||
|
||
Floating point literals can only contain the digits 0-9, or use
|
||
scientific notation 10e4, or have a float suffix.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn unicode_not_hex() {
|
||
report_problem_as(
|
||
r#""abc\u(zzzz)def""#,
|
||
indoc!(
|
||
r#"
|
||
── WEIRD CODE POINT ────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a unicode code point, but I got stuck
|
||
here:
|
||
|
||
1│ "abc\u(zzzz)def"
|
||
^
|
||
|
||
I was expecting a hexadecimal number, like \u(1100) or \u(00FF).
|
||
|
||
Learn more about working with unicode in roc at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn interpolate_not_identifier() {
|
||
report_problem_as(
|
||
r#""abc\(32)def""#,
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This string interpolation is invalid:
|
||
|
||
1│ "abc\(32)def"
|
||
^^
|
||
|
||
I was expecting an identifier, like \u(message) or
|
||
\u(LoremIpsum.text).
|
||
|
||
Learn more about string interpolation at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn unicode_too_large() {
|
||
report_problem_as(
|
||
r#""abc\u(110000)def""#,
|
||
indoc!(
|
||
r#"
|
||
── INVALID UNICODE ─────────────────────────────────────────────────────────────
|
||
|
||
This unicode code point is invalid:
|
||
|
||
1│ "abc\u(110000)def"
|
||
^^^^^^
|
||
|
||
Learn more about working with unicode in roc at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn weird_escape() {
|
||
report_problem_as(
|
||
r#""abc\qdef""#,
|
||
indoc!(
|
||
r#"
|
||
── WEIRD ESCAPE ────────────────────────────────────────────────────────────────
|
||
|
||
I was partway through parsing a string literal, but I got stuck here:
|
||
|
||
1│ "abc\qdef"
|
||
^^
|
||
|
||
This is not an escape sequence I recognize. After a backslash, I am
|
||
looking for one of these:
|
||
|
||
- A newline: \n
|
||
- A caret return: \r
|
||
- A tab: \t
|
||
- An escaped quote: \"
|
||
- An escaped backslash: \\
|
||
- A unicode code point: \u(00FF)
|
||
- An interpolated string: \(myVariable)
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn single_no_end() {
|
||
report_problem_as(
|
||
r#""there is no end"#,
|
||
indoc!(
|
||
r#"
|
||
── ENDLESS STRING ──────────────────────────────────────────────────────────────
|
||
|
||
I cannot find the end of this string:
|
||
|
||
1│ "there is no end
|
||
^
|
||
|
||
You could change it to something like "to be or not to be" or even
|
||
just "".
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn multi_no_end() {
|
||
report_problem_as(
|
||
r#""""there is no end"#,
|
||
indoc!(
|
||
r#"
|
||
── ENDLESS STRING ──────────────────────────────────────────────────────────────
|
||
|
||
I cannot find the end of this block string:
|
||
|
||
1│ """there is no end
|
||
^
|
||
|
||
You could change it to something like """to be or not to be""" or even
|
||
just """""".
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
// https://github.com/rtfeldman/roc/issues/1714
|
||
fn interpolate_concat_is_transparent_1714() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
greeting = "Privet"
|
||
|
||
if True then 1 else "\(greeting), World!"
|
||
"#,
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This `if` has an `else` branch with a different type from its `then` branch:
|
||
|
||
3│ if True then 1 else "\(greeting), World!"
|
||
^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
The `else` branch is a string of type:
|
||
|
||
Str
|
||
|
||
but the `then` branch has the type:
|
||
|
||
Num a
|
||
|
||
I need all branches in an `if` to have the same type!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
macro_rules! comparison_binop_transparency_tests {
|
||
($($op:expr, $name:ident),* $(,)?) => {
|
||
$(
|
||
#[test]
|
||
fn $name() {
|
||
report_problem_as(
|
||
&format!(r#"if True then "abc" else 1 {} 2"#, $op),
|
||
&format!(
|
||
r#"── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This `if` has an `else` branch with a different type from its `then` branch:
|
||
|
||
1│ if True then "abc" else 1 {} 2
|
||
^^{}^^
|
||
|
||
This comparison produces:
|
||
|
||
Bool
|
||
|
||
but the `then` branch has the type:
|
||
|
||
Str
|
||
|
||
I need all branches in an `if` to have the same type!
|
||
"#,
|
||
$op, "^".repeat($op.len())
|
||
),
|
||
)
|
||
}
|
||
)*
|
||
}
|
||
}
|
||
|
||
comparison_binop_transparency_tests! {
|
||
"<", lt_binop_is_transparent,
|
||
">", gt_binop_is_transparent,
|
||
"==", eq_binop_is_transparent,
|
||
"!=", neq_binop_is_transparent,
|
||
"<=", leq_binop_is_transparent,
|
||
">=", geq_binop_is_transparent,
|
||
}
|
||
|
||
#[test]
|
||
fn keyword_record_field_access() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
foo = {}
|
||
|
||
foo.if
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This expression is used in an unexpected way:
|
||
|
||
3│ foo.if
|
||
^^^^^^
|
||
|
||
This `foo` value is a:
|
||
|
||
{}
|
||
|
||
But you are trying to use it as:
|
||
|
||
{ if : a }b
|
||
|
||
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn keyword_qualified_import() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Num.if
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NOT EXPOSED ─────────────────────────────────────────────────────────────────
|
||
|
||
The Num module does not expose `if`:
|
||
|
||
1│ Num.if
|
||
^^^^^^
|
||
|
||
Did you mean one of these?
|
||
|
||
Num.sin
|
||
Num.div
|
||
Num.abs
|
||
Num.neg
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn stray_dot_expr() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Num.add . 23
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I trying to parse a record field access here:
|
||
|
||
1│ Num.add . 23
|
||
^
|
||
|
||
So I expect to see a lowercase letter next, like .name or .height.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn private_tag_not_uppercase() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Num.add @foo 23
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am trying to parse a private tag here:
|
||
|
||
1│ Num.add @foo 23
|
||
^
|
||
|
||
But after the `@` symbol I found a lowercase letter. All tag names
|
||
(global and private) must start with an uppercase letter, like @UUID
|
||
or @Secrets.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn private_tag_field_access() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
@UUID.bar
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am very confused by this field access:
|
||
|
||
1│ @UUID.bar
|
||
^^^^
|
||
|
||
It looks like a record field access on a private tag.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_ref_field_access() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
$UUID.bar
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am very confused by this field access:
|
||
|
||
1│ $UUID.bar
|
||
^^^^
|
||
|
||
It looks like a record field access on an opaque reference.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn weird_accessor() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
.foo.bar
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am very confused by this field access
|
||
|
||
1│ .foo.bar
|
||
^^^^^^^^
|
||
|
||
It looks like a field access on an accessor. I parse.client.name as
|
||
(.client).name. Maybe use an anonymous function like
|
||
(\r -> r.client.name) instead?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn part_starts_with_number() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
foo.100
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I trying to parse a record field access here:
|
||
|
||
1│ foo.100
|
||
^
|
||
|
||
So I expect to see a lowercase letter next, like .name or .height.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn closure_underscore_ident() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\the_answer -> 100
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NAMING PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am trying to parse an identifier here:
|
||
|
||
1│ \the_answer -> 100
|
||
^
|
||
|
||
Underscores are not allowed in identifiers. Use camelCase instead!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
#[ignore]
|
||
fn double_binop() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
key >= 97 && <= 122
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
#[ignore]
|
||
fn case_of() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
case 1 of
|
||
1 -> True
|
||
_ -> False
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn argument_without_space() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
[ "foo", bar("") ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `bar` value
|
||
|
||
1│ [ "foo", bar("") ]
|
||
^^^
|
||
|
||
Did you mean one of these?
|
||
|
||
Nat
|
||
Str
|
||
Err
|
||
U8
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_operator() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
main =
|
||
5 ** 3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNKNOWN OPERATOR ────────────────────────────────────────────────────────────
|
||
|
||
This looks like an operator, but it's not one I recognize!
|
||
|
||
1│ main =
|
||
2│ 5 ** 3
|
||
^^
|
||
|
||
I have no specific suggestion for this operator, see TODO for the full
|
||
list of operators in Roc.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn double_plus() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
main =
|
||
[] ++ []
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNKNOWN OPERATOR ────────────────────────────────────────────────────────────
|
||
|
||
This looks like an operator, but it's not one I recognize!
|
||
|
||
1│ main =
|
||
2│ [] ++ []
|
||
^^
|
||
|
||
To concatenate two lists or strings, try using List.concat or
|
||
Str.concat instead.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn inline_hastype() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
main =
|
||
(\x -> x) : I64
|
||
|
||
3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNKNOWN OPERATOR ────────────────────────────────────────────────────────────
|
||
|
||
This looks like an operator, but it's not one I recognize!
|
||
|
||
1│ main =
|
||
2│ (\x -> x) : I64
|
||
^
|
||
|
||
The has-type operator : can only occur in a definition's type
|
||
signature, like
|
||
|
||
increment : I64 -> I64
|
||
increment = \x -> x + 1
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn wild_case_arrow() {
|
||
// this is still bad, but changing the order and progress of other parsers should improve it
|
||
// down the line
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
main = 5 -> 3
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNKNOWN OPERATOR ────────────────────────────────────────────────────────────
|
||
|
||
This looks like an operator, but it's not one I recognize!
|
||
|
||
1│ main = 5 -> 3
|
||
^^
|
||
|
||
The arrow -> is only used to define cases in a `when`.
|
||
|
||
when color is
|
||
Red -> "stop!"
|
||
Green -> "go!"
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn provides_to_identifier() {
|
||
report_header_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test-base64"
|
||
packages { pf: "platform" }
|
||
imports [pf.Task, Base64 ]
|
||
provides [ main, @Foo ] to pf
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD PROVIDES ──────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a provides list, but I got stuck here:
|
||
|
||
3│ imports [pf.Task, Base64 ]
|
||
4│ provides [ main, @Foo ] to pf
|
||
^
|
||
|
||
I was expecting a type name, value name or function name next, like
|
||
|
||
provides [ Animal, default, tame ]
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn platform_requires_rigids() {
|
||
report_header_problem_as(
|
||
indoc!(
|
||
r#"
|
||
platform "folkertdev/foo"
|
||
requires { main : Effect {} }
|
||
exposes []
|
||
packages {}
|
||
imports [Task]
|
||
provides [ mainForHost ]
|
||
effects fx.Effect
|
||
{
|
||
putChar : I64 -> Effect {},
|
||
putLine : Str -> Effect {},
|
||
getLine : Effect Str
|
||
}
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── BAD REQUIRES ────────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a header, but I got stuck here:
|
||
|
||
1│ platform "folkertdev/foo"
|
||
2│ requires { main : Effect {} }
|
||
^
|
||
|
||
I am expecting a list of type names like `{}` or `{ Model }` next. A full
|
||
`requires` definition looks like
|
||
|
||
requires { Model, Msg } {main : Effect {}}
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn missing_imports() {
|
||
report_header_problem_as(
|
||
indoc!(
|
||
r#"
|
||
interface Foobar
|
||
exposes [ main, Foo ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD IMPORTS ───────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a header, but I got stuck here:
|
||
|
||
2│ exposes [ main, Foo ]
|
||
^
|
||
|
||
I am expecting the `imports` keyword next, like
|
||
|
||
imports [ Animal, default, tame ]
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn exposes_identifier() {
|
||
report_header_problem_as(
|
||
indoc!(
|
||
r#"
|
||
interface Foobar
|
||
exposes [ main, @Foo ]
|
||
imports [pf.Task, Base64 ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD EXPOSES ───────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing an `exposes` list, but I got stuck here:
|
||
|
||
1│ interface Foobar
|
||
2│ exposes [ main, @Foo ]
|
||
^
|
||
|
||
I was expecting a type name, value name or function name next, like
|
||
|
||
exposes [ Animal, default, tame ]
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_module_name() {
|
||
report_header_problem_as(
|
||
indoc!(
|
||
r#"
|
||
interface foobar
|
||
exposes [ main, @Foo ]
|
||
imports [pf.Task, Base64 ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD MODULE NAME ───────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a header, but got stuck here:
|
||
|
||
1│ interface foobar
|
||
^
|
||
|
||
I am expecting a module name next, like BigNum or Main. Module names
|
||
must start with an uppercase letter.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_app_name() {
|
||
report_header_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app foobar
|
||
exposes [ main, @Foo ]
|
||
imports [pf.Task, Base64 ]
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── WEIRD APP NAME ──────────────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a header, but got stuck here:
|
||
|
||
1│ app foobar
|
||
^
|
||
|
||
I am expecting an application name next, like app "main" or
|
||
app "editor". App names are surrounded by quotation marks.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn apply_unary_negative() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
foo = 3
|
||
|
||
-foo 1 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY ARGS ───────────────────────────────────────────────────────────────
|
||
|
||
This value is not a function, but it was given 2 arguments:
|
||
|
||
3│ -foo 1 2
|
||
^^^^
|
||
|
||
Are there any missing commas? Or missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn apply_unary_not() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
foo = True
|
||
|
||
!foo 1 2
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY ARGS ───────────────────────────────────────────────────────────────
|
||
|
||
This value is not a function, but it was given 2 arguments:
|
||
|
||
3│ !foo 1 2
|
||
^^^^
|
||
|
||
Are there any missing commas? Or missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn applied_tag_function() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : List [ Foo Str ]
|
||
x = List.map [ 1, 2 ] Foo
|
||
|
||
x
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `x` definition:
|
||
|
||
1│ x : List [ Foo Str ]
|
||
2│ x = List.map [ 1, 2 ] Foo
|
||
^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
This `map` call produces:
|
||
|
||
List [ Foo Num a ]
|
||
|
||
But the type annotation on `x` says it should be:
|
||
|
||
List [ Foo Str ]
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_in_parens_open() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\( a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a pattern in parentheses, but I got stuck
|
||
here:
|
||
|
||
1│ \( a
|
||
^
|
||
|
||
I was expecting to see a closing parenthesis before this, so try
|
||
adding a ) and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_in_parens_end_comma() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\( a,
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a pattern in parentheses, but I got stuck
|
||
here:
|
||
|
||
1│ \( a,
|
||
^
|
||
|
||
I was expecting to see a closing parenthesis before this, so try
|
||
adding a ) and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_in_parens_end() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\( a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED PARENTHESES ──────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a pattern in parentheses, but I got stuck
|
||
here:
|
||
|
||
1│ \( a
|
||
^
|
||
|
||
I was expecting to see a closing parenthesis before this, so try
|
||
adding a ) and see if that helps?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_in_parens_indent_end() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x = \( a
|
||
)
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NEED MORE INDENTATION ───────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a pattern in parentheses, but I got stuck
|
||
here:
|
||
|
||
1│ x = \( a
|
||
2│ )
|
||
^
|
||
|
||
I need this parenthesis to be indented more. Try adding more spaces
|
||
before it!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn pattern_in_parens_indent_open() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
\(
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED PATTERN ──────────────────────────────────────────────────────────
|
||
|
||
I just started parsing a pattern, but I got stuck here:
|
||
|
||
1│ \(
|
||
^
|
||
|
||
Note: I may be confused by indentation
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn outdented_in_parens() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Box : (
|
||
Str
|
||
)
|
||
|
||
4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NEED MORE INDENTATION ───────────────────────────────────────────────────────
|
||
|
||
I am partway through parsing a type in parentheses, but I got stuck
|
||
here:
|
||
|
||
1│ Box : (
|
||
2│ Str
|
||
3│ )
|
||
^
|
||
|
||
I need this parenthesis to be indented more. Try adding more spaces
|
||
before it!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn backpassing_type_error() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x <- List.map [ "a", "b" ]
|
||
|
||
x + 1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `map` is not what I expect:
|
||
|
||
1│> x <- List.map [ "a", "b" ]
|
||
2│>
|
||
3│> x + 1
|
||
|
||
This argument is an anonymous function of type:
|
||
|
||
Num a -> Num a
|
||
|
||
But `map` needs the 2nd argument to be:
|
||
|
||
Str -> Num a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn underscore_let() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
_ = 3
|
||
|
||
4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
Underscore patterns are not allowed in definitions
|
||
|
||
1│ _ = 3
|
||
^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn expect_expr_type_error() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
expect "foobar"
|
||
|
||
4
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This `expect` condition needs to be a Bool:
|
||
|
||
1│ expect "foobar"
|
||
^^^^^^^^
|
||
|
||
Right now it’s a string of type:
|
||
|
||
Str
|
||
|
||
But I need every `expect` condition to evaluate to a Bool—either `True`
|
||
or `False`.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn num_too_general_wildcard() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
mult : Num *, F64 -> F64
|
||
mult = \a, b -> a * b
|
||
|
||
mult 0 0
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `mul` is not what I expect:
|
||
|
||
2│ mult = \a, b -> a * b
|
||
^
|
||
|
||
This `b` value is a:
|
||
|
||
F64
|
||
|
||
But `mul` needs the 2nd argument to be:
|
||
|
||
Num *
|
||
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `mult` definition:
|
||
|
||
1│ mult : Num *, F64 -> F64
|
||
2│ mult = \a, b -> a * b
|
||
^^^^^
|
||
|
||
This `mul` call produces:
|
||
|
||
Num *
|
||
|
||
But the type annotation on `mult` says it should be:
|
||
|
||
F64
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn num_too_general_named() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
mult : Num a, F64 -> F64
|
||
mult = \a, b -> a * b
|
||
|
||
mult 0 0
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `mul` is not what I expect:
|
||
|
||
2│ mult = \a, b -> a * b
|
||
^
|
||
|
||
This `b` value is a:
|
||
|
||
F64
|
||
|
||
But `mul` needs the 2nd argument to be:
|
||
|
||
Num a
|
||
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `mult` definition:
|
||
|
||
1│ mult : Num a, F64 -> F64
|
||
2│ mult = \a, b -> a * b
|
||
^^^^^
|
||
|
||
This `mul` call produces:
|
||
|
||
Num a
|
||
|
||
But the type annotation on `mult` says it should be:
|
||
|
||
F64
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn inference_var_not_enough_in_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
canIGo : _ -> Result _
|
||
canIGo = \color ->
|
||
when color is
|
||
"green" -> Ok "go!"
|
||
"yellow" -> Err (SlowIt "whoa, let's slow down!")
|
||
"red" -> Err (StopIt "absolutely not")
|
||
_ -> Err (UnknownColor "this is a weird stoplight")
|
||
canIGo
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO FEW TYPE ARGUMENTS ──────────────────────────────────────────────────────
|
||
|
||
The `Result` alias expects 2 type arguments, but it got 1 instead:
|
||
|
||
1│ canIGo : _ -> Result _
|
||
^^^^^^^^
|
||
|
||
Are there missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn inference_var_too_many_in_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
canIGo : _ -> Result _ _ _
|
||
canIGo = \color ->
|
||
when color is
|
||
"green" -> Ok "go!"
|
||
"yellow" -> Err (SlowIt "whoa, let's slow down!")
|
||
"red" -> Err (StopIt "absolutely not")
|
||
_ -> Err (UnknownColor "this is a weird stoplight")
|
||
canIGo
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TOO MANY TYPE ARGUMENTS ─────────────────────────────────────────────────────
|
||
|
||
The `Result` alias expects 2 type arguments, but it got 3 instead:
|
||
|
||
1│ canIGo : _ -> Result _ _ _
|
||
^^^^^^^^^^^^
|
||
|
||
Are there missing parentheses?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn inference_var_conflict_in_rigid_links() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : a -> (_ -> b)
|
||
f = \x -> \y -> if x == y then x else y
|
||
f
|
||
"#
|
||
),
|
||
// TODO: We should tell the user that we inferred `_` as `a`
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : a -> (_ -> b)
|
||
2│ f = \x -> \y -> if x == y then x else y
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
The body is an anonymous function of type:
|
||
|
||
a -> a
|
||
|
||
But the type annotation on `f` says it should be:
|
||
|
||
a -> b
|
||
|
||
Tip: Your type annotation uses `a` and `b` as separate type variables.
|
||
Your code seems to be saying they are the same though. Maybe they
|
||
should be the same in your type annotation? Maybe your code uses them
|
||
in a weird way?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn error_wildcards_are_related() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : * -> *
|
||
f = \x -> x
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : * -> *
|
||
2│ f = \x -> x
|
||
^
|
||
|
||
The type annotation on `f` says this `x` value should have the type:
|
||
|
||
*
|
||
|
||
However, the type of this `x` value is connected to another type in a
|
||
way that isn't reflected in this annotation.
|
||
|
||
Tip: Any connection between types must use a named type variable, not
|
||
a `*`! Maybe the annotation on `f` should have a named type variable in
|
||
place of the `*`?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn error_nested_wildcards_are_related() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : a, b, * -> {x: a, y: b, z: *}
|
||
f = \x, y, z -> {x, y, z}
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : a, b, * -> {x: a, y: b, z: *}
|
||
2│ f = \x, y, z -> {x, y, z}
|
||
^^^^^^^^^
|
||
|
||
The type annotation on `f` says the body is a record should have the
|
||
type:
|
||
|
||
{ x : a, y : b, z : * }
|
||
|
||
However, the type of the body is a record is connected to another type
|
||
in a way that isn't reflected in this annotation.
|
||
|
||
Tip: Any connection between types must use a named type variable, not
|
||
a `*`! Maybe the annotation on `f` should have a named type variable in
|
||
place of the `*`?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn error_wildcards_are_related_in_nested_defs() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : a, b, * -> *
|
||
f = \_, _, x2 ->
|
||
inner : * -> *
|
||
inner = \y -> y
|
||
inner x2
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `f` definition:
|
||
|
||
1│ f : a, b, * -> *
|
||
2│ f = \_, _, x2 ->
|
||
3│ inner : * -> *
|
||
4│ inner = \y -> y
|
||
5│ inner x2
|
||
^^^^^^^^
|
||
|
||
The type annotation on `f` says this `inner` call should have the type:
|
||
|
||
*
|
||
|
||
However, the type of this `inner` call is connected to another type in a
|
||
way that isn't reflected in this annotation.
|
||
|
||
Tip: Any connection between types must use a named type variable, not
|
||
a `*`! Maybe the annotation on `f` should have a named type variable in
|
||
place of the `*`?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn error_inline_alias_not_an_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : List elem -> [ Nil, Cons elem a ] as a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NOT AN INLINE ALIAS ─────────────────────────────────────────────────────────
|
||
|
||
The inline type after this `as` is not a type alias:
|
||
|
||
1│ f : List elem -> [ Nil, Cons elem a ] as a
|
||
^
|
||
|
||
Inline alias types must start with an uppercase identifier and be
|
||
followed by zero or more type arguments, like Point or List a.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn error_inline_alias_qualified() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : List elem -> [ Nil, Cons elem a ] as Module.LinkedList a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── QUALIFIED ALIAS NAME ────────────────────────────────────────────────────────
|
||
|
||
This type alias has a qualified name:
|
||
|
||
1│ f : List elem -> [ Nil, Cons elem a ] as Module.LinkedList a
|
||
^
|
||
|
||
An alias introduces a new name to the current scope, so it must be
|
||
unqualified.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn error_inline_alias_argument_uppercase() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : List elem -> [ Nil, Cons elem a ] as LinkedList U
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE ARGUMENT NOT LOWERCASE ─────────────────────────────────────────────────
|
||
|
||
This alias type argument is not lowercase:
|
||
|
||
1│ f : List elem -> [ Nil, Cons elem a ] as LinkedList U
|
||
^
|
||
|
||
All type arguments must be lowercase.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn mismatched_single_tag_arg() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
isEmpty =
|
||
\email ->
|
||
Email str = email
|
||
Str.isEmpty str
|
||
|
||
isEmpty (Name "boo")
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `isEmpty` is not what I expect:
|
||
|
||
6│ isEmpty (Name "boo")
|
||
^^^^^^^^^^
|
||
|
||
This `Name` global tag application has the type:
|
||
|
||
[ Name Str ]a
|
||
|
||
But `isEmpty` needs the 1st argument to be:
|
||
|
||
[ Email Str ]
|
||
|
||
Tip: Seems like a tag typo. Maybe `Name` should be `Email`?
|
||
|
||
Tip: Can more type annotations be added? Type annotations always help
|
||
me give more specific messages, and I think they could help a lot in
|
||
this case
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn issue_2326() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
C a b : a -> D a b
|
||
D a b : { a, b }
|
||
|
||
f : C a Nat -> D a Nat
|
||
f = \c -> c 6
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `c` is not what I expect:
|
||
|
||
5│ f = \c -> c 6
|
||
^
|
||
|
||
This argument is a number of type:
|
||
|
||
Num a
|
||
|
||
But `c` needs the 1st argument to be:
|
||
|
||
a
|
||
|
||
Tip: The type annotation uses the type variable `a` to say that this
|
||
definition can produce any type of value. But in the body I see that
|
||
it will only produce a `Num` value of a single specific type. Maybe
|
||
change the type annotation to be more specific? Maybe change the code
|
||
to be more general?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn issue_2380_annotations_only() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
F : F
|
||
a : F
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CYCLIC ALIAS ────────────────────────────────────────────────────────────────
|
||
|
||
The `F` alias is self-recursive in an invalid way:
|
||
|
||
1│ F : F
|
||
^
|
||
|
||
Recursion in aliases is only allowed if recursion happens behind a
|
||
tagged union, at least one variant of which is not recursive.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn issue_2380_typed_body() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
F : F
|
||
a : F
|
||
a = 1
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CYCLIC ALIAS ────────────────────────────────────────────────────────────────
|
||
|
||
The `F` alias is self-recursive in an invalid way:
|
||
|
||
1│ F : F
|
||
^
|
||
|
||
Recursion in aliases is only allowed if recursion happens behind a
|
||
tagged union, at least one variant of which is not recursive.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn issue_2380_alias_with_vars() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
F a b : F a b
|
||
a : F Str Str
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CYCLIC ALIAS ────────────────────────────────────────────────────────────────
|
||
|
||
The `F` alias is self-recursive in an invalid way:
|
||
|
||
1│ F a b : F a b
|
||
^
|
||
|
||
Recursion in aliases is only allowed if recursion happens behind a
|
||
tagged union, at least one variant of which is not recursive.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn issue_2167_record_field_optional_and_required_mismatch() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Job : [ @Job { inputs : List Str } ]
|
||
job : { inputs ? List Str } -> Job
|
||
job = \{ inputs } ->
|
||
@Job { inputs }
|
||
|
||
job { inputs: [ "build", "test" ] }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `job` is weird:
|
||
|
||
3│ job = \{ inputs } ->
|
||
^^^^^^^^^^
|
||
|
||
The argument is a pattern that matches record values of type:
|
||
|
||
{ inputs : List Str }
|
||
|
||
But the annotation on `job` says the 1st argument should be:
|
||
|
||
{ inputs ? List Str }
|
||
|
||
Tip: To extract the `.inputs` field it must be non-optional, but the
|
||
type says this field is optional. Learn more about optional fields at
|
||
TODO.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn unify_recursive_with_nonrecursive() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Job : [ @Job { inputs : List Job } ]
|
||
|
||
job : { inputs : List Str } -> Job
|
||
job = \{ inputs } ->
|
||
@Job { inputs }
|
||
|
||
job { inputs: [ "build", "test" ] }
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `job` definition:
|
||
|
||
3│ job : { inputs : List Str } -> Job
|
||
4│ job = \{ inputs } ->
|
||
5│ @Job { inputs }
|
||
^^^^^^^^^^^^^^^
|
||
|
||
This `@Job` private tag application has the type:
|
||
|
||
[ @Job { inputs : List Str } ]
|
||
|
||
But the type annotation on `job` says it should be:
|
||
|
||
[ @Job { inputs : List a } ] as a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn nested_datatype() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Nested a : [ Chain a (Nested (List a)), Term ]
|
||
|
||
s : Nested Str
|
||
|
||
s
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NESTED DATATYPE ─────────────────────────────────────────────────────────────
|
||
|
||
`Nested` is a nested datatype. Here is one recursive usage of it:
|
||
|
||
1│ Nested a : [ Chain a (Nested (List a)), Term ]
|
||
^^^^^^^^^^^^^^^
|
||
|
||
But recursive usages of `Nested` must match its definition:
|
||
|
||
1│ Nested a : [ Chain a (Nested (List a)), Term ]
|
||
^^^^^^^^
|
||
|
||
Nested datatypes are not supported in Roc.
|
||
|
||
Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn nested_datatype_inline() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── NESTED DATATYPE ─────────────────────────────────────────────────────────────
|
||
|
||
`Nested` is a nested datatype. Here is one recursive usage of it:
|
||
|
||
1│ f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a
|
||
^^^^^^^^^^^^^^^
|
||
|
||
But recursive usages of `Nested` must match its definition:
|
||
|
||
1│ f : {} -> [ Chain a (Nested (List a)), Term ] as Nested a
|
||
^^^^^^^^
|
||
|
||
Nested datatypes are not supported in Roc.
|
||
|
||
Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
macro_rules! mismatched_suffix_tests {
|
||
($($number:expr, $suffix:expr, $name:ident)*) => {$(
|
||
#[test]
|
||
fn $name() {
|
||
let number = $number.to_string();
|
||
let mut typ = $suffix.to_string();
|
||
typ.get_mut(0..1).unwrap().make_ascii_uppercase();
|
||
let bad_type = if $suffix == "u8" { "I8" } else { "U8" };
|
||
let carets = "^".repeat(number.len() + $suffix.len());
|
||
let kind = match $suffix {
|
||
"dec"|"f32"|"f64" => "a float",
|
||
_ => "an integer",
|
||
};
|
||
|
||
report_problem_as(
|
||
&format!(indoc!(
|
||
r#"
|
||
use : {} -> U8
|
||
use {}{}
|
||
"#
|
||
), bad_type, number, $suffix),
|
||
&format!(indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `use` is not what I expect:
|
||
|
||
2│ use {}{}
|
||
{}
|
||
|
||
This argument is {} of type:
|
||
|
||
{}
|
||
|
||
But `use` needs the 1st argument to be:
|
||
|
||
{}
|
||
"#
|
||
), number, $suffix, carets, kind, typ, bad_type),
|
||
)
|
||
}
|
||
)*}
|
||
}
|
||
|
||
mismatched_suffix_tests! {
|
||
1, "u8", mismatched_suffix_u8
|
||
1, "u16", mismatched_suffix_u16
|
||
1, "u32", mismatched_suffix_u32
|
||
1, "u64", mismatched_suffix_u64
|
||
1, "u128", mismatched_suffix_u128
|
||
1, "i8", mismatched_suffix_i8
|
||
1, "i16", mismatched_suffix_i16
|
||
1, "i32", mismatched_suffix_i32
|
||
1, "i64", mismatched_suffix_i64
|
||
1, "i128", mismatched_suffix_i128
|
||
1, "nat", mismatched_suffix_nat
|
||
1, "dec", mismatched_suffix_dec
|
||
1, "f32", mismatched_suffix_f32
|
||
1, "f64", mismatched_suffix_f64
|
||
}
|
||
|
||
macro_rules! mismatched_suffix_tests_in_pattern {
|
||
($($number:expr, $suffix:expr, $name:ident)*) => {$(
|
||
#[test]
|
||
fn $name() {
|
||
let number = $number.to_string();
|
||
let mut typ = $suffix.to_string();
|
||
typ.get_mut(0..1).unwrap().make_ascii_uppercase();
|
||
let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" };
|
||
let bad_type = if $suffix == "u8" { "I8" } else { "U8" };
|
||
let carets = "^".repeat(number.len() + $suffix.len());
|
||
let kind = match $suffix {
|
||
"dec"|"f32"|"f64" => "floats",
|
||
_ => "integers",
|
||
};
|
||
|
||
report_problem_as(
|
||
&format!(indoc!(
|
||
r#"
|
||
when {}{} is
|
||
{}{} -> 1
|
||
_ -> 1
|
||
"#
|
||
), number, bad_suffix, number, $suffix),
|
||
&format!(indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st pattern in this `when` is causing a mismatch:
|
||
|
||
2│ {}{} -> 1
|
||
{}
|
||
|
||
The first pattern is trying to match {}:
|
||
|
||
{}
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
{}
|
||
"#
|
||
), number, $suffix, carets, kind, typ, bad_type),
|
||
)
|
||
}
|
||
)*}
|
||
}
|
||
|
||
mismatched_suffix_tests_in_pattern! {
|
||
1, "u8", mismatched_suffix_u8_pattern
|
||
1, "u16", mismatched_suffix_u16_pattern
|
||
1, "u32", mismatched_suffix_u32_pattern
|
||
1, "u64", mismatched_suffix_u64_pattern
|
||
1, "u128", mismatched_suffix_u128_pattern
|
||
1, "i8", mismatched_suffix_i8_pattern
|
||
1, "i16", mismatched_suffix_i16_pattern
|
||
1, "i32", mismatched_suffix_i32_pattern
|
||
1, "i64", mismatched_suffix_i64_pattern
|
||
1, "i128", mismatched_suffix_i128_pattern
|
||
1, "nat", mismatched_suffix_nat_pattern
|
||
1, "dec", mismatched_suffix_dec_pattern
|
||
1, "f32", mismatched_suffix_f32_pattern
|
||
1, "f64", mismatched_suffix_f64_pattern
|
||
}
|
||
|
||
#[test]
|
||
fn bad_numeric_literal_suffix() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
1u256
|
||
"#
|
||
),
|
||
// TODO: link to number suffixes
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal contains an invalid digit:
|
||
|
||
1│ 1u256
|
||
^^^^^
|
||
|
||
Integer literals can only contain the digits
|
||
0-9, or have an integer suffix.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn numer_literal_multi_suffix() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
1u8u8
|
||
"#
|
||
),
|
||
// TODO: link to number suffixes
|
||
indoc!(
|
||
r#"
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
This integer literal contains an invalid digit:
|
||
|
||
1│ 1u8u8
|
||
^^^^^
|
||
|
||
Integer literals can only contain the digits
|
||
0-9, or have an integer suffix.
|
||
|
||
Tip: Learn more about number literals at TODO
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn int_literal_has_float_suffix() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
0b1f32
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CONFLICTING NUMBER SUFFIX ───────────────────────────────────────────────────
|
||
|
||
This number literal is an integer, but it has a float suffix:
|
||
|
||
1│ 0b1f32
|
||
^^^^^^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn float_literal_has_int_suffix() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
1.0u8
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CONFLICTING NUMBER SUFFIX ───────────────────────────────────────────────────
|
||
|
||
This number literal is a float, but it has an integer suffix:
|
||
|
||
1│ 1.0u8
|
||
^^^^^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn u8_overflow() {
|
||
report_problem_as(
|
||
"256u8",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 256u8
|
||
^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U8, whose maximum value is
|
||
255.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn negative_u8() {
|
||
report_problem_as(
|
||
"-1u8",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -1u8
|
||
^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U8, whose minimum value is
|
||
0.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn u16_overflow() {
|
||
report_problem_as(
|
||
"65536u16",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 65536u16
|
||
^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U16, whose maximum value
|
||
is 65535.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn negative_u16() {
|
||
report_problem_as(
|
||
"-1u16",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -1u16
|
||
^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U16, whose minimum value
|
||
is 0.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn u32_overflow() {
|
||
report_problem_as(
|
||
"4_294_967_296u32",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 4_294_967_296u32
|
||
^^^^^^^^^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U32, whose maximum value
|
||
is 4_294_967_295.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn negative_u32() {
|
||
report_problem_as(
|
||
"-1u32",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -1u32
|
||
^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U32, whose minimum value
|
||
is 0.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn u64_overflow() {
|
||
report_problem_as(
|
||
"18_446_744_073_709_551_616u64",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 18_446_744_073_709_551_616u64
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U64, whose maximum value
|
||
is 18_446_744_073_709_551_615.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn negative_u64() {
|
||
report_problem_as(
|
||
"-1u64",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -1u64
|
||
^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U64, whose minimum value
|
||
is 0.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn negative_u128() {
|
||
report_problem_as(
|
||
"-1u128",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -1u128
|
||
^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a U128, whose minimum value
|
||
is 0.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i8_overflow() {
|
||
report_problem_as(
|
||
"128i8",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 128i8
|
||
^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I8, whose maximum value is
|
||
127.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i8_underflow() {
|
||
report_problem_as(
|
||
"-129i8",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -129i8
|
||
^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I8, whose minimum value is
|
||
-128.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i16_overflow() {
|
||
report_problem_as(
|
||
"32768i16",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 32768i16
|
||
^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I16, whose maximum value
|
||
is 32767.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i16_underflow() {
|
||
report_problem_as(
|
||
"-32769i16",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -32769i16
|
||
^^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I16, whose minimum value
|
||
is -32768.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i32_overflow() {
|
||
report_problem_as(
|
||
"2_147_483_648i32",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 2_147_483_648i32
|
||
^^^^^^^^^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I32, whose maximum value
|
||
is 2_147_483_647.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i32_underflow() {
|
||
report_problem_as(
|
||
"-2_147_483_649i32",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -2_147_483_649i32
|
||
^^^^^^^^^^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I32, whose minimum value
|
||
is -2_147_483_648.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i64_overflow() {
|
||
report_problem_as(
|
||
"9_223_372_036_854_775_808i64",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 9_223_372_036_854_775_808i64
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I64, whose maximum value
|
||
is 9_223_372_036_854_775_807.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i64_underflow() {
|
||
report_problem_as(
|
||
"-9_223_372_036_854_775_809i64",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER UNDERFLOWS SUFFIX ────────────────────────────────────────────────────
|
||
|
||
This integer literal underflows the type indicated by its suffix:
|
||
|
||
1│ -9_223_372_036_854_775_809i64
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I64, whose minimum value
|
||
is -9_223_372_036_854_775_808.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn i128_overflow() {
|
||
report_problem_as(
|
||
"170_141_183_460_469_231_731_687_303_715_884_105_728i128",
|
||
indoc!(
|
||
r#"
|
||
── NUMBER OVERFLOWS SUFFIX ─────────────────────────────────────────────────────
|
||
|
||
This integer literal overflows the type indicated by its suffix:
|
||
|
||
1│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128
|
||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||
|
||
Tip: The suffix indicates this integer is a I128, whose maximum value
|
||
is 170_141_183_460_469_231_731_687_303_715_884_105_727.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn list_get_negative_number() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
List.get [1,2,3] -1
|
||
"#
|
||
),
|
||
// TODO: this error message could be improved, e.g. something like "This argument can
|
||
// be used as ... because of its literal value"
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `get` is not what I expect:
|
||
|
||
1│ List.get [1,2,3] -1
|
||
^^
|
||
|
||
This argument is a number of type:
|
||
|
||
I8, I16, I32, I64, I128, F32, F64, or Dec
|
||
|
||
But `get` needs the 2nd argument to be:
|
||
|
||
Nat
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn list_get_negative_number_indirect() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
a = -9_223_372_036_854
|
||
List.get [1,2,3] a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `get` is not what I expect:
|
||
|
||
2│ List.get [1,2,3] a
|
||
^
|
||
|
||
This `a` value is a:
|
||
|
||
I64, I128, F32, F64, or Dec
|
||
|
||
But `get` needs the 2nd argument to be:
|
||
|
||
Nat
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn list_get_negative_number_double_indirect() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
a = -9_223_372_036_854
|
||
b = a
|
||
List.get [1,2,3] b
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd argument to `get` is not what I expect:
|
||
|
||
3│ List.get [1,2,3] b
|
||
^
|
||
|
||
This `b` value is a:
|
||
|
||
I64, I128, F32, F64, or Dec
|
||
|
||
But `get` needs the 2nd argument to be:
|
||
|
||
Nat
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn compare_unsigned_to_signed() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
when -1 is
|
||
1u8 -> 1
|
||
_ -> 1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st pattern in this `when` is causing a mismatch:
|
||
|
||
2│ 1u8 -> 1
|
||
^^^
|
||
|
||
The first pattern is trying to match integers:
|
||
|
||
U8
|
||
|
||
But the expression between `when` and `is` has the type:
|
||
|
||
I8, I16, I32, I64, I128, F32, F64, or Dec
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn recursive_type_alias_is_newtype() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
R a : [ Only (R a) ]
|
||
|
||
v : R U8
|
||
v
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CYCLIC ALIAS ────────────────────────────────────────────────────────────────
|
||
|
||
The `R` alias is self-recursive in an invalid way:
|
||
|
||
1│ R a : [ Only (R a) ]
|
||
^
|
||
|
||
Recursion in aliases is only allowed if recursion happens behind a
|
||
tagged union, at least one variant of which is not recursive.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn recursive_type_alias_is_newtype_deep() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
R a : [ Only { very: [ Deep (R a) ] } ]
|
||
|
||
v : R U8
|
||
v
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CYCLIC ALIAS ────────────────────────────────────────────────────────────────
|
||
|
||
The `R` alias is self-recursive in an invalid way:
|
||
|
||
1│ R a : [ Only { very: [ Deep (R a) ] } ]
|
||
^
|
||
|
||
Recursion in aliases is only allowed if recursion happens behind a
|
||
tagged union, at least one variant of which is not recursive.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn recursive_type_alias_is_newtype_mutual() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo a : [ Thing (Bar a) ]
|
||
Bar a : [ Stuff (Foo a) ]
|
||
|
||
v : Bar U8
|
||
v
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── CYCLIC ALIAS ────────────────────────────────────────────────────────────────
|
||
|
||
The `Foo` alias is recursive in an invalid way:
|
||
|
||
1│ Foo a : [ Thing (Bar a) ]
|
||
^^^
|
||
|
||
The `Foo` alias depends on itself through the following chain of
|
||
definitions:
|
||
|
||
┌─────┐
|
||
│ Foo
|
||
│ ↓
|
||
│ Bar
|
||
└─────┘
|
||
|
||
Recursion in aliases is only allowed if recursion happens behind a
|
||
tagged union, at least one variant of which is not recursive.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn issue_2458() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Foo a : [ Blah (Result (Bar a) []) ]
|
||
Bar a : Foo a
|
||
|
||
v : Bar U8
|
||
v
|
||
"#
|
||
),
|
||
"",
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_type_not_in_scope() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
$Age 21
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── OPAQUE TYPE NOT DEFINED ─────────────────────────────────────────────────────
|
||
|
||
The opaque type Age referenced here is not defined:
|
||
|
||
1│ $Age 21
|
||
^^^^
|
||
|
||
Note: It looks like there are no opaque types declared in this scope yet!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_reference_not_opaque_type() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Age : U8
|
||
|
||
$Age 21
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── OPAQUE TYPE NOT DEFINED ─────────────────────────────────────────────────────
|
||
|
||
The opaque type Age referenced here is not defined:
|
||
|
||
3│ $Age 21
|
||
^^^^
|
||
|
||
Note: There is an alias of the same name:
|
||
|
||
1│ Age : U8
|
||
^^^
|
||
|
||
Note: It looks like there are no opaque types declared in this scope yet!
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`Age` is not used anywhere in your code.
|
||
|
||
1│ Age : U8
|
||
^^^^^^^^
|
||
|
||
If you didn't intend on using `Age` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn qualified_opaque_reference() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
OtherModule.$Age 21
|
||
"#
|
||
),
|
||
// TODO: get rid of the first error. Consider parsing OtherModule.$Age to completion
|
||
// and checking it during can. The reason the error appears is because it is parsed as
|
||
// Apply(Error(OtherModule), [ $Age, 21 ])
|
||
indoc!(
|
||
r#"
|
||
── OPAQUE TYPE NOT APPLIED ─────────────────────────────────────────────────────
|
||
|
||
This opaque type is not applied to an argument:
|
||
|
||
1│ OtherModule.$Age 21
|
||
^^^^
|
||
|
||
Note: Opaque types always wrap exactly one argument!
|
||
|
||
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
|
||
|
||
I am trying to parse a qualified name here:
|
||
|
||
1│ OtherModule.$Age 21
|
||
^
|
||
|
||
I was expecting to see an identifier next, like height. A complete
|
||
qualified name looks something like Json.Decode.string.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_used_outside_declaration_scope() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
age =
|
||
Age := U8
|
||
21u8
|
||
|
||
$Age age
|
||
"#
|
||
),
|
||
// TODO(opaques): there is a potential for a better error message here, if the usage of
|
||
// `$Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to
|
||
// raise that declaration to the outer scope.
|
||
indoc!(
|
||
r#"
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`Age` is not used anywhere in your code.
|
||
|
||
2│ Age := U8
|
||
^^^^^^^^^
|
||
|
||
If you didn't intend on using `Age` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
|
||
── OPAQUE TYPE NOT DEFINED ─────────────────────────────────────────────────────
|
||
|
||
The opaque type Age referenced here is not defined:
|
||
|
||
5│ $Age age
|
||
^^^^
|
||
|
||
Note: It looks like there are no opaque types declared in this scope yet!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn unimported_modules_reported() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
main : Task.Task {} []
|
||
main = "whatever man you don't even know my type"
|
||
main
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── MODULE NOT IMPORTED ─────────────────────────────────────────────────────────
|
||
|
||
The `Task` module is not imported:
|
||
|
||
1│ main : Task.Task {} []
|
||
^^^^^^^^^^^^^^^
|
||
|
||
Is there an import missing? Perhaps there is a typo. Did you mean one
|
||
of these?
|
||
|
||
Test
|
||
List
|
||
Num
|
||
Box
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_mismatch_check() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Age := U8
|
||
|
||
n : Age
|
||
n = $Age ""
|
||
|
||
n
|
||
"#
|
||
),
|
||
// TODO(opaques): error could be improved by saying that the opaque definition demands
|
||
// that the argument be a U8, and linking to the definitin!
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This expression is used in an unexpected way:
|
||
|
||
4│ n = $Age ""
|
||
^^
|
||
|
||
This argument to an opaque type has type:
|
||
|
||
Str
|
||
|
||
But you are trying to use it as:
|
||
|
||
U8
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_mismatch_infer() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
F n := n
|
||
|
||
if True
|
||
then $F ""
|
||
else $F {}
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This expression is used in an unexpected way:
|
||
|
||
5│ else $F {}
|
||
^^
|
||
|
||
This argument to an opaque type has type:
|
||
|
||
{}
|
||
|
||
But you are trying to use it as:
|
||
|
||
Str
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_creation_is_not_wrapped() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
F n := n
|
||
|
||
v : F Str
|
||
v = ""
|
||
|
||
v
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with the body of the `v` definition:
|
||
|
||
3│ v : F Str
|
||
4│ v = ""
|
||
^^
|
||
|
||
The body is a string of type:
|
||
|
||
Str
|
||
|
||
But the type annotation on `v` says it should be:
|
||
|
||
F Str
|
||
|
||
Tip: Type comparisons between an opaque type are only ever equal if
|
||
both types are the same opaque type. Did you mean to create an opaque
|
||
type by wrapping it? If I have an opaque type Age := U32 I can create
|
||
an instance of this opaque type by doing @Age 23.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_mismatch_pattern_check() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Age := U8
|
||
|
||
f : Age -> U8
|
||
f = \Age n -> n
|
||
|
||
f
|
||
"#
|
||
),
|
||
// TODO(opaques): error could be improved by saying that the user-provided pattern
|
||
// probably wants to change "Age" to "@Age"!
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This pattern is being used in an unexpected way:
|
||
|
||
4│ f = \Age n -> n
|
||
^^^^^
|
||
|
||
It is a `Age` tag of type:
|
||
|
||
[ Age a ]
|
||
|
||
But it needs to match:
|
||
|
||
Age
|
||
|
||
Tip: Type comparisons between an opaque type are only ever equal if
|
||
both types are the same opaque type. Did you mean to create an opaque
|
||
type by wrapping it? If I have an opaque type Age := U32 I can create
|
||
an instance of this opaque type by doing @Age 23.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_mismatch_pattern_infer() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
F n := n
|
||
|
||
\x ->
|
||
when x is
|
||
$F A -> ""
|
||
$F {} -> ""
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 2nd pattern in this `when` does not match the previous ones:
|
||
|
||
6│ $F {} -> ""
|
||
^^^^^
|
||
|
||
The 2nd pattern is trying to matchF unwrappings of type:
|
||
|
||
F {}a
|
||
|
||
But all the previous branches match:
|
||
|
||
F [ A ]a
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_pattern_match_not_exhaustive_tag() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
F n := n
|
||
|
||
v : F [ A, B, C ]
|
||
|
||
when v is
|
||
$F A -> ""
|
||
$F B -> ""
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
5│> when v is
|
||
6│> $F A -> ""
|
||
7│> $F B -> ""
|
||
|
||
Other possibilities include:
|
||
|
||
$F C
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn opaque_pattern_match_not_exhaustive_int() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
F n := n
|
||
|
||
v : F U8
|
||
|
||
when v is
|
||
$F 1 -> ""
|
||
$F 2 -> ""
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
5│> when v is
|
||
6│> $F 1 -> ""
|
||
7│> $F 2 -> ""
|
||
|
||
Other possibilities include:
|
||
|
||
$F _
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn let_polymorphism_with_scoped_type_variables() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : a -> a
|
||
f = \x ->
|
||
y : a -> a
|
||
y = \z -> z
|
||
|
||
n = y 1u8
|
||
x1 = y x
|
||
(\_ -> x1) n
|
||
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
The 1st argument to `y` is not what I expect:
|
||
|
||
6│ n = y 1u8
|
||
^^^
|
||
|
||
This argument is an integer of type:
|
||
|
||
U8
|
||
|
||
But `y` needs the 1st argument to be:
|
||
|
||
a
|
||
|
||
Tip: The type annotation uses the type variable `a` to say that this
|
||
definition can produce any type of value. But in the body I see that
|
||
it will only produce a `U8` value of a single specific type. Maybe
|
||
change the type annotation to be more specific? Maybe change the code
|
||
to be more general?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn non_exhaustive_with_guard() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
x : [A]
|
||
when x is
|
||
A if True -> ""
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||
|
||
This `when` does not cover all the possibilities:
|
||
|
||
2│> when x is
|
||
3│> A if True -> ""
|
||
|
||
Other possibilities include:
|
||
|
||
A (note the lack of an if clause)
|
||
|
||
I would have to crash if I saw one of those! Add branches for them!
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_record_extension_type() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : { x : Nat }U32
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── INVALID_EXTENSION_TYPE ──────────────────────────────────────────────────────
|
||
|
||
This record extension type is invalid:
|
||
|
||
1│ f : { x : Nat }U32
|
||
^^^
|
||
|
||
Note: A record extension variable can only contain a type variable or
|
||
another record.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn invalid_tag_extension_type() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
f : [ A ]Str
|
||
f
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── INVALID_EXTENSION_TYPE ──────────────────────────────────────────────────────
|
||
|
||
This tag union extension type is invalid:
|
||
|
||
1│ f : [ A ]Str
|
||
^^^
|
||
|
||
Note: A tag union extension variable can only contain a type variable
|
||
or another tag union.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn unknown_type() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Type : [ Constructor UnknownType ]
|
||
|
||
insertHelper : UnknownType, Type -> Type
|
||
insertHelper = \h, m ->
|
||
when m is
|
||
Constructor _ -> Constructor h
|
||
|
||
insertHelper
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `UnknownType` value
|
||
|
||
1│ Type : [ Constructor UnknownType ]
|
||
^^^^^^^^^^^
|
||
|
||
Did you mean one of these?
|
||
|
||
Type
|
||
Unsigned8
|
||
Unsigned32
|
||
Unsigned16
|
||
|
||
── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────
|
||
|
||
I cannot find a `UnknownType` value
|
||
|
||
3│ insertHelper : UnknownType, Type -> Type
|
||
^^^^
|
||
|
||
Did you mean one of these?
|
||
|
||
Type
|
||
Unsigned8
|
||
Unsigned32
|
||
Unsigned16
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_first_demand_not_indented_enough() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Eq has
|
||
eq : a, a -> U64 | a has Eq
|
||
|
||
1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED ABILITY ──────────────────────────────────────────────────────────
|
||
|
||
I was partway through parsing an ability definition, but I got stuck
|
||
here:
|
||
|
||
1│ Eq has
|
||
2│ eq : a, a -> U64 | a has Eq
|
||
^
|
||
|
||
I suspect this line is not indented enough (by 1 spaces)
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_demands_not_indented_with_first() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Eq has
|
||
eq : a, a -> U64 | a has Eq
|
||
neq : a, a -> U64 | a has Eq
|
||
|
||
1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED ABILITY ──────────────────────────────────────────────────────────
|
||
|
||
I was partway through parsing an ability definition, but I got stuck
|
||
here:
|
||
|
||
5│ eq : a, a -> U64 | a has Eq
|
||
6│ neq : a, a -> U64 | a has Eq
|
||
^
|
||
|
||
I suspect this line is indented too much (by 4 spaces)"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_demand_value_has_args() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Eq has
|
||
eq b c : a, a -> U64 | a has Eq
|
||
|
||
1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED ABILITY ──────────────────────────────────────────────────────────
|
||
|
||
I was partway through parsing an ability definition, but I got stuck
|
||
here:
|
||
|
||
5│ eq b c : a, a -> U64 | a has Eq
|
||
^
|
||
|
||
I was expecting to see a : annotating the signature of this value
|
||
next."#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_non_signature_expression() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Eq has
|
||
123
|
||
|
||
1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNFINISHED ABILITY ──────────────────────────────────────────────────────────
|
||
|
||
I was partway through parsing an ability definition, but I got stuck
|
||
here:
|
||
|
||
1│ Eq has
|
||
2│ 123
|
||
^
|
||
|
||
I was expecting to see a value signature next.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn wildcard_in_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
I : Int *
|
||
a : I
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNBOUND TYPE VARIABLE ───────────────────────────────────────────────────────
|
||
|
||
The definition of `I` has an unbound type variable:
|
||
|
||
1│ I : Int *
|
||
^
|
||
|
||
Tip: Type variables must be bound before the `:`. Perhaps you intended
|
||
to add a type parameter to this type?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn wildcard_in_opaque() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
I := Int *
|
||
a : I
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNBOUND TYPE VARIABLE ───────────────────────────────────────────────────────
|
||
|
||
The definition of `I` has an unbound type variable:
|
||
|
||
1│ I := Int *
|
||
^
|
||
|
||
Tip: Type variables must be bound before the `:=`. Perhaps you intended
|
||
to add a type parameter to this type?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn multiple_wildcards_in_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
I : [ A (Int *), B (Int *) ]
|
||
a : I
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNBOUND TYPE VARIABLE ───────────────────────────────────────────────────────
|
||
|
||
The definition of `I` has 2 unbound type variables.
|
||
|
||
Here is one occurrence:
|
||
|
||
1│ I : [ A (Int *), B (Int *) ]
|
||
^
|
||
|
||
Tip: Type variables must be bound before the `:`. Perhaps you intended
|
||
to add a type parameter to this type?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn inference_var_in_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
I : Int _
|
||
a : I
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNBOUND TYPE VARIABLE ───────────────────────────────────────────────────────
|
||
|
||
The definition of `I` has an unbound type variable:
|
||
|
||
1│ I : Int _
|
||
^
|
||
|
||
Tip: Type variables must be bound before the `:`. Perhaps you intended
|
||
to add a type parameter to this type?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn unbound_var_in_alias() {
|
||
report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
I : Int a
|
||
a : I
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── UNBOUND TYPE VARIABLE ───────────────────────────────────────────────────────
|
||
|
||
The definition of `I` has an unbound type variable:
|
||
|
||
1│ I : Int a
|
||
^
|
||
|
||
Tip: Type variables must be bound before the `:`. Perhaps you intended
|
||
to add a type parameter to this type?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_bad_type_parameter() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Hash a b c has
|
||
hash : a -> U64 | a has Hash
|
||
|
||
1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── ABILITY HAS TYPE VARIABLES ──────────────────────────────────────────────────
|
||
|
||
The definition of the `Hash` ability includes type variables:
|
||
|
||
4│ Hash a b c has
|
||
^^^^^
|
||
|
||
Abilities cannot depend on type variables, but their member values
|
||
can!
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`Hash` is not used anywhere in your code.
|
||
|
||
4│ Hash a b c has
|
||
^^^^
|
||
|
||
If you didn't intend on using `Hash` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn alias_in_has_clause() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ hash ] to "./platform"
|
||
|
||
Hash has hash : a, b -> U64 | a has Hash, b has Bool
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── HAS CLAUSE IS NOT AN ABILITY ────────────────────────────────────────────────
|
||
|
||
The type referenced in this "has" clause is not an ability:
|
||
|
||
3│ Hash has hash : a, b -> U64 | a has Hash, b has Bool
|
||
^^^^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn shadowed_type_variable_in_has_clause() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ ab1 ] to "./platform"
|
||
|
||
Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── DUPLICATE NAME ──────────────────────────────────────────────────────────────
|
||
|
||
The `a` name is first defined here:
|
||
|
||
3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||
^^^^^^^^^
|
||
|
||
But then it's defined a second time here:
|
||
|
||
3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||
^^^^^^^^^
|
||
|
||
Since these variables have the same name, it's easy to use the wrong
|
||
one on accident. Give one of them a new name.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn alias_using_ability() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Ability has ab : a -> {} | a has Ability
|
||
|
||
Alias : Ability
|
||
|
||
a : Alias
|
||
a
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── ALIAS USES ABILITY ──────────────────────────────────────────────────────────
|
||
|
||
The definition of the `Alias` aliases references the ability `Ability`:
|
||
|
||
6│ Alias : Ability
|
||
^^^^^
|
||
|
||
Abilities are not types, but you can add an ability constraint to a
|
||
type variable `a` by writing
|
||
|
||
| a has Ability
|
||
|
||
at the end of the type.
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`ab` is not used anywhere in your code.
|
||
|
||
4│ Ability has ab : a -> {} | a has Ability
|
||
^^
|
||
|
||
If you didn't intend on using `ab` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_shadows_ability() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
Ability has ab : a -> U64 | a has Ability
|
||
|
||
Ability has ab1 : a -> U64 | a has Ability
|
||
|
||
1
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── DUPLICATE NAME ──────────────────────────────────────────────────────────────
|
||
|
||
The `Ability` name is first defined here:
|
||
|
||
4│ Ability has ab : a -> U64 | a has Ability
|
||
^^^^^^^
|
||
|
||
But then it's defined a second time here:
|
||
|
||
6│ Ability has ab1 : a -> U64 | a has Ability
|
||
^^^^^^^
|
||
|
||
Since these abilities have the same name, it's easy to use the wrong
|
||
one on accident. Give one of them a new name.
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`ab` is not used anywhere in your code.
|
||
|
||
4│ Ability has ab : a -> U64 | a has Ability
|
||
^^
|
||
|
||
If you didn't intend on using `ab` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_member_does_not_bind_ability() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ ] to "./platform"
|
||
|
||
Ability has ab : {} -> {}
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────────────────────────────
|
||
|
||
The definition of the ability member `ab` does not include a `has` clause
|
||
binding a type variable to the ability `Ability`:
|
||
|
||
3│ Ability has ab : {} -> {}
|
||
^^
|
||
|
||
Ability members must include a `has` clause binding a type variable to
|
||
an ability, like
|
||
|
||
a has Ability
|
||
|
||
Otherwise, the function does not need to be part of the ability!
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`Ability` is not used anywhere in your code.
|
||
|
||
3│ Ability has ab : {} -> {}
|
||
^^^^^^^
|
||
|
||
If you didn't intend on using `Ability` then remove it so future readers
|
||
of your code don't wonder why it is there.
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`ab` is not used anywhere in your code.
|
||
|
||
3│ Ability has ab : {} -> {}
|
||
^^
|
||
|
||
If you didn't intend on using `ab` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_member_binds_extra_ability() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ eq ] to "./platform"
|
||
|
||
Eq has eq : a, a -> Bool | a has Eq
|
||
Hash has hash : a, b -> U64 | a has Eq, b has Hash
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ────────────────────────────────────
|
||
|
||
The definition of the ability member `hash` includes a has clause
|
||
binding an ability it is not a part of:
|
||
|
||
4│ Hash has hash : a, b -> U64 | a has Eq, b has Hash
|
||
^^^^^^^^
|
||
|
||
Currently, ability members can only bind variables to the ability they
|
||
are a part of.
|
||
|
||
Hint: Did you mean to bind the `Hash` ability instead?
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`hash` is not used anywhere in your code.
|
||
|
||
4│ Hash has hash : a, b -> U64 | a has Eq, b has Hash
|
||
^^^^
|
||
|
||
If you didn't intend on using `hash` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_member_binds_parent_twice() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ ] to "./platform"
|
||
|
||
Eq has eq : a, b -> Bool | a has Eq, b has Eq
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────────────────────────────
|
||
|
||
The definition of the ability member `eq` includes multiple variables
|
||
bound to the `Eq`` ability:`
|
||
|
||
3│ Eq has eq : a, b -> Bool | a has Eq, b has Eq
|
||
^^^^^^^^^^^^^^^^^^
|
||
|
||
Ability members can only bind one type variable to their parent
|
||
ability. Otherwise, I wouldn't know what type implements an ability by
|
||
looking at specializations!
|
||
|
||
Hint: Did you mean to only bind `a` to `Eq`?
|
||
|
||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||
|
||
`eq` is not used anywhere in your code.
|
||
|
||
3│ Eq has eq : a, b -> Bool | a has Eq, b has Eq
|
||
^^
|
||
|
||
If you didn't intend on using `eq` then remove it so future readers of
|
||
your code don't wonder why it is there.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn has_clause_outside_of_ability() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ hash, f ] to "./platform"
|
||
|
||
Hash has hash : a -> U64 | a has Hash
|
||
|
||
f : a -> U64 | a has Hash
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── ILLEGAL HAS CLAUSE ──────────────────────────────────────────────────────────
|
||
|
||
A `has` clause is not allowed here:
|
||
|
||
5│ f : a -> U64 | a has Hash
|
||
^^^^^^^^^^
|
||
|
||
`has` clauses can only be specified on the top-level type annotation of
|
||
an ability member.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_specialization_with_non_implementing_type() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ hash ] to "./platform"
|
||
|
||
Hash has hash : a -> U64 | a has Hash
|
||
|
||
hash = \{} -> 0u64
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with this specialization of `hash`:
|
||
|
||
5│ hash = \{} -> 0u64
|
||
^^^^
|
||
|
||
This value is a declared specialization of type:
|
||
|
||
{}a -> U64
|
||
|
||
But the type annotation on `hash` says it must match:
|
||
|
||
a -> U64 | a has Hash
|
||
|
||
Note: Some types in this specialization don't implement the abilities
|
||
they are expected to. I found the following missing implementations:
|
||
|
||
{}a does not implement Hash
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_specialization_does_not_match_type() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ hash ] to "./platform"
|
||
|
||
Hash has hash : a -> U64 | a has Hash
|
||
|
||
Id := U32
|
||
|
||
hash = \$Id n -> n
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with this specialization of `hash`:
|
||
|
||
7│ hash = \$Id n -> n
|
||
^^^^
|
||
|
||
This value is a declared specialization of type:
|
||
|
||
Id -> U32
|
||
|
||
But the type annotation on `hash` says it must match:
|
||
|
||
Id -> U64
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_specialization_is_incomplete() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ eq, le ] to "./platform"
|
||
|
||
Eq has
|
||
eq : a, a -> Bool | a has Eq
|
||
le : a, a -> Bool | a has Eq
|
||
|
||
Id := U64
|
||
|
||
eq = \$Id m, $Id n -> m == n
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────────────────────────────
|
||
|
||
The type `Id` does not fully implement the ability `Eq`. The following
|
||
specializations are missing:
|
||
|
||
A specialization for `le`, which is defined here:
|
||
|
||
5│ le : a, a -> Bool | a has Eq
|
||
^^
|
||
|
||
Note: `Id` specializes the following members of `Eq`:
|
||
|
||
`eq`, specialized here:
|
||
|
||
9│ eq = \$Id m, $Id n -> m == n
|
||
^^
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_specialization_overly_generalized() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ hash ] to "./platform"
|
||
|
||
Hash has
|
||
hash : a -> U64 | a has Hash
|
||
|
||
hash = \_ -> 0u64
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
This specialization of `hash` is overly general:
|
||
|
||
6│ hash = \_ -> 0u64
|
||
^^^^
|
||
|
||
This value is a declared specialization of type:
|
||
|
||
a -> U64
|
||
|
||
But the type annotation on `hash` says it must match:
|
||
|
||
a -> U64 | a has Hash
|
||
|
||
Note: The specialized type is too general, and does not provide a
|
||
concrete type where a type variable is bound to an ability.
|
||
|
||
Specializations can only be made for concrete types. If you have a
|
||
generic implementation for this value, perhaps you don't need an
|
||
ability?
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn ability_specialization_conflicting_specialization_types() {
|
||
new_report_problem_as(
|
||
indoc!(
|
||
r#"
|
||
app "test" provides [ eq ] to "./platform"
|
||
|
||
Eq has
|
||
eq : a, a -> Bool | a has Eq
|
||
|
||
You := {}
|
||
AndI := {}
|
||
|
||
eq = \$You {}, $AndI {} -> False
|
||
"#
|
||
),
|
||
indoc!(
|
||
r#"
|
||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||
|
||
Something is off with this specialization of `eq`:
|
||
|
||
9│ eq = \$You {}, $AndI {} -> False
|
||
^^
|
||
|
||
This value is a declared specialization of type:
|
||
|
||
You, AndI -> [ False, True ]
|
||
|
||
But the type annotation on `eq` says it must match:
|
||
|
||
You, You -> Bool
|
||
|
||
Tip: Type comparisons between an opaque type are only ever equal if
|
||
both types are the same opaque type. Did you mean to create an opaque
|
||
type by wrapping it? If I have an opaque type Age := U32 I can create
|
||
an instance of this opaque type by doing @Age 23.
|
||
"#
|
||
),
|
||
)
|
||
}
|
||
}
|