#[macro_use] extern crate pretty_assertions; #[macro_use] extern crate indoc; extern crate bumpalo; extern crate roc_reporting; mod helpers; #[cfg(test)] mod test_reporting { use crate::helpers::test_home; use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut}; use bumpalo::Bump; use roc_module::symbol::{Interns, ModuleId}; use roc_mono::ir::{Procs, Stmt}; use roc_mono::layout::LayoutCache; use roc_reporting::report::{ can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE, CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE, }; use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_solve::solve; use roc_types::pretty_print::name_all_type_vars; use roc_types::subs::Subs; use std::path::PathBuf; fn filename_from_string(str: &str) -> PathBuf { let mut filename = PathBuf::new(); filename.push(str); filename } fn to_simple_report<'b>(doc: RocDocBuilder<'b>) -> Report<'b> { Report { title: "".to_string(), doc, filename: filename_from_string(r"\code\proj\Main.roc"), } } fn infer_expr_help<'a>( arena: &'a Bump, expr_src: &'a str, ) -> Result< ( Vec, Vec, Vec, ModuleId, Interns, ), ParseErrOut<'a>, > { let CanExprOut { loc_expr, output, var_store, var, constraint, home, mut interns, problems: can_problems, .. } = can_expr(arena, expr_src)?; let mut subs = Subs::new(var_store.into()); for (var, name) in output.introduced_variables.name_by_var { subs.rigid_var(var, name); } for var in output.introduced_variables.wildcards { subs.rigid_var(var, "*".into()); } let mut unify_problems = Vec::new(); let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); name_all_type_vars(var, &mut subs); let mut mono_problems = Vec::new(); // MONO if unify_problems.is_empty() && can_problems.is_empty() { let arena = Bump::new(); // Compile and add all the Procs before adding main let mut procs = Procs::new_in(&arena); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mut layout_cache = LayoutCache::default(); let mut mono_env = roc_mono::ir::Env { arena: &arena, subs: &mut subs, problems: &mut mono_problems, home, ident_ids: &mut ident_ids, ptr_bytes: 8, }; let _mono_expr = Stmt::new( &mut mono_env, loc_expr.value, var, &mut procs, &mut layout_cache, ); } Ok((unify_problems, can_problems, mono_problems, home, interns)) } fn list_reports(arena: &Bump, src: &str, buf: &mut String, callback: F) where F: FnOnce(RocDocBuilder<'_>, &mut String), { use ven_pretty::DocAllocator; let src_lines: Vec<&str> = src.split('\n').collect(); let filename = filename_from_string(r"\code\proj\Main.roc"); match infer_expr_help(arena, src) { Err(parse_err) => { let ParseErrOut { fail, home, interns, } = parse_err; let alloc = RocDocAllocator::new(&src_lines, home, &interns); let problem = fail.into_parse_problem(filename.clone(), "", src.as_bytes()); let doc = parse_problem(&alloc, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) } Ok((type_problems, can_problems, mono_problems, home, interns)) => { let mut reports = Vec::new(); let alloc = RocDocAllocator::new(&src_lines, home, &interns); for problem in can_problems { let report = can_problem(&alloc, filename.clone(), problem.clone()); reports.push(report); } for problem in type_problems { let report = type_problem(&alloc, filename.clone(), problem.clone()); reports.push(report); } for problem in mono_problems { let report = mono_problem(&alloc, filename.clone(), problem.clone()); reports.push(report); } let has_reports = !reports.is_empty(); let doc = alloc .stack(reports.into_iter().map(|v| v.pretty(&alloc))) .append(if has_reports { alloc.line() } else { alloc.nil() }); callback(doc, buf) } } } fn list_header_reports(arena: &Bump, src: &str, buf: &mut String, callback: F) where F: FnOnce(RocDocBuilder<'_>, &mut String), { use ven_pretty::DocAllocator; use roc_parse::parser::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(); 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 = SyntaxError::Header(fail).into_parse_problem( filename.clone(), "", src.as_bytes(), ); let doc = parse_problem(&alloc, filename, 0, problem); callback(doc.pretty(&alloc).append(alloc.line()), buf) } Ok(_) => 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 { if buf != expected_rendering { for line in buf.split("\n") { println!(" {}", line); } } } assert_eq!(buf, expected_rendering); } 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 { if buf != expected_rendering { for line in buf.split("\n") { println!(" {}", line); } } } assert_eq!(buf, expected_rendering); } fn color_report_problem_as(src: &str, expected_rendering: &str) { let mut buf: String = String::new(); let arena = Bump::new(); let callback = |doc: RocDocBuilder<'_>, buf: &mut String| { doc.1 .render_raw( 70, &mut roc_reporting::report::ColorWrite::new( &roc_reporting::report::DEFAULT_PALETTE, buf, ), ) .expect("list_reports") }; list_reports(&arena, src, &mut buf, callback); let readable = human_readable(&buf); assert_eq!(readable, expected_rendering); } fn human_readable(str: &str) -> String { str.replace(RED_CODE, "") .replace(WHITE_CODE, "") .replace(BLUE_CODE, "") .replace(YELLOW_CODE, "") .replace(GREEN_CODE, "") .replace(CYAN_CODE, "") .replace(MAGENTA_CODE, "") .replace(RESET_CODE, "") .replace(BOLD_CODE, "") .replace(UNDERLINE_CODE, "") } #[test] fn value_not_exposed() { report_problem_as( indoc!( r#" List.foobar 1 2 "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── The List module does not expose a foobar value: 1│ List.foobar 1 2 ^^^^^^^^^^^ "# ), ) } #[test] fn report_unused_def() { report_problem_as( indoc!( r#" x = 1 y = 2 x "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── `y` is not used anywhere in your code. 2│ y = 2 ^ If you didn't intend on using `y` then remove it so future readers of your code don't wonder why it is there. "# ), ) } #[test] fn report_shadowing() { report_problem_as( indoc!( r#" i = 1 s = \i -> i + 1 s i "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── The `i` name is first defined here: 1│ i = 1 ^ But then it's defined a second time here: 3│ s = \i -> ^ Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name. "# ), ) } #[test] fn report_shadowing_in_annotation() { report_problem_as( indoc!( r#" Booly : [ Yes, No ] Booly : [ Yes, No, Maybe ] x = No x "# ), // Booly is called a "variable" indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── The `Booly` name is first defined here: 1│ Booly : [ Yes, No ] ^^^^^^^^^^^^^^^^^^^ But then it's defined a second time here: 3│ Booly : [ Yes, No, Maybe ] ^^^^^^^^^^^^^^^^^^^^^^^^^^ Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name. ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── `Booly` is not used anywhere in your code. 1│ Booly : [ Yes, No ] ^^^^^^^^^^^^^^^^^^^ If you didn't intend on using `Booly` then remove it so future readers of your code don't wonder why it is there. "# ), ) } // #[test] // fn report_multi_line_shadowing_in_annotation() { // report_problem_as( // indoc!( // r#" // Booly : // [ // Yes, // No // ] // // Booly : // [ // Yes, // No, // Maybe // ] // // x = // No // // x // "# // ), // indoc!( // r#" // Booly is first defined here: // // 1│> Booly : // 2│> [ // 3│> Yes, // 4│> No // 5│> ] // // But then it's defined a second time here: // // 7 │> Booly : // 8 │> [ // 9 │> Yes, // 10│> No, // 11│> Maybe // 12│> ] // // Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."# // ), // ) // } // #[test] // fn report_unsupported_top_level_def() { // report_problem_as( // indoc!( // r#" // x = 1 // // 5 = 2 + 1 // // x // "# // ), // indoc!(r#" "#), // ) // } #[test] fn report_precedence_problem_single_line() { report_problem_as( indoc!( r#"x = 1 y = if selectedId != thisId == adminsId then 4 else 5 { x, y } "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── Using != and == together requires parentheses, to clarify how they should be grouped. 3│ if selectedId != thisId == adminsId then ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ "# ), ) } #[test] fn unused_undefined_argument() { report_problem_as( indoc!( r#" foo = { x: 1 == 1, y: 0x4 } baz = 3 main : Str main = when foo.y is 4 -> bar baz "yay" _ -> "nay" main "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── I cannot find a `bar` value 8│ 4 -> bar baz "yay" ^^^ these names seem close though: baz Nat Str U8 "# ), ) } #[test] fn report_precedence_problem_multiline() { report_problem_as( indoc!( r#" if 1 == 2 == 3 then 2 else 3 "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── Using more than one == like this requires parentheses, to clarify how things should be grouped. 2│> 1 3│> == 2 4│> == 3 "# ), ) } // #[test] // fn report_unused_argument() { // report_problem_as( // indoc!(r#" // y = 9 // // box = \class, htmlChildren -> // div [ class ] [] // // div = 4 // // box "wizard" [] // "#), // indoc!( // r#" // box doesn't use htmlChildren. // // 3│ box = \class, htmlChildren -> // // If you don't need htmlChildren, then you can just remove it. However, if you really do need htmlChildren as an argument of box, prefix it with an underscore, like this: "_htmlChildren". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."# // ), // ); // } // #[test] // fn report_unused_import() { // report_problem_as( // indoc!(r#" // interface Report // exposes [ // plainText, // emText // ] // imports [ // Symbol.{ Interns } // ] // // plainText = \str -> PlainText str // // emText = \str -> EmText str // "#), // indoc!( // r#" // Nothing from Symbol is used in this module. // // 6│ imports [ // 7│ Symbol.{ Interns } // ^^^^^^ // 8│ ] // // Since Symbol isn't used, you don't need to import it."# // ), // ); // } #[test] fn report_value_color() { let src: &str = indoc!( r#" activityIndicatorLarge = div view activityIndicatorLarge "# ); let arena = Bump::new(); let (_type_problems, _can_problems, _mono_problems, home, interns) = infer_expr_help(&arena, src).expect("parse error"); let mut buf = String::new(); let src_lines: Vec<&str> = src.split('\n').collect(); let alloc = RocDocAllocator::new(&src_lines, home, &interns); let symbol = interns.symbol(test_home(), "activityIndicatorLarge".into()); to_simple_report(alloc.symbol_unqualified(symbol)).render_color_terminal( &mut buf, &alloc, &DEFAULT_PALETTE, ); assert_eq!(human_readable(&buf), "activityIndicatorLarge"); } #[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), "Util.Int"); } #[test] fn report_region_in_color() { color_report_problem_as( indoc!( r#" isDisabled = \user -> user.isAdmin theAdmin |> isDisabled "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── I cannot find a `theAdmin` value 3 theAdmin ^^^^^^^^ these names seem close though: Result Num Set U8 "# ), ); } // #[test] // fn shadowing_type_alias() { // report_problem_as( // indoc!( // r#" // foo : I64 as I64 // foo = 42 // // foo // "# // ), // indoc!( // r#" // You cannot mix (!=) and (==) without parentheses // // 3│ if selectedId != thisId == adminsId then // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // // "# // ), // ) // } // #[test] // fn invalid_as_type_alias() { // report_problem_as( // indoc!( // r#" // foo : I64 as a // foo = 42 // // foo // "# // ), // indoc!( // r#" // You cannot mix (!=) and (==) without parentheses // // 3│ if selectedId != thisId == adminsId then // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // // "# // ), // ) // } #[test] fn if_condition_not_bool() { report_problem_as( indoc!( r#" if "foo" then 2 else 3 "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── This `if` condition needs to be a Bool: 1│ if "foo" then 2 else 3 ^^^^^ Right now it’s a string of type: Str But I need every `if` condition to evaluate to a Bool—either `True` or `False`. "# ), ) } #[test] fn when_if_guard() { report_problem_as( indoc!( r#" when 1 is 2 if 1 -> 0x0 _ -> 0x1 "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── This `if` guard condition needs to be a Bool: 1│ when 1 is 2│> 2 if 1 -> 0x0 3│ _ -> 0x1 Right now it’s a number of type: Num a But I need every `if` guard condition to evaluate to a Bool—either `True` or `False`. "# ), ) } #[test] fn if_2_branch_mismatch() { report_problem_as( indoc!( r#" if True then 2 else "foo" "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── This `if` has an `else` branch with a different type from its `then` branch: 1│ if True then 2 else "foo" ^^^^^ The `else` branch is a string of type: Str but the `then` branch has the type: Num a I need all branches in an `if` to have the same type! "# ), ) } #[test] fn if_3_branch_mismatch() { report_problem_as( indoc!( r#" if True then 2 else if False then 2 else "foo" "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 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: 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() { report_problem_as( indoc!( r#" when { foo: 1 } is { foo: True } -> 42 "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st pattern in this `when` is causing a mismatch: 2│ { foo: True } -> 42 ^^^^^^^^^^^^^ The first pattern is trying to match record values of type: { foo : [ True ]a } But the expression between `when` and `is` has the type: { foo : Num a } "# ), ) } #[test] fn pattern_guard_does_not_bind_label() { // needs some improvement, but the principle works report_problem_as( indoc!( r#" when { foo: 1 } is { foo: 2 } -> foo "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── I cannot find a `foo` value 2│ { foo: 2 } -> foo ^^^ these names seem close though: Bool U8 F64 Nat "# ), ) } #[test] fn pattern_guard_can_be_shadowed_above() { report_problem_as( indoc!( r#" foo = 3 when { foo: 1 } is { foo: 2 } -> foo _ -> foo "# ), // should give no error "", ) } #[test] fn pattern_guard_can_be_shadowed_below() { report_problem_as( indoc!( r#" when { foo: 1 } is { foo: 2 } -> foo = 3 foo _ -> 3 "# ), // should give no error "", ) } #[test] fn pattern_or_pattern_mismatch() { report_problem_as( indoc!( r#" when { foo: 1 } is {} | 1 -> 3 "# ), // Just putting this here. We should probably handle or-patterns better indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st pattern in this `when` is causing a mismatch: 2│ {} | 1 -> 3 ^^^^^^ The first pattern is trying to match numbers: Num a But the expression between `when` and `is` has the type: { foo : Num a } "# ), ) } #[test] fn pattern_let_mismatch() { report_problem_as( indoc!( r#" (Foo x) = 42 x "# ), // Maybe this should specifically say the pattern doesn't work? indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── This expression is used in an unexpected way: 1│ (Foo x) = 42 ^^ It is a number of type: Num a But you are trying to use it as: [ Foo a ]b "# ), ) } #[test] fn from_annotation_complex_pattern() { report_problem_as( indoc!( r#" { x } : { x : Int * } { x } = { x: 4.0 } x "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── Something is off with the body of this definition: 1│ { x } : { x : Int * } 2│ { x } = { x: 4.0 } ^^^^^^^^^^ The body is a record of type: { x : Float a } But the type annotation says it should be: { x : Int * } 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: 2│ f = \x, y -> if True then x else y ^ This `y` value is a: b But the type annotation on `f` says it should be: a Tip: Your type annotation uses `b` and `a` as separate type variables. Your code seems to be saying they are the same though. Maybe they should be the same your type annotation? Maybe your code uses them in a weird way? "# ), ) } #[test] fn bad_rigid_function() { report_problem_as( indoc!( r#" f : Bool -> msg f = \_ -> Foo f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── Something is off with the body of the `f` definition: 1│ f : Bool -> msg 2│ f = \_ -> Foo ^^^ This `Foo` global tag has the type: [ Foo ]a But the type annotation on `f` says it should be: msg Tip: The type annotation uses the type variable `msg` to say that this definition can produce any type of value. But in the body I see that it will only produce a tag value of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general? "# ), ) } #[test] fn bad_rigid_value() { report_problem_as( indoc!( r#" f : msg f = 0x3 f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── Something is off with the body of the `f` definition: 1│ f : msg 2│ f = 0x3 ^^^ The body is an integer of type: Int a But the type annotation on `f` says it should be: msg Tip: The type annotation uses the type variable `msg` to say that this definition can produce any type of value. But in the body I see that it will only produce a `Int` value of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general? "# ), ) } #[test] fn typo_lowercase_ok() { // TODO improve tag suggestions report_problem_as( indoc!( r#" f : Bool -> [ Ok I64, InvalidFoo ] f = \_ -> ok 4 f "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── I cannot find a `ok` value 2│ f = \_ -> ok 4 ^^ these names seem close though: U8 f I8 F64 "# ), ) } #[test] fn typo_uppercase_ok() { // these error messages seem pretty helpful report_problem_as( indoc!( r#" f : Bool -> I64 f = \_ -> ok = 3 Ok f "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── `ok` is not used anywhere in your code. 3│ ok = 3 ^^ If you didn't intend on using `ok` then remove it so future readers of your code don't wonder why it is there. ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── Something is off with the body of the `f` definition: 1│ f : Bool -> I64 2│ f = \_ -> 3│ ok = 3 4│ 5│ Ok ^^ This `Ok` global tag has the type: [ Ok ]a But the type annotation on `f` says it should be: I64 "# ), ) } #[test] fn circular_definition_self() { // invalid recursion report_problem_as( indoc!( r#" f = f f "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── The `f` value is defined directly in terms of itself, causing an infinite loop. "# ), ) } #[test] fn circular_definition() { // invalid mutual recursion report_problem_as( indoc!( r#" foo = bar bar = foo foo "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── The `foo` definition is causing a very tricky infinite loop: 1│ foo = bar ^^^ The `foo` value depends on itself through the following chain of definitions: ┌─────┐ │ foo │ ↓ │ bar └─────┘ "# ), ) } #[test] fn update_empty_record() { report_problem_as( indoc!( r#" x = {} { x & foo: 3 } "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The `x` record does not have a `.foo` field: 3│ { x & foo: 3 } ^^^^^^ In fact, `x` is a record with NO fields! "# ), ) } #[test] fn update_record() { report_problem_as( indoc!( r#" x = { fo: 3, bar: 4 } { x & foo: 3 } "# ), // TODO also suggest fields with the correct type indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The `x` record does not have a `.foo` field: 3│ { x & foo: 3 } ^^^^^^ This is usually a typo. Here are the `x` fields that are most similar: { fo : Num b , bar : Num a } So maybe `.foo` should be `.fo`? "# ), ) } #[test] fn update_record_ext() { report_problem_as( indoc!( r#" f : { fo: I64 }ext -> I64 f = \r -> r2 = { r & foo: r.fo } r2.fo f "# ), // TODO also suggest fields with the correct type indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The `r` record does not have a `.foo` field: 3│ r2 = { r & foo: r.fo } ^^^^^^^^^ This is usually a typo. Here are the `r` fields that are most similar: { fo : I64 }ext So maybe `.foo` should be `.fo`? "# ), ) } #[test] fn update_record_snippet() { report_problem_as( indoc!( r#" x = { fo: 3, bar: 4, baz: 3, spam: 42, foobar: 3 } { x & foo: 3 } "# ), // TODO also suggest fields with the correct type indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The `x` record does not have a `.foo` field: 3│ { x & foo: 3 } ^^^^^^ This is usually a typo. Here are the `x` fields that are most similar: { fo : Num c , foobar : Num a , bar : Num e , baz : Num b , ... } So maybe `.foo` should be `.fo`? "# ), ) } #[test] fn plus_on_str() { report_problem_as( indoc!( r#" 0x4 + "foo" "# ), // TODO also suggest fields with the correct type indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 2nd argument to `add` is not what I expect: 1│ 0x4 + "foo" ^^^^^ This argument is a string of type: Str But `add` needs the 2nd argument to be: Num (Integer a) "# ), ) } #[test] fn int_float() { report_problem_as( indoc!( r#" 0x4 + 3.14 "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 2nd argument to `add` is not what I expect: 1│ 0x4 + 3.14 ^^^^ This argument is a float of type: Float a But `add` needs the 2nd argument to be: Num (Integer a) Tip: You can convert between Int and Float using functions like `Num.toFloat` and `Num.round`. "# ), ) } #[test] fn boolean_tag() { report_problem_as( indoc!( r#" 42 + True "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 2nd argument to `add` is not what I expect: 1│ 42 + True ^^^^ This `True` boolean has the type: [ True ]a But `add` needs the 2nd argument to be: Num a "# ), ) } #[test] fn tag_missing() { report_problem_as( indoc!( r#" f : [ A ] -> [ A, B ] f = \a -> a f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── Something is off with the body of the `f` definition: 1│ f : [ A ] -> [ A, B ] 2│ f = \a -> a ^ This `a` value is a: [ A ] But the type annotation on `f` says it should be: [ A, B ] Tip: Looks like a closed tag union does not have the `B` tag. Tip: Closed tag unions can't grow, because that might change the size in memory. Can you use an open tag union? "# ), ) } #[test] fn tags_missing() { report_problem_as( indoc!( r#" f : [ A ] -> [ A, B, C ] f = \a -> a f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── Something is off with the body of the `f` definition: 1│ f : [ A ] -> [ A, B, C ] 2│ f = \a -> a ^ This `a` value is a: [ A ] But the type annotation on `f` says it should be: [ A, B, C ] Tip: Looks like a closed tag union does not have the `C` and `B` tags. Tip: Closed tag unions can't grow, because that might change the size in memory. Can you use an open tag union? "# ), ) } #[test] fn patterns_fn_not_exhaustive() { report_problem_as( indoc!( r#" Either : [ Left I64, Right Bool ] x : Either x = Left 42 f : Either -> I64 f = \Left v -> v f x "# ), indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This pattern does not cover all the possibilities: 7│ f = \Left v -> v ^^^^^^ Other possibilities include: Right _ I would have to crash if I saw one of those! So rather than pattern matching in function arguments, put a `when` in the function body to account for all possibilities. "# ), ) } #[test] fn patterns_let_not_exhaustive() { report_problem_as( indoc!( r#" x : [ Left I64, Right Bool ] x = Left 42 (Left y) = x y "# ), indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This pattern does not cover all the possibilities: 5│ (Left y) = x ^^^^^^ Other possibilities include: Right _ I would have to crash if I saw one of those! You can use a binding to deconstruct a value if there is only ONE possibility. Use a `when` to account for all possibilities. "# ), ) } #[test] fn patterns_when_not_exhaustive() { report_problem_as( indoc!( r#" when 0x1 is 2 -> 0x3 "# ), indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This `when` does not cover all the possibilities: 1│> when 0x1 is 2│> 2 -> 0x3 Other possibilities include: _ I would have to crash if I saw one of those! Add branches for them! "# ), ) } #[test] fn patterns_bool_not_exhaustive() { report_problem_as( indoc!( r#" x : Bool x = True when x is False -> 3 "# ), indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This `when` does not cover all the possibilities: 4│> when x is 5│> False -> 3 Other possibilities include: True I would have to crash if I saw one of those! Add branches for them! "# ), ) } #[test] fn patterns_enum_not_exhaustive() { report_problem_as( indoc!( r#" x : [ Red, Green, Blue ] x = Red when x is Red -> 0 Green -> 1 "# ), indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This `when` does not cover all the possibilities: 4│> when x is 5│> Red -> 0 6│> Green -> 1 Other possibilities include: Blue I would have to crash if I saw one of those! Add branches for them! "# ), ) } #[test] fn patterns_remote_data_not_exhaustive() { report_problem_as( indoc!( r#" RemoteData e a : [ NotAsked, Loading, Failure e, Success a ] x : RemoteData I64 Str when x is NotAsked -> 3 "# ), indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This `when` does not cover all the possibilities: 5│> when x is 6│> NotAsked -> 3 Other possibilities include: Failure _ Loading Success _ I would have to crash if I saw one of those! Add branches for them! "# ), ) } #[test] fn patterns_record_not_exhaustive() { report_problem_as( indoc!( r#" x = { a: 3 } when x is { a: 4 } -> 4 "# ), // Tip: Looks like a record field guard is not exhaustive. Learn more about record pattern matches at TODO. indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This `when` does not cover all the possibilities: 3│> when x is 4│> { a: 4 } -> 4 Other possibilities include: { a } I would have to crash if I saw one of those! Add branches for them! "# ), ) } #[test] fn patterns_record_guard_not_exhaustive() { report_problem_as( indoc!( r#" y : [ Nothing, Just I64 ] y = Just 4 x = { a: y, b: 42} when x is { a: Nothing } -> 4 { a: Just 3 } -> 4 "# ), indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This `when` does not cover all the possibilities: 5│> when x is 6│> { a: Nothing } -> 4 7│> { a: Just 3 } -> 4 Other possibilities include: { a: Just _, b } I would have to crash if I saw one of those! Add branches for them! "# ), ) } #[test] fn patterns_nested_tag_not_exhaustive() { report_problem_as( indoc!( r#" when Record Nothing 1 is Record (Nothing) b -> b Record (Just 3) b -> b "# ), indoc!( r#" ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── This `when` does not cover all the possibilities: 1│> when Record Nothing 1 is 2│> Record (Nothing) b -> b 3│> Record (Just 3) b -> b Other possibilities include: Record (Just _) _ I would have to crash if I saw one of those! Add branches for them! "# ), ) } #[test] fn patterns_int_redundant() { report_problem_as( indoc!( r#" when 0x1 is 2 -> 3 2 -> 4 _ -> 5 "# ), indoc!( r#" ── REDUNDANT PATTERN ─────────────────────────────────────────────────────────── The 2nd pattern is redundant: 1│ when 0x1 is 2│ 2 -> 3 3│> 2 -> 4 4│ _ -> 5 Any value of this shape will be handled by a previous pattern, so this one should be removed. "# ), ) } #[test] fn unify_alias_other() { report_problem_as( indoc!( r#" Foo : { x : Int * } f : Foo -> Int * f = \r -> r.x f { y: 3.14 } "# ), // de-aliases the alias to give a better error message indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st argument to `f` is not what I expect: 6│ f { y: 3.14 } ^^^^^^^^^^^ This argument is a record of type: { y : Float a } But `f` needs the 1st argument to be: { x : Int * } Tip: Seems like a record field typo. Maybe `y` should be `x`? Tip: Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case "# ), ) } #[test] #[ignore] fn cyclic_alias() { report_problem_as( indoc!( r#" Foo : { x : Bar } Bar : { y : Foo } f : Foo f "# ), // should not report Bar as unused! indoc!( r#" ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── The `Foo` alias is recursive in an invalid way: 1│ Foo : { x : Bar } ^^^^^^^^^^^ The `Foo` alias depends on itself through the following chain of definitions: ┌─────┐ │ Foo │ ↓ │ Bar └─────┘ Recursion in aliases is only allowed if recursion happens behind a tag. ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── `Bar` is not used anywhere in your code. 2│ Bar : { y : Foo } ^^^^^^^^^^^^^^^^^ If you didn't intend on using `Bar` then remove it so future readers of your code don't wonder why it is there. "# ), ) } #[test] fn self_recursive_alias() { report_problem_as( indoc!( r#" Foo : { x : Foo } f : Foo f = 3 f "# ), // should not report Bar as unused! indoc!( r#" ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── The `Foo` alias is self-recursive in an invalid way: 1│ Foo : { x : Foo } ^^^ Recursion in aliases is only allowed if recursion happens behind a tag. "# ), ) } #[test] fn record_duplicate_field_same_type() { report_problem_as( indoc!( r#" { x: 4, y: 3, x: 4 } "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This record defines the `.x` field twice! 1│ { x: 4, y: 3, x: 4 } ^^^^ ^^^^ In the rest of the program, I will only use the latter definition: 1│ { x: 4, y: 3, x: 4 } ^^^^ For clarity, remove the previous `.x` definitions from this record. "# ), ) } #[test] fn record_duplicate_field_different_types() { report_problem_as( indoc!( r#" { x: 4, y: 3, x: "foo" } "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This record defines the `.x` field twice! 1│ { x: 4, y: 3, x: "foo" } ^^^^ ^^^^^^^^ In the rest of the program, I will only use the latter definition: 1│ { x: 4, y: 3, x: "foo" } ^^^^^^^^ For clarity, remove the previous `.x` definitions from this record. "# ), ) } #[test] fn record_duplicate_field_multiline() { report_problem_as( indoc!( r#" { x: 4, y: 3, x: "foo" } "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This record defines the `.x` field twice! 1│ { 2│> x: 4, 3│ y: 3, 4│> x: "foo" 5│ } In the rest of the program, I will only use the latter definition: 1│ { 2│ x: 4, 3│ y: 3, 4│> x: "foo" 5│ } For clarity, remove the previous `.x` definitions from this record. "# ), ) } #[test] fn record_update_duplicate_field_multiline() { report_problem_as( indoc!( r#" \r -> { r & x: 4, y: 3, x: "foo" } "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This record defines the `.x` field twice! 2│ { r & 3│> x: 4, 4│ y: 3, 5│> x: "foo" 6│ } In the rest of the program, I will only use the latter definition: 2│ { r & 3│ x: 4, 4│ y: 3, 5│> x: "foo" 6│ } For clarity, remove the previous `.x` definitions from this record. "# ), ) } #[test] fn record_type_duplicate_field() { report_problem_as( indoc!( r#" a : { foo : I64, bar : F64, foo : Str } a = { bar: 3.0, foo: "foo" } a "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This record type defines the `.foo` field twice! 1│ a : { foo : I64, bar : F64, foo : Str } ^^^^^^^^^ ^^^^^^^^^ In the rest of the program, I will only use the latter definition: 1│ a : { foo : I64, bar : F64, foo : Str } ^^^^^^^^^ For clarity, remove the previous `.foo` definitions from this record type. "# ), ) } #[test] fn tag_union_duplicate_tag() { report_problem_as( indoc!( r#" a : [ Foo I64, Bar F64, Foo Str ] a = Foo "foo" a "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This tag union type defines the `Foo` tag twice! 1│ a : [ Foo I64, Bar F64, Foo Str ] ^^^^^^^ ^^^^^^^ In the rest of the program, I will only use the latter definition: 1│ a : [ Foo I64, Bar F64, Foo Str ] ^^^^^^^ For clarity, remove the previous `Foo` definitions from this tag union type. "# ), ) } #[test] fn annotation_definition_mismatch() { report_problem_as( indoc!( r#" bar : I64 foo = \x -> x # NOTE: neither bar or foo are defined at this point 4 "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This annotation does not match the definition immediately following it: 1│> bar : I64 2│> foo = \x -> x Is it a typo? If not, put either a newline or comment between them. "# ), ) } #[test] fn annotation_newline_body_is_fine() { report_problem_as( indoc!( r#" bar : I64 foo = \x -> x foo bar "# ), indoc!(""), ) } #[test] fn invalid_alias_rigid_var_pattern() { report_problem_as( indoc!( r#" MyAlias 1 : I64 4 "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This pattern in the definition of `MyAlias` is not what I expect: 1│ MyAlias 1 : I64 ^ Only type variables like `a` or `value` can occur in this position. ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── `MyAlias` is not used anywhere in your code. 1│ MyAlias 1 : I64 ^^^^^^^^^^^^^^^ If you didn't intend on using `MyAlias` then remove it so future readers of your code don't wonder why it is there. "# ), ) } #[test] fn invalid_num() { report_problem_as( indoc!( r#" a : Num I64 F64 a = 3 a "# ), indoc!( r#" ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── The `Num` alias expects 1 type argument, but it got 2 instead: 1│ a : Num I64 F64 ^^^^^^^^^^^ Are there missing parentheses? "# ), ) } #[test] fn invalid_num_fn() { report_problem_as( indoc!( r#" f : Bool -> Num I64 F64 f = \_ -> 3 f "# ), indoc!( r#" ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── The `Num` alias expects 1 type argument, but it got 2 instead: 1│ f : Bool -> Num I64 F64 ^^^^^^^^^^^ Are there missing parentheses? "# ), ) } #[test] fn too_few_type_arguments() { report_problem_as( indoc!( r#" Pair a b : [ Pair a b ] x : Pair I64 x = Pair 2 3 x "# ), indoc!( r#" ── TOO FEW TYPE ARGUMENTS ────────────────────────────────────────────────────── The `Pair` alias expects 2 type arguments, but it got 1 instead: 3│ x : Pair I64 ^^^^^^^^ Are there missing parentheses? "# ), ) } #[test] fn too_many_type_arguments() { report_problem_as( indoc!( r#" Pair a b : [ Pair a b ] x : Pair I64 I64 I64 x = 3 x "# ), indoc!( r#" ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── The `Pair` alias expects 2 type arguments, but it got 3 instead: 3│ x : Pair I64 I64 I64 ^^^^^^^^^^^^^^^^ Are there missing parentheses? "# ), ) } #[test] fn phantom_type_variable() { report_problem_as( indoc!( r#" Foo a : [ Foo ] f : Foo I64 f "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── The `a` type variable is not used in the `Foo` alias definition: 1│ Foo a : [ Foo ] ^ Roc does not allow unused type parameters! Tip: If you want an unused type parameter (a so-called "phantom type"), read the guide section on phantom data. "# ), ) } #[test] fn elm_function_syntax() { report_problem_as( indoc!( r#" f x y = x "# ), indoc!( r#" ── 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 } "# ), 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 a) [ ACons Str [ BNil ]b ]c ]d, ANil ] But the type annotation on `x` says it should be: [ ACons I64 BList I64 I64, ANil ] "# ), ) } #[test] fn integer_out_of_range() { report_problem_as( indoc!( r#" x = 9_223_372_036_854_775_807_000 y = -9_223_372_036_854_775_807_000 h = 0x8FFF_FFFF_FFFF_FFFF l = -0x8FFF_FFFF_FFFF_FFFF minlit = -9_223_372_036_854_775_808 maxlit = 9_223_372_036_854_775_807 x + y + h + l + minlit + maxlit "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This integer literal is too big: 1│ x = 9_223_372_036_854_775_807_000 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This integer literal is too small: 3│ y = -9_223_372_036_854_775_807_000 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This integer literal is too big: 5│ h = 0x8FFF_FFFF_FFFF_FFFF ^^^^^^^^^^^^^^^^^^^^^ Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This integer literal is too small: 6│ l = -0x8FFF_FFFF_FFFF_FFFF ^^^^^^^^^^^^^^^^^^^^^^ Roc uses signed 64-bit integers, allowing values between −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. Tip: Learn more about number literals at TODO "# ), ) } #[test] fn float_out_of_range() { // have to deal with some whitespace issues because of the format! macro report_problem_as( indoc!( r#" overflow = 11.7976931348623157e308 underflow = -11.7976931348623157e308 overflow + underflow "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This float literal is too big: 1│ overflow = 11.7976931348623157e308 ^^^^^^^^^^^^^^^^^^^^^^^ Roc uses signed 64-bit floating points, allowing values between -1.7976931348623157e308 and 1.7976931348623157e308 Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This float literal is too small: 2│ underflow = -11.7976931348623157e308 ^^^^^^^^^^^^^^^^^^^^^^^^ Roc uses signed 64-bit floating points, allowing values between -1.7976931348623157e308 and 1.7976931348623157e308 Tip: Learn more about number literals at TODO "# ), ) } #[test] fn integer_malformed() { // the generated messages here are incorrect. Waiting for a rust nightly feature to land, // see https://github.com/rust-lang/rust/issues/22639 // this test is here to spot regressions in error reporting report_problem_as( indoc!( r#" dec = 100A hex = 0xZZZ oct = 0o9 bin = 0b2 dec + hex + oct + bin "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This integer literal contains an invalid digit: 1│ dec = 100A ^^^^ Integer literals can only contain the digits 0-9. Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This hex integer literal contains an invalid digit: 3│ hex = 0xZZZ ^^^^^ Hexadecimal (base-16) integer literals can only contain the digits 0-9, a-f and A-F. Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This octal integer literal contains an invalid digit: 5│ oct = 0o9 ^^^ Octal (base-8) integer literals can only contain the digits 0-7. Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This binary integer literal contains an invalid digit: 7│ bin = 0b2 ^^^ Binary (base-2) integer literals can only contain the digits 0 and 1. Tip: Learn more about number literals at TODO "# ), ) } #[test] fn integer_empty() { report_problem_as( indoc!( r#" dec = 20 hex = 0x oct = 0o bin = 0b dec + hex + oct + bin "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This hex integer literal contains no digits: 3│ hex = 0x ^^ Hexadecimal (base-16) integer literals must contain at least one of the digits 0-9, a-f and A-F. Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This octal integer literal contains no digits: 5│ oct = 0o ^^ Octal (base-8) integer literals must contain at least one of the digits 0-7. Tip: Learn more about number literals at TODO ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This binary integer literal contains no digits: 7│ bin = 0b ^^ Binary (base-2) integer literals must contain at least one of the digits 0 and 1. Tip: Learn more about number literals at TODO "# ), ) } #[test] fn float_malformed() { report_problem_as( indoc!( r#" x = 3.0A x "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This float literal contains an invalid digit: 1│ x = 3.0A ^^^^ Floating point literals can only contain the digits 0-9, or use scientific notation 10e4 Tip: Learn more about number literals at TODO "# ), ) } #[test] fn invalid_record_update() { report_problem_as( indoc!( r#" foo = { bar: 3 } updateNestedRecord = { foo.bar & x: 4 } example = { age: 42 } # these should work y = { Test.example & age: 3 } x = { example & age: 4 } { updateNestedRecord, foo, x, y } "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── This expression cannot be updated: 2│ updateNestedRecord = { foo.bar & x: 4 } ^^^^^^^ Only variables can be updated with record update syntax. "# ), ) } #[test] fn module_not_imported() { report_problem_as( indoc!( r#" Foo.test "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── The `Foo` module is not imported: 1│ Foo.test ^^^^^^^^ Is there an import missing? Perhaps there is a typo, these names seem close: Bool Num Set Str "# ), ) } #[test] fn optional_record_default_type_error() { report_problem_as( indoc!( r#" \{ x, y ? True } -> x + y "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 2nd argument to `add` is not what I expect: 1│ \{ x, y ? True } -> x + y ^ This `y` value is a: [ True ]a But `add` needs the 2nd argument to be: Num a "# ), ) } #[test] fn optional_record_default_with_signature() { report_problem_as( indoc!( r#" f : { x : I64, y ? I64 } -> I64 f = \{ x, y ? "foo" } -> (\g, _ -> g) x y f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st argument to `f` is weird: 2│ f = \{ x, y ? "foo" } -> (\g, _ -> g) x y ^^^^^^^^^^^^^^^^ The argument is a pattern that matches record values of type: { x : I64, y ? Str } But the annotation on `f` says the 1st argument should be: { x : I64, y ? I64 } "# ), ) } #[test] fn optional_record_invalid_let_binding() { report_problem_as( indoc!( r#" \rec -> { x, y } : { x : I64, y ? Bool } { x, y } = rec { x, y } "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── Something is off with the body of this definition: 2│> { x, y } : { x : I64, y ? Bool } 3│> { x, y } = rec The body is a value of type: { x : I64, y : Bool } But the type annotation says it should be: { x : I64, y ? Bool } Tip: To extract the `.y` field it must be non-optional, but the type says this field is optional. Learn more about optional fields at TODO. "# ), ) } #[test] fn optional_record_invalid_function() { report_problem_as( indoc!( r#" f : { x : I64, y ? I64 } -> I64 f = \{ x, y } -> x + y f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st argument to `f` is weird: 2│ f = \{ x, y } -> x + y ^^^^^^^^ The argument is a pattern that matches record values of type: { x : I64, y : I64 } But the annotation on `f` says the 1st argument should be: { x : I64, y ? I64 } Tip: To extract the `.y` field it must be non-optional, but the type says this field is optional. Learn more about optional fields at TODO. "# ), ) } #[test] fn optional_record_invalid_when() { report_problem_as( indoc!( r#" f : { x : I64, y ? I64 } -> I64 f = \r -> when r is { x, y } -> x + y f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st pattern in this `when` is causing a mismatch: 4│ { x, y } -> x + y ^^^^^^^^ The first pattern is trying to match record values of type: { x : I64, y : I64 } But the expression between `when` and `is` has the type: { x : I64, y ? I64 } Tip: To extract the `.y` field it must be non-optional, but the type says this field is optional. Learn more about optional fields at TODO. "# ), ) } #[test] fn optional_record_invalid_access() { report_problem_as( indoc!( r#" f : { x : I64, y ? I64 } -> I64 f = \r -> r.y f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── This expression is used in an unexpected way: 2│ f = \r -> r.y ^^^ This `r` value is a: { x : I64, y ? I64 } But you are trying to use it as: { x : I64, y : I64 } Tip: To extract the `.y` field it must be non-optional, but the type says this field is optional. Learn more about optional fields at TODO. "# ), ) } #[test] fn optional_record_invalid_accessor() { report_problem_as( indoc!( r#" f : { x : I64, y ? I64 } -> I64 f = \r -> .y r f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st argument to this function is not what I expect: 2│ f = \r -> .y r ^ This `r` value is a: { x : I64, y ? I64 } But this function needs the 1st argument to be: { x : I64, y : I64 } Tip: To extract the `.y` field it must be non-optional, but the type says this field is optional. Learn more about optional fields at TODO. "# ), ) } #[test] fn guard_mismatch_with_annotation() { report_problem_as( indoc!( r#" f : { x : I64, y : I64 } -> I64 f = \r -> when r is { x, y : "foo" } -> x + 0 _ -> 0 f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st pattern in this `when` is causing a mismatch: 4│ { x, y : "foo" } -> x + 0 ^^^^^^^^^^^^^^^^ The first pattern is trying to match record values of type: { x : I64, y : Str } But the expression between `when` and `is` has the type: { x : I64, y : I64 } "# ), ) } #[test] fn optional_field_mismatch_with_annotation() { report_problem_as( indoc!( r#" f : { x : I64, y ? I64 } -> I64 f = \r -> when r is { x, y ? "foo" } -> (\g, _ -> g) x y _ -> 0 f "# ), indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── The 1st pattern in this `when` is causing a mismatch: 4│ { x, y ? "foo" } -> (\g, _ -> g) x y ^^^^^^^^^^^^^^^^ The first pattern is trying to match record values of type: { x : I64, y ? Str } But the expression between `when` and `is` has the type: { x : I64, y ? I64 } "# ), ) } #[test] fn incorrect_optional_field() { report_problem_as( indoc!( r#" { x: 5, y ? 42 } "# ), indoc!( r#" ── 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#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── `f` doesn't use `foo`. 1│ f = \foo -> 1 ^^^ If you don't need `foo`, then you can just remove it. However, if you really do need `foo` as an argument of `f`, prefix it with an underscore, like this: "_`foo`". Adding an underscore at the start of a variable name is a way of saying that the variable is not used. "# ), ) } #[test] fn qualified_global_tag() { report_problem_as( indoc!( r#" Foo.Bar "# ), indoc!( r#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── 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_indent_end() { report_problem_as( indoc!( r#" f : { a: Int } "# ), indoc!( r#" ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── I am partway through parsing a record type, but I got stuck here: 1│ f : { a: Int 2│ } ^ I need this curly brace to be indented more. Try adding more spaces before it! "# ), ) } #[test] fn record_type_keyword_field_name() { report_problem_as( indoc!( r#" f : { if : I64 } "# ), indoc!( r#" ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── I just started parsing a record type, but I got stuck on this field name: 1│ f : { if : I64 } ^^ Looks like you are trying to use `if` as a field name, but that is a reserved word. Try using a different name! "# ), ) } #[test] fn record_type_missing_comma() { // a case where the message cannot be as good as elm's report_problem_as( indoc!( r#" f : { foo bar } "# ), indoc!( r#" ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── I am partway through parsing a record type, but I got stuck here: 1│ f : { foo bar } ^ I was expecting to see a colon, question mark, comma or closing curly brace. "# ), ) } #[test] fn record_type_tab() { // a case where the message cannot be as good as elm's report_problem_as( "f : { foo \t }", indoc!( r#" ── TAB CHARACTER ─────────────────────────────────────────────────────────────── I encountered a tab character 1│ f : { foo } ^ Tab characters are not allowed. "# ), ) } #[test] 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 encounterd two commas in a row: 1│ f : I64,,I64 -> I64 ^ Try removing one of them. "# ), ) } #[test] fn type_argument_no_arrow() { report_problem_as( indoc!( r#" f : I64, I64 f = 0 f "# ), indoc!( r#" ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── I 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 list_bad_indent() { report_problem_as( indoc!( r#" x = [ 1, 2, ] x "# ), indoc!( r#" ── UNFINISHED LIST ───────────────────────────────────────────────────────────── I cannot find the end of this list: 1│ x = [ 1, 2, ^ You could change it to something like [ 1, 2, 3 ] or even just []. Anything where there is an open and a close square bracket, and where the elements of the list are separated by commas. Note: I may be confused by indentation "# ), ) } #[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 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#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── 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] 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#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── The Num module does not expose a if value: 1│ Num.if ^^^^^^ "# ), ) } #[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 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#" ── SYNTAX 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#" ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── I cannot find a `bar` value 1│ [ "foo", bar("") ] ^^^ these names seem close though: Nat Str U8 F64 "# ), ) } #[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 { base: "platform" } imports [base.Task, Base64 ] provides [ main, @Foo ] to base "# ), indoc!( r#" ── WEIRD PROVIDES ────────────────────────────────────────────────────────────── I am partway through parsing a provides list, but I got stuck here: 3│ imports [base.Task, Base64 ] 4│ provides [ main, @Foo ] to base ^ I was expecting a type name, value name or function name next, like provides [ Animal, default, tame ] "# ), ) } #[test] fn exposes_identifier() { report_header_problem_as( indoc!( r#" interface Foobar exposes [ main, @Foo ] imports [base.Task, Base64 ] "# ), indoc!( r#" ── WEIRD EXPOSES ─────────────────────────────────────────────────────────────── I am partway through parsing a 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 [base.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 [base.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_alias() { report_problem_as( indoc!( r#" Box item : [ Box item, Items item item ] 4 "# ), indoc!( r#" ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── I am partway through parsing a tag union type, but I got stuck here: 1│ Box item : [ 2│ Box item, 3│ Items item item 4│ ] ^ I need this square bracket to be indented more. Try adding more spaces before it! "# ), ) } #[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 outdented_record() { report_problem_as( indoc!( r#" Box : { id: Str } 4 "# ), indoc!( r#" ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── I am partway through parsing a record type, but I got stuck here: 1│ Box : { 2│ id: Str 3│ } ^ I need this curly brace 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 "# ), ) } }