diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 5e4d07e6bc..7e437ba1e7 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -93,7 +93,10 @@ fn jit_to_ast_help<'a>( ), Layout::Builtin(Builtin::EmptyList) => { Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| { - Expr::List(&[]) + Expr::List { + items: &[], + final_comments: &[], + } })) } Layout::Builtin(Builtin::List(_, elem_layout)) => Ok(run_jit_function!( @@ -251,7 +254,10 @@ fn ptr_to_ast<'a>( num_to_ast(env, f64_to_ast(env.arena, num), content) } - Layout::Builtin(Builtin::EmptyList) => Expr::List(&[]), + Layout::Builtin(Builtin::EmptyList) => Expr::List { + items: &[], + final_comments: &[], + }, Layout::Builtin(Builtin::List(_, elem_layout)) => { // Turn the (ptr, len) wrapper struct into actual ptr and len values. let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) }; @@ -331,7 +337,10 @@ fn list_to_ast<'a>( let output = output.into_bump_slice(); - Expr::List(output) + Expr::List { + items: output, + final_comments: &[], + } } fn single_tag_union_to_ast<'a>( diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index cf1cc2d02b..777e51f599 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -285,7 +285,9 @@ pub fn canonicalize_expr<'a>( } } ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal), - ast::Expr::List(loc_elems) => { + ast::Expr::List { + items: loc_elems, .. + } => { if loc_elems.is_empty() { ( List { diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 0ec2c82f72..5f9d73bb70 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -109,14 +109,24 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a arena.alloc(Located { region, value }) } - List(elems) | Nested(List(elems)) => { - let mut new_elems = Vec::with_capacity_in(elems.len(), arena); + List { + items, + final_comments, + } + | Nested(List { + items, + final_comments, + }) => { + let mut new_items = Vec::with_capacity_in(items.len(), arena); - for elem in elems.iter() { - new_elems.push(desugar_expr(arena, elem)); + for item in items.iter() { + new_items.push(desugar_expr(arena, item)); } - let new_elems = new_elems.into_bump_slice(); - let value: Expr<'a> = List(new_elems); + let new_items = new_items.into_bump_slice(); + let value: Expr<'a> = List { + items: new_items, + final_comments, + }; arena.alloc(Located { region: loc_expr.region, diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index ef83d39241..f6d9558ede 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -122,7 +122,7 @@ pub fn fmt_body<'a>( Expr::SpaceBefore(_, _) => { body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); } - Expr::Record { .. } | Expr::List(_) => { + Expr::Record { .. } | Expr::List { .. } => { newline(buf, indent + INDENT); body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index ec386bc54e..1be56ee514 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -38,7 +38,7 @@ impl<'a> Formattable<'a> for Expr<'a> { // These expressions always have newlines Defs(_, _) | When(_, _) => true, - List(elems) => elems.iter().any(|loc_expr| loc_expr.is_multiline()), + List { items, .. } => items.iter().any(|loc_expr| loc_expr.is_multiline()), Str(literal) => { use roc_parse::ast::StrLiteral::*; @@ -261,8 +261,11 @@ impl<'a> Formattable<'a> for Expr<'a> { fmt_if(buf, loc_condition, loc_then, loc_else, indent); } When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), - List(loc_items) => { - fmt_list(buf, &loc_items, indent); + List { + items, + final_comments, + } => { + fmt_list(buf, &items, final_comments, indent); } BinOp((loc_left_side, bin_op, loc_right_side)) => fmt_bin_op( buf, @@ -396,92 +399,88 @@ fn fmt_bin_op<'a>( } } -pub fn fmt_list<'a>(buf: &mut String<'a>, loc_items: &[&Located>], indent: u16) { - buf.push('['); - - let mut iter = loc_items.iter().peekable(); - - let is_multiline = loc_items.iter().any(|item| (&item.value).is_multiline()); - - let item_indent = if is_multiline { - indent + INDENT +pub fn fmt_list<'a>( + buf: &mut String<'a>, + loc_items: &[&Located>], + final_comments: &'a [CommentOrNewline<'a>], + indent: u16, +) { + if loc_items.is_empty() && final_comments.iter().all(|c| c.is_newline()) { + buf.push_str("[]"); } else { - indent - }; - - while let Some(item) = iter.next() { + buf.push('['); + let is_multiline = loc_items.iter().any(|item| (&item.value).is_multiline()); if is_multiline { - match &item.value { - Expr::SpaceBefore(expr_below, spaces_above_expr) => { - newline(buf, item_indent); - fmt_comments_only( - buf, - spaces_above_expr.iter(), - NewlineAt::Bottom, - item_indent, - ); + let item_indent = indent + INDENT; + for item in loc_items.iter() { + match &item.value { + // TODO?? These SpaceAfter/SpaceBefore litany seems overcomplicated + // Can we simplify this? Or at least move this in a separate function. + Expr::SpaceBefore(expr_below, spaces_above_expr) => { + newline(buf, item_indent); + fmt_comments_only( + buf, + spaces_above_expr.iter(), + NewlineAt::Bottom, + item_indent, + ); - match &expr_below { - Expr::SpaceAfter(expr_above, spaces_below_expr) => { - expr_above.format(buf, item_indent); - - if iter.peek().is_some() { + match &expr_below { + Expr::SpaceAfter(expr_above, spaces_below_expr) => { + expr_above.format(buf, item_indent); buf.push(','); - } - fmt_comments_only( - buf, - spaces_below_expr.iter(), - NewlineAt::Top, - item_indent, - ); - } - _ => { - expr_below.format(buf, item_indent); - if iter.peek().is_some() { + fmt_comments_only( + buf, + spaces_below_expr.iter(), + NewlineAt::Top, + item_indent, + ); + } + _ => { + expr_below.format(buf, item_indent); buf.push(','); } } } - } - Expr::SpaceAfter(sub_expr, spaces) => { - newline(buf, item_indent); + Expr::SpaceAfter(sub_expr, spaces) => { + newline(buf, item_indent); - sub_expr.format(buf, item_indent); - - if iter.peek().is_some() { + sub_expr.format(buf, item_indent); buf.push(','); + + fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, item_indent); } - fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, item_indent); - } - - _ => { - newline(buf, item_indent); - item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, item_indent); - if iter.peek().is_some() { + _ => { + newline(buf, item_indent); + item.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + item_indent, + ); buf.push(','); } } } + fmt_comments_only(buf, final_comments.iter(), NewlineAt::Top, item_indent); + newline(buf, indent); + buf.push(']'); } else { - buf.push(' '); - item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, item_indent); - if iter.peek().is_some() { - buf.push(','); + // is_multiline == false + let mut iter = loc_items.iter().peekable(); + while let Some(item) = iter.next() { + buf.push(' '); + item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + if iter.peek().is_some() { + buf.push(','); + } } + buf.push_str(" ]"); } } - - if is_multiline { - newline(buf, indent); - } - - if !loc_items.is_empty() && !is_multiline { - buf.push(' '); - } - buf.push(']'); } pub fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { @@ -809,7 +808,7 @@ pub fn fmt_record<'a>( final_comments: &'a [CommentOrNewline<'a>], indent: u16, ) { - if loc_fields.is_empty() { + if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) { buf.push_str("{}"); } else { buf.push('{'); @@ -846,7 +845,7 @@ pub fn fmt_record<'a>( newline(buf, indent); } else { - // is_multiline == false */ + // is_multiline == false buf.push(' '); let field_indent = indent; let mut iter = loc_fields.iter().peekable(); diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 5daac4b348..bd99d58dd2 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -1022,7 +1022,7 @@ mod test_fmt { [ 7, 8, - 9 + 9, ] "# )); @@ -1041,7 +1041,7 @@ mod test_fmt { [ 17, 18, - 19 + 19, ] "# ), @@ -1064,7 +1064,7 @@ mod test_fmt { [ 27, 28, - 29 + 29, ] "# ), @@ -1084,7 +1084,7 @@ mod test_fmt { [ 157, 158, - 159 + 159, ] "# ), @@ -1105,7 +1105,7 @@ mod test_fmt { 557, 648, 759, - 837 + 837, ] "# ), @@ -1127,7 +1127,7 @@ mod test_fmt { 257, 358, # Hey! - 459 + 459, ] "# ), @@ -1155,7 +1155,7 @@ mod test_fmt { 37, # Thirty Eight 38, - 39 + 39, ] "# ), @@ -1189,7 +1189,32 @@ mod test_fmt { 48, # Bottom 48 # Top 49 - 49 + 49, + # Bottom 49 + # 49! + ] + "# + ), + ); + } + #[test] + fn ending_comments_in_list() { + expr_formats_to( + indoc!( + r#" + [ # Top 49 + 49 + # Bottom 49 + , + # 49! + ] + "# + ), + indoc!( + r#" + [ + # Top 49 + 49, # Bottom 49 # 49! ] @@ -1197,7 +1222,6 @@ mod test_fmt { ), ); } - #[test] fn multi_line_list_def() { // expr_formats_same(indoc!( @@ -1228,7 +1252,7 @@ mod test_fmt { results = [ Ok 4, - Ok 5 + Ok 5, ] allOks results @@ -1271,6 +1295,27 @@ mod test_fmt { expr_formats_same("{}"); } + #[test] + fn empty_record_with_comment() { + expr_formats_same(indoc!( + r#" + { + # comment + }"# + )); + } + + #[test] + fn empty_record_with_newline() { + expr_formats_to( + indoc!( + r#" + { + }"# + ), + "{}", + ); + } #[test] fn one_field() { expr_formats_same("{ x: 4 }"); @@ -2407,22 +2452,54 @@ mod test_fmt { )); } - // TODO This raises a parse error: - // NotYetImplemented("TODO the : in this declaration seems outdented") - // #[test] - // fn multiline_tag_union_annotation() { - // expr_formats_same(indoc!( - // r#" - // b : - // [ - // True, - // False, - // ] + #[test] + fn multiline_tag_union_annotation() { + expr_formats_same(indoc!( + r#" + b : + [ + True, + False, + ] - // b - // "# - // )); - // } + b + "# + )); + } + + #[test] + fn multiline_tag_union_annotation_with_final_comment() { + expr_formats_to( + indoc!( + r#" + b : + [ + True, + # comment 1 + False # comment 2 + , + # comment 3 + ] + + b + "# + ), + indoc!( + r#" + b : + [ + True, + # comment 1 + False, + # comment 2 + # comment 3 + ] + + b + "# + ), + ); + } #[test] fn tag_union() { @@ -2436,6 +2513,28 @@ mod test_fmt { )); } + // TODO: the current formatting seems a bit odd for multiline function annotations + // (beside weird indentation, note the trailing space after the "->") + // #[test] + // fn multiline_tag_union_function_annotation() { + // expr_formats_same(indoc!( + // r#" + // f : + // [ + // True, + // False, + // ] -> + // [ + // True, + // False, + // ] + // f = \x -> x + + // a + // "# + // )); + // } + #[test] fn recursive_tag_union() { expr_formats_same(indoc!( diff --git a/compiler/gen/src/run_roc.rs b/compiler/gen/src/run_roc.rs index 024a5198ca..f5e75c4b1a 100644 --- a/compiler/gen/src/run_roc.rs +++ b/compiler/gen/src/run_roc.rs @@ -29,7 +29,7 @@ impl Into> for RocCallResult { #[macro_export] macro_rules! run_jit_function { ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ - let v: std::vec::Vec = std::vec::Vec::new(); + let v: String = String::new(); run_jit_function!($lib, $main_fn_name, $ty, $transform, v) }}; @@ -52,12 +52,7 @@ macro_rules! run_jit_function { match result.assume_init().into() { Ok(success) => { // only if there are no exceptions thrown, check for errors - assert_eq!( - $errors, - std::vec::Vec::new(), - "Encountered errors: {:?}", - $errors - ); + assert!($errors.is_empty(), "Encountered errors:\n{}", $errors); $transform(success) } @@ -73,7 +68,7 @@ macro_rules! run_jit_function { #[macro_export] macro_rules! run_jit_function_dynamic_type { ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{ - let v: std::vec::Vec = std::vec::Vec::new(); + let v: String = String::new(); run_jit_function_dynamic_type!($lib, $main_fn_name, $bytes, $transform, v) }}; diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs index 56c39caef0..b30b4cfa4e 100644 --- a/compiler/gen/tests/gen_primitives.rs +++ b/compiler/gen/tests/gen_primitives.rs @@ -1748,4 +1748,59 @@ mod gen_primitives { u8 ); } + + #[test] + #[should_panic( + expected = "Roc failed with message: \"Shadowing { original_region: |L 3-3, C 4-5|, shadow: |L 6-6, C 8-9| Ident(\\\"x\\\") }\"" + )] + fn pattern_shadowing() { + assert_evals_to!( + indoc!( + r#" + x = 4 + + when 4 is + x -> x + "# + ), + 0, + i64 + ); + } + + #[test] + #[should_panic(expected = "TODO non-exhaustive pattern")] + fn non_exhaustive_pattern_let() { + assert_evals_to!( + indoc!( + r#" + x : Result I64 F64 + x = Ok 4 + + (Ok y) = x + + y + "# + ), + 0, + i64 + ); + } + + #[test] + #[ignore] + #[should_panic(expected = "")] + fn unsupported_pattern_str_interp() { + assert_evals_to!( + indoc!( + r#" + { x: 4 } = { x : 4 } + + x + "# + ), + 0, + i64 + ); + } } diff --git a/compiler/gen/tests/gen_records.rs b/compiler/gen/tests/gen_records.rs index b744222235..bed2cad4ea 100644 --- a/compiler/gen/tests/gen_records.rs +++ b/compiler/gen/tests/gen_records.rs @@ -561,7 +561,9 @@ mod gen_records { } #[test] + #[ignore] fn optional_field_function_no_use_default() { + // blocked on https://github.com/rtfeldman/roc/issues/786 assert_evals_to!( indoc!( r#" @@ -579,7 +581,9 @@ mod gen_records { } #[test] + #[ignore] fn optional_field_function_no_use_default_nested() { + // blocked on https://github.com/rtfeldman/roc/issues/786 assert_evals_to!( indoc!( r#" diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index fe22eebc85..eeb7117e13 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -22,7 +22,7 @@ pub fn helper<'a>( stdlib: roc_builtins::std::StdLib, leak: bool, context: &'a inkwell::context::Context, -) -> (&'static str, Vec, Library) { +) -> (&'static str, String, Library) { use roc_gen::llvm::build::{build_proc, build_proc_header, Scope}; use std::path::{Path, PathBuf}; @@ -64,11 +64,14 @@ pub fn helper<'a>( debug_assert_eq!(exposed_to_host.len(), 1); let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap(); - let (_, main_fn_layout) = procedures - .keys() - .find(|(s, _)| *s == main_fn_symbol) - .unwrap() - .clone(); + let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { + Some(found) => found.clone(), + None => panic!( + "The main function symbol {:?} does not have a procedure in {:?}", + main_fn_symbol, + &procedures.keys() + ), + }; let target = target_lexicon::Triple::host(); let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; @@ -106,14 +109,14 @@ pub fn helper<'a>( | UnusedArgument(_, _, _) | UnusedImport(_, _) | RuntimeError(_) + | UnsupportedPattern(_, _) | ExposedButNotDefined(_) => { - delayed_errors.push(problem.clone()); - let report = can_problem(&alloc, module_path.clone(), problem); let mut buf = String::new(); report.render_color_terminal(&mut buf, &alloc, &palette); + delayed_errors.push(buf.clone()); lines.push(buf); } _ => { @@ -142,6 +145,7 @@ pub fn helper<'a>( report.render_color_terminal(&mut buf, &alloc, &palette); + delayed_errors.push(buf.clone()); lines.push(buf); } } @@ -283,7 +287,7 @@ pub fn helper<'a>( let lib = module_to_dylib(&env.module, &target, opt_level) .expect("Error loading compiled dylib for test"); - (main_fn_name, delayed_errors, lib) + (main_fn_name, delayed_errors.join("\n"), lib) } // TODO this is almost all code duplication with assert_llvm_evals_to diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index ec9fff2c5f..2f8580bf67 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -379,7 +379,7 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V match pattern { // TODO use guard! - Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => { + Identifier(_) | Underscore => { if let Guard::Guard { symbol, id, stmt } = guard { all_tests.push(Guarded { opt_test: None, @@ -408,12 +408,9 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V DestructType::Guard(guard) => { arguments.push((guard.clone(), destruct.layout.clone())); } - DestructType::Required => { + DestructType::Required(_) => { arguments.push((Pattern::Underscore, destruct.layout.clone())); } - DestructType::Optional(_expr) => { - // do nothing - } } } @@ -531,7 +528,7 @@ fn to_relevant_branch_help<'a>( use Test::*; match pattern { - Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => Some(branch.clone()), + Identifier(_) | Underscore => Some(branch.clone()), RecordDestructure(destructs, _) => match test { IsCtor { @@ -540,27 +537,22 @@ fn to_relevant_branch_help<'a>( .. } => { debug_assert!(test_name == &TagName::Global(RECORD_TAG_NAME.into())); - let sub_positions = destructs - .into_iter() - .filter(|destruct| !matches!(destruct.typ, DestructType::Optional(_))) - .enumerate() - .map(|(index, destruct)| { - let pattern = match destruct.typ { - DestructType::Guard(guard) => guard.clone(), - DestructType::Required => Pattern::Underscore, - DestructType::Optional(_expr) => unreachable!("because of the filter"), - }; + let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| { + let pattern = match destruct.typ { + DestructType::Guard(guard) => guard.clone(), + DestructType::Required(_) => Pattern::Underscore, + }; - ( - Path::Index { - index: index as u64, - tag_id: *tag_id, - path: Box::new(path.clone()), - }, - Guard::NoGuard, - pattern, - ) - }); + ( + Path::Index { + index: index as u64, + tag_id: *tag_id, + path: Box::new(path.clone()), + }, + Guard::NoGuard, + pattern, + ) + }); start.extend(sub_positions); start.extend(end); @@ -742,7 +734,7 @@ fn needs_tests<'a>(pattern: &Pattern<'a>) -> bool { use Pattern::*; match pattern { - Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => false, + Identifier(_) | Underscore => false, RecordDestructure(_, _) | AppliedTag { .. } diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index 70077c2199..018c448098 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -67,7 +67,7 @@ fn simplify<'a>(pattern: &crate::ir::Pattern<'a>) -> Pattern { field_names.push(destruct.label.clone()); match &destruct.typ { - DestructType::Required | DestructType::Optional(_) => patterns.push(Anything), + DestructType::Required(_) => patterns.push(Anything), DestructType::Guard(guard) => patterns.push(simplify(guard)), } } @@ -84,17 +84,6 @@ fn simplify<'a>(pattern: &crate::ir::Pattern<'a>) -> Pattern { Ctor(union, tag_id, patterns) } - Shadowed(_region, _ident) => { - // Treat as an Anything - // code-gen will make a runtime error out of the branch - Anything - } - UnsupportedPattern(_region) => { - // Treat as an Anything - // code-gen will make a runtime error out of the branch - Anything - } - AppliedTag { tag_id, arguments, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index f8138d74d5..231901d73b 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4,7 +4,7 @@ use crate::layout::{Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem}; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::{default_hasher, MutMap, MutSet}; -use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; +use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; @@ -1264,7 +1264,50 @@ fn patterns_to_when<'a>( // this must be fixed when moving exhaustiveness checking to the new canonical AST for (pattern_var, pattern) in patterns.into_iter() { let context = crate::exhaustive::Context::BadArg; - let mono_pattern = from_can_pattern(env, layout_cache, &pattern.value); + let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) { + Ok((pat, assignments)) => { + for (symbol, variable, expr) in assignments.into_iter().rev() { + if let Ok(old_body) = body { + let def = roc_can::def::Def { + annotation: None, + expr_var: variable, + loc_expr: Located::at(pattern.region, expr), + loc_pattern: Located::at( + pattern.region, + roc_can::pattern::Pattern::Identifier(symbol), + ), + pattern_vars: std::iter::once((symbol, variable)).collect(), + }; + let new_expr = roc_can::expr::Expr::LetNonRec( + Box::new(def), + Box::new(old_body), + variable, + ); + let new_body = Located { + region: pattern.region, + value: new_expr, + }; + + body = Ok(new_body); + } + } + + pat + } + Err(runtime_error) => { + // Even if the body was Ok, replace it with this Err. + // If it was already an Err, leave it at that Err, so the first + // RuntimeError we encountered remains the first. + body = body.and({ + Err(Located { + region: pattern.region, + value: runtime_error, + }) + }); + + continue; + } + }; match crate::exhaustive::check( pattern.region, @@ -2392,7 +2435,14 @@ pub fn with_hole<'a>( } } else { // this may be a destructure pattern - let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value); + let (mono_pattern, assignments) = + match from_can_pattern(env, layout_cache, &def.loc_pattern.value) { + Ok(v) => v, + Err(_runtime_error) => { + // todo + panic!(); + } + }; let context = crate::exhaustive::Context::BadDestruct; match crate::exhaustive::check( @@ -2412,6 +2462,14 @@ pub fn with_hole<'a>( // return Stmt::RuntimeError("TODO non-exhaustive pattern"); } + let mut hole = hole; + + for (symbol, variable, expr) in assignments { + let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole); + + hole = env.arena.alloc(stmt); + } + // convert the continuation let mut stmt = with_hole( env, @@ -3923,13 +3981,23 @@ pub fn from_can<'a>( } // this may be a destructure pattern - let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value); + let (mono_pattern, assignments) = + match from_can_pattern(env, layout_cache, &def.loc_pattern.value) { + Ok(v) => v, + Err(_) => todo!(), + }; if let Pattern::Identifier(symbol) = mono_pattern { - let hole = + let mut hole = env.arena .alloc(from_can(env, variable, cont.value, procs, layout_cache)); + for (symbol, variable, expr) in assignments { + let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole); + + hole = env.arena.alloc(stmt); + } + with_hole( env, def.loc_expr.value, @@ -3954,13 +4022,20 @@ pub fn from_can<'a>( for error in errors { env.problems.push(MonoProblem::PatternProblem(error)) } - } // TODO make all variables bound in the pattern evaluate to a runtime error - // return Stmt::RuntimeError("TODO non-exhaustive pattern"); + + return Stmt::RuntimeError("TODO non-exhaustive pattern"); + } } // convert the continuation let mut stmt = from_can(env, variable, cont.value, procs, layout_cache); + // layer on any default record fields + for (symbol, variable, expr) in assignments { + let hole = env.arena.alloc(stmt); + stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole); + } + if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value { store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) .unwrap() @@ -4015,19 +4090,51 @@ fn to_opt_branches<'a>( }; for loc_pattern in when_branch.patterns { - let mono_pattern = from_can_pattern(env, layout_cache, &loc_pattern.value); + match from_can_pattern(env, layout_cache, &loc_pattern.value) { + Ok((mono_pattern, assignments)) => { + loc_branches.push(( + Located::at(loc_pattern.region, mono_pattern.clone()), + exhaustive_guard.clone(), + )); - loc_branches.push(( - Located::at(loc_pattern.region, mono_pattern.clone()), - exhaustive_guard.clone(), - )); + let mut loc_expr = when_branch.value.clone(); + let region = loc_pattern.region; + for (symbol, variable, expr) in assignments.into_iter().rev() { + let def = roc_can::def::Def { + annotation: None, + expr_var: variable, + loc_expr: Located::at(region, expr), + loc_pattern: Located::at( + region, + roc_can::pattern::Pattern::Identifier(symbol), + ), + pattern_vars: std::iter::once((symbol, variable)).collect(), + }; + let new_expr = roc_can::expr::Expr::LetNonRec( + Box::new(def), + Box::new(loc_expr), + variable, + ); + loc_expr = Located::at(region, new_expr); + } - // TODO remove clone? - opt_branches.push(( - mono_pattern, - when_branch.guard.clone(), - when_branch.value.value.clone(), - )); + // TODO remove clone? + opt_branches.push((mono_pattern, when_branch.guard.clone(), loc_expr.value)); + } + Err(runtime_error) => { + loc_branches.push(( + Located::at(loc_pattern.region, Pattern::Underscore), + exhaustive_guard.clone(), + )); + + // TODO remove clone? + opt_branches.push(( + Pattern::Underscore, + when_branch.guard.clone(), + roc_can::expr::Expr::RuntimeError(runtime_error), + )); + } + } } } @@ -4658,14 +4765,6 @@ fn store_pattern<'a>( RecordDestructure(_, _) => { unreachable!("a record destructure must always occur on a struct layout"); } - - Shadowed(_region, _ident) => { - return Err(&"shadowed"); - } - - UnsupportedPattern(_region) => { - return Err(&"unsupported pattern"); - } } Ok(stmt) @@ -4695,25 +4794,14 @@ fn store_record_destruct<'a>( }; match &destruct.typ { - DestructType::Required => { + DestructType::Required(symbol) => { stmt = Stmt::Let( - destruct.symbol, + *symbol, load, destruct.layout.clone(), env.arena.alloc(stmt), ); } - DestructType::Optional(expr) => { - stmt = with_hole( - env, - expr.clone(), - destruct.variable, - procs, - layout_cache, - destruct.symbol, - env.arena.alloc(stmt), - ); - } DestructType::Guard(guard_pattern) => match &guard_pattern { Identifier(symbol) => { stmt = Stmt::Let( @@ -5429,11 +5517,6 @@ pub enum Pattern<'a> { layout: Layout<'a>, union: crate::exhaustive::Union, }, - - // Runtime Exceptions - Shadowed(Region, Located), - // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! - UnsupportedPattern(Region), } #[derive(Clone, Debug, PartialEq)] @@ -5441,14 +5524,12 @@ pub struct RecordDestruct<'a> { pub label: Lowercase, pub variable: Variable, pub layout: Layout<'a>, - pub symbol: Symbol, pub typ: DestructType<'a>, } #[derive(Clone, Debug, PartialEq)] pub enum DestructType<'a> { - Required, - Optional(roc_can::expr::Expr), + Required(Symbol), Guard(Pattern<'a>), } @@ -5459,27 +5540,51 @@ pub struct WhenBranch<'a> { pub guard: Option>, } -pub fn from_can_pattern<'a>( +#[allow(clippy::type_complexity)] +fn from_can_pattern<'a>( env: &mut Env<'a, '_>, layout_cache: &mut LayoutCache<'a>, can_pattern: &roc_can::pattern::Pattern, -) -> Pattern<'a> { +) -> Result< + ( + Pattern<'a>, + Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, + ), + RuntimeError, +> { + let mut assignments = Vec::new_in(env.arena); + let pattern = from_can_pattern_help(env, layout_cache, can_pattern, &mut assignments)?; + + Ok((pattern, assignments)) +} + +fn from_can_pattern_help<'a>( + env: &mut Env<'a, '_>, + layout_cache: &mut LayoutCache<'a>, + can_pattern: &roc_can::pattern::Pattern, + assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, +) -> Result, RuntimeError> { use roc_can::pattern::Pattern::*; + match can_pattern { - Underscore => Pattern::Underscore, - Identifier(symbol) => Pattern::Identifier(*symbol), - IntLiteral(v) => Pattern::IntLiteral(*v), - FloatLiteral(v) => Pattern::FloatLiteral(f64::to_bits(*v)), - StrLiteral(v) => Pattern::StrLiteral(v.clone()), - Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()), - UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region), + Underscore => Ok(Pattern::Underscore), + Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), + IntLiteral(v) => Ok(Pattern::IntLiteral(*v)), + FloatLiteral(v) => Ok(Pattern::FloatLiteral(f64::to_bits(*v))), + StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), + Shadowed(region, ident) => Err(RuntimeError::Shadowing { + original_region: *region, + shadow: ident.clone(), + }), + UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)), + MalformedPattern(_problem, region) => { // TODO preserve malformed problem information here? - Pattern::UnsupportedPattern(*region) + Err(RuntimeError::UnsupportedPattern(*region)) } NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) { - IntOrFloat::IntType => Pattern::IntLiteral(*num), - IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64), + IntOrFloat::IntType => Ok(Pattern::IntLiteral(*num)), + IntOrFloat::FloatType => Ok(Pattern::FloatLiteral(*num as u64)), }, AppliedTag { @@ -5493,7 +5598,7 @@ pub fn from_can_pattern<'a>( let variant = crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs); - match variant { + let result = match variant { Never => unreachable!( "there is no pattern of type `[]`, union var {:?}", *whole_var @@ -5582,7 +5687,7 @@ pub fn from_can_pattern<'a>( let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) { mono_args.push(( - from_can_pattern(env, layout_cache, &loc_pat.value), + from_can_pattern_help(env, layout_cache, &loc_pat.value, assignments)?, layout.clone(), )); } @@ -5642,7 +5747,7 @@ pub fn from_can_pattern<'a>( let it = argument_layouts[1..].iter(); for ((_, loc_pat), layout) in arguments.iter().zip(it) { mono_args.push(( - from_can_pattern(env, layout_cache, &loc_pat.value), + from_can_pattern_help(env, layout_cache, &loc_pat.value, assignments)?, layout.clone(), )); } @@ -5664,7 +5769,9 @@ pub fn from_can_pattern<'a>( layout, } } - } + }; + + Ok(result) } RecordDestructure { @@ -5704,14 +5811,14 @@ pub fn from_can_pattern<'a>( layout_cache, &destruct.value, field_layout.clone(), - )); + assignments, + )?); } None => { // this field is not destructured by the pattern // put in an underscore mono_destructs.push(RecordDestruct { label: label.clone(), - symbol: env.unique_symbol(), variable, layout: field_layout.clone(), typ: DestructType::Guard(Pattern::Underscore), @@ -5727,29 +5834,26 @@ pub fn from_can_pattern<'a>( match destructs_by_label.remove(&label) { Some(destruct) => { // this field is destructured by the pattern - mono_destructs.push(RecordDestruct { - label: destruct.value.label.clone(), - symbol: destruct.value.symbol, - layout: field_layout, - variable, - typ: match &destruct.value.typ { - roc_can::pattern::DestructType::Optional(_, loc_expr) => { - // if we reach this stage, the optional field is not present - // so use the default - DestructType::Optional(loc_expr.value.clone()) - } - _ => unreachable!( - "only optional destructs can be optional fields" - ), - }, - }); + match &destruct.value.typ { + roc_can::pattern::DestructType::Optional(_, loc_expr) => { + // if we reach this stage, the optional field is not present + // so we push the default assignment into the branch + assignments.push(( + destruct.value.symbol, + variable, + loc_expr.value.clone(), + )); + } + _ => unreachable!( + "only optional destructs can be optional fields" + ), + }; } None => { // this field is not destructured by the pattern // put in an underscore mono_destructs.push(RecordDestruct { label: label.clone(), - symbol: env.unique_symbol(), variable, layout: field_layout.clone(), typ: DestructType::Guard(Pattern::Underscore), @@ -5765,28 +5869,30 @@ pub fn from_can_pattern<'a>( // it must be an optional field, and we will use the default match &destruct.value.typ { roc_can::pattern::DestructType::Optional(field_var, loc_expr) => { - let field_layout = layout_cache - .from_var(env.arena, *field_var, env.subs) - .unwrap_or_else(|err| { - panic!("TODO turn fn_var into a RuntimeError {:?}", err) - }); - - mono_destructs.push(RecordDestruct { - label: destruct.value.label.clone(), - symbol: destruct.value.symbol, - variable: destruct.value.var, - layout: field_layout, - typ: DestructType::Optional(loc_expr.value.clone()), - }) + // TODO these don't match up in the uniqueness inference; when we remove + // that, reinstate this assert! + // + // dbg!(&env.subs.get_without_compacting(*field_var).content); + // dbg!(&env.subs.get_without_compacting(destruct.value.var).content); + // debug_assert_eq!( + // env.subs.get_root_key_without_compacting(*field_var), + // env.subs.get_root_key_without_compacting(destruct.value.var) + // ); + assignments.push(( + destruct.value.symbol, + // destruct.value.var, + *field_var, + loc_expr.value.clone(), + )); } _ => unreachable!("only optional destructs can be optional fields"), } } - Pattern::RecordDestructure( + Ok(Pattern::RecordDestructure( mono_destructs, Layout::Struct(field_layouts.into_bump_slice()), - ) + )) } } } @@ -5796,24 +5902,23 @@ fn from_can_record_destruct<'a>( layout_cache: &mut LayoutCache<'a>, can_rd: &roc_can::pattern::RecordDestruct, field_layout: Layout<'a>, -) -> RecordDestruct<'a> { - RecordDestruct { + assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, +) -> Result, RuntimeError> { + Ok(RecordDestruct { label: can_rd.label.clone(), - symbol: can_rd.symbol, variable: can_rd.var, layout: field_layout, typ: match &can_rd.typ { - roc_can::pattern::DestructType::Required => DestructType::Required, + roc_can::pattern::DestructType::Required => DestructType::Required(can_rd.symbol), roc_can::pattern::DestructType::Optional(_, _) => { // if we reach this stage, the optional field is present - // DestructType::Optional(loc_expr.value.clone()) - DestructType::Required - } - roc_can::pattern::DestructType::Guard(_, loc_pattern) => { - DestructType::Guard(from_can_pattern(env, layout_cache, &loc_pattern.value)) + DestructType::Required(can_rd.symbol) } + roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard( + from_can_pattern_help(env, layout_cache, &loc_pattern.value, assignments)?, + ), }, - } + }) } /// Potentially translate LowLevel operations into more efficient ones based on diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index d707775ab9..d4961543e0 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -93,7 +93,10 @@ pub enum Expr<'a> { AccessorFunction(&'a str), // Collection Literals - List(&'a [&'a Loc>]), + List { + items: &'a [&'a Loc>], + final_comments: &'a [CommentOrNewline<'a>], + }, Record { update: Option<&'a Loc>>, @@ -300,6 +303,15 @@ impl<'a> CommentOrNewline<'a> { DocComment(_) => true, } } + + pub fn is_newline(&self) -> bool { + use CommentOrNewline::*; + match self { + Newline => true, + LineComment(_) => false, + DocComment(_) => false, + } + } } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index ee66a5bc9c..f584b16d33 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -306,7 +306,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result, // These would not have parsed as patterns Expr::AccessorFunction(_) | Expr::Access(_, _) - | Expr::List(_) + | Expr::List { .. } | Expr::Closure(_, _) | Expr::BinOp(_) | Expr::Defs(_, _) @@ -1848,7 +1848,7 @@ fn binop<'a>() -> impl Parser<'a, BinOp> { } pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { - let elems = collection!( + let elems = collection_trailing_sep!( ascii_char(b'['), loc!(expr(min_indent)), ascii_char(b','), @@ -1858,14 +1858,21 @@ pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { parser::attempt( Attempting::List, - map_with_arena!(elems, |arena, parsed_elems: Vec<'a, Located>>| { + map_with_arena!(elems, |arena, + (parsed_elems, final_comments): ( + Vec<'a, Located>>, + &'a [CommentOrNewline<'a>] + )| { let mut allocated = Vec::with_capacity_in(parsed_elems.len(), arena); for parsed_elem in parsed_elems { allocated.push(&*arena.alloc(parsed_elem)); } - Expr::List(allocated.into_bump_slice()) + Expr::List { + items: allocated.into_bump_slice(), + final_comments, + } }), ) } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 5f07bc73aa..cbfb86db3f 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -1017,8 +1017,10 @@ mod test_parse { #[test] fn empty_list() { let arena = Bump::new(); - let elems = &[]; - let expected = List(elems); + let expected = List { + items: &[], + final_comments: &[], + }; let actual = parse_expr_with(&arena, "[]"); assert_eq!(Ok(expected), actual); @@ -1028,8 +1030,10 @@ mod test_parse { fn spaces_inside_empty_list() { // This is a regression test! let arena = Bump::new(); - let elems = &[]; - let expected = List(elems); + let expected = List { + items: &[], + final_comments: &[], + }; let actual = parse_expr_with(&arena, "[ ]"); assert_eq!(Ok(expected), actual); @@ -1038,8 +1042,11 @@ mod test_parse { #[test] fn packed_singleton_list() { let arena = Bump::new(); - let elems = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))]; - let expected = List(elems); + let items = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))]; + let expected = List { + items, + final_comments: &[], + }; let actual = parse_expr_with(&arena, "[1]"); assert_eq!(Ok(expected), actual); @@ -1048,8 +1055,11 @@ mod test_parse { #[test] fn spaced_singleton_list() { let arena = Bump::new(); - let elems = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))]; - let expected = List(elems); + let items = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))]; + let expected = List { + items, + final_comments: &[], + }; let actual = parse_expr_with(&arena, "[ 1 ]"); assert_eq!(Ok(expected), actual); diff --git a/editor/src/def.rs b/editor/src/def.rs index 50b2f043c8..e8af973209 100644 --- a/editor/src/def.rs +++ b/editor/src/def.rs @@ -43,6 +43,28 @@ pub enum Def { Function(FunctionDef), } +impl Def { + pub fn symbols(&self, pool: &Pool) -> MutSet { + let mut output = MutSet::default(); + + match self { + Def::AnnotationOnly { .. } => todo!("lost pattern information here ... "), + Def::Value(ValueDef { pattern, .. }) => { + let pattern2 = &pool[*pattern]; + output.extend(symbols_from_pattern(pool, pattern2)); + } + Def::Function(function_def) => match function_def { + FunctionDef::NoAnnotation { name, .. } + | FunctionDef::WithAnnotation { name, .. } => { + output.insert(*name); + } + }, + } + + output + } +} + impl ShallowClone for Def { fn shallow_clone(&self) -> Self { match self { @@ -787,7 +809,6 @@ pub struct CanDefs { pub fn canonicalize_defs<'a>( env: &mut Env<'a>, mut output: Output, - var_store: &mut VarStore, original_scope: &Scope, loc_defs: &'a [&'a Located>], pattern_type: PatternType, diff --git a/editor/src/expr.rs b/editor/src/expr.rs index 8cd7df43f8..ac5d30b66b 100644 --- a/editor/src/expr.rs +++ b/editor/src/expr.rs @@ -43,7 +43,7 @@ impl Output { pub struct Env<'a> { pub home: ModuleId, - pub var_store: VarStore, + pub var_store: &'a mut VarStore, pub pool: &'a mut Pool, pub arena: &'a Bump, @@ -63,6 +63,32 @@ pub struct Env<'a> { } impl<'a> Env<'a> { + pub fn new( + home: ModuleId, + arena: &'a Bump, + pool: &'a mut Pool, + var_store: &'a mut VarStore, + dep_idents: MutMap, + module_ids: &'a ModuleIds, + exposed_ident_ids: IdentIds, + ) -> Env<'a> { + Env { + home, + arena, + pool, + var_store, + dep_idents, + module_ids, + ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later + exposed_ident_ids, + closures: MutMap::default(), + qualified_lookups: MutSet::default(), + tailcallable_symbol: None, + closure_name_symbol: None, + top_level_symbols: MutSet::default(), + } + } + pub fn add(&mut self, item: T, region: Region) -> NodeId { let id = self.pool.add(item); self.set_region(id, region); @@ -285,14 +311,14 @@ pub fn to_expr2<'a>( Str(literal) => flatten_str_literal(env, scope, &literal), - List(elements) => { + List { items, .. } => { let mut output = Output::default(); let output_ref = &mut output; - let elems = PoolVec::with_capacity(elements.len() as u32, env.pool); + let elems = PoolVec::with_capacity(items.len() as u32, env.pool); - for (node_id, element) in elems.iter_node_ids().zip(elements.iter()) { - let (expr, sub_output) = to_expr2(env, scope, &element.value, element.region); + for (node_id, item) in elems.iter_node_ids().zip(items.iter()) { + let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region); output_ref.union(sub_output); diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 5ff0e5a137..ac5e376f42 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -34,6 +34,7 @@ pub mod error; pub mod expr; pub mod file; mod keyboard_input; +mod module; mod ortho; mod pattern; pub mod pool; diff --git a/editor/src/module.rs b/editor/src/module.rs new file mode 100644 index 0000000000..73b1fc7086 --- /dev/null +++ b/editor/src/module.rs @@ -0,0 +1,317 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] +use crate::ast::{FunctionDef, ValueDef}; +use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; +use crate::expr::Env; +use crate::expr::Output; +use crate::pattern::Pattern2; +use crate::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; +use crate::scope::Scope; +use crate::types::Alias; +use bumpalo::Bump; +use roc_can::operator::desugar_def; +use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_module::ident::Ident; +use roc_module::ident::Lowercase; +use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_parse::ast; +use roc_parse::pattern::PatternType; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; +use roc_types::subs::{VarStore, Variable}; + +pub struct ModuleOutput { + pub aliases: MutMap>, + pub rigid_variables: MutMap, + pub declarations: Vec, + pub exposed_imports: MutMap, + pub lookups: Vec<(Symbol, Variable, Region)>, + pub problems: Vec, + pub ident_ids: IdentIds, + pub references: MutSet, +} + +// TODO trim these down +#[allow(clippy::too_many_arguments)] +pub fn canonicalize_module_defs<'a>( + arena: &Bump, + loc_defs: &'a [Located>], + home: ModuleId, + module_ids: &ModuleIds, + exposed_ident_ids: IdentIds, + dep_idents: MutMap, + aliases: MutMap, + exposed_imports: MutMap, + mut exposed_symbols: MutSet, + var_store: &mut VarStore, +) -> Result { + let mut pool = Pool::with_capacity(1 << 10); + let mut can_exposed_imports = MutMap::default(); + let mut scope = Scope::new(home, &mut pool, var_store); + let num_deps = dep_idents.len(); + + for (name, alias) in aliases.into_iter() { + let vars = PoolVec::with_capacity(alias.targs.len() as u32, &mut pool); + + for (node_id, targ_id) in vars.iter_node_ids().zip(alias.targs.iter_node_ids()) { + let (poolstr, var) = &pool[targ_id]; + pool[node_id] = (poolstr.shallow_clone(), *var); + } + scope.add_alias(&mut pool, name, vars, alias.actual); + } + + // Desugar operators (convert them to Apply calls, taking into account + // operator precedence and associativity rules), before doing other canonicalization. + // + // If we did this *during* canonicalization, then each time we + // visited a BinOp node we'd recursively try to apply this to each of its nested + // operators, and then again on *their* nested operators, ultimately applying the + // rules multiple times unnecessarily. + let mut desugared = + bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena); + + for loc_def in loc_defs.iter() { + desugared.push(&*arena.alloc(Located { + value: desugar_def(arena, &loc_def.value), + region: loc_def.region, + })); + } + + let mut env = Env::new( + home, + arena, + &mut pool, + var_store, + dep_idents, + module_ids, + exposed_ident_ids, + ); + let mut lookups = Vec::with_capacity(num_deps); + let rigid_variables = MutMap::default(); + + // Exposed values are treated like defs that appear before any others, e.g. + // + // imports [ Foo.{ bar, baz } ] + // + // ...is basically the same as if we'd added these extra defs at the start of the module: + // + // bar = Foo.bar + // baz = Foo.baz + // + // Here we essentially add those "defs" to "the beginning of the module" + // by canonicalizing them right before we canonicalize the actual ast::Def nodes. + for (ident, (symbol, region)) in exposed_imports { + let first_char = ident.as_inline_str().chars().next().unwrap(); + + if first_char.is_lowercase() { + // this is a value definition + let expr_var = env.var_store.fresh(); + + match scope.import(ident, symbol, region) { + Ok(()) => { + // Add an entry to exposed_imports using the current module's name + // as the key; e.g. if this is the Foo module and we have + // exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when + // anything references `baz` in this Foo module, it will resolve to Bar.baz. + can_exposed_imports.insert(symbol, expr_var); + + // This will be used during constraint generation, + // to add the usual Lookup constraint as if this were a normal def. + lookups.push((symbol, expr_var, region)); + } + Err((_shadowed_symbol, _region)) => { + panic!("TODO gracefully handle shadowing in imports.") + } + } + } else { + // This is a type alias + + // the should already be added to the scope when this module is canonicalized + debug_assert!(scope.contains_alias(symbol)); + } + } + + let (defs, _scope, output, symbols_introduced) = canonicalize_defs( + &mut env, + Output::default(), + &scope, + &desugared, + PatternType::TopLevelDef, + ); + + // See if any of the new idents we defined went unused. + // If any were unused and also not exposed, report it. + for (symbol, region) in symbols_introduced { + if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + // TODO register rigids + // for (var, lowercase) in output.introduced_variables.name_by_var.clone() { + // rigid_variables.insert(var, lowercase); + // } + + let mut references = MutSet::default(); + + // Gather up all the symbols that were referenced across all the defs' lookups. + for symbol in output.references.lookups.iter() { + references.insert(*symbol); + } + + // Gather up all the symbols that were referenced across all the defs' calls. + for symbol in output.references.calls.iter() { + references.insert(*symbol); + } + + // Gather up all the symbols that were referenced from other modules. + for symbol in env.qualified_lookups.iter() { + references.insert(*symbol); + } + + // NOTE previously we inserted builtin defs into the list of defs here + // this is now done later, in file.rs. + + match sort_can_defs(&mut env, defs, Output::default()) { + (Ok(mut declarations), output) => { + use crate::def::Declaration::*; + + for decl in declarations.iter() { + match decl { + Declare(def) => { + for symbol in def.symbols(env.pool) { + if exposed_symbols.contains(&symbol) { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + } + } + } + DeclareRec(defs) => { + for def in defs { + for symbol in def.symbols(env.pool) { + if exposed_symbols.contains(&symbol) { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + } + } + } + } + + InvalidCycle(identifiers, _) => { + panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers); + } + Builtin(def) => { + // Builtins cannot be exposed in module declarations. + // This should never happen! + debug_assert!(def + .symbols(env.pool) + .iter() + .all(|symbol| !exposed_symbols.contains(symbol))); + } + } + } + + let mut aliases = MutMap::default(); + + for (symbol, alias) in output.aliases { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + + aliases.insert(symbol, alias); + } + + // By this point, all exposed symbols should have been removed from + // exposed_symbols and added to exposed_vars_by_symbol. If any were + // not, that means they were declared as exposed but there was + // no actual declaration with that name! + for symbol in exposed_symbols { + env.problem(Problem::ExposedButNotDefined(symbol)); + + // In case this exposed value is referenced by other modules, + // create a decl for it whose implementation is a runtime error. + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(symbol, env.var_store.fresh()); + + let runtime_error = RuntimeError::ExposedButNotDefined(symbol); + + let value_def = { + let pattern = env.pool.add(Pattern2::Identifier(symbol)); + ValueDef { + pattern, + expr_type: None, + expr_var: env.var_store.fresh(), + } + }; + + let def = Def::Value(value_def); + + declarations.push(Declaration::Declare(def)); + } + + // Incorporate any remaining output.lookups entries into references. + for symbol in output.references.lookups { + references.insert(symbol); + } + + // Incorporate any remaining output.calls entries into references. + for symbol in output.references.calls { + references.insert(symbol); + } + + // Gather up all the symbols that were referenced from other modules. + for symbol in env.qualified_lookups.iter() { + references.insert(*symbol); + } + + // TODO find captured variables + // for declaration in declarations.iter_mut() { + // match declaration { + // Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()), + // DeclareRec(defs) => { + // fix_values_captured_in_closure_defs(defs, &mut MutSet::default()) + // } + // InvalidCycle(_, _) | Builtin(_) => {} + // } + // } + + // TODO this loops over all symbols in the module, we can speed it up by having an + // iterator over all builtin symbols + + // TODO move over the builtins + // for symbol in references.iter() { + // if symbol.is_builtin() { + // // this can fail when the symbol is for builtin types, or has no implementation yet + // if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) { + // declarations.push(Declaration::Builtin(def)); + // } + // } + // } + + Ok(ModuleOutput { + aliases, + rigid_variables, + declarations, + references, + exposed_imports: can_exposed_imports, + problems: vec![], // TODO env.problems, + lookups, + ident_ids: env.ident_ids, + }) + } + (Err(runtime_error), _) => Err(runtime_error), + } +}