diff --git a/Cargo.lock b/Cargo.lock index eb989d06e2..87e157cd88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3980,6 +3980,7 @@ dependencies = [ "bumpalo", "distance", "indoc", + "insta", "pretty_assertions", "roc_builtins", "roc_can", diff --git a/crates/bindgen/src/load.rs b/crates/bindgen/src/load.rs index ab50290097..c66e2216d1 100644 --- a/crates/bindgen/src/load.rs +++ b/crates/bindgen/src/load.rs @@ -23,7 +23,7 @@ pub fn load_types( mut type_problems, mut declarations_by_id, mut solved, - interns, + mut interns, .. } = roc_load::load_and_typecheck( arena, @@ -74,7 +74,7 @@ pub fn load_types( let types_and_targets = Architecture::iter() .map(|arch| { let target_info = arch.into(); - let mut env = Env::new(arena, subs, &interns, target_info); + let mut env = Env::new(arena, subs, &mut interns, target_info); (env.vars_to_types(variables.clone()), target_info) }) diff --git a/crates/bindgen/src/types.rs b/crates/bindgen/src/types.rs index d2c34976ea..6886612794 100644 --- a/crates/bindgen/src/types.rs +++ b/crates/bindgen/src/types.rs @@ -419,7 +419,12 @@ pub struct Env<'a> { } impl<'a> Env<'a> { - pub fn new(arena: &'a Bump, subs: &'a Subs, interns: &'a Interns, target: TargetInfo) -> Self { + pub fn new( + arena: &'a Bump, + subs: &'a Subs, + interns: &'a mut Interns, + target: TargetInfo, + ) -> Self { Env { arena, subs, diff --git a/crates/compiler/alias_analysis/src/lib.rs b/crates/compiler/alias_analysis/src/lib.rs index 3c9c404802..b1157d8059 100644 --- a/crates/compiler/alias_analysis/src/lib.rs +++ b/crates/compiler/alias_analysis/src/lib.rs @@ -12,7 +12,7 @@ use roc_mono::ir::{ Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, OptLevel, Proc, Stmt, }; -use roc_mono::layout::{Builtin, Layout, RawFunctionLayout, UnionLayout}; +use roc_mono::layout::{Builtin, CapturesNiche, Layout, RawFunctionLayout, UnionLayout}; // just using one module for now pub const MOD_APP: ModName = ModName(b"UserApp"); @@ -23,7 +23,13 @@ pub const STATIC_LIST_NAME: ConstName = ConstName(b"THIS IS A STATIC LIST"); const ENTRY_POINT_NAME: &[u8] = b"mainForHost"; pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] { - func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), &proc.ret_layout) + let bytes = func_name_bytes_help( + proc.name.name(), + proc.args.iter().map(|x| x.0), + proc.name.captures_niche(), + &proc.ret_layout, + ); + bytes } #[inline(always)] @@ -64,6 +70,7 @@ impl TagUnionId { pub fn func_name_bytes_help<'a, I>( symbol: Symbol, argument_layouts: I, + captures_niche: CapturesNiche<'a>, return_layout: &Layout<'a>, ) -> [u8; SIZE] where @@ -82,6 +89,8 @@ where layout.hash(&mut hasher); } + captures_niche.hash(&mut hasher); + return_layout.hash(&mut hasher); hasher.finish() @@ -173,13 +182,22 @@ where match layout { RawFunctionLayout::Function(_, _, _) => { let it = top_level.arguments.iter().copied(); - let bytes = func_name_bytes_help(*symbol, it, &top_level.result); + let bytes = func_name_bytes_help( + *symbol, + it, + CapturesNiche::no_niche(), + &top_level.result, + ); host_exposed_functions.push((bytes, top_level.arguments)); } RawFunctionLayout::ZeroArgumentThunk(_) => { - let bytes = - func_name_bytes_help(*symbol, [Layout::UNIT], &top_level.result); + let bytes = func_name_bytes_help( + *symbol, + [Layout::UNIT], + CapturesNiche::no_niche(), + &top_level.result, + ); host_exposed_functions.push((bytes, top_level.arguments)); } @@ -207,6 +225,7 @@ where let roc_main_bytes = func_name_bytes_help( entry_point.symbol, entry_point.layout.arguments.iter().copied(), + CapturesNiche::no_niche(), &entry_point.layout.result, ); let roc_main = FuncName(&roc_main_bytes); @@ -631,7 +650,7 @@ fn call_spec( match &call.call_type { ByName { - name: symbol, + name, ret_layout, arg_layouts, specialization_id, @@ -640,8 +659,9 @@ fn call_spec( let spec_var = CalleeSpecVar(&array); let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?; - let it = arg_layouts.iter().copied(); - let bytes = func_name_bytes_help(*symbol, it, ret_layout); + let args_it = arg_layouts.iter().copied(); + let captures_niche = name.captures_niche(); + let bytes = func_name_bytes_help(name.name(), args_it, captures_niche, ret_layout); let name = FuncName(&bytes); let module = MOD_APP; builder.add_call(block, spec_var, module, name, arg_value_id) @@ -684,9 +704,14 @@ fn call_spec( let mode = update_mode.to_bytes(); let update_mode_var = UpdateModeVar(&mode); - let it = passed_function.argument_layouts.iter().copied(); - let bytes = - func_name_bytes_help(passed_function.name, it, &passed_function.return_layout); + let args_it = passed_function.argument_layouts.iter().copied(); + let captures_niche = passed_function.name.captures_niche(); + let bytes = func_name_bytes_help( + passed_function.name.name(), + args_it, + captures_niche, + &passed_function.return_layout, + ); let name = FuncName(&bytes); let module = MOD_APP; @@ -1201,14 +1226,12 @@ fn expr_spec<'a>( Call(call) => call_spec(builder, env, block, layout, call), Reuse { tag_layout, - tag_name: _, tag_id, arguments, .. } | Tag { tag_layout, - tag_name: _, tag_id, arguments, } => { diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 417e0005fe..9006e083d6 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -143,6 +143,7 @@ const str = @import("str.zig"); comptime { exportStrFn(str.init, "init"); exportStrFn(str.strToScalarsC, "to_scalars"); + exportStrFn(str.strSplit, "str_split"); exportStrFn(str.strSplitInPlaceC, "str_split_in_place"); exportStrFn(str.countSegments, "count_segments"); exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); diff --git a/crates/compiler/builtins/bitcode/src/str.zig b/crates/compiler/builtins/bitcode/src/str.zig index bf898c3713..beba67172c 100644 --- a/crates/compiler/builtins/bitcode/src/str.zig +++ b/crates/compiler/builtins/bitcode/src/str.zig @@ -752,6 +752,21 @@ fn strFromFloatHelp(comptime T: type, float: T) RocStr { } // Str.split + +// For dev backends +pub fn strSplit(string: RocStr, delimiter: RocStr) callconv(.C) RocList { + const segment_count = countSegments(string, delimiter); + const list = RocList.allocate(@alignOf(RocStr), segment_count, @sizeOf(RocStr)); + + if (list.bytes) |bytes| { + const strings = @ptrCast([*]RocStr, @alignCast(@alignOf(RocStr), bytes)); + strSplitInPlace(strings, string, delimiter); + } + + return list; +} + +// For LLVM backend pub fn strSplitInPlaceC(opt_array: ?[*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void { if (opt_array) |array| { return @call(.{ .modifier = always_inline }, strSplitInPlace, .{ array, string, delimiter }); @@ -1595,7 +1610,7 @@ const CountAndStart = extern struct { start: usize, }; -pub fn fromUtf8C(arg: RocList, update_mode: UpdateMode, output: *FromUtf8Result) callconv(.C) void { +pub fn fromUtf8C(output: *FromUtf8Result, arg: RocList, update_mode: UpdateMode) callconv(.C) void { output.* = fromUtf8(arg, update_mode); } @@ -1650,7 +1665,7 @@ inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result { } } -pub fn fromUtf8RangeC(arg: RocList, countAndStart: CountAndStart, output: *FromUtf8Result) callconv(.C) void { +pub fn fromUtf8RangeC(output: *FromUtf8Result, arg: RocList, countAndStart: CountAndStart) callconv(.C) void { output.* = @call(.{ .modifier = always_inline }, fromUtf8Range, .{ arg, countAndStart }); } diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 3680c2fb59..7173e94c5f 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -29,7 +29,7 @@ pub enum DecWidth { } #[repr(u8)] -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum FloatWidth { F32, F64, @@ -76,7 +76,7 @@ impl FloatWidth { } #[repr(u8)] -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum IntWidth { U8 = 0, U16 = 1, @@ -310,6 +310,7 @@ pub const STR_INIT: &str = "roc_builtins.str.init"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; pub const STR_CONCAT: &str = "roc_builtins.str.concat"; pub const STR_JOIN_WITH: &str = "roc_builtins.str.joinWith"; +pub const STR_STR_SPLIT: &str = "roc_builtins.str.str_split"; pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place"; pub const STR_TO_SCALARS: &str = "roc_builtins.str.to_scalars"; pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters"; diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index 170387ec1f..71875966b3 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -1770,7 +1770,7 @@ fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { // Ok arg_2.str // else // # problem - // Err (BadUtf8 { byteIndex: arg_2.byteIndex, problem : arg_2.problem }) + // Err (BadUtf8 arg_2.problem arg_2.byteIndex) let def = crate::def::Def { loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_2)), @@ -1872,7 +1872,7 @@ fn str_from_utf8_range(symbol: Symbol, var_store: &mut VarStore) -> Def { // if arg_3.a then // Ok arg_3.str // else - // Err (BadUtf8 { byteIndex: arg_3.byteIndex, problem : arg_3.problem }) + // Err (BadUtf8 arg_3.problem arg_3.byteIndex) let def = crate::def::Def { loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index 9a077e0e40..6463029cc8 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -7,6 +7,7 @@ use crate::annotation::make_apply_symbol; use crate::annotation::IntroducedVariables; use crate::annotation::OwnedNamedOrAble; use crate::env::Env; +use crate::expr::AccessorData; use crate::expr::AnnotatedMark; use crate::expr::ClosureData; use crate::expr::Declarations; @@ -1554,7 +1555,10 @@ fn canonicalize_pending_value_def<'a>( region: loc_ann.region, } } else { - let symbol = scope.gen_unique_symbol(); + let symbol = match &loc_can_pattern.value { + Pattern::Identifier(symbol) => *symbol, + _ => scope.gen_unique_symbol(), + }; // generate a fake pattern for each argument. this makes signatures // that are functions only crash when they are applied. @@ -1725,6 +1729,36 @@ fn canonicalize_pending_body<'a>( (loc_can_expr, def_references) } + // Turn f = .foo into f = \rcd -[f]-> rcd.foo + ( + Pattern::Identifier(defined_symbol) + | Pattern::AbilityMemberSpecialization { + ident: defined_symbol, + .. + }, + ast::Expr::AccessorFunction(field), + ) => { + let (loc_can_expr, can_output) = ( + Loc::at( + loc_expr.region, + Accessor(AccessorData { + name: *defined_symbol, + function_var: var_store.fresh(), + record_var: var_store.fresh(), + ext_var: var_store.fresh(), + closure_var: var_store.fresh(), + field_var: var_store.fresh(), + field: (*field).into(), + }), + ), + Output::default(), + ); + let def_references = DefReferences::Value(can_output.references.clone()); + output.union(can_output); + + (loc_can_expr, def_references) + } + _ => { let (loc_can_expr, can_output) = canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); diff --git a/crates/compiler/collections/src/vec_map.rs b/crates/compiler/collections/src/vec_map.rs index 827f2ad293..7b117ee842 100644 --- a/crates/compiler/collections/src/vec_map.rs +++ b/crates/compiler/collections/src/vec_map.rs @@ -97,6 +97,10 @@ impl VecMap { self.keys.iter().zip(self.values.iter()) } + pub fn iter_mut(&mut self) -> impl ExactSizeIterator { + self.keys.iter().zip(self.values.iter_mut()) + } + pub fn keys(&self) -> impl ExactSizeIterator { self.keys.iter() } diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs index 61b8f1e1a2..468c1c2d8c 100644 --- a/crates/compiler/fmt/src/expr.rs +++ b/crates/compiler/fmt/src/expr.rs @@ -699,25 +699,44 @@ fn fmt_when<'a, 'buf>( buf.push_str("is"); buf.newline(); - let mut it = branches.iter().peekable(); - while let Some(branch) = it.next() { + let mut it = branches.iter().enumerate().peekable(); + + while let Some((branch_index, branch)) = it.next() { let expr = &branch.value; let patterns = &branch.patterns; let is_multiline_expr = expr.is_multiline(); let is_multiline_patterns = is_when_patterns_multiline(branch); for (index, pattern) in patterns.iter().enumerate() { - if index != 0 { + if index == 0 { + match &pattern.value { + Pattern::SpaceBefore(sub_pattern, spaces) if branch_index == 0 => { + // Never include extra newlines before the first branch. + // Instead, write the comments and that's it. + fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + INDENT); + + fmt_pattern(buf, sub_pattern, indent + INDENT, Parens::NotNeeded); + } + other => { + fmt_pattern(buf, other, indent + INDENT, Parens::NotNeeded); + } + } + } else { if is_multiline_patterns { - buf.newline(); - buf.indent(indent + INDENT); + buf.ensure_ends_in_newline(); + // Indent an extra level for the `|`; + // otherwise it'll be at the start of the line, + // and will be incorrectly parsed as a pattern + buf.indent(indent + INDENT + INDENT); + buf.push('|'); + } else { + buf.push_str(" |"); } - buf.push_str(" |"); buf.spaces(1); - } - fmt_pattern(buf, &pattern.value, indent + INDENT, Parens::NotNeeded); + fmt_pattern(buf, &pattern.value, indent + INDENT, Parens::NotNeeded); + } } if let Some(guard_expr) = &branch.guard { diff --git a/crates/compiler/fmt/src/lib.rs b/crates/compiler/fmt/src/lib.rs index 1e15f605b8..782f47b3f0 100644 --- a/crates/compiler/fmt/src/lib.rs +++ b/crates/compiler/fmt/src/lib.rs @@ -89,6 +89,14 @@ impl<'a> Buf<'a> { self.beginning_of_line = true; } + /// Ensures the current buffer ends in a newline, if it didn't already. + /// Doesn't add a newline if the buffer already ends in one. + pub fn ensure_ends_in_newline(&mut self) { + if !self.text.ends_with('\n') { + self.newline() + } + } + fn flush_spaces(&mut self) { if self.spaces_to_flush > 0 { for _ in 0..self.spaces_to_flush { diff --git a/crates/compiler/fmt/src/pattern.rs b/crates/compiler/fmt/src/pattern.rs index 4df623dd5c..43bbc298b9 100644 --- a/crates/compiler/fmt/src/pattern.rs +++ b/crates/compiler/fmt/src/pattern.rs @@ -1,7 +1,7 @@ use crate::annotation::{Formattable, Newlines, Parens}; use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt}; use crate::Buf; -use roc_parse::ast::{Base, Pattern}; +use roc_parse::ast::{Base, CommentOrNewline, Pattern}; pub fn fmt_pattern<'a, 'buf>( buf: &mut Buf<'buf>, @@ -167,11 +167,16 @@ impl<'a> Formattable for Pattern<'a> { } else { fmt_spaces(buf, spaces.iter(), indent); } + sub_pattern.format_with_options(buf, parens, newlines, indent); } SpaceAfter(sub_pattern, spaces) => { sub_pattern.format_with_options(buf, parens, newlines, indent); - // if only_comments { + + if starts_with_inline_comment(spaces.iter()) { + buf.spaces(1); + } + if !sub_pattern.is_multiline() { fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent) } else { @@ -196,3 +201,12 @@ impl<'a> Formattable for Pattern<'a> { } } } + +fn starts_with_inline_comment<'a, I: IntoIterator>>( + spaces: I, +) -> bool { + matches!( + spaces.into_iter().next(), + Some(CommentOrNewline::LineComment(_)) + ) +} diff --git a/crates/compiler/fmt/tests/test_fmt.rs b/crates/compiler/fmt/tests/test_fmt.rs index 504b646da7..b647603f34 100644 --- a/crates/compiler/fmt/tests/test_fmt.rs +++ b/crates/compiler/fmt/tests/test_fmt.rs @@ -3373,6 +3373,18 @@ mod test_fmt { )); } + #[test] + fn when_with_integer_comments() { + expr_formats_same(indoc!( + r#" + when 0 is + 1 # comment + | 2 -> "a" + _ -> "b" + "# + )); + } + #[test] fn nested_when() { expr_formats_same(indoc!( @@ -3465,18 +3477,18 @@ mod test_fmt { r#" when b is 1 - | 2 - | 3 -> + | 2 + | 3 -> 4 5 | 6 | 7 -> 8 9 - | 10 -> 11 + | 10 -> 11 12 | 13 -> when c is 14 | 15 -> 16 17 - | 18 -> 19 + | 18 -> 19 20 -> 21 "# ), @@ -3498,7 +3510,7 @@ mod test_fmt { when b is 3 -> 4 9 - | 8 -> 9 + | 8 -> 9 "# ), ); diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 73927170e6..eef12f4cf8 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -14,9 +14,7 @@ use roc_mono::ir::{ BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, SelfRecursive, Stmt, }; -use roc_mono::layout::{ - Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, TagOrClosure, UnionLayout, -}; +use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout}; mod generic64; mod object_builder; @@ -106,8 +104,8 @@ trait Backend<'a> { proc: Proc<'a>, layout_ids: &mut LayoutIds<'a>, ) -> (Vec, Vec, Vec<'a, (Symbol, String)>) { - let layout_id = layout_ids.get(proc.name, &proc.ret_layout); - let proc_name = self.symbol_to_string(proc.name, layout_id); + let layout_id = layout_ids.get(proc.name.name(), &proc.ret_layout); + let proc_name = self.symbol_to_string(proc.name.name(), layout_id); self.reset(proc_name, proc.is_self_recursive); self.load_args(proc.args, &proc.ret_layout); for (layout, sym) in proc.args { @@ -263,7 +261,7 @@ trait Backend<'a> { .. } => { if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = - LowLevelWrapperType::from_symbol(*func_sym) + LowLevelWrapperType::from_symbol(func_sym.name()) { self.build_run_low_level( sym, @@ -272,14 +270,20 @@ trait Backend<'a> { arg_layouts, ret_layout, ) - } else if self.defined_in_app_module(*func_sym) { - let layout_id = LayoutIds::default().get(*func_sym, layout); - let fn_name = self.symbol_to_string(*func_sym, layout_id); + } else if self.defined_in_app_module(func_sym.name()) { + let layout_id = LayoutIds::default().get(func_sym.name(), layout); + let fn_name = self.symbol_to_string(func_sym.name(), layout_id); // Now that the arguments are needed, load them if they are literals. self.load_literal_symbols(arguments); self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout) } else { - self.build_builtin(sym, *func_sym, arguments, arg_layouts, ret_layout) + self.build_builtin( + sym, + func_sym.name(), + arguments, + arg_layouts, + ret_layout, + ) } } @@ -909,18 +913,9 @@ trait Backend<'a> { } } Expr::Reuse { - symbol, - arguments, - tag_name, - .. + symbol, arguments, .. } => { self.set_last_seen(*symbol, stmt); - match tag_name { - TagOrClosure::Closure(sym) => { - self.set_last_seen(*sym, stmt); - } - TagOrClosure::Tag(_) => {} - } for sym in *arguments { self.set_last_seen(*sym, stmt); } diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 76def28c92..5d53a2a84c 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -62,7 +62,9 @@ use roc_mono::ir::{ BranchInfo, CallType, EntryPoint, HigherOrderLowLevel, JoinPointId, ListLiteralElement, ModifyRc, OptLevel, ProcLayout, }; -use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; +use roc_mono::layout::{ + Builtin, CapturesNiche, LambdaName, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout, +}; use roc_std::RocDec; use roc_target::{PtrWidth, TargetInfo}; use std::convert::{TryFrom, TryInto}; @@ -715,7 +717,12 @@ fn promote_to_main_function<'a, 'ctx, 'env>( top_level: ProcLayout<'a>, ) -> (&'static str, FunctionValue<'ctx>) { let it = top_level.arguments.iter().copied(); - let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, &top_level.result); + let bytes = roc_alias_analysis::func_name_bytes_help( + symbol, + it, + CapturesNiche::no_niche(), + &top_level.result, + ); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); @@ -727,7 +734,14 @@ fn promote_to_main_function<'a, 'ctx, 'env>( ); // NOTE fake layout; it is only used for debug prints - let roc_main_fn = function_value_by_func_spec(env, *func_spec, symbol, &[], &Layout::UNIT); + let roc_main_fn = function_value_by_func_spec( + env, + *func_spec, + symbol, + &[], + CapturesNiche::no_niche(), + &Layout::UNIT, + ); let main_fn_name = "$Test.main"; @@ -3296,6 +3310,7 @@ fn expose_function_to_host<'a, 'ctx, 'env>( symbol: Symbol, roc_function: FunctionValue<'ctx>, arguments: &'a [Layout<'a>], + captures_niche: CapturesNiche<'a>, return_layout: Layout<'a>, layout_ids: &mut LayoutIds<'a>, ) { @@ -3304,6 +3319,7 @@ fn expose_function_to_host<'a, 'ctx, 'env>( let proc_layout = ProcLayout { arguments, result: return_layout, + captures_niche, }; let c_function_name: String = layout_ids @@ -4185,7 +4201,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( // only have top-level thunks for this proc's module in scope // this retain is not needed for correctness, but will cause less confusion when debugging - let home = proc.name.module_id(); + let home = proc.name.name().module_id(); current_scope.retain_top_level_thunks_for_module(home); build_proc( @@ -4301,6 +4317,7 @@ fn build_proc_header<'a, 'ctx, 'env>( symbol, fn_val, arguments.into_bump_slice(), + proc.name.captures_niche(), proc.ret_layout, layout_ids, ); @@ -4530,8 +4547,12 @@ pub fn build_proc<'a, 'ctx, 'env>( // * roc__mainForHost_1_Update_result_size() -> i64 let it = top_level.arguments.iter().copied(); - let bytes = - roc_alias_analysis::func_name_bytes_help(symbol, it, &top_level.result); + let bytes = roc_alias_analysis::func_name_bytes_help( + symbol, + it, + CapturesNiche::no_niche(), + &top_level.result, + ); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); @@ -4548,6 +4569,7 @@ pub fn build_proc<'a, 'ctx, 'env>( *func_spec, symbol, top_level.arguments, + CapturesNiche::no_niche(), &top_level.result, ) } @@ -4559,7 +4581,7 @@ pub fn build_proc<'a, 'ctx, 'env>( } }; - let ident_string = proc.name.as_str(&env.interns); + let ident_string = proc.name.name().as_str(&env.interns); let fn_name: String = format!("{}_1", ident_string); build_closure_caller( @@ -4624,17 +4646,19 @@ fn function_value_by_func_spec<'a, 'ctx, 'env>( func_spec: FuncSpec, symbol: Symbol, arguments: &[Layout<'a>], + captures_niche: CapturesNiche<'a>, result: &Layout<'a>, ) -> FunctionValue<'ctx> { let fn_name = func_spec_name(env.arena, &env.interns, symbol, func_spec); let fn_name = fn_name.as_str(); - function_value_by_name_help(env, arguments, result, symbol, fn_name) + function_value_by_name_help(env, arguments, captures_niche, result, symbol, fn_name) } fn function_value_by_name_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, arguments: &[Layout<'a>], + _captures_niche: CapturesNiche<'a>, result: &Layout<'a>, symbol: Symbol, fn_name: &str, @@ -4675,12 +4699,18 @@ fn roc_call_with_args<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, argument_layouts: &[Layout<'a>], result_layout: &Layout<'a>, - symbol: Symbol, + name: LambdaName<'a>, func_spec: FuncSpec, arguments: &[BasicValueEnum<'ctx>], ) -> BasicValueEnum<'ctx> { - let fn_val = - function_value_by_func_spec(env, func_spec, symbol, argument_layouts, result_layout); + let fn_val = function_value_by_func_spec( + env, + func_spec, + name.name(), + argument_layouts, + name.captures_niche(), + result_layout, + ); call_roc_function(env, fn_val, result_layout, arguments) } @@ -4869,8 +4899,9 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( let function = function_value_by_func_spec( env, func_spec, - function_name, + function_name.name(), argument_layouts, + function_name.captures_niche(), return_layout, ); diff --git a/crates/compiler/gen_llvm/src/llvm/build_str.rs b/crates/compiler/gen_llvm/src/llvm/build_str.rs index 12b24b2dea..8ed77c2142 100644 --- a/crates/compiler/gen_llvm/src/llvm/build_str.rs +++ b/crates/compiler/gen_llvm/src/llvm/build_str.rs @@ -168,10 +168,10 @@ pub fn str_from_utf8_range<'a, 'ctx, 'env>( call_void_bitcode_fn( env, &[ + result_ptr.into(), list_symbol_to_c_abi(env, scope, list).into(), count, start, - result_ptr.into(), ], bitcode::STR_FROM_UTF8_RANGE, ); @@ -194,9 +194,9 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( call_void_bitcode_fn( env, &[ + result_ptr.into(), list_symbol_to_c_abi(env, scope, list).into(), pass_update_mode(env, update_mode), - result_ptr.into(), ], bitcode::STR_FROM_UTF8, ); diff --git a/crates/compiler/gen_llvm/src/llvm/compare.rs b/crates/compiler/gen_llvm/src/llvm/compare.rs index 8af0ea575c..be5e6116a9 100644 --- a/crates/compiler/gen_llvm/src/llvm/compare.rs +++ b/crates/compiler/gen_llvm/src/llvm/compare.rs @@ -1374,12 +1374,14 @@ fn build_box_eq_help<'a, 'ctx, 'env>( box1.set_name(Symbol::ARG_1.as_str(&env.interns)); box2.set_name(Symbol::ARG_2.as_str(&env.interns)); + let entry = ctx.append_basic_block(parent, "entry"); + env.builder.position_at_end(entry); + let return_true = ctx.append_basic_block(parent, "return_true"); env.builder.position_at_end(return_true); env.builder .build_return(Some(&env.context.bool_type().const_all_ones())); - let entry = ctx.append_basic_block(parent, "entry"); env.builder.position_at_end(entry); let ptr_equal = env.builder.build_int_compare( @@ -1402,8 +1404,8 @@ fn build_box_eq_help<'a, 'ctx, 'env>( let box1 = box1.into_pointer_value(); let box2 = box2.into_pointer_value(); - let value1 = env.builder.build_load(box1, "load_box1"); - let value2 = env.builder.build_load(box2, "load_box2"); + let value1 = load_roc_value(env, *inner_layout, box1, "load_box1"); + let value2 = load_roc_value(env, *inner_layout, box2, "load_box2"); let is_equal = build_eq( env, diff --git a/crates/compiler/gen_wasm/src/backend.rs b/crates/compiler/gen_wasm/src/backend.rs index d84741d8de..ce374c31e1 100644 --- a/crates/compiler/gen_wasm/src/backend.rs +++ b/crates/compiler/gen_wasm/src/backend.rs @@ -17,7 +17,7 @@ use roc_std::RocDec; use crate::layout::{CallConv, ReturnMethod, WasmLayout}; use crate::low_level::{call_higher_order_lowlevel, LowLevelCall}; -use crate::storage::{Storage, StoredValue, StoredVarKind}; +use crate::storage::{AddressValue, Storage, StoredValue, StoredVarKind}; use crate::wasm_module::linking::{DataSymbol, WasmObjectSymbol}; use crate::wasm_module::sections::{ ConstExpr, DataMode, DataSegment, Export, Global, GlobalType, Import, ImportDesc, Limits, @@ -380,7 +380,7 @@ impl<'a> WasmBackend<'a> { println!("\ngenerating procedure {:?}\n", proc.name); } - self.append_proc_debug_name(proc.name); + self.append_proc_debug_name(proc.name.name()); self.start_proc(proc); @@ -928,7 +928,7 @@ impl<'a> WasmBackend<'a> { index, field_layouts, structure, - } => self.expr_struct_at_index(sym, storage, *index, field_layouts, *structure), + } => self.expr_struct_at_index(sym, *index, field_layouts, *structure), Expr::Array { elems, elem_layout } => self.expr_array(sym, storage, elem_layout, elems), @@ -953,9 +953,9 @@ impl<'a> WasmBackend<'a> { index, } => self.expr_union_at_index(*structure, *tag_id, union_layout, *index, sym), - Expr::ExprBox { .. } | Expr::ExprUnbox { .. } => { - todo!("Expression `{}`", expr.to_pretty(100)) - } + Expr::ExprBox { symbol: arg_sym } => self.expr_box(sym, *arg_sym, layout, storage), + + Expr::ExprUnbox { symbol: arg_sym } => self.expr_unbox(sym, *arg_sym), Expr::Reuse { tag_layout, @@ -1125,9 +1125,10 @@ impl<'a> WasmBackend<'a> { let proc_layout = ProcLayout { arguments: arg_layouts, result: **result, + captures_niche: func_sym.captures_niche(), }; self.expr_call_by_name( - *func_sym, + func_sym.name(), &proc_layout, arguments, ret_sym, @@ -1353,16 +1354,15 @@ impl<'a> WasmBackend<'a> { fn expr_struct_at_index( &mut self, sym: Symbol, - storage: &StoredValue, index: u64, field_layouts: &'a [Layout<'a>], structure: Symbol, ) { - self.storage - .ensure_value_has_local(&mut self.code_builder, sym, storage.to_owned()); - let (local_id, mut offset) = match self.storage.get(&structure) { + let (from_addr_val, mut offset) = match self.storage.get(&structure) { StoredValue::StackMemory { location, .. } => { - location.local_and_offset(self.storage.stack_frame_pointer) + let (local_id, offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + (AddressValue::NotLoaded(local_id), offset) } StoredValue::Local { @@ -1371,18 +1371,20 @@ impl<'a> WasmBackend<'a> { .. } => { debug_assert!(matches!(value_type, ValueType::I32)); - (*local_id, 0) + (AddressValue::NotLoaded(*local_id), 0) } StoredValue::VirtualMachineStack { .. } => { - internal_error!("ensure_value_has_local didn't work") + self.storage + .load_symbols(&mut self.code_builder, &[structure]); + (AddressValue::Loaded, 0) } }; for field in field_layouts.iter().take(index as usize) { offset += field.stack_size(TARGET_INFO); } self.storage - .copy_value_from_memory(&mut self.code_builder, sym, local_id, offset); + .copy_value_from_memory(&mut self.code_builder, sym, from_addr_val, offset); } /******************************************************************* @@ -1700,20 +1702,83 @@ impl<'a> WasmBackend<'a> { let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(TARGET_INFO); - let from_ptr = if stores_tag_id_in_pointer { - let ptr = self.storage.create_anonymous_local(ValueType::I32); + let from_addr_val = if stores_tag_id_in_pointer { self.code_builder.get_local(tag_local_id); self.code_builder.i32_const(-4); // 11111111...1100 self.code_builder.i32_and(); - self.code_builder.set_local(ptr); - ptr + AddressValue::Loaded } else { - tag_local_id + AddressValue::NotLoaded(tag_local_id) }; let from_offset = tag_offset + field_offset; + self.storage.copy_value_from_memory( + &mut self.code_builder, + symbol, + from_addr_val, + from_offset, + ); + } + + /******************************************************************* + * Box + *******************************************************************/ + + fn expr_box( + &mut self, + ret_sym: Symbol, + arg_sym: Symbol, + layout: &Layout<'a>, + storage: &StoredValue, + ) { + // create a local variable for the heap pointer + let ptr_local_id = match self.storage.ensure_value_has_local( + &mut self.code_builder, + ret_sym, + storage.clone(), + ) { + StoredValue::Local { local_id, .. } => local_id, + _ => internal_error!("A heap pointer will always be an i32"), + }; + + // allocate heap memory and load its data address onto the value stack + let arg_layout = match layout { + Layout::Boxed(arg) => *arg, + _ => internal_error!("ExprBox should always produce a Boxed layout"), + }; + let (size, alignment) = arg_layout.stack_size_and_alignment(TARGET_INFO); + self.allocate_with_refcount(Some(size), alignment, 1); + + // store the pointer value from the value stack into the local variable + self.code_builder.set_local(ptr_local_id); + + // copy the argument to the pointer address self.storage - .copy_value_from_memory(&mut self.code_builder, symbol, from_ptr, from_offset); + .copy_value_to_memory(&mut self.code_builder, ptr_local_id, 0, arg_sym); + } + + fn expr_unbox(&mut self, ret_sym: Symbol, arg_sym: Symbol) { + let (from_addr_val, from_offset) = match self.storage.get(&arg_sym) { + StoredValue::VirtualMachineStack { .. } => { + self.storage + .load_symbols(&mut self.code_builder, &[arg_sym]); + (AddressValue::Loaded, 0) + } + StoredValue::Local { local_id, .. } => (AddressValue::NotLoaded(*local_id), 0), + StoredValue::StackMemory { location, .. } => { + let (local_id, offset) = + location.local_and_offset(self.storage.stack_frame_pointer); + (AddressValue::NotLoaded(local_id), offset) + } + }; + + // Copy the value + self.storage.copy_value_from_memory( + &mut self.code_builder, + ret_sym, + from_addr_val, + from_offset, + ); } /******************************************************************* diff --git a/crates/compiler/gen_wasm/src/lib.rs b/crates/compiler/gen_wasm/src/lib.rs index 4afc37c7fe..1d00e29f64 100644 --- a/crates/compiler/gen_wasm/src/lib.rs +++ b/crates/compiler/gen_wasm/src/lib.rs @@ -184,7 +184,8 @@ pub fn build_app_module<'a>( } let (module, called_preload_fns) = backend.finalize(); - let main_function_index = maybe_main_fn_index.unwrap(); + let main_function_index = + maybe_main_fn_index.expect("The app must expose at least one value to the host"); (module, called_preload_fns, main_function_index) } diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index 639faa0c6d..cb7fbab7f8 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -11,7 +11,7 @@ use roc_mono::low_level::HigherOrder; use crate::backend::{ProcLookupData, ProcSource, WasmBackend}; use crate::layout::{CallConv, StackMemoryFormat, WasmLayout}; -use crate::storage::{StackMemoryLocation, StoredValue}; +use crate::storage::{AddressValue, StackMemoryLocation, StoredValue}; use crate::wasm_module::{Align, LocalId, ValueType}; use crate::TARGET_INFO; @@ -236,15 +236,7 @@ impl<'a> LowLevelCall<'a> { self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH_SCALAR) } StrEndsWith => self.load_args_and_call_zig(backend, bitcode::STR_ENDS_WITH), - StrSplit => { - // LLVM implementation (build_str.rs) does the following - // 1. Call bitcode::STR_COUNT_SEGMENTS - // 2. Allocate a `List Str` - // 3. Call bitcode::STR_STR_SPLIT_IN_PLACE - // 4. Write the elements and length of the List - // To do this here, we need full access to WasmBackend, or we could make a Zig wrapper - todo!("{:?}", self.lowlevel); - } + StrSplit => self.load_args_and_call_zig(backend, bitcode::STR_STR_SPLIT), StrCountGraphemes => { self.load_args_and_call_zig(backend, bitcode::STR_COUNT_GRAPEHEME_CLUSTERS) } @@ -272,10 +264,30 @@ impl<'a> LowLevelCall<'a> { } StrFromInt => self.num_to_str(backend), StrFromFloat => self.num_to_str(backend), - StrFromUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8), + StrFromUtf8 => { + /* + Low-level op returns a struct with all the data for both Ok and Err. + Roc AST wrapper converts this to a tag union, with app-dependent tag IDs. + + fromUtf8C(output: *FromUtf8Result, arg: RocList, update_mode: UpdateMode) callconv(.C) void + output: *FromUtf8Result i32 + arg: RocList i64, i32 + update_mode: UpdateMode i32 + */ + backend.storage.load_symbols_for_call( + backend.env.arena, + &mut backend.code_builder, + self.arguments, + self.ret_symbol, + &WasmLayout::new(&self.ret_layout), + CallConv::Zig, + ); + backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE); + backend.call_host_fn_after_loading_args(bitcode::STR_FROM_UTF8, 4, false); + } + StrFromUtf8Range => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8_RANGE), StrTrimLeft => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_LEFT), StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT), - StrFromUtf8Range => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8_RANGE), StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8), StrReserve => self.load_args_and_call_zig(backend, bitcode::STR_RESERVE), StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT), @@ -325,14 +337,12 @@ impl<'a> LowLevelCall<'a> { // Target element heap pointer backend.code_builder.i32_add(); // base + index*size - let elem_heap_ptr = backend.storage.create_anonymous_local(ValueType::I32); - backend.code_builder.set_local(elem_heap_ptr); // Copy to stack backend.storage.copy_value_from_memory( &mut backend.code_builder, self.ret_symbol, - elem_heap_ptr, + AddressValue::Loaded, 0, ); @@ -1726,7 +1736,8 @@ impl<'a> LowLevelCall<'a> { Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) | Layout::Struct { .. } | Layout::Union(_) - | Layout::LambdaSet(_) => { + | Layout::LambdaSet(_) + | Layout::Boxed(_) => { // Don't want Zig calling convention here, we're calling internal Roc functions backend .storage @@ -1744,8 +1755,6 @@ impl<'a> LowLevelCall<'a> { } } - Layout::Boxed(_) => todo!(), - Layout::RecursivePointer => { internal_error!( "Tried to apply `==` to RecursivePointer values {:?}", @@ -1962,12 +1971,13 @@ pub fn call_higher_order_lowlevel<'a>( let passed_proc_layout = ProcLayout { arguments: argument_layouts, result: *result_layout, + captures_niche: fn_name.captures_niche(), }; let passed_proc_index = backend .proc_lookup .iter() .position(|ProcLookupData { name, layout, .. }| { - name == fn_name && layout == &passed_proc_layout + *name == fn_name.name() && layout == &passed_proc_layout }) .unwrap(); ProcSource::HigherOrderWrapper(passed_proc_index) @@ -1995,6 +2005,7 @@ pub fn call_higher_order_lowlevel<'a>( ProcLayout { arguments: wrapper_arg_layouts.into_bump_slice(), result: Layout::UNIT, + captures_niche: fn_name.captures_niche(), } }; diff --git a/crates/compiler/gen_wasm/src/storage.rs b/crates/compiler/gen_wasm/src/storage.rs index ff37d11e84..0c7b03d5da 100644 --- a/crates/compiler/gen_wasm/src/storage.rs +++ b/crates/compiler/gen_wasm/src/storage.rs @@ -76,6 +76,13 @@ impl StoredValue { } } +pub enum AddressValue { + /// The address value has been loaded to the VM stack + Loaded, + /// The address value is in a local variable + NotLoaded(LocalId), +} + /// Helper structure for WasmBackend, to keep track of how values are stored, /// including the VM stack, local variables, and linear memory #[derive(Debug)] @@ -610,7 +617,7 @@ impl<'a> Storage<'a> { &mut self, code_builder: &mut CodeBuilder, to_symbol: Symbol, - from_ptr: LocalId, + from_addr: AddressValue, from_offset: u32, ) -> u32 { let to_storage = self.get(&to_symbol).to_owned(); @@ -625,6 +632,16 @@ impl<'a> Storage<'a> { self.stack_frame_pointer = Some(self.get_next_local_id()); } + let from_ptr = match from_addr { + AddressValue::NotLoaded(ptr) => ptr, + AddressValue::Loaded => { + // The `from` address is on the VM stack but we want it in a local for copying + let tmp_local = self.create_anonymous_local(PTR_TYPE); + code_builder.set_local(tmp_local); + tmp_local + } + }; + let (to_ptr, to_offset) = location.local_and_offset(self.stack_frame_pointer); copy_memory( code_builder, @@ -648,7 +665,10 @@ impl<'a> Storage<'a> { } => { use crate::wasm_module::Align::*; - code_builder.get_local(from_ptr); + if let AddressValue::NotLoaded(from_ptr) = from_addr { + code_builder.get_local(from_ptr); + } + match (value_type, size) { (ValueType::I64, 8) => code_builder.i64_load(Bytes8, from_offset), (ValueType::I32, 4) => code_builder.i32_load(Bytes4, from_offset), diff --git a/crates/compiler/gen_wasm/src/wasm32_result.rs b/crates/compiler/gen_wasm/src/wasm32_result.rs index e9fd6358e0..1863bff7e6 100644 --- a/crates/compiler/gen_wasm/src/wasm32_result.rs +++ b/crates/compiler/gen_wasm/src/wasm32_result.rs @@ -76,7 +76,7 @@ pub fn insert_wrapper_for_layout<'a>( bool::insert_wrapper(arena, module, wrapper_name, main_fn_index); } Layout::Union(UnionLayout::NonRecursive(_)) => stack_data_structure(), - Layout::Union(_) => { + Layout::Union(_) | Layout::Boxed(_) => { i32::insert_wrapper(arena, module, wrapper_name, main_fn_index); } _ => stack_data_structure(), diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index eaabd9b14e..3a89dee23b 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -33,7 +33,7 @@ use roc_mono::ir::{ CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase, UpdateModeIds, }; -use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; +use roc_mono::layout::{CapturesNiche, LambdaName, Layout, LayoutCache, LayoutProblem}; use roc_parse::ast::{self, Defs, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName}; @@ -132,7 +132,7 @@ struct ModuleCache<'a> { typechecked: MutMap>, found_specializations: MutMap>, late_specializations: MutMap>, - external_specializations_requested: MutMap>, + external_specializations_requested: MutMap>>, /// Various information imports: MutMap>, @@ -715,7 +715,7 @@ enum Msg<'a> { module_id: ModuleId, ident_ids: IdentIds, layout_cache: LayoutCache<'a>, - external_specializations_requested: BumpMap, + external_specializations_requested: BumpMap>, procs_base: ProcsBase<'a>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, update_mode_ids: UpdateModeIds, @@ -1007,7 +1007,7 @@ enum BuildTask<'a> { subs: Subs, procs_base: ProcsBase<'a>, layout_cache: LayoutCache<'a>, - specializations_we_must_make: Vec, + specializations_we_must_make: Vec>, module_timing: ModuleTiming, world_abilities: WorldAbilities, derived_symbols: GlobalDerivedSymbols, @@ -2592,9 +2592,11 @@ fn finish_specialization( .into_inner() .into_module_ids(); + let all_ident_ids = state.constrained_ident_ids; + let interns = Interns { module_ids, - all_ident_ids: state.constrained_ident_ids, + all_ident_ids, }; let State { @@ -2660,6 +2662,7 @@ fn finish_specialization( layout: roc_mono::ir::ProcLayout { arguments: &[], result: Layout::struct_no_name_order(&[]), + captures_niche: CapturesNiche::no_niche(), }, symbol, } @@ -4407,7 +4410,7 @@ fn make_specializations<'a>( mut subs: Subs, procs_base: ProcsBase<'a>, mut layout_cache: LayoutCache<'a>, - specializations_we_must_make: Vec, + specializations_we_must_make: Vec>, mut module_timing: ModuleTiming, target_info: TargetInfo, world_abilities: WorldAbilities, @@ -4571,7 +4574,7 @@ fn build_pending_specializations<'a>( procs_base.host_specializations.insert_host_exposed( mono_env.subs, - symbol, + LambdaName::no_niche(symbol), annotation, expr_var, ); @@ -4631,7 +4634,7 @@ fn build_pending_specializations<'a>( procs_base.host_specializations.insert_host_exposed( mono_env.subs, - symbol, + LambdaName::no_niche(symbol), annotation, expr_var, ); @@ -4709,7 +4712,7 @@ fn build_pending_specializations<'a>( procs_base.host_specializations.insert_host_exposed( mono_env.subs, - symbol, + LambdaName::no_niche(symbol), annotation, expr_var, ); @@ -4769,7 +4772,7 @@ fn build_pending_specializations<'a>( procs_base.host_specializations.insert_host_exposed( mono_env.subs, - symbol, + LambdaName::no_niche(symbol), annotation, expr_var, ); diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index 5e85d14ced..d4c39724a1 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -163,7 +163,7 @@ impl<'a> DeclarationToIndex<'a> { } } unreachable!( - "symbol/layout {:?} {:?} combo must be in DeclarationToIndex", + "symbol/layout {:?} {:#?} combo must be in DeclarationToIndex", needle_symbol, needle_layout ) } @@ -263,7 +263,7 @@ impl<'a> ParamMap<'a> { self.declarations[index + i] = param; } - self.visit_stmt(arena, proc.name, &proc.body); + self.visit_stmt(arena, proc.name.name(), &proc.body); } fn visit_proc_always_owned( @@ -282,7 +282,7 @@ impl<'a> ParamMap<'a> { self.declarations[index + i] = param; } - self.visit_stmt(arena, proc.name, &proc.body); + self.visit_stmt(arena, proc.name.name(), &proc.body); } fn visit_stmt(&mut self, arena: &'a Bump, _fnid: Symbol, stmt: &Stmt<'a>) { @@ -501,11 +501,12 @@ impl<'a> BorrowInfState<'a> { arg_layouts, .. } => { - let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout); + let top_level = + ProcLayout::new(self.arena, arg_layouts, name.captures_niche(), **ret_layout); // get the borrow signature of the applied function let ps = param_map - .get_symbol(*name, top_level) + .get_symbol(name.name(), top_level) .expect("function is defined"); // the return value will be owned @@ -544,12 +545,14 @@ impl<'a> BorrowInfState<'a> { let closure_layout = ProcLayout { arguments: passed_function.argument_layouts, result: passed_function.return_layout, + captures_niche: passed_function.name.captures_niche(), }; - let function_ps = match param_map.get_symbol(passed_function.name, closure_layout) { - Some(function_ps) => function_ps, - None => unreachable!(), - }; + let function_ps = + match param_map.get_symbol(passed_function.name.name(), closure_layout) { + Some(function_ps) => function_ps, + None => unreachable!(), + }; match op { ListMap { xs } => { @@ -743,12 +746,13 @@ impl<'a> BorrowInfState<'a> { Stmt::Ret(z), ) = (v, b) { - let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout); + let top_level = + ProcLayout::new(self.arena, arg_layouts, g.captures_niche(), **ret_layout); - if self.current_proc == *g && x == *z { + if self.current_proc == g.name() && x == *z { // anonymous functions (for which the ps may not be known) // can never be tail-recursive, so this is fine - if let Some(ps) = param_map.get_symbol(*g, top_level) { + if let Some(ps) = param_map.get_symbol(g.name(), top_level) { self.own_params_using_args(ys, ps) } } @@ -852,10 +856,10 @@ impl<'a> BorrowInfState<'a> { let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice(); self.update_param_set_symbols(ys); - self.current_proc = proc.name; + self.current_proc = proc.name.name(); // ensure that current_proc is in the owned map - self.owned.entry(proc.name).or_default(); + self.owned.entry(proc.name.name()).or_default(); self.collect_stmt(param_map, &proc.body); self.update_param_map_declaration(param_map, param_offset, proc.args.len()); @@ -978,7 +982,7 @@ fn call_info_call<'a>(call: &crate::ir::Call<'a>, info: &mut CallInfo<'a>) { match call.call_type { ByName { name, .. } => { - info.keys.push(name); + info.keys.push(name.name()); } Foreign { .. } => {} LowLevel { .. } => {} diff --git a/crates/compiler/mono/src/code_gen_help/equality.rs b/crates/compiler/mono/src/code_gen_help/equality.rs index 374daeb6d2..887ecfb947 100644 --- a/crates/compiler/mono/src/code_gen_help/equality.rs +++ b/crates/compiler/mono/src/code_gen_help/equality.rs @@ -530,12 +530,43 @@ fn eq_tag_fields<'a>( } fn eq_boxed<'a>( - _root: &mut CodeGenHelp<'a>, - _ident_ids: &mut IdentIds, - _ctx: &mut Context<'a>, - _inner_layout: &'a Layout<'a>, + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + inner_layout: &'a Layout<'a>, ) -> Stmt<'a> { - todo!() + let a = root.create_symbol(ident_ids, "a"); + let b = root.create_symbol(ident_ids, "b"); + let result = root.create_symbol(ident_ids, "result"); + + let a_expr = Expr::ExprUnbox { symbol: ARG_1 }; + let b_expr = Expr::ExprUnbox { symbol: ARG_2 }; + let eq_call_expr = root + .call_specialized_op(ident_ids, ctx, *inner_layout, root.arena.alloc([a, b])) + .unwrap(); + + Stmt::Let( + a, + a_expr, + *inner_layout, + root.arena.alloc( + // + Stmt::Let( + b, + b_expr, + *inner_layout, + root.arena.alloc( + // + Stmt::Let( + result, + eq_call_expr, + LAYOUT_BOOL, + root.arena.alloc(Stmt::Ret(result)), + ), + ), + ), + ), + ) } /// List equality diff --git a/crates/compiler/mono/src/code_gen_help/mod.rs b/crates/compiler/mono/src/code_gen_help/mod.rs index dadaafd9f1..5ea45d298c 100644 --- a/crates/compiler/mono/src/code_gen_help/mod.rs +++ b/crates/compiler/mono/src/code_gen_help/mod.rs @@ -8,7 +8,7 @@ use crate::ir::{ Call, CallSpecId, CallType, Expr, HostExposedLayouts, JoinPointId, ModifyRc, Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId, }; -use crate::layout::{Builtin, Layout, UnionLayout}; +use crate::layout::{Builtin, CapturesNiche, LambdaName, Layout, UnionLayout}; mod equality; mod refcount; @@ -170,7 +170,7 @@ impl<'a> CodeGenHelp<'a> { let arg_layouts = self.arena.alloc([layout]); let expr = Expr::Call(Call { call_type: CallType::ByName { - name: proc_name, + name: LambdaName::no_niche(proc_name), ret_layout, arg_layouts, specialization_id: CallSpecId::BACKEND_DUMMY, @@ -262,7 +262,7 @@ impl<'a> CodeGenHelp<'a> { Some(Expr::Call(Call { call_type: CallType::ByName { - name: proc_name, + name: LambdaName::no_niche(proc_name), ret_layout, arg_layouts, specialization_id: CallSpecId::BACKEND_DUMMY, @@ -286,11 +286,11 @@ impl<'a> CodeGenHelp<'a> { &mut self, ident_ids: &mut IdentIds, ctx: &mut Context<'a>, - layout: Layout<'a>, + orig_layout: Layout<'a>, ) -> Symbol { use HelperOp::*; - let layout = self.replace_rec_ptr(ctx, layout); + let layout = self.replace_rec_ptr(ctx, orig_layout); let found = self .specializations @@ -343,7 +343,7 @@ impl<'a> CodeGenHelp<'a> { }; self.specializations[spec_index].proc = Some(Proc { - name: proc_symbol, + name: LambdaName::no_niche(proc_symbol), args, body, closure_data_layout: None, @@ -375,19 +375,23 @@ impl<'a> CodeGenHelp<'a> { HelperOp::Inc => ProcLayout { arguments: self.arena.alloc([*layout, self.layout_isize]), result: LAYOUT_UNIT, + captures_niche: CapturesNiche::no_niche(), }, HelperOp::Dec => ProcLayout { arguments: self.arena.alloc([*layout]), result: LAYOUT_UNIT, + captures_niche: CapturesNiche::no_niche(), }, HelperOp::Reset => ProcLayout { arguments: self.arena.alloc([*layout]), result: *layout, + captures_niche: CapturesNiche::no_niche(), }, HelperOp::DecRef(_) => unreachable!("No generated Proc for DecRef"), HelperOp::Eq => ProcLayout { arguments: self.arena.alloc([*layout, *layout]), result: LAYOUT_BOOL, + captures_niche: CapturesNiche::no_niche(), }, }; @@ -450,7 +454,9 @@ impl<'a> CodeGenHelp<'a> { layout } - Layout::Boxed(inner) => self.replace_rec_ptr(ctx, *inner), + Layout::Boxed(inner) => { + Layout::Boxed(self.arena.alloc(self.replace_rec_ptr(ctx, *inner))) + } Layout::LambdaSet(lambda_set) => { self.replace_rec_ptr(ctx, lambda_set.runtime_representation()) diff --git a/crates/compiler/mono/src/code_gen_help/refcount.rs b/crates/compiler/mono/src/code_gen_help/refcount.rs index e3a6282ce0..24f1b9c9ba 100644 --- a/crates/compiler/mono/src/code_gen_help/refcount.rs +++ b/crates/compiler/mono/src/code_gen_help/refcount.rs @@ -129,7 +129,9 @@ pub fn refcount_generic<'a>( Layout::RecursivePointer => unreachable!( "We should never call a refcounting helper on a RecursivePointer layout directly" ), - Layout::Boxed(_) => rc_todo(), + Layout::Boxed(inner_layout) => { + refcount_boxed(root, ident_ids, ctx, &layout, inner_layout, structure) + } } } @@ -343,7 +345,7 @@ pub fn is_rc_implemented_yet(layout: &Layout) -> bool { is_rc_implemented_yet(&lambda_set.runtime_representation()) } Layout::RecursivePointer => true, - Layout::Boxed(_) => false, + Layout::Boxed(_) => true, } } @@ -1465,3 +1467,66 @@ fn refcount_tag_fields<'a>( stmt } + +fn refcount_boxed<'a>( + root: &mut CodeGenHelp<'a>, + ident_ids: &mut IdentIds, + ctx: &mut Context<'a>, + layout: &Layout, + inner_layout: &'a Layout, + outer: Symbol, +) -> Stmt<'a> { + let arena = root.arena; + + // + // modify refcount of the inner and outer structures + // RC on inner first, to avoid use-after-free for Dec + // We're defining statements in reverse, so define outer first + // + + let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); + let alignment = layout.alignment_bytes(root.target_info); + let ret_stmt = rc_return_stmt(root, ident_ids, ctx); + let modify_outer = modify_refcount( + root, + ident_ids, + ctx, + rc_ptr, + alignment, + arena.alloc(ret_stmt), + ); + + let get_rc_and_modify_outer = rc_ptr_from_data_ptr( + root, + ident_ids, + outer, + rc_ptr, + false, + arena.alloc(modify_outer), + ); + + if inner_layout.is_refcounted() && !ctx.op.is_decref() { + let inner = root.create_symbol(ident_ids, "inner"); + let inner_expr = Expr::ExprUnbox { symbol: outer }; + + let mod_inner_unit = root.create_symbol(ident_ids, "mod_inner_unit"); + let mod_inner_args = refcount_args(root, ctx, inner); + let mod_inner_expr = root + .call_specialized_op(ident_ids, ctx, *inner_layout, mod_inner_args) + .unwrap(); + + Stmt::Let( + inner, + inner_expr, + *inner_layout, + arena.alloc(Stmt::Let( + mod_inner_unit, + mod_inner_expr, + LAYOUT_UNIT, + arena.alloc(get_rc_and_modify_outer), + )), + ) + } else { + get_rc_and_modify_outer + } +} diff --git a/crates/compiler/mono/src/inc_dec.rs b/crates/compiler/mono/src/inc_dec.rs index 28e72e006d..c570451131 100644 --- a/crates/compiler/mono/src/inc_dec.rs +++ b/crates/compiler/mono/src/inc_dec.rs @@ -564,12 +564,13 @@ impl<'a> Context<'a> { arg_layouts, .. } => { - let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout); + let top_level = + ProcLayout::new(self.arena, arg_layouts, name.captures_niche(), **ret_layout); // get the borrow signature let ps = self .param_map - .get_symbol(*name, top_level) + .get_symbol(name.name(), top_level) .expect("function is defined"); let v = Expr::Call(crate::ir::Call { @@ -614,11 +615,12 @@ impl<'a> Context<'a> { let function_layout = ProcLayout { arguments: passed_function.argument_layouts, result: passed_function.return_layout, + captures_niche: passed_function.name.captures_niche(), }; let function_ps = match self .param_map - .get_symbol(passed_function.name, function_layout) + .get_symbol(passed_function.name.name(), function_layout) { Some(function_ps) => function_ps, None => unreachable!(), @@ -1406,7 +1408,7 @@ fn visit_proc<'a, 'i>( proc: &mut Proc<'a>, layout: ProcLayout<'a>, ) { - let params = match param_map.get_symbol(proc.name, layout) { + let params = match param_map.get_symbol(proc.name.name(), layout) { Some(slice) => slice, None => Vec::from_iter_in( proc.args.iter().cloned().map(|(layout, symbol)| Param { diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 91612ac787..f38e2c3082 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -1,8 +1,8 @@ #![allow(clippy::manual_map)] use crate::layout::{ - Builtin, ClosureRepresentation, LambdaSet, Layout, LayoutCache, LayoutProblem, - RawFunctionLayout, TagIdIntType, TagOrClosure, UnionLayout, WrappedVariant, + Builtin, CapturesNiche, ClosureRepresentation, LambdaName, LambdaSet, Layout, LayoutCache, + LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant, }; use bumpalo::collections::{CollectIn, Vec}; use bumpalo::Bump; @@ -55,16 +55,16 @@ pub fn pretty_print_ir_symbols() -> bool { roc_error_macros::assert_sizeof_wasm!(Literal, 24); roc_error_macros::assert_sizeof_wasm!(Expr, 48); roc_error_macros::assert_sizeof_wasm!(Stmt, 120); -roc_error_macros::assert_sizeof_wasm!(ProcLayout, 32); -roc_error_macros::assert_sizeof_wasm!(Call, 36); -roc_error_macros::assert_sizeof_wasm!(CallType, 28); +roc_error_macros::assert_sizeof_wasm!(ProcLayout, 40); +roc_error_macros::assert_sizeof_wasm!(Call, 44); +roc_error_macros::assert_sizeof_wasm!(CallType, 36); roc_error_macros::assert_sizeof_non_wasm!(Literal, 3 * 8); roc_error_macros::assert_sizeof_non_wasm!(Expr, 10 * 8); roc_error_macros::assert_sizeof_non_wasm!(Stmt, 19 * 8); -roc_error_macros::assert_sizeof_non_wasm!(ProcLayout, 6 * 8); -roc_error_macros::assert_sizeof_non_wasm!(Call, 7 * 8); -roc_error_macros::assert_sizeof_non_wasm!(CallType, 5 * 8); +roc_error_macros::assert_sizeof_non_wasm!(ProcLayout, 8 * 8); +roc_error_macros::assert_sizeof_non_wasm!(Call, 9 * 8); +roc_error_macros::assert_sizeof_non_wasm!(CallType, 7 * 8); macro_rules! return_on_layout_error { ($env:expr, $layout_result:expr) => { @@ -305,7 +305,7 @@ impl<'a> Default for CapturedSymbols<'a> { #[derive(Clone, Debug, PartialEq)] pub struct Proc<'a> { - pub name: Symbol, + pub name: LambdaName<'a>, pub args: &'a [(Layout<'a>, Symbol)], pub body: Stmt<'a>, pub closure_data_layout: Option>, @@ -352,12 +352,12 @@ impl<'a> Proc<'a> { if pretty_print_ir_symbols() { alloc .text("procedure : ") - .append(symbol_to_doc(alloc, self.name)) + .append(symbol_to_doc(alloc, self.name.name())) .append(" ") .append(self.ret_layout.to_doc(alloc, Parens::NotNeeded)) .append(alloc.hardline()) .append(alloc.text("procedure = ")) - .append(symbol_to_doc(alloc, self.name)) + .append(symbol_to_doc(alloc, self.name.name())) .append(" (") .append(alloc.intersperse(args_doc, ", ")) .append("):") @@ -366,7 +366,7 @@ impl<'a> Proc<'a> { } else { alloc .text("procedure ") - .append(symbol_to_doc(alloc, self.name)) + .append(symbol_to_doc(alloc, self.name.name())) .append(" (") .append(alloc.intersperse(args_doc, ", ")) .append("):") @@ -455,10 +455,11 @@ impl<'a> Proc<'a> { /// A host-exposed function must be specialized; it's a seed for subsequent specializations #[derive(Clone, Debug)] -pub struct HostSpecializations { +pub struct HostSpecializations<'a> { /// Not a bumpalo vec because bumpalo is not thread safe /// Separate array so we can search for membership quickly - symbols: std::vec::Vec, + /// If it's a value and not a lambda, the value is recorded as LambdaName::no_niche. + symbol_or_lambdas: std::vec::Vec>, storage_subs: StorageSubs, /// For each symbol, what types to specialize it for, points into the storage_subs types_to_specialize: std::vec::Vec, @@ -466,16 +467,16 @@ pub struct HostSpecializations { exposed_aliases: std::vec::Vec>, } -impl Default for HostSpecializations { +impl Default for HostSpecializations<'_> { fn default() -> Self { Self::new() } } -impl HostSpecializations { +impl<'a> HostSpecializations<'a> { pub fn new() -> Self { Self { - symbols: std::vec::Vec::new(), + symbol_or_lambdas: std::vec::Vec::new(), storage_subs: StorageSubs::new(Subs::default()), types_to_specialize: std::vec::Vec::new(), exposed_aliases: std::vec::Vec::new(), @@ -485,7 +486,7 @@ impl HostSpecializations { pub fn insert_host_exposed( &mut self, env_subs: &mut Subs, - symbol: Symbol, + symbol_or_lambda: LambdaName<'a>, opt_annotation: Option, variable: Variable, ) { @@ -497,9 +498,13 @@ impl HostSpecializations { host_exposed_aliases.extend(annotation.introduced_variables.host_exposed_aliases); } - match self.symbols.iter().position(|s| *s == symbol) { + match self + .symbol_or_lambdas + .iter() + .position(|s| *s == symbol_or_lambda) + { None => { - self.symbols.push(symbol); + self.symbol_or_lambdas.push(symbol_or_lambda); self.types_to_specialize.push(variable); self.exposed_aliases.push(host_exposed_aliases); } @@ -518,9 +523,9 @@ impl HostSpecializations { self, ) -> ( StorageSubs, - impl Iterator)>, + impl Iterator, Variable, std::vec::Vec<(Symbol, Variable)>)>, ) { - let it1 = self.symbols.into_iter(); + let it1 = self.symbol_or_lambdas.into_iter(); let it2 = self.types_to_specialize.into_iter(); let it3 = self.exposed_aliases.into_iter(); @@ -534,36 +539,46 @@ impl HostSpecializations { /// Specializations of this module's symbols that other modules need #[derive(Clone, Debug)] -pub struct ExternalSpecializations { +pub struct ExternalSpecializations<'a> { /// Not a bumpalo vec because bumpalo is not thread safe /// Separate array so we can search for membership quickly - pub symbols: std::vec::Vec, + /// If it's a value and not a lambda, the value is recorded as LambdaName::no_niche. + pub symbol_or_lambda: std::vec::Vec>, storage_subs: StorageSubs, /// For each symbol, what types to specialize it for, points into the storage_subs types_to_specialize: std::vec::Vec>, } -impl Default for ExternalSpecializations { +impl Default for ExternalSpecializations<'_> { fn default() -> Self { Self::new() } } -impl ExternalSpecializations { +impl<'a> ExternalSpecializations<'a> { pub fn new() -> Self { Self { - symbols: std::vec::Vec::new(), + symbol_or_lambda: std::vec::Vec::new(), storage_subs: StorageSubs::new(Subs::default()), types_to_specialize: std::vec::Vec::new(), } } - fn insert_external(&mut self, symbol: Symbol, env_subs: &mut Subs, variable: Variable) { + fn insert_external( + &mut self, + symbol_or_lambda: LambdaName<'a>, + env_subs: &mut Subs, + variable: Variable, + ) { let variable = self.storage_subs.extend_with_variable(env_subs, variable); - match self.symbols.iter().position(|s| *s == symbol) { + match self + .symbol_or_lambda + .iter() + .position(|s| *s == symbol_or_lambda) + { None => { - self.symbols.push(symbol); + self.symbol_or_lambda.push(symbol_or_lambda); self.types_to_specialize.push(vec![variable]); } Some(index) => { @@ -577,11 +592,11 @@ impl ExternalSpecializations { self, ) -> ( StorageSubs, - impl Iterator)>, + impl Iterator, std::vec::Vec)>, ) { ( self.storage_subs, - self.symbols + self.symbol_or_lambda .into_iter() .zip(self.types_to_specialize.into_iter()), ) @@ -591,7 +606,8 @@ impl ExternalSpecializations { #[derive(Clone, Debug)] pub struct Suspended<'a> { pub store: StorageSubs, - pub symbols: Vec<'a, Symbol>, + /// LambdaName::no_niche if it's a value + pub symbol_or_lambdas: Vec<'a, LambdaName<'a>>, pub layouts: Vec<'a, ProcLayout<'a>>, pub variables: Vec<'a, Variable>, } @@ -600,7 +616,7 @@ impl<'a> Suspended<'a> { fn new_in(arena: &'a Bump) -> Self { Self { store: StorageSubs::new(Subs::new_from_varstore(Default::default())), - symbols: Vec::new_in(arena), + symbol_or_lambdas: Vec::new_in(arena), layouts: Vec::new_in(arena), variables: Vec::new_in(arena), } @@ -609,13 +625,13 @@ impl<'a> Suspended<'a> { fn specialization( &mut self, subs: &mut Subs, - symbol: Symbol, + symbol_or_lambda: LambdaName<'a>, proc_layout: ProcLayout<'a>, variable: Variable, ) { // de-duplicate - for (i, s) in self.symbols.iter().enumerate() { - if *s == symbol { + for (i, s) in self.symbol_or_lambdas.iter().enumerate() { + if *s == symbol_or_lambda { let existing = &self.layouts[i]; if &proc_layout == existing { // symbol + layout combo exists @@ -624,7 +640,7 @@ impl<'a> Suspended<'a> { } } - self.symbols.push(symbol); + self.symbol_or_lambdas.push(symbol_or_lambda); self.layouts.push(proc_layout); let variable = self.store.extend_with_variable(subs, variable); @@ -670,7 +686,9 @@ impl<'a> Specialized<'a> { None } else { match in_progress { - InProgressProc::InProgress => panic!("Function is not done specializing"), + InProgressProc::InProgress => { + panic!("Function {:?} ({:?}) is not done specializing", s, l) + } InProgressProc::Done(proc) => Some((s, l, proc)), } } @@ -907,7 +925,7 @@ pub struct ProcsBase<'a> { pub partial_procs: BumpMap>, pub module_thunks: &'a [Symbol], /// A host-exposed function must be specialized; it's a seed for subsequent specializations - pub host_specializations: HostSpecializations, + pub host_specializations: HostSpecializations<'a>, pub runtime_errors: BumpMap, pub imported_module_thunks: &'a [Symbol], } @@ -921,7 +939,7 @@ pub struct Procs<'a> { pending_specializations: PendingSpecializations<'a>, specialized: Specialized<'a>, pub runtime_errors: BumpMap, - pub externals_we_need: BumpMap, + pub externals_we_need: BumpMap>, symbol_specializations: SymbolSpecializations<'a>, } @@ -991,7 +1009,7 @@ impl<'a> Procs<'a> { fn insert_anonymous( &mut self, env: &mut Env<'a, '_>, - symbol: Symbol, + name: LambdaName<'a>, annotation: Variable, loc_args: std::vec::Vec<(Variable, AnnotatedMark, Loc)>, loc_body: Loc, @@ -1003,12 +1021,12 @@ impl<'a> Procs<'a> { .raw_from_var(env.arena, annotation, env.subs) .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); - let top_level = ProcLayout::from_raw(env.arena, raw_layout); + let top_level = ProcLayout::from_raw(env.arena, raw_layout, name.captures_niche()); // anonymous functions cannot reference themselves, therefore cannot be tail-recursive // EXCEPT when the closure conversion makes it tail-recursive. let is_self_recursive = match top_level.arguments.last() { - Some(Layout::LambdaSet(lambda_set)) => lambda_set.contains(symbol), + Some(Layout::LambdaSet(lambda_set)) => lambda_set.contains(name.name()), _ => false, }; @@ -1018,22 +1036,22 @@ impl<'a> Procs<'a> { // by the surrounding context, so we can add pending specializations // for them immediately. - let already_specialized = self.specialized.is_specialized(symbol, &top_level); + let already_specialized = self.specialized.is_specialized(name.name(), &top_level); let layout = top_level; // if we've already specialized this one, no further work is needed. if !already_specialized { - if self.is_module_thunk(symbol) { + if self.is_module_thunk(name.name()) { debug_assert!(layout.arguments.is_empty()); } match &mut self.pending_specializations { PendingSpecializations::Finding(suspended) => { // register the pending specialization, so this gets code genned later - suspended.specialization(env.subs, symbol, layout, annotation); + suspended.specialization(env.subs, name, layout, annotation); - match self.partial_procs.symbol_to_id(symbol) { + match self.partial_procs.symbol_to_id(name.name()) { Some(occupied) => { let existing = self.partial_procs.get_id(occupied); // if we're adding the same partial proc twice, they must be the actual same! @@ -1059,7 +1077,7 @@ impl<'a> Procs<'a> { is_self_recursive, }; - self.partial_procs.insert(symbol, partial_proc); + self.partial_procs.insert(name.name(), partial_proc); } } } @@ -1067,12 +1085,12 @@ impl<'a> Procs<'a> { // Mark this proc as in-progress, so if we're dealing with // mutually recursive functions, we don't loop forever. // (We had a bug around this before this system existed!) - self.specialized.mark_in_progress(symbol, layout); + self.specialized.mark_in_progress(name.name(), layout); let outside_layout = layout; let partial_proc_id = if let Some(partial_proc_id) = - self.partial_procs.symbol_to_id(symbol) + self.partial_procs.symbol_to_id(name.name()) { let existing = self.partial_procs.get_id(partial_proc_id); // if we're adding the same partial proc twice, they must be the actual same! @@ -1097,20 +1115,24 @@ impl<'a> Procs<'a> { is_self_recursive, }; - self.partial_procs.insert(symbol, partial_proc) + self.partial_procs.insert(name.name(), partial_proc) }; match specialize_variable( env, self, - symbol, + name, layout_cache, annotation, &[], partial_proc_id, ) { Ok((proc, layout)) => { - let top_level = ProcLayout::from_raw(env.arena, layout); + let top_level = ProcLayout::from_raw( + env.arena, + layout, + proc.name.captures_niche(), + ); debug_assert_eq!( outside_layout, top_level, @@ -1118,11 +1140,15 @@ impl<'a> Procs<'a> { proc.name ); - if self.is_module_thunk(proc.name) { + if self.is_module_thunk(proc.name.name()) { debug_assert!(top_level.arguments.is_empty()); } - self.specialized.insert_specialized(symbol, top_level, proc); + self.specialized.insert_specialized( + name.name(), + top_level, + proc, + ); } Err(error) => { panic!("TODO generate a RuntimeError message for {:?}", error); @@ -1142,23 +1168,23 @@ impl<'a> Procs<'a> { &mut self, env: &mut Env<'a, '_>, fn_var: Variable, - name: Symbol, + name: LambdaName<'a>, layout: ProcLayout<'a>, layout_cache: &mut LayoutCache<'a>, ) { // If we've already specialized this one, no further work is needed. - if self.specialized.is_specialized(name, &layout) { + if self.specialized.is_specialized(name.name(), &layout) { return; } // If this is an imported symbol, let its home module make this specialization - if env.is_imported_symbol(name) { + if env.is_imported_symbol(name.name()) { add_needed_external(self, env, fn_var, name); return; } // register the pending specialization, so this gets code genned later - if self.module_thunks.contains(&name) { + if self.module_thunks.contains(&name.name()) { debug_assert!(layout.arguments.is_empty()); } @@ -1171,7 +1197,7 @@ impl<'a> Procs<'a> { PendingSpecializations::Making => { let symbol = name; - let partial_proc_id = match self.partial_procs.symbol_to_id(symbol) { + let partial_proc_id = match self.partial_procs.symbol_to_id(symbol.name()) { Some(p) => p, None => panic!("no partial_proc for {:?} in module {:?}", symbol, env.home), }; @@ -1179,7 +1205,7 @@ impl<'a> Procs<'a> { // Mark this proc as in-progress, so if we're dealing with // mutually recursive functions, we don't loop forever. // (We had a bug around this before this system existed!) - self.specialized.mark_in_progress(symbol, layout); + self.specialized.mark_in_progress(symbol.name(), layout); // See https://github.com/rtfeldman/roc/issues/1600 // @@ -1212,14 +1238,15 @@ impl<'a> Procs<'a> { let proper_layout = ProcLayout { arguments, result: proc.ret_layout, + captures_niche: proc.name.captures_niche(), }; // NOTE: some function are specialized to have a closure, but don't actually // need any closure argument. Here is where we correct this sort of thing, // by trusting the layout of the Proc, not of what we specialize for - self.specialized.remove_specialized(symbol, &layout); + self.specialized.remove_specialized(symbol.name(), &layout); self.specialized - .insert_specialized(symbol, proper_layout, proc); + .insert_specialized(symbol.name(), proper_layout, proc); } Err(error) => { panic!("TODO generate a RuntimeError message for {:?}", error); @@ -1547,7 +1574,7 @@ impl<'a> Call<'a> { match self.call_type { CallType::ByName { name, .. } => { - let it = std::iter::once(name) + let it = std::iter::once(name.name()) .chain(arguments.iter().copied()) .map(|s| symbol_to_doc(alloc, s)); @@ -1630,7 +1657,7 @@ impl UpdateModeIds { #[derive(Clone, Debug, PartialEq)] pub enum CallType<'a> { ByName { - name: Symbol, + name: LambdaName<'a>, ret_layout: &'a Layout<'a>, arg_layouts: &'a [Layout<'a>], specialization_id: CallSpecId, @@ -1650,7 +1677,7 @@ pub enum CallType<'a> { pub struct PassedFunction<'a> { /// name of the top-level function that is passed as an argument /// e.g. in `List.map xs Num.abs` this would be `Num.abs` - pub name: Symbol, + pub name: LambdaName<'a>, pub argument_layouts: &'a [Layout<'a>], pub return_layout: Layout<'a>, @@ -1686,7 +1713,6 @@ pub enum Expr<'a> { Tag { tag_layout: UnionLayout<'a>, - tag_name: TagOrClosure, tag_id: TagIdIntType, arguments: &'a [Symbol], }, @@ -1730,7 +1756,6 @@ pub enum Expr<'a> { update_mode: UpdateModeId, // normal Tag fields tag_layout: UnionLayout<'a>, - tag_name: TagOrClosure, tag_id: TagIdIntType, arguments: &'a [Symbol], }, @@ -1813,17 +1838,12 @@ impl<'a> Expr<'a> { Call(call) => call.to_doc(alloc), Tag { - tag_name, - arguments, - .. + tag_id, arguments, .. } => { - let doc_tag = match tag_name { - TagOrClosure::Tag(TagName(s)) => alloc.text(s.as_str()), - TagOrClosure::Closure(s) => alloc - .text("ClosureTag(") - .append(symbol_to_doc(alloc, *s)) - .append(")"), - }; + let doc_tag = alloc + .text("TagId(") + .append(alloc.text(tag_id.to_string())) + .append(")"); let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s)); @@ -1833,18 +1853,15 @@ impl<'a> Expr<'a> { } Reuse { symbol, - tag_name, + tag_id, arguments, update_mode, .. } => { - let doc_tag = match tag_name { - TagOrClosure::Tag(TagName(s)) => alloc.text(s.as_str()), - TagOrClosure::Closure(s) => alloc - .text("ClosureTag(") - .append(symbol_to_doc(alloc, *s)) - .append(")"), - }; + let doc_tag = alloc + .text("TagId(") + .append(alloc.text(tag_id.to_string())) + .append(")"); let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s)); @@ -2566,28 +2583,33 @@ fn specialize_suspended<'a>( ) { let offset_variable = StorageSubs::merge_into(suspended.store, env.subs); - for (i, (symbol, var)) in suspended - .symbols + for (i, (symbol_or_lambda, var)) in suspended + .symbol_or_lambdas .iter() .zip(suspended.variables.iter()) .enumerate() { - let name = *symbol; + let name = *symbol_or_lambda; let outside_layout = suspended.layouts[i]; let var = offset_variable(*var); // TODO define our own Entry for Specialized? - let partial_proc = if procs.specialized.is_specialized(name, &outside_layout) { + let partial_proc = if procs + .specialized + .is_specialized(name.name(), &outside_layout) + { // already specialized, just continue continue; } else { - match procs.partial_procs.symbol_to_id(name) { + match procs.partial_procs.symbol_to_id(name.name()) { Some(v) => { // Mark this proc as in-progress, so if we're dealing with // mutually recursive functions, we don't loop forever. // (We had a bug around this before this system existed!) - procs.specialized.mark_in_progress(name, outside_layout); + procs + .specialized + .mark_in_progress(name.name(), outside_layout); v } @@ -2602,9 +2624,9 @@ fn specialize_suspended<'a>( match specialize_variable(env, procs, name, layout_cache, var, &[], partial_proc) { Ok((proc, layout)) => { // TODO thiscode is duplicated elsewhere - let top_level = ProcLayout::from_raw(env.arena, layout); + let top_level = ProcLayout::from_raw(env.arena, layout, proc.name.captures_niche()); - if procs.is_module_thunk(proc.name) { + if procs.is_module_thunk(proc.name.name()) { debug_assert!( top_level.arguments.is_empty(), "{:?} from {:?}", @@ -2614,16 +2636,21 @@ fn specialize_suspended<'a>( } debug_assert_eq!(outside_layout, top_level, " in {:?}", name); - procs.specialized.insert_specialized(name, top_level, proc); + procs + .specialized + .insert_specialized(name.name(), top_level, proc); } Err(SpecializeFailure { attempted_layout, .. }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); + let proc = generate_runtime_error_function(env, name.name(), attempted_layout); - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + let top_level = + ProcLayout::from_raw(env.arena, attempted_layout, CapturesNiche::no_niche()); - procs.specialized.insert_specialized(name, top_level, proc); + procs + .specialized + .insert_specialized(name.name(), top_level, proc); } } } @@ -2632,8 +2659,8 @@ fn specialize_suspended<'a>( pub fn specialize_all<'a>( env: &mut Env<'a, '_>, mut procs: Procs<'a>, - externals_others_need: std::vec::Vec, - specializations_for_host: HostSpecializations, + externals_others_need: std::vec::Vec>, + specializations_for_host: HostSpecializations<'a>, layout_cache: &mut LayoutCache<'a>, ) -> Procs<'a> { for externals in externals_others_need { @@ -2669,7 +2696,7 @@ fn specialize_host_specializations<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, - host_specializations: HostSpecializations, + host_specializations: HostSpecializations<'a>, ) { let (store, it) = host_specializations.decompose(); @@ -2691,7 +2718,7 @@ fn specialize_external_specializations<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, - externals_others_need: ExternalSpecializations, + externals_others_need: ExternalSpecializations<'a>, ) { let (store, it) = externals_others_need.decompose(); @@ -2720,11 +2747,11 @@ fn specialize_external_help<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, - name: Symbol, + name: LambdaName<'a>, variable: Variable, host_exposed_aliases: &[(Symbol, Variable)], ) { - let partial_proc_id = match procs.partial_procs.symbol_to_id(name) { + let partial_proc_id = match procs.partial_procs.symbol_to_id(name.name()) { Some(v) => v, None => { panic!("Cannot find a partial proc for {:?}", name); @@ -2743,20 +2770,25 @@ fn specialize_external_help<'a>( match specialization_result { Ok((proc, layout)) => { - let top_level = ProcLayout::from_raw(env.arena, layout); + let top_level = ProcLayout::from_raw(env.arena, layout, proc.name.captures_niche()); - if procs.is_module_thunk(name) { + if procs.is_module_thunk(name.name()) { debug_assert!(top_level.arguments.is_empty()); } - procs.specialized.insert_specialized(name, top_level, proc); + procs + .specialized + .insert_specialized(name.name(), top_level, proc); } Err(SpecializeFailure { attempted_layout }) => { - let proc = generate_runtime_error_function(env, name, attempted_layout); + let proc = generate_runtime_error_function(env, name.name(), attempted_layout); - let top_level = ProcLayout::from_raw(env.arena, attempted_layout); + let top_level = + ProcLayout::from_raw(env.arena, attempted_layout, proc.name.captures_niche()); - procs.specialized.insert_specialized(name, top_level, proc); + procs + .specialized + .insert_specialized(name.name(), top_level, proc); } } } @@ -2798,7 +2830,7 @@ fn generate_runtime_error_function<'a>( }; Proc { - name, + name: LambdaName::no_niche(name), args, body: runtime_error, closure_data_layout: None, @@ -2812,7 +2844,7 @@ fn generate_runtime_error_function<'a>( fn specialize_external<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - proc_name: Symbol, + lambda_name: LambdaName<'a>, layout_cache: &mut LayoutCache<'a>, fn_var: Variable, host_exposed_variables: &[(Symbol, Variable)], @@ -2844,7 +2876,7 @@ fn specialize_external<'a>( }; let specialized = - build_specialized_proc_from_var(env, layout_cache, proc_name, pattern_symbols, fn_var)?; + build_specialized_proc_from_var(env, layout_cache, lambda_name, pattern_symbols, fn_var)?; // determine the layout of aliases/rigids exposed to the host let host_exposed_layouts = if host_exposed_variables.is_empty() { @@ -2900,7 +2932,7 @@ fn specialize_external<'a>( ); let proc = Proc { - name, + name: LambdaName::no_niche(name), args: proc_arguments.into_bump_slice(), body, closure_data_layout: None, @@ -2913,6 +2945,7 @@ fn specialize_external<'a>( let top_level = ProcLayout::new( env.arena, top_level_arguments.into_bump_slice(), + CapturesNiche::no_niche(), *return_layout, ); @@ -2968,7 +3001,7 @@ fn specialize_external<'a>( debug_assert!(matches!(captured_symbols, CapturedSymbols::None)); let proc = Proc { - name: proc_name, + name: lambda_name, args: &[], body: specialized_body, closure_data_layout: Some(closure_data_layout), @@ -3000,7 +3033,7 @@ fn specialize_external<'a>( .unwrap_or(symbol) }; - match closure_layout.layout_for_member(proc_name) { + match closure_layout.layout_for_member_with_lambda_name(lambda_name) { ClosureRepresentation::Union { alphabetic_order_fields: field_layouts, union_layout, @@ -3065,7 +3098,7 @@ fn specialize_external<'a>( captured.len(), field_layouts.len(), "{:?} captures {:?} but has layout {:?}", - proc_name, + lambda_name, &captured, &field_layouts ); @@ -3142,7 +3175,7 @@ fn specialize_external<'a>( }; let proc = Proc { - name: proc_name, + name: lambda_name, args: proc_args.into_bump_slice(), body: specialized_body, closure_data_layout, @@ -3176,7 +3209,7 @@ enum SpecializedLayout<'a> { fn build_specialized_proc_from_var<'a>( env: &mut Env<'a, '_>, layout_cache: &mut LayoutCache<'a>, - proc_name: Symbol, + lambda_name: LambdaName<'a>, pattern_symbols: &[Symbol], fn_var: Variable, ) -> Result, LayoutProblem> { @@ -3187,7 +3220,7 @@ fn build_specialized_proc_from_var<'a>( build_specialized_proc( env.arena, - proc_name, + lambda_name, pattern_symbols, pattern_layouts_vec, Some(closure_layout), @@ -3198,7 +3231,7 @@ fn build_specialized_proc_from_var<'a>( // a top-level constant 0-argument thunk build_specialized_proc( env.arena, - proc_name, + lambda_name, pattern_symbols, Vec::new_in(env.arena), None, @@ -3211,7 +3244,7 @@ fn build_specialized_proc_from_var<'a>( #[allow(clippy::type_complexity)] fn build_specialized_proc<'a>( arena: &'a Bump, - proc_name: Symbol, + lambda_name: LambdaName<'a>, pattern_symbols: &[Symbol], pattern_layouts: Vec<'a, Layout<'a>>, lambda_set: Option>, @@ -3245,6 +3278,7 @@ fn build_specialized_proc<'a>( // // then + let proc_name = lambda_name.name(); match lambda_set { Some(lambda_set) if pattern_symbols.last() == Some(&Symbol::ARG_CLOSURE) => { // here we define the lifted (now top-level) f function. Its final argument is `Symbol::ARG_CLOSURE`, @@ -3359,7 +3393,7 @@ type SpecializeSuccess<'a> = (Proc<'a>, RawFunctionLayout<'a>); fn specialize_variable<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - proc_name: Symbol, + proc_name: LambdaName<'a>, layout_cache: &mut LayoutCache<'a>, fn_var: Variable, host_exposed_aliases: &[(Symbol, Variable)], @@ -3379,7 +3413,7 @@ fn specialize_variable<'a>( fn specialize_variable_help<'a, F>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - proc_name: Symbol, + proc_name: LambdaName<'a>, layout_cache: &mut LayoutCache<'a>, fn_var_thunk: F, host_exposed_variables: &[(Symbol, Variable)], @@ -3401,7 +3435,7 @@ where .raw_from_var(env.arena, fn_var, env.subs) .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); - let raw = if procs.is_module_thunk(proc_name) { + let raw = if procs.is_module_thunk(proc_name.name()) { match raw { RawFunctionLayout::Function(_, lambda_set, _) => { RawFunctionLayout::ZeroArgumentThunk(Layout::LambdaSet(lambda_set)) @@ -3460,10 +3494,16 @@ where pub struct ProcLayout<'a> { pub arguments: &'a [Layout<'a>], pub result: Layout<'a>, + pub captures_niche: CapturesNiche<'a>, } impl<'a> ProcLayout<'a> { - pub fn new(arena: &'a Bump, old_arguments: &'a [Layout<'a>], result: Layout<'a>) -> Self { + pub fn new( + arena: &'a Bump, + old_arguments: &'a [Layout<'a>], + old_captures_niche: CapturesNiche<'a>, + result: Layout<'a>, + ) -> Self { let mut arguments = Vec::with_capacity_in(old_arguments.len(), arena); for old in old_arguments { @@ -3476,17 +3516,24 @@ impl<'a> ProcLayout<'a> { ProcLayout { arguments: arguments.into_bump_slice(), + captures_niche: old_captures_niche, result: new_result, } } - pub fn from_raw(arena: &'a Bump, raw: RawFunctionLayout<'a>) -> Self { + pub fn from_raw( + arena: &'a Bump, + raw: RawFunctionLayout<'a>, + captures_niche: CapturesNiche<'a>, + ) -> Self { match raw { RawFunctionLayout::Function(arguments, lambda_set, result) => { let arguments = lambda_set.extend_argument_list(arena, arguments); - ProcLayout::new(arena, arguments, *result) + ProcLayout::new(arena, arguments, captures_niche, *result) + } + RawFunctionLayout::ZeroArgumentThunk(result) => { + ProcLayout::new(arena, &[], CapturesNiche::no_niche(), result) } - RawFunctionLayout::ZeroArgumentThunk(result) => ProcLayout::new(arena, &[], result), } } } @@ -3910,7 +3957,7 @@ pub fn with_hole<'a>( #[allow(clippy::enum_variant_names)] enum Field { // TODO: rename this since it can handle unspecialized expressions now too - Function(Symbol, Variable), + FunctionOrUnspecialized(Symbol, Variable), ValueSymbol, Field(roc_can::expr::Field), } @@ -3925,7 +3972,7 @@ pub fn with_hole<'a>( | LocalFunction(symbol) | UnspecializedExpr(symbol) => { field_symbols.push(symbol); - can_fields.push(Field::Function(symbol, variable)); + can_fields.push(Field::FunctionOrUnspecialized(symbol, variable)); } Value(symbol) => { let reusable = procs.symbol_specializations.get_or_insert( @@ -3971,7 +4018,7 @@ pub fn with_hole<'a>( Field::ValueSymbol => { // this symbol is already defined; nothing to do } - Field::Function(symbol, variable) => { + Field::FunctionOrUnspecialized(symbol, variable) => { stmt = specialize_symbol( env, procs, @@ -4376,7 +4423,7 @@ pub fn with_hole<'a>( match procs.insert_anonymous( env, - name, + LambdaName::no_niche(name), function_type, arguments, *loc_body, @@ -4387,12 +4434,21 @@ pub fn with_hole<'a>( Ok(_) => { let raw_layout = return_on_layout_error!( env, - layout_cache.raw_from_var(env.arena, function_type, env.subs) + layout_cache.raw_from_var(env.arena, function_type, env.subs,) ); match raw_layout { RawFunctionLayout::Function(_, lambda_set, _) => { - construct_closure_data(env, lambda_set, name, &[], assigned, hole) + let lambda_name = + find_lambda_name(env, layout_cache, lambda_set, name, &[]); + construct_closure_data( + env, + lambda_set, + lambda_name, + &[], + assigned, + hole, + ) } RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(), } @@ -4589,9 +4645,20 @@ pub fn with_hole<'a>( captured_symbols.sort(); let captured_symbols = captured_symbols.into_bump_slice(); + let symbols = + Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice(); + + let lambda_name = find_lambda_name( + env, + layout_cache, + lambda_set, + name, + symbols.iter().copied(), + ); + let inserted = procs.insert_anonymous( env, - name, + lambda_name, function_type, arguments, loc_body, @@ -4613,13 +4680,10 @@ pub fn with_hole<'a>( // define the closure data - let symbols = - Vec::from_iter_in(captured_symbols.iter(), env.arena).into_bump_slice(); - construct_closure_data( env, lambda_set, - name, + lambda_name, symbols.iter().copied(), assigned, hole, @@ -4704,7 +4768,7 @@ pub fn with_hole<'a>( let full_layout = return_on_layout_error!( env, - layout_cache.raw_from_var(env.arena, fn_var, env.subs) + layout_cache.raw_from_var(env.arena, fn_var, env.subs,) ); // if the function expression (loc_expr) is already a symbol, @@ -4718,7 +4782,12 @@ pub fn with_hole<'a>( Imported(thunk_name) => { debug_assert!(procs.is_imported_module_thunk(thunk_name)); - add_needed_external(procs, env, fn_var, thunk_name); + add_needed_external( + procs, + env, + fn_var, + LambdaName::no_niche(thunk_name), + ); let function_symbol = env.unique_symbol(); @@ -4883,7 +4952,7 @@ pub fn with_hole<'a>( // layout of the return type let layout = - return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs)); + return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs,)); let call = self::Call { call_type: CallType::Foreign { @@ -4919,7 +4988,7 @@ pub fn with_hole<'a>( // layout of the return type let layout = - return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs)); + return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs,)); macro_rules! match_on_closure_argument { ( $ho:ident, [$($x:ident),* $(,)?]) => {{ @@ -4932,7 +5001,9 @@ pub fn with_hole<'a>( layout_cache.raw_from_var(env.arena, closure_data_var, env.subs) ); - let top_level = ProcLayout::from_raw(env.arena, closure_data_layout); + // NB: I don't think the top_level here can have a captures niche? + let top_level_capture_niche = CapturesNiche::no_niche(); + let top_level = ProcLayout::from_raw(env.arena, closure_data_layout, top_level_capture_niche); let arena = env.arena; @@ -4966,7 +5037,7 @@ pub fn with_hole<'a>( self::Call { call_type: CallType::HigherOrder(arena.alloc(higher_order)), - arguments: arena.alloc([$($x,)* top_level_function, closure_data]), + arguments: arena.alloc([$($x,)* top_level_function.name(), closure_data]), } }, layout, @@ -5163,12 +5234,32 @@ fn late_resolve_ability_specialization<'a>( } } +fn find_lambda_name<'a, I>( + env: &mut Env<'a, '_>, + layout_cache: &mut LayoutCache<'a>, + lambda_set: LambdaSet<'a>, + function_name: Symbol, + captures: I, +) -> LambdaName<'a> +where + I: IntoIterator, +{ + let this_function_captures_layouts = captures + .into_iter() + .map(|(_, var)| { + layout_cache + .from_var(env.arena, *var, env.subs) + .expect("layout problem for capture") + }) + .collect_in::>(env.arena); + lambda_set.find_lambda_name(function_name, &this_function_captures_layouts) +} + #[allow(clippy::too_many_arguments)] fn construct_closure_data<'a, I>( env: &mut Env<'a, '_>, - // procs: &mut Procs<'a>, lambda_set: LambdaSet<'a>, - name: Symbol, + name: LambdaName<'a>, symbols: I, assigned: Symbol, hole: &'a Stmt<'a>, @@ -5180,12 +5271,12 @@ where let lambda_set_layout = Layout::LambdaSet(lambda_set); let symbols = symbols.into_iter(); - let result = match lambda_set.layout_for_member(name) { + let result = match lambda_set.layout_for_member_with_lambda_name(name) { ClosureRepresentation::Union { tag_id, alphabetic_order_fields: field_layouts, - closure_name: tag_name, union_layout, + closure_name: _, } => { // captured variables are in symbol-alphabetic order, but now we want // them ordered by their alignment requirements @@ -5209,7 +5300,6 @@ where let expr = Expr::Tag { tag_id, tag_layout: union_layout, - tag_name: tag_name.into(), arguments: symbols, }; @@ -5252,7 +5342,7 @@ where debug_assert_eq!(symbols.len(), 0); debug_assert_eq!(lambda_set.set.len(), 2); - let tag_id = name != lambda_set.set[0].0; + let tag_id = name.name() != lambda_set.iter_set().next().unwrap().name(); let expr = Expr::Literal(Literal::Bool(tag_id)); Stmt::Let(assigned, expr, lambda_set_layout, hole) @@ -5261,7 +5351,11 @@ where debug_assert_eq!(symbols.len(), 0); debug_assert!(lambda_set.set.len() > 2); - let tag_id = lambda_set.set.iter().position(|(s, _)| *s == name).unwrap() as u8; + let tag_id = lambda_set + .iter_set() + .position(|s| s.name() == name.name()) + .unwrap() as u8; + let expr = Expr::Literal(Literal::Byte(tag_id)); Stmt::Let(assigned, expr, lambda_set_layout, hole) @@ -5373,7 +5467,7 @@ fn convert_tag_union<'a>( // version is not the same as the minimal version. let union_layout = match return_on_layout_error!( env, - layout_cache.from_var(env.arena, variant_var, env.subs) + layout_cache.from_var(env.arena, variant_var, env.subs,) ) { Layout::Union(ul) => ul, _ => unreachable!(), @@ -5401,7 +5495,6 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, - tag_name: tag_name.into(), tag_id: tag_id as _, arguments: field_symbols, }; @@ -5412,7 +5505,7 @@ fn convert_tag_union<'a>( tag_name: wrapped_tag_name, .. } => { - debug_assert_eq!(TagOrClosure::Tag(tag_name.clone()), wrapped_tag_name); + debug_assert_eq!(wrapped_tag_name.expect_tag(), tag_name); field_symbols = { let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena); @@ -5424,7 +5517,6 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, - tag_name: tag_name.into(), tag_id: tag_id as _, arguments: field_symbols, }; @@ -5449,7 +5541,6 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, - tag_name: tag_name.into(), tag_id: tag_id as _, arguments: field_symbols, }; @@ -5476,7 +5567,6 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, - tag_name: tag_name.into(), tag_id: tag_id as _, arguments: field_symbols, }; @@ -5494,7 +5584,6 @@ fn convert_tag_union<'a>( let tag = Expr::Tag { tag_layout: union_layout, - tag_name: tag_name.into(), tag_id: tag_id as _, arguments: field_symbols, }; @@ -5552,9 +5641,12 @@ fn tag_union_to_function<'a>( ext_var, }); + // Lambda does not capture anything, can't have a captures niche + let lambda_name = LambdaName::no_niche(proc_symbol); + let inserted = procs.insert_anonymous( env, - proc_symbol, + lambda_name, whole_var, loc_pattern_args, loc_body, @@ -5568,12 +5660,15 @@ fn tag_union_to_function<'a>( // only need to construct closure data let raw_layout = return_on_layout_error!( env, - layout_cache.raw_from_var(env.arena, whole_var, env.subs) + layout_cache.raw_from_var(env.arena, whole_var, env.subs,) ); match raw_layout { RawFunctionLayout::Function(_, lambda_set, _) => { - construct_closure_data(env, lambda_set, proc_symbol, &[], assigned, hole) + let lambda_name = + find_lambda_name(env, layout_cache, lambda_set, proc_symbol, &[]); + debug_assert!(lambda_name.no_captures()); + construct_closure_data(env, lambda_set, lambda_name, &[], assigned, hole) } RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(), } @@ -5735,7 +5830,7 @@ fn register_capturing_closure<'a>( captured_symbols.is_empty(), "{:?} with layout {:?} {:?} {:?}", &captured_symbols, - layout_cache.raw_from_var(env.arena, function_type, env.subs), + layout_cache.raw_from_var(env.arena, function_type, env.subs,), env.subs, (function_type, closure_type), ); @@ -6029,10 +6124,10 @@ fn from_can_when<'a>( let opt_branches = to_opt_branches(env, procs, branches, exhaustive_mark, layout_cache); let cond_layout = - return_on_layout_error!(env, layout_cache.from_var(env.arena, cond_var, env.subs)); + return_on_layout_error!(env, layout_cache.from_var(env.arena, cond_var, env.subs,)); let ret_layout = - return_on_layout_error!(env, layout_cache.from_var(env.arena, expr_var, env.subs)); + return_on_layout_error!(env, layout_cache.from_var(env.arena, expr_var, env.subs,)); let arena = env.arena; let it = opt_branches @@ -6287,8 +6382,8 @@ fn substitute_in_call<'a>( arg_layouts, ret_layout, specialization_id, - } => substitute(subs, *name).map(|new| CallType::ByName { - name: new, + } => substitute(subs, name.name()).map(|new| CallType::ByName { + name: name.replace_name(new), arg_layouts, ret_layout: *ret_layout, specialization_id: *specialization_id, @@ -6338,7 +6433,6 @@ fn substitute_in_expr<'a>( Tag { tag_layout, - tag_name, tag_id, arguments: args, } => { @@ -6359,7 +6453,6 @@ fn substitute_in_expr<'a>( Some(Tag { tag_layout: *tag_layout, - tag_name: tag_name.clone(), tag_id: *tag_id, arguments, }) @@ -7023,7 +7116,7 @@ where // specialized, and wrap the original in a function pointer. let mut result = result; for (_, (variable, left)) in needed_specializations_of_left { - add_needed_external(procs, env, variable, right); + add_needed_external(procs, env, variable, LambdaName::no_niche(right)); let res_layout = layout_cache.from_var(env.arena, variable, env.subs); let layout = return_on_layout_error!(env, res_layout); @@ -7034,7 +7127,7 @@ where } else if env.is_imported_symbol(right) { // if this is an imported symbol, then we must make sure it is // specialized, and wrap the original in a function pointer. - add_needed_external(procs, env, variable, right); + add_needed_external(procs, env, variable, LambdaName::no_niche(right)); // then we must construct its closure; since imported symbols have no closure, we use the empty struct let_empty_struct(left, env.arena.alloc(result)) @@ -7086,7 +7179,7 @@ fn force_thunk<'a>( ) -> Stmt<'a> { let call = self::Call { call_type: CallType::ByName { - name: thunk_name, + name: LambdaName::no_niche(thunk_name), ret_layout: env.arena.alloc(layout), arg_layouts: &[], specialization_id: env.next_call_specialization_id(), @@ -7129,23 +7222,27 @@ fn specialize_symbol<'a>( }; let raw = RawFunctionLayout::ZeroArgumentThunk(layout); - let top_level = ProcLayout::from_raw(env.arena, raw); + let top_level = + ProcLayout::from_raw(env.arena, raw, CapturesNiche::no_niche()); procs.insert_passed_by_name( env, arg_var, - original, + LambdaName::no_niche(original), top_level, layout_cache, ); force_thunk(env, original, layout, symbol, env.arena.alloc(result)) } else { - let top_level = ProcLayout::from_raw(env.arena, raw); + // Imported symbol, so it must have no captures niche (since + // top-levels can't capture) + let top_level = + ProcLayout::from_raw(env.arena, raw, CapturesNiche::no_niche()); procs.insert_passed_by_name( env, arg_var, - original, + LambdaName::no_niche(original), top_level, layout_cache, ); @@ -7174,7 +7271,7 @@ fn specialize_symbol<'a>( // to it in the IR. let res_layout = return_on_layout_error!( env, - layout_cache.raw_from_var(env.arena, arg_var, env.subs) + layout_cache.raw_from_var(env.arena, arg_var, env.subs,) ); // we have three kinds of functions really. Plain functions, closures by capture, @@ -7185,21 +7282,7 @@ fn specialize_symbol<'a>( match res_layout { RawFunctionLayout::Function(_, lambda_set, _) => { - // define the function pointer - let function_ptr_layout = ProcLayout::from_raw(env.arena, res_layout); - if captures { - // this is a closure by capture, meaning it itself captures local variables. - procs.insert_passed_by_name( - env, - arg_var, - original, - function_ptr_layout, - layout_cache, - ); - - let closure_data = symbol; - let symbols = match captured { CapturedSymbols::Captured(captured_symbols) => { Vec::from_iter_in(captured_symbols.iter(), env.arena) @@ -7208,10 +7291,36 @@ fn specialize_symbol<'a>( CapturedSymbols::None => unreachable!(), }; + let lambda_name = find_lambda_name( + env, + layout_cache, + lambda_set, + original, + symbols.iter().copied(), + ); + + // define the function pointer + let function_ptr_layout = ProcLayout::from_raw( + env.arena, + res_layout, + lambda_name.captures_niche(), + ); + + // this is a closure by capture, meaning it itself captures local variables. + procs.insert_passed_by_name( + env, + arg_var, + lambda_name, + function_ptr_layout, + layout_cache, + ); + + let closure_data = symbol; + construct_closure_data( env, lambda_set, - original, + lambda_name, symbols.iter().copied(), closure_data, env.arena.alloc(result), @@ -7223,31 +7332,44 @@ fn specialize_symbol<'a>( // let layout = Layout::Closure(argument_layouts, lambda_set, ret_layout); // panic!("suspicious"); let layout = Layout::LambdaSet(lambda_set); - let top_level = ProcLayout::new(env.arena, &[], layout); + let top_level = + ProcLayout::new(env.arena, &[], CapturesNiche::no_niche(), layout); procs.insert_passed_by_name( env, arg_var, - original, + LambdaName::no_niche(original), top_level, layout_cache, ); force_thunk(env, original, layout, symbol, env.arena.alloc(result)) } else { + // even though this function may not itself capture, + // unification may still cause it to have an extra argument + let lambda_name = + find_lambda_name(env, layout_cache, lambda_set, original, &[]); + + debug_assert!(lambda_name.no_captures()); + + // define the function pointer + let function_ptr_layout = ProcLayout::from_raw( + env.arena, + res_layout, + lambda_name.captures_niche(), + ); + procs.insert_passed_by_name( env, arg_var, - original, + lambda_name, function_ptr_layout, layout_cache, ); - // even though this function may not itself capture, - // unification may still cause it to have an extra argument construct_closure_data( env, lambda_set, - original, + lambda_name, &[], symbol, env.arena.alloc(result), @@ -7256,8 +7378,15 @@ fn specialize_symbol<'a>( } RawFunctionLayout::ZeroArgumentThunk(ret_layout) => { // this is a 0-argument thunk - let top_level = ProcLayout::new(env.arena, &[], ret_layout); - procs.insert_passed_by_name(env, arg_var, original, top_level, layout_cache); + let top_level = + ProcLayout::new(env.arena, &[], CapturesNiche::no_niche(), ret_layout); + procs.insert_passed_by_name( + env, + arg_var, + LambdaName::no_niche(original), + top_level, + layout_cache, + ); force_thunk(env, original, ret_layout, symbol, env.arena.alloc(result)) } @@ -7323,12 +7452,12 @@ fn add_needed_external<'a>( procs: &mut Procs<'a>, env: &mut Env<'a, '_>, fn_var: Variable, - name: Symbol, + name: LambdaName<'a>, ) { // call of a function that is not in this module use hashbrown::hash_map::Entry::{Occupied, Vacant}; - let existing = match procs.externals_we_need.entry(name.module_id()) { + let existing = match procs.externals_we_need.entry(name.name().module_id()) { Vacant(entry) => entry.insert(ExternalSpecializations::new()), Occupied(entry) => entry.into_mut(), }; @@ -7504,7 +7633,7 @@ fn call_by_name<'a>( hole, ) } else if env.is_imported_symbol(proc_name) { - add_needed_external(procs, env, fn_var, proc_name); + add_needed_external(procs, env, fn_var, LambdaName::no_niche(proc_name)); force_thunk(env, proc_name, ret_layout, assigned, hole) } else { panic!("most likely we're trying to call something that is not a function"); @@ -7536,12 +7665,43 @@ fn call_by_name_help<'a>( possible_reuse_symbol_or_specialize(env, procs, layout_cache, &arg_expr.value, *arg_var) })); + // THEORY: with a call by name, there are three options: + // - this is actually a thunk, and the lambda set is empty + // - the name references a function directly, like `main = \x -> ...`. In this case the + // lambda set includes only the function itself, and hence there is exactly one captures + // niche for the function. + // - the name references a value that yields a function, like + // `main = if b then \x -> .. else \y -> ..`. In that case the name being called never + // actually appears in the lambda set, and in fact has no capture set, and hence no + // captures niche. + // So, if this function has any captures niche, it will be the first one. + let mut iter_lambda_names = lambda_set + .iter_set() + .filter(|lam_name| lam_name.name() == proc_name); + let proc_name = match iter_lambda_names.next() { + Some(name) => { + debug_assert!( + iter_lambda_names.next().is_none(), + "Somehow, call by name for {:?} has multiple capture niches: {:?}", + proc_name, + lambda_set + ); + name + } + None => LambdaName::no_niche(proc_name), + }; + // If required, add an extra argument to the layout that is the captured environment // afterwards, we MUST make sure the number of arguments in the layout matches the // number of arguments actually passed. let top_level_layout = { let argument_layouts = lambda_set.extend_argument_list(env.arena, argument_layouts); - ProcLayout::new(env.arena, argument_layouts, *ret_layout) + ProcLayout::new( + env.arena, + argument_layouts, + proc_name.captures_niche(), + *ret_layout, + ) }; // the variables of the given arguments @@ -7562,7 +7722,7 @@ fn call_by_name_help<'a>( // If we've already specialized this one, no further work is needed. if procs .specialized - .is_specialized(proc_name, &top_level_layout) + .is_specialized(proc_name.name(), &top_level_layout) { debug_assert_eq!( argument_layouts.len(), @@ -7587,15 +7747,15 @@ fn call_by_name_help<'a>( let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); assign_to_symbols(env, procs, layout_cache, iter, result) - } else if env.is_imported_symbol(proc_name) { + } else if env.is_imported_symbol(proc_name.name()) { add_needed_external(procs, env, original_fn_var, proc_name); - debug_assert_ne!(proc_name.module_id(), ModuleId::ATTR); + debug_assert_ne!(proc_name.name().module_id(), ModuleId::ATTR); - if procs.is_imported_module_thunk(proc_name) { + if procs.is_imported_module_thunk(proc_name.name()) { force_thunk( env, - proc_name, + proc_name.name(), Layout::LambdaSet(lambda_set), assigned, hole, @@ -7605,6 +7765,7 @@ fn call_by_name_help<'a>( // imported symbols cannot capture anything let captured = &[]; + debug_assert!(proc_name.no_captures()); construct_closure_data(env, lambda_set, proc_name, captured, assigned, hole) } else { @@ -7644,13 +7805,13 @@ fn call_by_name_help<'a>( // the same specialization independently), we work through the // queue of pending specializations to complete each specialization // exactly once. - if procs.is_module_thunk(proc_name) { + if procs.is_module_thunk(proc_name.name()) { debug_assert!(top_level_layout.arguments.is_empty()); } match &mut procs.pending_specializations { PendingSpecializations::Finding(suspended) => { - debug_assert!(!env.is_imported_symbol(proc_name)); + debug_assert!(!env.is_imported_symbol(proc_name.name())); // register the pending specialization, so this gets code genned later suspended.specialization(env.subs, proc_name, top_level_layout, fn_var); @@ -7662,10 +7823,10 @@ fn call_by_name_help<'a>( proc_name, ); - let has_closure = argument_layouts.len() != top_level_layout.arguments.len(); + let has_captures = argument_layouts.len() != top_level_layout.arguments.len(); let closure_argument = env.unique_symbol(); - if has_closure { + if has_captures { field_symbols.push(closure_argument); } @@ -7689,8 +7850,8 @@ fn call_by_name_help<'a>( let iter = loc_args.into_iter().zip(field_symbols.iter()).rev(); let result = assign_to_symbols(env, procs, layout_cache, iter, result); - if has_closure { - let partial_proc = procs.partial_procs.get_symbol(proc_name).unwrap(); + if has_captures { + let partial_proc = procs.partial_procs.get_symbol(proc_name.name()).unwrap(); let captured = match partial_proc.captured_symbols { CapturedSymbols::None => &[], @@ -7710,7 +7871,7 @@ fn call_by_name_help<'a>( } } PendingSpecializations::Making => { - let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name); + let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name.name()); let field_symbols = field_symbols.into_bump_slice(); @@ -7721,7 +7882,7 @@ fn call_by_name_help<'a>( // (We had a bug around this before this system existed!) procs .specialized - .mark_in_progress(proc_name, top_level_layout); + .mark_in_progress(proc_name.name(), top_level_layout); match specialize_variable( env, @@ -7737,7 +7898,6 @@ fn call_by_name_help<'a>( call_specialized_proc( env, procs, - proc_name, proc, lambda_set, layout, @@ -7751,14 +7911,13 @@ fn call_by_name_help<'a>( Err(SpecializeFailure { attempted_layout }) => { let proc = generate_runtime_error_function( env, - proc_name, + proc_name.name(), attempted_layout, ); call_specialized_proc( env, procs, - proc_name, proc, lambda_set, attempted_layout, @@ -7792,7 +7951,7 @@ fn call_by_name_module_thunk<'a>( assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { - let top_level_layout = ProcLayout::new(env.arena, &[], *ret_layout); + let top_level_layout = ProcLayout::new(env.arena, &[], CapturesNiche::no_niche(), *ret_layout); let inner_layout = *ret_layout; @@ -7824,7 +7983,12 @@ fn call_by_name_module_thunk<'a>( debug_assert!(!env.is_imported_symbol(proc_name)); // register the pending specialization, so this gets code genned later - suspended.specialization(env.subs, proc_name, top_level_layout, fn_var); + suspended.specialization( + env.subs, + LambdaName::no_niche(proc_name), + top_level_layout, + fn_var, + ); force_thunk(env, proc_name, inner_layout, assigned, hole) } @@ -7843,7 +8007,7 @@ fn call_by_name_module_thunk<'a>( match specialize_variable( env, procs, - proc_name, + LambdaName::no_niche(proc_name), layout_cache, fn_var, &[], @@ -7905,7 +8069,6 @@ fn call_by_name_module_thunk<'a>( fn call_specialized_proc<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - proc_name: Symbol, proc: Proc<'a>, lambda_set: LambdaSet<'a>, layout: RawFunctionLayout<'a>, @@ -7915,11 +8078,12 @@ fn call_specialized_proc<'a>( assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { - let function_layout = ProcLayout::from_raw(env.arena, layout); + let proc_name = proc.name; + let function_layout = ProcLayout::from_raw(env.arena, layout, proc_name.captures_niche()); procs .specialized - .insert_specialized(proc_name, function_layout, proc); + .insert_specialized(proc_name.name(), function_layout, proc); if field_symbols.is_empty() { debug_assert!(loc_args.is_empty()); @@ -7957,7 +8121,7 @@ fn call_specialized_proc<'a>( match procs .partial_procs - .get_symbol(proc_name) + .get_symbol(proc_name.name()) .map(|pp| &pp.captured_symbols) { Some(&CapturedSymbols::Captured(captured_symbols)) => { @@ -8877,7 +9041,13 @@ pub fn num_argument_to_int_or_float( } } -type ToLowLevelCallArguments<'a> = (Symbol, Symbol, Option>, CallSpecId, UpdateModeId); +type ToLowLevelCallArguments<'a> = ( + LambdaName<'a>, + Symbol, + Option>, + CallSpecId, + UpdateModeId, +); /// Use the lambda set to figure out how to make a lowlevel call #[allow(clippy::too_many_arguments)] @@ -8900,7 +9070,7 @@ where let result = lowlevel_union_lambda_set_to_switch( env, - lambda_set.set, + lambda_set.iter_set(), closure_tag_id_symbol, union_layout.tag_id_layout(), closure_data_symbol, @@ -8924,12 +9094,12 @@ where env.arena.alloc(result), ) } - Layout::Struct { .. } => match lambda_set.set.get(0) { - Some((function_symbol, _)) => { + Layout::Struct { .. } => match lambda_set.iter_set().next() { + Some(lambda_name) => { let call_spec_id = env.next_call_specialization_id(); let update_mode = env.next_update_mode_id(); let call = to_lowlevel_call(( - *function_symbol, + lambda_name, closure_data_symbol, lambda_set.is_represented(), call_spec_id, @@ -8954,7 +9124,7 @@ where lowlevel_enum_lambda_set_to_switch( env, - lambda_set.set, + lambda_set.iter_set(), closure_tag_id_symbol, Layout::Builtin(Builtin::Bool), closure_data_symbol, @@ -8970,7 +9140,7 @@ where lowlevel_enum_lambda_set_to_switch( env, - lambda_set.set, + lambda_set.iter_set(), closure_tag_id_symbol, Layout::Builtin(Builtin::Int(IntWidth::U8)), closure_data_symbol, @@ -8988,7 +9158,7 @@ where #[allow(clippy::too_many_arguments)] fn lowlevel_union_lambda_set_to_switch<'a, ToLowLevelCall>( env: &mut Env<'a, '_>, - lambda_set: &'a [(Symbol, &'a [Layout<'a>])], + lambda_set: impl ExactSizeIterator> + 'a, closure_tag_id_symbol: Symbol, closure_tag_id_layout: Layout<'a>, closure_data_symbol: Symbol, @@ -9001,13 +9171,13 @@ fn lowlevel_union_lambda_set_to_switch<'a, ToLowLevelCall>( where ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy, { - debug_assert!(!lambda_set.is_empty()); + debug_assert_ne!(lambda_set.len(), 0); let join_point_id = JoinPointId(env.unique_symbol()); let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena); - for (i, (function_symbol, _)) in lambda_set.iter().enumerate() { + for (i, lambda_name) in lambda_set.into_iter().enumerate() { let assigned = env.unique_symbol(); let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned])); @@ -9015,7 +9185,7 @@ where let call_spec_id = env.next_call_specialization_id(); let update_mode = env.next_update_mode_id(); let call = to_lowlevel_call(( - *function_symbol, + lambda_name, closure_data_symbol, closure_env_layout, call_spec_id, @@ -9101,17 +9271,24 @@ fn match_on_lambda_set<'a>( field_layouts, field_order_hash, } => { - let function_symbol = lambda_set.set[0].0; + let function_symbol = lambda_set.iter_set().next().unwrap(); + + let closure_info = match field_layouts { + [] => ClosureInfo::DoesNotCapture, + _ => ClosureInfo::Captures { + lambda_set, + closure_data_symbol, + closure_data_layout: Layout::Struct { + field_layouts, + field_order_hash, + }, + }, + }; union_lambda_set_branch_help( env, function_symbol, - lambda_set, - closure_data_symbol, - Layout::Struct { - field_layouts, - field_order_hash, - }, + closure_info, argument_symbols, argument_layouts, return_layout, @@ -9124,7 +9301,7 @@ fn match_on_lambda_set<'a>( enum_lambda_set_to_switch( env, - lambda_set.set, + lambda_set.iter_set(), closure_tag_id_symbol, Layout::Builtin(Builtin::Bool), closure_data_symbol, @@ -9140,7 +9317,7 @@ fn match_on_lambda_set<'a>( enum_lambda_set_to_switch( env, - lambda_set.set, + lambda_set.iter_set(), closure_tag_id_symbol, Layout::Builtin(Builtin::Int(IntWidth::U8)), closure_data_symbol, @@ -9183,14 +9360,22 @@ fn union_lambda_set_to_switch<'a>( let mut branches = Vec::with_capacity_in(lambda_set.set.len(), env.arena); - for (i, (function_symbol, _)) in lambda_set.set.iter().enumerate() { + for (i, lambda_name) in lambda_set.iter_set().enumerate() { + let closure_info = if lambda_name.no_captures() { + ClosureInfo::DoesNotCapture + } else { + ClosureInfo::Captures { + lambda_set, + closure_data_symbol, + closure_data_layout: closure_layout, + } + }; + let stmt = union_lambda_set_branch( env, - lambda_set, join_point_id, - *function_symbol, - closure_data_symbol, - closure_layout, + lambda_name, + closure_info, argument_symbols, argument_layouts, return_layout, @@ -9229,11 +9414,9 @@ fn union_lambda_set_to_switch<'a>( #[allow(clippy::too_many_arguments)] fn union_lambda_set_branch<'a>( env: &mut Env<'a, '_>, - lambda_set: LambdaSet<'a>, join_point_id: JoinPointId, - function_symbol: Symbol, - closure_data_symbol: Symbol, - closure_data_layout: Layout<'a>, + lambda_name: LambdaName<'a>, + closure_info: ClosureInfo<'a>, argument_symbols_slice: &'a [Symbol], argument_layouts_slice: &'a [Layout<'a>], return_layout: &'a Layout<'a>, @@ -9244,10 +9427,8 @@ fn union_lambda_set_branch<'a>( union_lambda_set_branch_help( env, - function_symbol, - lambda_set, - closure_data_symbol, - closure_data_layout, + lambda_name, + closure_info, argument_symbols_slice, argument_layouts_slice, return_layout, @@ -9256,58 +9437,73 @@ fn union_lambda_set_branch<'a>( ) } +enum ClosureInfo<'a> { + Captures { + closure_data_symbol: Symbol, + /// The layout of this closure variant + closure_data_layout: Layout<'a>, + /// The whole lambda set representation this closure is a variant of + lambda_set: LambdaSet<'a>, + }, + DoesNotCapture, +} + #[allow(clippy::too_many_arguments)] fn union_lambda_set_branch_help<'a>( env: &mut Env<'a, '_>, - function_symbol: Symbol, - lambda_set: LambdaSet<'a>, - closure_data_symbol: Symbol, - closure_data_layout: Layout<'a>, + lambda_name: LambdaName<'a>, + closure_info: ClosureInfo<'a>, argument_symbols_slice: &'a [Symbol], argument_layouts_slice: &'a [Layout<'a>], return_layout: &'a Layout<'a>, assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { - let (argument_layouts, argument_symbols) = match closure_data_layout { - Layout::Struct { - field_layouts: &[], .. - } - | Layout::Builtin(Builtin::Bool) - | Layout::Builtin(Builtin::Int(IntWidth::U8)) => { - (argument_layouts_slice, argument_symbols_slice) - } - _ if lambda_set.member_does_not_need_closure_argument(function_symbol) => { + let (argument_layouts, argument_symbols) = match closure_info { + ClosureInfo::Captures { + lambda_set, + closure_data_symbol, + closure_data_layout, + } => match closure_data_layout { + Layout::Struct { + field_layouts: &[], .. + } + | Layout::Builtin(Builtin::Bool) + | Layout::Builtin(Builtin::Int(IntWidth::U8)) => { + (argument_layouts_slice, argument_symbols_slice) + } + _ => { + // extend layouts with the layout of the closure environment + let mut argument_layouts = + Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena); + argument_layouts.extend(argument_layouts_slice); + argument_layouts.push(Layout::LambdaSet(lambda_set)); + + // extend symbols with the symbol of the closure environment + let mut argument_symbols = + Vec::with_capacity_in(argument_symbols_slice.len() + 1, env.arena); + argument_symbols.extend(argument_symbols_slice); + argument_symbols.push(closure_data_symbol); + + ( + argument_layouts.into_bump_slice(), + argument_symbols.into_bump_slice(), + ) + } + }, + ClosureInfo::DoesNotCapture => { // sometimes unification causes a function that does not itself capture anything // to still get a lambda set that does store information. We must not pass a closure // argument in this case (argument_layouts_slice, argument_symbols_slice) } - _ => { - // extend layouts with the layout of the closure environment - let mut argument_layouts = - Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena); - argument_layouts.extend(argument_layouts_slice); - argument_layouts.push(Layout::LambdaSet(lambda_set)); - - // extend symbols with the symbol of the closure environment - let mut argument_symbols = - Vec::with_capacity_in(argument_symbols_slice.len() + 1, env.arena); - argument_symbols.extend(argument_symbols_slice); - argument_symbols.push(closure_data_symbol); - - ( - argument_layouts.into_bump_slice(), - argument_symbols.into_bump_slice(), - ) - } }; // build the call let call = self::Call { call_type: CallType::ByName { - name: function_symbol, + name: lambda_name, ret_layout: return_layout, arg_layouts: argument_layouts, specialization_id: env.next_call_specialization_id(), @@ -9321,7 +9517,7 @@ fn union_lambda_set_branch_help<'a>( #[allow(clippy::too_many_arguments)] fn enum_lambda_set_to_switch<'a>( env: &mut Env<'a, '_>, - lambda_set: &'a [(Symbol, &'a [Layout<'a>])], + lambda_set: impl ExactSizeIterator>, closure_tag_id_symbol: Symbol, closure_tag_id_layout: Layout<'a>, closure_data_symbol: Symbol, @@ -9331,7 +9527,7 @@ fn enum_lambda_set_to_switch<'a>( assigned: Symbol, hole: &'a Stmt<'a>, ) -> Stmt<'a> { - debug_assert!(!lambda_set.is_empty()); + debug_assert_ne!(lambda_set.len(), 0); let join_point_id = JoinPointId(env.unique_symbol()); @@ -9339,11 +9535,11 @@ fn enum_lambda_set_to_switch<'a>( let closure_layout = closure_tag_id_layout; - for (i, (function_symbol, _)) in lambda_set.iter().enumerate() { + for (i, lambda_name) in lambda_set.into_iter().enumerate() { let stmt = enum_lambda_set_branch( env, join_point_id, - *function_symbol, + lambda_name, closure_data_symbol, closure_layout, argument_symbols, @@ -9385,7 +9581,7 @@ fn enum_lambda_set_to_switch<'a>( fn enum_lambda_set_branch<'a>( env: &mut Env<'a, '_>, join_point_id: JoinPointId, - function_symbol: Symbol, + lambda_name: LambdaName<'a>, closure_data_symbol: Symbol, closure_data_layout: Layout<'a>, argument_symbols_slice: &'a [Symbol], @@ -9428,7 +9624,7 @@ fn enum_lambda_set_branch<'a>( let call = self::Call { call_type: CallType::ByName { - name: function_symbol, + name: lambda_name, ret_layout: return_layout, arg_layouts: argument_layouts, specialization_id: env.next_call_specialization_id(), @@ -9441,7 +9637,7 @@ fn enum_lambda_set_branch<'a>( #[allow(clippy::too_many_arguments)] fn lowlevel_enum_lambda_set_to_switch<'a, ToLowLevelCall>( env: &mut Env<'a, '_>, - lambda_set: &'a [(Symbol, &'a [Layout<'a>])], + lambda_set: impl ExactSizeIterator>, closure_tag_id_symbol: Symbol, closure_tag_id_layout: Layout<'a>, closure_data_symbol: Symbol, @@ -9454,13 +9650,13 @@ fn lowlevel_enum_lambda_set_to_switch<'a, ToLowLevelCall>( where ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy, { - debug_assert!(!lambda_set.is_empty()); + debug_assert_ne!(lambda_set.len(), 0); let join_point_id = JoinPointId(env.unique_symbol()); let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena); - for (i, (function_symbol, _)) in lambda_set.iter().enumerate() { + for (i, function_symbol) in lambda_set.into_iter().enumerate() { let result_symbol = env.unique_symbol(); let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol])); @@ -9468,7 +9664,7 @@ where let call_spec_id = env.next_call_specialization_id(); let update_mode = env.next_update_mode_id(); let call = to_lowlevel_call(( - *function_symbol, + function_symbol, closure_data_symbol, closure_env_layout, call_spec_id, diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index df89ee1049..976a2c903a 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -8,7 +8,6 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, Symbol}; use roc_problem::can::RuntimeError; use roc_target::{PtrWidth, TargetInfo}; -use roc_types::pretty_print::ResolvedLambdaSet; use roc_types::subs::{ self, Content, FlatType, Label, RecordFields, Subs, UnionTags, UnsortedUnionLabels, Variable, }; @@ -226,7 +225,7 @@ impl<'a> RawFunctionLayout<'a> { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct FieldOrderHash(u64); impl FieldOrderHash { @@ -248,7 +247,7 @@ impl FieldOrderHash { } /// Types for code gen must be monomorphic. No type variables allowed! -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Layout<'a> { Builtin(Builtin<'a>), Struct { @@ -270,7 +269,7 @@ pub enum Layout<'a> { RecursivePointer, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum UnionLayout<'a> { /// A non-recursive tag union /// e.g. `Result a e : [Ok a, Err e]` @@ -693,7 +692,74 @@ impl std::fmt::Debug for LambdaSet<'_> { } } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +/// Sometimes we can end up with lambdas of the same name and different captures in the same +/// lambda set, like `fun` having lambda set `[[thunk U64, thunk U8]]` due to the following program: +/// +/// ```roc +/// capture : _ -> ({} -> Str) +/// capture = \val -> +/// thunk = \{} -> Num.toStr val +/// thunk +/// +/// fun = \x -> +/// when x is +/// True -> capture 123u64 +/// False -> capture 18u8 +/// ``` +/// +/// By recording the captures layouts this lambda expects in its identifier, we can distinguish +/// between such differences when constructing closure capture data. +/// +/// See also https://github.com/rtfeldman/roc/issues/3336. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct CapturesNiche<'a>(&'a [Layout<'a>]); + +impl CapturesNiche<'_> { + pub fn no_niche() -> Self { + Self(&[]) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct LambdaName<'a> { + name: Symbol, + captures_niche: CapturesNiche<'a>, +} + +impl<'a> LambdaName<'a> { + #[inline(always)] + pub fn name(&self) -> Symbol { + self.name + } + + #[inline(always)] + pub fn captures_niche(&self) -> CapturesNiche<'a> { + self.captures_niche + } + + #[inline(always)] + pub fn no_captures(&self) -> bool { + self.captures_niche.0.is_empty() + } + + #[inline(always)] + pub fn no_niche(name: Symbol) -> Self { + Self { + name, + captures_niche: CapturesNiche::no_niche(), + } + } + + #[inline(always)] + pub fn replace_name(&self, name: Symbol) -> Self { + Self { + name, + captures_niche: self.captures_niche, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct LambdaSet<'a> { /// collection of function names and their closure arguments pub set: &'a [(Symbol, &'a [Layout<'a>])], @@ -741,23 +807,57 @@ impl<'a> LambdaSet<'a> { } } - pub fn member_does_not_need_closure_argument(&self, function_symbol: Symbol) -> bool { - match self.layout_for_member(function_symbol) { - ClosureRepresentation::Union { - alphabetic_order_fields, - .. - } => alphabetic_order_fields.is_empty(), - ClosureRepresentation::AlphabeticOrderStruct(fields) => fields.is_empty(), - ClosureRepresentation::Other(_) => false, + pub fn iter_set(&self) -> impl ExactSizeIterator> { + self.set.iter().map(|(name, captures_layouts)| LambdaName { + name: *name, + captures_niche: CapturesNiche(captures_layouts), + }) + } + + pub fn layout_for_member_with_lambda_name( + &self, + lambda_name: LambdaName, + ) -> ClosureRepresentation<'a> { + debug_assert!(self.contains(lambda_name.name)); + + let comparator = |other_name: Symbol, other_captures_layouts: &[Layout]| { + other_name == lambda_name.name + && other_captures_layouts + .iter() + .eq(lambda_name.captures_niche.0) + }; + + self.layout_for_member(comparator) + } + + /// Finds an alias name for a possible-multimorphic lambda variant in the lambda set. + pub fn find_lambda_name( + &self, + function_symbol: Symbol, + captures_layouts: &[Layout], + ) -> LambdaName<'a> { + debug_assert!(self.contains(function_symbol), "function symbol not in set"); + + let comparator = |other_name: Symbol, other_captures_layouts: &[Layout]| { + other_name == function_symbol && other_captures_layouts.iter().eq(captures_layouts) + }; + + let (name, layouts) = self + .set + .iter() + .find(|(name, layouts)| comparator(*name, layouts)) + .expect("no lambda set found"); + + LambdaName { + name: *name, + captures_niche: CapturesNiche(layouts), } } - pub fn layout_for_member(&self, function_symbol: Symbol) -> ClosureRepresentation<'a> { - debug_assert!( - self.set.iter().any(|(s, _)| *s == function_symbol), - "function symbol not in set" - ); - + fn layout_for_member(&self, comparator: F) -> ClosureRepresentation<'a> + where + F: Fn(Symbol, &[Layout]) -> bool, + { match self.representation { Layout::Union(union) => { // here we rely on the fact that a union in a closure would be stored in a one-element record. @@ -766,17 +866,19 @@ impl<'a> LambdaSet<'a> { UnionLayout::NonRecursive(_) => { // get the fields from the set, where they are sorted in alphabetic order // (and not yet sorted by their alignment) - let (index, (_, fields)) = self + let (index, (name, fields)) = self .set .iter() .enumerate() - .find(|(_, (s, _))| *s == function_symbol) + .find(|(_, (s, layouts))| comparator(*s, layouts)) .unwrap(); + let closure_name = *name; + ClosureRepresentation::Union { tag_id: index as TagIdIntType, alphabetic_order_fields: fields, - closure_name: function_symbol, + closure_name, union_layout: *union, } } @@ -793,12 +895,14 @@ impl<'a> LambdaSet<'a> { } } Layout::Struct { .. } => { + debug_assert_eq!(self.set.len(), 1); + // get the fields from the set, where they are sorted in alphabetic order // (and not yet sorted by their alignment) let (_, fields) = self .set .iter() - .find(|(s, _)| *s == function_symbol) + .find(|(s, layouts)| comparator(*s, layouts)) .unwrap(); ClosureRepresentation::AlphabeticOrderStruct(fields) @@ -846,32 +950,86 @@ impl<'a> LambdaSet<'a> { closure_var: Variable, target_info: TargetInfo, ) -> Result { - match roc_types::pretty_print::resolve_lambda_set(subs, closure_var) { + match resolve_lambda_set(subs, closure_var) { ResolvedLambdaSet::Set(mut lambdas) => { // sort the tags; make sure ordering stays intact! - lambdas.sort(); + lambdas.sort_by_key(|(sym, _)| *sym); - let mut set = Vec::with_capacity_in(lambdas.len(), arena); + let mut set: Vec<(Symbol, &[Layout])> = Vec::with_capacity_in(lambdas.len(), arena); + let mut set_with_variables: std::vec::Vec<(Symbol, std::vec::Vec)> = + std::vec::Vec::with_capacity(lambdas.len()); - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; + let mut last_function_symbol = None; + let mut lambdas_it = lambdas.iter().peekable(); - for (function_symbol, variables) in lambdas.iter() { + let mut has_duplicate_lambda_names = false; + while let Some((function_symbol, variables)) = lambdas_it.next() { let mut arguments = Vec::with_capacity_in(variables.len(), arena); + let mut env = Env { + arena, + subs, + seen: Vec::new_in(arena), + target_info, + }; + for var in variables { arguments.push(Layout::from_var(&mut env, *var)?); } - set.push((*function_symbol, arguments.into_bump_slice())); + let arguments = arguments.into_bump_slice(); + + let is_multimorphic = match (last_function_symbol, lambdas_it.peek()) { + (None, None) => false, + (Some(sym), None) | (None, Some((sym, _))) => function_symbol == sym, + (Some(sym1), Some((sym2, _))) => { + function_symbol == sym1 || function_symbol == sym2 + } + }; + + has_duplicate_lambda_names = has_duplicate_lambda_names || is_multimorphic; + + set.push((*function_symbol, arguments)); + set_with_variables.push((*function_symbol, variables.to_vec())); + + last_function_symbol = Some(function_symbol); } - let representation = - arena.alloc(Self::make_representation(arena, subs, lambdas, target_info)); + let (set, set_with_variables) = if has_duplicate_lambda_names { + // If we have a lambda set with duplicate names, then we sort first by name, + // and break ties by sorting on the layout. We need to do this again since the + // first sort would not have sorted on the layout. + + // TODO: be more efficient, we can compute the permutation once and then apply + // it to both vectors. + let mut joined = set + .into_iter() + .zip(set_with_variables.into_iter()) + .collect::>(); + joined.sort_by(|(lam_and_captures1, _), (lam_and_captures2, _)| { + lam_and_captures1.cmp(lam_and_captures2) + }); + // Remove duplicate lambda captures layouts unification can't see as + // duplicates, for example [[Thunk {a: Str}, Thunk [A Str]]], each of which are + // newtypes over the lambda layout `Thunk Str`. + joined.dedup_by_key(|((name, captures), _)| (*name, *captures)); + + let (set, set_with_variables): (std::vec::Vec<_>, std::vec::Vec<_>) = + joined.into_iter().unzip(); + + let set = Vec::from_iter_in(set, arena); + + (set, set_with_variables) + } else { + (set, set_with_variables) + }; + + let representation = arena.alloc(Self::make_representation( + arena, + subs, + set_with_variables, + target_info, + )); Ok(LambdaSet { set: set.into_bump_slice(), @@ -951,7 +1109,41 @@ impl<'a> LambdaSet<'a> { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +enum ResolvedLambdaSet { + Set(std::vec::Vec<(Symbol, std::vec::Vec)>), + /// TODO: figure out if this can happen in a correct program, or is the result of a bug in our + /// compiler. See https://github.com/rtfeldman/roc/issues/3163. + Unbound, +} + +fn resolve_lambda_set(subs: &Subs, mut var: Variable) -> ResolvedLambdaSet { + let mut set = vec![]; + loop { + match subs.get_content_without_compacting(var) { + Content::LambdaSet(subs::LambdaSet { + solved, + recursion_var: _, + unspecialized, + }) => { + debug_assert!( + unspecialized.is_empty(), + "unspecialized lambda sets left over during resolution: {:?}", + roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(var), subs), + ); + roc_types::pretty_print::push_union(subs, solved, &mut set); + return ResolvedLambdaSet::Set(set); + } + Content::RecursionVar { structure, .. } => { + var = *structure; + } + Content::FlexVar(_) => return ResolvedLambdaSet::Unbound, + + c => internal_error!("called with a non-lambda set {:?}", c), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum Builtin<'a> { Int(IntWidth), Float(FloatWidth), diff --git a/crates/compiler/mono/src/reset_reuse.rs b/crates/compiler/mono/src/reset_reuse.rs index 189a1b8481..44c77190f7 100644 --- a/crates/compiler/mono/src/reset_reuse.rs +++ b/crates/compiler/mono/src/reset_reuse.rs @@ -97,7 +97,6 @@ fn function_s<'a, 'i>( Expr::Tag { tag_layout, tag_id, - tag_name, arguments, } if may_reuse(*tag_layout, *tag_id, c) => { // for now, always overwrite the tag ID just to be sure @@ -109,7 +108,6 @@ fn function_s<'a, 'i>( update_tag_id, tag_layout: *tag_layout, tag_id: *tag_id, - tag_name: tag_name.clone(), arguments, }; let new_stmt = Let(*symbol, new_expr, *layout, continuation); diff --git a/crates/compiler/mono/src/tail_recursion.rs b/crates/compiler/mono/src/tail_recursion.rs index 1bfc43d324..5d29f3f527 100644 --- a/crates/compiler/mono/src/tail_recursion.rs +++ b/crates/compiler/mono/src/tail_recursion.rs @@ -1,7 +1,7 @@ #![allow(clippy::manual_map)] use crate::ir::{CallType, Expr, JoinPointId, Param, Stmt}; -use crate::layout::Layout; +use crate::layout::{LambdaName, Layout}; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_module::symbol::Symbol; @@ -31,7 +31,7 @@ use roc_module::symbol::Symbol; pub fn make_tail_recursive<'a>( arena: &'a Bump, id: JoinPointId, - needle: Symbol, + needle: LambdaName, stmt: Stmt<'a>, args: &'a [(Layout<'a>, Symbol, Symbol)], ret_layout: Layout, @@ -71,7 +71,7 @@ fn insert_jumps<'a>( arena: &'a Bump, stmt: &'a Stmt<'a>, goal_id: JoinPointId, - needle: Symbol, + needle: LambdaName, needle_arguments: &'a [(Layout<'a>, Symbol, Symbol)], needle_result: Layout, ) -> Option<&'a Stmt<'a>> { @@ -80,7 +80,7 @@ fn insert_jumps<'a>( // to insert a tail-call, it must not just be a call to the function itself, but it must also // have the same layout. In particular when lambda sets get involved, a self-recursive call may // have a different type and should not be converted to a jump! - let is_equal_function = |function_name: Symbol, arguments: &[_], result| { + let is_equal_function = |function_name: LambdaName, arguments: &[_], result| { let it = needle_arguments.iter().map(|t| &t.0); needle == function_name && it.eq(arguments.iter()) && needle_result == result }; diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 1a380b0dc5..0f00ca6f47 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -6953,6 +6953,101 @@ mod solve_expr { "# ), "Str", + ) + } + + #[test] + fn lambda_sets_collide_with_captured_var() { + infer_queries!( + indoc!( + r#" + capture : a -> ({} -> Str) + capture = \val -> + thunk = + \{} -> + when val is + _ -> "" + thunk + + x : [True, False] + + fun = + when x is + True -> capture "" + # ^^^^^^^ + False -> capture {} + # ^^^^^^^ + fun + #^^^{-1} + "# + ), + &[ + "capture : Str -[[capture(1)]]-> ({} -[[thunk(5) {}, thunk(5) Str]]-> Str)", + "capture : {} -[[capture(1)]]-> ({} -[[thunk(5) {}, thunk(5) Str]]-> Str)", + "fun : {} -[[thunk(5) {}, thunk(5) Str]]-> Str", + ] + ); + } + + #[test] + fn lambda_sets_collide_with_captured_function() { + infer_queries!( + indoc!( + r#" + Lazy a : {} -> a + + after : Lazy a, (a -> Lazy b) -> Lazy b + after = \effect, map -> + thunk = \{} -> + when map (effect {}) is + b -> b {} + thunk + + f = \_ -> \_ -> "" + g = \{ s1 } -> \_ -> s1 + + x : [True, False] + + fun = + when x is + True -> after (\{} -> "") f + False -> after (\{} -> {s1: "s1"}) g + fun + #^^^{-1} + "# + ), + &[ + "fun : {} -[[thunk(9) (({} -[[15(15)]]-> { s1 : Str })) ({ s1 : Str } -[[g(4)]]-> ({} -[[13(13) Str]]-> Str)), \ + thunk(9) (({} -[[14(14)]]-> Str)) (Str -[[f(3)]]-> ({} -[[11(11)]]-> Str))]]-> Str", + ], + print_only_under_alias = true, + ); + } + + #[test] + fn lambda_set_niche_same_layout_different_constructor() { + infer_queries!( + indoc!( + r#" + capture : a -> ({} -> Str) + capture = \val -> + thunk = + \{} -> + when val is + _ -> "" + thunk + + x : [True, False] + + fun = + when x is + True -> capture {a: ""} + False -> capture (A "") + fun + #^^^{-1} + "# + ), + &["fun : {} -[[thunk(5) [A Str]*, thunk(5) { a : Str }]]-> Str",] ); } } diff --git a/crates/compiler/test_gen/src/gen_compare.rs b/crates/compiler/test_gen/src/gen_compare.rs index efc062eb5c..495ddee3c0 100644 --- a/crates/compiler/test_gen/src/gen_compare.rs +++ b/crates/compiler/test_gen/src/gen_compare.rs @@ -672,3 +672,25 @@ fn compare_nullable_recursive_union_same_content() { bool ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn boxed_eq_int() { + assert_evals_to!("Box.box 1 == Box.box 1", true, bool); + assert_evals_to!("Box.box 2 == Box.box 1", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn boxed_eq_str() { + assert_evals_to!( + "Box.box \"Hello, world\" == Box.box \"Hello, world\"", + true, + bool + ); + assert_evals_to!( + "Box.box \"Hello, world\" == Box.box \"Hello, stranger\"", + false, + bool + ); +} diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 7e4dc1e8b5..06d2985b5a 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -3260,7 +3260,7 @@ fn issue_2322() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn box_and_unbox_string() { assert_evals_to!( indoc!( @@ -3278,7 +3278,7 @@ fn box_and_unbox_string() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn box_and_unbox_num() { assert_evals_to!( indoc!( @@ -3292,7 +3292,7 @@ fn box_and_unbox_num() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn box_and_unbox_record() { assert_evals_to!( indoc!( @@ -3306,7 +3306,7 @@ fn box_and_unbox_record() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn box_and_unbox_tag_union() { assert_evals_to!( indoc!( @@ -3498,3 +3498,129 @@ fn polymorphic_lambda_captures_polymorphic_value() { u64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn lambda_capture_niche_u64_vs_u8_capture() { + assert_evals_to!( + indoc!( + r#" + capture : _ -> ({} -> Str) + capture = \val -> + \{} -> + Num.toStr val + + x : [True, False] + x = True + + fun = + when x is + True -> capture 123u64 + False -> capture 18u8 + + fun {} + "# + ), + RocStr::from("123"), + RocStr + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn lambda_capture_niches_with_other_lambda_capture() { + assert_evals_to!( + indoc!( + r#" + capture : _ -> ({} -> Str) + capture = \val -> + \{} -> + Num.toStr val + + capture2 = \val -> \{} -> val + + f = \x -> + g = + when x is + A -> capture 11u8 + B -> capture2 "lisa" + C -> capture 187128u64 + g {} + + {a: f A, b: f B, c: f C} + "# + ), + ( + RocStr::from("11"), + RocStr::from("lisa"), + RocStr::from("187128") + ), + (RocStr, RocStr, RocStr) + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn lambda_capture_niches_with_non_capturing_function() { + assert_evals_to!( + indoc!( + r#" + capture : _ -> ({} -> Str) + capture = \val -> + \{} -> + Num.toStr val + + triv = \{} -> "triv" + + f = \x -> + g = + when x is + A -> capture 11u8 + B -> triv + C -> capture 187128u64 + g {} + + {a: f A, b: f B, c: f C} + "# + ), + ( + RocStr::from("11"), + RocStr::from("triv"), + RocStr::from("187128") + ), + (RocStr, RocStr, RocStr) + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn lambda_capture_niches_have_captured_function_in_closure() { + assert_evals_to!( + indoc!( + r#" + Lazy a : {} -> a + + after : Lazy a, (a -> Lazy b) -> Lazy b + after = \effect, map -> + thunk = \{} -> + when map (effect {}) is + b -> b {} + thunk + + f = \_ -> \_ -> "fun f" + g = \{ s1 } -> \_ -> s1 + + fun = \x -> + h = + when x is + True -> after (\{} -> "") f + False -> after (\{} -> {s1: "s1"}) g + h {} + + {a: fun False, b: fun True} + "# + ), + (RocStr::from("s1"), RocStr::from("fun f")), + (RocStr, RocStr) + ) +} diff --git a/crates/compiler/test_gen/src/gen_refcount.rs b/crates/compiler/test_gen/src/gen_refcount.rs index e707495919..9024d96a44 100644 --- a/crates/compiler/test_gen/src/gen_refcount.rs +++ b/crates/compiler/test_gen/src/gen_refcount.rs @@ -431,3 +431,46 @@ fn union_linked_list_long_dec() { &[Deallocated; 1_000] ); } + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn boxed_str_inc() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + b = Box.box s + + Tuple b b + "# + ), + (Pointer, Pointer), + &[ + Live(2), // s + Live(2), // b + ] + ); +} + +#[test] +#[cfg(any(feature = "gen-wasm"))] +fn boxed_str_dec() { + assert_refcounts!( + indoc!( + r#" + s = Str.concat "A long enough string " "to be heap-allocated" + b = Box.box s + + if False then + ReturnTheBox b + else + DeallocateEverything + "# + ), + (i32, i32), + &[ + Deallocated, // s + Deallocated, // b + ] + ); +} diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index a8b78b0e58..90f7223932 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -25,7 +25,7 @@ fn str_split_empty_delimiter() { "# ), 1, - i64 + usize ); assert_evals_to!( @@ -41,7 +41,7 @@ fn str_split_empty_delimiter() { "# ), 3, - i64 + usize ); } @@ -55,7 +55,7 @@ fn str_split_bigger_delimiter_small_str() { "# ), 1, - i64 + usize ); assert_evals_to!( @@ -71,7 +71,7 @@ fn str_split_bigger_delimiter_small_str() { "# ), 3, - i64 + usize ); } @@ -210,7 +210,7 @@ fn str_split_small_str_big_delimiter() { "# ), 3, - i64 + usize ); assert_evals_to!( diff --git a/crates/compiler/test_gen/src/wasm_linking.rs b/crates/compiler/test_gen/src/wasm_linking.rs index e911bb3c7a..a3e1c7eab2 100644 --- a/crates/compiler/test_gen/src/wasm_linking.rs +++ b/crates/compiler/test_gen/src/wasm_linking.rs @@ -18,7 +18,7 @@ use roc_mono::ir::{ Call, CallType, Expr, HostExposedLayouts, Literal, Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId, }; -use roc_mono::layout::{Builtin, Layout}; +use roc_mono::layout::{Builtin, CapturesNiche, LambdaName, Layout}; const LINKING_TEST_HOST_WASM: &str = "build/wasm_linking_test_host.wasm"; const LINKING_TEST_HOST_NATIVE: &str = "build/wasm_linking_test_host"; @@ -110,7 +110,7 @@ fn build_app_mono<'a>( ); let proc = Proc { - name: app_proc, + name: LambdaName::no_niche(app_proc), args: &[], body, closure_data_layout: None, @@ -123,6 +123,7 @@ fn build_app_mono<'a>( let proc_layout = ProcLayout { arguments: &[], result: int_layout, + captures_niche: CapturesNiche::no_niche(), }; let mut app = MutMap::default(); diff --git a/crates/compiler/test_gen/src/wasm_str.rs b/crates/compiler/test_gen/src/wasm_str.rs index 52bcf50293..e2458572ac 100644 --- a/crates/compiler/test_gen/src/wasm_str.rs +++ b/crates/compiler/test_gen/src/wasm_str.rs @@ -14,257 +14,255 @@ use crate::helpers::wasm::assert_evals_to; use indoc::indoc; use roc_std::{RocList, RocStr}; -// #[test] -// fn str_split_empty_delimiter() { -// assert_evals_to!( -// indoc!( -// r#" -// List.len (Str.split "hello" "") -// "# -// ), -// 1, -// i64 -// ); +#[test] +fn str_split_empty_delimiter() { + assert_evals_to!( + indoc!( + r#" + List.len (Str.split "hello" "") + "# + ), + 1, + usize + ); +} -// assert_evals_to!( -// indoc!( -// r#" -// when List.first (Str.split "JJJ" "") is -// Ok str -> -// Str.countGraphemes str +// This test produces an app that exposes nothing to the host! +#[test] +#[ignore] +fn str_split_empty_delimiter_broken() { + assert_evals_to!( + indoc!( + r#" + when List.first (Str.split "JJJ" "") is + Ok str -> + Str.countGraphemes str -// _ -> -// -1 + _ -> + -1 -// "# -// ), -// 3, -// i64 -// ); -// } + "# + ), + 3, + usize + ); +} -// #[test] -// fn str_split_bigger_delimiter_small_str() { -// assert_evals_to!( -// indoc!( -// r#" -// List.len (Str.split "hello" "JJJJ there") -// "# -// ), -// 1, -// i64 -// ); +#[test] +fn str_split_bigger_delimiter_small_str() { + assert_evals_to!( + indoc!( + r#" + List.len (Str.split "hello" "JJJJ there") + "# + ), + 1, + usize + ); +} -// assert_evals_to!( -// indoc!( -// r#" -// when List.first (Str.split "JJJ" "JJJJ there") is -// Ok str -> -// Str.countGraphemes str +// This test produces an app that exposes nothing to the host! +#[test] +#[ignore] +fn str_split_bigger_delimiter_small_str_broken() { + assert_evals_to!( + indoc!( + r#" + when List.first (Str.split "JJJ" "JJJJ there") is + Ok str -> + Str.countGraphemes str -// _ -> -// -1 + _ -> + -1 -// "# -// ), -// 3, -// i64 -// ); -// } + "# + ), + 3, + usize + ); +} -// #[test] -// fn str_split_str_concat_repeated() { -// assert_evals_to!( -// indoc!( -// r#" -// when List.first (Str.split "JJJJJ" "JJJJ there") is -// Ok str -> -// str -// |> Str.concat str -// |> Str.concat str -// |> Str.concat str -// |> Str.concat str +#[test] +fn str_split_str_concat_repeated() { + assert_evals_to!( + indoc!( + r#" + when List.first (Str.split "JJJJJ" "JJJJ there") is + Ok str -> + str + |> Str.concat str + |> Str.concat str + |> Str.concat str + |> Str.concat str -// _ -> -// "Not Str!" + _ -> + "Not Str!" -// "# -// ), -// RocStr::from_slice_unchecked(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"), -// RocStr -// ); -// } + "# + ), + RocStr::from("JJJJJJJJJJJJJJJJJJJJJJJJJ"), + RocStr + ); +} -// #[test] -// fn str_split_small_str_bigger_delimiter() { -// assert_evals_to!( -// indoc!( -// r#" -// when -// List.first -// (Str.split "JJJ" "0123456789abcdefghi") -// is -// Ok str -> str -// _ -> "" -// "# -// ), -// RocStr::from_slice_unchecked(b"JJJ"), -// RocStr -// ); -// } +#[test] +fn str_split_small_str_bigger_delimiter() { + assert_evals_to!( + indoc!( + r#" + when + List.first + (Str.split "JJJ" "0123456789abcdefghi") + is + Ok str -> str + _ -> "" + "# + ), + RocStr::from("JJJ"), + RocStr + ); +} -// #[test] -// fn str_split_big_str_small_delimiter() { -// assert_evals_to!( -// indoc!( -// r#" -// Str.split "01234567789abcdefghi?01234567789abcdefghi" "?" -// "# -// ), -// RocList::from_slice(&[ -// RocStr::from_slice_unchecked(b"01234567789abcdefghi"), -// RocStr::from_slice_unchecked(b"01234567789abcdefghi") -// ]), -// RocList -// ); +#[test] +fn str_split_big_str_small_delimiter() { + assert_evals_to!( + indoc!( + r#" + Str.split "01234567789abcdefghi?01234567789abcdefghi" "?" + "# + ), + RocList::from_slice(&[ + RocStr::from("01234567789abcdefghi"), + RocStr::from("01234567789abcdefghi") + ]), + RocList + ); -// assert_evals_to!( -// indoc!( -// r#" -// Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch" -// "# -// ), -// RocList::from_slice(&[ -// RocStr::from_slice_unchecked(b"01234567789abcdefghi "), -// RocStr::from_slice_unchecked(b" 01234567789abcdefghi") -// ]), -// RocList -// ); -// } + assert_evals_to!( + indoc!( + r#" + Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch" + "# + ), + RocList::from_slice(&[ + RocStr::from("01234567789abcdefghi "), + RocStr::from(" 01234567789abcdefghi") + ]), + RocList + ); +} -// #[test] -// fn str_split_small_str_small_delimiter() { -// assert_evals_to!( -// indoc!( -// r#" -// Str.split "J!J!J" "!" -// "# -// ), -// RocList::from_slice(&[ -// RocStr::from_slice_unchecked(b"J"), -// RocStr::from_slice_unchecked(b"J"), -// RocStr::from_slice_unchecked(b"J") -// ]), -// RocList -// ); -// } +#[test] +fn str_split_small_str_small_delimiter() { + assert_evals_to!( + indoc!( + r#" + Str.split "J!J!J" "!" + "# + ), + RocList::from_slice(&[RocStr::from("J"), RocStr::from("J"), RocStr::from("J")]), + RocList + ); +} -// #[test] -// fn str_split_bigger_delimiter_big_strs() { -// assert_evals_to!( -// indoc!( -// r#" -// Str.split -// "string to split is shorter" -// "than the delimiter which happens to be very very long" -// "# -// ), -// RocList::from_slice(&[RocStr::from_slice_unchecked(b"string to split is shorter")]), -// RocList -// ); -// } +#[test] +fn str_split_bigger_delimiter_big_strs() { + assert_evals_to!( + indoc!( + r#" + Str.split + "string to split is shorter" + "than the delimiter which happens to be very very long" + "# + ), + RocList::from_slice(&[RocStr::from("string to split is shorter")]), + RocList + ); +} -// #[test] -// fn str_split_empty_strs() { -// assert_evals_to!( -// indoc!( -// r#" -// Str.split "" "" -// "# -// ), -// RocList::from_slice(&[RocStr::from_slice_unchecked(b"")]), -// RocList -// ); -// } +#[test] +fn str_split_empty_strs() { + assert_evals_to!( + indoc!( + r#" + Str.split "" "" + "# + ), + RocList::from_slice(&[RocStr::from("")]), + RocList + ); +} -// #[test] -// fn str_split_minimal_example() { -// assert_evals_to!( -// indoc!( -// r#" -// Str.split "a," "," -// "# -// ), -// RocList::from_slice(&[RocStr::from_slice_unchecked(b"a"), RocStr::from_slice_unchecked(b"")]), -// RocList -// ) -// } +#[test] +fn str_split_minimal_example() { + assert_evals_to!( + indoc!( + r#" + Str.split "a," "," + "# + ), + RocList::from_slice(&[RocStr::from("a"), RocStr::from("")]), + RocList + ) +} -// #[test] -// fn str_split_small_str_big_delimiter() { -// assert_evals_to!( -// indoc!( -// r#" -// Str.split -// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" -// "---- ---- ---- ---- ----" -// |> List.len -// "# -// ), -// 3, -// i64 -// ); +#[test] +fn str_split_small_str_big_delimiter() { + assert_evals_to!( + indoc!( + r#" + Str.split + "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" + "---- ---- ---- ---- ----" + |> List.len + "# + ), + 3, + usize + ); -// assert_evals_to!( -// indoc!( -// r#" -// Str.split -// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" -// "---- ---- ---- ---- ----" -// "# -// ), -// RocList::from_slice(&[ -// RocStr::from_slice_unchecked(b"1"), -// RocStr::from_slice_unchecked(b"2"), -// RocStr::from_slice_unchecked(b"") -// ]), -// RocList -// ); -// } + assert_evals_to!( + indoc!( + r#" + Str.split + "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" + "---- ---- ---- ---- ----" + "# + ), + RocList::from_slice(&[RocStr::from("1"), RocStr::from("2"), RocStr::from("")]), + RocList + ); +} -// #[test] -// fn str_split_small_str_20_char_delimiter() { -// assert_evals_to!( -// indoc!( -// r#" -// Str.split -// "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |" -// "|-- -- -- -- -- -- |" -// "# -// ), -// RocList::from_slice(&[ -// RocStr::from_slice_unchecked(b"3"), -// RocStr::from_slice_unchecked(b"4"), -// RocStr::from_slice_unchecked(b"") -// ]), -// RocList -// ); -// } +#[test] +fn str_split_small_str_20_char_delimiter() { + assert_evals_to!( + indoc!( + r#" + Str.split + "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |" + "|-- -- -- -- -- -- |" + "# + ), + RocList::from_slice(&[RocStr::from("3"), RocStr::from("4"), RocStr::from("")]), + RocList + ); +} -// #[test] -// fn str_concat_big_to_big() { -// assert_evals_to!( -// indoc!( -// r#" -// Str.concat -// "First string that is fairly long. Longer strings make for different errors. " -// "Second string that is also fairly long. Two long strings test things that might not appear with short strings." -// "# -// ), -// RocStr::from_slice_unchecked(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."), -// RocStr -// ); -// } +#[test] +fn str_concat_big_to_big() { + assert_evals_to!( + indoc!( + r#" + Str.concat + "First string that is fairly long. Longer strings make for different errors. " + "Second string that is also fairly long. Two long strings test things that might not appear with short strings." + "# + ), + RocStr::from("First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."), + RocStr + ); +} #[test] #[cfg(any(feature = "gen-wasm"))] @@ -488,224 +486,224 @@ fn str_starts_with_false_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); } -// #[test] -// fn str_from_utf8_pass_single_ascii() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [97] is -// Ok val -> val -// Err _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("a".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_pass_single_ascii() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [97] is + Ok val -> val + Err _ -> "" + "# + ), + roc_std::RocStr::from("a"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_pass_many_ascii() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [97, 98, 99, 0x7E] is -// Ok val -> val -// Err _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("abc~".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_pass_many_ascii() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [97, 98, 99, 0x7E] is + Ok val -> val + Err _ -> "" + "# + ), + roc_std::RocStr::from("abc~"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_pass_single_unicode() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [0xE2, 0x88, 0x86] is -// Ok val -> val -// Err _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("∆".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_pass_single_unicode() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [0xE2, 0x88, 0x86] is + Ok val -> val + Err _ -> "" + "# + ), + roc_std::RocStr::from("∆"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_pass_many_unicode() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC] is -// Ok val -> val -// Err _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("∆œ¬".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_pass_many_unicode() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC] is + Ok val -> val + Err _ -> "" + "# + ), + roc_std::RocStr::from("∆œ¬"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_pass_single_grapheme() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [0xF0, 0x9F, 0x92, 0x96] is -// Ok val -> val -// Err _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("💖".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_pass_single_grapheme() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [0xF0, 0x9F, 0x92, 0x96] is + Ok val -> val + Err _ -> "" + "# + ), + roc_std::RocStr::from("💖"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_pass_many_grapheme() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80] is -// Ok val -> val -// Err _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("💖🤠🚀".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_pass_many_grapheme() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80] is + Ok val -> val + Err _ -> "" + "# + ), + roc_std::RocStr::from("💖🤠🚀"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_pass_all() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86] is -// Ok val -> val -// Err _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("💖b∆".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_pass_all() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86] is + Ok val -> val + Err _ -> "" + "# + ), + roc_std::RocStr::from("💖b∆"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_fail_invalid_start_byte() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [97, 98, 0x80, 99] is -// Err (BadUtf8 InvalidStartByte byteIndex) -> -// if byteIndex == 2 then -// "a" -// else -// "b" -// _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("a".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_fail_invalid_start_byte() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [97, 98, 0x80, 99] is + Err (BadUtf8 InvalidStartByte byteIndex) -> + if byteIndex == 2 then + "a" + else + "b" + _ -> "" + "# + ), + roc_std::RocStr::from("a"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_fail_unexpected_end_of_sequence() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [97, 98, 99, 0xC2] is -// Err (BadUtf8 UnexpectedEndOfSequence byteIndex) -> -// if byteIndex == 3 then -// "a" -// else -// "b" -// _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("a".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_fail_unexpected_end_of_sequence() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [97, 98, 99, 0xC2] is + Err (BadUtf8 UnexpectedEndOfSequence byteIndex) -> + if byteIndex == 3 then + "a" + else + "b" + _ -> "" + "# + ), + roc_std::RocStr::from("a"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_fail_expected_continuation() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [97, 98, 99, 0xC2, 0x00] is -// Err (BadUtf8 ExpectedContinuation byteIndex) -> -// if byteIndex == 3 then -// "a" -// else -// "b" -// _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("a".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_fail_expected_continuation() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [97, 98, 99, 0xC2, 0x00] is + Err (BadUtf8 ExpectedContinuation byteIndex) -> + if byteIndex == 3 then + "a" + else + "b" + _ -> "" + "# + ), + roc_std::RocStr::from("a"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_fail_overlong_encoding() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [97, 0xF0, 0x80, 0x80, 0x80] is -// Err (BadUtf8 OverlongEncoding byteIndex) -> -// if byteIndex == 1 then -// "a" -// else -// "b" -// _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("a".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_fail_overlong_encoding() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [97, 0xF0, 0x80, 0x80, 0x80] is + Err (BadUtf8 OverlongEncoding byteIndex) -> + if byteIndex == 1 then + "a" + else + "b" + _ -> "" + "# + ), + roc_std::RocStr::from("a"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_fail_codepoint_too_large() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [97, 0xF4, 0x90, 0x80, 0x80] is -// Err (BadUtf8 CodepointTooLarge byteIndex) -> -// if byteIndex == 1 then -// "a" -// else -// "b" -// _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("a".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_fail_codepoint_too_large() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [97, 0xF4, 0x90, 0x80, 0x80] is + Err (BadUtf8 CodepointTooLarge byteIndex) -> + if byteIndex == 1 then + "a" + else + "b" + _ -> "" + "# + ), + roc_std::RocStr::from("a"), + roc_std::RocStr + ); +} -// #[test] -// fn str_from_utf8_fail_surrogate_half() { -// assert_evals_to!( -// indoc!( -// r#" -// when Str.fromUtf8 [97, 98, 0xED, 0xA0, 0x80] is -// Err (BadUtf8 EncodesSurrogateHalf byteIndex) -> -// if byteIndex == 2 then -// "a" -// else -// "b" -// _ -> "" -// "# -// ), -// roc_std::RocStr::from_slice_unchecked("a".as_bytes()), -// roc_std::RocStr -// ); -// } +#[test] +fn str_from_utf8_fail_surrogate_half() { + assert_evals_to!( + indoc!( + r#" + when Str.fromUtf8 [97, 98, 0xED, 0xA0, 0x80] is + Err (BadUtf8 EncodesSurrogateHalf byteIndex) -> + if byteIndex == 2 then + "a" + else + "b" + _ -> "" + "# + ), + roc_std::RocStr::from("a"), + roc_std::RocStr + ); +} #[test] fn str_equality() { @@ -719,36 +717,6 @@ fn str_equality() { assert_evals_to!(r#""a" == "b""#, false, bool); } -// #[test] -// fn nested_recursive_literal() { -// assert_evals_to!( -// indoc!( -// r#" -// Expr : [Add Expr Expr, Val I64, Var I64] - -// expr : Expr -// expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1)) - -// printExpr : Expr -> Str -// printExpr = \e -> -// when e is -// Add a b -> -// "Add (" -// |> Str.concat (printExpr a) -// |> Str.concat ") (" -// |> Str.concat (printExpr b) -// |> Str.concat ")" -// Val v -> "Val " |> Str.concat (Num.toStr v) -// Var v -> "Var " |> Str.concat (Num.toStr v) - -// printExpr expr -// "# -// ), -// RocStr::from_slice_unchecked(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"), -// RocStr -// ); -// } - #[test] fn str_join_comma_small() { assert_evals_to!( @@ -789,120 +757,120 @@ fn str_to_utf8() { ); } -// #[test] -// fn str_from_utf8_range() { -// assert_evals_to!( -// indoc!( -// r#" -// bytes = Str.toUtf8 "hello" -// when Str.fromUtf8Range bytes { count: 5, start: 0 } is -// Ok utf8String -> utf8String -// _ -> "" -// "# -// ), -// RocStr::from("hello"), -// RocStr -// ); -// } +#[test] +fn str_from_utf8_range() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { count: 5, start: 0 } is + Ok utf8String -> utf8String + _ -> "" + "# + ), + RocStr::from("hello"), + RocStr + ); +} -// #[test] -// fn str_from_utf8_range_slice() { -// assert_evals_to!( -// indoc!( -// r#" -// bytes = Str.toUtf8 "hello" -// when Str.fromUtf8Range bytes { count: 4, start: 1 } is -// Ok utf8String -> utf8String -// _ -> "" -// "# -// ), -// RocStr::from("ello"), -// RocStr -// ); -// } +#[test] +fn str_from_utf8_range_slice() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { count: 4, start: 1 } is + Ok utf8String -> utf8String + _ -> "" + "# + ), + RocStr::from("ello"), + RocStr + ); +} -// #[test] -// fn str_from_utf8_range_slice_not_end() { -// assert_evals_to!( -// indoc!( -// r#" -// bytes = Str.toUtf8 "hello" -// when Str.fromUtf8Range bytes { count: 3, start: 1 } is -// Ok utf8String -> utf8String -// _ -> "" -// "# -// ), -// RocStr::from("ell"), -// RocStr -// ); -// } +#[test] +fn str_from_utf8_range_slice_not_end() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { count: 3, start: 1 } is + Ok utf8String -> utf8String + _ -> "" + "# + ), + RocStr::from("ell"), + RocStr + ); +} -// #[test] -// fn str_from_utf8_range_order_does_not_matter() { -// assert_evals_to!( -// indoc!( -// r#" -// bytes = Str.toUtf8 "hello" -// when Str.fromUtf8Range bytes { start: 1, count: 3 } is -// Ok utf8String -> utf8String -// _ -> "" -// "# -// ), -// RocStr::from("ell"), -// RocStr -// ); -// } +#[test] +fn str_from_utf8_range_order_does_not_matter() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { start: 1, count: 3 } is + Ok utf8String -> utf8String + _ -> "" + "# + ), + RocStr::from("ell"), + RocStr + ); +} -// #[test] -// fn str_from_utf8_range_out_of_bounds_start_value() { -// assert_evals_to!( -// indoc!( -// r#" -// bytes = Str.toUtf8 "hello" -// when Str.fromUtf8Range bytes { start: 7, count: 3 } is -// Ok _ -> "" -// Err (BadUtf8 _ _) -> "" -// Err OutOfBounds -> "out of bounds" -// "# -// ), -// RocStr::from("out of bounds"), -// RocStr -// ); -// } +#[test] +fn str_from_utf8_range_out_of_bounds_start_value() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { start: 7, count: 3 } is + Ok _ -> "" + Err (BadUtf8 _ _) -> "" + Err OutOfBounds -> "out of bounds" + "# + ), + RocStr::from("out of bounds"), + RocStr + ); +} -// #[test] -// fn str_from_utf8_range_count_too_high() { -// assert_evals_to!( -// indoc!( -// r#" -// bytes = Str.toUtf8 "hello" -// when Str.fromUtf8Range bytes { start: 0, count: 6 } is -// Ok _ -> "" -// Err (BadUtf8 _ _) -> "" -// Err OutOfBounds -> "out of bounds" -// "# -// ), -// RocStr::from("out of bounds"), -// RocStr -// ); -// } +#[test] +fn str_from_utf8_range_count_too_high() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { start: 0, count: 6 } is + Ok _ -> "" + Err (BadUtf8 _ _) -> "" + Err OutOfBounds -> "out of bounds" + "# + ), + RocStr::from("out of bounds"), + RocStr + ); +} -// #[test] -// fn str_from_utf8_range_count_too_high_for_start() { -// assert_evals_to!( -// indoc!( -// r#" -// bytes = Str.toUtf8 "hello" -// when Str.fromUtf8Range bytes { start: 4, count: 3 } is -// Ok _ -> "" -// Err (BadUtf8 _ _) -> "" -// Err OutOfBounds -> "out of bounds" -// "# -// ), -// RocStr::from("out of bounds"), -// RocStr -// ); -// } +#[test] +fn str_from_utf8_range_count_too_high_for_start() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Str.fromUtf8Range bytes { start: 4, count: 3 } is + Ok _ -> "" + Err (BadUtf8 _ _) -> "" + Err OutOfBounds -> "out of bounds" + "# + ), + RocStr::from("out of bounds"), + RocStr + ); +} #[test] fn str_repeat_small() { diff --git a/crates/compiler/test_mono/generated/closure_in_list.txt b/crates/compiler/test_mono/generated/closure_in_list.txt index e26803f469..8efa4cf634 100644 --- a/crates/compiler/test_mono/generated/closure_in_list.txt +++ b/crates/compiler/test_mono/generated/closure_in_list.txt @@ -1,6 +1,6 @@ procedure List.6 (#Attr.2): - let List.284 : U64 = lowlevel ListLen #Attr.2; - ret List.284; + let List.266 : U64 = lowlevel ListLen #Attr.2; + ret List.266; procedure Test.1 (Test.5): let Test.2 : I64 = 41i64; diff --git a/crates/compiler/test_mono/generated/dict.txt b/crates/compiler/test_mono/generated/dict.txt index 31ae33d719..bd378e9414 100644 --- a/crates/compiler/test_mono/generated/dict.txt +++ b/crates/compiler/test_mono/generated/dict.txt @@ -1,11 +1,11 @@ procedure Dict.1 (): - let Dict.28 : Dict [] [] = lowlevel DictEmpty ; - ret Dict.28; + let Dict.16 : Dict [] [] = lowlevel DictEmpty ; + ret Dict.16; procedure Dict.7 (#Attr.2): - let Dict.27 : U64 = lowlevel DictSize #Attr.2; + let Dict.15 : U64 = lowlevel DictSize #Attr.2; dec #Attr.2; - ret Dict.27; + ret Dict.15; procedure Test.0 (): let Test.2 : Dict [] [] = CallByName Dict.1; diff --git a/crates/compiler/test_mono/generated/empty_list_of_function_type.txt b/crates/compiler/test_mono/generated/empty_list_of_function_type.txt index 617e58feba..f378649fab 100644 --- a/crates/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/crates/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -1,26 +1,26 @@ -procedure List.2 (List.75, List.76): - let List.290 : U64 = CallByName List.6 List.75; - let List.286 : Int1 = CallByName Num.22 List.76 List.290; - if List.286 then - let List.288 : {} = CallByName List.60 List.75 List.76; - let List.287 : [C {}, C {}] = Ok List.288; - ret List.287; +procedure List.2 (List.74, List.75): + let List.272 : U64 = CallByName List.6 List.74; + let List.268 : Int1 = CallByName Num.22 List.75 List.272; + if List.268 then + let List.270 : {} = CallByName List.60 List.74 List.75; + let List.269 : [C {}, C {}] = TagId(1) List.270; + ret List.269; else - let List.285 : {} = Struct {}; - let List.284 : [C {}, C {}] = Err List.285; - ret List.284; + let List.267 : {} = Struct {}; + let List.266 : [C {}, C {}] = TagId(0) List.267; + ret List.266; procedure List.6 (#Attr.2): - let List.293 : U64 = lowlevel ListLen #Attr.2; - ret List.293; + let List.275 : U64 = lowlevel ListLen #Attr.2; + ret List.275; procedure List.60 (#Attr.2, #Attr.3): - let List.292 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.292; + let List.274 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.274; procedure Num.22 (#Attr.2, #Attr.3): - let Num.273 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.188; procedure Test.2 (Test.6): let Test.18 : Str = "bar"; diff --git a/crates/compiler/test_mono/generated/encode.txt b/crates/compiler/test_mono/generated/encode.txt index cf24697b6f..fdebaf3203 100644 --- a/crates/compiler/test_mono/generated/encode.txt +++ b/crates/compiler/test_mono/generated/encode.txt @@ -1,6 +1,6 @@ procedure List.4 (#Attr.2, #Attr.3): - let List.284 : List U8 = lowlevel ListAppend #Attr.2 #Attr.3; - ret List.284; + let List.266 : List U8 = lowlevel ListAppend #Attr.2 #Attr.3; + ret List.266; procedure Test.20 (Test.22): let Test.34 : {U8} = Struct {Test.22}; diff --git a/crates/compiler/test_mono/generated/factorial.txt b/crates/compiler/test_mono/generated/factorial.txt index a0ad7c91ac..165dc059a3 100644 --- a/crates/compiler/test_mono/generated/factorial.txt +++ b/crates/compiler/test_mono/generated/factorial.txt @@ -1,10 +1,10 @@ procedure Num.20 (#Attr.2, #Attr.3): - let Num.274 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.274; + let Num.189 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.189; procedure Num.21 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.188; procedure Test.1 (Test.15, Test.16): joinpoint Test.7 Test.2 Test.3: diff --git a/crates/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/crates/compiler/test_mono/generated/if_guard_bind_variable_false.txt index cf492ae71d..7d73011b72 100644 --- a/crates/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/crates/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -1,6 +1,6 @@ procedure Bool.7 (#Attr.2, #Attr.3): - let Bool.14 : Int1 = lowlevel Eq #Attr.2 #Attr.3; - ret Bool.14; + let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3; + ret Bool.9; procedure Test.1 (Test.3): let Test.6 : I64 = 10i64; diff --git a/crates/compiler/test_mono/generated/ir_int_add.txt b/crates/compiler/test_mono/generated/ir_int_add.txt index 60f044c4cd..f4d92f2cfc 100644 --- a/crates/compiler/test_mono/generated/ir_int_add.txt +++ b/crates/compiler/test_mono/generated/ir_int_add.txt @@ -1,10 +1,10 @@ procedure List.6 (#Attr.2): - let List.284 : U64 = lowlevel ListLen #Attr.2; - ret List.284; + let List.266 : U64 = lowlevel ListLen #Attr.2; + ret List.266; procedure Num.19 (#Attr.2, #Attr.3): - let Num.275 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.275; + let Num.190 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.190; procedure Test.0 (): let Test.1 : List I64 = Array [1i64, 2i64]; diff --git a/crates/compiler/test_mono/generated/ir_plus.txt b/crates/compiler/test_mono/generated/ir_plus.txt index 8a3eff1485..7a8550c5e0 100644 --- a/crates/compiler/test_mono/generated/ir_plus.txt +++ b/crates/compiler/test_mono/generated/ir_plus.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.2 : I64 = 1i64; diff --git a/crates/compiler/test_mono/generated/ir_round.txt b/crates/compiler/test_mono/generated/ir_round.txt index 19f78559ec..233527563b 100644 --- a/crates/compiler/test_mono/generated/ir_round.txt +++ b/crates/compiler/test_mono/generated/ir_round.txt @@ -1,6 +1,6 @@ procedure Num.45 (#Attr.2): - let Num.273 : I64 = lowlevel NumRound #Attr.2; - ret Num.273; + let Num.188 : I64 = lowlevel NumRound #Attr.2; + ret Num.188; procedure Test.0 (): let Test.2 : Float64 = 3.6f64; diff --git a/crates/compiler/test_mono/generated/ir_two_defs.txt b/crates/compiler/test_mono/generated/ir_two_defs.txt index bdfba4a214..52214a8c65 100644 --- a/crates/compiler/test_mono/generated/ir_two_defs.txt +++ b/crates/compiler/test_mono/generated/ir_two_defs.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.1 : I64 = 3i64; diff --git a/crates/compiler/test_mono/generated/ir_when_idiv.txt b/crates/compiler/test_mono/generated/ir_when_idiv.txt index a4c3915acf..fc0e6e7ffb 100644 --- a/crates/compiler/test_mono/generated/ir_when_idiv.txt +++ b/crates/compiler/test_mono/generated/ir_when_idiv.txt @@ -1,14 +1,14 @@ procedure Num.40 (#Attr.2, #Attr.3): - let Num.278 : I64 = 0i64; - let Num.275 : Int1 = lowlevel NotEq #Attr.3 Num.278; - if Num.275 then - let Num.277 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; - let Num.276 : [C {}, C I64] = Ok Num.277; - ret Num.276; + let Num.193 : I64 = 0i64; + let Num.190 : Int1 = lowlevel NotEq #Attr.3 Num.193; + if Num.190 then + let Num.192 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; + let Num.191 : [C {}, C I64] = TagId(1) Num.192; + ret Num.191; else - let Num.274 : {} = Struct {}; - let Num.273 : [C {}, C I64] = Err Num.274; - ret Num.273; + let Num.189 : {} = Struct {}; + let Num.188 : [C {}, C I64] = TagId(0) Num.189; + ret Num.188; procedure Test.0 (): let Test.8 : I64 = 1000i64; diff --git a/crates/compiler/test_mono/generated/ir_when_just.txt b/crates/compiler/test_mono/generated/ir_when_just.txt index d6619036ab..e6b67860ca 100644 --- a/crates/compiler/test_mono/generated/ir_when_just.txt +++ b/crates/compiler/test_mono/generated/ir_when_just.txt @@ -1,10 +1,10 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.10 : I64 = 41i64; - let Test.1 : [C I64, C ] = Just Test.10; + let Test.1 : [C I64, C ] = TagId(0) Test.10; let Test.7 : U8 = 0i64; let Test.8 : U8 = GetTagId Test.1; let Test.9 : Int1 = lowlevel Eq Test.7 Test.8; diff --git a/crates/compiler/test_mono/generated/ir_when_maybe.txt b/crates/compiler/test_mono/generated/ir_when_maybe.txt index e5a573c182..b038594df1 100644 --- a/crates/compiler/test_mono/generated/ir_when_maybe.txt +++ b/crates/compiler/test_mono/generated/ir_when_maybe.txt @@ -1,6 +1,6 @@ procedure Test.0 (): let Test.9 : I64 = 3i64; - let Test.3 : [C I64, C ] = Just Test.9; + let Test.3 : [C I64, C ] = TagId(0) Test.9; let Test.6 : U8 = 0i64; let Test.7 : U8 = GetTagId Test.3; let Test.8 : Int1 = lowlevel Eq Test.6 Test.7; diff --git a/crates/compiler/test_mono/generated/ir_when_these.txt b/crates/compiler/test_mono/generated/ir_when_these.txt index 0c7453e2b7..456c42f01c 100644 --- a/crates/compiler/test_mono/generated/ir_when_these.txt +++ b/crates/compiler/test_mono/generated/ir_when_these.txt @@ -1,7 +1,7 @@ procedure Test.0 (): let Test.10 : I64 = 1i64; let Test.11 : I64 = 2i64; - let Test.5 : [C I64, C I64 I64, C I64] = These Test.10 Test.11; + let Test.5 : [C I64, C I64 I64, C I64] = TagId(1) Test.10 Test.11; let Test.9 : U8 = GetTagId Test.5; switch Test.9: case 2: diff --git a/crates/compiler/test_mono/generated/is_nil.txt b/crates/compiler/test_mono/generated/is_nil.txt index 9b47d797ca..d12f69a017 100644 --- a/crates/compiler/test_mono/generated/is_nil.txt +++ b/crates/compiler/test_mono/generated/is_nil.txt @@ -11,8 +11,8 @@ procedure Test.3 (Test.4): procedure Test.0 (): let Test.16 : I64 = 2i64; - let Test.17 : [, C I64 *self] = Nil ; - let Test.10 : [, C I64 *self] = Cons Test.16 Test.17; + let Test.17 : [, C I64 *self] = TagId(1) ; + let Test.10 : [, C I64 *self] = TagId(0) Test.16 Test.17; let Test.9 : Int1 = CallByName Test.3 Test.10; dec Test.10; ret Test.9; diff --git a/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt b/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt index 1b388ac601..68ec0f3e24 100644 --- a/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt +++ b/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt @@ -1,55 +1,55 @@ -procedure List.2 (List.75, List.76): - let List.299 : U64 = CallByName List.6 List.75; - let List.295 : Int1 = CallByName Num.22 List.76 List.299; - if List.295 then - let List.297 : I64 = CallByName List.60 List.75 List.76; - let List.296 : [C {}, C I64] = Ok List.297; - ret List.296; +procedure List.2 (List.74, List.75): + let List.281 : U64 = CallByName List.6 List.74; + let List.277 : Int1 = CallByName Num.22 List.75 List.281; + if List.277 then + let List.279 : I64 = CallByName List.60 List.74 List.75; + let List.278 : [C {}, C I64] = TagId(1) List.279; + ret List.278; else - let List.294 : {} = Struct {}; - let List.293 : [C {}, C I64] = Err List.294; - ret List.293; + let List.276 : {} = Struct {}; + let List.275 : [C {}, C I64] = TagId(0) List.276; + ret List.275; procedure List.6 (#Attr.2): - let List.300 : U64 = lowlevel ListLen #Attr.2; - ret List.300; + let List.282 : U64 = lowlevel ListLen #Attr.2; + ret List.282; procedure List.60 (#Attr.2, #Attr.3): - let List.298 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.298; + let List.280 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.280; -procedure List.9 (List.202): - let List.291 : U64 = 0i64; - let List.284 : [C {}, C I64] = CallByName List.2 List.202 List.291; - let List.288 : U8 = 1i64; - let List.289 : U8 = GetTagId List.284; - let List.290 : Int1 = lowlevel Eq List.288 List.289; - if List.290 then - let List.203 : I64 = UnionAtIndex (Id 1) (Index 0) List.284; - let List.285 : [C Int1, C I64] = Ok List.203; - ret List.285; +procedure List.9 (List.188): + let List.273 : U64 = 0i64; + let List.266 : [C {}, C I64] = CallByName List.2 List.188 List.273; + let List.270 : U8 = 1i64; + let List.271 : U8 = GetTagId List.266; + let List.272 : Int1 = lowlevel Eq List.270 List.271; + if List.272 then + let List.189 : I64 = UnionAtIndex (Id 1) (Index 0) List.266; + let List.267 : [C Int1, C I64] = TagId(1) List.189; + ret List.267; else - let List.287 : Int1 = true; - let List.286 : [C Int1, C I64] = Err List.287; - ret List.286; + let List.269 : Int1 = true; + let List.268 : [C Int1, C I64] = TagId(0) List.269; + ret List.268; procedure Num.22 (#Attr.2, #Attr.3): - let Num.273 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.188; procedure Str.27 (#Attr.2): let #Attr.3 : {I64, U8} = lowlevel StrToNum #Attr.2; - let Str.154 : U8 = StructAtIndex 1 #Attr.3; - let Str.155 : U8 = 0i64; - let Str.151 : Int1 = lowlevel NumGt Str.154 Str.155; - if Str.151 then - let Str.153 : Int1 = false; - let Str.152 : [C Int1, C I64] = Err Str.153; - ret Str.152; + let Str.131 : U8 = StructAtIndex 1 #Attr.3; + let Str.132 : U8 = 0i64; + let Str.128 : Int1 = lowlevel NumGt Str.131 Str.132; + if Str.128 then + let Str.130 : Int1 = false; + let Str.129 : [C Int1, C I64] = TagId(0) Str.130; + ret Str.129; else - let Str.150 : I64 = StructAtIndex 0 #Attr.3; - let Str.149 : [C Int1, C I64] = Ok Str.150; - ret Str.149; + let Str.127 : I64 = StructAtIndex 0 #Attr.3; + let Str.126 : [C Int1, C I64] = TagId(1) Str.127; + ret Str.126; procedure Test.0 (): let Test.4 : Int1 = true; diff --git a/crates/compiler/test_mono/generated/issue_2810.txt b/crates/compiler/test_mono/generated/issue_2810.txt index 31f6b7125e..f898de15a3 100644 --- a/crates/compiler/test_mono/generated/issue_2810.txt +++ b/crates/compiler/test_mono/generated/issue_2810.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.19 : [C [C [C *self, C ]], C ] = SystemTool ; - let Test.17 : [C [C *self, C ]] = Job Test.19; - let Test.16 : [C [C [C *self, C ]], C ] = FromJob Test.17; - let Test.7 : [C [C *self, C ]] = Job Test.16; + let Test.19 : [C [C [C *self, C ]], C ] = TagId(1) ; + let Test.17 : [C [C *self, C ]] = TagId(0) Test.19; + let Test.16 : [C [C [C *self, C ]], C ] = TagId(0) Test.17; + let Test.7 : [C [C *self, C ]] = TagId(0) Test.16; ret Test.7; diff --git a/crates/compiler/test_mono/generated/lambda_capture_niche_u8_vs_u64.txt b/crates/compiler/test_mono/generated/lambda_capture_niche_u8_vs_u64.txt new file mode 100644 index 0000000000..da0ea30610 --- /dev/null +++ b/crates/compiler/test_mono/generated/lambda_capture_niche_u8_vs_u64.txt @@ -0,0 +1,54 @@ +procedure Num.94 (#Attr.2): + let Num.188 : Str = lowlevel NumToStr #Attr.2; + ret Num.188; + +procedure Num.94 (#Attr.2): + let Num.189 : Str = lowlevel NumToStr #Attr.2; + ret Num.189; + +procedure Test.1 (Test.4): + let Test.16 : [C U8, C U64] = TagId(1) Test.4; + ret Test.16; + +procedure Test.1 (Test.4): + let Test.22 : [C U8, C U64] = TagId(0) Test.4; + ret Test.22; + +procedure Test.5 (Test.17, #Attr.12): + let Test.4 : U64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.19 : Str = CallByName Num.94 Test.4; + ret Test.19; + +procedure Test.5 (Test.17, #Attr.12): + let Test.4 : U8 = UnionAtIndex (Id 0) (Index 0) #Attr.12; + let Test.25 : Str = CallByName Num.94 Test.4; + ret Test.25; + +procedure Test.0 (): + let Test.2 : Int1 = true; + joinpoint Test.13 Test.3: + let Test.8 : {} = Struct {}; + let Test.9 : U8 = GetTagId Test.3; + joinpoint Test.10 Test.7: + ret Test.7; + in + switch Test.9: + case 0: + let Test.11 : Str = CallByName Test.5 Test.8 Test.3; + jump Test.10 Test.11; + + default: + let Test.12 : Str = CallByName Test.5 Test.8 Test.3; + jump Test.10 Test.12; + + in + let Test.26 : Int1 = true; + let Test.27 : Int1 = lowlevel Eq Test.26 Test.2; + if Test.27 then + let Test.15 : U64 = 123i64; + let Test.14 : [C U8, C U64] = CallByName Test.1 Test.15; + jump Test.13 Test.14; + else + let Test.21 : U8 = 18i64; + let Test.20 : [C U8, C U64] = CallByName Test.1 Test.21; + jump Test.13 Test.20; diff --git a/crates/compiler/test_mono/generated/lambda_capture_niches_have_captured_function_in_closure.txt b/crates/compiler/test_mono/generated/lambda_capture_niches_have_captured_function_in_closure.txt new file mode 100644 index 0000000000..5228441c5b --- /dev/null +++ b/crates/compiler/test_mono/generated/lambda_capture_niches_have_captured_function_in_closure.txt @@ -0,0 +1,85 @@ +procedure Test.11 (Test.37): + let Test.38 : Str = ""; + ret Test.38; + +procedure Test.13 (Test.51, #Attr.12): + let Test.12 : Str = StructAtIndex 0 #Attr.12; + inc Test.12; + dec #Attr.12; + ret Test.12; + +procedure Test.15 (Test.39): + let Test.40 : Str = ""; + ret Test.40; + +procedure Test.16 (Test.54): + let Test.56 : Str = "s1"; + ret Test.56; + +procedure Test.2 (Test.7, Test.8): + let Test.9 : [C {} {}, C {} {}] = TagId(0) Test.7 Test.8; + ret Test.9; + +procedure Test.2 (Test.7, Test.8): + let Test.9 : [C {} {}, C {} {}] = TagId(1) Test.7 Test.8; + ret Test.9; + +procedure Test.3 (Test.17): + let Test.36 : {} = Struct {}; + ret Test.36; + +procedure Test.4 (Test.18): + let Test.50 : {Str} = Struct {Test.18}; + ret Test.50; + +procedure Test.9 (Test.29, #Attr.12): + let Test.8 : {} = UnionAtIndex (Id 0) (Index 1) #Attr.12; + let Test.7 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; + let Test.35 : {} = Struct {}; + let Test.34 : Str = CallByName Test.15 Test.35; + let Test.31 : {} = CallByName Test.3 Test.34; + dec Test.34; + let Test.33 : {} = Struct {}; + let Test.32 : Str = CallByName Test.11 Test.33; + ret Test.32; + +procedure Test.9 (Test.29, #Attr.12): + let Test.8 : {} = UnionAtIndex (Id 1) (Index 1) #Attr.12; + let Test.7 : {} = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.49 : {} = Struct {}; + let Test.48 : Str = CallByName Test.16 Test.49; + let Test.45 : {Str} = CallByName Test.4 Test.48; + let Test.47 : {} = Struct {}; + let Test.46 : Str = CallByName Test.13 Test.47 Test.45; + ret Test.46; + +procedure Test.0 (): + let Test.5 : Int1 = true; + joinpoint Test.25 Test.6: + let Test.20 : {} = Struct {}; + let Test.21 : U8 = GetTagId Test.6; + joinpoint Test.22 Test.19: + ret Test.19; + in + switch Test.21: + case 0: + let Test.23 : Str = CallByName Test.9 Test.20 Test.6; + jump Test.22 Test.23; + + default: + let Test.24 : Str = CallByName Test.9 Test.20 Test.6; + jump Test.22 Test.24; + + in + let Test.57 : Int1 = true; + let Test.58 : Int1 = lowlevel Eq Test.57 Test.5; + if Test.58 then + let Test.27 : {} = Struct {}; + let Test.28 : {} = Struct {}; + let Test.26 : [C {} {}, C {} {}] = CallByName Test.2 Test.27 Test.28; + jump Test.25 Test.26; + else + let Test.42 : {} = Struct {}; + let Test.43 : {} = Struct {}; + let Test.41 : [C {} {}, C {} {}] = CallByName Test.2 Test.42 Test.43; + jump Test.25 Test.41; diff --git a/crates/compiler/test_mono/generated/lambda_capture_niches_with_non_capturing_function.txt b/crates/compiler/test_mono/generated/lambda_capture_niches_with_non_capturing_function.txt new file mode 100644 index 0000000000..e6eb82b21b --- /dev/null +++ b/crates/compiler/test_mono/generated/lambda_capture_niches_with_non_capturing_function.txt @@ -0,0 +1,59 @@ +procedure Test.1 (Test.5): + let Test.19 : [C , C U64, C {}] = TagId(2) Test.5; + ret Test.19; + +procedure Test.1 (Test.5): + let Test.27 : [C , C U64, C {}] = TagId(1) Test.5; + ret Test.27; + +procedure Test.2 (Test.8): + let Test.24 : Str = ""; + ret Test.24; + +procedure Test.6 (Test.20, #Attr.12): + let Test.5 : U64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.30 : Str = ""; + ret Test.30; + +procedure Test.6 (Test.20, #Attr.12): + let Test.5 : {} = UnionAtIndex (Id 2) (Index 0) #Attr.12; + let Test.22 : Str = ""; + ret Test.22; + +procedure Test.0 (): + let Test.3 : U8 = 0u8; + joinpoint Test.16 Test.4: + let Test.10 : {} = Struct {}; + let Test.11 : U8 = GetTagId Test.4; + joinpoint Test.12 Test.9: + ret Test.9; + in + switch Test.11: + case 0: + let Test.13 : Str = CallByName Test.2 Test.10; + jump Test.12 Test.13; + + case 1: + let Test.14 : Str = CallByName Test.6 Test.10 Test.4; + jump Test.12 Test.14; + + default: + let Test.15 : Str = CallByName Test.6 Test.10 Test.4; + jump Test.12 Test.15; + + in + switch Test.3: + case 0: + let Test.18 : {} = Struct {}; + let Test.17 : [C , C U64, C {}] = CallByName Test.1 Test.18; + jump Test.16 Test.17; + + case 1: + let Test.2 : [C , C U64, C {}] = TagId(0) ; + jump Test.16 Test.2; + + default: + let Test.26 : U64 = 1i64; + let Test.25 : [C , C U64, C {}] = CallByName Test.1 Test.26; + jump Test.16 Test.25; + diff --git a/crates/compiler/test_mono/generated/lambda_capture_niches_with_other_lambda_capture.txt b/crates/compiler/test_mono/generated/lambda_capture_niches_with_other_lambda_capture.txt new file mode 100644 index 0000000000..0ec50cc024 --- /dev/null +++ b/crates/compiler/test_mono/generated/lambda_capture_niches_with_other_lambda_capture.txt @@ -0,0 +1,68 @@ +procedure Test.1 (Test.5): + let Test.20 : [C U64, C {}, C Str] = TagId(1) Test.5; + ret Test.20; + +procedure Test.1 (Test.5): + let Test.32 : [C U64, C {}, C Str] = TagId(0) Test.5; + ret Test.32; + +procedure Test.2 (Test.7): + let Test.26 : [C U64, C {}, C Str] = TagId(2) Test.7; + ret Test.26; + +procedure Test.6 (Test.21, #Attr.12): + let Test.5 : U64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; + dec #Attr.12; + let Test.35 : Str = ""; + ret Test.35; + +procedure Test.6 (Test.21, #Attr.12): + let Test.5 : {} = UnionAtIndex (Id 1) (Index 0) #Attr.12; + dec #Attr.12; + let Test.23 : Str = ""; + ret Test.23; + +procedure Test.8 (Test.27, #Attr.12): + let Test.7 : Str = UnionAtIndex (Id 2) (Index 0) #Attr.12; + inc Test.7; + dec #Attr.12; + ret Test.7; + +procedure Test.0 (): + let Test.3 : U8 = 0u8; + joinpoint Test.17 Test.4: + let Test.11 : {} = Struct {}; + let Test.12 : U8 = GetTagId Test.4; + joinpoint Test.13 Test.10: + ret Test.10; + in + switch Test.12: + case 0: + let Test.14 : Str = CallByName Test.6 Test.11 Test.4; + jump Test.13 Test.14; + + case 1: + let Test.15 : Str = CallByName Test.6 Test.11 Test.4; + jump Test.13 Test.15; + + default: + let Test.16 : Str = CallByName Test.8 Test.11 Test.4; + jump Test.13 Test.16; + + in + switch Test.3: + case 0: + let Test.19 : {} = Struct {}; + let Test.18 : [C U64, C {}, C Str] = CallByName Test.1 Test.19; + jump Test.17 Test.18; + + case 1: + let Test.25 : Str = "foo"; + let Test.24 : [C U64, C {}, C Str] = CallByName Test.2 Test.25; + jump Test.17 Test.24; + + default: + let Test.31 : U64 = 1i64; + let Test.30 : [C U64, C {}, C Str] = CallByName Test.1 Test.31; + jump Test.17 Test.30; + diff --git a/crates/compiler/test_mono/generated/lambda_set_niche_same_layout_different_constructor.txt b/crates/compiler/test_mono/generated/lambda_set_niche_same_layout_different_constructor.txt new file mode 100644 index 0000000000..ae024a188f --- /dev/null +++ b/crates/compiler/test_mono/generated/lambda_set_niche_same_layout_different_constructor.txt @@ -0,0 +1,26 @@ +procedure Test.1 (Test.4): + let Test.5 : {Str} = Struct {Test.4}; + ret Test.5; + +procedure Test.5 (Test.12, #Attr.12): + let Test.4 : Str = StructAtIndex 0 #Attr.12; + inc Test.4; + dec #Attr.12; + let Test.14 : Str = ""; + ret Test.14; + +procedure Test.0 (): + let Test.2 : Int1 = true; + joinpoint Test.9 Test.3: + ret Test.3; + in + let Test.19 : Int1 = true; + let Test.20 : Int1 = lowlevel Eq Test.19 Test.2; + if Test.20 then + let Test.15 : Str = ""; + let Test.10 : {Str} = CallByName Test.1 Test.15; + jump Test.9 Test.10; + else + let Test.18 : Str = ""; + let Test.16 : {Str} = CallByName Test.1 Test.18; + jump Test.9 Test.16; diff --git a/crates/compiler/test_mono/generated/list_append.txt b/crates/compiler/test_mono/generated/list_append.txt index ba872126f2..bd91996634 100644 --- a/crates/compiler/test_mono/generated/list_append.txt +++ b/crates/compiler/test_mono/generated/list_append.txt @@ -1,6 +1,6 @@ procedure List.4 (#Attr.2, #Attr.3): - let List.284 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; - ret List.284; + let List.266 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; + ret List.266; procedure Test.0 (): let Test.2 : List I64 = Array [1i64]; diff --git a/crates/compiler/test_mono/generated/list_append_closure.txt b/crates/compiler/test_mono/generated/list_append_closure.txt index c02bd4ddcb..d3b8b4a722 100644 --- a/crates/compiler/test_mono/generated/list_append_closure.txt +++ b/crates/compiler/test_mono/generated/list_append_closure.txt @@ -1,6 +1,6 @@ procedure List.4 (#Attr.2, #Attr.3): - let List.284 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; - ret List.284; + let List.266 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; + ret List.266; procedure Test.1 (Test.2): let Test.6 : I64 = 42i64; diff --git a/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt b/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt index f98eb4a9d0..ed68035643 100644 --- a/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,35 +1,35 @@ -procedure List.3 (List.84, List.85, List.86): - let List.287 : {List I64, I64} = CallByName List.57 List.84 List.85 List.86; - let List.286 : List I64 = StructAtIndex 0 List.287; - inc List.286; - dec List.287; - ret List.286; +procedure List.3 (List.82, List.83, List.84): + let List.269 : {List I64, I64} = CallByName List.57 List.82 List.83 List.84; + let List.268 : List I64 = StructAtIndex 0 List.269; + inc List.268; + dec List.269; + ret List.268; -procedure List.57 (List.81, List.82, List.83): - let List.293 : U64 = CallByName List.6 List.81; - let List.290 : Int1 = CallByName Num.22 List.82 List.293; - if List.290 then - let List.291 : {List I64, I64} = CallByName List.61 List.81 List.82 List.83; - ret List.291; +procedure List.57 (List.79, List.80, List.81): + let List.275 : U64 = CallByName List.6 List.79; + let List.272 : Int1 = CallByName Num.22 List.80 List.275; + if List.272 then + let List.273 : {List I64, I64} = CallByName List.61 List.79 List.80 List.81; + ret List.273; else - let List.289 : {List I64, I64} = Struct {List.81, List.83}; - ret List.289; + let List.271 : {List I64, I64} = Struct {List.79, List.81}; + ret List.271; procedure List.6 (#Attr.2): - let List.285 : U64 = lowlevel ListLen #Attr.2; - ret List.285; + let List.267 : U64 = lowlevel ListLen #Attr.2; + ret List.267; procedure List.61 (#Attr.2, #Attr.3, #Attr.4): - let List.292 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret List.292; + let List.274 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.274; procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Num.22 (#Attr.2, #Attr.3): - let Num.274 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.274; + let Num.189 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.189; procedure Test.1 (): let Test.8 : List I64 = Array [1i64, 2i64, 3i64]; diff --git a/crates/compiler/test_mono/generated/list_get.txt b/crates/compiler/test_mono/generated/list_get.txt index 17c1f4fc6d..e54a7a6995 100644 --- a/crates/compiler/test_mono/generated/list_get.txt +++ b/crates/compiler/test_mono/generated/list_get.txt @@ -1,26 +1,26 @@ -procedure List.2 (List.75, List.76): - let List.290 : U64 = CallByName List.6 List.75; - let List.286 : Int1 = CallByName Num.22 List.76 List.290; - if List.286 then - let List.288 : I64 = CallByName List.60 List.75 List.76; - let List.287 : [C {}, C I64] = Ok List.288; - ret List.287; +procedure List.2 (List.74, List.75): + let List.272 : U64 = CallByName List.6 List.74; + let List.268 : Int1 = CallByName Num.22 List.75 List.272; + if List.268 then + let List.270 : I64 = CallByName List.60 List.74 List.75; + let List.269 : [C {}, C I64] = TagId(1) List.270; + ret List.269; else - let List.285 : {} = Struct {}; - let List.284 : [C {}, C I64] = Err List.285; - ret List.284; + let List.267 : {} = Struct {}; + let List.266 : [C {}, C I64] = TagId(0) List.267; + ret List.266; procedure List.6 (#Attr.2): - let List.293 : U64 = lowlevel ListLen #Attr.2; - ret List.293; + let List.275 : U64 = lowlevel ListLen #Attr.2; + ret List.275; procedure List.60 (#Attr.2, #Attr.3): - let List.292 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.292; + let List.274 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.274; procedure Num.22 (#Attr.2, #Attr.3): - let Num.273 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.188; procedure Test.1 (Test.2): let Test.6 : List I64 = Array [1i64, 2i64, 3i64]; diff --git a/crates/compiler/test_mono/generated/list_len.txt b/crates/compiler/test_mono/generated/list_len.txt index 246d7c2fb4..f06fa86d6b 100644 --- a/crates/compiler/test_mono/generated/list_len.txt +++ b/crates/compiler/test_mono/generated/list_len.txt @@ -1,14 +1,14 @@ procedure List.6 (#Attr.2): - let List.284 : U64 = lowlevel ListLen #Attr.2; - ret List.284; + let List.266 : U64 = lowlevel ListLen #Attr.2; + ret List.266; procedure List.6 (#Attr.2): - let List.285 : U64 = lowlevel ListLen #Attr.2; - ret List.285; + let List.267 : U64 = lowlevel ListLen #Attr.2; + ret List.267; procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.1 : List I64 = Array [1i64, 2i64, 3i64]; diff --git a/crates/compiler/test_mono/generated/list_map_closure_borrows.txt b/crates/compiler/test_mono/generated/list_map_closure_borrows.txt index ca84153e09..cd8eeb351d 100644 --- a/crates/compiler/test_mono/generated/list_map_closure_borrows.txt +++ b/crates/compiler/test_mono/generated/list_map_closure_borrows.txt @@ -1,38 +1,38 @@ -procedure List.2 (List.75, List.76): - let List.290 : U64 = CallByName List.6 List.75; - let List.286 : Int1 = CallByName Num.22 List.76 List.290; - if List.286 then - let List.288 : Str = CallByName List.60 List.75 List.76; - let List.287 : [C {}, C Str] = Ok List.288; - ret List.287; +procedure List.2 (List.74, List.75): + let List.272 : U64 = CallByName List.6 List.74; + let List.268 : Int1 = CallByName Num.22 List.75 List.272; + if List.268 then + let List.270 : Str = CallByName List.60 List.74 List.75; + let List.269 : [C {}, C Str] = TagId(1) List.270; + ret List.269; else - let List.285 : {} = Struct {}; - let List.284 : [C {}, C Str] = Err List.285; - ret List.284; + let List.267 : {} = Struct {}; + let List.266 : [C {}, C Str] = TagId(0) List.267; + ret List.266; procedure List.5 (#Attr.2, #Attr.3): - let List.292 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; - ret List.292; + let List.274 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; + ret List.274; procedure List.6 (#Attr.2): - let List.294 : U64 = lowlevel ListLen #Attr.2; - ret List.294; + let List.276 : U64 = lowlevel ListLen #Attr.2; + ret List.276; procedure List.60 (#Attr.2, #Attr.3): - let List.293 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.293; + let List.275 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.275; procedure Num.22 (#Attr.2, #Attr.3): - let Num.273 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.188; procedure Str.16 (#Attr.2, #Attr.3): - let Str.149 : Str = lowlevel StrRepeat #Attr.2 #Attr.3; - ret Str.149; + let Str.126 : Str = lowlevel StrRepeat #Attr.2 #Attr.3; + ret Str.126; procedure Str.3 (#Attr.2, #Attr.3): - let Str.150 : Str = lowlevel StrConcat #Attr.2 #Attr.3; - ret Str.150; + let Str.127 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.127; procedure Test.1 (): let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; diff --git a/crates/compiler/test_mono/generated/list_map_closure_owns.txt b/crates/compiler/test_mono/generated/list_map_closure_owns.txt index 52be57028e..cccc71d497 100644 --- a/crates/compiler/test_mono/generated/list_map_closure_owns.txt +++ b/crates/compiler/test_mono/generated/list_map_closure_owns.txt @@ -1,36 +1,36 @@ -procedure List.2 (List.75, List.76): - let List.290 : U64 = CallByName List.6 List.75; - let List.286 : Int1 = CallByName Num.22 List.76 List.290; - if List.286 then - let List.288 : Str = CallByName List.60 List.75 List.76; - let List.287 : [C {}, C Str] = Ok List.288; - ret List.287; +procedure List.2 (List.74, List.75): + let List.272 : U64 = CallByName List.6 List.74; + let List.268 : Int1 = CallByName Num.22 List.75 List.272; + if List.268 then + let List.270 : Str = CallByName List.60 List.74 List.75; + let List.269 : [C {}, C Str] = TagId(1) List.270; + ret List.269; else - let List.285 : {} = Struct {}; - let List.284 : [C {}, C Str] = Err List.285; - ret List.284; + let List.267 : {} = Struct {}; + let List.266 : [C {}, C Str] = TagId(0) List.267; + ret List.266; procedure List.5 (#Attr.2, #Attr.3): inc #Attr.2; - let List.292 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; + let List.274 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; decref #Attr.2; - ret List.292; + ret List.274; procedure List.6 (#Attr.2): - let List.294 : U64 = lowlevel ListLen #Attr.2; - ret List.294; + let List.276 : U64 = lowlevel ListLen #Attr.2; + ret List.276; procedure List.60 (#Attr.2, #Attr.3): - let List.293 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.293; + let List.275 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.275; procedure Num.22 (#Attr.2, #Attr.3): - let Num.273 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.188; procedure Str.3 (#Attr.2, #Attr.3): - let Str.150 : Str = lowlevel StrConcat #Attr.2 #Attr.3; - ret Str.150; + let Str.127 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.127; procedure Test.1 (): let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; diff --git a/crates/compiler/test_mono/generated/list_pass_to_function.txt b/crates/compiler/test_mono/generated/list_pass_to_function.txt index 28b6e420a1..e97bf720c0 100644 --- a/crates/compiler/test_mono/generated/list_pass_to_function.txt +++ b/crates/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,31 +1,31 @@ -procedure List.3 (List.84, List.85, List.86): - let List.285 : {List I64, I64} = CallByName List.57 List.84 List.85 List.86; - let List.284 : List I64 = StructAtIndex 0 List.285; - inc List.284; - dec List.285; - ret List.284; +procedure List.3 (List.82, List.83, List.84): + let List.267 : {List I64, I64} = CallByName List.57 List.82 List.83 List.84; + let List.266 : List I64 = StructAtIndex 0 List.267; + inc List.266; + dec List.267; + ret List.266; -procedure List.57 (List.81, List.82, List.83): - let List.291 : U64 = CallByName List.6 List.81; - let List.288 : Int1 = CallByName Num.22 List.82 List.291; - if List.288 then - let List.289 : {List I64, I64} = CallByName List.61 List.81 List.82 List.83; - ret List.289; +procedure List.57 (List.79, List.80, List.81): + let List.273 : U64 = CallByName List.6 List.79; + let List.270 : Int1 = CallByName Num.22 List.80 List.273; + if List.270 then + let List.271 : {List I64, I64} = CallByName List.61 List.79 List.80 List.81; + ret List.271; else - let List.287 : {List I64, I64} = Struct {List.81, List.83}; - ret List.287; + let List.269 : {List I64, I64} = Struct {List.79, List.81}; + ret List.269; procedure List.6 (#Attr.2): - let List.292 : U64 = lowlevel ListLen #Attr.2; - ret List.292; + let List.274 : U64 = lowlevel ListLen #Attr.2; + ret List.274; procedure List.61 (#Attr.2, #Attr.3, #Attr.4): - let List.290 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret List.290; + let List.272 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.272; procedure Num.22 (#Attr.2, #Attr.3): - let Num.273 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.188; procedure Test.2 (Test.3): let Test.6 : U64 = 0i64; diff --git a/crates/compiler/test_mono/generated/list_sort_asc.txt b/crates/compiler/test_mono/generated/list_sort_asc.txt index fa7e00c424..7762a77377 100644 --- a/crates/compiler/test_mono/generated/list_sort_asc.txt +++ b/crates/compiler/test_mono/generated/list_sort_asc.txt @@ -1,20 +1,20 @@ procedure List.28 (#Attr.2, #Attr.3): - let List.287 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3; - let Bool.14 : Int1 = lowlevel ListIsUnique #Attr.2; - if Bool.14 then - ret List.287; + let List.269 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3; + let Bool.9 : Int1 = lowlevel ListIsUnique #Attr.2; + if Bool.9 then + ret List.269; else decref #Attr.2; - ret List.287; + ret List.269; -procedure List.54 (List.196): - let List.285 : {} = Struct {}; - let List.284 : List I64 = CallByName List.28 List.196 List.285; - ret List.284; +procedure List.54 (List.183): + let List.267 : {} = Struct {}; + let List.266 : List I64 = CallByName List.28 List.183 List.267; + ret List.266; procedure Num.46 (#Attr.2, #Attr.3): - let Num.273 : U8 = lowlevel NumCompare #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : U8 = lowlevel NumCompare #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.2 : List I64 = Array [4i64, 3i64, 2i64, 1i64]; diff --git a/crates/compiler/test_mono/generated/monomorphized_applied_tag.txt b/crates/compiler/test_mono/generated/monomorphized_applied_tag.txt index 0d3b277cf4..eedf08c0d9 100644 --- a/crates/compiler/test_mono/generated/monomorphized_applied_tag.txt +++ b/crates/compiler/test_mono/generated/monomorphized_applied_tag.txt @@ -15,6 +15,6 @@ procedure Test.2 (Test.4): procedure Test.0 (): let Test.13 : Str = "A"; - let Test.1 : [C Str, C Str] = A Test.13; + let Test.1 : [C Str, C Str] = TagId(0) Test.13; let Test.7 : Str = CallByName Test.2 Test.1; ret Test.7; diff --git a/crates/compiler/test_mono/generated/nested_pattern_match.txt b/crates/compiler/test_mono/generated/nested_pattern_match.txt index 272fa01449..42df0a49ee 100644 --- a/crates/compiler/test_mono/generated/nested_pattern_match.txt +++ b/crates/compiler/test_mono/generated/nested_pattern_match.txt @@ -1,11 +1,11 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.19 : I64 = 41i64; - let Test.18 : [C I64, C ] = Just Test.19; - let Test.2 : [C [C I64, C ], C ] = Just Test.18; + let Test.18 : [C I64, C ] = TagId(0) Test.19; + let Test.2 : [C [C I64, C ], C ] = TagId(0) Test.18; joinpoint Test.15: let Test.8 : I64 = 1i64; ret Test.8; diff --git a/crates/compiler/test_mono/generated/opaque_assign_to_symbol.txt b/crates/compiler/test_mono/generated/opaque_assign_to_symbol.txt index 23eee2ceea..f2e8378359 100644 --- a/crates/compiler/test_mono/generated/opaque_assign_to_symbol.txt +++ b/crates/compiler/test_mono/generated/opaque_assign_to_symbol.txt @@ -1,5 +1,5 @@ procedure Test.3 (Test.4): - let Test.8 : [C {}, C U8] = Ok Test.4; + let Test.8 : [C {}, C U8] = TagId(1) Test.4; ret Test.8; procedure Test.0 (): diff --git a/crates/compiler/test_mono/generated/optional_when.txt b/crates/compiler/test_mono/generated/optional_when.txt index 239dbc721f..00f3ca57b7 100644 --- a/crates/compiler/test_mono/generated/optional_when.txt +++ b/crates/compiler/test_mono/generated/optional_when.txt @@ -1,6 +1,6 @@ procedure Num.21 (#Attr.2, #Attr.3): - let Num.275 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.275; + let Num.190 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.190; procedure Test.1 (Test.6): let Test.21 : Int1 = false; diff --git a/crates/compiler/test_mono/generated/peano.txt b/crates/compiler/test_mono/generated/peano.txt index 8b7370a838..ee5393219a 100644 --- a/crates/compiler/test_mono/generated/peano.txt +++ b/crates/compiler/test_mono/generated/peano.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.11 : [, C *self] = Z ; - let Test.10 : [, C *self] = S Test.11; - let Test.9 : [, C *self] = S Test.10; - let Test.3 : [, C *self] = S Test.9; + let Test.11 : [, C *self] = TagId(1) ; + let Test.10 : [, C *self] = TagId(0) Test.11; + let Test.9 : [, C *self] = TagId(0) Test.10; + let Test.3 : [, C *self] = TagId(0) Test.9; ret Test.3; diff --git a/crates/compiler/test_mono/generated/peano1.txt b/crates/compiler/test_mono/generated/peano1.txt index 63db968efc..75c118328e 100644 --- a/crates/compiler/test_mono/generated/peano1.txt +++ b/crates/compiler/test_mono/generated/peano1.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.15 : [, C *self] = Z ; - let Test.14 : [, C *self] = S Test.15; - let Test.13 : [, C *self] = S Test.14; - let Test.3 : [, C *self] = S Test.13; + let Test.15 : [, C *self] = TagId(1) ; + let Test.14 : [, C *self] = TagId(0) Test.15; + let Test.13 : [, C *self] = TagId(0) Test.14; + let Test.3 : [, C *self] = TagId(0) Test.13; let Test.10 : Int1 = 1i64; let Test.11 : Int1 = GetTagId Test.3; dec Test.3; diff --git a/crates/compiler/test_mono/generated/peano2.txt b/crates/compiler/test_mono/generated/peano2.txt index 73dd42687a..36a67b1b66 100644 --- a/crates/compiler/test_mono/generated/peano2.txt +++ b/crates/compiler/test_mono/generated/peano2.txt @@ -1,8 +1,8 @@ procedure Test.0 (): - let Test.21 : [, C *self] = Z ; - let Test.20 : [, C *self] = S Test.21; - let Test.19 : [, C *self] = S Test.20; - let Test.3 : [, C *self] = S Test.19; + let Test.21 : [, C *self] = TagId(1) ; + let Test.20 : [, C *self] = TagId(0) Test.21; + let Test.19 : [, C *self] = TagId(0) Test.20; + let Test.3 : [, C *self] = TagId(0) Test.19; let Test.16 : Int1 = 0i64; let Test.17 : Int1 = GetTagId Test.3; let Test.18 : Int1 = lowlevel Eq Test.16 Test.17; diff --git a/crates/compiler/test_mono/generated/quicksort_help.txt b/crates/compiler/test_mono/generated/quicksort_help.txt index 2c00aa804b..ba35a161f7 100644 --- a/crates/compiler/test_mono/generated/quicksort_help.txt +++ b/crates/compiler/test_mono/generated/quicksort_help.txt @@ -1,14 +1,14 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Num.20 (#Attr.2, #Attr.3): - let Num.274 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.274; + let Num.189 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.189; procedure Num.22 (#Attr.2, #Attr.3): - let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.275; + let Num.190 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.190; procedure Test.1 (Test.24, Test.25, Test.26): joinpoint Test.12 Test.2 Test.3 Test.4: diff --git a/crates/compiler/test_mono/generated/quicksort_swap.txt b/crates/compiler/test_mono/generated/quicksort_swap.txt index 84bedc1a65..e71e93a6b4 100644 --- a/crates/compiler/test_mono/generated/quicksort_swap.txt +++ b/crates/compiler/test_mono/generated/quicksort_swap.txt @@ -1,47 +1,47 @@ -procedure List.2 (List.75, List.76): - let List.304 : U64 = CallByName List.6 List.75; - let List.300 : Int1 = CallByName Num.22 List.76 List.304; - if List.300 then - let List.302 : I64 = CallByName List.60 List.75 List.76; - let List.301 : [C {}, C I64] = Ok List.302; - ret List.301; +procedure List.2 (List.74, List.75): + let List.286 : U64 = CallByName List.6 List.74; + let List.282 : Int1 = CallByName Num.22 List.75 List.286; + if List.282 then + let List.284 : I64 = CallByName List.60 List.74 List.75; + let List.283 : [C {}, C I64] = TagId(1) List.284; + ret List.283; else - let List.299 : {} = Struct {}; - let List.298 : [C {}, C I64] = Err List.299; - ret List.298; + let List.281 : {} = Struct {}; + let List.280 : [C {}, C I64] = TagId(0) List.281; + ret List.280; -procedure List.3 (List.84, List.85, List.86): - let List.288 : {List I64, I64} = CallByName List.57 List.84 List.85 List.86; - let List.287 : List I64 = StructAtIndex 0 List.288; - inc List.287; - dec List.288; - ret List.287; +procedure List.3 (List.82, List.83, List.84): + let List.270 : {List I64, I64} = CallByName List.57 List.82 List.83 List.84; + let List.269 : List I64 = StructAtIndex 0 List.270; + inc List.269; + dec List.270; + ret List.269; -procedure List.57 (List.81, List.82, List.83): - let List.310 : U64 = CallByName List.6 List.81; - let List.307 : Int1 = CallByName Num.22 List.82 List.310; - if List.307 then - let List.308 : {List I64, I64} = CallByName List.61 List.81 List.82 List.83; - ret List.308; +procedure List.57 (List.79, List.80, List.81): + let List.292 : U64 = CallByName List.6 List.79; + let List.289 : Int1 = CallByName Num.22 List.80 List.292; + if List.289 then + let List.290 : {List I64, I64} = CallByName List.61 List.79 List.80 List.81; + ret List.290; else - let List.306 : {List I64, I64} = Struct {List.81, List.83}; - ret List.306; + let List.288 : {List I64, I64} = Struct {List.79, List.81}; + ret List.288; procedure List.6 (#Attr.2): - let List.311 : U64 = lowlevel ListLen #Attr.2; - ret List.311; + let List.293 : U64 = lowlevel ListLen #Attr.2; + ret List.293; procedure List.60 (#Attr.2, #Attr.3): - let List.312 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.312; + let List.294 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.294; procedure List.61 (#Attr.2, #Attr.3, #Attr.4): - let List.309 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret List.309; + let List.291 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.291; procedure Num.22 (#Attr.2, #Attr.3): - let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.275; + let Num.190 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.190; procedure Test.1 (Test.2): let Test.28 : U64 = 0i64; diff --git a/crates/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/crates/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index 82db9ad2db..2c661940cc 100644 --- a/crates/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/crates/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.1 (Test.4): let Test.2 : I64 = StructAtIndex 0 Test.4; diff --git a/crates/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/crates/compiler/test_mono/generated/record_optional_field_function_use_default.txt index eaba7d9de5..2145dec395 100644 --- a/crates/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/crates/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.1 (Test.4): let Test.2 : I64 = 10i64; diff --git a/crates/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/crates/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index 959902f6d8..84a986e3c6 100644 --- a/crates/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/crates/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.1 (Test.2): let Test.3 : I64 = StructAtIndex 0 Test.2; diff --git a/crates/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/crates/compiler/test_mono/generated/record_optional_field_let_use_default.txt index aad801aab9..ede661ab6b 100644 --- a/crates/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/crates/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.1 (Test.2): let Test.3 : I64 = 10i64; diff --git a/crates/compiler/test_mono/generated/rigids.txt b/crates/compiler/test_mono/generated/rigids.txt index 2285fe56ac..b9b09bca94 100644 --- a/crates/compiler/test_mono/generated/rigids.txt +++ b/crates/compiler/test_mono/generated/rigids.txt @@ -1,47 +1,47 @@ -procedure List.2 (List.75, List.76): - let List.304 : U64 = CallByName List.6 List.75; - let List.300 : Int1 = CallByName Num.22 List.76 List.304; - if List.300 then - let List.302 : I64 = CallByName List.60 List.75 List.76; - let List.301 : [C {}, C I64] = Ok List.302; - ret List.301; +procedure List.2 (List.74, List.75): + let List.286 : U64 = CallByName List.6 List.74; + let List.282 : Int1 = CallByName Num.22 List.75 List.286; + if List.282 then + let List.284 : I64 = CallByName List.60 List.74 List.75; + let List.283 : [C {}, C I64] = TagId(1) List.284; + ret List.283; else - let List.299 : {} = Struct {}; - let List.298 : [C {}, C I64] = Err List.299; - ret List.298; + let List.281 : {} = Struct {}; + let List.280 : [C {}, C I64] = TagId(0) List.281; + ret List.280; -procedure List.3 (List.84, List.85, List.86): - let List.288 : {List I64, I64} = CallByName List.57 List.84 List.85 List.86; - let List.287 : List I64 = StructAtIndex 0 List.288; - inc List.287; - dec List.288; - ret List.287; +procedure List.3 (List.82, List.83, List.84): + let List.270 : {List I64, I64} = CallByName List.57 List.82 List.83 List.84; + let List.269 : List I64 = StructAtIndex 0 List.270; + inc List.269; + dec List.270; + ret List.269; -procedure List.57 (List.81, List.82, List.83): - let List.310 : U64 = CallByName List.6 List.81; - let List.307 : Int1 = CallByName Num.22 List.82 List.310; - if List.307 then - let List.308 : {List I64, I64} = CallByName List.61 List.81 List.82 List.83; - ret List.308; +procedure List.57 (List.79, List.80, List.81): + let List.292 : U64 = CallByName List.6 List.79; + let List.289 : Int1 = CallByName Num.22 List.80 List.292; + if List.289 then + let List.290 : {List I64, I64} = CallByName List.61 List.79 List.80 List.81; + ret List.290; else - let List.306 : {List I64, I64} = Struct {List.81, List.83}; - ret List.306; + let List.288 : {List I64, I64} = Struct {List.79, List.81}; + ret List.288; procedure List.6 (#Attr.2): - let List.311 : U64 = lowlevel ListLen #Attr.2; - ret List.311; + let List.293 : U64 = lowlevel ListLen #Attr.2; + ret List.293; procedure List.60 (#Attr.2, #Attr.3): - let List.312 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.312; + let List.294 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.294; procedure List.61 (#Attr.2, #Attr.3, #Attr.4): - let List.309 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret List.309; + let List.291 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.291; procedure Num.22 (#Attr.2, #Attr.3): - let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.275; + let Num.190 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.190; procedure Test.1 (Test.2, Test.3, Test.4): let Test.29 : [C {}, C I64] = CallByName List.2 Test.4 Test.3; diff --git a/crates/compiler/test_mono/generated/specialize_closures.txt b/crates/compiler/test_mono/generated/specialize_closures.txt index 251e80285d..9fc9ae5a24 100644 --- a/crates/compiler/test_mono/generated/specialize_closures.txt +++ b/crates/compiler/test_mono/generated/specialize_closures.txt @@ -1,10 +1,10 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.274 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.274; + let Num.189 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.189; procedure Num.21 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.188; procedure Test.1 (Test.2, Test.3): let Test.17 : U8 = GetTagId Test.2; @@ -46,8 +46,8 @@ procedure Test.0 (): in let Test.25 : Int1 = true; if Test.25 then - let Test.7 : [C I64, C I64 Int1] = ClosureTag(Test.7) Test.4; + let Test.7 : [C I64, C I64 Int1] = TagId(0) Test.4; jump Test.22 Test.7; else - let Test.8 : [C I64, C I64 Int1] = ClosureTag(Test.8) Test.5 Test.6; + let Test.8 : [C I64, C I64 Int1] = TagId(1) Test.5 Test.6; jump Test.22 Test.8; diff --git a/crates/compiler/test_mono/generated/specialize_lowlevel.txt b/crates/compiler/test_mono/generated/specialize_lowlevel.txt index 74bc5c3bbb..157c9b7fb7 100644 --- a/crates/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/crates/compiler/test_mono/generated/specialize_lowlevel.txt @@ -1,10 +1,10 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.274 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.274; + let Num.189 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.189; procedure Num.21 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.188; procedure Test.6 (Test.8, #Attr.12): let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; @@ -37,8 +37,8 @@ procedure Test.0 (): in let Test.21 : Int1 = true; if Test.21 then - let Test.6 : [C I64, C I64] = ClosureTag(Test.6) Test.4; + let Test.6 : [C I64, C I64] = TagId(0) Test.4; jump Test.19 Test.6; else - let Test.7 : [C I64, C I64] = ClosureTag(Test.7) Test.5; + let Test.7 : [C I64, C I64] = TagId(1) Test.5; jump Test.19 Test.7; diff --git a/crates/compiler/test_mono/generated/tail_call_elimination.txt b/crates/compiler/test_mono/generated/tail_call_elimination.txt index 236038343a..fb7a162575 100644 --- a/crates/compiler/test_mono/generated/tail_call_elimination.txt +++ b/crates/compiler/test_mono/generated/tail_call_elimination.txt @@ -1,10 +1,10 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Num.20 (#Attr.2, #Attr.3): - let Num.274 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.274; + let Num.189 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.189; procedure Test.1 (Test.15, Test.16): joinpoint Test.7 Test.2 Test.3: diff --git a/crates/compiler/test_mono/generated/when_nested_maybe.txt b/crates/compiler/test_mono/generated/when_nested_maybe.txt index 272fa01449..42df0a49ee 100644 --- a/crates/compiler/test_mono/generated/when_nested_maybe.txt +++ b/crates/compiler/test_mono/generated/when_nested_maybe.txt @@ -1,11 +1,11 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.19 : I64 = 41i64; - let Test.18 : [C I64, C ] = Just Test.19; - let Test.2 : [C [C I64, C ], C ] = Just Test.18; + let Test.18 : [C I64, C ] = TagId(0) Test.19; + let Test.2 : [C [C I64, C ], C ] = TagId(0) Test.18; joinpoint Test.15: let Test.8 : I64 = 1i64; ret Test.8; diff --git a/crates/compiler/test_mono/generated/when_on_record.txt b/crates/compiler/test_mono/generated/when_on_record.txt index 71fec29389..2cc52368b6 100644 --- a/crates/compiler/test_mono/generated/when_on_record.txt +++ b/crates/compiler/test_mono/generated/when_on_record.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.5 : I64 = 2i64; diff --git a/crates/compiler/test_mono/generated/when_on_result.txt b/crates/compiler/test_mono/generated/when_on_result.txt index 6fbe2699d4..242c90438d 100644 --- a/crates/compiler/test_mono/generated/when_on_result.txt +++ b/crates/compiler/test_mono/generated/when_on_result.txt @@ -1,6 +1,6 @@ procedure Test.1 (Test.5): let Test.19 : I64 = 2i64; - let Test.2 : [C I64, C I64] = Ok Test.19; + let Test.2 : [C I64, C I64] = TagId(1) Test.19; joinpoint Test.9 Test.3: ret Test.3; in diff --git a/crates/compiler/test_mono/generated/when_on_two_values.txt b/crates/compiler/test_mono/generated/when_on_two_values.txt index f3d84fff9e..c5361ba0b9 100644 --- a/crates/compiler/test_mono/generated/when_on_two_values.txt +++ b/crates/compiler/test_mono/generated/when_on_two_values.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.273; + let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.188; procedure Test.0 (): let Test.15 : I64 = 3i64; diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index 17b7bf51f3..39da628f64 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -1525,3 +1525,130 @@ fn tail_call_with_different_layout() { "# ) } + +#[mono_test] +fn lambda_capture_niche_u8_vs_u64() { + indoc!( + r#" + capture : _ -> ({} -> Str) + capture = \val -> + \{} -> + Num.toStr val + + x : [True, False] + x = True + + fun = + when x is + True -> capture 123u64 + False -> capture 18u8 + + fun {} + "# + ) +} + +#[mono_test] +fn lambda_capture_niches_with_other_lambda_capture() { + indoc!( + r#" + capture : a -> ({} -> Str) + capture = \val -> + \{} -> + when val is + _ -> "" + + capture2 = \val -> \{} -> "\(val)" + + x : [A, B, C] + x = A + + fun = + when x is + A -> capture {} + B -> capture2 "foo" + C -> capture 1u64 + + fun {} + "# + ) +} + +#[mono_test] +fn lambda_capture_niches_with_non_capturing_function() { + indoc!( + r#" + capture : a -> ({} -> Str) + capture = \val -> + \{} -> + when val is + _ -> "" + + triv = \{} -> "" + + x : [A, B, C] + x = A + + fun = + when x is + A -> capture {} + B -> triv + C -> capture 1u64 + + fun {} + "# + ) +} + +#[mono_test] +fn lambda_capture_niches_have_captured_function_in_closure() { + indoc!( + r#" + Lazy a : {} -> a + + after : Lazy a, (a -> Lazy b) -> Lazy b + after = \effect, map -> + thunk = \{} -> + when map (effect {}) is + b -> b {} + thunk + + f = \_ -> \_ -> "" + g = \{ s1 } -> \_ -> s1 + + x : [True, False] + x = True + + fun = + when x is + True -> after (\{} -> "") f + False -> after (\{} -> {s1: "s1"}) g + + fun {} + "# + ) +} + +#[mono_test] +fn lambda_set_niche_same_layout_different_constructor() { + indoc!( + r#" + capture : a -> ({} -> Str) + capture = \val -> + thunk = + \{} -> + when val is + _ -> "" + thunk + + x : [True, False] + x = True + + fun = + when x is + True -> capture {a: ""} + False -> capture (A "") + fun + "# + ) +} diff --git a/crates/compiler/types/src/pretty_print.rs b/crates/compiler/types/src/pretty_print.rs index 0203793009..0837e5f20e 100644 --- a/crates/compiler/types/src/pretty_print.rs +++ b/crates/compiler/types/src/pretty_print.rs @@ -4,7 +4,6 @@ use crate::subs::{ }; use crate::types::{name_type_var, RecordField, Uls}; use roc_collections::all::MutMap; -use roc_error_macros::internal_error; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -1152,7 +1151,7 @@ fn write_flat_type<'a>( } } -fn push_union<'a, L: Label>( +pub fn push_union<'a, L: Label>( subs: &'a Subs, tags: &UnionLabels, fields: &mut Vec<(L, Vec)>, @@ -1196,40 +1195,6 @@ pub fn chase_ext_tag_union<'a>( } } -pub enum ResolvedLambdaSet { - Set(Vec<(Symbol, Vec)>), - /// TODO: figure out if this can happen in a correct program, or is the result of a bug in our - /// compiler. See https://github.com/rtfeldman/roc/issues/3163. - Unbound, -} - -pub fn resolve_lambda_set(subs: &Subs, mut var: Variable) -> ResolvedLambdaSet { - let mut set = vec![]; - loop { - match subs.get_content_without_compacting(var) { - Content::LambdaSet(subs::LambdaSet { - solved, - recursion_var: _, - unspecialized, - }) => { - debug_assert!( - unspecialized.is_empty(), - "unspecialized lambda sets left over during resolution: {:?}", - crate::subs::SubsFmtContent(subs.get_content_without_compacting(var), subs), - ); - push_union(subs, solved, &mut set); - return ResolvedLambdaSet::Set(set); - } - Content::RecursionVar { structure, .. } => { - var = *structure; - } - Content::FlexVar(_) => return ResolvedLambdaSet::Unbound, - - c => internal_error!("called with a non-lambda set {:?}", c), - } - } -} - fn write_apply<'a>( env: &Env, ctx: &mut Context<'a>, diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index f48e11f7a8..6707a96399 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -1872,6 +1872,10 @@ impl Subs { self.utable.unioned(left, right) } + pub fn equivalent_without_compacting(&self, left: Variable, right: Variable) -> bool { + self.utable.unioned_without_compacting(left, right) + } + pub fn redundant(&self, var: Variable) -> bool { self.utable.is_redirect(var) } @@ -2646,7 +2650,8 @@ impl UnionLabels where L: Label + Ord, { - pub fn is_sorted_no_duplicates(&self, subs: &Subs) -> bool { + /// Checks if the union of labels is sorted by label, without duplicates. + pub fn is_sorted(&self, subs: &Subs) -> bool { let mut iter = self.iter_from_subs(subs).peekable(); while let Some((before, _)) = iter.next() { if let Some((after, _)) = iter.peek() { @@ -2657,6 +2662,19 @@ where } true } + + /// Checks if the union of labels is sorted by label, without duplicates. + pub fn is_sorted_allow_duplicates(&self, subs: &Subs) -> bool { + let mut iter = self.iter_from_subs(subs).peekable(); + while let Some((before, _)) = iter.next() { + if let Some((after, _)) = iter.peek() { + if before > after { + return false; + } + } + } + true + } } impl UnionTags { diff --git a/crates/compiler/types/src/unification_table.rs b/crates/compiler/types/src/unification_table.rs index 12e10f4648..cb44cb4f80 100644 --- a/crates/compiler/types/src/unification_table.rs +++ b/crates/compiler/types/src/unification_table.rs @@ -323,6 +323,10 @@ impl UnificationTable { self.root_key(a) == self.root_key(b) } + pub fn unioned_without_compacting(&self, a: Variable, b: Variable) -> bool { + self.root_key_without_compacting(a) == self.root_key_without_compacting(b) + } + // custom very specific helpers #[inline(always)] pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index 19974716af..2fa46947aa 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -966,6 +966,174 @@ fn extract_specialization_lambda_set( outcome } +#[derive(Debug)] +struct Sides { + left: Vec<(Symbol, VariableSubsSlice)>, + right: Vec<(Symbol, VariableSubsSlice)>, +} + +impl Default for Sides { + fn default() -> Self { + Self { + left: Vec::with_capacity(1), + right: Vec::with_capacity(1), + } + } +} + +struct SeparatedUnionLambdas { + only_in_left: Vec<(Symbol, VariableSubsSlice)>, + only_in_right: Vec<(Symbol, VariableSubsSlice)>, + joined: Vec<(Symbol, VariableSubsSlice)>, +} + +fn separate_union_lambdas( + subs: &mut Subs, + pool: &mut Pool, + fields1: UnionLambdas, + fields2: UnionLambdas, +) -> SeparatedUnionLambdas { + debug_assert!( + fields1.is_sorted_allow_duplicates(subs), + "not sorted: {:?}", + fields1.iter_from_subs(subs).collect::>() + ); + debug_assert!( + fields2.is_sorted_allow_duplicates(subs), + "not sorted: {:?}", + fields2.iter_from_subs(subs).collect::>() + ); + + // lambda names -> (the captures for that lambda on the left side, the captures for that lambda on the right side) + // e.g. [[F1 U8], [F1 U64], [F2 a]] ~ [[F1 Str], [F2 Str]] becomes + // F1 -> { left: [ [U8], [U64] ], right: [ [Str] ] } + // F2 -> { left: [ [a] ], right: [ [Str] ] } + let mut buckets: VecMap = VecMap::with_capacity(fields1.len() + fields2.len()); + + let (mut fields_left, mut fields_right) = ( + fields1.iter_all().into_iter().peekable(), + fields2.iter_all().into_iter().peekable(), + ); + + loop { + use std::cmp::Ordering; + + let ord = match (fields_left.peek(), fields_right.peek()) { + (Some((l, _)), Some((r, _))) => Some((subs[*l]).cmp(&subs[*r])), + (Some(_), None) => Some(Ordering::Less), + (None, Some(_)) => Some(Ordering::Greater), + (None, None) => None, + }; + + match ord { + Some(Ordering::Less) => { + let (sym, vars) = fields_left.next().unwrap(); + let bucket = buckets.get_or_insert(subs[sym], Sides::default); + bucket.left.push((subs[sym], subs[vars])); + } + Some(Ordering::Greater) => { + let (sym, vars) = fields_right.next().unwrap(); + let bucket = buckets.get_or_insert(subs[sym], Sides::default); + bucket.right.push((subs[sym], subs[vars])); + } + Some(Ordering::Equal) => { + let (sym, left_vars) = fields_left.next().unwrap(); + let (_sym, right_vars) = fields_right.next().unwrap(); + debug_assert_eq!(subs[sym], subs[_sym]); + + let bucket = buckets.get_or_insert(subs[sym], Sides::default); + bucket.left.push((subs[sym], subs[left_vars])); + bucket.right.push((subs[sym], subs[right_vars])); + } + None => break, + } + } + + let mut only_in_left = Vec::with_capacity(fields1.len()); + let mut only_in_right = Vec::with_capacity(fields2.len()); + let mut joined = Vec::with_capacity(fields1.len() + fields2.len()); + for (lambda_name, Sides { left, mut right }) in buckets { + match (left.as_slice(), right.as_slice()) { + (&[], &[]) => internal_error!("somehow both are empty but there's an entry?"), + (&[], _) => only_in_right.extend(right), + (_, &[]) => only_in_left.extend(left), + (_, _) => { + 'next_left: for (_, left_slice) in left { + // Does the current slice on the left unify with a slice on the right? + // + // If yes, we unify then and the unified result to `joined`. + // + // Otherwise if no such slice on the right is found, then the slice on the `left` has no slice, + // either on the left or right, it unifies with (since the left was constructed + // inductively via the same procedure). + // + // At the end each slice in the left and right has been explored, so + // - `joined` contains all the slices that can unify + // - left contains unique captures slices that will unify with no other slice + // - right contains unique captures slices that will unify with no other slice + // + // Note also if a slice l on the left and a slice r on the right unify, there + // is no other r' != r on the right such that l ~ r', and respectively there is + // no other l' != l on the left such that l' ~ r. Otherwise, it must be that l ~ l' + // (resp. r ~ r'), but then l = l' (resp. r = r'), and they would have become the same + // slice in a previous call to `separate_union_lambdas`. + 'try_next_right: for (right_index, (_, right_slice)) in right.iter().enumerate() + { + if left_slice.len() != right_slice.len() { + continue 'try_next_right; + } + + let snapshot = subs.snapshot(); + for (var1, var2) in (left_slice.into_iter()).zip(right_slice.into_iter()) { + let (var1, var2) = (subs[var1], subs[var2]); + + // Lambda sets are effectively tags under another name, and their usage can also result + // in the arguments of a lambda name being recursive. It very well may happen that + // during unification, a lambda set previously marked as not recursive becomes + // recursive. See the docs of [LambdaSet] for one example, or https://github.com/rtfeldman/roc/pull/2307. + // + // Like with tag unions, if it has, we'll always pass through this branch. So, take + // this opportunity to promote the lambda set to recursive if need be. + maybe_mark_union_recursive(subs, var1); + maybe_mark_union_recursive(subs, var2); + + let outcome = + unify_pool::(subs, pool, var1, var2, Mode::EQ); + + if !outcome.mismatches.is_empty() { + subs.rollback_to(snapshot); + continue 'try_next_right; + } + } + + // All the variables unified, so we can join the left + right. + // The variables are unified in left and right slice, so just reuse the left slice. + joined.push((lambda_name, left_slice)); + // Remove the right slice, it unifies with the left so this is its unique + // unification. + // Remove in-place so that the order is preserved. + right.remove(right_index); + continue 'next_left; + } + + // No slice on the right unified with the left, so the slice on the left is on + // its own. + only_in_left.push((lambda_name, left_slice)); + } + + // Possible that there are items left over in the right, they are on their own. + only_in_right.extend(right); + } + } + } + + SeparatedUnionLambdas { + only_in_left, + only_in_right, + joined, + } +} + fn unify_lambda_set_help( subs: &mut Subs, pool: &mut Pool, @@ -994,109 +1162,68 @@ fn unify_lambda_set_help( "Recursion var is present, but it doesn't have a recursive content!" ); - let Separate { - only_in_1, - only_in_2, - in_both, - } = separate_union_lambdas(subs, solved1, solved2); + let SeparatedUnionLambdas { + only_in_left, + only_in_right, + joined, + } = separate_union_lambdas(subs, pool, solved1, solved2); - let num_shared = in_both.len(); + let all_lambdas = joined + .into_iter() + .map(|(name, slice)| (name, subs.get_subs_slice(slice).to_vec())); - let mut joined_lambdas = vec![]; - for (tag_name, (vars1, vars2)) in in_both { - let mut matching_vars = vec![]; + let all_lambdas = merge_sorted_preserving_duplicates( + all_lambdas, + only_in_left.into_iter().map(|(name, subs_slice)| { + let vec = subs.get_subs_slice(subs_slice).to_vec(); + (name, vec) + }), + ); + let all_lambdas = merge_sorted_preserving_duplicates( + all_lambdas, + only_in_right.into_iter().map(|(name, subs_slice)| { + let vec = subs.get_subs_slice(subs_slice).to_vec(); + (name, vec) + }), + ); - if vars1.len() != vars2.len() { - continue; // this is a type mismatch; not adding the tag will trigger it below. + let recursion_var = match (rec1.into_variable(), rec2.into_variable()) { + // Prefer left when it's available. + (Some(rec), _) | (_, Some(rec)) => OptVariable::from(rec), + (None, None) => OptVariable::NONE, + }; + + // Combine the unspecialized lambda sets as needed. Note that we don't need to update the + // bookkeeping of variable -> lambda set to be resolved, because if we had v1 -> lset1, and + // now lset1 ~ lset2, then afterward either lset1 still resolves to itself or re-points to + // lset2. In either case the merged unspecialized lambda sets will be there. + let merged_unspecialized = match (uls1.is_empty(), uls2.is_empty()) { + (true, true) => SubsSlice::default(), + (false, true) => uls1, + (true, false) => uls2, + (false, false) => { + let mut all_uls = (subs.get_subs_slice(uls1).iter()) + .chain(subs.get_subs_slice(uls2)) + .map(|&Uls(var, sym, region)| { + // Take the root key to deduplicate + Uls(subs.get_root_key_without_compacting(var), sym, region) + }) + .collect::>(); + all_uls.sort(); + all_uls.dedup(); + + SubsSlice::extend_new(&mut subs.unspecialized_lambda_sets, all_uls) } + }; - let num_vars = vars1.len(); - for (var1, var2) in (vars1.into_iter()).zip(vars2.into_iter()) { - let (var1, var2) = (subs[var1], subs[var2]); + let new_solved = UnionLabels::insert_into_subs(subs, all_lambdas); + let new_lambda_set = Content::LambdaSet(LambdaSet { + solved: new_solved, + recursion_var, + unspecialized: merged_unspecialized, + }); - // Lambda sets are effectively tags under another name, and their usage can also result - // in the arguments of a lambda name being recursive. It very well may happen that - // during unification, a lambda set previously marked as not recursive becomes - // recursive. See the docs of [LambdaSet] for one example, or https://github.com/rtfeldman/roc/pull/2307. - // - // Like with tag unions, if it has, we'll always pass through this branch. So, take - // this opportunity to promote the lambda set to recursive if need be. - maybe_mark_union_recursive(subs, var1); - maybe_mark_union_recursive(subs, var2); - - let outcome = unify_pool::(subs, pool, var1, var2, ctx.mode); - - if outcome.mismatches.is_empty() { - matching_vars.push(var1); - } - } - - if matching_vars.len() == num_vars { - joined_lambdas.push((tag_name, matching_vars)); - } - } - - if joined_lambdas.len() == num_shared { - let all_lambdas = joined_lambdas; - let all_lambdas = merge_sorted( - all_lambdas, - only_in_1.into_iter().map(|(name, subs_slice)| { - let vec = subs.get_subs_slice(subs_slice).to_vec(); - (name, vec) - }), - ); - let all_lambdas = merge_sorted( - all_lambdas, - only_in_2.into_iter().map(|(name, subs_slice)| { - let vec = subs.get_subs_slice(subs_slice).to_vec(); - (name, vec) - }), - ); - - let recursion_var = match (rec1.into_variable(), rec2.into_variable()) { - // Prefer left when it's available. - (Some(rec), _) | (_, Some(rec)) => OptVariable::from(rec), - (None, None) => OptVariable::NONE, - }; - - // Combine the unspecialized lambda sets as needed. Note that we don't need to update the - // bookkeeping of variable -> lambda set to be resolved, because if we had v1 -> lset1, and - // now lset1 ~ lset2, then afterward either lset1 still resolves to itself or re-points to - // lset2. In either case the merged unspecialized lambda sets will be there. - let merged_unspecialized = match (uls1.is_empty(), uls2.is_empty()) { - (true, true) => SubsSlice::default(), - (false, true) => uls1, - (true, false) => uls2, - (false, false) => { - let mut all_uls = (subs.get_subs_slice(uls1).iter()) - .chain(subs.get_subs_slice(uls2)) - .map(|&Uls(var, sym, region)| { - // Take the root key to deduplicate - Uls(subs.get_root_key_without_compacting(var), sym, region) - }) - .collect::>(); - all_uls.sort(); - all_uls.dedup(); - - SubsSlice::extend_new(&mut subs.unspecialized_lambda_sets, all_uls) - } - }; - - let new_solved = UnionLabels::insert_into_subs(subs, all_lambdas); - let new_lambda_set = Content::LambdaSet(LambdaSet { - solved: new_solved, - recursion_var, - unspecialized: merged_unspecialized, - }); - - merge(subs, ctx, new_lambda_set) - } else { - mismatch!( - "Problem with lambda sets: there should be {:?} matching lambda, but only found {:?}", - num_shared, - &joined_lambdas - ) - } + merge(subs, ctx, new_lambda_set) } /// Ensures that a non-recursive tag union, when unified with a recursion var to become a recursive @@ -1396,7 +1523,7 @@ struct Separate { in_both: Vec<(K, (V, V))>, } -fn merge_sorted(input1: I1, input2: I2) -> Vec<(K, V)> +fn merge_sorted_help(input1: I1, input2: I2, preserve_duplicates: bool) -> Vec<(K, V)> where K: Ord, I1: IntoIterator, @@ -1426,8 +1553,11 @@ where } Some(Ordering::Equal) => { let (k, v) = it1.next().unwrap(); - let (_, _) = it2.next().unwrap(); + let (k2, v2) = it2.next().unwrap(); result.push((k, v)); + if preserve_duplicates { + result.push((k2, v2)); + } } Some(Ordering::Greater) => { result.push(it2.next().unwrap()); @@ -1439,6 +1569,24 @@ where result } +fn merge_sorted(input1: I1, input2: I2) -> Vec<(K, V)> +where + K: Ord, + I1: IntoIterator, + I2: IntoIterator, +{ + merge_sorted_help(input1, input2, false) +} + +fn merge_sorted_preserving_duplicates(input1: I1, input2: I2) -> Vec<(K, V)> +where + K: Ord, + I1: IntoIterator, + I2: IntoIterator, +{ + merge_sorted_help(input1, input2, true) +} + fn separate(input1: I1, input2: I2) -> Separate where K: Ord, @@ -1501,19 +1649,6 @@ fn separate_union_tags( (separate(it1, it2), new_ext1, new_ext2) } -fn separate_union_lambdas( - subs: &Subs, - fields1: UnionLambdas, - fields2: UnionLambdas, -) -> Separate { - debug_assert!(fields1.is_sorted_no_duplicates(subs)); - debug_assert!(fields2.is_sorted_no_duplicates(subs)); - let it1 = fields1.iter_all().map(|(s, vars)| (subs[s], subs[vars])); - let it2 = fields2.iter_all().map(|(s, vars)| (subs[s], subs[vars])); - - separate(it1, it2) -} - #[derive(Debug, Copy, Clone)] enum Rec { None, diff --git a/crates/docs/src/lib.rs b/crates/docs/src/lib.rs index 98fb5be02f..c3995c8bc3 100644 --- a/crates/docs/src/lib.rs +++ b/crates/docs/src/lib.rs @@ -191,6 +191,8 @@ fn render_module_documentation( if should_render_entry { match entry { DocEntry::DocDef(doc_def) => { + buf.push_str("
"); + let mut href = String::new(); href.push('#'); href.push_str(doc_def.name.as_str()); @@ -239,6 +241,8 @@ fn render_module_documentation( .as_str(), ); } + + buf.push_str("
"); } DocEntry::DetachedDoc(docs) => { let markdown = markdown_to_html( diff --git a/crates/docs/src/static/styles.css b/crates/docs/src/static/styles.css index a00e19a2bd..8f14f37c72 100644 --- a/crates/docs/src/static/styles.css +++ b/crates/docs/src/static/styles.css @@ -30,6 +30,7 @@ --nav-link-hover-color: #000000; --type-signature-color: var(--purple-5); --type-signature-bg-color: var(--purple-1); + --module-entry-border-color: var(--gray-3); } a { @@ -75,6 +76,8 @@ a { color: var(--type-signature-color); background-color: var(--type-signature-bg-color); width: fit-content; + margin-top: 0; + margin-bottom: 24px; padding: 8px 16px; border-radius: 8px; } @@ -158,6 +161,17 @@ main { min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ } +section { + border: 1px solid var(--module-entry-border-color); + border-radius: 8px; + padding: 32px; + margin: 32px 0; +} + +section > *:last-child { + margin-bottom: 0; +} + #sidebar-nav { grid-column-start: sidebar; grid-column-end: sidebar; @@ -447,6 +461,7 @@ pre { --top-bar-fg: #eeeeee; --type-signature-color: var(--grey-1); --type-signature-bg-color: var(--purple-4); + --module-entry-border-color: var(--purple-7); } html { diff --git a/crates/repl_eval/src/eval.rs b/crates/repl_eval/src/eval.rs index 84b666e7e0..df2ca2562a 100644 --- a/crates/repl_eval/src/eval.rs +++ b/crates/repl_eval/src/eval.rs @@ -57,6 +57,7 @@ pub fn jit_to_ast<'a, A: ReplApp<'a>>( ProcLayout { arguments: [], result, + captures_niche: _, } => { // this is a thunk jit_to_ast_help(&env, app, main_fn_name, &result, content) diff --git a/crates/reporting/Cargo.toml b/crates/reporting/Cargo.toml index 46a53791a7..e1af05fbc6 100644 --- a/crates/reporting/Cargo.toml +++ b/crates/reporting/Cargo.toml @@ -31,3 +31,4 @@ roc_target = { path = "../compiler/roc_target" } roc_test_utils = { path = "../test_utils" } pretty_assertions = "1.0.0" indoc = "1.0.3" +insta = "1.15.0" diff --git a/crates/reporting/src/report.rs b/crates/reporting/src/report.rs index 010c900f29..201285c528 100644 --- a/crates/reporting/src/report.rs +++ b/crates/reporting/src/report.rs @@ -650,7 +650,7 @@ impl<'a> RocDocAllocator<'a> { let line_number = line_number_string; let this_line_number_length = line_number.len(); - let line = self.src_lines[i as usize]; + let line: &str = self.src_lines.get(i as usize).unwrap_or(&""); let rest_of_line = if !line.trim().is_empty() { self.text(line) diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 5917c331ac..af5c751023 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -22,7 +22,6 @@ mod test_reporting { }; use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_solve::solve; - use roc_test_utils::assert_multiline_str_eq; use roc_types::subs::Subs; use std::path::PathBuf; @@ -333,28 +332,6 @@ mod test_reporting { } } - 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 buf != expected_rendering { - for line in buf.split('\n') { - println!(" {}", line); - } - } - - assert_multiline_str_eq!(expected_rendering, buf.as_str()); - } - fn report_header_problem_as(src: &str, expected_rendering: &str) { let mut buf: String = String::new(); let arena = Bump::new(); @@ -401,7 +378,7 @@ mod test_reporting { } /// Do not call this directly! Use the test_report macro below! - fn __new_report_problem_as(subdir: &str, src: &str, expected_rendering: &str) { + fn __new_report_problem_as(subdir: &str, src: &str, check_render: impl FnOnce(&str)) { let arena = Bump::new(); let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| { @@ -412,23 +389,20 @@ mod test_reporting { let buf = list_reports_new(subdir, &arena, src, finalize_render); - // convenient to copy-paste the generated message - if buf != expected_rendering { - for line in buf.split('\n') { - println!(" {}", line); - } - } - - assert_multiline_str_eq!(expected_rendering, buf.as_str()); + check_render(buf.as_str()); } macro_rules! test_report { - ($test_name:ident, $program:expr, $output:expr) => { - #[test] - fn $test_name() { - __new_report_problem_as(std::stringify!($test_name), $program, $output) - } + ($(#[$meta:meta])* $test_name:ident, $program:expr, @$output:literal) => { + test_report!($(#[$meta])* $test_name, $program, |golden| insta::assert_snapshot!(golden, @$output) ); }; + ($(#[$meta:meta])* $test_name: ident, $program:expr, $expecting:expr) => { + #[test] + $(#[$meta])* + fn $test_name() { + __new_report_problem_as(std::stringify!($test_name), $program, $expecting) + } + } } fn human_readable(str: &str) -> String { @@ -444,364 +418,265 @@ mod test_reporting { .replace(ANSI_STYLE_CODES.underline, "") } - #[test] - fn value_not_exposed() { - report_problem_as( - indoc!( - r#" - List.isempty 1 2 - "# - ), - indoc!( - r#" - ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ + test_report!( + value_not_exposed, + indoc!( + r#" + List.isempty 1 2 + "# + ), + @r###" + ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ - The List module does not expose `isempty`: + The List module does not expose `isempty`: - 1│ List.isempty 1 2 - ^^^^^^^^^^^^ + 4│ List.isempty 1 2 + ^^^^^^^^^^^^ - Did you mean one of these? + Did you mean one of these? - List.isEmpty - List.set - List.iterate - List.get - "# - ), - ) - } + List.isEmpty + List.set + List.iterate + List.get + "### + ); - #[test] - fn report_unused_def() { - report_problem_as( - indoc!( - r#" - x = 1 - y = 2 + test_report!( + report_unused_def, + indoc!( + r#" + x = 1 + y = 2 - x - "# - ), - indoc!( - r#" - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + x + "# + ), + @r###" + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - `y` is not used anywhere in your code. + `y` is not used anywhere in your code. - 2│ y = 2 - ^ + 5│ y = 2 + ^ - If you didn't intend on using `y` then remove it so future readers of - your code don't wonder why it is there. - "# - ), - ) - } + 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 + test_report!( + report_shadowing, + indoc!( + r#" + i = 1 - s = \i -> - i + 1 + s = \i -> + i + 1 - s i - "# - ), - indoc!( - r#" - ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + s i + "# + ), + @r###" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - The `i` name is first defined here: + The `i` name is first defined here: - 1│ i = 1 - ^ + 4│ i = 1 + ^ - But then it's defined a second time here: + But then it's defined a second time here: - 3│ s = \i -> - ^ + 6│ s = \i -> + ^ - Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. - "# - ), - ) - } + 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] + test_report!( + report_shadowing_in_annotation, + indoc!( + r#" + Booly : [Yes, No] - Booly : [Yes, No, Maybe] + Booly : [Yes, No, Maybe] - x : List Booly - x = [] + x : List Booly + x = [] - x - "# - ), - indoc!( - r#" - ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + x + "# + ), + @r###" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - The `Booly` name is first defined here: + The `Booly` name is first defined here: - 1│ Booly : [Yes, No] - ^^^^^^^^^^^^^^^^^ + 4│ Booly : [Yes, No] + ^^^^^^^^^^^^^^^^^ - But then it's defined a second time here: + But then it's defined a second time here: - 3│ Booly : [Yes, No, Maybe] - ^^^^^^^^^^^^^^^^^^^^^^^^ + 6│ Booly : [Yes, No, Maybe] + ^^^^^^^^^^^^^^^^^^^^^^^^ - Since these aliases have the same name, it's easy to use the wrong one - on accident. Give one of them a new name. - "# - ), - ) - } + Since these aliases have the same name, it's easy to use the wrong one + on accident. Give one of them a new name. + "### + ); - // #[test] - // 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - Using != and == together requires parentheses, to clarify how they - should be grouped. - - 3│ if selectedId != thisId == adminsId then - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - "# - ), - ) - } - - #[test] - fn unrecognized_name() { - report_problem_as( - indoc!( - r#" - foo = { x: 1 == 1, y: 0x4 } - - baz = 3 - - main : Str - main = - when foo.y is - 4 -> bar baz "yay" - _ -> "nay" - - main - "# - ), - indoc!( - r#" - ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - - Nothing is named `bar` in this scope. - - 8│ 4 -> bar baz "yay" - ^^^ - - Did you mean one of these? - - baz - Str - Err - main - "# - ), - ) - } - - #[test] - fn lowercase_primitive_tag_bool() { - report_problem_as( - indoc!( - r#" - if true then 1 else 2 - "# - ), - indoc!( - r#" - ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - - Nothing is named `true` in this scope. - - 1│ if true then 1 else 2 - ^^^^ - - Did you mean one of these? - - True - Str - Err - List - "# - ), - ) - } - - #[test] - fn report_precedence_problem_multiline() { - report_problem_as( - indoc!( - r#" - if - 1 - == 2 - == 3 - then - 2 + test_report!( + report_precedence_problem_single_line, + indoc!( + r#"x = 1 + y = + if selectedId != thisId == adminsId then + 4 else - 3 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + 5 - Using more than one == like this requires parentheses, to clarify how - things should be grouped. + { x, y } + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - 2│> 1 - 3│> == 2 - 4│> == 3 - "# - ), - ) - } + Using != and == together requires parentheses, to clarify how they + should be grouped. - #[test] - fn unused_arg_and_unused_def() { - report_problem_as( - indoc!( - r#" - y = 9 + 6│ if selectedId != thisId == adminsId then + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + "### + ); - box = \class, htmlChildren -> - div [class] [] + test_report!( + #[ignore = "Blocked on https://github.com/rtfeldman/roc/issues/3385"] + unrecognized_name, + indoc!( + r#" + foo = { x: 1 == 1, y: 0x4 } - div = \_, _ -> 4 + baz = 3 - box "wizard" [] - "# - ), - indoc!( - r#" - ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ + main : Str + main = + when foo.y is + 4 -> bar baz "yay" + _ -> "nay" - `box` doesn't use `htmlChildren`. + main + "# + ), + @r#" + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - 3│ box = \class, htmlChildren -> - ^^^^^^^^^^^^ + Nothing is named `bar` in this scope. - 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. + 8│ 4 -> bar baz "yay" + ^^^ - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + Did you mean one of these? - `y` is not used anywhere in your code. + baz + Str + Err + main + "# + ); - 1│ y = 9 - ^ + test_report!( + lowercase_primitive_tag_bool, + indoc!( + r#" + if true then 1 else 2 + "# + ), + @r###" + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - If you didn't intend on using `y` then remove it so future readers of - your code don't wonder why it is there. - "# - ), - ); - } + Nothing is named `true` in this scope. + + 4│ if true then 1 else 2 + ^^^^ + + Did you mean one of these? + + True + Str + Frac + Num + "### + ); + + test_report!( + report_precedence_problem_multiline, + indoc!( + r#" + if + 1 + == 2 + == 3 + then + 2 + + else + 3 + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + Using more than one == like this requires parentheses, to clarify how + things should be grouped. + + 5│> 1 + 6│> == 2 + 7│> == 3 + "### + ); + + test_report!( + unused_arg_and_unused_def, + indoc!( + r#" + y = 9 + + box = \class, htmlChildren -> + div [class] [] + + div = \_, _ -> 4 + + box "wizard" [] + "# + ), + @r###" + ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ + + `box` doesn't use `htmlChildren`. + + 6│ box = \class, htmlChildren -> + ^^^^^^^^^^^^ + + If you don't need `htmlChildren`, then you can just remove it. However, + if you really do need `htmlChildren` as an argument of `box`, prefix it + with an underscore, like this: "_`htmlChildren`". Adding an underscore + at the start of a variable name is a way of saying that the variable + is not used. + + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + + `y` is not used anywhere in your code. + + 4│ y = 9 + ^ + + If you didn't intend on using `y` then remove it so future readers of + your code don't wonder why it is there. + "### + ); #[test] fn report_value_color() { @@ -893,488 +768,390 @@ mod test_reporting { ); } - // #[test] - // fn shadowing_type_alias() { - // report_problem_as( - // indoc!( - // r#" - // foo : I64 as I64 - // foo = 42 + test_report!( + if_condition_not_bool, + indoc!( + r#" + if "foo" then 2 else 3 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - // foo - // "# - // ), - // indoc!( - // r#" - // You cannot mix (!=) and (==) without parentheses + This `if` condition needs to be a Bool: - // 3│ if selectedId != thisId == adminsId then - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 4│ if "foo" then 2 else 3 + ^^^^^ - // "# - // ), - // ) - // } + Right now it’s a string of type: - // #[test] - // fn invalid_as_type_alias() { - // report_problem_as( - // indoc!( - // r#" - // foo : I64 as a - // foo = 42 + Str - // foo - // "# - // ), - // indoc!( - // r#" - // You cannot mix (!=) and (==) without parentheses + But I need every `if` condition to evaluate to a Bool—either `True` or + `False`. + "### + ); - // 3│ if selectedId != thisId == adminsId then - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + test_report!( + when_if_guard, + indoc!( + r#" + when 1 is + 2 if 1 -> 0x0 + _ -> 0x1 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - // "# - // ), - // ) - // } + This `if` guard condition needs to be a Bool: - #[test] - fn if_condition_not_bool() { - report_problem_as( - indoc!( - r#" - if "foo" then 2 else 3 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + 4│ when 1 is + 5│> 2 if 1 -> 0x0 + 6│ _ -> 0x1 - This `if` condition needs to be a Bool: + Right now it’s a number of type: - 1│ if "foo" then 2 else 3 + Num a + + But I need every `if` guard condition to evaluate to a Bool—either + `True` or `False`. + "### + ); + + test_report!( + if_2_branch_mismatch, + indoc!( + r#" + if True then 2 else "foo" + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This `if` has an `else` branch with a different type from its `then` branch: + + 4│ if True then 2 else "foo" + ^^^^^ + + The `else` branch is a string of type: + + Str + + but the `then` branch has the type: + + Num a + + All branches in an `if` must have the same type! + "### + ); + + test_report!( + if_3_branch_mismatch, + indoc!( + r#" + if True then 2 else if False then 2 else "foo" + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 3rd branch of this `if` does not match all the previous branches: + + 4│ if True then 2 else if False then 2 else "foo" + ^^^^^ + + The 3rd branch is a string of type: + + Str + + But all the previous branches have type: + + Num a + + All branches in an `if` must have the same type! + "### + ); + + test_report!( + when_branch_mismatch, + indoc!( + r#" + when 1 is + 2 -> "foo" + 3 -> {} + _ -> "" + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd branch of this `when` does not match all the previous branches: + + 4│ when 1 is + 5│ 2 -> "foo" + 6│> 3 -> {} + 7│ _ -> "" + + The 2nd branch is a record of type: + + {} + + But all the previous branches have type: + + Str + + All branches of a `when` must have the same type! + "### + ); + + test_report!( + elem_in_list, + indoc!( + r#" + [1, 3, "foo"] + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This list contains elements with different types: + + 4│ [1, 3, "foo"] + ^^^^^ + + Its 3rd element is a string of type: + + Str + + However, the preceding elements in the list all have the type: + + Num a + + Every element in a list must have the same type! + "### + ); + + test_report!( + unwrap_num_elem_in_list, + indoc!( + r#" + [1, 2.2, 0x3] + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This list contains elements with different types: + + 4│ [1, 2.2, 0x3] + ^^^ + + Its 3rd element is an integer of type: + + Int a + + However, the preceding elements in the list all have the type: + + Frac a + + Every element in a list must have the same type! + + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. + "### + ); + + test_report!( + record_update_value, + indoc!( + r#" + x : { foo : {} } + x = { foo: {} } + + { x & foo: "bar" } + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + I cannot update the `.foo` field like this: + + 7│ { x & foo: "bar" } ^^^^^ - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 - - All branches in an `if` must 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 + You are trying to update `.foo` to be a string of type: - All branches in an `if` must have the same type! - "# - ), - ) - } - - #[test] - fn when_branch_mismatch() { - report_problem_as( - indoc!( - r#" - when 1 is - 2 -> "foo" - 3 -> {} - _ -> "" - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd branch of this `when` does not match all the previous branches: - - 1│ when 1 is - 2│ 2 -> "foo" - 3│> 3 -> {} - 4│ _ -> "" + Str - The 2nd branch is a record of type: + But it should be: - {} + {} - But all the previous branches have type: + Record update syntax does not allow you to change the type of fields. + You can achieve that with record literal syntax. + "### + ); - Str + test_report!( + circular_type, + indoc!( + r#" + f = \g -> g g - All branches of a `when` must have the same type! - "# - ), - ) - } + f + "# + ), + @r###" + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn elem_in_list() { - report_problem_as( - indoc!( - r#" - [1, 3, "foo"] - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + I'm inferring a weird self-referential type for `g`: - This list contains elements with different types: + 4│ f = \g -> g g + ^ - 1│ [1, 3, "foo"] - ^^^^^ + 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. - Its 3rd element is a string of type: + ∞ -> a + "### + ); - Str + test_report!( + polymorphic_recursion, + indoc!( + r#" + f = \x -> f [x] - However, the preceding elements in the list all have the type: + f + "# + ), + @r###" + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - Num a + I'm inferring a weird self-referential type for `f`: - Every element in a list must have the same type! - "# - ), - ) - } - - #[test] - fn unwrap_num_elem_in_list() { - report_problem_as( - indoc!( - r#" - [1, 2.2, 0x3] - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + 4│ f = \x -> f [x] + ^ - This list contains elements with different types: + 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. - 1│ [1, 2.2, 0x3] - ^^^ + List ∞ -> a + "### + ); - Its 3rd element is an integer of type: + test_report!( + polymorphic_mutual_recursion, + indoc!( + r#" + f = \x -> g x + g = \x -> f [x] - Int a + f + "# + ), + @r###" + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - However, the preceding elements in the list all have the type: - - Frac a - - Every element in a list must have the same type! - - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. - "# - ), - ) - } - - #[test] - fn record_update_value() { - report_problem_as( - indoc!( - r#" - x : { foo : {} } - x = { foo: {} } - - { x & foo: "bar" } - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - I cannot update the `.foo` field like this: - - 4│ { x & foo: "bar" } - ^^^^^ + I'm inferring a weird self-referential type for `f`: - You are trying to update `.foo` to be a string of type: + 4│ f = \x -> g x + ^ - Str + 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. - But it should be: + List ∞ -> a - {} - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - 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 polymorphic_mutual_recursion() { - report_problem_as( - indoc!( - r#" - f = \x -> g x - g = \x -> f [x] - - f - "# - ), - indoc!( - r#" - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - - I'm inferring a weird self-referential type for `f`: - - 1│ f = \x -> g x - ^ - - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. - - List ∞ -> a - - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - - I'm inferring a weird self-referential type for `g`: - - 2│ g = \x -> f [x] - ^ - - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. - - List ∞ -> a - "# - ), - ) - } - - #[test] - fn polymorphic_mutual_recursion_annotated() { - report_problem_as( - indoc!( - r#" - f : a -> List a - f = \x -> g x - g = \x -> f [x] - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression is used in an unexpected way: - - 2│ f = \x -> g x - ^^^ - - This `g` call produces: - - List List a - - But you are trying to use it as: - - List a - - Tip: The type annotation uses the type variable `a` to say that this - definition can produce any type of value. But in the body I see that - it will only produce a `List` value of a single specific type. Maybe - change the type annotation to be more specific? Maybe change the code - to be more general? - "# - ), - ) - } - - #[test] - fn polymorphic_mutual_recursion_dually_annotated_lie() { - report_problem_as( - indoc!( - r#" - f : a -> List a - f = \x -> g x - g : b -> List b - g = \x -> f [x] - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression is used in an unexpected way: - - 4│ g = \x -> f [x] - ^^^^^ - - This `f` call produces: - - List List b + I'm inferring a weird self-referential type for `g`: - But you are trying to use it as: - - List b + 5│ g = \x -> f [x] + ^ - Tip: The type annotation uses the type variable `b` to say that this - definition can produce any type of value. But in the body I see that - it will only produce a `List` value of a single specific type. Maybe - change the type annotation to be more specific? Maybe change the code - to be more general? - "# - ), - ) - } + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. + + List ∞ -> a + "### + ); + + test_report!( + polymorphic_mutual_recursion_annotated, + indoc!( + r#" + f : a -> List a + f = \x -> g x + g = \x -> f [x] + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression is used in an unexpected way: + + 5│ f = \x -> g x + ^^^ + + This `g` call produces: + + List List a + + But you are trying to use it as: + + List a + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `List` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "### + ); + + test_report!( + polymorphic_mutual_recursion_dually_annotated_lie, + indoc!( + r#" + f : a -> List a + f = \x -> g x + g : b -> List b + g = \x -> f [x] + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression is used in an unexpected way: + + 7│ g = \x -> f [x] + ^^^^^ + + This `f` call produces: + + List List b + + But you are trying to use it as: + + List b + + Tip: The type annotation uses the type variable `b` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `List` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "### + ); test_report!( polymorphic_recursion_inference_var, @@ -1386,22 +1163,20 @@ mod test_reporting { f "# ), - indoc!( - r#" - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `f`: + I'm inferring a weird self-referential type for `f`: - 5│ f = \x -> f [x] - ^ + 5│ f = \x -> f [x] + ^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. + 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 - "# - ) + List ∞ -> a + "# ); test_report!( @@ -1414,22 +1189,20 @@ mod test_reporting { f "# ), - indoc!( - r#" - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `x`: + I'm inferring a weird self-referential type for `x`: - 5│ f = \x -> f [x] - ^ + 5│ f = \x -> f [x] + ^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. - List ∞ - "# - ) + List ∞ + "# ); test_report!( @@ -1443,59 +1216,57 @@ mod test_reporting { f "# ), - indoc!( - // TODO: the second error is duplicated because when solving `f : _ -> List _`, we - // introduce the variable for `f` twice: once to solve `f` without generalization, - // and then a second time to properly generalize it. When a def is unannotated - // (like in `g`) the same variable gets used both times, because the type of `g` is - // only an unbound type variable. However, for `f`, we run `type_to_var` twice, - // receiving two separate variables, and the second variable doesn't have the cycle - // error already recorded for the first. - // The way to resolve this is to always give type annotation signatures an extra - // variables they can put themselves in, and to run the constraint algorithm - // against that extra variable, rather than possibly having to translate a `Type` - // again. - r#" - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + // TODO: the second error is duplicated because when solving `f : _ -> List _`, we + // introduce the variable for `f` twice: once to solve `f` without generalization, + // and then a second time to properly generalize it. When a def is unannotated + // (like in `g`) the same variable gets used both times, because the type of `g` is + // only an unbound type variable. However, for `f`, we run `type_to_var` twice, + // receiving two separate variables, and the second variable doesn't have the cycle + // error already recorded for the first. + // The way to resolve this is to always give type annotation signatures an extra + // variables they can put themselves in, and to run the constraint algorithm + // against that extra variable, rather than possibly having to translate a `Type` + // again. + @r#" + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `f`: + I'm inferring a weird self-referential type for `f`: - 5│ f = \x -> g x - ^ + 5│ f = \x -> g x + ^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. - List ∞ -> List a + List ∞ -> List a - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `g`: + I'm inferring a weird self-referential type for `g`: - 6│ g = \x -> f [x] - ^ + 6│ g = \x -> f [x] + ^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. - List ∞ -> List a + List ∞ -> List a - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `f`: + I'm inferring a weird self-referential type for `f`: - 5│ f = \x -> g x - ^ + 5│ f = \x -> g x + ^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. - List ∞ -> List a - "# - ) + List ∞ -> List a + "# ); test_report!( @@ -1509,4521 +1280,3966 @@ mod test_reporting { f "# ), - indoc!( - r#" - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `x`: + I'm inferring a weird self-referential type for `x`: - 6│ g = \x -> f [x] - ^ + 6│ g = \x -> f [x] + ^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. - List ∞ - "# - ) + List ∞ + "# ); - #[test] - fn record_field_mismatch() { - report_problem_as( - indoc!( - r#" - bar = { bar : 0x3 } + test_report!( + record_field_mismatch, + indoc!( + r#" + bar = { bar : 0x3 } - f : { foo : Num.Int * } -> [Yes, No] - f = \_ -> Yes + f : { foo : Num.Int * } -> [Yes, No] + f = \_ -> Yes - f bar - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + f bar + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st argument to `f` is not what I expect: + The 1st argument to `f` is not what I expect: - 6│ f bar - ^^^ + 9│ f bar + ^^^ - This `bar` value is a: + This `bar` value is a: - { bar : Int a } + { bar : Int a } - But `f` needs the 1st argument to be: + But `f` needs the 1st argument to be: - { foo : Int * } + { foo : Int * } - Tip: Seems like a record field typo. Maybe `bar` should be `foo`? + 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 - "# - ), - ) - } + 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] -> [Yes, No] - f = \_ -> Yes + test_report!( + tag_mismatch, + indoc!( + r#" + f : [Red, Green] -> [Yes, No] + f = \_ -> Yes - f Blue - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + f Blue + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st argument to `f` is not what I expect: + The 1st argument to `f` is not what I expect: - 4│ f Blue + 7│ f Blue + ^^^^ + + This `Blue` 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_report!( + tag_with_arguments_mismatch, + indoc!( + r#" + f : [Red (Num.Int *), Green Str] -> Str + f = \_ -> "yes" + + f (Blue 3.14) + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `f` is not what I expect: + + 7│ f (Blue 3.14) + ^^^^^^^^^ + + This `Blue` tag application has the type: + + [Blue (Frac a)]b + + But `f` needs the 1st argument to be: + + [Green Str, Red (Int *)] + + Tip: Seems like a tag typo. Maybe `Blue` should be `Red`? + + Tip: Can more type annotations be added? Type annotations always help + me give more specific messages, and I think they could help a lot in + this case + "### + ); + + test_report!( + from_annotation_if, + indoc!( + r#" + x : Num.Int * + x = if True then 3.14 else 4 + + x + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the `then` branch of this `if` expression: + + 4│ x : Num.Int * + 5│ x = if True then 3.14 else 4 + ^^^^ + + The 1st branch is a frac of type: + + Frac a + + But the type annotation on `x` says it should be: + + Int * + + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. + "### + ); + + test_report!( + from_annotation_when, + indoc!( + r#" + x : Num.Int * + x = + when True is + _ -> 3.14 + + x + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `x` definition: + + 4│ x : Num.Int * + 5│ x = + 6│> when True is + 7│> _ -> 3.14 + + This `when` expression produces: + + Frac a + + But the type annotation on `x` says it should be: + + Int * + + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. + "### + ); + + test_report!( + from_annotation_function, + indoc!( + r#" + x : Num.Int * -> Num.Int * + x = \_ -> 3.14 + + x + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `x` definition: + + 4│ x : Num.Int * -> Num.Int * + 5│ x = \_ -> 3.14 ^^^^ - This `Blue` tag has the type: + The body is a frac of type: - [Blue]a + Frac a - But `f` needs the 1st argument to be: + But the type annotation on `x` says it should be: - [Green, Red] - - Tip: Seems like a tag typo. Maybe `Blue` should be `Red`? + Int * - 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 - "# - ), - ) - } + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. + "### + ); - #[test] - fn tag_with_arguments_mismatch() { - report_problem_as( - indoc!( - r#" - f : [Red (Num.Int *), Green Str] -> Str - f = \_ -> "yes" + test_report!( + fncall_value, + indoc!( + r#" + x : Num.I64 + x = 42 + + x 3 + "# + ), + @r###" + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ - f (Blue 3.14) - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + The `x` value is not a function, but it was given 1 argument: - The 1st argument to `f` is not what I expect: + 7│ x 3 + ^ - 4│ f (Blue 3.14) - ^^^^^^^^^ + Are there any missing commas? Or missing parentheses? + "### + ); - This `Blue` tag application has the type: + test_report!( + fncall_overapplied, + indoc!( + r#" + f : Num.I64 -> Num.I64 + f = \_ -> 42 - [Blue (Frac a)]b + f 1 2 + "# + ), + @r###" + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ - But `f` needs the 1st argument to be: - - [Green Str, Red (Int *)] + The `f` function expects 1 argument, but it got 2 instead: - Tip: Seems like a tag typo. Maybe `Blue` should be `Red`? + 7│ f 1 2 + ^ - 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 - "# - ), - ) - } + Are there any missing commas? Or missing parentheses? + "### + ); - #[test] - fn from_annotation_if() { - report_problem_as( - indoc!( - r#" - x : Num.Int * - x = if True then 3.14 else 4 + test_report!( + fncall_underapplied, + indoc!( + r#" + f : Num.I64, Num.I64 -> Num.I64 + f = \_, _ -> 42 - x - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + f 1 + "# + ), + @r###" + ── TOO FEW ARGS ────────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with the `then` branch of this `if` expression: + The `f` function expects 2 arguments, but it got only 1: - 1│ x : Num.Int * - 2│ x = if True then 3.14 else 4 - ^^^^ + 7│ f 1 + ^ - The 1st branch is a frac of type: + Roc does not allow functions to be partially applied. Use a closure to + make partial application explicit. + "### + ); - Frac a - - But the type annotation on `x` says it should be: + test_report!( + pattern_when_condition, + indoc!( + r#" + when 1 is + {} -> 42 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Int * + The branches of this `when` expression don't match the condition: - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. - "# - ), - ) - } - - #[test] - fn from_annotation_when() { - report_problem_as( - indoc!( - r#" - x : Num.Int * - x = - when True is - _ -> 3.14 - - x - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `x` definition: - - 1│ x : Num.Int * - 2│ x = - 3│> when True is - 4│> _ -> 3.14 - - This `when` expression produces: - - Frac a - - But the type annotation on `x` says it should be: - - Int * - - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. - "# - ), - ) - } - - #[test] - fn from_annotation_function() { - report_problem_as( - indoc!( - r#" - x : Num.Int * -> Num.Int * - x = \_ -> 3.14 - - x - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `x` definition: - - 1│ x : Num.Int * -> Num.Int * - 2│ x = \_ -> 3.14 - ^^^^ - - The body is a frac of type: - - Frac a - - But the type annotation on `x` says it should be: - - Int * - - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. - "# - ), - ) - } - - #[test] - fn fncall_value() { - report_problem_as( - indoc!( - r#" - x : Num.I64 - x = 42 - - x 3 - "# - ), - indoc!( - r#" - ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.I64 -> Num.I64 - f = \_ -> 42 - - f 1 2 - "# - ), - indoc!( - r#" - ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.I64, Num.I64 -> Num.I64 - f = \_, _ -> 42 - - f 1 - "# - ), - indoc!( - r#" - ── TOO FEW ARGS ────────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - The branches of this `when` expression don't match the condition: - - 1│> when 1 is - 2│ {} -> 42 - - The `when` condition is a number of type: - - Num a - - But the branch patterns have type: - - {}a - - The branches must be cases of the `when` condition's type! - "# - ), - ) - } - - #[test] - fn pattern_when_pattern() { - report_problem_as( - indoc!( - r#" - when 1 is - 2 -> 3 - {} -> 42 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd pattern in this `when` does not match the previous ones: - - 3│ {} -> 42 - ^^ - - The 2nd pattern is trying to match record values of type: - - {}a - - But all the previous branches match: - - Num a - "# - ), - ) - } - - #[test] - fn pattern_guard_mismatch_alias() { - report_problem_as( - indoc!( - r#" - when { foo: 1 } is - { foo: True } -> 42 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The branches of this `when` expression don't match the condition: - - 1│> when { foo: 1 } is - 2│ { foo: True } -> 42 - - The `when` condition is a record of type: - - { foo : Num a } - - But the branch patterns have type: - - { foo : [True] } - - The branches must be cases of the `when` condition's type! - "# - ), - ) - } - - #[test] - fn pattern_guard_mismatch() { - report_problem_as( - indoc!( - r#" - when { foo: "" } is - { foo: True } -> 42 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The branches of this `when` expression don't match the condition: - - 1│> when { foo: "" } is - 2│ { foo: True } -> 42 - - The `when` condition is a record of type: - - { foo : Str } - - But the branch patterns have type: - - { foo : [True] } - - The branches must be cases of the `when` condition's type! - "# - ), - ) - } - - #[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: _ } -> foo - "# - ), - indoc!( - r#" - ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - - Nothing is named `foo` in this scope. - - 2│ { foo: _ } -> foo - ^^^ - - Did you mean one of these? - - Box - Set - Str - Ok - "# - ), - ) - } - - #[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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd pattern in this branch does not match the previous ones: - - 2│ {} | 1 -> 3 - ^ - - The 2nd pattern is trying to match numbers: - - Num a - - But all the previous branches match: - - {}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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression is used in an unexpected way: - - 1│ (Foo x) = 42 - ^^ - - It is a number of type: - - Num a - - But you are trying to use it as: - - [Foo a] - "# - ), - ) - } - - #[test] - fn from_annotation_complex_pattern() { - report_problem_as( - indoc!( - r#" - { x } : { x : Num.Int * } - { x } = { x: 4.0 } - - x - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of this definition: - - 1│ { x } : { x : Num.Int * } - 2│ { x } = { x: 4.0 } - ^^^^^^^^^^ - - The body is a record of type: - - { x : Frac a } - - But the type annotation says it should be: - - { x : Int * } - - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. - "# - ), - ) - } - - #[test] - fn malformed_int_pattern() { - report_problem_as( - indoc!( - r#" - when 1 is - 100A -> 3 - _ -> 4 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.Int *, b : Num.Frac *, c : Str } - x = { b: 4.0 } - - x - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `x` definition: - - 1│ x : { a : Num.Int *, b : Num.Frac *, c : Str } - 2│ x = { b: 4.0 } - ^^^^^^^^^^ - - The body is a record of type: - - { b : Frac a } - - But the type annotation on `x` says it should be: - - { a : Int *, b : Frac *, c : Str } - - Tip: Looks like the c and a fields are missing. - "# - ), - ) - } - - #[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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the `else` branch of this `if` expression: - - 1│ f : a, b -> a - 2│ f = \x, y -> if True then x else y - ^ - - This `y` value is a: - - b - - But the type annotation on `f` says it should be: - - a - - Tip: Your type annotation uses `b` and `a` as separate type variables. - Your code seems to be saying they are the same though. Maybe they - should be the same in your type annotation? Maybe your code uses them - in a weird way? - "# - ), - ) - } - - #[test] - fn bad_rigid_function() { - report_problem_as( - indoc!( - r#" - f : Str -> msg - f = \_ -> Foo - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `f` definition: - - 1│ f : Str -> msg - 2│ f = \_ -> Foo + 4│> when 1 is + 5│ {} -> 42 + + The `when` condition is a number of type: + + Num a + + But the branch patterns have type: + + {}a + + The branches must be cases of the `when` condition's type! + "### + ); + + test_report!( + pattern_when_pattern, + indoc!( + r#" + when 1 is + 2 -> 3 + {} -> 42 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd pattern in this `when` does not match the previous ones: + + 6│ {} -> 42 + ^^ + + The 2nd pattern is trying to match record values of type: + + {}a + + But all the previous branches match: + + Num a + "### + ); + + test_report!( + pattern_guard_mismatch_alias, + indoc!( + r#" + when { foo: 1 } is + { foo: True } -> 42 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The branches of this `when` expression don't match the condition: + + 4│> when { foo: 1 } is + 5│ { foo: True } -> 42 + + The `when` condition is a record of type: + + { foo : Num a } + + But the branch patterns have type: + + { foo : [True] } + + The branches must be cases of the `when` condition's type! + "### + ); + + test_report!( + pattern_guard_mismatch, + indoc!( + r#" + when { foo: "" } is + { foo: True } -> 42 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The branches of this `when` expression don't match the condition: + + 4│> when { foo: "" } is + 5│ { foo: True } -> 42 + + The `when` condition is a record of type: + + { foo : Str } + + But the branch patterns have type: + + { foo : [True] } + + The branches must be cases of the `when` condition's type! + "### + ); + + // needs some improvement, but the principle works + test_report!( + pattern_guard_does_not_bind_label, + indoc!( + r#" + when { foo: 1 } is + { foo: _ } -> foo + "# + ), + @r###" + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ + + Nothing is named `foo` in this scope. + + 5│ { foo: _ } -> foo ^^^ - This `Foo` tag has the type: + Did you mean one of these? - [Foo]a + Box + Bool + U8 + F64 + "### + ); - But the type annotation on `f` says it should be: + test_report! { + pattern_guard_can_be_shadowed_above, + indoc!( + r#" + foo = 3 - 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? - "# - ), - ) + when { foo: 1 } is + { foo: 2 } -> foo + _ -> foo + "# + ), + @"" // should give no error } - #[test] - fn bad_rigid_value() { - report_problem_as( - indoc!( - r#" - f : msg - f = 0x3 + test_report! { + pattern_guard_can_be_shadowed_below, + indoc!( + r#" + when { foo: 1 } is + { foo: 2 } -> + foo = 3 - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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? - "# - ), - ) + foo + _ -> 3 + "# + ), + // should give no error + @"" } - #[test] - fn typo_lowercase_ok() { - // TODO improve tag suggestions - report_problem_as( - indoc!( - r#" - f : Str -> [Ok Num.I64, InvalidFoo] - f = \_ -> ok 4 - - f - "# - ), - indoc!( - r#" - ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - - Nothing is named `ok` in this scope. - - 2│ f = \_ -> ok 4 - ^^ - - Did you mean one of these? - - Ok - f - Box - Set - "# - ), - ) - } - - #[test] - fn typo_uppercase_ok() { - // these error messages seem pretty helpful - report_problem_as( - indoc!( - r#" - f : Str -> Num.I64 - f = \_ -> - ok = 3 - - Ok - - f - "# - ), - indoc!( - r#" - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `f` definition: - - 1│ f : Str -> Num.I64 - 2│ f = \_ -> - 3│ ok = 3 - 4│ - 5│ Ok - ^^ - - This `Ok` tag has the type: - - [Ok]a - - But the type annotation on `f` says it should be: - - I64 - "# - ), - ) - } - - #[test] - fn circular_definition_self() { - // invalid recursion - report_problem_as( - indoc!( - r#" - f = f - - f - "# - ), - indoc!( - r#" - ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ - - The `f` value is defined directly in terms of itself, causing an - infinite loop. - "# - ), - ) - } - - #[test] - fn circular_definition() { - // invalid mutual recursion - report_problem_as( - indoc!( - r#" - foo = bar - - bar = foo - - foo - "# - ), - indoc!( - r#" - ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - This `x` record doesn’t have a `foo` field: - - 3│ { x & foo: 3 } - ^^^^^^ - - In fact, `x` is a record with no fields at all! - "# - ), - ) - } - - #[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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - This `x` record doesn’t have a `foo` field: - - 3│ { x & foo: 3 } - ^^^^^^ - - There may be a typo. These `x` fields are the most similar: - - { - fo : Num b, - bar : Num a, - } - - Maybe `foo:` should be `fo:` instead? - "# - ), - ) - } - - #[test] - fn update_record_ext() { - report_problem_as( - indoc!( - r#" - f : { fo: Num.I64 }ext -> Num.I64 - f = \r -> - r2 = { r & foo: r.fo } - - r2.fo - - f - "# - ), - // TODO also suggest fields with the correct type - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This `r` record doesn’t have a `foo` field: - - 3│ r2 = { r & foo: r.fo } - ^^^^^^^^^ - - There may be a typo. These `r` fields are the most similar: - - { - fo : I64, - }ext - - Maybe `foo:` should be `fo:` instead? - "# - ), - ) - } - - #[test] - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - This `x` record doesn’t have a `foo` field: - - 3│ { x & foo: 3 } - ^^^^^^ - - There may be a typo. These `x` fields are the most similar: - - { - fo : Num c, - foobar : Num d, - bar : Num a, - baz : Num b, - … - } - - Maybe `foo:` should be `fo:` instead? - "# - ), - ) - } - - #[test] - fn plus_on_str() { - report_problem_as( - indoc!( - r#" - 0x4 + "foo" - "# - ), - // TODO also suggest fields with the correct type - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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: - - Int a - "# - ), - ) - } - - #[test] - fn int_frac() { - report_problem_as( - indoc!( - r#" - 0x4 + 3.14 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd argument to `add` is not what I expect: - - 1│ 0x4 + 3.14 + test_report!( + pattern_or_pattern_mismatch, + indoc!( + r#" + when { foo: 1 } is + {} | 1 -> 3 + "# + ), + // Just putting this here. We should probably handle or-patterns better + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd pattern in this branch does not match the previous ones: + + 5│ {} | 1 -> 3 + ^ + + The 2nd pattern is trying to match numbers: + + Num a + + But all the previous branches match: + + {}a + "### + ); + + test_report!( + pattern_let_mismatch, + indoc!( + r#" + (Foo x) = 42 + + x + "# + ), + // Maybe this should specifically say the pattern doesn't work? + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression is used in an unexpected way: + + 4│ (Foo x) = 42 + ^^ + + It is a number of type: + + Num a + + But you are trying to use it as: + + [Foo a] + "### + ); + + test_report!( + from_annotation_complex_pattern, + indoc!( + r#" + { x } : { x : Num.Int * } + { x } = { x: 4.0 } + + x + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of this definition: + + 4│ { x } : { x : Num.Int * } + 5│ { x } = { x: 4.0 } + ^^^^^^^^^^ + + The body is a record of type: + + { x : Frac a } + + But the type annotation says it should be: + + { x : Int * } + + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. + "### + ); + + test_report!( + malformed_int_pattern, + indoc!( + r#" + when 1 is + 100A -> 3 + _ -> 4 + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This integer pattern is malformed: + + 5│ 100A -> 3 + ^^^^ + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + malformed_float_pattern, + indoc!( + r#" + when 1 is + 2.X -> 3 + _ -> 4 + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This float pattern is malformed: + + 5│ 2.X -> 3 + ^^^ + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + malformed_hex_pattern, + indoc!( + r#" + when 1 is + 0xZ -> 3 + _ -> 4 + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This hex integer pattern is malformed: + + 5│ 0xZ -> 3 + ^^^ + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + malformed_oct_pattern, + indoc!( + r#" + when 1 is + 0o9 -> 3 + _ -> 4 + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This octal integer pattern is malformed: + + 5│ 0o9 -> 3 + ^^^ + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + malformed_bin_pattern, + indoc!( + r#" + when 1 is + 0b4 -> 3 + _ -> 4 + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This binary integer pattern is malformed: + + 5│ 0b4 -> 3 + ^^^ + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + missing_fields, + indoc!( + r#" + x : { a : Num.Int *, b : Num.Frac *, c : Str } + x = { b: 4.0 } + + x + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `x` definition: + + 4│ x : { a : Num.Int *, b : Num.Frac *, c : Str } + 5│ x = { b: 4.0 } + ^^^^^^^^^^ + + The body is a record of type: + + { b : Frac a } + + But the type annotation on `x` says it should be: + + { a : Int *, b : Frac *, c : Str } + + Tip: Looks like the c and a fields are missing. + "### + ); + + // this previously reported the message below, not sure which is better + // + // Something is off with the body of the `f` definition: + // + // 1│ f : a, b -> a + // 2│ f = \x, y -> if True then x else y + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // + // The body is an anonymous function of type: + // + // a, a -> a + // + // But the type annotation on `f` says it should be: + // + // a, b -> a + test_report!( + bad_double_rigid, + indoc!( + r#" + f : a, b -> a + f = \x, y -> if True then x else y + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the `else` branch of this `if` expression: + + 4│ f : a, b -> a + 5│ f = \x, y -> if True then x else y + ^ + + This `y` value is a: + + b + + But the type annotation on `f` says it should be: + + a + + Tip: Your type annotation uses `b` and `a` as separate type variables. + Your code seems to be saying they are the same though. Maybe they + should be the same in your type annotation? Maybe your code uses them + in a weird way? + "### + ); + + test_report!( + bad_rigid_function, + indoc!( + r#" + f : Str -> msg + f = \_ -> Foo + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `f` definition: + + 4│ f : Str -> msg + 5│ f = \_ -> Foo + ^^^ + + This `Foo` tag has the type: + + [Foo]a + + But the type annotation on `f` says it should be: + + msg + + Tip: The type annotation uses the type variable `msg` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a tag value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "### + ); + + test_report!( + bad_rigid_value, + indoc!( + r#" + f : msg + f = 0x3 + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `f` definition: + + 4│ f : msg + 5│ f = 0x3 + ^^^ + + The body is an integer of type: + + Int a + + But the type annotation on `f` says it should be: + + msg + + Tip: The type annotation uses the type variable `msg` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `Int` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "### + ); + + // TODO improve tag suggestions + test_report!( + typo_lowercase_ok, + indoc!( + r#" + f : Str -> [Ok Num.I64, InvalidFoo] + f = \_ -> ok 4 + + f + "# + ), + @r###" + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ + + Nothing is named `ok` in this scope. + + 5│ f = \_ -> ok 4 + ^^ + + Did you mean one of these? + + Ok + U8 + Box + f + "### + ); + + // these error messages seem pretty helpful + test_report!( + typo_uppercase_ok, + indoc!( + r#" + f : Str -> Num.I64 + f = \_ -> + ok = 3 + + Ok + + f + "# + ), + @r###" + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + + `ok` is not used anywhere in your code. + + 6│ ok = 3 + ^^ + + If you didn't intend on using `ok` then remove it so future readers of + your code don't wonder why it is there. + + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `f` definition: + + 4│ f : Str -> Num.I64 + 5│ f = \_ -> + 6│ ok = 3 + 7│ + 8│ Ok + ^^ + + This `Ok` tag has the type: + + [Ok]a + + But the type annotation on `f` says it should be: + + I64 + "### + ); + + // invalid recursion + test_report!( + circular_definition_self, + indoc!( + r#" + f = f + + f + "# + ), + @r#" + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ + + The `f` value is defined directly in terms of itself, causing an + infinite loop. + "# + ); + + // invalid mutual recursion + test_report!( + circular_definition, + indoc!( + r#" + foo = bar + + bar = foo + + foo + "# + ), + @r###" + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ + + The `foo` definition is causing a very tricky infinite loop: + + 4│ foo = bar + ^^^ + + The `foo` value depends on itself through the following chain of + definitions: + + ┌─────┐ + │ foo + │ ↓ + │ bar + └─────┘ + "### + ); + + test_report!( + update_empty_record, + indoc!( + r#" + x = {} + + { x & foo: 3 } + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This `x` record doesn’t have a `foo` field: + + 6│ { x & foo: 3 } + ^^^^^^ + + In fact, `x` is a record with no fields at all! + "### + ); + + test_report!( + update_record, + indoc!( + r#" + x = { fo: 3, bar: 4 } + + { x & foo: 3 } + "# + ), + // TODO also suggest fields with the correct type + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This `x` record doesn’t have a `foo` field: + + 6│ { x & foo: 3 } + ^^^^^^ + + There may be a typo. These `x` fields are the most similar: + + { + fo : Num b, + bar : Num a, + } + + Maybe `foo:` should be `fo:` instead? + "### + ); + + test_report!( + update_record_ext, + indoc!( + r#" + f : { fo: Num.I64 }ext -> Num.I64 + f = \r -> + r2 = { r & foo: r.fo } + + r2.fo + + f + "# + ), + // TODO also suggest fields with the correct type + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This `r` record doesn’t have a `foo` field: + + 6│ r2 = { r & foo: r.fo } + ^^^^^^^^^ + + There may be a typo. These `r` fields are the most similar: + + { + fo : I64, + }ext + + Maybe `foo:` should be `fo:` instead? + "### + ); + + test_report!( + update_record_snippet, + indoc!( + r#" + x = { fo: 3, bar: 4, baz: 3, spam: 42, foobar: 3 } + + { x & foo: 3 } + "# + ), + // TODO also suggest fields with the correct type + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This `x` record doesn’t have a `foo` field: + + 6│ { x & foo: 3 } + ^^^^^^ + + There may be a typo. These `x` fields are the most similar: + + { + fo : Num c, + foobar : Num d, + bar : Num a, + baz : Num b, + … + } + + Maybe `foo:` should be `fo:` instead? + "### + ); + + test_report!( + plus_on_str, + indoc!( + r#" + 0x4 + "foo" + "# + ), + // TODO also suggest fields with the correct type + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd argument to `add` is not what I expect: + + 4│ 0x4 + "foo" + ^^^^^ + + This argument is a string of type: + + Str + + But `add` needs the 2nd argument to be: + + Int a + "### + ); + + test_report!( + int_frac, + indoc!( + r#" + 0x4 + 3.14 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd argument to `add` is not what I expect: + + 4│ 0x4 + 3.14 + ^^^^ + + This argument is a frac of type: + + Frac a + + But `add` needs the 2nd argument to be: + + Int a + + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. + "### + ); + + test_report!( + boolean_tag, + indoc!( + r#" + 42 + True + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd argument to `add` is not what I expect: + + 4│ 42 + True + ^^^^ + + This `True` boolean has the type: + + [True]a + + But `add` needs the 2nd argument to be: + + Num a + "### + ); + + test_report!( + tag_missing, + indoc!( + r#" + f : [A] -> [A, B] + f = \a -> a + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `f` definition: + + 4│ f : [A] -> [A, B] + 5│ f = \a -> a + ^ + + This `a` value is a: + + [A] + + But the type annotation on `f` says it should be: + + [A, B] + + Tip: Looks like a closed tag union does not have the `B` tag. + + Tip: Closed tag unions can't grow, because that might change the size + in memory. Can you use an open tag union? + "### + ); + + test_report!( + tags_missing, + indoc!( + r#" + f : [A] -> [A, B, C] + f = \a -> a + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `f` definition: + + 4│ f : [A] -> [A, B, C] + 5│ f = \a -> a + ^ + + This `a` value is a: + + [A] + + But the type annotation on `f` says it should be: + + [A, B, C] + + Tip: Looks like a closed tag union does not have the `B` and `C` tags. + + Tip: Closed tag unions can't grow, because that might change the size + in memory. Can you use an open tag union? + "### + ); + + test_report!( + patterns_fn_not_exhaustive, + indoc!( + r#" + Either : [Left {}, Right Str] + + x : Either + x = Left {} + + f : Either -> {} + f = \Left v -> v + + f x + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This pattern does not cover all the possibilities: + + 10│ f = \Left v -> v + ^^^^^^ + + Other possibilities include: + + Right _ + + I would have to crash if I saw one of those! So rather than pattern + matching in function arguments, put a `when` in the function body to + account for all possibilities. + "### + ); + + test_report!( + patterns_let_not_exhaustive, + indoc!( + r#" + x : [Left {}, Right Str] + x = Left {} + + + (Left y) = x + + y + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression is used in an unexpected way: + + 8│ (Left y) = x + ^ + + This `x` value is a: + + [Left {}, Right Str] + + But you are trying to use it as: + + [Left a] + + Tip: Looks like a closed tag union does not have the `Right` tag. + + Tip: Closed tag unions can't grow, because that might change the size + in memory. Can you use an open tag union? + "### + ); + + test_report!( + patterns_when_not_exhaustive, + indoc!( + r#" + when 0x1 is + 2 -> 0x3 + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 4│> when 0x1 is + 5│> 2 -> 0x3 + + Other possibilities include: + + _ + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + patterns_bool_not_exhaustive, + indoc!( + r#" + x : [Red, Green] + x = Green + + when x is + Red -> 3 + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 7│> when x is + 8│> Red -> 3 + + Other possibilities include: + + Green + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + patterns_enum_not_exhaustive, + indoc!( + r#" + x : [Red, Green, Blue] + x = Red + + when x is + Red -> 0 + Green -> 1 + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 7│> when x is + 8│> Red -> 0 + 9│> Green -> 1 + + Other possibilities include: + + Blue + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + patterns_remote_data_not_exhaustive, + indoc!( + r#" + RemoteData e a : [NotAsked, Loading, Failure e, Success a] + + x : RemoteData Num.I64 Str + + when x is + NotAsked -> 3 + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 8│> when x is + 9│> NotAsked -> 3 + + Other possibilities include: + + Failure _ + Loading + Success _ + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + patterns_record_not_exhaustive, + indoc!( + r#" + x = { a: 3 } + + when x is + { a: 4 } -> 4 + "# + ), + // Tip: Looks like a record field guard is not exhaustive. Learn more about record pattern matches at TODO. + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 6│> when x is + 7│> { a: 4 } -> 4 + + Other possibilities include: + + { a } + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + patterns_record_guard_not_exhaustive, + indoc!( + r#" + y : [Nothing, Just Num.I64] + y = Just 4 + x = { a: y, b: 42} + + when x is + { a: Nothing } -> 4 + { a: Just 3 } -> 4 + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 8│> when x is + 9│> { a: Nothing } -> 4 + 10│> { a: Just 3 } -> 4 + + Other possibilities include: + + { a: Just _ } + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + patterns_nested_tag_not_exhaustive, + indoc!( + r#" + when Record Nothing 1 is + Record (Nothing) b -> b + Record (Just 3) b -> b + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 4│> when Record Nothing 1 is + 5│> Record (Nothing) b -> b + 6│> Record (Just 3) b -> b + + Other possibilities include: + + Record (Just _) _ + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + patterns_int_redundant, + indoc!( + r#" + when 0x1 is + 2 -> 3 + 2 -> 4 + _ -> 5 + "# + ), + @r###" + ── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd pattern is redundant: + + 4│ when 0x1 is + 5│ 2 -> 3 + 6│> 2 -> 4 + 7│ _ -> 5 + + Any value of this shape will be handled by a previous pattern, so this + one should be removed. + "### + ); + + test_report!( + unify_alias_other, + indoc!( + r#" + Foo a : { x : Num.Int a } + + f : Foo a -> Num.Int a + f = \r -> r.x + + f { y: 3.14 } + "# + ), + // de-aliases the alias to give a better error message + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `f` is not what I expect: + + 9│ f { y: 3.14 } + ^^^^^^^^^^^ + + This argument is a record of type: + + { y : Frac a } + + But `f` needs the 1st argument to be: + + { x : Int a } + + Tip: Seems like a record field typo. Maybe `y` should be `x`? + + Tip: Can more type annotations be added? Type annotations always help + me give more specific messages, and I think they could help a lot in + this case + "### + ); + + test_report!( + #[ignore] + cyclic_alias, + indoc!( + r#" + Foo : { x : Bar } + Bar : { y : Foo } + + f : Foo + + f + "# + ), + // should not report Bar as unused! + @r###" + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + + The `Foo` alias is self-recursive in an invalid way: + + 4│ Foo : { x : Bar } + ^^^ + + Recursion in aliases is only allowed if recursion happens behind a + tagged union, at least one variant of which is not recursive. + "### + ); + + test_report!( + self_recursive_alias, + indoc!( + r#" + Foo : { x : Foo } + + f : Foo + f = 3 + + f + "# + ), + // should not report Bar as unused! + @r###" + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + + The `Foo` alias is self-recursive in an invalid way: + + 4│ Foo : { x : Foo } + ^^^ + + Recursion in aliases is only allowed if recursion happens behind a + tagged union, at least one variant of which is not recursive. + "### + ); + + test_report!( + record_duplicate_field_same_type, + indoc!( + r#" + { x: 4, y: 3, x: 4 } + "# + ), + @r###" + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ + + This record defines the `.x` field twice! + + 4│ { x: 4, y: 3, x: 4 } + ^^^^ ^^^^ + + In the rest of the program, I will only use the latter definition: + + 4│ { x: 4, y: 3, x: 4 } ^^^^ - This argument is a frac of type: - - Frac a - - But `add` needs the 2nd argument to be: - - Int a - - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. - "# - ), - ) - } - - #[test] - fn boolean_tag() { - report_problem_as( - indoc!( - r#" - 42 + True - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 `B` and `C` tags. - - Tip: Closed tag unions can't grow, because that might change the size - in memory. Can you use an open tag union? - "# - ), - ) - } - - #[test] - fn patterns_fn_not_exhaustive() { - report_problem_as( - indoc!( - r#" - Either : [Left {}, Right Str] - - x : Either - x = Left {} - - f : Either -> {} - f = \Left v -> v - - f x - "# - ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 {}, Right Str] - x = Left {} - - - (Left y) = x - - y - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression is used in an unexpected way: - - 5│ (Left y) = x - ^ - - This `x` value is a: - - [Left {}, Right Str] - - But you are trying to use it as: - - [Left a] - - Tip: Looks like a closed tag union does not have the `Right` tag. - - Tip: Closed tag unions can't grow, because that might change the size - in memory. Can you use an open tag union? - "# - ), - ) - } - - #[test] - fn patterns_when_not_exhaustive() { - report_problem_as( - indoc!( - r#" - when 0x1 is - 2 -> 0x3 - "# - ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : [Red, Green] - x = Green - - when x is - Red -> 3 - "# - ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ - - This `when` does not cover all the possibilities: - - 4│> when x is - 5│> Red -> 3 - - Other possibilities include: - - Green - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 Num.I64 Str - - when x is - NotAsked -> 3 - "# - ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 Num.I64] - y = Just 4 - x = { a: y, b: 42} - - when x is - { a: Nothing } -> 4 - { a: Just 3 } -> 4 - "# - ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 _ } - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd pattern is redundant: - - 1│ when 0x1 is - 2│ 2 -> 3 - 3│> 2 -> 4 - 4│ _ -> 5 - - Any value of this shape will be handled by a previous pattern, so this - one should be removed. - "# - ), - ) - } - - #[test] - fn unify_alias_other() { - report_problem_as( - indoc!( - r#" - Foo a : { x : Num.Int a } - - f : Foo a -> Num.Int a - f = \r -> r.x - - f { y: 3.14 } - "# - ), - // de-aliases the alias to give a better error message - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 1st argument to `f` is not what I expect: - - 6│ f { y: 3.14 } - ^^^^^^^^^^^ - - This argument is a record of type: - - { y : Frac a } - - But `f` needs the 1st argument to be: - - { x : Int a } - - Tip: Seems like a record field typo. Maybe `y` should be `x`? - - Tip: Can more type annotations be added? Type annotations always help - me give more specific messages, and I think they could help a lot in - this case - "# - ), - ) - } - - #[test] - #[ignore] - fn cyclic_alias() { - report_problem_as( - indoc!( - r#" - Foo : { x : Bar } - Bar : { y : Foo } - - f : Foo - - f - "# - ), - // should not report Bar as unused! - indoc!( - r#" - ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - `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 ────────────────────────────────────────── /code/proj/Main.roc ─ - - The `Foo` alias is self-recursive in an invalid way: - - 1│ Foo : { x : Foo } - ^^^ - - Recursion in aliases is only allowed if recursion happens behind a - tagged union, at least one variant of which is not recursive. - "# - ), - ) - } - - #[test] - fn record_duplicate_field_same_type() { - report_problem_as( - indoc!( - r#" - { x: 4, y: 3, x: 4 } - "# - ), - indoc!( - r#" - ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ - - This record defines the `.x` field twice! - - 1│ { x: 4, y: 3, x: 4 } - ^^^^ ^^^^ - - In the rest of the program, I will only use the latter definition: - - 1│ { x: 4, y: 3, x: 4 } - ^^^^ - - For clarity, remove the previous `.x` definitions from this record. - "# - ), - ) - } - - #[test] - fn record_duplicate_field_different_types() { - report_problem_as( - indoc!( - r#" - { x: 4, y: 3, x: "foo" } - "# - ), - indoc!( - r#" - ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ - - 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#" - { + For clarity, remove the previous `.x` definitions from this record. + "### + ); + + test_report!( + record_duplicate_field_different_types, + indoc!( + r#" + { x: 4, y: 3, x: "foo" } + "# + ), + @r###" + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ + + This record defines the `.x` field twice! + + 4│ { x: 4, y: 3, x: "foo" } + ^^^^ ^^^^^^^^ + + In the rest of the program, I will only use the latter definition: + + 4│ { x: 4, y: 3, x: "foo" } + ^^^^^^^^ + + For clarity, remove the previous `.x` definitions from this record. + "### + ); + + test_report!( + record_duplicate_field_multiline, + indoc!( + r#" + { + x: 4, + y: 3, + x: "foo" + } + "# + ), + @r###" + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ + + This record defines the `.x` field twice! + + 4│ { + 5│> x: 4, + 6│ y: 3, + 7│> x: "foo" + 8│ } + + In the rest of the program, I will only use the latter definition: + + 4│ { + 5│ x: 4, + 6│ y: 3, + 7│> x: "foo" + 8│ } + + For clarity, remove the previous `.x` definitions from this record. + "### + ); + + test_report!( + record_update_duplicate_field_multiline, + indoc!( + r#" + \r -> + { r & x: 4, y: 3, x: "foo" } - "# - ), - indoc!( - r#" - ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ - - This record defines the `.x` field twice! - - 1│ { - 2│> x: 4, - 3│ y: 3, - 4│> x: "foo" - 5│ } - - In the rest of the program, I will only use the latter definition: - - 1│ { - 2│ x: 4, - 3│ y: 3, - 4│> x: "foo" - 5│ } - - For clarity, remove the previous `.x` definitions from this record. - "# - ), - ) - } - - #[test] - fn record_update_duplicate_field_multiline() { - report_problem_as( - indoc!( - r#" - \r -> - { r & - x: 4, - y: 3, - x: "foo" - } - "# - ), - indoc!( - r#" - ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.I64, bar : {}, foo : Str } - a = { bar: {}, foo: "foo" } - - a - "# - ), - indoc!( - r#" - ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ - - This record type defines the `.foo` field twice! - - 1│ a : { foo : Num.I64, bar : {}, foo : Str } - ^^^^^^^^^^^^^ ^^^^^^^^^ - - In the rest of the program, I will only use the latter definition: - - 1│ a : { foo : Num.I64, bar : {}, 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 Num.I64, Bar {}, Foo Str] - a = Foo "foo" - - a - "# - ), - indoc!( - r#" - ── DUPLICATE TAG NAME ──────────────────────────────────── /code/proj/Main.roc ─ - - This tag union type defines the `Foo` tag twice! - - 1│ a : [Foo Num.I64, Bar {}, Foo Str] - ^^^^^^^^^^^ ^^^^^^^ - - In the rest of the program, I will only use the latter definition: - - 1│ a : [Foo Num.I64, Bar {}, 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 : Num.I64 - foo = \x -> x - - # NOTE: neither bar or foo are defined at this point - 4 - "# - ), - indoc!( - r#" - ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This annotation does not match the definition immediately following - it: - - 1│> bar : Num.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 : Num.I64 - - foo = \x -> x - - foo bar - "# - ), - indoc!(""), - ) - } - - #[test] - fn invalid_alias_rigid_var_pattern() { - report_problem_as( - indoc!( - r#" - MyAlias 1 : Num.I64 - - 4 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This pattern in the definition of `MyAlias` is not what I expect: - - 1│ MyAlias 1 : Num.I64 - ^ - - Only type variables like `a` or `value` can occur in this position. - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `MyAlias` is not used anywhere in your code. - - 1│ MyAlias 1 : Num.I64 - ^^^^^^^^^^^^^^^^^^^ - - If you didn't intend on using `MyAlias` then remove it so future readers - of your code don't wonder why it is there. - "# - ), - ) - } - - #[test] - fn invalid_opaque_rigid_var_pattern() { - report_problem_as( - indoc!( - r#" - Age 1 := Num.I64 - - a : Age - a - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This pattern in the definition of `Age` is not what I expect: - - 1│ Age 1 := Num.I64 - ^ - - Only type variables like `a` or `value` can occur in this position. - "# - ), - ) - } - - #[test] - fn invalid_num() { - report_problem_as( - indoc!( - r#" - a : Num.Num Num.I64 Num.F64 - a = 3 - - a - "# - ), - indoc!( - r#" - ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ - - The `Num` alias expects 1 type argument, but it got 2 instead: - - 1│ a : Num.Num Num.I64 Num.F64 - ^^^^^^^^^^^^^^^^^^^^^^^ - - Are there missing parentheses? - "# - ), - ) - } - - #[test] - fn invalid_num_fn() { - report_problem_as( - indoc!( - r#" - f : Str -> Num.Num Num.I64 Num.F64 - f = \_ -> 3 - - f - "# - ), - indoc!( - r#" - ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ - - The `Num` alias expects 1 type argument, but it got 2 instead: - - 1│ f : Str -> Num.Num Num.I64 Num.F64 - ^^^^^^^^^^^^^^^^^^^^^^^ - - Are there missing parentheses? - "# - ), - ) - } - - #[test] - fn too_few_type_arguments() { - report_problem_as( - indoc!( - r#" - Pair a b : [Pair a b] - - x : Pair Num.I64 - x = Pair 2 3 - - x - "# - ), - indoc!( - r#" - ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ - - The `Pair` alias expects 2 type arguments, but it got 1 instead: - - 3│ x : Pair Num.I64 - ^^^^^^^^^^^^ - - Are there missing parentheses? - "# - ), - ) - } - - #[test] - fn too_many_type_arguments() { - report_problem_as( - indoc!( - r#" - Pair a b : [Pair a b] - - x : Pair Num.I64 Num.I64 Num.I64 - x = 3 - - x - "# - ), - indoc!( - r#" - ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ - - The `Pair` alias expects 2 type arguments, but it got 3 instead: - - 3│ x : Pair Num.I64 Num.I64 Num.I64 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Are there missing parentheses? - "# - ), - ) - } - - #[test] - fn phantom_type_variable() { - report_problem_as( - indoc!( - r#" - Foo a : [Foo] - - f : Foo Num.I64 - - f - "# - ), - indoc!( - r#" - ── UNUSED TYPE ALIAS PARAMETER ─────────────────────────── /code/proj/Main.roc ─ - - The `a` type parameter is not used in the `Foo` alias definition: - - 1│ Foo a : [Foo] - ^ - - Roc does not allow unused type alias parameters! - - Tip: If you want an unused type parameter (a so-called "phantom - type"), read the guide section on phantom values. - "# - ), - ) - } - - #[test] - fn elm_function_syntax() { - report_problem_as( - indoc!( - r#" - f x y = x - "# - ), - indoc!( - r#" - ── ARGUMENTS BEFORE EQUALS ─────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `x` definition: - - 3│ x : ConsList {} - 4│ x = Cons {} (Cons "foo" Nil) - ^^^^^^^^^^^^^^^^^^^^^^^^ - - This `Cons` tag application has the type: - - [Cons {} [Cons Str [Cons {} a, Nil] as a, Nil], Nil] - - But the type annotation on `x` says it should be: - - [Cons {} a, Nil] as a - "# - ), - ) - } - - #[test] - 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 Num.I64 Num.I64 - x = ACons 0 (BCons 1 (ACons "foo" BNil )) - - y : BList a a - y = BNil - - { x, y } - "# - ), - // TODO render tag unions across multiple lines - // TODO do not show recursion var if the recursion var does not render on the surface of a type - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `x` definition: - - 4│ x : AList Num.I64 Num.I64 - 5│ x = ACons 0 (BCons 1 (ACons "foo" BNil )) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - This `ACons` tag application has the type: - - [ACons (Int Signed64) [BCons (Int Signed64) [ACons Str [BCons I64 [ACons I64 (BList I64 I64), - ANil] as ∞, BNil], ANil], BNil], ANil] - - But the type annotation on `x` says it should be: - - [ACons I64 (BList I64 I64), ANil] as a - "# - ), - ) - } - - #[test] - fn integer_out_of_range() { - report_problem_as( - indoc!( - r#" - x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000 - - y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000 - - h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - - minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 - maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 - - x + y + h + l + minlit + maxlit - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This integer literal is too big: - - 1│ x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - The largest number representable in Roc is the maximum U128 value, - 340_282_366_920_938_463_463_374_607_431_768_211_455. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This integer literal is too small: - - 3│ y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - The smallest number representable in Roc is the minimum I128 value, - -170_141_183_460_469_231_731_687_303_715_884_105_728. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This integer literal is too big: - - 5│ h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - The largest number representable in Roc is the maximum U128 value, - 340_282_366_920_938_463_463_374_607_431_768_211_455. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This integer literal is too small: - - 6│ l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - The smallest number representable in Roc is the minimum I128 value, - -170_141_183_460_469_231_731_687_303_715_884_105_728. - - Tip: Learn more about number literals at TODO - "# - ), - ) - } - - #[test] - fn float_out_of_range() { - // have to deal with some whitespace issues because of the format! macro - report_problem_as( - indoc!( - r#" - overflow = 11.7976931348623157e308 - underflow = -11.7976931348623157e308 - - overflow + underflow - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - This integer literal contains an invalid digit: - - 1│ dec = 100A - ^^^^ - - Integer literals can only contain the digits - 0-9, or have an integer suffix. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This hex integer literal contains an invalid digit: - - 3│ hex = 0xZZZ - ^^^^^ - - Hexadecimal (base-16) integer literals can only contain the digits - 0-9, a-f and A-F, or have an integer suffix. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This octal integer literal contains an invalid digit: - - 5│ oct = 0o9 - ^^^ - - Octal (base-8) integer literals can only contain the digits - 0-7, or have an integer suffix. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This binary integer literal contains an invalid digit: - - 7│ bin = 0b2 - ^^^ - - Binary (base-2) integer literals can only contain the digits - 0 and 1, or have an integer suffix. - - Tip: Learn more about number literals at TODO - "# - ), - ) - } - - #[test] - fn integer_empty() { - report_problem_as( - indoc!( - r#" - dec = 20 - - hex = 0x - - oct = 0o - - bin = 0b - - dec + hex + oct + bin - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This hex integer literal contains no digits: - - 3│ hex = 0x - ^^ - - Hexadecimal (base-16) integer literals must contain at least one of - the digits 0-9, a-f and A-F, or have an integer suffix. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This octal integer literal contains no digits: - - 5│ oct = 0o - ^^ - - Octal (base-8) integer literals must contain at least one of the - digits 0-7, or have an integer suffix. - - Tip: Learn more about number literals at TODO - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This binary integer literal contains no digits: - - 7│ bin = 0b - ^^ - - Binary (base-2) integer literals must contain at least one of the - digits 0 and 1, or have an integer suffix. - - Tip: Learn more about number literals at TODO - "# - ), - ) - } - - #[test] - fn float_malformed() { - report_problem_as( - indoc!( - r#" - x = 3.0A - - x - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This float literal contains an invalid digit: - - 1│ x = 3.0A - ^^^^ - - Floating point literals can only contain the digits 0-9, or use - scientific notation 10e4, or have a float suffix. - - Tip: Learn more about number literals at TODO - "# - ), - ) - } - - #[test] - fn invalid_record_update() { - report_problem_as( - indoc!( - r#" - foo = { bar: 3 } - updateNestedRecord = { foo.bar & x: 4 } - - example = { age: 42 } - - # these should work - y = { Test.example & age: 3 } - x = { example & age: 4 } - - { updateNestedRecord, foo, x, y } - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This expression cannot be updated: - - 2│ updateNestedRecord = { foo.bar & x: 4 } - ^^^^^^^ - - Only variables can be updated with record update syntax. - "# - ), - ) - } - - #[test] - fn module_not_imported() { - report_problem_as( - indoc!( - r#" - Foo.test - "# - ), - indoc!( - r#" - ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ - - The `Foo` module is not imported: - - 1│ Foo.test - ^^^^^^^^ - - Is there an import missing? Perhaps there is a typo. Did you mean one - of these? - - Box - Bool - Num - Set - "# - ), - ) - } - - #[test] - fn optional_record_default_type_error() { - report_problem_as( - indoc!( - r#" - \{ x, y ? True } -> x + y - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.I64, y ? Num.I64 } -> Num.I64 - f = \{ x, y ? "foo" } -> (\g, _ -> g) x y - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.I64, y ? Str } - { x, y } = rec - - { x, y } - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of this definition: - - 2│> { x, y } : { x : Num.I64, y ? Str } - 3│> { x, y } = rec - - The body is a value of type: - - { x : I64, y : Str } - - But the type annotation says it should be: - - { x : I64, y ? Str } - - Tip: To extract the `.y` field it must be non-optional, but the type - says this field is optional. Learn more about optional fields at TODO. - "# - ), - ) - } - - #[test] - fn optional_record_invalid_function() { - report_problem_as( - indoc!( - r#" - f : { x : Num.I64, y ? Num.I64 } -> Num.I64 - f = \{ x, y } -> x + y - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.I64, y ? Num.I64 } -> Num.I64 - f = \r -> - when r is - { x, y } -> x + y - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The branches of this `when` expression don't match the condition: - - 3│> when r is - 4│ { x, y } -> x + y - - This `r` value is a: - - { x : I64, y ? I64 } - - But the branch patterns have type: - - { x : I64, y : I64 } - - The branches must be cases of the `when` condition's type! - - Tip: To extract the `.y` field it must be non-optional, but the type - says this field is optional. Learn more about optional fields at TODO. - "# - ), - ) - } - - #[test] - fn optional_record_invalid_access() { - report_problem_as( - indoc!( - r#" - f : { x : Num.I64, y ? Num.I64 } -> Num.I64 - f = \r -> r.y - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.I64, y ? Num.I64 } -> Num.I64 - f = \r -> .y r - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 : Num.I64, y : Num.I64 } -> Num.I64 - f = \r -> - when r is - { x, y : "foo" } -> x + 0 - _ -> 0 - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The branches of this `when` expression don't match the condition: - - 3│> when r is - 4│ { x, y : "foo" } -> x + 0 - 5│ _ -> 0 - - This `r` value is a: - - { x : I64, y : I64 } - - But the branch patterns have type: - - { x : I64, y : Str } - - The branches must be cases of the `when` condition's type! - "# - ), - ) - } - - #[test] - fn optional_field_mismatch_with_annotation() { - report_problem_as( - indoc!( - r#" - f : { x : Num.I64, y ? Num.I64 } -> Num.I64 - f = \r -> - when r is - { x, y ? "foo" } -> (\g, _ -> g) x y - _ -> 0 - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The branches of this `when` expression don't match the condition: - - 3│> when r is - 4│ { x, y ? "foo" } -> (\g, _ -> g) x y - 5│ _ -> 0 - - This `r` value is a: - - { x : I64, y ? I64 } - - But the branch patterns have type: - - { x : I64, y ? Str } - - The branches must be cases of the `when` condition's type! - "# - ), - ) - } - - #[test] - fn incorrect_optional_field() { - report_problem_as( - indoc!( - r#" - { x: 5, y ? 42 } - "# - ), - indoc!( - r#" - ── BAD OPTIONAL VALUE ──────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────── /code/proj/Main.roc ─ - - The 3rd pattern is redundant: - - 1│ when Foo 1 2 3 is - 2│ Foo _ 1 _ -> 1 - 3│ _ -> 2 - 4│ _ -> 3 + "# + ), + @r###" + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ + + This record defines the `.x` field twice! + + 5│ { r & + 6│> x: 4, + 7│ y: 3, + 8│> x: "foo" + 9│ } + + In the rest of the program, I will only use the latter definition: + + 5│ { r & + 6│ x: 4, + 7│ y: 3, + 8│> x: "foo" + 9│ } + + For clarity, remove the previous `.x` definitions from this record. + "### + ); + + test_report!( + record_type_duplicate_field, + indoc!( + r#" + a : { foo : Num.I64, bar : {}, foo : Str } + a = { bar: {}, foo: "foo" } + + a + "# + ), + @r###" + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ + + This record type defines the `.foo` field twice! + + 4│ a : { foo : Num.I64, bar : {}, foo : Str } + ^^^^^^^^^^^^^ ^^^^^^^^^ + + In the rest of the program, I will only use the latter definition: + + 4│ a : { foo : Num.I64, bar : {}, foo : Str } + ^^^^^^^^^ + + For clarity, remove the previous `.foo` definitions from this record + type. + "### + ); + + test_report!( + tag_union_duplicate_tag, + indoc!( + r#" + a : [Foo Num.I64, Bar {}, Foo Str] + a = Foo "foo" + + a + "# + ), + @r###" + ── DUPLICATE TAG NAME ──────────────────────────────────── /code/proj/Main.roc ─ + + This tag union type defines the `Foo` tag twice! + + 4│ a : [Foo Num.I64, Bar {}, Foo Str] + ^^^^^^^^^^^ ^^^^^^^ + + In the rest of the program, I will only use the latter definition: + + 4│ a : [Foo Num.I64, Bar {}, Foo Str] + ^^^^^^^ + + For clarity, remove the previous `Foo` definitions from this tag union + type. + "### + ); + + test_report!( + annotation_definition_mismatch, + indoc!( + r#" + bar : Num.I64 + foo = \x -> x + + # NOTE: neither bar or foo are defined at this point + 4 + "# + ), + @r###" + ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This annotation does not match the definition immediately following + it: + + 4│> bar : Num.I64 + 5│> foo = \x -> x + + Is it a typo? If not, put either a newline or comment between them. + "### + ); + + test_report!( + annotation_newline_body_is_fine, + indoc!( + r#" + bar : Num.I64 + + foo = \x -> x + + foo bar + "# + ), + @"" + ); + + test_report!( + invalid_alias_rigid_var_pattern, + indoc!( + r#" + MyAlias 1 : Num.I64 + + 4 + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This pattern in the definition of `MyAlias` is not what I expect: + + 4│ MyAlias 1 : Num.I64 ^ - Any value of this shape will be handled by a previous pattern, so this - one should be removed. + Only type variables like `a` or `value` can occur in this position. + + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + + `MyAlias` is not used anywhere in your code. + + 4│ MyAlias 1 : Num.I64 + ^^^^^^^^^^^^^^^^^^^ + + If you didn't intend on using `MyAlias` then remove it so future readers + of your code don't wonder why it is there. + "### + ); + + test_report!( + invalid_opaque_rigid_var_pattern, + indoc!( + r#" + Age 1 := Num.I64 + + a : Age + a "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn alias_using_alias() { - report_problem_as( - indoc!( - r#" - # The color of a node. Leaves are considered Black. - NodeColor : [Red, Black] + This pattern in the definition of `Age` is not what I expect: - RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + 4│ Age 1 := Num.I64 + ^ - # Create an empty dictionary. - empty : RBTree k v - empty = - Empty + Only type variables like `a` or `value` can occur in this position. + "### + ); - empty - "# - ), - "", - ) - } + test_report!( + invalid_num, + indoc!( + r#" + a : Num.Num Num.I64 Num.F64 + a = 3 - #[test] - fn unused_argument() { - report_problem_as( - indoc!( - r#" - f = \foo -> 1 - - f - "# - ), - indoc!( - r#" - ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ - - `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. + a "# - ), - ) - } + ), + @r###" + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn qualified_tag() { - report_problem_as( - indoc!( - r#" - Foo.Bar - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + The `Num` alias expects 1 type argument, but it got 2 instead: - I am trying to parse a qualified name here: + 4│ a : Num.Num Num.I64 Num.F64 + ^^^^^^^^^^^^^^^^^^^^^^^ - 1│ Foo.Bar - ^ + Are there missing parentheses? + "### + ); - This looks like a qualified tag name to me, but tags cannot be - qualified! Maybe you wanted a qualified name, something like - Json.Decode.string? + test_report!( + invalid_num_fn, + indoc!( + r#" + f : Str -> Num.Num Num.I64 Num.F64 + f = \_ -> 3 + + f "# - ), - ) - } + ), + @r###" + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn module_ident_ends_with_dot() { - report_problem_as( - indoc!( - r#" - Foo.Bar. - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + The `Num` alias expects 1 type argument, but it got 2 instead: - I am trying to parse a qualified name here: + 4│ f : Str -> Num.Num Num.I64 Num.F64 + ^^^^^^^^^^^^^^^^^^^^^^^ - 1│ Foo.Bar. - ^ + Are there missing parentheses? + "### + ); - I was expecting to see an identifier next, like height. A complete - qualified name looks something like Json.Decode.string. + test_report!( + too_few_type_arguments, + indoc!( + r#" + Pair a b : [Pair a b] + + x : Pair Num.I64 + x = Pair 2 3 + + x "# - ), - ) - } + ), + @r###" + ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn record_access_ends_with_dot() { - report_problem_as( - indoc!( - r#" - foo.bar. - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + The `Pair` alias expects 2 type arguments, but it got 1 instead: - I trying to parse a record field access here: + 6│ x : Pair Num.I64 + ^^^^^^^^^^^^ - 1│ foo.bar. - ^ + Are there missing parentheses? + "### + ); - So I expect to see a lowercase letter next, like .name or .height. + test_report!( + too_many_type_arguments, + indoc!( + r#" + Pair a b : [Pair a b] + + x : Pair Num.I64 Num.I64 Num.I64 + x = 3 + + x "# - ), - ) - } + ), + @r###" + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn type_annotation_double_colon() { - report_problem_as( - indoc!( - r#" - f :: I64 - f = 42 + The `Pair` alias expects 2 type arguments, but it got 3 instead: - f - "# - ), - indoc!( - r#" - ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ + 6│ x : Pair Num.I64 Num.I64 Num.I64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - This looks like an operator, but it's not one I recognize! + Are there missing parentheses? + "### + ); - 1│ f :: I64 - ^^ + test_report!( + phantom_type_variable, + indoc!( + r#" + Foo a : [Foo] - I have no specific suggestion for this operator, see TODO for the full - list of operators in Roc. + f : Foo Num.I64 + + f "# - ), - ) - } + ), + @r###" + ── UNUSED TYPE ALIAS PARAMETER ─────────────────────────── /code/proj/Main.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 + The `a` type parameter is not used in the `Foo` alias definition: - { x, y } - "# - ), - indoc!( - r#" - ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ + 4│ Foo a : [Foo] + ^ - This value is not a function, but it was given 3 arguments: + Roc does not allow unused type alias parameters! - 3│ x == 5 - ^ + Tip: If you want an unused type parameter (a so-called "phantom + type"), read the guide section on phantom values. + "### + ); - Are there any missing commas? Or missing parentheses? + test_report!( + elm_function_syntax, + indoc!( + r#" + f x y = x "# - ), - ) - } + ), + @r###" + ── ARGUMENTS BEFORE EQUALS ────────────────── tmp/elm_function_syntax/Test.roc ─ - #[test] - fn tag_union_open() { - report_problem_as( - indoc!( - r#" - f : [ - "# - ), - indoc!( - r#" - ── UNFINISHED TAG UNION TYPE ───────────────────────────── /code/proj/Main.roc ─ + I am partway through parsing a definition, but I got stuck here: - I just started parsing a tag union type, but I got stuck here: + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ f x y = x + ^^^ - 1│ f : [ - ^ + Looks like you are trying to define a function. In roc, functions are + always written as a lambda, like increment = \n -> n + 1. + "### + ); - Tag unions look like [Many I64, None], so I was expecting to see a tag - name next. + test_report!( + two_different_cons, + indoc!( + r#" + ConsList a : [Cons a (ConsList a), Nil] + + x : ConsList {} + x = Cons {} (Cons "foo" Nil) + + x "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn tag_union_end() { - report_problem_as( - indoc!( - r#" - f : [Yes, - "# - ), - indoc!( - r#" - ── UNFINISHED TAG UNION TYPE ───────────────────────────── /code/proj/Main.roc ─ + Something is off with the body of the `x` definition: - I am partway through parsing a tag union type, but I got stuck here: + 6│ x : ConsList {} + 7│ x = Cons {} (Cons "foo" Nil) + ^^^^^^^^^^^^^^^^^^^^^^^^ - 1│ f : [Yes, - ^ + This `Cons` tag application has the type: - I was expecting to see a closing square bracket before this, so try - adding a ] and see if that helps? + [Cons {} [Cons Str [Cons {} a, Nil] as a, Nil], Nil] + + But the type annotation on `x` says it should be: + + [Cons {} a, Nil] as a + "### + ); + + test_report!( + mutually_recursive_types_with_type_error, + indoc!( + r#" + AList a b : [ACons a (BList a b), ANil] + BList a b : [BCons a (AList a b), BNil] + + x : AList Num.I64 Num.I64 + x = ACons 0 (BCons 1 (ACons "foo" BNil )) + + y : BList a a + y = BNil + + { x, y } "# - ), - ) - } + ), + // TODO render tag unions across multiple lines + // TODO do not show recursion var if the recursion var does not render on the surface of a type + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn tag_union_lowercase_tag_name() { - report_problem_as( - indoc!( - r#" - f : [lowercase] - "# - ), - indoc!( - r#" - ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + Something is off with the body of the `x` definition: - I am partway through parsing a tag union type, but I got stuck here: + 7│ x : AList Num.I64 Num.I64 + 8│ x = ACons 0 (BCons 1 (ACons "foo" BNil )) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 1│ f : [lowercase] - ^ + This `ACons` tag application has the type: - I was expecting to see a tag name. + [ACons (Int Signed64) [BCons (Int Signed64) [ACons Str [BCons I64 [ACons I64 (BList I64 I64), + ANil] as ∞, BNil], ANil], BNil], ANil] - Hint: Tag names start with an uppercase letter, like Err or Green. + But the type annotation on `x` says it should be: + + [ACons I64 (BList I64 I64), ANil] as a + "### + ); + + test_report!( + integer_out_of_range, + indoc!( + r#" + x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000 + + y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000 + + h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + + minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 + maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 + + x + y + h + l + minlit + maxlit "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn tag_union_second_lowercase_tag_name() { - report_problem_as( - indoc!( - r#" - f : [Good, bad] - "# - ), - indoc!( - r#" - ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + This integer literal is too big: - I am partway through parsing a tag union type, but I got stuck here: + 4│ x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 1│ f : [Good, bad] - ^ + The largest number representable in Roc is the maximum U128 value, + 340_282_366_920_938_463_463_374_607_431_768_211_455. - I was expecting to see a tag name. + Tip: Learn more about number literals at TODO - Hint: Tag names start with an uppercase letter, like Err or Green. + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This integer literal is too small: + + 6│ y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The smallest number representable in Roc is the minimum I128 value, + -170_141_183_460_469_231_731_687_303_715_884_105_728. + + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This integer literal is too big: + + 8│ h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The largest number representable in Roc is the maximum U128 value, + 340_282_366_920_938_463_463_374_607_431_768_211_455. + + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This integer literal is too small: + + 9│ l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + The smallest number representable in Roc is the minimum I128 value, + -170_141_183_460_469_231_731_687_303_715_884_105_728. + + Tip: Learn more about number literals at TODO + "### + ); + + // have to deal with some whitespace issues because of the format! macro + test_report!( + float_out_of_range, + indoc!( + r#" + overflow = 11.7976931348623157e308 + underflow = -11.7976931348623157e308 + + overflow + underflow "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn record_type_open() { - report_problem_as( - indoc!( - r#" - f : { - "# - ), - indoc!( - r#" - ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ + This float literal is too big: - I just started parsing a record type, but I got stuck here: + 4│ overflow = 11.7976931348623157e308 + ^^^^^^^^^^^^^^^^^^^^^^^ - 1│ f : { - ^ + Roc uses signed 64-bit floating points, allowing values between + -1.7976931348623157e308 and 1.7976931348623157e308 - Record types look like { name : String, age : Int }, so I was - expecting to see a field name next. + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This float literal is too small: + + 5│ underflow = -11.7976931348623157e308 + ^^^^^^^^^^^^^^^^^^^^^^^^ + + Roc uses signed 64-bit floating points, allowing values between + -1.7976931348623157e308 and 1.7976931348623157e308 + + Tip: Learn more about number literals at TODO + "### + ); + + // the generated messages here are incorrect. Waiting for a rust nightly feature to land, + // see https://github.com/rust-lang/rust/issues/22639 + // this test is here to spot regressions in error reporting + test_report!( + integer_malformed, + indoc!( + r#" + dec = 100A + + hex = 0xZZZ + + oct = 0o9 + + bin = 0b2 + + dec + hex + oct + bin "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn record_type_open_indent() { - report_problem_as( - indoc!( - r#" - f : { - foo : I64, - "# - ), - indoc!( - r#" - ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ + This integer literal contains an invalid digit: - I am partway through parsing a record type, but I got stuck here: + 4│ dec = 100A + ^^^^ - 1│ f : { - ^ + Integer literals can only contain the digits + 0-9, or have an integer suffix. - I was expecting to see a closing curly brace before this, so try - adding a } and see if that helps? + Tip: Learn more about number literals at TODO - Note: I may be confused by indentation + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This hex integer literal contains an invalid digit: + + 6│ hex = 0xZZZ + ^^^^^ + + Hexadecimal (base-16) integer literals can only contain the digits + 0-9, a-f and A-F, or have an integer suffix. + + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This octal integer literal contains an invalid digit: + + 8│ oct = 0o9 + ^^^ + + Octal (base-8) integer literals can only contain the digits + 0-7, or have an integer suffix. + + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This binary integer literal contains an invalid digit: + + 10│ bin = 0b2 + ^^^ + + Binary (base-2) integer literals can only contain the digits + 0 and 1, or have an integer suffix. + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + integer_empty, + indoc!( + r#" + dec = 20 + + hex = 0x + + oct = 0o + + bin = 0b + + dec + hex + oct + bin "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn record_type_end() { - report_problem_as( - indoc!( - r#" - f : { a: Int, - "# - ), - indoc!( - r#" - ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ + This hex integer literal contains no digits: - I am partway through parsing a record type, but I got stuck here: + 6│ hex = 0x + ^^ - 1│ f : { a: Int, - ^ + Hexadecimal (base-16) integer literals must contain at least one of + the digits 0-9, a-f and A-F, or have an integer suffix. - I was expecting to see a closing curly brace before this, so try - adding a } and see if that helps? + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This octal integer literal contains no digits: + + 8│ oct = 0o + ^^ + + Octal (base-8) integer literals must contain at least one of the + digits 0-7, or have an integer suffix. + + Tip: Learn more about number literals at TODO + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This binary integer literal contains no digits: + + 10│ bin = 0b + ^^ + + Binary (base-2) integer literals must contain at least one of the + digits 0 and 1, or have an integer suffix. + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + float_malformed, + indoc!( + r#" + x = 3.0A + + x "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn record_type_keyword_field_name() { - report_problem_as( - indoc!( - r#" - f : { if : I64 } - "# - ), - indoc!( - r#" - ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ + This float literal contains an invalid digit: - I just started parsing a record type, but I got stuck on this field - name: + 4│ x = 3.0A + ^^^^ - 1│ f : { if : I64 } - ^^ + Floating point literals can only contain the digits 0-9, or use + scientific notation 10e4, or have a float suffix. - Looks like you are trying to use `if` as a field name, but that is a - reserved word. Try using a different name! + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + invalid_record_update, + indoc!( + r#" + foo = { bar: 3 } + updateNestedRecord = { foo.bar & x: 4 } + + example = { age: 42 } + + # these should work + y = { Test.example & age: 3 } + x = { example & age: 4 } + + { updateNestedRecord, foo, x, y } "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[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 ──────────────────────────────── /code/proj/Main.roc ─ + This expression cannot be updated: - I am partway through parsing a record type, but I got stuck here: + 5│ updateNestedRecord = { foo.bar & x: 4 } + ^^^^^^^ - 1│ f : { foo bar } - ^ + Only variables can be updated with record update syntax. - I was expecting to see a colon, question mark, comma or closing curly - brace. + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ + + The `Test` module is not imported: + + 10│ y = { Test.example & age: 3 } + ^^^^^^^^^^^^ + + Is there an import missing? Perhaps there is a typo. Did you mean one + of these? + + List + Set + Dict + Result + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This expression cannot be updated: + + 10│ y = { Test.example & age: 3 } + ^^^^^^^^^^^^ + + Only variables can be updated with record update syntax. + "### + ); + + test_report!( + module_not_imported, + indoc!( + r#" + Foo.test "# - ), - ) - } + ), + @r###" + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ - #[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 ───────────────────────────────────────── /code/proj/Main.roc ─ + The `Foo` module is not imported: - I encountered a tab character + 4│ Foo.test + ^^^^^^^^ - 1│ f : { foo } - ^ + Is there an import missing? Perhaps there is a typo. Did you mean one + of these? - Tab characters are not allowed. + Box + Bool + Num + Set + "### + ); + + test_report!( + optional_record_default_type_error, + indoc!( + r#" + \{ x, y ? True } -> x + y "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn comment_with_tab() { - report_problem_as( - "# comment with a \t\n4", - indoc!( - " - ── TAB CHARACTER ───────────────────────────────────────── /code/proj/Main.roc ─ + The 2nd argument to `add` is not what I expect: - 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 ─────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ─────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ────────────────────────────── /code/proj/Main.roc ─ - - 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 ─────────────────────────────── /code/proj/Main.roc ─ - - 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 ────────────────────────────────────────── /code/proj/Main.roc ─ - - I just started parsing a function argument type, but I encountered two - commas in a row: - - 1│ f : I64,,I64 -> I64 - ^ - - Try removing one of them. - "# - ), - ) - } - - #[test] - fn type_argument_no_arrow() { - report_problem_as( - indoc!( - r#" - f : I64, I64 - f = 0 - - f - "# - ), - indoc!( - r#" - ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ─────────────────────────────────────── /code/proj/Main.roc ─ - - I just started parsing a type, but I got stuck here: - - 1│ f : I64, I64 -> - ^ - - Note: I may be confused by indentation - "# - ), - ) - } - - #[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 Num.I64 Str - myDict = Dict.insert Dict.empty "foo" 42 - - myDict - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `myDict` definition: - - 1│ myDict : Dict Num.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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ────────────────────────────── /code/proj/Main.roc ─ - - 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 ────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - 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 ───────────────────────────────────────── /code/proj/Main.roc ─ - - I was partway through parsing an `if` expression, but I got stuck here: - - 1│ if 5 == 5 then 2 + 4│ \{ x, y ? True } -> x + y ^ - I was expecting to see the `else` keyword next. - "# - ), - ) - } + This `y` value is a: - #[test] - fn list_double_comma() { - report_problem_as( - indoc!( - r#" - [1, 2, , 3] + [True]a + + But `add` needs the 2nd argument to be: + + Num a + "### + ); + + test_report!( + optional_record_default_with_signature, + indoc!( + r#" + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 + f = \{ x, y ? "foo" } -> (\g, _ -> g) x y + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `f` is weird: + + 5│ f = \{ x, y ? "foo" } -> (\g, _ -> g) x y + ^^^^^^^^^^^^^^^^ + + The argument is a pattern that matches record values of type: + + { x : I64, y ? Str } + + But the annotation on `f` says the 1st argument should be: + + { x : I64, y ? I64 } + "### + ); + + test_report!( + optional_record_invalid_let_binding, + indoc!( + r#" + \rec -> + { x, y } : { x : Num.I64, y ? Str } + { x, y } = rec + + { x, y } + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of this definition: + + 5│> { x, y } : { x : Num.I64, y ? Str } + 6│> { x, y } = rec + + The body is a value of type: + + { x : I64, y : Str } + + But the type annotation says it should be: + + { x : I64, y ? Str } + + Tip: To extract the `.y` field it must be non-optional, but the type + says this field is optional. Learn more about optional fields at TODO. + "### + ); + + test_report!( + optional_record_invalid_function, + indoc!( + r#" + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 + f = \{ x, y } -> x + y + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `f` is weird: + + 5│ f = \{ x, y } -> x + y + ^^^^^^^^ + + The argument is a pattern that matches record values of type: + + { x : I64, y : I64 } + + But the annotation on `f` says the 1st argument should be: + + { x : I64, y ? I64 } + + Tip: To extract the `.y` field it must be non-optional, but the type + says this field is optional. Learn more about optional fields at TODO. + "### + ); + + test_report!( + optional_record_invalid_when, + indoc!( + r#" + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 + f = \r -> + when r is + { x, y } -> x + y + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The branches of this `when` expression don't match the condition: + + 6│> when r is + 7│ { x, y } -> x + y + + This `r` value is a: + + { x : I64, y ? I64 } + + But the branch patterns have type: + + { x : I64, y : I64 } + + The branches must be cases of the `when` condition's type! + + Tip: To extract the `.y` field it must be non-optional, but the type + says this field is optional. Learn more about optional fields at TODO. + "### + ); + + test_report!( + optional_record_invalid_access, + indoc!( + r#" + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 + f = \r -> r.y + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression is used in an unexpected way: + + 5│ f = \r -> r.y + ^^^ + + This `r` value is a: + + { x : I64, y ? I64 } + + But you are trying to use it as: + + { x : I64, y : I64 } + + Tip: To extract the `.y` field it must be non-optional, but the type + says this field is optional. Learn more about optional fields at TODO. + "### + ); + + test_report!( + optional_record_invalid_accessor, + indoc!( + r#" + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 + f = \r -> .y r + + f "# - ), - indoc!( - r#" - ── UNFINISHED LIST ─────────────────────────────────────── /code/proj/Main.roc ─ + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - I am partway through started parsing a list, but I got stuck here: + The 1st argument to this function is not what I expect: - 1│ [1, 2, , 3] - ^ + 5│ f = \r -> .y r + ^ - I was expecting to see a list entry before this comma, so try adding a - list entry and see if that helps? + 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_report!( + guard_mismatch_with_annotation, + indoc!( + r#" + f : { x : Num.I64, y : Num.I64 } -> Num.I64 + f = \r -> + when r is + { x, y : "foo" } -> x + 0 + _ -> 0 + + f "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn list_without_end() { - report_problem_as( - indoc!( - r#" - [1, 2, - "# - ), - indoc!( - r#" - ── UNFINISHED LIST ─────────────────────────────────────── /code/proj/Main.roc ─ + The branches of this `when` expression don't match the condition: - I am partway through started parsing a list, but I got stuck here: + 6│> when r is + 7│ { x, y : "foo" } -> x + 0 + 8│ _ -> 0 - 1│ [1, 2, - ^ + This `r` value is a: - I was expecting to see a closing square bracket before this, so try - adding a ] and see if that helps? + { x : I64, y : I64 } - 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. + But the branch patterns have type: + + { x : I64, y : Str } + + The branches must be cases of the `when` condition's type! + "### + ); + + test_report!( + optional_field_mismatch_with_annotation, + indoc!( + r#" + f : { x : Num.I64, y ? Num.I64 } -> Num.I64 + f = \r -> + when r is + { x, y ? "foo" } -> (\g, _ -> g) x y + _ -> 0 + + f "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn number_double_dot() { - report_problem_as( - indoc!( - r#" - 1.1.1 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + The branches of this `when` expression don't match the condition: - This float literal contains an invalid digit: + 6│> when r is + 7│ { x, y ? "foo" } -> (\g, _ -> g) x y + 8│ _ -> 0 - 1│ 1.1.1 - ^^^^^ + This `r` value is a: - Floating point literals can only contain the digits 0-9, or use - scientific notation 10e4, or have a float suffix. + { x : I64, y ? I64 } - Tip: Learn more about number literals at TODO + But the branch patterns have type: + + { x : I64, y ? Str } + + The branches must be cases of the `when` condition's type! + "### + ); + + test_report!( + incorrect_optional_field, + indoc!( + r#" + { x: 5, y ? 42 } "# - ), - ) - } + ), + @r###" + ── BAD OPTIONAL VALUE ──────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn unicode_not_hex() { - report_problem_as( - r#""abc\u(zzzz)def""#, - indoc!( - r#" - ── WEIRD CODE POINT ────────────────────────────────────── /code/proj/Main.roc ─ + This record uses an optional value for the `.y` field in an incorrect + context! - I am partway through parsing a unicode code point, but I got stuck - here: + 4│ { x: 5, y ? 42 } + ^^^^^^ - 1│ "abc\u(zzzz)def" - ^ + You can only use optional values in record destructuring, like: - I was expecting a hexadecimal number, like \u(1100) or \u(00FF). + { answer ? 42, otherField } = myRecord + "### + ); - Learn more about working with unicode in roc at TODO + test_report!( + first_wildcard_is_required, + indoc!( + r#" + when Foo 1 2 3 is + Foo _ 1 _ -> 1 + _ -> 2 "# - ), - ) - } + ), + @"" + ); - #[test] - fn interpolate_not_identifier() { - report_problem_as( - r#""abc\(32)def""#, - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - 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_report!( + second_wildcard_is_redundant, + indoc!( + r#" + when Foo 1 2 3 is + Foo _ 1 _ -> 1 + _ -> 2 + _ -> 3 "# - ), - ) - } + ), + @r###" + ── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn unicode_too_large() { - report_problem_as( - r#""abc\u(110000)def""#, - indoc!( - r#" - ── INVALID UNICODE ─────────────────────────────────────── /code/proj/Main.roc ─ + The 3rd pattern is redundant: - This unicode code point is invalid: + 4│ when Foo 1 2 3 is + 5│ Foo _ 1 _ -> 1 + 6│ _ -> 2 + 7│ _ -> 3 + ^ - 1│ "abc\u(110000)def" - ^^^^^^ + Any value of this shape will be handled by a previous pattern, so this + one should be removed. + "### + ); - Learn more about working with unicode in roc at TODO + test_report!( + alias_using_alias, + indoc!( + r#" + # The color of a node. Leaves are considered Black. + NodeColor : [Red, Black] + + RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + # Create an empty dictionary. + empty : RBTree k v + empty = + Empty + + empty "# - ), - ) - } + ), + @"" + ); - #[test] - fn weird_escape() { - report_problem_as( - r#""abc\qdef""#, - indoc!( - r#" - ── WEIRD ESCAPE ────────────────────────────────────────── /code/proj/Main.roc ─ + test_report!( + unused_argument, + indoc!( + r#" + f = \foo -> 1 - 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) + f "# - ), - ) - } + ), + @r###" + ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn single_no_end() { - report_problem_as( - r#""there is no end"#, - indoc!( - r#" - ── ENDLESS STRING ──────────────────────────────────────── /code/proj/Main.roc ─ + `f` doesn't use `foo`. - I cannot find the end of this string: + 4│ f = \foo -> 1 + ^^^ - 1│ "there is no end + If you don't need `foo`, then you can just remove it. However, if you + really do need `foo` as an argument of `f`, prefix it with an underscore, + like this: "_`foo`". Adding an underscore at the start of a variable + name is a way of saying that the variable is not used. + "### + ); + + test_report!( + qualified_tag, + indoc!( + r#" + Foo.Bar + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + I am trying to parse a qualified name here: + + 4│ Foo.Bar + ^ + + This looks like a qualified tag name to me, but tags cannot be + qualified! Maybe you wanted a qualified name, something like + Json.Decode.string? + "### + ); + + test_report!( + module_ident_ends_with_dot, + indoc!( + r#" + Foo.Bar. + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + I am trying to parse a qualified name here: + + 4│ Foo.Bar. + ^ + + I was expecting to see an identifier next, like height. A complete + qualified name looks something like Json.Decode.string. + "### + ); + + test_report!( + record_access_ends_with_dot, + indoc!( + r#" + foo.bar. + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + I trying to parse a record field access here: + + 4│ foo.bar. + ^ + + So I expect to see a lowercase letter next, like .name or .height. + "### + ); + + test_report!( + type_annotation_double_colon, + indoc!( + r#" + f :: I64 + f = 42 + + f + "# + ), + @r###" + ── UNKNOWN OPERATOR ──────────────── tmp/type_annotation_double_colon/Test.roc ─ + + This looks like an operator, but it's not one I recognize! + + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ f :: I64 + ^^ + + I have no specific suggestion for this operator, see TODO for the full + list of operators in Roc. + "### + ); + + // NOTE: VERY BAD ERROR MESSAGE + // + // looks like `x y` are considered argument to the add, even though they are + // on a lower indentation level + test_report!( + double_equals_in_def, + indoc!( + r#" + x = 3 + y = + x == 5 + Num.add 1 2 + + { x, y } + "# + ), + @r###" + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ + + This value is not a function, but it was given 3 arguments: + + 6│ x == 5 ^ - You could change it to something like "to be or not to be" or even - just "". + Are there any missing commas? Or missing parentheses? + "### + ); + + test_report!( + tag_union_open, + indoc!( + r#" + f : [ "# - ), - ) - } + ), + @r###" + ── UNFINISHED TAG UNION TYPE ───────────────────── tmp/tag_union_open/Test.roc ─ - #[test] - fn multi_no_end() { - report_problem_as( - r#""""there is no end"#, - indoc!( - r#" - ── ENDLESS STRING ──────────────────────────────────────── /code/proj/Main.roc ─ + I am partway through parsing a tag union type, but I got stuck here: - I cannot find the end of this block string: + 4│ f : [ + ^ - 1│ """there is no end + I was expecting to see a closing square bracket before this, so try + adding a ] and see if that helps? + + Note: I may be confused by indentation + "### + ); + + test_report!( + tag_union_end, + indoc!( + r#" + f : [Yes, + "# + ), + @r###" + ── UNFINISHED TAG UNION TYPE ────────────────────── tmp/tag_union_end/Test.roc ─ + + I am partway through parsing a tag union type, but I got stuck here: + + 4│ f : [Yes, + 5│ + 6│ + ^ + + I was expecting to see a closing square bracket before this, so try + adding a ] and see if that helps? + "### + ); + + test_report!( + tag_union_lowercase_tag_name, + indoc!( + r#" + f : [lowercase] + "# + ), + @r###" + ── WEIRD TAG NAME ────────────────── tmp/tag_union_lowercase_tag_name/Test.roc ─ + + I am partway through parsing a tag union type, but I got stuck here: + + 4│ f : [lowercase] + ^ + + I was expecting to see a tag name. + + Hint: Tag names start with an uppercase letter, like Err or Green. + "### + ); + + test_report!( + tag_union_second_lowercase_tag_name, + indoc!( + r#" + f : [Good, bad] + "# + ), + @r###" + ── WEIRD TAG NAME ─────────── tmp/tag_union_second_lowercase_tag_name/Test.roc ─ + + I am partway through parsing a tag union type, but I got stuck here: + + 4│ f : [Good, bad] ^ - You could change it to something like """to be or not to be""" or even - just """""". + I was expecting to see a tag name. + + Hint: Tag names start with an uppercase letter, like Err or Green. + "### + ); + + test_report!( + record_type_open, + indoc!( + r#" + f : { "# - ), - ) - } + ), + @r###" + ── UNFINISHED RECORD TYPE ────────────────────── tmp/record_type_open/Test.roc ─ + + I am partway through parsing a record type, but I got stuck here: + + 4│ f : { + ^ + + I was expecting to see a closing curly brace before this, so try + adding a } and see if that helps? + + Note: I may be confused by indentation + "### + ); + + test_report!( + record_type_open_indent, + indoc!( + r#" + f : { + foo : I64, + "# + ), + @r###" + ── UNFINISHED RECORD TYPE ─────────────── tmp/record_type_open_indent/Test.roc ─ + + I am partway through parsing a record type, but I got stuck here: + + 4│ f : { + ^ + + I was expecting to see a closing curly brace before this, so try + adding a } and see if that helps? + + Note: I may be confused by indentation + "### + ); + + test_report!( + record_type_end, + indoc!( + r#" + f : { a: Int, + "# + ), + @r###" + ── UNFINISHED RECORD TYPE ─────────────────────── tmp/record_type_end/Test.roc ─ + + I am partway through parsing a record type, but I got stuck here: + + 4│ f : { a: Int, + 5│ + 6│ + ^ + + I was expecting to see a closing curly brace before this, so try + adding a } and see if that helps? + "### + ); + + test_report!( + record_type_keyword_field_name, + indoc!( + r#" + f : { if : I64 } + "# + ), + @r###" + ── UNFINISHED RECORD TYPE ──────── tmp/record_type_keyword_field_name/Test.roc ─ + + I just started parsing a record type, but I got stuck on this field + name: + + 4│ f : { if : I64 } + ^^ + + Looks like you are trying to use `if` as a field name, but that is a + reserved word. Try using a different name! + "### + ); + + // a case where the message cannot be as good as elm's + test_report!( + record_type_missing_comma, + indoc!( + r#" + f : { foo bar } + "# + ), + @r###" + ── UNFINISHED RECORD TYPE ───────────── tmp/record_type_missing_comma/Test.roc ─ + + I am partway through parsing a record type, but I got stuck here: + + 4│ f : { foo bar } + ^ + + I was expecting to see a colon, question mark, comma or closing curly + brace. + "### + ); + + // a case where the message cannot be as good as elm's + test_report!( + record_type_tab, + "f : { foo \t }", + @r###" + ── TAB CHARACTER ──────────────────────────────── tmp/record_type_tab/Test.roc ─ + + I encountered a tab character + + 4│ f : { foo } + ^ + + Tab characters are not allowed. + "### + ); + + test_report!( + comment_with_tab, + "# comment with a \t\n4", + @r###" + ── TAB CHARACTER ─────────────────────────────── tmp/comment_with_tab/Test.roc ─ + + I encountered a tab character + + 4│ # comment with a + ^ + + Tab characters are not allowed. + "### + ); + + // TODO bad error message + test_report!( + type_in_parens_start, + indoc!( + r#" + f : ( + "# + ), + @r###" + ── UNFINISHED PARENTHESES ────────────────── tmp/type_in_parens_start/Test.roc ─ + + I just started parsing a type in parentheses, but I got stuck here: + + 4│ f : ( + ^ + + Tag unions look like [Many I64, None], so I was expecting to see a tag + name next. + + Note: I may be confused by indentation + "### + ); + + test_report!( + type_in_parens_end, + indoc!( + r#" + f : ( I64 + "# + ), + @r###" + ── UNFINISHED PARENTHESES ──────────────────── tmp/type_in_parens_end/Test.roc ─ + + I am partway through parsing a type in parentheses, but I got stuck + here: + + 4│ f : ( I64 + ^ + + I was expecting to see a parenthesis before this, so try adding a ) + and see if that helps? + + Note: I may be confused by indentation + "### + ); + + test_report!( + type_apply_double_dot, + indoc!( + r#" + f : Foo..Bar + + f + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + I am confused by this type name: + + 4│ f : Foo..Bar + ^^^^^^^^ + + Type names start with an uppercase letter, and can optionally be + qualified by a module name, like Bool or Http.Request.Request. + "### + ); + // ── DOUBLE DOT ────────────────────────────────────────────────────────────────── + // + // I encountered two dots in a row: + // + // 1│ f : Foo..Bar + // ^ + // + // Try removing one of them. + + test_report!( + type_apply_trailing_dot, + indoc!( + r#" + f : Foo.Bar. + + f + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + I am confused by this type name: + + 4│ f : Foo.Bar. + ^^^^^^^^ + + Type names start with an uppercase letter, and can optionally be + qualified by a module name, like Bool or Http.Request.Request. + "### + ); + // ── TRAILING DOT ──────────────────────────────────────────────────────────────── + // + // I encountered a dot with nothing after it: + // + // 1│ f : Foo.Bar. + // ^ + // + // Dots are used to refer to a type in a qualified way, like + // Num.I64 or List.List a. Try adding a type name next. + + test_report!( + type_apply_stray_dot, + indoc!( + r#" + f : . + "# + ), + @r###" + ── UNFINISHED TYPE ───────────────────────── tmp/type_apply_stray_dot/Test.roc ─ + + I just started parsing a type, but I got stuck here: + + 4│ f : . + ^ + + I am expecting a type next, like Bool or List a. + "### + ); + + test_report!( + type_apply_start_with_number, + indoc!( + r#" + f : Foo.1 + + f + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + I am confused by this type name: + + 4│ f : Foo.1 + ^^^^^ + + Type names start with an uppercase letter, and can optionally be + qualified by a module name, like Bool or Http.Request.Request. + "### + ); + // ── WEIRD QUALIFIED NAME ──────────────────────────────────────────────────────── + // + // I encountered a number at the start of a qualified name segment: + // + // 1│ f : Foo.1 + // ^ + // + // All parts of a qualified type name must start with an uppercase + // letter, like Num.I64 or List.List a. + + test_report!( + type_apply_start_with_lowercase, + indoc!( + r#" + f : Foo.foo + + f + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + I am confused by this type name: + + 4│ f : Foo.foo + ^^^^^^^ + + Type names start with an uppercase letter, and can optionally be + qualified by a module name, like Bool or Http.Request.Request. + "### + ); + + test_report!( + def_missing_final_expression, + indoc!( + r#" + f : Foo.foo + "# + ), + @r###" + ── MISSING FINAL EXPRESSION ──────── tmp/def_missing_final_expression/Test.roc ─ + + I am partway through parsing a definition, but I got stuck here: + + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ f : Foo.foo + ^ + + This definition is missing a final expression. A nested definition + must be followed by either another definition, or an expression + + x = 4 + y = 2 + + x + y + "### + ); + + test_report!( + type_inline_alias, + indoc!( + r#" + f : I64 as + f = 0 + + f + "# + ), + @r###" + ── UNFINISHED INLINE ALIAS ──────────────────── tmp/type_inline_alias/Test.roc ─ + + I just started parsing an inline type alias, but I got stuck here: + + 4│ f : I64 as + ^ + + Note: I may be confused by indentation + "### + ); + + test_report!( + type_double_comma, + indoc!( + r#" + f : I64,,I64 -> I64 + f = 0 + + f + "# + ), + @r###" + ── DOUBLE COMMA ─────────────────────────────── tmp/type_double_comma/Test.roc ─ + + I just started parsing a function argument type, but I encountered two + commas in a row: + + 4│ f : I64,,I64 -> I64 + ^ + + Try removing one of them. + "### + ); + + test_report!( + type_argument_no_arrow, + indoc!( + r#" + f : I64, I64 + f = 0 + + f + "# + ), + @r###" + ── UNFINISHED TYPE ─────────────────────── tmp/type_argument_no_arrow/Test.roc ─ + + I am partway through parsing a type, but I got stuck here: + + 4│ f : I64, I64 + ^ + + Note: I may be confused by indentation + "### + ); + + // TODO could do better by pointing out we're parsing a function type + test_report!( + type_argument_arrow_then_nothing, + indoc!( + r#" + f : I64, I64 -> + f = 0 + + f + "# + ), + @r###" + ── UNFINISHED TYPE ───────────── tmp/type_argument_arrow_then_nothing/Test.roc ─ + + I just started parsing a type, but I got stuck here: + + 4│ f : I64, I64 -> + ^ + + Note: I may be confused by indentation + "### + ); + + // TODO could do better by pointing out we're parsing a function type + test_report!( + dict_type_formatting, + indoc!( + r#" + myDict : Dict Num.I64 Str + myDict = Dict.insert Dict.empty "foo" 42 + + myDict + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `myDict` definition: + + 4│ myDict : Dict Num.I64 Str + 5│ myDict = Dict.insert Dict.empty "foo" 42 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + This `insert` call produces: + + Dict Str (Num a) + + But the type annotation on `myDict` says it should be: + + Dict I64 Str + "### + ); + + test_report!( + alias_type_diff, + indoc!( + r#" + HSet a : Set a + + foo : Str -> HSet {} + + myDict : HSet Str + myDict = foo "bar" + + myDict + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `myDict` definition: + + 8│ myDict : HSet Str + 9│ myDict = foo "bar" + ^^^^^^^^^ + + This `foo` call produces: + + HSet {} + + But the type annotation on `myDict` says it should be: + + HSet Str + "### + ); + + // this should get better with time + test_report!( + if_guard_without_condition, + indoc!( + r#" + when Just 4 is + Just if -> + 4 + + _ -> + 2 + "# + ), + @r###" + ── IF GUARD NO CONDITION ───────────── tmp/if_guard_without_condition/Test.roc ─ + + I just started parsing an if guard, but there is no guard condition: + + 4│ when Just 4 is + 5│ Just if -> + ^ + + Try adding an expression before the arrow! + "### + ); + + test_report!( + empty_or_pattern, + indoc!( + r#" + when Just 4 is + Just 4 | -> + 4 + + _ -> + 2 + "# + ), + @r###" + ── UNFINISHED PATTERN ────────────────────────── tmp/empty_or_pattern/Test.roc ─ + + I just started parsing a pattern, but I got stuck here: + + 5│ Just 4 | -> + ^ + + Note: I may be confused by indentation + "### + ); + + // TODO check if "what_is_next" is a keyword + test_report!( + pattern_binds_keyword, + indoc!( + r#" + when Just 4 is + Just when -> + 4 + + _ -> + 2 + "# + ), + @r###" + ── MISSING EXPRESSION ───────────────────── tmp/pattern_binds_keyword/Test.roc ─ + + I am partway through parsing a definition, but I got stuck here: + + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ when Just 4 is + 5│ Just when -> + ^ + + I was expecting to see an expression like 42 or "hello". + "### + ); + + // this should get better with time + test_report!( + when_missing_arrow, + indoc!( + r#" + when 5 is + 1 -> 2 + _ + "# + ), + @r###" + ── UNFINISHED WHEN ─────────────────────────── tmp/when_missing_arrow/Test.roc ─ + + I was partway through parsing a `when` expression, but I got stuck here: + + 6│ _ + ^ + + I was expecting to see a pattern next + + Note: Here is an example of a valid `when` expression for reference. + + when List.first plants is + Ok n -> + n + + Err _ -> + 200 + + Notice the indentation. All patterns are aligned, and each branch is + indented a bit more than the corresponding pattern. That is important! + "### + ); + + test_report!( + lambda_double_comma, + indoc!( + r#" + \a,,b -> 1 + "# + ), + @r###" + ── UNFINISHED ARGUMENT LIST ───────────────── tmp/lambda_double_comma/Test.roc ─ + + I am partway through parsing a function argument list, but I got stuck + at this comma: + + 4│ \a,,b -> 1 + ^ + + I was expecting an argument pattern before this, so try adding an + argument before the comma and see if that helps? + "### + ); + + test_report!( + lambda_leading_comma, + indoc!( + r#" + \,b -> 1 + "# + ), + @r###" + ── UNFINISHED ARGUMENT LIST ──────────────── tmp/lambda_leading_comma/Test.roc ─ + + I am partway through parsing a function argument list, but I got stuck + at this comma: + + 4│ \,b -> 1 + ^ + + I was expecting an argument pattern before this, so try adding an + argument before the comma and see if that helps? + "### + ); + + // this should get better with time + // TODO this formerly gave + // + // ── UNFINISHED WHEN ───────────────────────────────────────────────────────────── + // + // I was partway through parsing a `when` expression, but I got stuck here: + // + // 3│ _ -> 2 + // ^ + // + // I suspect this is a pattern that is not indented enough? (by 2 spaces) + // + // but that requires parsing the next pattern blindly, irrespective of indentation. Can + // we find an efficient solution that doesn't require parsing an extra pattern for + // every `when`, i.e. we want a good error message for the test case above, but for + // a valid `when`, we don't want to do extra work, e.g. here + // + // x + // when x is + // n -> n + // + // 4 + // + // We don't want to parse the `4` and say it's an outdented pattern! + test_report!( + when_outdented_branch, + indoc!( + r#" + when 4 is + 5 -> 2 + 2 -> 2 + "# + ), + @r###" + ── NOT END OF FILE ──────────────────────── tmp/when_outdented_branch/Test.roc ─ + + I expected to reach the end of the file, but got stuck here: + + 6│ 2 -> 2 + ^ + "### + ); + + test_report!( + when_over_indented_underscore, + indoc!( + r#" + when 4 is + 5 -> 2 + _ -> 2 + "# + ), + @r###" + ── NOT END OF FILE ──────────────── tmp/when_over_indented_underscore/Test.roc ─ + + I expected to reach the end of the file, but got stuck here: + + 6│ _ -> 2 + ^ + "### + ); + + test_report!( + when_over_indented_int, + indoc!( + r#" + when 4 is + 5 -> Num.neg + 2 -> 2 + "# + ), + @r###" + ── UNEXPECTED ARROW ────────────────────── tmp/when_over_indented_int/Test.roc ─ + + I am parsing a `when` expression right now, but this arrow is confusing + me: + + 6│ 2 -> 2 + ^^ + + It makes sense to see arrows around here, so I suspect it is something + earlier.Maybe this pattern is indented a bit farther from the previous + patterns? + + Note: Here is an example of a valid `when` expression for reference. + + when List.first plants is + Ok n -> + n + + Err _ -> + 200 + + Notice the indentation. All patterns are aligned, and each branch is + indented a bit more than the corresponding pattern. That is important! + "### + ); + + // TODO I think we can do better here + test_report!( + if_outdented_then, + indoc!( + r#" + x = + if 5 == 5 + then 2 else 3 + + x + "# + ), + @r###" + ── UNFINISHED IF ────────────────────────────── tmp/if_outdented_then/Test.roc ─ + + I was partway through parsing an `if` expression, but I got stuck here: + + 5│ if 5 == 5 + ^ + + I was expecting to see the `then` keyword next. + "### + ); + + // this should get better with time + test_report!( + if_missing_else, + indoc!( + r#" + if 5 == 5 then 2 + "# + ), + @r###" + ── UNFINISHED IF ──────────────────────────────── tmp/if_missing_else/Test.roc ─ + + I was partway through parsing an `if` expression, but I got stuck here: + + 4│ if 5 == 5 then 2 + ^ + + I was expecting to see the `else` keyword next. + "### + ); + + test_report!( + list_double_comma, + indoc!( + r#" + [1, 2, , 3] + "# + ), + @r###" + ── UNFINISHED LIST ──────────────────────────── tmp/list_double_comma/Test.roc ─ + + I am partway through started parsing a list, but I got stuck here: + + 4│ [1, 2, , 3] + ^ + + I was expecting to see a list entry before this comma, so try adding a + list entry and see if that helps? + "### + ); + + test_report!( + list_without_end, + indoc!( + r#" + [1, 2, + "# + ), + @r###" + ── UNFINISHED LIST ───────────────────────────── tmp/list_without_end/Test.roc ─ + + I am partway through started parsing a list, but I got stuck here: + + 4│ [1, 2, + 5│ + 6│ + ^ + + I was expecting to see a closing square bracket before this, so try + adding a ] and see if that helps? + + Note: When I get stuck like this, it usually means that there is a + missing parenthesis or bracket somewhere earlier. It could also be a + stray keyword or operator. + "### + ); + + test_report!( + number_double_dot, + indoc!( + r#" + 1.1.1 + "# + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This float literal contains an invalid digit: + + 4│ 1.1.1 + ^^^^^ + + Floating point literals can only contain the digits 0-9, or use + scientific notation 10e4, or have a float suffix. + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + unicode_not_hex, + r#""abc\u(zzzz)def""#, + @r###" + ── WEIRD CODE POINT ───────────────────────────── tmp/unicode_not_hex/Test.roc ─ + + I am partway through parsing a unicode code point, but I got stuck + here: + + 4│ "abc\u(zzzz)def" + ^ + + I was expecting a hexadecimal number, like \u(1100) or \u(00FF). + + Learn more about working with unicode in roc at TODO + "### + ); + + test_report!( + interpolate_not_identifier, + r#""abc\(32)def""#, + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This string interpolation is invalid: + + 4│ "abc\(32)def" + ^^ + + I was expecting an identifier, like \u(message) or + \u(LoremIpsum.text). + + Learn more about string interpolation at TODO + "### + ); + + test_report!( + unicode_too_large, + r#""abc\u(110000)def""#, + @r###" + ── INVALID UNICODE ─────────────────────────────────────── /code/proj/Main.roc ─ + + This unicode code point is invalid: + + 4│ "abc\u(110000)def" + ^^^^^^ + + Learn more about working with unicode in roc at TODO + "### + ); + + test_report!( + weird_escape, + r#""abc\qdef""#, + @r###" + ── WEIRD ESCAPE ──────────────────────────────────── tmp/weird_escape/Test.roc ─ + + I was partway through parsing a string literal, but I got stuck here: + + 4│ "abc\qdef" + ^^ + + This is not an escape sequence I recognize. After a backslash, I am + looking for one of these: + + - A newline: \n + - A caret return: \r + - A tab: \t + - An escaped quote: \" + - An escaped backslash: \\ + - A unicode code point: \u(00FF) + - An interpolated string: \(myVariable) + "### + ); + + test_report!( + single_no_end, + r#""there is no end"#, + @r###" + ── ENDLESS STRING ───────────────────────────────── tmp/single_no_end/Test.roc ─ + + I cannot find the end of this string: + + 4│ "there is no end + ^ + + You could change it to something like "to be or not to be" or even + just "". + "### + ); + + test_report!( + multi_no_end, + r#""""there is no end"#, + @r###" + ── ENDLESS STRING ────────────────────────────────── tmp/multi_no_end/Test.roc ─ + + I cannot find the end of this block string: + + 4│ """there is no end + ^ + + You could change it to something like """to be or not to be""" or even + just """""". + "### + ); - #[test] // https://github.com/rtfeldman/roc/issues/1714 - fn interpolate_concat_is_transparent_1714() { - report_problem_as( + test_report!( + interpolate_concat_is_transparent_1714, indoc!( r#" - greeting = "Privet" + greeting = "Privet" - if True then 1 else "\(greeting), World!" - "#, + if True then 1 else "\(greeting), World!" + "#, ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This `if` has an `else` branch with a different type from its `then` branch: + This `if` has an `else` branch with a different type from its `then` branch: - 3│ if True then 1 else "\(greeting), World!" - ^^^^^^^^^^^^^^^^^^^^^ + 6│ if True then 1 else "\(greeting), World!" + ^^^^^^^^^^^^^^^^^^^^^ - The `else` branch is a string of type: + The `else` branch is a string of type: - Str + Str - but the `then` branch has the type: + but the `then` branch has the type: - Num a + Num a - All branches in an `if` must have the same type! - "# - ), - ) - } + All branches in an `if` must have the same type! + "### + ); macro_rules! comparison_binop_transparency_tests { ($($op:expr, $name:ident),* $(,)?) => { $( - #[test] - fn $name() { - report_problem_as( - &format!(r#"if True then "abc" else 1 {} 2"#, $op), - &format!( + test_report!( + $name, + &format!(r#"if True then "abc" else 1 {} 2"#, $op), + |golden| assert_eq!(golden, format!( r#"── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: -1│ if True then "abc" else 1 {} 2 - ^^{}^^ +4│ if True then "abc" else 1 {} 2 + ^^{}^^ This comparison produces: @@ -6035,10 +5251,9 @@ but the `then` branch has the type: All branches in an `if` must have the same type! "#, - $op, "^".repeat($op.len()) - ), - ) - } + $op, "^".repeat($op.len()) + )) + ); )* } } @@ -6052,349 +5267,321 @@ All branches in an `if` must have the same type! ">=", geq_binop_is_transparent, } - #[test] - fn keyword_record_field_access() { - report_problem_as( - indoc!( - r#" - foo = {} + test_report!( + keyword_record_field_access, + indoc!( + r#" + foo = {} - foo.if - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This `foo` record doesn’t have a `if` field: - - 3│ foo.if - ^^^^^^ - - In fact, `foo` is a record with no fields at all! + foo.if "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn keyword_qualified_import() { - report_problem_as( - indoc!( - r#" - Num.if - "# - ), - indoc!( - r#" - ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ + This `foo` record doesn’t have a `if` field: - The Num module does not expose `if`: + 6│ foo.if + ^^^^^^ - 1│ Num.if - ^^^^^^ + In fact, `foo` is a record with no fields at all! + "### + ); - Did you mean one of these? - - Num.sin - Num.div - Num.abs - Num.neg + test_report!( + keyword_qualified_import, + indoc!( + r#" + Num.if "# - ), - ) - } + ), + @r###" + ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn stray_dot_expr() { - report_problem_as( - indoc!( - r#" - Num.add . 23 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + The Num module does not expose `if`: - I trying to parse a record field access here: + 4│ Num.if + ^^^^^^ - 1│ Num.add . 23 - ^ + Did you mean one of these? - So I expect to see a lowercase letter next, like .name or .height. + Num.sin + Num.div + Num.abs + Num.neg + "### + ); + + test_report!( + stray_dot_expr, + indoc!( + r#" + Num.add . 23 "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn opaque_ref_field_access() { - report_problem_as( - indoc!( - r#" - @UUID.bar - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + I trying to parse a record field access here: - I am very confused by this field access: + 4│ Num.add . 23 + ^ - 1│ @UUID.bar - ^^^^ + So I expect to see a lowercase letter next, like .name or .height. + "### + ); - It looks like a record field access on an opaque reference. + test_report!( + opaque_ref_field_access, + indoc!( + r#" + @UUID.bar "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn weird_accessor() { - report_problem_as( - indoc!( - r#" - .foo.bar - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + I am very confused by this field access: - I am very confused by this field access + 4│ @UUID.bar + ^^^^ - 1│ .foo.bar - ^^^^^^^^ + It looks like a record field access on an opaque reference. + "### + ); - It looks like a field access on an accessor. I parse.client.name as - (.client).name. Maybe use an anonymous function like - (\r -> r.client.name) instead? + test_report!( + weird_accessor, + indoc!( + r#" + .foo.bar "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn part_starts_with_number() { - report_problem_as( - indoc!( - r#" - foo.100 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + I am very confused by this field access - I trying to parse a record field access here: + 4│ .foo.bar + ^^^^^^^^ - 1│ foo.100 - ^ + 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? + "### + ); - So I expect to see a lowercase letter next, like .name or .height. + test_report!( + part_starts_with_number, + indoc!( + r#" + foo.100 "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn closure_underscore_ident() { - report_problem_as( - indoc!( - r#" - \the_answer -> 100 - "# - ), - indoc!( - r#" - ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + I trying to parse a record field access here: - I am trying to parse an identifier here: + 4│ foo.100 + ^ - 1│ \the_answer -> 100 - ^ + So I expect to see a lowercase letter next, like .name or .height. + "### + ); - Underscores are not allowed in identifiers. Use camelCase instead! + test_report!( + closure_underscore_ident, + indoc!( + r#" + \the_answer -> 100 "# - ), - ) - } + ), + @r###" + ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - #[ignore] - fn double_binop() { - report_problem_as( - indoc!( - r#" - key >= 97 && <= 122 - "# - ), - indoc!( - r#" - "# - ), - ) - } + I am trying to parse an identifier here: - #[test] - #[ignore] - fn case_of() { - report_problem_as( - indoc!( - r#" - case 1 of - 1 -> True - _ -> False - "# - ), - indoc!( - r#" - "# - ), - ) - } + 4│ \the_answer -> 100 + ^ - #[test] - fn argument_without_space() { - report_problem_as( - indoc!( - r#" - ["foo", bar("")] - "# - ), - indoc!( - r#" - ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ + Underscores are not allowed in identifiers. Use camelCase instead! + "### + ); - Nothing is named `bar` in this scope. - - 1│ ["foo", bar("")] - ^^^ - - Did you mean one of these? - - Str - Err - Box - Set - "# - ), - ) - } - - #[test] - fn invalid_operator() { - report_problem_as( - indoc!( - r#" - main = - 5 ** 3 - "# - ), - indoc!( - r#" - ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ - - 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_report!( + #[ignore] + double_binop, + indoc!( + r#" + key >= 97 && <= 122 "# - ), - ) - } + ), + @r#" + "# + ); - #[test] - fn double_plus() { - report_problem_as( - indoc!( - r#" - main = - [] ++ [] - "# - ), - indoc!( - r#" - ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ - - 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_report!( + #[ignore] + case_of, + indoc!( + r#" + case 1 of + 1 -> True + _ -> False "# - ), - ) - } + ), + @r###" + ── UNKNOWN OPERATOR ───────────────────────────────────── tmp/case_of/Test.roc ─ - #[test] - fn inline_hastype() { - report_problem_as( - indoc!( - r#" - main = - (\x -> x) : I64 + This looks like an operator, but it's not one I recognize! - 3 - "# - ), - indoc!( - r#" - ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ case 1 of + 5│ 1 -> True + ^^ - This looks like an operator, but it's not one I recognize! + The arrow -> is only used to define cases in a `when`. - 1│ main = - 2│ (\x -> x) : I64 - ^ + when color is + Red -> "stop!" + Green -> "go!" + "### + ); - The has-type operator : can only occur in a definition's type - signature, like - - increment : I64 -> I64 - increment = \x -> x + 1 + test_report!( + argument_without_space, + indoc!( + r#" + ["foo", bar("")] "# - ), - ) - } + ), + @r###" + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - #[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 ────────────────────────────────────── /code/proj/Main.roc ─ + Nothing is named `bar` in this scope. - This looks like an operator, but it's not one I recognize! + 4│ ["foo", bar("")] + ^^^ - 1│ main = 5 -> 3 - ^^ + Did you mean one of these? - The arrow -> is only used to define cases in a `when`. + Nat + Str + Err + U8 + "### + ); - when color is - Red -> "stop!" - Green -> "go!" + test_report!( + invalid_operator, + indoc!( + r#" + main = + 5 ** 3 "# - ), - ) - } + ), + @r###" + ── UNKNOWN OPERATOR ──────────────────────────── tmp/invalid_operator/Test.roc ─ + + This looks like an operator, but it's not one I recognize! + + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ main = + 5│ 5 ** 3 + ^^ + + I have no specific suggestion for this operator, see TODO for the full + list of operators in Roc. + "### + ); + + test_report!( + double_plus, + indoc!( + r#" + main = + [] ++ [] + "# + ), + @r###" + ── UNKNOWN OPERATOR ───────────────────────────────── tmp/double_plus/Test.roc ─ + + This looks like an operator, but it's not one I recognize! + + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ main = + 5│ [] ++ [] + ^^ + + To concatenate two lists or strings, try using List.concat or + Str.concat instead. + "### + ); + + test_report!( + inline_hastype, + indoc!( + r#" + main = + (\x -> x) : I64 + + 3 + "# + ), + @r###" + ── UNKNOWN OPERATOR ────────────────────────────── tmp/inline_hastype/Test.roc ─ + + This looks like an operator, but it's not one I recognize! + + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ main = + 5│ (\x -> x) : I64 + ^ + + The has-type operator : can only occur in a definition's type + signature, like + + increment : I64 -> I64 + increment = \x -> x + 1 + "### + ); + + // this is still bad, but changing the order and progress of other parsers should improve it + // down the line + test_report!( + wild_case_arrow, + indoc!( + r#" + main = 5 -> 3 + "# + ), + @r###" + ── UNKNOWN OPERATOR ───────────────────────────── tmp/wild_case_arrow/Test.roc ─ + + This looks like an operator, but it's not one I recognize! + + 1│ app "test" provides [main] to "./platform" + 2│ + 3│ main = + 4│ main = 5 -> 3 + ^^ + + The arrow -> is only used to define cases in a `when`. + + when color is + Red -> "stop!" + Green -> "go!" + "### + ); #[test] fn provides_to_identifier() { @@ -6569,1040 +5756,956 @@ All branches in an `if` must have the same type! ) } - #[test] - fn apply_unary_negative() { - report_problem_as( - indoc!( - r#" - foo = 3 + test_report!( + apply_unary_negative, + indoc!( + r#" + foo = 3 - -foo 1 2 - "# - ), - indoc!( - r#" - ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ - - This value is not a function, but it was given 2 arguments: - - 3│ -foo 1 2 - ^^^^ - - Are there any missing commas? Or missing parentheses? + -foo 1 2 "# - ), - ) - } + ), + @r###" + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn apply_unary_not() { - report_problem_as( - indoc!( - r#" - foo = True + This value is not a function, but it was given 2 arguments: - !foo 1 2 - "# - ), - indoc!( - r#" - ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ + 6│ -foo 1 2 + ^^^^ - This value is not a function, but it was given 2 arguments: + Are there any missing commas? Or missing parentheses? + "### + ); - 3│ !foo 1 2 - ^^^^ + test_report!( + apply_unary_not, + indoc!( + r#" + foo = True - Are there any missing commas? Or missing parentheses? + !foo 1 2 "# - ), - ) - } + ), + @r###" + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn applied_tag_function() { - report_problem_as( - indoc!( - r#" - x : List [Foo Str] - x = List.map [1, 2] Foo + This value is not a function, but it was given 2 arguments: - x - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + 6│ !foo 1 2 + ^^^^ - Something is off with the body of the `x` definition: + Are there any missing commas? Or missing parentheses? + "### + ); - 1│ x : List [Foo Str] - 2│ x = List.map [1, 2] Foo - ^^^^^^^^^^^^^^^^^^^ + test_report!( + applied_tag_function, + indoc!( + r#" + x : List [Foo Str] + 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 ──────────────────────────────── /code/proj/Main.roc ─ - - 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? + x "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn pattern_in_parens_end_comma() { - report_problem_as( - indoc!( - r#" - \( a, - "# - ), - indoc!( - r#" - ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ + Something is off with the body of the `x` definition: - I am partway through parsing a pattern in parentheses, but I got stuck - here: + 4│ x : List [Foo Str] + 5│ x = List.map [1, 2] Foo + ^^^^^^^^^^^^^^^^^^^ - 1│ \( a, - ^ + This `map` call produces: - I was expecting to see a closing parenthesis before this, so try - adding a ) and see if that helps? + List [Foo Num a] + + But the type annotation on `x` says it should be: + + List [Foo Str] + "### + ); + + test_report!( + pattern_in_parens_open, + indoc!( + r#" + \( a "# - ), - ) - } + ), + @r###" + ── UNFINISHED PARENTHESES ──────────────── tmp/pattern_in_parens_open/Test.roc ─ - #[test] - fn pattern_in_parens_end() { - report_problem_as( - indoc!( - r#" - \( a - "# - ), - indoc!( - r#" - ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ + I am partway through parsing a pattern in parentheses, but I got stuck + here: - I am partway through parsing a pattern in parentheses, but I got stuck - here: + 4│ \( a + ^ - 1│ \( a - ^ + I was expecting to see a closing parenthesis before this, so try + adding a ) and see if that helps? - I was expecting to see a closing parenthesis before this, so try - adding a ) and see if that helps? + Note: I may be confused by indentation + "### + ); + + test_report!( + pattern_in_parens_end_comma, + indoc!( + r#" + \( a, "# - ), - ) - } + ), + @r###" + ── UNFINISHED PARENTHESES ─────────── tmp/pattern_in_parens_end_comma/Test.roc ─ - #[test] - fn pattern_in_parens_indent_end() { - report_problem_as( - indoc!( - r#" - x = \( a - ) - "# - ), - indoc!( - r#" - ── NEED MORE INDENTATION ───────────────────────────────── /code/proj/Main.roc ─ + I am partway through parsing a pattern in parentheses, but I got stuck + here: - I am partway through parsing a pattern in parentheses, but I got stuck - here: + 4│ \( a, + ^ - 1│ x = \( a - 2│ ) - ^ + I was expecting to see a closing parenthesis before this, so try + adding a ) and see if that helps? + "### + ); - I need this parenthesis to be indented more. Try adding more spaces - before it! + test_report!( + pattern_in_parens_end, + indoc!( + r#" + \( a "# - ), - ) - } + ), + @r###" + ── UNFINISHED PARENTHESES ───────────────── tmp/pattern_in_parens_end/Test.roc ─ - #[test] - fn pattern_in_parens_indent_open() { - report_problem_as( - indoc!( - r#" - \( - "# - ), - indoc!( - r#" - ── UNFINISHED PATTERN ──────────────────────────────────── /code/proj/Main.roc ─ + I am partway through parsing a pattern in parentheses, but I got stuck + here: - I just started parsing a pattern, but I got stuck here: + 4│ \( a + ^ - 1│ \( - ^ + I was expecting to see a closing parenthesis before this, so try + adding a ) and see if that helps? - Note: I may be confused by indentation + Note: I may be confused by indentation + "### + ); + + test_report!( + pattern_in_parens_indent_end, + indoc!( + r#" + x = \( a + ) "# - ), - ) - } + ), + @r###" + ── NEED MORE INDENTATION ─────────── tmp/pattern_in_parens_indent_end/Test.roc ─ - #[test] - fn outdented_in_parens() { - report_problem_as( - indoc!( - r#" - Box : ( - Str - ) + I am partway through parsing a pattern in parentheses, but I got stuck + here: - 4 - "# - ), - indoc!( - r#" - ── NEED MORE INDENTATION ───────────────────────────────── /code/proj/Main.roc ─ + 4│ x = \( a + 5│ ) + ^ - I am partway through parsing a type in parentheses, but I got stuck - here: + I need this parenthesis to be indented more. Try adding more spaces + before it! + "### + ); - 1│ Box : ( - 2│ Str - 3│ ) - ^ - - I need this parenthesis to be indented more. Try adding more spaces - before it! + test_report!( + pattern_in_parens_indent_open, + indoc!( + r#" + \( "# - ), - ) - } + ), + @r###" + ── UNFINISHED PARENTHESES ───────── tmp/pattern_in_parens_indent_open/Test.roc ─ - #[test] - fn backpassing_type_error() { - report_problem_as( - indoc!( - r#" - x <- List.map ["a", "b"] + I just started parsing a pattern in parentheses, but I got stuck here: - x + 1 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + 4│ \( + ^ - The 2nd argument to `map` is not what I expect: + Record pattern look like { name, age: currentAge }, so I was expecting + to see a field name next. - 1│> x <- List.map ["a", "b"] - 2│> - 3│> x + 1 + Note: I may be confused by indentation + "### + ); - This argument is an anonymous function of type: + test_report!( + outdented_in_parens, + indoc!( + r#" + Box : ( + Str + ) - Num a -> Num a - - But `map` needs the 2nd argument to be: - - Str -> Num a + 4 "# - ), - ) - } + ), + @r###" + ── NEED MORE INDENTATION ──────────────────── tmp/outdented_in_parens/Test.roc ─ - #[test] - fn underscore_let() { - report_problem_as( - indoc!( - r#" - _ = 3 + I am partway through parsing a type in parentheses, but I got stuck + here: - 4 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + 4│ Box : ( + 5│ Str + 6│ ) + ^ - Underscore patterns are not allowed in definitions + I need this parenthesis to be indented more. Try adding more spaces + before it! + "### + ); - 1│ _ = 3 - ^ + test_report!( + backpassing_type_error, + indoc!( + r#" + x <- List.map ["a", "b"] + + x + 1 "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn expect_expr_type_error() { - report_problem_as( - indoc!( - r#" - expect "foobar" + The 2nd argument to `map` is not what I expect: - 4 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + 4│> x <- List.map ["a", "b"] + 5│> + 6│> x + 1 - This `expect` condition needs to be a Bool: + This argument is an anonymous function of type: - 1│ expect "foobar" - ^^^^^^^^ + Num a -> Num a - Right now it’s a string of type: + But `map` needs the 2nd argument to be: - Str + Str -> Num a + "### + ); - But I need every `expect` condition to evaluate to a Bool—either `True` - or `False`. + test_report!( + underscore_let, + indoc!( + r#" + _ = 3 + + 4 "# - ), - ) - } + ), + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn num_too_general_wildcard() { - report_problem_as( - indoc!( - r#" - mult : Num.Num *, Num.F64 -> Num.F64 - mult = \a, b -> a * b + Underscore patterns are not allowed in definitions - mult 0 0 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + 4│ _ = 3 + ^ + "### + ); - The 2nd argument to `mul` is not what I expect: + test_report!( + expect_expr_type_error, + indoc!( + r#" + expect "foobar" - 2│ mult = \a, b -> a * b - ^ - - This `b` value is a: - - F64 - - But `mul` needs the 2nd argument to be: - - Num * - - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `mult` definition: - - 1│ mult : Num.Num *, Num.F64 -> Num.F64 - 2│ mult = \a, b -> a * b - ^^^^^ - - This `mul` call produces: - - Num * - - But the type annotation on `mult` says it should be: - - F64 + 4 "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn num_too_general_named() { - report_problem_as( - indoc!( - r#" - mult : Num.Num a, Num.F64 -> Num.F64 - mult = \a, b -> a * b + This `expect` condition needs to be a Bool: - mult 0 0 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + 4│ expect "foobar" + ^^^^^^^^ - The 2nd argument to `mul` is not what I expect: + Right now it’s a string of type: - 2│ mult = \a, b -> a * b - ^ + Str - This `b` value is a: + But I need every `expect` condition to evaluate to a Bool—either `True` + or `False`. + "### + ); - F64 + test_report!( + num_too_general_wildcard, + indoc!( + r#" + mult : Num.Num *, Num.F64 -> Num.F64 + mult = \a, b -> a * b - But `mul` needs the 2nd argument to be: - - Num a - - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `mult` definition: - - 1│ mult : Num.Num a, Num.F64 -> Num.F64 - 2│ mult = \a, b -> a * b - ^^^^^ - - This `mul` call produces: - - Num a - - But the type annotation on `mult` says it should be: - - F64 + mult 0 0 "# - ), - ) - } + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn inference_var_not_enough_in_alias() { - report_problem_as( - indoc!( - r#" - Result a b : [Ok a, Err b] + The 2nd argument to `mul` is not what I expect: - canIGo : _ -> Result _ - canIGo = \color -> - when color is - "green" -> Ok "go!" - "yellow" -> Err (SlowIt "whoa, let's slow down!") - "red" -> Err (StopIt "absolutely not") - _ -> Err (UnknownColor "this is a weird stoplight") - canIGo - "# - ), - indoc!( - r#" - ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ - - The `Result` alias expects 2 type arguments, but it got 1 instead: - - 3│ canIGo : _ -> Result _ - ^^^^^^^^ - - Are there missing parentheses? - "# - ), - ) - } - - #[test] - fn inference_var_too_many_in_alias() { - report_problem_as( - indoc!( - r#" - Result a b : [Ok a, Err b] - - canIGo : _ -> Result _ _ _ - canIGo = \color -> - when color is - "green" -> Ok "go!" - "yellow" -> Err (SlowIt "whoa, let's slow down!") - "red" -> Err (StopIt "absolutely not") - _ -> Err (UnknownColor "this is a weird stoplight") - canIGo - "# - ), - indoc!( - r#" - ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ - - The `Result` alias expects 2 type arguments, but it got 3 instead: - - 3│ canIGo : _ -> Result _ _ _ - ^^^^^^^^^^^^ - - Are there missing parentheses? - "# - ), - ) - } - - #[test] - fn inference_var_conflict_in_rigid_links() { - report_problem_as( - indoc!( - r#" - f : a -> (_ -> b) - f = \x -> \y -> if x == y then x else y - f - "# - ), - // TODO: We should tell the user that we inferred `_` as `a` - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `f` definition: - - 1│ f : a -> (_ -> b) - 2│ f = \x -> \y -> if x == y then x else y - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - The body is an anonymous function of type: - - a -> a - - But the type annotation on `f` says it should be: - - a -> b - - Tip: Your type annotation uses `a` and `b` as separate type variables. - Your code seems to be saying they are the same though. Maybe they - should be the same in your type annotation? Maybe your code uses them - in a weird way? - "# - ), - ) - } - - #[test] - fn error_wildcards_are_related() { - report_problem_as( - indoc!( - r#" - f : * -> * - f = \x -> x - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `f` definition: - - 1│ f : * -> * - 2│ f = \x -> x - ^ - - The type annotation on `f` says this `x` value should have the type: - - * - - However, the type of this `x` value is connected to another type in a - way that isn't reflected in this annotation. - - Tip: Any connection between types must use a named type variable, not - a `*`! Maybe the annotation on `f` should have a named type variable in - place of the `*`? - "# - ), - ) - } - - #[test] - fn error_nested_wildcards_are_related() { - report_problem_as( - indoc!( - r#" - f : a, b, * -> {x: a, y: b, z: *} - f = \x, y, z -> {x, y, z} - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `f` definition: - - 1│ f : a, b, * -> {x: a, y: b, z: *} - 2│ f = \x, y, z -> {x, y, z} - ^^^^^^^^^ - - The type annotation on `f` says the body is a record should have the - type: - - { x : a, y : b, z : * } - - However, the type of the body is a record is connected to another type - in a way that isn't reflected in this annotation. - - Tip: Any connection between types must use a named type variable, not - a `*`! Maybe the annotation on `f` should have a named type variable in - place of the `*`? - "# - ), - ) - } - - #[test] - fn error_wildcards_are_related_in_nested_defs() { - report_problem_as( - indoc!( - r#" - f : a, b, * -> * - f = \_, _, x2 -> - inner : * -> * - inner = \y -> y - inner x2 - - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `inner` definition: - - 3│ inner : * -> * - 4│ inner = \y -> y - ^ - - The type annotation on `inner` says this `y` value should have the type: - - * - - However, the type of this `y` value is connected to another type in a - way that isn't reflected in this annotation. - - Tip: Any connection between types must use a named type variable, not - a `*`! Maybe the annotation on `inner` should have a named type variable - in place of the `*`? - "# - ), - ) - } - - #[test] - fn error_inline_alias_not_an_alias() { - report_problem_as( - indoc!( - r#" - f : List elem -> [Nil, Cons elem a] as a - "# - ), - indoc!( - r#" - ── NOT AN INLINE ALIAS ─────────────────────────────────── /code/proj/Main.roc ─ - - The inline type after this `as` is not a type alias: - - 1│ f : List elem -> [Nil, Cons elem a] as a - ^ - - Inline alias types must start with an uppercase identifier and be - followed by zero or more type arguments, like Point or List a. - "# - ), - ) - } - - #[test] - fn error_inline_alias_qualified() { - report_problem_as( - indoc!( - r#" - f : List elem -> [Nil, Cons elem a] as Module.LinkedList a - "# - ), - indoc!( - r#" - ── QUALIFIED ALIAS NAME ────────────────────────────────── /code/proj/Main.roc ─ - - This type alias has a qualified name: - - 1│ f : List elem -> [Nil, Cons elem a] as Module.LinkedList a - ^ - - An alias introduces a new name to the current scope, so it must be - unqualified. - "# - ), - ) - } - - #[test] - fn error_inline_alias_argument_uppercase() { - report_problem_as( - indoc!( - r#" - f : List elem -> [Nil, Cons elem a] as LinkedList U - "# - ), - indoc!( - r#" - ── TYPE ARGUMENT NOT LOWERCASE ─────────────────────────── /code/proj/Main.roc ─ - - This alias type argument is not lowercase: - - 1│ f : List elem -> [Nil, Cons elem a] as LinkedList U - ^ - - All type arguments must be lowercase. - "# - ), - ) - } - - #[test] - fn mismatched_single_tag_arg() { - report_problem_as( - indoc!( - r#" - isEmpty = - \email -> - Email str = email - Str.isEmpty str - - isEmpty (Name "boo") - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 1st argument to `isEmpty` is not what I expect: - - 6│ isEmpty (Name "boo") - ^^^^^^^^^^ - - This `Name` tag application has the type: - - [Name Str]a - - But `isEmpty` needs the 1st argument to be: - - [Email Str] - - Tip: Seems like a tag typo. Maybe `Name` should be `Email`? - - Tip: Can more type annotations be added? Type annotations always help - me give more specific messages, and I think they could help a lot in - this case - "# - ), - ) - } - - #[test] - fn issue_2326() { - report_problem_as( - indoc!( - r#" - C a b : a -> D a b - D a b : { a, b } - - f : C a Num.Nat -> D a Num.Nat - f = \c -> c 6 - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 1st argument to `c` is not what I expect: - - 5│ f = \c -> c 6 + 5│ mult = \a, b -> a * b ^ - This argument is a number of type: + This `b` value is a: - Num a + F64 - But `c` needs the 1st argument to be: + But `mul` needs the 2nd argument to be: - a + Num * - Tip: The type annotation uses the type variable `a` to say that this - definition can produce any type of value. But in the body I see that - it will only produce a `Num` value of a single specific type. Maybe - change the type annotation to be more specific? Maybe change the code - to be more general? - "# - ), - ) - } + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn issue_2380_annotations_only() { - report_problem_as( - indoc!( - r#" - F : F - a : F - a - "# - ), - indoc!( - r#" - ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + Something is off with the body of the `mult` definition: - The `F` alias is self-recursive in an invalid way: + 4│ mult : Num.Num *, Num.F64 -> Num.F64 + 5│ mult = \a, b -> a * b + ^^^^^ - 1│ F : F - ^ + This `mul` call produces: - Recursion in aliases is only allowed if recursion happens behind a - tagged union, at least one variant of which is not recursive. - "# - ), - ) - } + Num * - #[test] - fn issue_2380_typed_body() { - report_problem_as( - indoc!( - r#" - F : F - a : F - a = 1 - a - "# - ), - indoc!( - r#" - ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + But the type annotation on `mult` says it should be: - The `F` alias is self-recursive in an invalid way: + F64 + "### + ); - 1│ F : F - ^ + test_report!( + num_too_general_named, + indoc!( + r#" + mult : Num.Num a, Num.F64 -> Num.F64 + mult = \a, b -> a * b - Recursion in aliases is only allowed if recursion happens behind a - tagged union, at least one variant of which is not recursive. - "# - ), - ) - } + mult 0 0 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn issue_2380_alias_with_vars() { - report_problem_as( - indoc!( - r#" - F a b : F a b - a : F Str Str - a - "# - ), - indoc!( - r#" - ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + The 2nd argument to `mul` is not what I expect: - The `F` alias is self-recursive in an invalid way: + 5│ mult = \a, b -> a * b + ^ - 1│ F a b : F a b - ^ + This `b` value is a: - Recursion in aliases is only allowed if recursion happens behind a - tagged union, at least one variant of which is not recursive. - "# - ), - ) - } + F64 - #[test] - fn issue_2167_record_field_optional_and_required_mismatch() { - report_problem_as( - indoc!( - r#" - Job : [Job { inputs : List Str }] - job : { inputs ? List Str } -> Job - job = \{ inputs } -> - Job { inputs } + But `mul` needs the 2nd argument to be: - job { inputs: ["build", "test"] } - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + Num a - The 1st argument to `job` is weird: + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - 3│ job = \{ inputs } -> - ^^^^^^^^^^ + Something is off with the body of the `mult` definition: - The argument is a pattern that matches record values of type: + 4│ mult : Num.Num a, Num.F64 -> Num.F64 + 5│ mult = \a, b -> a * b + ^^^^^ - { inputs : List Str } + This `mul` call produces: - But the annotation on `job` says the 1st argument should be: + Num a - { inputs ? List Str } + But the type annotation on `mult` says it should be: - Tip: To extract the `.inputs` field it must be non-optional, but the - type says this field is optional. Learn more about optional fields at - TODO. - "# - ), - ) - } + F64 + "### + ); - #[test] - fn unify_recursive_with_nonrecursive() { - report_problem_as( - indoc!( - r#" - Job : [Job { inputs : List Job }] + test_report!( + inference_var_not_enough_in_alias, + indoc!( + r#" + Result a b : [Ok a, Err b] - job : { inputs : List Str } -> Job - job = \{ inputs } -> - Job { inputs } + canIGo : _ -> Result _ + canIGo = \color -> + when color is + "green" -> Ok "go!" + "yellow" -> Err (SlowIt "whoa, let's slow down!") + "red" -> Err (StopIt "absolutely not") + _ -> Err (UnknownColor "this is a weird stoplight") + canIGo + "# + ), + @r###" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - job { inputs: ["build", "test"] } - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + The `Result` name is first defined here: - Something is off with the body of the `job` definition: + 1│ app "test" provides [main] to "./platform" + - 3│ job : { inputs : List Str } -> Job - 4│ job = \{ inputs } -> - 5│ Job { inputs } - ^^^^^^^^^^^^^^ + But then it's defined a second time here: - This `Job` tag application has the type: + 4│ Result a b : [Ok a, Err b] + ^^^^^^^^^^^^^^^^^^^^^^^^^^ - [Job { inputs : List Str }] + Since these aliases have the same name, it's easy to use the wrong one + on accident. Give one of them a new name. - But the type annotation on `job` says it should be: + ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ - [Job { inputs : List a }] as a - "# - ), - ) - } + The `Result` alias expects 2 type arguments, but it got 1 instead: - #[test] - fn nested_datatype() { - report_problem_as( - indoc!( - r#" - Nested a : [Chain a (Nested (List a)), Term] + 6│ canIGo : _ -> Result _ + ^^^^^^^^ - s : Nested Str + Are there missing parentheses? + "### + ); - s - "# - ), - indoc!( - r#" - ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ + test_report!( + inference_var_too_many_in_alias, + indoc!( + r#" + Result a b : [Ok a, Err b] - `Nested` is a nested datatype. Here is one recursive usage of it: + canIGo : _ -> Result _ _ _ + canIGo = \color -> + when color is + "green" -> Ok "go!" + "yellow" -> Err (SlowIt "whoa, let's slow down!") + "red" -> Err (StopIt "absolutely not") + _ -> Err (UnknownColor "this is a weird stoplight") + canIGo + "# + ), + @r###" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - 1│ Nested a : [Chain a (Nested (List a)), Term] - ^^^^^^^^^^^^^^^ + The `Result` name is first defined here: - But recursive usages of `Nested` must match its definition: + 1│ app "test" provides [main] to "./platform" + - 1│ Nested a : [Chain a (Nested (List a)), Term] - ^^^^^^^^ + But then it's defined a second time here: - Nested datatypes are not supported in Roc. + 4│ Result a b : [Ok a, Err b] + ^^^^^^^^^^^^^^^^^^^^^^^^^^ - Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments. - "# - ), - ) - } + Since these aliases have the same name, it's easy to use the wrong one + on accident. Give one of them a new name. - #[test] - fn nested_datatype_inline() { - report_problem_as( - indoc!( - r#" - f : {} -> [Chain a (Nested (List a)), Term] as Nested a + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ - f - "# - ), - indoc!( - r#" - ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ + The `Result` alias expects 2 type arguments, but it got 3 instead: - `Nested` is a nested datatype. Here is one recursive usage of it: + 6│ canIGo : _ -> Result _ _ _ + ^^^^^^^^^^^^ - 1│ f : {} -> [Chain a (Nested (List a)), Term] as Nested a - ^^^^^^^^^^^^^^^ + Are there missing parentheses? + "### + ); - But recursive usages of `Nested` must match its definition: + test_report!( + inference_var_conflict_in_rigid_links, + indoc!( + r#" + f : a -> (_ -> b) + f = \x -> \y -> if x == y then x else y + f + "# + ), + // TODO: We should tell the user that we inferred `_` as `a` + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - 1│ f : {} -> [Chain a (Nested (List a)), Term] as Nested a - ^^^^^^^^ + Something is off with the body of the `f` definition: - Nested datatypes are not supported in Roc. + 4│ f : a -> (_ -> b) + 5│ f = \x -> \y -> if x == y then x else y + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments. - "# - ), - ) - } + The body is an anonymous function of type: + + a -> a + + But the type annotation on `f` says it should be: + + a -> b + + Tip: Your type annotation uses `a` and `b` as separate type variables. + Your code seems to be saying they are the same though. Maybe they + should be the same in your type annotation? Maybe your code uses them + in a weird way? + "### + ); + + test_report!( + error_wildcards_are_related, + indoc!( + r#" + f : * -> * + f = \x -> x + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `f` definition: + + 4│ f : * -> * + 5│ f = \x -> x + ^ + + The type annotation on `f` says this `x` value should have the type: + + * + + However, the type of this `x` value is connected to another type in a + way that isn't reflected in this annotation. + + Tip: Any connection between types must use a named type variable, not + a `*`! Maybe the annotation on `f` should have a named type variable in + place of the `*`? + "### + ); + + test_report!( + error_nested_wildcards_are_related, + indoc!( + r#" + f : a, b, * -> {x: a, y: b, z: *} + f = \x, y, z -> {x, y, z} + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `f` definition: + + 4│ f : a, b, * -> {x: a, y: b, z: *} + 5│ f = \x, y, z -> {x, y, z} + ^^^^^^^^^ + + The type annotation on `f` says the body is a record should have the + type: + + { x : a, y : b, z : * } + + However, the type of the body is a record is connected to another type + in a way that isn't reflected in this annotation. + + Tip: Any connection between types must use a named type variable, not + a `*`! Maybe the annotation on `f` should have a named type variable in + place of the `*`? + "### + ); + + test_report!( + error_wildcards_are_related_in_nested_defs, + indoc!( + r#" + f : a, b, * -> * + f = \_, _, x2 -> + inner : * -> * + inner = \y -> y + inner x2 + + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `inner` definition: + + 6│ inner : * -> * + 7│ inner = \y -> y + ^ + + The type annotation on `inner` says this `y` value should have the type: + + * + + However, the type of this `y` value is connected to another type in a + way that isn't reflected in this annotation. + + Tip: Any connection between types must use a named type variable, not + a `*`! Maybe the annotation on `inner` should have a named type variable + in place of the `*`? + "### + ); + + test_report!( + error_inline_alias_not_an_alias, + indoc!( + r#" + f : List elem -> [Nil, Cons elem a] as a + "# + ), + @r###" + ── NOT AN INLINE ALIAS ────────── tmp/error_inline_alias_not_an_alias/Test.roc ─ + + The inline type after this `as` is not a type alias: + + 4│ f : List elem -> [Nil, Cons elem a] as a + ^ + + Inline alias types must start with an uppercase identifier and be + followed by zero or more type arguments, like Point or List a. + "### + ); + + test_report!( + error_inline_alias_qualified, + indoc!( + r#" + f : List elem -> [Nil, Cons elem a] as Module.LinkedList a + "# + ), + @r###" + ── QUALIFIED ALIAS NAME ──────────── tmp/error_inline_alias_qualified/Test.roc ─ + + This type alias has a qualified name: + + 4│ f : List elem -> [Nil, Cons elem a] as Module.LinkedList a + ^ + + An alias introduces a new name to the current scope, so it must be + unqualified. + "### + ); + + test_report!( + error_inline_alias_argument_uppercase, + indoc!( + r#" + f : List elem -> [Nil, Cons elem a] as LinkedList U + "# + ), + @r###" + ── TYPE ARGUMENT NOT LOWERCASE ─ ...r_inline_alias_argument_uppercase/Test.roc ─ + + This alias type argument is not lowercase: + + 4│ f : List elem -> [Nil, Cons elem a] as LinkedList U + ^ + + All type arguments must be lowercase. + "### + ); + + test_report!( + mismatched_single_tag_arg, + indoc!( + r#" + isEmpty = + \email -> + Email str = email + Str.isEmpty str + + isEmpty (Name "boo") + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `isEmpty` is not what I expect: + + 9│ isEmpty (Name "boo") + ^^^^^^^^^^ + + This `Name` tag application has the type: + + [Name Str]a + + But `isEmpty` needs the 1st argument to be: + + [Email Str] + + Tip: Seems like a tag typo. Maybe `Name` should be `Email`? + + Tip: Can more type annotations be added? Type annotations always help + me give more specific messages, and I think they could help a lot in + this case + "### + ); + + test_report!( + issue_2326, + indoc!( + r#" + C a b : a -> D a b + D a b : { a, b } + + f : C a Num.Nat -> D a Num.Nat + f = \c -> c 6 + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `c` is not what I expect: + + 8│ f = \c -> c 6 + ^ + + This argument is a number of type: + + Num a + + But `c` needs the 1st argument to be: + + a + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `Num` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "### + ); + + test_report!( + issue_2380_annotations_only, + indoc!( + r#" + F : F + a : F + a + "# + ), + @r###" + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + + The `F` alias is self-recursive in an invalid way: + + 4│ F : F + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tagged union, at least one variant of which is not recursive. + "### + ); + + test_report!( + issue_2380_typed_body, + indoc!( + r#" + F : F + a : F + a = 1 + a + "# + ), + @r###" + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + + The `F` alias is self-recursive in an invalid way: + + 4│ F : F + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tagged union, at least one variant of which is not recursive. + "### + ); + + test_report!( + issue_2380_alias_with_vars, + indoc!( + r#" + F a b : F a b + a : F Str Str + a + "# + ), + @r###" + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + + The `F` alias is self-recursive in an invalid way: + + 4│ F a b : F a b + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tagged union, at least one variant of which is not recursive. + "### + ); + + test_report!( + issue_2167_record_field_optional_and_required_mismatch, + indoc!( + r#" + Job : [Job { inputs : List Str }] + job : { inputs ? List Str } -> Job + job = \{ inputs } -> + Job { inputs } + + job { inputs: ["build", "test"] } + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `job` is weird: + + 6│ job = \{ inputs } -> + ^^^^^^^^^^ + + The argument is a pattern that matches record values of type: + + { inputs : List Str } + + But the annotation on `job` says the 1st argument should be: + + { inputs ? List Str } + + Tip: To extract the `.inputs` field it must be non-optional, but the + type says this field is optional. Learn more about optional fields at + TODO. + "### + ); + + test_report!( + unify_recursive_with_nonrecursive, + indoc!( + r#" + Job : [Job { inputs : List Job }] + + job : { inputs : List Str } -> Job + job = \{ inputs } -> + Job { inputs } + + job { inputs: ["build", "test"] } + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `job` definition: + + 6│ job : { inputs : List Str } -> Job + 7│ job = \{ inputs } -> + 8│ Job { inputs } + ^^^^^^^^^^^^^^ + + This `Job` tag application has the type: + + [Job { inputs : List Str }] + + But the type annotation on `job` says it should be: + + [Job { inputs : List a }] as a + "### + ); + + test_report!( + nested_datatype, + indoc!( + r#" + Nested a : [Chain a (Nested (List a)), Term] + + s : Nested Str + + s + "# + ), + @r###" + ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ + + `Nested` is a nested datatype. Here is one recursive usage of it: + + 4│ Nested a : [Chain a (Nested (List a)), Term] + ^^^^^^^^^^^^^^^ + + But recursive usages of `Nested` must match its definition: + + 4│ Nested a : [Chain a (Nested (List a)), Term] + ^^^^^^^^ + + Nested datatypes are not supported in Roc. + + Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments. + "### + ); + + test_report!( + nested_datatype_inline, + indoc!( + r#" + f : {} -> [Chain a (Nested (List a)), Term] as Nested a + + f + "# + ), + @r###" + ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ + + `Nested` is a nested datatype. Here is one recursive usage of it: + + 4│ f : {} -> [Chain a (Nested (List a)), Term] as Nested a + ^^^^^^^^^^^^^^^ + + But recursive usages of `Nested` must match its definition: + + 4│ f : {} -> [Chain a (Nested (List a)), Term] as Nested a + ^^^^^^^^ + + Nested datatypes are not supported in Roc. + + Hint: Consider rewriting the definition of `Nested` to use the recursive type with the same arguments. + "### + ); macro_rules! mismatched_suffix_tests { ($($number:expr, $suffix:expr, $name:ident)*) => {$( - #[test] - fn $name() { - let number = $number.to_string(); - let mut typ = $suffix.to_string(); - typ.get_mut(0..1).unwrap().make_ascii_uppercase(); - let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; - let carets = "^".repeat(number.len() + $suffix.len()); - let kind = match $suffix { - "dec"|"f32"|"f64" => "a frac", - _ => "an integer", - }; + test_report!( + $name, + &{ + let number = $number.to_string(); + let mut typ = $suffix.to_string(); + typ.get_mut(0..1).unwrap().make_ascii_uppercase(); + let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; - report_problem_as( - &format!(indoc!( + format!(indoc!( r#" use : Num.{} -> Num.U8 use {}{} "# - ), bad_type, number, $suffix), - &format!(indoc!( + ), bad_type, number, $suffix) + }, + |golden| { + let number = $number.to_string(); + let mut typ = $suffix.to_string(); + typ.get_mut(0..1).unwrap().make_ascii_uppercase(); + let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; + let carets = "^".repeat(number.len() + $suffix.len()); + let kind = match $suffix { + "dec"|"f32"|"f64" => "a frac", + _ => "an integer", + }; + + let real = format!(indoc!( r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `use` is not what I expect: - 2│ use {}{} - {} + 5│ use {}{} + {} This argument is {} of type: @@ -7612,9 +6715,11 @@ All branches in an `if` must have the same type! {} "# - ), number, $suffix, carets, kind, typ, bad_type), - ) - } + ), number, $suffix, carets, kind, typ, bad_type); + + assert_eq!(golden, real); + } + ); )*} } @@ -7637,31 +6742,38 @@ All branches in an `if` must have the same type! macro_rules! mismatched_suffix_tests_in_pattern { ($($number:expr, $suffix:expr, $name:ident)*) => {$( - #[test] - fn $name() { - let number = $number.to_string(); - let mut typ = $suffix.to_string(); - typ.get_mut(0..1).unwrap().make_ascii_uppercase(); - let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" }; - let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; + test_report!( + $name, + &{ + let number = $number.to_string(); + let mut typ = $suffix.to_string(); + typ.get_mut(0..1).unwrap().make_ascii_uppercase(); + let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" }; - report_problem_as( - &format!(indoc!( + format!(indoc!( r#" when {}{} is {}{} -> 1 _ -> 1 "# - ), number, bad_suffix, number, $suffix), - &format!(indoc!( + ), number, bad_suffix, number, $suffix) + }, + |golden| { + let number = $number.to_string(); + let mut typ = $suffix.to_string(); + typ.get_mut(0..1).unwrap().make_ascii_uppercase(); + let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" }; + let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; + + let real = format!(indoc!( r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The branches of this `when` expression don't match the condition: - 1│> when {}{} is - 2│ {}{} -> 1 - 3│ _ -> 1 + 4│> when {}{} is + 5│ {}{} -> 1 + 6│ _ -> 1 The `when` condition is an integer of type: @@ -7673,9 +6785,11 @@ All branches in an `if` must have the same type! The branches must be cases of the `when` condition's type! "# - ), number, bad_suffix, number, $suffix, bad_type, typ), - ) - } + ), number, bad_suffix, number, $suffix, bad_type, typ); + + assert_eq!(golden, real); + } + ); )*} } @@ -7696,1326 +6810,1152 @@ All branches in an `if` must have the same type! 1, "f64", mismatched_suffix_f64_pattern } - #[test] - fn bad_numeric_literal_suffix() { - report_problem_as( - indoc!( - r#" - 1u256 - "# - ), - // TODO: link to number suffixes - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This integer literal contains an invalid digit: - - 1│ 1u256 - ^^^^^ - - Integer literals can only contain the digits - 0-9, or have an integer suffix. - - Tip: Learn more about number literals at TODO - "# - ), - ) - } - - #[test] - fn numer_literal_multi_suffix() { - report_problem_as( - indoc!( - r#" - 1u8u8 - "# - ), - // TODO: link to number suffixes - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - This integer literal contains an invalid digit: - - 1│ 1u8u8 - ^^^^^ - - Integer literals can only contain the digits - 0-9, or have an integer suffix. - - Tip: Learn more about number literals at TODO - "# - ), - ) - } - - #[test] - fn int_literal_has_float_suffix() { - report_problem_as( - indoc!( - r#" - 0b1f32 - "# - ), - indoc!( - r#" - ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ - - This number literal is an integer, but it has a float suffix: - - 1│ 0b1f32 - ^^^^^^ - "# - ), - ) - } - - #[test] - fn float_literal_has_int_suffix() { - report_problem_as( - indoc!( - r#" - 1.0u8 - "# - ), - indoc!( - r#" - ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ - - This number literal is a float, but it has an integer suffix: - - 1│ 1.0u8 - ^^^^^ - "# - ), - ) - } - - #[test] - fn u8_overflow() { - report_problem_as( - "256u8", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 256u8 - ^^^^^ - - Tip: The suffix indicates this integer is a U8, whose maximum value is - 255. - "# - ), - ) - } - - #[test] - fn negative_u8() { - report_problem_as( - "-1u8", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -1u8 - ^^^^ - - Tip: The suffix indicates this integer is a U8, whose minimum value is - 0. - "# - ), - ) - } - - #[test] - fn u16_overflow() { - report_problem_as( - "65536u16", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 65536u16 - ^^^^^^^^ - - Tip: The suffix indicates this integer is a U16, whose maximum value - is 65535. - "# - ), - ) - } - - #[test] - fn negative_u16() { - report_problem_as( - "-1u16", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -1u16 - ^^^^^ - - Tip: The suffix indicates this integer is a U16, whose minimum value - is 0. - "# - ), - ) - } - - #[test] - fn u32_overflow() { - report_problem_as( - "4_294_967_296u32", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 4_294_967_296u32 - ^^^^^^^^^^^^^^^^ - - Tip: The suffix indicates this integer is a U32, whose maximum value - is 4_294_967_295. - "# - ), - ) - } - - #[test] - fn negative_u32() { - report_problem_as( - "-1u32", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -1u32 - ^^^^^ - - Tip: The suffix indicates this integer is a U32, whose minimum value - is 0. - "# - ), - ) - } - - #[test] - fn u64_overflow() { - report_problem_as( - "18_446_744_073_709_551_616u64", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 18_446_744_073_709_551_616u64 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Tip: The suffix indicates this integer is a U64, whose maximum value - is 18_446_744_073_709_551_615. - "# - ), - ) - } - - #[test] - fn negative_u64() { - report_problem_as( - "-1u64", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -1u64 - ^^^^^ - - Tip: The suffix indicates this integer is a U64, whose minimum value - is 0. - "# - ), - ) - } - - #[test] - fn negative_u128() { - report_problem_as( - "-1u128", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -1u128 - ^^^^^^ - - Tip: The suffix indicates this integer is a U128, whose minimum value - is 0. - "# - ), - ) - } - - #[test] - fn i8_overflow() { - report_problem_as( - "128i8", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 128i8 - ^^^^^ - - Tip: The suffix indicates this integer is a I8, whose maximum value is - 127. - "# - ), - ) - } - - #[test] - fn i8_underflow() { - report_problem_as( - "-129i8", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -129i8 - ^^^^^^ - - Tip: The suffix indicates this integer is a I8, whose minimum value is - -128. - "# - ), - ) - } - - #[test] - fn i16_overflow() { - report_problem_as( - "32768i16", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 32768i16 - ^^^^^^^^ - - Tip: The suffix indicates this integer is a I16, whose maximum value - is 32767. - "# - ), - ) - } - - #[test] - fn i16_underflow() { - report_problem_as( - "-32769i16", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -32769i16 - ^^^^^^^^^ - - Tip: The suffix indicates this integer is a I16, whose minimum value - is -32768. - "# - ), - ) - } - - #[test] - fn i32_overflow() { - report_problem_as( - "2_147_483_648i32", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 2_147_483_648i32 - ^^^^^^^^^^^^^^^^ - - Tip: The suffix indicates this integer is a I32, whose maximum value - is 2_147_483_647. - "# - ), - ) - } - - #[test] - fn i32_underflow() { - report_problem_as( - "-2_147_483_649i32", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -2_147_483_649i32 - ^^^^^^^^^^^^^^^^^ - - Tip: The suffix indicates this integer is a I32, whose minimum value - is -2_147_483_648. - "# - ), - ) - } - - #[test] - fn i64_overflow() { - report_problem_as( - "9_223_372_036_854_775_808i64", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 9_223_372_036_854_775_808i64 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Tip: The suffix indicates this integer is a I64, whose maximum value - is 9_223_372_036_854_775_807. - "# - ), - ) - } - - #[test] - fn i64_underflow() { - report_problem_as( - "-9_223_372_036_854_775_809i64", - indoc!( - r#" - ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ - - This integer literal underflows the type indicated by its suffix: - - 1│ -9_223_372_036_854_775_809i64 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Tip: The suffix indicates this integer is a I64, whose minimum value - is -9_223_372_036_854_775_808. - "# - ), - ) - } - - #[test] - fn i128_overflow() { - report_problem_as( - "170_141_183_460_469_231_731_687_303_715_884_105_728i128", - indoc!( - r#" - ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ - - This integer literal overflows the type indicated by its suffix: - - 1│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Tip: The suffix indicates this integer is a I128, whose maximum value - is 170_141_183_460_469_231_731_687_303_715_884_105_727. - "# - ), - ) - } - - #[test] - fn list_get_negative_number() { - report_problem_as( - indoc!( - r#" - List.get [1,2,3] -1 - "# - ), - // TODO: this error message could be improved, e.g. something like "This argument can - // be used as ... because of its literal value" - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd argument to `get` is not what I expect: - - 1│ List.get [1,2,3] -1 - ^^ - - This argument is a number of type: - - I8, I16, I32, I64, I128, F32, F64, or Dec - - But `get` needs the 2nd argument to be: - - Nat - "# - ), - ) - } - - #[test] - fn list_get_negative_number_indirect() { - report_problem_as( - indoc!( - r#" - a = -9_223_372_036_854 - List.get [1,2,3] a - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd argument to `get` is not what I expect: - - 2│ List.get [1,2,3] a - ^ - - This `a` value is a: - - I64, I128, F32, F64, or Dec - - But `get` needs the 2nd argument to be: - - Nat - "# - ), - ) - } - - #[test] - fn list_get_negative_number_double_indirect() { - report_problem_as( - indoc!( - r#" - a = -9_223_372_036_854 - b = a - List.get [1,2,3] b - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd argument to `get` is not what I expect: - - 3│ List.get [1,2,3] b - ^ - - This `b` value is a: - - I64, I128, F32, F64, or Dec - - But `get` needs the 2nd argument to be: - - Nat - "# - ), - ) - } - - #[test] - fn compare_unsigned_to_signed() { - report_problem_as( - indoc!( - r#" - when -1 is - 1u8 -> 1 - _ -> 1 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The branches of this `when` expression don't match the condition: - - 1│> when -1 is - 2│ 1u8 -> 1 - 3│ _ -> 1 - - The `when` condition is a number of type: - - I8, I16, I32, I64, I128, F32, F64, or Dec - - But the branch patterns have type: - - U8 - - The branches must be cases of the `when` condition's type! - "# - ), - ) - } - - #[test] - fn recursive_type_alias_is_newtype() { - report_problem_as( - indoc!( - r#" - R a : [Only (R a)] - - v : R Str - v - "# - ), - indoc!( - r#" - ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ - - The `R` alias is self-recursive in an invalid way: - - 1│ R a : [Only (R a)] - ^ - - Recursion in aliases is only allowed if recursion happens behind a - tagged union, at least one variant of which is not recursive. - "# - ), - ) - } - - #[test] - fn recursive_type_alias_is_newtype_deep() { - report_problem_as( - indoc!( - r#" - R a : [Only { very: [Deep (R a)] }] - - v : R Str - v - "# - ), - indoc!( - r#" - ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ - - The `R` alias is self-recursive in an invalid way: - - 1│ R a : [Only { very: [Deep (R a)] }] - ^ - - Recursion in aliases is only allowed if recursion happens behind a - tagged union, at least one variant of which is not recursive. - "# - ), - ) - } - - #[test] - fn recursive_type_alias_is_newtype_mutual() { - report_problem_as( - indoc!( - r#" - Foo a : [Thing (Bar a)] - Bar a : [Stuff (Foo a)] - - v : Bar Str - v - "# - ), - indoc!( - r#" - ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ - - The `Foo` alias is recursive in an invalid way: - - 1│ Foo a : [Thing (Bar a)] - ^^^ - - The `Foo` alias depends on itself through the following chain of - definitions: - - ┌─────┐ - │ Foo - │ ↓ - │ Bar - └─────┘ - - Recursion in aliases is only allowed if recursion happens behind a - tagged union, at least one variant of which is not recursive. - "# - ), - ) - } - - #[test] - fn issue_2458() { - report_problem_as( - indoc!( - r#" - Result a b : [Ok a, Err b] - - Foo a : [Blah (Result (Bar a) [])] - Bar a : Foo a - - v : Bar Str - v - "# - ), - "", - ) - } - - #[test] - fn opaque_type_not_in_scope() { - report_problem_as( - indoc!( - r#" - @Age 21 - "# - ), - indoc!( - r#" - ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ - - The opaque type Age referenced here is not defined: - - 1│ @Age 21 - ^^^^ - - Note: It looks like there are no opaque types declared in this scope yet! - "# - ), - ) - } - - #[test] - fn opaque_reference_not_opaque_type() { - report_problem_as( - indoc!( - r#" - Age : Num.U8 - - @Age 21 - "# - ), - indoc!( - r#" - ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ - - The opaque type Age referenced here is not defined: - - 3│ @Age 21 - ^^^^ - - Note: There is an alias of the same name: - - 1│ Age : Num.U8 - ^^^ - - Note: It looks like there are no opaque types declared in this scope yet! - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `Age` is not used anywhere in your code. - - 1│ Age : Num.U8 - ^^^^^^^^^^^^ - - If you didn't intend on using `Age` then remove it so future readers of - your code don't wonder why it is there. - "# - ), - ) - } - - #[test] - fn qualified_opaque_reference() { - report_problem_as( - indoc!( - r#" - OtherModule.@Age 21 - "# - ), - // TODO: get rid of the first error. Consider parsing OtherModule.@Age to completion - // and checking it during can. The reason the error appears is because it is parsed as - // Apply(Error(OtherModule), [@Age, 21]) - indoc!( - r#" - ── OPAQUE TYPE NOT APPLIED ─────────────────────────────── /code/proj/Main.roc ─ - - This opaque type is not applied to an argument: - - 1│ OtherModule.@Age 21 - ^^^^ - - Note: Opaque types always wrap exactly one argument! - - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - I am trying to parse a qualified name here: - - 1│ OtherModule.@Age 21 - ^ - - I was expecting to see an identifier next, like height. A complete - qualified name looks something like Json.Decode.string. - "# - ), - ) - } - - #[test] - fn opaque_used_outside_declaration_scope() { - report_problem_as( - indoc!( - r#" - age = - Age := Num.U8 - 21u8 - - @Age age - "# - ), - // TODO(opaques): there is a potential for a better error message here, if the usage of - // `@Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to - // raise that declaration to the outer scope. - indoc!( - r#" - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `Age` is not used anywhere in your code. - - 2│ Age := Num.U8 - ^^^^^^^^^^^^^ - - If you didn't intend on using `Age` then remove it so future readers of - your code don't wonder why it is there. - - ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ - - The opaque type Age referenced here is not defined: - - 5│ @Age age - ^^^^ - - Note: It looks like there are no opaque types declared in this scope yet! - "# - ), - ) - } - - #[test] - fn unimported_modules_reported() { - report_problem_as( - indoc!( - r#" - main : Task.Task {} [] - main = "whatever man you don't even know my type" - main - "# - ), - indoc!( - r#" - ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ - - The `Task` module is not imported: - - 1│ main : Task.Task {} [] - ^^^^^^^^^^^^^^^ - - Is there an import missing? Perhaps there is a typo. Did you mean one - of these? - - Test - List - Num - Box - "# - ), - ) - } - - #[test] - fn opaque_mismatch_check() { - report_problem_as( - indoc!( - r#" - Age := Num.U8 - - n : Age - n = @Age "" - - n - "# - ), - // TODO(opaques): error could be improved by saying that the opaque definition demands - // that the argument be a U8, and linking to the definitin! - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression is used in an unexpected way: - - 4│ n = @Age "" + test_report!( + bad_numeric_literal_suffix, + indoc!( + r#" + 1u256 + "# + ), + // TODO: link to number suffixes + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This integer literal contains an invalid digit: + + 4│ 1u256 + ^^^^^ + + Integer literals can only contain the digits + 0-9, or have an integer suffix. + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + numer_literal_multi_suffix, + indoc!( + r#" + 1u8u8 + "# + ), + // TODO: link to number suffixes + @r###" + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + This integer literal contains an invalid digit: + + 4│ 1u8u8 + ^^^^^ + + Integer literals can only contain the digits + 0-9, or have an integer suffix. + + Tip: Learn more about number literals at TODO + "### + ); + + test_report!( + int_literal_has_float_suffix, + indoc!( + r#" + 0b1f32 + "# + ), + @r###" + ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ + + This number literal is an integer, but it has a float suffix: + + 4│ 0b1f32 + ^^^^^^ + "### + ); + + test_report!( + float_literal_has_int_suffix, + indoc!( + r#" + 1.0u8 + "# + ), + @r###" + ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ + + This number literal is a float, but it has an integer suffix: + + 4│ 1.0u8 + ^^^^^ + "### + ); + + test_report!( + u8_overflow, + "256u8", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 256u8 + ^^^^^ + + Tip: The suffix indicates this integer is a U8, whose maximum value is + 255. + "### + ); + + test_report!( + negative_u8, + "-1u8", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -1u8 + ^^^^ + + Tip: The suffix indicates this integer is a U8, whose minimum value is + 0. + "### + ); + + test_report!( + u16_overflow, + "65536u16", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 65536u16 + ^^^^^^^^ + + Tip: The suffix indicates this integer is a U16, whose maximum value + is 65535. + "### + ); + + test_report!( + negative_u16, + "-1u16", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -1u16 + ^^^^^ + + Tip: The suffix indicates this integer is a U16, whose minimum value + is 0. + "### + ); + + test_report!( + u32_overflow, + "4_294_967_296u32", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 4_294_967_296u32 + ^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a U32, whose maximum value + is 4_294_967_295. + "### + ); + + test_report!( + negative_u32, + "-1u32", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -1u32 + ^^^^^ + + Tip: The suffix indicates this integer is a U32, whose minimum value + is 0. + "### + ); + + test_report!( + u64_overflow, + "18_446_744_073_709_551_616u64", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 18_446_744_073_709_551_616u64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a U64, whose maximum value + is 18_446_744_073_709_551_615. + "### + ); + + test_report!( + negative_u64, + "-1u64", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -1u64 + ^^^^^ + + Tip: The suffix indicates this integer is a U64, whose minimum value + is 0. + "### + ); + + test_report!( + negative_u128, + "-1u128", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -1u128 + ^^^^^^ + + Tip: The suffix indicates this integer is a U128, whose minimum value + is 0. + "### + ); + + test_report!( + i8_overflow, + "128i8", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 128i8 + ^^^^^ + + Tip: The suffix indicates this integer is a I8, whose maximum value is + 127. + "### + ); + + test_report!( + i8_underflow, + "-129i8", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -129i8 + ^^^^^^ + + Tip: The suffix indicates this integer is a I8, whose minimum value is + -128. + "### + ); + + test_report!( + i16_overflow, + "32768i16", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 32768i16 + ^^^^^^^^ + + Tip: The suffix indicates this integer is a I16, whose maximum value + is 32767. + "### + ); + + test_report!( + i16_underflow, + "-32769i16", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -32769i16 + ^^^^^^^^^ + + Tip: The suffix indicates this integer is a I16, whose minimum value + is -32768. + "### + ); + + test_report!( + i32_overflow, + "2_147_483_648i32", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 2_147_483_648i32 + ^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I32, whose maximum value + is 2_147_483_647. + "### + ); + + test_report!( + i32_underflow, + "-2_147_483_649i32", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -2_147_483_649i32 + ^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I32, whose minimum value + is -2_147_483_648. + "### + ); + + test_report!( + i64_overflow, + "9_223_372_036_854_775_808i64", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 9_223_372_036_854_775_808i64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I64, whose maximum value + is 9_223_372_036_854_775_807. + "### + ); + + test_report!( + i64_underflow, + "-9_223_372_036_854_775_809i64", + @r###" + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ + + This integer literal underflows the type indicated by its suffix: + + 4│ -9_223_372_036_854_775_809i64 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I64, whose minimum value + is -9_223_372_036_854_775_808. + "### + ); + + test_report!( + i128_overflow, + "170_141_183_460_469_231_731_687_303_715_884_105_728i128", + @r###" + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ + + This integer literal overflows the type indicated by its suffix: + + 4│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Tip: The suffix indicates this integer is a I128, whose maximum value + is 170_141_183_460_469_231_731_687_303_715_884_105_727. + "### + ); + + test_report!( + list_get_negative_number, + indoc!( + r#" + List.get [1,2,3] -1 + "# + ), + // TODO: this error message could be improved, e.g. something like "This argument can + // be used as ... because of its literal value" + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd argument to `get` is not what I expect: + + 4│ List.get [1,2,3] -1 ^^ - This argument to an opaque type has type: + This argument is a number of type: - Str + I8, I16, I32, I64, I128, F32, F64, or Dec - But you are trying to use it as: + But `get` needs the 2nd argument to be: - U8 - "# - ), - ) - } + Nat + "### + ); - #[test] - fn opaque_mismatch_infer() { - report_problem_as( - indoc!( - r#" - F n := n + test_report!( + list_get_negative_number_indirect, + indoc!( + r#" + a = -9_223_372_036_854 + List.get [1,2,3] a + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - if True - then @F "" - else @F {} - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + The 2nd argument to `get` is not what I expect: - This expression is used in an unexpected way: + 5│ List.get [1,2,3] a + ^ - 5│ else @F {} - ^^ + This `a` value is a: - This argument to an opaque type has type: + I64, I128, F32, F64, or Dec - {} + But `get` needs the 2nd argument to be: - But you are trying to use it as: + Nat + "### + ); - Str - "# - ), - ) - } + test_report!( + list_get_negative_number_double_indirect, + indoc!( + r#" + a = -9_223_372_036_854 + b = a + List.get [1,2,3] b + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn opaque_creation_is_not_wrapped() { - report_problem_as( - indoc!( - r#" - F n := n + The 2nd argument to `get` is not what I expect: - v : F Str - v = "" + 6│ List.get [1,2,3] b + ^ - v - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + This `b` value is a: - Something is off with the body of the `v` definition: + I64, I128, F32, F64, or Dec - 3│ v : F Str - 4│ v = "" - ^^ + But `get` needs the 2nd argument to be: - The body is a string of type: + Nat + "### + ); - Str + test_report!( + compare_unsigned_to_signed, + indoc!( + r#" + when -1 is + 1u8 -> 1 + _ -> 1 + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - But the type annotation on `v` says it should be: + The branches of this `when` expression don't match the condition: - F Str + 4│> when -1 is + 5│ 1u8 -> 1 + 6│ _ -> 1 - Tip: Type comparisons between an opaque type are only ever equal if - both types are the same opaque type. Did you mean to create an opaque - type by wrapping it? If I have an opaque type Age := U32 I can create - an instance of this opaque type by doing @Age 23. - "# - ), - ) - } + The `when` condition is a number of type: - #[test] - fn opaque_mismatch_pattern_check() { - report_problem_as( - indoc!( - r#" + I8, I16, I32, I64, I128, F32, F64, or Dec + + But the branch patterns have type: + + U8 + + The branches must be cases of the `when` condition's type! + "### + ); + + test_report!( + recursive_type_alias_is_newtype, + indoc!( + r#" + R a : [Only (R a)] + + v : R Str + v + "# + ), + @r###" + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + + The `R` alias is self-recursive in an invalid way: + + 4│ R a : [Only (R a)] + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tagged union, at least one variant of which is not recursive. + "### + ); + + test_report!( + recursive_type_alias_is_newtype_deep, + indoc!( + r#" + R a : [Only { very: [Deep (R a)] }] + + v : R Str + v + "# + ), + @r###" + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + + The `R` alias is self-recursive in an invalid way: + + 4│ R a : [Only { very: [Deep (R a)] }] + ^ + + Recursion in aliases is only allowed if recursion happens behind a + tagged union, at least one variant of which is not recursive. + "### + ); + + test_report!( + recursive_type_alias_is_newtype_mutual, + indoc!( + r#" + Foo a : [Thing (Bar a)] + Bar a : [Stuff (Foo a)] + + v : Bar Str + v + "# + ), + @r###" + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ + + The `Foo` alias is recursive in an invalid way: + + 4│ Foo a : [Thing (Bar a)] + ^^^ + + The `Foo` alias depends on itself through the following chain of + definitions: + + ┌─────┐ + │ Foo + │ ↓ + │ Bar + └─────┘ + + Recursion in aliases is only allowed if recursion happens behind a + tagged union, at least one variant of which is not recursive. + "### + ); + + test_report!( + issue_2458, + indoc!( + r#" + Result a b : [Ok a, Err b] + + Foo a : [Blah (Result (Bar a) [])] + Bar a : Foo a + + v : Bar Str + v + "# + ), + @r###" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + + The `Result` name is first defined here: + + 1│ app "test" provides [main] to "./platform" + + + But then it's defined a second time here: + + 4│ Result a b : [Ok a, Err b] + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Since these aliases have the same name, it's easy to use the wrong one + on accident. Give one of them a new name. + "### + ); + + test_report!( + opaque_type_not_in_scope, + indoc!( + r#" + @Age 21 + "# + ), + @r###" + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ + + The opaque type Age referenced here is not defined: + + 4│ @Age 21 + ^^^^ + + Note: It looks like there are no opaque types declared in this scope yet! + "### + ); + + test_report!( + opaque_reference_not_opaque_type, + indoc!( + r#" + Age : Num.U8 + + @Age 21 + "# + ), + @r###" + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ + + The opaque type Age referenced here is not defined: + + 6│ @Age 21 + ^^^^ + + Note: There is an alias of the same name: + + 4│ Age : Num.U8 + ^^^ + + Note: It looks like there are no opaque types declared in this scope yet! + + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + + `Age` is not used anywhere in your code. + + 4│ Age : Num.U8 + ^^^^^^^^^^^^ + + If you didn't intend on using `Age` then remove it so future readers of + your code don't wonder why it is there. + "### + ); + + test_report!( + qualified_opaque_reference, + indoc!( + r#" + OtherModule.@Age 21 + "# + ), + // TODO: get rid of the first error. Consider parsing OtherModule.@Age to completion + // and checking it during can. The reason the error appears is because it is parsed as + // Apply(Error(OtherModule), [@Age, 21]) + @r###" + ── OPAQUE TYPE NOT APPLIED ─────────────────────────────── /code/proj/Main.roc ─ + + This opaque type is not applied to an argument: + + 4│ OtherModule.@Age 21 + ^^^^ + + Note: Opaque types always wrap exactly one argument! + + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ + + I am trying to parse a qualified name here: + + 4│ OtherModule.@Age 21 + ^ + + I was expecting to see an identifier next, like height. A complete + qualified name looks something like Json.Decode.string. + "### + ); + + test_report!( + opaque_used_outside_declaration_scope, + indoc!( + r#" + age = Age := Num.U8 + 21u8 - f : Age -> Num.U8 - f = \Age n -> n + @Age age + "# + ), + // TODO(opaques): there is a potential for a better error message here, if the usage of + // `@Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to + // raise that declaration to the outer scope. + @r###" + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - f - "# - ), - // TODO(opaques): error could be improved by saying that the user-provided pattern - // probably wants to change "Age" to "@Age"! - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + `Age` is not used anywhere in your code. - The 1st argument to `f` is weird: + 5│ Age := Num.U8 + ^^^^^^^^^^^^^ - 4│ f = \Age n -> n - ^^^^^ + If you didn't intend on using `Age` then remove it so future readers of + your code don't wonder why it is there. - The argument is a pattern that matches a `Age` tag of type: + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ - [Age a] + The opaque type Age referenced here is not defined: - But the annotation on `f` says the 1st argument should be: + 8│ @Age age + ^^^^ - Age + Note: It looks like there are no opaque types declared in this scope yet! + "### + ); - Tip: Type comparisons between an opaque type are only ever equal if - both types are the same opaque type. Did you mean to create an opaque - type by wrapping it? If I have an opaque type Age := U32 I can create - an instance of this opaque type by doing @Age 23. - "# - ), - ) - } + test_report!( + #[ignore = "Blocked on https://github.com/rtfeldman/roc/issues/3385"] + unimported_modules_reported, + indoc!( + r#" + main : Task.Task {} [] + main = "whatever man you don't even know my type" + main + "# + ), + @r#" + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn opaque_mismatch_pattern_infer() { - report_problem_as( - indoc!( - r#" - F n := n + The `Task` module is not imported: - \x -> - when x is - @F A -> "" - @F {} -> "" - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + 1│ main : Task.Task {} [] + ^^^^^^^^^^^^^^^ - The 2nd pattern in this `when` does not match the previous ones: + Is there an import missing? Perhaps there is a typo. Did you mean one + of these? - 6│ @F {} -> "" - ^^^^^ + Test + List + Num + Box + "# + ); - The 2nd pattern is trying to matchF unwrappings of type: + test_report!( + opaque_mismatch_check, + indoc!( + r#" + Age := Num.U8 - F {}a + n : Age + n = @Age "" - But all the previous branches match: + n + "# + ), + // TODO(opaques): error could be improved by saying that the opaque definition demands + // that the argument be a U8, and linking to the definitin! + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - F [A]a - "# - ), - ) - } + This expression is used in an unexpected way: - #[test] - fn opaque_pattern_match_not_exhaustive_tag() { - report_problem_as( - indoc!( - r#" - F n := n + 7│ n = @Age "" + ^^ - v : F [A, B, C] + This argument to an opaque type has type: - when v is - @F A -> "" - @F B -> "" - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + Str - The branches of this `when` expression don't match the condition: + But you are trying to use it as: - 5│> when v is - 6│ @F A -> "" - 7│ @F B -> "" + U8 + "### + ); - This `v` value is a: + test_report!( + opaque_mismatch_infer, + indoc!( + r#" + F n := n - F [A, B, C] + if True + then @F "" + else @F {} + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - But the branch patterns have type: + This expression is used in an unexpected way: - F [A, B] + 8│ else @F {} + ^^ - The branches must be cases of the `when` condition's type! + This argument to an opaque type has type: - Tip: Looks like the branches are missing coverage of the `C` tag. + {} - Tip: Maybe you need to add a catch-all branch, like `_`? - "# - ), - ) - } + But you are trying to use it as: - #[test] - fn opaque_pattern_match_not_exhaustive_int() { - report_problem_as( - indoc!( - r#" - F n := n + Str + "### + ); - v : F Num.U8 + test_report!( + opaque_creation_is_not_wrapped, + indoc!( + r#" + F n := n - when v is - @F 1 -> "" - @F 2 -> "" - "# - ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + v : F Str + v = "" - This `when` does not cover all the possibilities: + v + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - 5│> when v is - 6│> @F 1 -> "" - 7│> @F 2 -> "" + Something is off with the body of the `v` definition: - Other possibilities include: + 6│ v : F Str + 7│ v = "" + ^^ - @F _ + The body is a string of type: - I would have to crash if I saw one of those! Add branches for them! - "# - ), - ) - } + Str - #[test] - fn let_polymorphism_with_scoped_type_variables() { - report_problem_as( - indoc!( - r#" - f : a -> a - f = \x -> - y : a -> a - y = \z -> z + But the type annotation on `v` says it should be: - n = y 1u8 - x1 = y x - (\_ -> x1) n + F Str - f - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + Tip: Type comparisons between an opaque type are only ever equal if + both types are the same opaque type. Did you mean to create an opaque + type by wrapping it? If I have an opaque type Age := U32 I can create + an instance of this opaque type by doing @Age 23. + "### + ); - The 1st argument to `y` is not what I expect: + test_report!( + opaque_mismatch_pattern_check, + indoc!( + r#" + Age := Num.U8 - 6│ n = y 1u8 - ^^^ + f : Age -> Num.U8 + f = \Age n -> n - This argument is an integer of type: + f + "# + ), + // TODO(opaques): error could be improved by saying that the user-provided pattern + // probably wants to change "Age" to "@Age"! + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - U8 + The 1st argument to `f` is weird: - But `y` needs the 1st argument to be: + 7│ f = \Age n -> n + ^^^^^ - a + The argument is a pattern that matches a `Age` tag of type: - Tip: The type annotation uses the type variable `a` to say that this - definition can produce any type of value. But in the body I see that - it will only produce a `U8` value of a single specific type. Maybe - change the type annotation to be more specific? Maybe change the code - to be more general? - "# - ), - ) - } + [Age a] - #[test] - fn non_exhaustive_with_guard() { - report_problem_as( - indoc!( - r#" - x : [A] + But the annotation on `f` says the 1st argument should be: + + Age + + Tip: Type comparisons between an opaque type are only ever equal if + both types are the same opaque type. Did you mean to create an opaque + type by wrapping it? If I have an opaque type Age := U32 I can create + an instance of this opaque type by doing @Age 23. + "### + ); + + test_report!( + opaque_mismatch_pattern_infer, + indoc!( + r#" + F n := n + + \x -> when x is - A if True -> "" - "# - ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + @F A -> "" + @F {} -> "" + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This `when` does not cover all the possibilities: + The 2nd pattern in this `when` does not match the previous ones: - 2│> when x is - 3│> A if True -> "" + 9│ @F {} -> "" + ^^^^^ - Other possibilities include: + The 2nd pattern is trying to matchF unwrappings of type: - A (note the lack of an if clause) + F {}a - I would have to crash if I saw one of those! Add branches for them! - "# - ), - ) - } + But all the previous branches match: - #[test] - fn invalid_record_extension_type() { - report_problem_as( - indoc!( - r#" - f : { x : Num.Nat }[] - f - "# - ), - indoc!( - r#" - ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ + F [A]a + "### + ); - This record extension type is invalid: + test_report!( + opaque_pattern_match_not_exhaustive_tag, + indoc!( + r#" + F n := n - 1│ f : { x : Num.Nat }[] - ^^ + v : F [A, B, C] - Note: A record extension variable can only contain a type variable or - another record. - "# - ), - ) - } + when v is + @F A -> "" + @F B -> "" + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - #[test] - fn invalid_tag_extension_type() { - report_problem_as( - indoc!( - r#" - f : [A]Str - f - "# - ), - indoc!( - r#" - ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ + The branches of this `when` expression don't match the condition: - This tag union extension type is invalid: + 8│> when v is + 9│ @F A -> "" + 10│ @F B -> "" - 1│ f : [A]Str - ^^^ + This `v` value is a: - Note: A tag union extension variable can only contain a type variable - or another tag union. - "# - ), - ) - } + F [A, B, C] - #[test] - fn unknown_type() { - report_problem_as( - indoc!( - r#" - Type : [Constructor UnknownType] + But the branch patterns have type: - insertHelper : UnknownType, Type -> Type - insertHelper = \h, m -> - when m is - Constructor _ -> Constructor h + F [A, B] - insertHelper - "# - ), - indoc!( - r#" - ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ + The branches must be cases of the `when` condition's type! - Nothing is named `UnknownType` in this scope. + Tip: Looks like the branches are missing coverage of the `C` tag. - 1│ Type : [Constructor UnknownType] - ^^^^^^^^^^^ + Tip: Maybe you need to add a catch-all branch, like `_`? + "### + ); - Did you mean one of these? + test_report!( + opaque_pattern_match_not_exhaustive_int, + indoc!( + r#" + F n := n - Type - True - Box - Ok + v : F Num.U8 - ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ + when v is + @F 1 -> "" + @F 2 -> "" + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ - Nothing is named `UnknownType` in this scope. + This `when` does not cover all the possibilities: - 3│ insertHelper : UnknownType, Type -> Type - ^^^^^^^^^^^ + 8│> when v is + 9│> @F 1 -> "" + 10│> @F 2 -> "" - Did you mean one of these? + Other possibilities include: - Type - True - insertHelper - Box - "# - ), - ) - } + @F _ - #[test] - fn ability_first_demand_not_indented_enough() { - report_problem_as( - indoc!( - r#" - Eq has - eq : a, a -> U64 | a has Eq + I would have to crash if I saw one of those! Add branches for them! + "### + ); - 1 - "# - ), - indoc!( - r#" - ── UNFINISHED ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ + test_report!( + let_polymorphism_with_scoped_type_variables, + indoc!( + r#" + f : a -> a + f = \x -> + y : a -> a + y = \z -> z - I was partway through parsing an ability definition, but I got stuck - here: + n = y 1u8 + x1 = y x + (\_ -> x1) n - 1│ Eq has - 2│ eq : a, a -> U64 | a has Eq - ^ + f + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - I suspect this line is not indented enough (by 1 spaces) - "# - ), - ) - } + The 1st argument to `y` is not what I expect: + + 9│ n = y 1u8 + ^^^ + + This argument is an integer of type: + + U8 + + But `y` needs the 1st argument to be: + + a + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `U8` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "### + ); + + test_report!( + non_exhaustive_with_guard, + indoc!( + r#" + x : [A] + when x is + A if True -> "" + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 5│> when x is + 6│> A if True -> "" + + Other possibilities include: + + A (note the lack of an if clause) + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + invalid_record_extension_type, + indoc!( + r#" + f : { x : Num.Nat }[] + f + "# + ), + @r###" + ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ + + This record extension type is invalid: + + 4│ f : { x : Num.Nat }[] + ^^ + + Note: A record extension variable can only contain a type variable or + another record. + "### + ); + + test_report!( + invalid_tag_extension_type, + indoc!( + r#" + f : [A]Str + f + "# + ), + @r###" + ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ + + This tag union extension type is invalid: + + 4│ f : [A]Str + ^^^ + + Note: A tag union extension variable can only contain a type variable + or another tag union. + "### + ); + + test_report!( + unknown_type, + indoc!( + r#" + Type : [Constructor UnknownType] + + insertHelper : UnknownType, Type -> Type + insertHelper = \h, m -> + when m is + Constructor _ -> Constructor h + + insertHelper + "# + ), + @r###" + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ + + Nothing is named `UnknownType` in this scope. + + 4│ Type : [Constructor UnknownType] + ^^^^^^^^^^^ + + Did you mean one of these? + + Type + Unsigned8 + Unsigned32 + Unsigned16 + + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ + + Nothing is named `UnknownType` in this scope. + + 6│ insertHelper : UnknownType, Type -> Type + ^^^^^^^^^^^ + + Did you mean one of these? + + Type + Unsigned8 + Unsigned32 + Unsigned16 + "### + ); + + test_report!( + ability_first_demand_not_indented_enough, + indoc!( + r#" + Eq has + eq : a, a -> U64 | a has Eq + + 1 + "# + ), + @r###" + ── UNFINISHED ABILITY ── tmp/ability_first_demand_not_indented_enough/Test.roc ─ + + I was partway through parsing an ability definition, but I got stuck + here: + + 4│ Eq has + 5│ eq : a, a -> U64 | a has Eq + ^ + + I suspect this line is not indented enough (by 1 spaces) + "### + ); test_report!( ability_demands_not_indented_with_first, @@ -9028,19 +7968,17 @@ All branches in an `if` must have the same type! 1 "# ), - indoc!( - r#" - ── UNFINISHED ABILITY ─── tmp/ability_demands_not_indented_with_first/Test.roc ─ + @r#" + ── UNFINISHED ABILITY ─── tmp/ability_demands_not_indented_with_first/Test.roc ─ - I was partway through parsing an ability definition, but I got stuck - here: + I was partway through parsing an ability definition, but I got stuck + here: - 5│ eq : a, a -> U64 | a has Eq - 6│ neq : a, a -> U64 | a has Eq - ^ + 5│ eq : a, a -> U64 | a has Eq + 6│ neq : a, a -> U64 | a has Eq + ^ - I suspect this line is indented too much (by 4 spaces)"# - ) + I suspect this line is indented too much (by 4 spaces)"# ); test_report!( @@ -9053,180 +7991,154 @@ All branches in an `if` must have the same type! 1 "# ), - indoc!( - r#" - ── UNFINISHED ABILITY ───────────── tmp/ability_demand_value_has_args/Test.roc ─ + @r#" + ── UNFINISHED ABILITY ───────────── tmp/ability_demand_value_has_args/Test.roc ─ - I was partway through parsing an ability definition, but I got stuck - here: + I was partway through parsing an ability definition, but I got stuck + here: - 5│ eq b c : a, a -> U64 | a has Eq - ^ + 5│ eq b c : a, a -> U64 | a has Eq + ^ - I was expecting to see a : annotating the signature of this value - next."# - ) + I was expecting to see a : annotating the signature of this value + next."# ); - #[test] - fn ability_non_signature_expression() { - report_problem_as( - indoc!( - r#" - Eq has - 123 + test_report!( + ability_non_signature_expression, + indoc!( + r#" + Eq has + 123 - 1 - "# - ), - indoc!( - r#" - ── UNFINISHED ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ + 1 + "# + ), + @r###" + ── UNFINISHED ABILITY ────────── tmp/ability_non_signature_expression/Test.roc ─ - I was partway through parsing an ability definition, but I got stuck - here: + I was partway through parsing an ability definition, but I got stuck + here: - 1│ Eq has - 2│ 123 + 4│ Eq has + 5│ 123 + ^ + + I was expecting to see a value signature next. + "### + ); + + test_report!( + wildcard_in_alias, + indoc!( + r#" + I : Num.Int * + a : I + a + "# + ), + @r###" + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ + + The definition of `I` has an unbound type variable: + + 4│ I : Num.Int * ^ - I was expecting to see a value signature next. - "# - ), - ) - } + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "### + ); - #[test] - fn wildcard_in_alias() { - report_problem_as( - indoc!( - r#" - I : Num.Int * - a : I - a - "# - ), - indoc!( - r#" - ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ + test_report!( + wildcard_in_opaque, + indoc!( + r#" + I := Num.Int * + a : I + a + "# + ), + @r###" + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ - The definition of `I` has an unbound type variable: + The definition of `I` has an unbound type variable: - 1│ I : Num.Int * - ^ + 4│ I := Num.Int * + ^ - Tip: Type variables must be bound before the `:`. Perhaps you intended - to add a type parameter to this type? - "# - ), - ) - } + Tip: Type variables must be bound before the `:=`. Perhaps you intended + to add a type parameter to this type? + "### + ); - #[test] - fn wildcard_in_opaque() { - report_problem_as( - indoc!( - r#" - I := Num.Int * - a : I - a - "# - ), - indoc!( - r#" - ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ + test_report!( + multiple_wildcards_in_alias, + indoc!( + r#" + I : [A (Num.Int *), B (Num.Int *)] + a : I + a + "# + ), + @r###" + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ - The definition of `I` has an unbound type variable: + The definition of `I` has 2 unbound type variables. - 1│ I := Num.Int * - ^ + Here is one occurrence: - Tip: Type variables must be bound before the `:=`. Perhaps you intended - to add a type parameter to this type? - "# - ), - ) - } + 4│ I : [A (Num.Int *), B (Num.Int *)] + ^ - #[test] - fn multiple_wildcards_in_alias() { - report_problem_as( - indoc!( - r#" - I : [A (Num.Int *), B (Num.Int *)] - a : I - a - "# - ), - indoc!( - r#" - ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "### + ); - The definition of `I` has 2 unbound type variables. + test_report!( + inference_var_in_alias, + indoc!( + r#" + I : Num.Int _ + a : I + a + "# + ), + @r###" + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ - Here is one occurrence: + The definition of `I` has an unbound type variable: - 1│ I : [A (Num.Int *), B (Num.Int *)] - ^ + 4│ I : Num.Int _ + ^ - Tip: Type variables must be bound before the `:`. Perhaps you intended - to add a type parameter to this type? - "# - ), - ) - } + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "### + ); - #[test] - fn inference_var_in_alias() { - report_problem_as( - indoc!( - r#" - I : Num.Int _ - a : I - a - "# - ), - indoc!( - r#" - ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ + test_report!( + unbound_var_in_alias, + indoc!( + r#" + I : Num.Int a + a : I + a + "# + ), + @r###" + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ - The definition of `I` has an unbound type variable: + The definition of `I` has an unbound type variable: - 1│ I : Num.Int _ - ^ + 4│ I : Num.Int a + ^ - Tip: Type variables must be bound before the `:`. Perhaps you intended - to add a type parameter to this type? - "# - ), - ) - } - - #[test] - fn unbound_var_in_alias() { - report_problem_as( - indoc!( - r#" - I : Num.Int a - a : I - a - "# - ), - indoc!( - r#" - ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ - - The definition of `I` has an unbound type variable: - - 1│ I : Num.Int a - ^ - - Tip: Type variables must be bound before the `:`. Perhaps you intended - to add a type parameter to this type? - "# - ), - ) - } + Tip: Type variables must be bound before the `:`. Perhaps you intended + to add a type parameter to this type? + "### + ); test_report!( ability_bad_type_parameter, @@ -9238,29 +8150,27 @@ All branches in an `if` must have the same type! hash : a -> U64 | a has Hash "# ), - indoc!( - r#" - ── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─ + @r#" + ── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─ - The definition of the `Hash` ability includes type variables: + The definition of the `Hash` ability includes type variables: - 3│ Hash a b c has - ^^^^^ + 3│ Hash a b c has + ^^^^^ - Abilities cannot depend on type variables, but their member values - can! + Abilities cannot depend on type variables, but their member values + can! - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - `Hash` is not used anywhere in your code. + `Hash` is not used anywhere in your code. - 3│ Hash a b c has - ^^^^ + 3│ Hash a b c has + ^^^^ - If you didn't intend on using `Hash` then remove it so future readers of - your code don't wonder why it is there. - "# - ) + If you didn't intend on using `Hash` then remove it so future readers of + your code don't wonder why it is there. + "# ); test_report!( @@ -9272,16 +8182,14 @@ All branches in an `if` must have the same type! Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool "# ), - indoc!( - r#" - ── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─ + @r#" + ── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─ - The type referenced in this "has" clause is not an ability: + The type referenced in this "has" clause is not an ability: - 3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool - ^^^^^^^^^ - "# - ) + 3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool + ^^^^^^^^^ + "# ); test_report!( @@ -9293,24 +8201,22 @@ All branches in an `if` must have the same type! Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 "# ), - indoc!( - r#" - ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - The `a` name is first defined here: + The `a` name is first defined here: - 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 - ^^^^^^^^^ + 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 + ^^^^^^^^^ - But then it's defined a second time here: + But then it's defined a second time here: - 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 - ^^^^^^^^^ + 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 + ^^^^^^^^^ - Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. - "# - ) + Since these variables have the same name, it's easy to use the wrong + one on accident. Give one of them a new name. + "# ); test_report!( @@ -9324,24 +8230,22 @@ All branches in an `if` must have the same type! Ability has ab1 : a -> U64 | a has Ability "# ), - indoc!( - r#" - ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - The `Ability` name is first defined here: + The `Ability` name is first defined here: - 3│ Ability has ab : a -> U64 | a has Ability - ^^^^^^^ + 3│ Ability has ab : a -> U64 | a has Ability + ^^^^^^^ - But then it's defined a second time here: + But then it's defined a second time here: - 5│ Ability has ab1 : a -> U64 | a has Ability - ^^^^^^^ + 5│ Ability has ab1 : a -> U64 | a has Ability + ^^^^^^^ - Since these abilities have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. - "# - ) + Since these abilities have the same name, it's easy to use the wrong + one on accident. Give one of them a new name. + "# ); test_report!( @@ -9353,34 +8257,32 @@ All branches in an `if` must have the same type! Ability has ab : {} -> {} "# ), - indoc!( - r#" - ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ + @r#" + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ - The definition of the ability member `ab` does not include a `has` clause - binding a type variable to the ability `Ability`: + The definition of the ability member `ab` does not include a `has` clause + binding a type variable to the ability `Ability`: - 3│ Ability has ab : {} -> {} - ^^ + 3│ Ability has ab : {} -> {} + ^^ - Ability members must include a `has` clause binding a type variable to - an ability, like + Ability members must include a `has` clause binding a type variable to + an ability, like - a has Ability + a has Ability - Otherwise, the function does not need to be part of the ability! + Otherwise, the function does not need to be part of the ability! - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - `Ability` is not used anywhere in your code. + `Ability` is not used anywhere in your code. - 3│ Ability has ab : {} -> {} - ^^^^^^^ + 3│ Ability has ab : {} -> {} + ^^^^^^^ - If you didn't intend on using `Ability` then remove it so future readers - of your code don't wonder why it is there. - "# - ) + If you didn't intend on using `Ability` then remove it so future readers + of your code don't wonder why it is there. + "# ); test_report!( @@ -9392,23 +8294,21 @@ All branches in an `if` must have the same type! Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq "# ), - indoc!( - r#" - ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────── /code/proj/Main.roc ─ + @r#" + ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────── /code/proj/Main.roc ─ - The definition of the ability member `eq` includes multiple variables - bound to the `Eq`` ability:` + The definition of the ability member `eq` includes multiple variables + bound to the `Eq`` ability:` - 3│ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq - ^^^^^^^^^^^^^^^^^^ + 3│ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq + ^^^^^^^^^^^^^^^^^^ - Ability members can only bind one type variable to their parent - ability. Otherwise, I wouldn't know what type implements an ability by - looking at specializations! + Ability members can only bind one type variable to their parent + ability. Otherwise, I wouldn't know what type implements an ability by + looking at specializations! - Hint: Did you mean to only bind `a` to `Eq`? - "# - ) + Hint: Did you mean to only bind `a` to `Eq`? + "# ); test_report!( @@ -9422,33 +8322,31 @@ All branches in an `if` must have the same type! f : a -> Num.U64 | a has Hash "# ), - indoc!( - r#" - ── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─ - A `has` clause is not allowed here: + A `has` clause is not allowed here: - 3│ Hash has hash : (a | a has Hash) -> Num.U64 - ^^^^^^^^^^ + 3│ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^^^^^^^ - `has` clauses can only be specified on the top-level type annotations. + `has` clauses can only be specified on the top-level type annotations. - ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ - The definition of the ability member `hash` does not include a `has` - clause binding a type variable to the ability `Hash`: + The definition of the ability member `hash` does not include a `has` + clause binding a type variable to the ability `Hash`: - 3│ Hash has hash : (a | a has Hash) -> Num.U64 - ^^^^ + 3│ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^ - Ability members must include a `has` clause binding a type variable to - an ability, like + Ability members must include a `has` clause binding a type variable to + an ability, like - a has Hash + a has Hash - Otherwise, the function does not need to be part of the ability! - "# - ) + Otherwise, the function does not need to be part of the ability! + "# ); test_report!( @@ -9462,24 +8360,22 @@ All branches in an `if` must have the same type! hash = \{} -> 0u64 "# ), - indoc!( - r#" - ── ILLEGAL SPECIALIZATION ──────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── ILLEGAL SPECIALIZATION ──────────────────────────────── /code/proj/Main.roc ─ - This specialization of `hash` is for a non-opaque type: + This specialization of `hash` is for a non-opaque type: - 5│ hash = \{} -> 0u64 - ^^^^ + 5│ hash = \{} -> 0u64 + ^^^^ - It is specialized for + It is specialized for - {}a + {}a - but structural types can never specialize abilities! + but structural types can never specialize abilities! - Note: `hash` is a member of `#UserApp.Hash` - "# - ) + Note: `hash` is a member of `#UserApp.Hash` + "# ); test_report!( @@ -9495,24 +8391,22 @@ All branches in an `if` must have the same type! hash = \@Id n -> n "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with this specialization of `hash`: + Something is off with this specialization of `hash`: - 7│ hash = \@Id n -> n - ^^^^ + 7│ hash = \@Id n -> n + ^^^^ - This value is a declared specialization of type: + This value is a declared specialization of type: - Id -> U32 + Id -> U32 - But the type annotation on `hash` says it must match: + But the type annotation on `hash` says it must match: - Id -> U64 - "# - ) + Id -> U64 + "# ); test_report!( @@ -9530,19 +8424,17 @@ All branches in an `if` must have the same type! eq = \@Id m, @Id n -> m == n "# ), - indoc!( - r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + @r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ - The type `Id` does not fully implement the ability `Eq`. The following - specializations are missing: + The type `Id` does not fully implement the ability `Eq`. The following + specializations are missing: - A specialization for `le`, which is defined here: + A specialization for `le`, which is defined here: - 5│ le : a, a -> Bool | a has Eq - ^^ - "# - ) + 5│ le : a, a -> Bool | a has Eq + ^^ + "# ); test_report!( @@ -9557,31 +8449,29 @@ All branches in an `if` must have the same type! hash = \_ -> 0u64 "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This specialization of `hash` is overly general: + This specialization of `hash` is overly general: - 6│ hash = \_ -> 0u64 - ^^^^ + 6│ hash = \_ -> 0u64 + ^^^^ - This value is a declared specialization of type: + This value is a declared specialization of type: - a -> U64 + a -> U64 - But the type annotation on `hash` says it must match: + But the type annotation on `hash` says it must match: - a -> U64 | a has Hash + a -> U64 | a has Hash - Note: The specialized type is too general, and does not provide a - concrete type where a type variable is bound to an ability. + Note: The specialized type is too general, and does not provide a + concrete type where a type variable is bound to an ability. - Specializations can only be made for concrete types. If you have a - generic implementation for this value, perhaps you don't need an - ability? - "# - ) + Specializations can only be made for concrete types. If you have a + generic implementation for this value, perhaps you don't need an + ability? + "# ); test_report!( @@ -9599,29 +8489,27 @@ All branches in an `if` must have the same type! eq = \@You {}, @AndI {} -> False "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with this specialization of `eq`: + Something is off with this specialization of `eq`: - 9│ eq = \@You {}, @AndI {} -> False - ^^ + 9│ eq = \@You {}, @AndI {} -> False + ^^ - This value is a declared specialization of type: + This value is a declared specialization of type: - You, AndI -> [False, True] + You, AndI -> [False, True] - But the type annotation on `eq` says it must match: + But the type annotation on `eq` says it must match: - You, You -> Bool + You, You -> Bool - Tip: Type comparisons between an opaque type are only ever equal if - both types are the same opaque type. Did you mean to create an opaque - type by wrapping it? If I have an opaque type Age := U32 I can create - an instance of this opaque type by doing @Age 23. - "# - ) + Tip: Type comparisons between an opaque type are only ever equal if + both types are the same opaque type. Did you mean to create an opaque + type by wrapping it? If I have an opaque type Age := U32 I can create + an instance of this opaque type by doing @Age 23. + "# ); test_report!( @@ -9639,40 +8527,38 @@ All branches in an `if` must have the same type! hash = \@Id n -> n "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with the body of the `hash` definition: + Something is off with the body of the `hash` definition: - 8│ hash : Id -> U32 - 9│ hash = \@Id n -> n - ^ + 8│ hash : Id -> U32 + 9│ hash = \@Id n -> n + ^ - This `n` value is a: + This `n` value is a: - U64 + U64 - But the type annotation on `hash` says it should be: + But the type annotation on `hash` says it should be: - U32 + U32 - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with this specialization of `hash`: + Something is off with this specialization of `hash`: - 9│ hash = \@Id n -> n - ^^^^^^^^^^^ + 9│ hash = \@Id n -> n + ^^^^^^^^^^^ - This value is a declared specialization of type: + This value is a declared specialization of type: - Id -> U32 + Id -> U32 - But the type annotation on `hash` says it must match: + But the type annotation on `hash` says it must match: - Id -> U64 - "# - ) + Id -> U64 + "# ); test_report!( @@ -9697,37 +8583,35 @@ All branches in an `if` must have the same type! } "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This expression has a type that does not implement the abilities it's expected to: + This expression has a type that does not implement the abilities it's expected to: - 15│ notYet: hash (A 1), - ^^^ + 15│ notYet: hash (A 1), + ^^^ - Roc can't generate an implementation of the `#UserApp.Hash` ability for + Roc can't generate an implementation of the `#UserApp.Hash` ability for - [A (Num a)]b + [A (Num a)]b - Only builtin abilities can have generated implementations! + Only builtin abilities can have generated implementations! - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This expression has a type that does not implement the abilities it's expected to: + This expression has a type that does not implement the abilities it's expected to: - 14│ nope: hash (@User {}), - ^^^^^^^^ + 14│ nope: hash (@User {}), + ^^^^^^^^ - The type `User` does not fully implement the ability `Hash`. The following - specializations are missing: + The type `User` does not fully implement the ability `Hash`. The following + specializations are missing: - A specialization for `hash`, which is defined here: + A specialization for `hash`, which is defined here: - 4│ hash : a -> U64 | a has Hash - ^^^^ - "# - ) + 4│ hash : a -> U64 | a has Hash + ^^^^ + "# ); test_report!( @@ -9743,18 +8627,16 @@ All branches in an `if` must have the same type! 123 "# ), - indoc!( - r#" - ── ABILITY NOT ON TOP-LEVEL ────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── ABILITY NOT ON TOP-LEVEL ────────────────────────────── /code/proj/Main.roc ─ - This ability definition is not on the top-level of a module: + This ability definition is not on the top-level of a module: - 4│> Hash has - 5│> hash : a -> U64 | a has Hash + 4│> Hash has + 5│> hash : a -> U64 | a has Hash - Abilities can only be defined on the top-level of a Roc module. - "# - ) + Abilities can only be defined on the top-level of a Roc module. + "# ); test_report!( @@ -9773,31 +8655,29 @@ All branches in an `if` must have the same type! hashable = @Id 15 "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with the body of the `hashable` definition: + Something is off with the body of the `hashable` definition: - 9│ hashable : a | a has Hash - 10│ hashable = @Id 15 - ^^^^^^ + 9│ hashable : a | a has Hash + 10│ hashable = @Id 15 + ^^^^^^ - This Id opaque wrapping has the type: + This Id opaque wrapping has the type: - Id + Id - But the type annotation on `hashable` says it should be: + But the type annotation on `hashable` says it should be: - a | a has Hash + a | a has Hash - Tip: The type annotation uses the type variable `a` to say that this - definition can produce any value implementing the `Hash` ability. But in - the body I see that it will only produce a `Id` value of a single - specific type. Maybe change the type annotation to be more specific? - Maybe change the code to be more general? - "# - ) + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any value implementing the `Hash` ability. But in + the body I see that it will only produce a `Id` value of a single + specific type. Maybe change the type annotation to be more specific? + Maybe change the code to be more general? + "# ); test_report!( @@ -9821,37 +8701,35 @@ All branches in an `if` must have the same type! result = mulHashes (@Id 100) (@Three {}) "# ), - indoc!( - r#" - ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ - You are attempting to use the ability `Hash` as a type directly: + You are attempting to use the ability `Hash` as a type directly: - 6│ mulHashes : Hash, Hash -> U64 - ^^^^ + 6│ mulHashes : Hash, Hash -> U64 + ^^^^ - Abilities can only be used in type annotations to constrain type - variables. + Abilities can only be used in type annotations to constrain type + variables. - Hint: Perhaps you meant to include a `has` annotation, like + Hint: Perhaps you meant to include a `has` annotation, like - a has Hash + a has Hash - ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ - You are attempting to use the ability `Hash` as a type directly: + You are attempting to use the ability `Hash` as a type directly: - 6│ mulHashes : Hash, Hash -> U64 - ^^^^ + 6│ mulHashes : Hash, Hash -> U64 + ^^^^ - Abilities can only be used in type annotations to constrain type - variables. + Abilities can only be used in type annotations to constrain type + variables. - Hint: Perhaps you meant to include a `has` annotation, like + Hint: Perhaps you meant to include a `has` annotation, like - b has Hash - "# - ) + b has Hash + "# ); test_report!( @@ -9867,48 +8745,44 @@ All branches in an `if` must have the same type! foo "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The branches of this `when` expression don't match the condition: + The branches of this `when` expression don't match the condition: - 6│> when bool is - 7│ True -> "true" - 8│ False -> "false" - 9│ Wat -> "surprise!" + 6│> when bool is + 7│ True -> "true" + 8│ False -> "false" + 9│ Wat -> "surprise!" - This `bool` value is a: + This `bool` value is a: - Bool + Bool - But the branch patterns have type: + But the branch patterns have type: - [False, True, Wat] + [False, True, Wat] - The branches must be cases of the `when` condition's type! - "# - ) + The branches must be cases of the `when` condition's type! + "# ); - #[test] - fn always_function() { - // from https://github.com/rtfeldman/roc/commit/1372737f5e53ee5bb96d7e1b9593985e5537023a - // There was a bug where this reported UnusedArgument("val") - // since it was used only in the returned function only. - // - // we want this to not give any warnings/errors! - report_problem_as( - indoc!( - r#" - always = \val -> \_ -> val + // from https://github.com/rtfeldman/roc/commit/1372737f5e53ee5bb96d7e1b9593985e5537023a + // There was a bug where this reported UnusedArgument("val") + // since it was used only in the returned function only. + // + // we want this to not give any warnings/errors! + test_report!( + always_function, + indoc!( + r#" + always = \val -> \_ -> val - always - "# - ), - "", - ) - } + always + "# + ), + @"" + ); test_report!( imports_missing_comma, @@ -9920,20 +8794,18 @@ All branches in an `if` must have the same type! provides [main, @Foo] to pf "# ), - indoc!( - r#" - ── WEIRD IMPORTS ────────────────────────── tmp/imports_missing_comma/Test.roc ─ + @r#" + ── WEIRD IMPORTS ────────────────────────── tmp/imports_missing_comma/Test.roc ─ - I am partway through parsing a imports list, but I got stuck here: + I am partway through parsing a imports list, but I got stuck here: - 2│ packages { pf: "platform/main.roc" } - 3│ imports [pf.Task Base64] - ^ + 2│ packages { pf: "platform/main.roc" } + 3│ imports [pf.Task Base64] + ^ - I am expecting a comma or end of list, like + I am expecting a comma or end of list, like - imports [Shape, Vector]"# - ) + imports [Shape, Vector]"# ); test_report!( @@ -9947,23 +8819,21 @@ All branches in an `if` must have the same type! foo "# ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ - This `when` does not cover all the possibilities: + This `when` does not cover all the possibilities: - 6│> when it is - 7│> A -> "" + 6│> when it is + 7│> A -> "" - Other possibilities include: + Other possibilities include: - B - _ + B + _ - I would have to crash if I saw one of those! Add branches for them! - "# - ) + I would have to crash if I saw one of those! Add branches for them! + "# ); test_report!( @@ -9979,7 +8849,7 @@ All branches in an `if` must have the same type! Red |> formatColor |> Str.concat (formatColor Orange) "# ), - "" // no problem + @"" // no problem ); test_report!( @@ -9996,19 +8866,17 @@ All branches in an `if` must have the same type! default {} "# ), - indoc!( - r#" - ── SPECIALIZATION NOT ON TOP-LEVEL ─────────────────────── /code/proj/Main.roc ─ + @r#" + ── SPECIALIZATION NOT ON TOP-LEVEL ─────────────────────── /code/proj/Main.roc ─ - This specialization of the `default` ability member is in a nested - scope: + This specialization of the `default` ability member is in a nested + scope: - 7│ default = \{} -> @A {} - ^^^^^^^ + 7│ default = \{} -> @A {} + ^^^^^^^ - Specializations can only be defined on the top-level of a module. - "# - ) + Specializations can only be defined on the top-level of a module. + "# ); test_report!( @@ -10023,24 +8891,22 @@ All branches in an `if` must have the same type! Job lst -> lst == "" "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 2nd argument to `isEq` is not what I expect: + The 2nd argument to `isEq` is not what I expect: - 9│ Job lst -> lst == "" - ^^ + 9│ Job lst -> lst == "" + ^^ - This argument is a string of type: + This argument is a string of type: - Str + Str - But `isEq` needs the 2nd argument to be: + But `isEq` needs the 2nd argument to be: - List [Job ∞] as ∞ - "# - ) + List [Job ∞] as ∞ + "# ); test_report!( @@ -10060,56 +8926,54 @@ All branches in an `if` must have the same type! go goal new "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st argument to `remove` is not what I expect: + The 1st argument to `remove` is not what I expect: - 10│ new = { model & set : Set.remove goal model.set } - ^^^^ + 10│ new = { model & set : Set.remove goal model.set } + ^^^^ - This `goal` value is a: + This `goal` value is a: - a + a - But `remove` needs the 1st argument to be: + But `remove` needs the 1st argument to be: - Set a + Set a - Tip: The type annotation uses the type variable `a` to say that this - definition can produce any type of value. But in the body I see that - it will only produce a `Set` value of a single specific type. Maybe - change the type annotation to be more specific? Maybe change the code - to be more general? + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `Set` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `new`: + I'm inferring a weird self-referential type for `new`: - 10│ new = { model & set : Set.remove goal model.set } - ^^^ + 10│ new = { model & set : Set.remove goal model.set } + ^^^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. - { set : Set ∞ } + { set : Set ∞ } - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `goal`: + I'm inferring a weird self-referential type for `goal`: - 6│ go = \goal, model -> - ^^^^ + 6│ go = \goal, model -> + ^^^^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. - Set ∞ - "# - ) + Set ∞ + "# ); test_report!( @@ -10126,25 +8990,23 @@ All branches in an `if` must have the same type! t2 "# ), - indoc!( - r#" - ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ - The `t1` definition is causing a very tricky infinite loop: + The `t1` definition is causing a very tricky infinite loop: - 7│ t1 = \_ -> force (\_ -> t2) - ^^ + 7│ t1 = \_ -> force (\_ -> t2) + ^^ - The `t1` value depends on itself through the following chain of - definitions: + The `t1` value depends on itself through the following chain of + definitions: - ┌─────┐ - │ t1 - │ ↓ - │ t2 - └─────┘ - "# - ) + ┌─────┐ + │ t1 + │ ↓ + │ t2 + └─────┘ + "# ); test_report!( @@ -10156,23 +9018,21 @@ All branches in an `if` must have the same type! main = Encode.toEncoder \x -> x "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This expression has a type that does not implement the abilities it's expected to: + This expression has a type that does not implement the abilities it's expected to: - 3│ main = Encode.toEncoder \x -> x - ^^^^^^^ + 3│ main = Encode.toEncoder \x -> x + ^^^^^^^ - Roc can't generate an implementation of the `Encode.Encoding` ability - for + Roc can't generate an implementation of the `Encode.Encoding` ability + for - a -> a + a -> a - Note: `Encoding` cannot be generated for functions. - "# - ) + Note: `Encoding` cannot be generated for functions. + "# ); test_report!( @@ -10184,30 +9044,28 @@ All branches in an `if` must have the same type! main = \x -> Encode.toEncoder { x: x } "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This expression has a type that does not implement the abilities it's expected to: + This expression has a type that does not implement the abilities it's expected to: - 3│ main = \x -> Encode.toEncoder { x: x } - ^^^^^^^^ + 3│ main = \x -> Encode.toEncoder { x: x } + ^^^^^^^^ - Roc can't generate an implementation of the `Encode.Encoding` ability - for + Roc can't generate an implementation of the `Encode.Encoding` ability + for - { x : a } + { x : a } - In particular, an implementation for + In particular, an implementation for - a + a - cannot be generated. + cannot be generated. - Tip: This type variable is not bound to `Encoding`. Consider adding a - `has` clause to bind the type variable, like `| a has Encode.Encoding` - "# - ) + Tip: This type variable is not bound to `Encoding`. Consider adding a + `has` clause to bind the type variable, like `| a has Encode.Encoding` + "# ); test_report!( @@ -10220,30 +9078,28 @@ All branches in an `if` must have the same type! main = Encode.toEncoder { x: @A {} } "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This expression has a type that does not implement the abilities it's expected to: + This expression has a type that does not implement the abilities it's expected to: - 4│ main = Encode.toEncoder { x: @A {} } - ^^^^^^^^^^^^ + 4│ main = Encode.toEncoder { x: @A {} } + ^^^^^^^^^^^^ - Roc can't generate an implementation of the `Encode.Encoding` ability - for + Roc can't generate an implementation of the `Encode.Encoding` ability + for - { x : A } + { x : A } - In particular, an implementation for + In particular, an implementation for - A + A - cannot be generated. + cannot be generated. - Tip: `A` does not implement `Encoding`. Consider adding a custom - implementation or `has Encode.Encoding` to the definition of `A`. - "# - ) + Tip: `A` does not implement `Encoding`. Consider adding a custom + implementation or `has Encode.Encoding` to the definition of `A`. + "# ); test_report!( @@ -10260,25 +9116,23 @@ All branches in an `if` must have the same type! t2 = t1 {} "# ), - indoc!( - r#" - ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ - The `t1` definition is causing a very tricky infinite loop: + The `t1` definition is causing a very tricky infinite loop: - 6│ t1 = \_ -> force (\_ -> t2) - ^^ + 6│ t1 = \_ -> force (\_ -> t2) + ^^ - The `t1` value depends on itself through the following chain of - definitions: + The `t1` value depends on itself through the following chain of + definitions: - ┌─────┐ - │ t1 - │ ↓ - │ t2 - └─────┘ - "# - ) + ┌─────┐ + │ t1 + │ ↓ + │ t2 + └─────┘ + "# ); test_report!( @@ -10292,20 +9146,18 @@ All branches in an `if` must have the same type! A := {} has [Ab] "# ), - indoc!( - r#" - ── ILLEGAL DERIVE ──────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── ILLEGAL DERIVE ──────────────────────────────────────── /code/proj/Main.roc ─ - This ability cannot be derived: + This ability cannot be derived: - 5│ A := {} has [Ab] - ^^ + 5│ A := {} has [Ab] + ^^ - Only builtin abilities can be derived. + Only builtin abilities can be derived. - Note: The builtin abilities are `Encode.Encoding` - "# - ) + Note: The builtin abilities are `Encode.Encoding` + "# ); test_report!( @@ -10317,20 +9169,18 @@ All branches in an `if` must have the same type! A a := a -> a has [Encode.Encoding] "# ), - indoc!( - r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + @r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ - Roc can't derive an implementation of the `Encode.Encoding` for `A`: + Roc can't derive an implementation of the `Encode.Encoding` for `A`: - 3│ A a := a -> a has [Encode.Encoding] - ^^^^^^^^^^^^^^^ + 3│ A a := a -> a has [Encode.Encoding] + ^^^^^^^^^^^^^^^ - Note: `Encoding` cannot be generated for functions. + Note: `Encoding` cannot be generated for functions. - Tip: You can define a custom implementation of `Encode.Encoding` for `A`. - "# - ) + Tip: You can define a custom implementation of `Encode.Encoding` for `A`. + "# ); test_report!( @@ -10344,21 +9194,19 @@ All branches in an `if` must have the same type! B := {} "# ), - indoc!( - r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + @r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ - Roc can't derive an implementation of the `Encode.Encoding` for `A`: + Roc can't derive an implementation of the `Encode.Encoding` for `A`: - 3│ A := B has [Encode.Encoding] - ^^^^^^^^^^^^^^^ + 3│ A := B has [Encode.Encoding] + ^^^^^^^^^^^^^^^ - Tip: `B` does not implement `Encoding`. Consider adding a custom - implementation or `has Encode.Encoding` to the definition of `B`. + Tip: `B` does not implement `Encoding`. Consider adding a custom + implementation or `has Encode.Encoding` to the definition of `B`. - Tip: You can define a custom implementation of `Encode.Encoding` for `A`. - "# - ) + Tip: You can define a custom implementation of `Encode.Encoding` for `A`. + "# ); test_report!( @@ -10372,7 +9220,7 @@ All branches in an `if` must have the same type! B := {} has [Encode.Encoding] "# ), - indoc!("") // no error + @"" // no error ); test_report!( @@ -10384,7 +9232,7 @@ All branches in an `if` must have the same type! MyNat := [S MyNat, Z] has [Encode.Encoding] "# ), - indoc!("") // no error + @"" // no error ); test_report!( @@ -10398,24 +9246,22 @@ All branches in an `if` must have the same type! main = \n -> n + 2 "# ), - indoc!( - r#" - ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - - The `main` name is first defined here: - - 3│ main = 1 - ^^^^ - - But then it's defined a second time here: - - 5│ main = \n -> n + 2 - ^^^^ - - Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. - "# - ) + @r#" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + + The `main` name is first defined here: + + 3│ main = 1 + ^^^^ + + But then it's defined a second time here: + + 5│ main = \n -> n + 2 + ^^^^ + + Since these variables have the same name, it's easy to use the wrong + one on accident. Give one of them a new name. + "# ); test_report!( @@ -10429,29 +9275,27 @@ All branches in an `if` must have the same type! toEncoder = \@A {} -> custom \l, _ -> l "# ), - indoc!( - r#" - ── CONFLICTING DERIVE AND IMPLEMENTATION ───────────────── /code/proj/Main.roc ─ + @r#" + ── CONFLICTING DERIVE AND IMPLEMENTATION ───────────────── /code/proj/Main.roc ─ - `A` both derives and custom-implements `Encode.Encoding`. We found the - derive here: + `A` both derives and custom-implements `Encode.Encoding`. We found the + derive here: - 3│ A := {} has [Encode.Encoding] - ^^^^^^^^^^^^^^^ + 3│ A := {} has [Encode.Encoding] + ^^^^^^^^^^^^^^^ - and one custom implementation of `Encode.Encoding` here: + and one custom implementation of `Encode.Encoding` here: - 5│ toEncoder = \@A {} -> custom \l, _ -> l - ^^^^^^^^^ + 5│ toEncoder = \@A {} -> custom \l, _ -> l + ^^^^^^^^^ - Derived and custom implementations can conflict, so one of them needs - to be removed! + Derived and custom implementations can conflict, so one of them needs + to be removed! - Note: We'll try to compile your program using the custom - implementation first, and fall-back on the derived implementation if - needed. Make sure to disambiguate which one you want! - "# - ) + Note: We'll try to compile your program using the custom + implementation first, and fall-back on the derived implementation if + needed. Make sure to disambiguate which one you want! + "# ); test_report!( @@ -10473,31 +9317,29 @@ All branches in an `if` must have the same type! withOpen "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with the body of the `withOpen` definition: + Something is off with the body of the `withOpen` definition: - 10│ withOpen : (Handle -> Result {} *) -> Result {} * - 11│ withOpen = \callback -> - 12│> handle <- await (open {}) - 13│> {} <- await (callback handle) - 14│> close handle + 10│ withOpen : (Handle -> Result {} *) -> Result {} * + 11│ withOpen = \callback -> + 12│> handle <- await (open {}) + 13│> {} <- await (callback handle) + 14│> close handle - The type annotation on `withOpen` says this `await` call should have the - type: + The type annotation on `withOpen` says this `await` call should have the + type: - Result {} * + Result {} * - However, the type of this `await` call is connected to another type in a - way that isn't reflected in this annotation. + However, the type of this `await` call is connected to another type in a + way that isn't reflected in this annotation. - Tip: Any connection between types must use a named type variable, not - a `*`! Maybe the annotation on `withOpen` should have a named type - variable in place of the `*`? - "# - ) + Tip: Any connection between types must use a named type variable, not + a `*`! Maybe the annotation on `withOpen` should have a named type + variable in place of the `*`? + "# ); test_report!( @@ -10510,23 +9352,21 @@ All branches in an `if` must have the same type! f "# ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This expression is used in an unexpected way: + This expression is used in an unexpected way: - 5│ f = \_ -> if True then {} else f {} - ^^^^^^^^^^^^^^^^^^^^^^^^^ + 5│ f = \_ -> if True then {} else f {} + ^^^^^^^^^^^^^^^^^^^^^^^^^ - It is a value of type: + It is a value of type: - {} + {} - But you are trying to use it as: + But you are trying to use it as: - a -> Str - "# - ) + a -> Str + "### ); } diff --git a/examples/platform-switching/web-assembly-platform/README.md b/examples/platform-switching/web-assembly-platform/README.md index 47b65fbe64..2e77e5a0f0 100644 --- a/examples/platform-switching/web-assembly-platform/README.md +++ b/examples/platform-switching/web-assembly-platform/README.md @@ -3,12 +3,12 @@ To run this website, first compile either of these identical apps: ```bash -# Option A: Compile examples/platform-switching/web-assembly-platform/rocLovesWeb.roc -cargo run -- build --target=wasm32 examples/platform-switching/web-assembly-platform/rocLovesWeb.roc +# Option A: Compile examples/platform-switching/web-assembly-platform/rocLovesWebAssembly.roc +cargo run -- build --target=wasm32 examples/platform-switching/web-assembly-platform/rocLovesWebAssembly.roc # Option B: Compile examples/platform-switching/main.roc with `pf: "web-assembly-platform/main.roc"` and move the result cargo run -- build --target=wasm32 examples/platform-switching/main.roc -(cd examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWeb.wasm) +(cd examples/platform-switching && mv rocLovesPlatforms.wasm web-assembly-platform/rocLovesWebAssembly.wasm) ``` Then `cd` into the website directory diff --git a/examples/platform-switching/web-assembly-platform/host.js b/examples/platform-switching/web-assembly-platform/host.js index 77f2bbed78..e8cdfab3c5 100644 --- a/examples/platform-switching/web-assembly-platform/host.js +++ b/examples/platform-switching/web-assembly-platform/host.js @@ -17,6 +17,7 @@ async function roc_web_platform_run(wasm_filename, callback) { } exit_code = code; }, + fd_write: (x) => { console.error(`fd_write not supported: ${x}`); } }, env: { js_display_roc_string, diff --git a/examples/platform-switching/web-assembly-platform/host.test.js b/examples/platform-switching/web-assembly-platform/host.test.js index ac0b1f5dc7..0152e80a8d 100644 --- a/examples/platform-switching/web-assembly-platform/host.test.js +++ b/examples/platform-switching/web-assembly-platform/host.test.js @@ -15,8 +15,8 @@ global.fetch = (filename) => const { roc_web_platform_run } = require("./host"); -roc_web_platform_run("../helloWeb.wasm", (string_from_roc) => { - const expected = "Hello, World!\n"; +roc_web_platform_run("./rocLovesWebAssembly.wasm", (string_from_roc) => { + const expected = "Roc <3 Web Assembly!\n"; if (string_from_roc !== expected) { console.error(`Expected "${expected}", but got "${string_from_roc}"`); process.exit(1); diff --git a/examples/platform-switching/web-assembly-platform/index.html b/examples/platform-switching/web-assembly-platform/index.html index 0397305c5a..a664e3469c 100644 --- a/examples/platform-switching/web-assembly-platform/index.html +++ b/examples/platform-switching/web-assembly-platform/index.html @@ -4,7 +4,7 @@