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