diff --git a/cli/src/build.rs b/cli/src/build.rs index a1f3add2bc..d75bf0361b 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -26,6 +26,16 @@ pub enum BuildOutcome { Errors, } +impl BuildOutcome { + pub fn status_code(&self) -> i32 { + match self { + Self::NoProblems => 0, + Self::OnlyWarnings => 1, + Self::Errors => 2, + } + } +} + pub struct BuiltFile { pub binary_path: PathBuf, pub outcome: BuildOutcome, @@ -205,10 +215,14 @@ pub fn build_file<'a>( let total_time = compilation_start.elapsed().unwrap(); // If the cmd errored out, return the Err. - cmd_result?; + let exit_status = cmd_result?; // TODO change this to report whether there were errors or warnings! - let outcome = BuildOutcome::NoProblems; + let outcome = if exit_status.success() { + BuildOutcome::NoProblems + } else { + BuildOutcome::Errors + }; Ok(BuiltFile { binary_path, diff --git a/cli/src/lib.rs b/cli/src/lib.rs index a86c3ec56c..1cd2ea0eab 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -196,13 +196,6 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io:: .strip_prefix(env::current_dir().unwrap()) .unwrap_or(&binary_path); - // Return a nonzero exit code if there were problems - let status_code = match outcome { - BuildOutcome::NoProblems => 0, - BuildOutcome::OnlyWarnings => 1, - BuildOutcome::Errors => 2, - }; - // No need to waste time freeing this memory, // since the process is about to exit anyway. std::mem::forget(arena); @@ -213,7 +206,8 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io:: total_time.as_millis() ); - Ok(status_code) + // Return a nonzero exit code if there were problems + Ok(outcome.status_code()) } BuildAndRun { roc_file_arg_index } => { let mut cmd = Command::new(binary_path); @@ -231,7 +225,10 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io:: } } - roc_run(cmd.current_dir(original_cwd)) + match outcome { + BuildOutcome::Errors => Ok(outcome.status_code()), + _ => roc_run(cmd.current_dir(original_cwd)), + } } } } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 94877e4ad0..364dafcbdf 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -3150,118 +3150,123 @@ pub fn with_hole<'a>( branches, final_else, } => { - let ret_layout = layout_cache - .from_var(env.arena, branch_var, env.subs) - .expect("invalid ret_layout"); - let cond_layout = layout_cache - .from_var(env.arena, cond_var, env.subs) - .expect("invalid cond_layout"); + match ( + layout_cache.from_var(env.arena, branch_var, env.subs), + layout_cache.from_var(env.arena, cond_var, env.subs), + ) { + (Ok(ret_layout), Ok(cond_layout)) => { + // if the hole is a return, then we don't need to merge the two + // branches together again, we can just immediately return + let is_terminated = matches!(hole, Stmt::Ret(_)); - // if the hole is a return, then we don't need to merge the two - // branches together again, we can just immediately return - let is_terminated = matches!(hole, Stmt::Ret(_)); + if is_terminated { + let terminator = hole; - if is_terminated { - let terminator = hole; + let mut stmt = with_hole( + env, + final_else.value, + branch_var, + procs, + layout_cache, + assigned, + terminator, + ); - let mut stmt = with_hole( - env, - final_else.value, - branch_var, - procs, - layout_cache, - assigned, - terminator, - ); + for (loc_cond, loc_then) in branches.into_iter().rev() { + let branching_symbol = env.unique_symbol(); - for (loc_cond, loc_then) in branches.into_iter().rev() { - let branching_symbol = env.unique_symbol(); + let then = with_hole( + env, + loc_then.value, + branch_var, + procs, + layout_cache, + assigned, + terminator, + ); - let then = with_hole( - env, - loc_then.value, - branch_var, - procs, - layout_cache, - assigned, - terminator, - ); + stmt = cond(env, branching_symbol, cond_layout, then, stmt, ret_layout); - stmt = cond(env, branching_symbol, cond_layout, then, stmt, ret_layout); + // add condition + stmt = with_hole( + env, + loc_cond.value, + cond_var, + procs, + layout_cache, + branching_symbol, + env.arena.alloc(stmt), + ); + } + stmt + } else { + let assigned_in_jump = env.unique_symbol(); + let id = JoinPointId(env.unique_symbol()); - // add condition - stmt = with_hole( - env, - loc_cond.value, - cond_var, - procs, - layout_cache, - branching_symbol, - env.arena.alloc(stmt), - ); - } - stmt - } else { - let assigned_in_jump = env.unique_symbol(); - let id = JoinPointId(env.unique_symbol()); - - let terminator = env - .arena - .alloc(Stmt::Jump(id, env.arena.alloc([assigned_in_jump]))); - - let mut stmt = with_hole( - env, - final_else.value, - branch_var, - procs, - layout_cache, - assigned_in_jump, - terminator, - ); - - for (loc_cond, loc_then) in branches.into_iter().rev() { - let branching_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); - - let then = with_hole( - env, - loc_then.value, - branch_var, - procs, - layout_cache, - assigned_in_jump, - terminator, - ); - - stmt = cond(env, branching_symbol, cond_layout, then, stmt, ret_layout); - - // add condition - stmt = assign_to_symbol( - env, - procs, - layout_cache, - cond_var, - loc_cond, - branching_symbol, - stmt, - ); - } - - let layout = layout_cache - .from_var(env.arena, branch_var, env.subs) - .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); - - let param = Param { - symbol: assigned, - layout, - borrow: false, - }; - - Stmt::Join { - id, - parameters: env.arena.alloc([param]), - remainder: env.arena.alloc(stmt), - body: hole, + let terminator = env + .arena + .alloc(Stmt::Jump(id, env.arena.alloc([assigned_in_jump]))); + + let mut stmt = with_hole( + env, + final_else.value, + branch_var, + procs, + layout_cache, + assigned_in_jump, + terminator, + ); + + for (loc_cond, loc_then) in branches.into_iter().rev() { + let branching_symbol = + possible_reuse_symbol(env, procs, &loc_cond.value); + + let then = with_hole( + env, + loc_then.value, + branch_var, + procs, + layout_cache, + assigned_in_jump, + terminator, + ); + + stmt = cond(env, branching_symbol, cond_layout, then, stmt, ret_layout); + + // add condition + stmt = assign_to_symbol( + env, + procs, + layout_cache, + cond_var, + loc_cond, + branching_symbol, + stmt, + ); + } + + let layout = layout_cache + .from_var(env.arena, branch_var, env.subs) + .unwrap_or_else(|err| { + panic!("TODO turn fn_var into a RuntimeError {:?}", err) + }); + + let param = Param { + symbol: assigned, + layout, + borrow: false, + }; + + Stmt::Join { + id, + parameters: env.arena.alloc([param]), + remainder: env.arena.alloc(stmt), + body: hole, + } + } } + (Err(_), _) => Stmt::RuntimeError("invalid ret_layout"), + (_, Err(_)) => Stmt::RuntimeError("invalid cond_layout"), } } @@ -4126,17 +4131,17 @@ fn convert_tag_union<'a>( hole, ), ByteUnion(tag_names) => { - let tag_id = tag_names - .iter() - .position(|key| key == &tag_name) - .expect("tag must be in its own type"); + let opt_tag_id = tag_names.iter().position(|key| key == &tag_name); - Stmt::Let( - assigned, - Expr::Literal(Literal::Byte(tag_id as u8)), - Layout::Builtin(Builtin::Int8), - hole, - ) + match opt_tag_id { + Some(tag_id) => Stmt::Let( + assigned, + Expr::Literal(Literal::Byte(tag_id as u8)), + Layout::Builtin(Builtin::Int8), + hole, + ), + None => Stmt::RuntimeError("tag must be in its own type"), + } } Newtype { diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index b6384a8ba5..9a964c3213 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1939,3 +1939,23 @@ fn list_sort_with() { RocList ); } + +#[test] +#[should_panic(expected = r#"Roc failed with message: "invalid ret_layout""#)] +fn lists_with_incompatible_type_param_in_if() { + assert_evals_to!( + indoc!( + r#" + list1 = [ {} ] + + list2 = [ "" ] + + x = if True then list1 else list2 + + "" + "# + ), + RocStr::empty(), + RocStr + ); +} diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index 776c06ff07..cfd11b807f 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1033,3 +1033,20 @@ fn applied_tag_function_linked_list() { i64 ); } + +#[test] +#[should_panic(expected = "")] +fn tag_must_be_its_own_type() { + assert_evals_to!( + indoc!( + r#" + z : [ A, B, C ] + z = Z + + z + "# + ), + 1, + i64 + ); +} diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index 547d120f05..1356c919ab 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -88,7 +88,27 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * Show edit history for this function. * Adjusting settings: switch to light theme, increase font size... * Use (context specific) voice command state machine to assist Machine Learning voice recognition model. -* Nice special use case: using voice to code while on treadmill desk. +* Nice special use case: using voice to code while on treadmill desk. +* Use word embeddings to find most similar voice command to recorded input in vector space. + +#### Useful voice commands + +* clear all breakpoints +* increase/decrease font size +* switch to dark/light/high-contrast mode +* open/go to file "Main"(fuzzy matching) +* go to function "foo" +* go to definition +* show all references(uses) of this function/type/... +* show history timeline of this function/file +* show recent projects +* generate unit test for this function +* generate unit test for this function based on debug trace (input and output is recorded and used in test) +* who wrote this line (git blame integration) +* search documentation of library X for Foo +* show example of how to use library function Foo +* open google/github/duckduckgo search for error... +* show editor plugins for library X #### Inspiration diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs index 8faf0cbf4b..ed8c8586ec 100644 --- a/editor/src/editor/markup/nodes.rs +++ b/editor/src/editor/markup/nodes.rs @@ -349,6 +349,12 @@ pub fn expr2_to_markup<'a, 'b>( syn_high_style: HighlightStyle::Blank, parent_id_opt: None, }), + Expr2::RuntimeError() => new_markup_node( + "RunTimeError".to_string(), + expr2_node_id, + HighlightStyle::Blank, + markup_node_pool, + ), rest => todo!("implement expr2_to_markup for {:?}", rest), } } diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 859f01cece..ba615e28bf 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -1048,27 +1048,27 @@ pub mod test_ed_update { fn test_record() -> Result<(), String> { assert_insert(&["┃"], &["{ ┃ }"], '{')?; assert_insert(&["{ ┃ }"], &["{ a┃ }"], 'a')?; - assert_insert(&["{ a┃ }"], &["{ ab┃ }"], 'b')?; - assert_insert(&["{ a┃ }"], &["{ a1┃ }"], '1')?; - assert_insert(&["{ a1┃ }"], &["{ a1z┃ }"], 'z')?; - assert_insert(&["{ a1┃ }"], &["{ a15┃ }"], '5')?; - assert_insert(&["{ ab┃ }"], &["{ abc┃ }"], 'c')?; - assert_insert(&["{ ┃abc }"], &["{ z┃abc }"], 'z')?; - assert_insert(&["{ a┃b }"], &["{ az┃b }"], 'z')?; - assert_insert(&["{ a┃b }"], &["{ a9┃b }"], '9')?; + assert_insert(&["{ a┃ }"], &["{ ab┃: RunTimeError }"], 'b')?; + assert_insert(&["{ a┃ }"], &["{ a1┃: RunTimeError }"], '1')?; + assert_insert(&["{ a1┃ }"], &["{ a1z┃: RunTimeError }"], 'z')?; + assert_insert(&["{ a1┃ }"], &["{ a15┃: RunTimeError }"], '5')?; + assert_insert(&["{ ab┃ }"], &["{ abc┃: RunTimeError }"], 'c')?; + assert_insert(&["{ ┃abc }"], &["{ z┃abc: RunTimeError }"], 'z')?; + assert_insert(&["{ a┃b }"], &["{ az┃b: RunTimeError }"], 'z')?; + assert_insert(&["{ a┃b }"], &["{ a9┃b: RunTimeError }"], '9')?; // extra space for Blank node - assert_insert(&["{ a┃ }"], &["{ a: ┃ }"], ':')?; - assert_insert(&["{ abc┃ }"], &["{ abc: ┃ }"], ':')?; - assert_insert(&["{ aBc┃ }"], &["{ aBc: ┃ }"], ':')?; + assert_insert(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ':')?; + assert_insert(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ':')?; + assert_insert(&["{ aBc┃ }"], &["{ aBc┃: RunTimeError }"], ':')?; - assert_insert_seq(&["{ a┃ }"], &["{ a: \"┃\" }"], ":\"")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc: \"┃\" }"], ":\"")?; + assert_insert_seq(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ":\"")?; + assert_insert_seq(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ":\"")?; - assert_insert_seq(&["{ a┃ }"], &["{ a: 0┃ }"], ":0")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc: 9┃ }"], ":9")?; - assert_insert_seq(&["{ a┃ }"], &["{ a: 1000┃ }"], ":1000")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc: 98761┃ }"], ":98761")?; + assert_insert_seq(&["{ a┃ }"], &["{ a0┃: RunTimeError }"], ":0")?; + assert_insert_seq(&["{ abc┃ }"], &["{ abc9┃: RunTimeError }"], ":9")?; + assert_insert_seq(&["{ a┃ }"], &["{ a1000┃: RunTimeError }"], ":1000")?; + assert_insert_seq(&["{ abc┃ }"], &["{ abc98761┃: RunTimeError }"], ":98761")?; assert_insert(&["{ a: \"┃\" }"], &["{ a: \"a┃\" }"], 'a')?; assert_insert(&["{ a: \"a┃\" }"], &["{ a: \"ab┃\" }"], 'b')?; @@ -1124,9 +1124,9 @@ pub mod test_ed_update { #[test] fn test_nested_record() -> Result<(), String> { - assert_insert_seq(&["{ a┃ }"], &["{ a: { ┃ } }"], ":{")?; - assert_insert_seq(&["{ abc┃ }"], &["{ abc: { ┃ } }"], ":{")?; - assert_insert_seq(&["{ camelCase┃ }"], &["{ camelCase: { ┃ } }"], ":{")?; + assert_insert_seq(&["{ a┃ }"], &["{ a┃: RunTimeError }"], ":{")?; + assert_insert_seq(&["{ abc┃ }"], &["{ abc┃: RunTimeError }"], ":{")?; + assert_insert_seq(&["{ camelCase┃ }"], &["{ camelCase┃: RunTimeError }"], ":{")?; assert_insert_seq(&["{ a: { ┃ } }"], &["{ a: { zulu┃ } }"], "zulu")?; assert_insert_seq( @@ -1136,35 +1136,51 @@ pub mod test_ed_update { )?; assert_insert_seq(&["{ camelCase: { ┃ } }"], &["{ camelCase: { z┃ } }"], "z")?; - assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: ┃ } }"], ":")?; + assert_insert_seq( + &["{ a: { zulu┃ } }"], + &["{ a: { zulu┃: RunTimeError } }"], + ":", + )?; assert_insert_seq( &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase: ┃ } }"], + &["{ abc: { camelCase┃: RunTimeError } }"], ":", )?; assert_insert_seq( &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z: ┃ } }"], + &["{ camelCase: { z┃: RunTimeError } }"], ":", )?; - assert_insert_seq(&["{ a┃: { zulu } }"], &["{ a0┃: { zulu } }"], "0")?; + assert_insert_seq( + &["{ a┃: { zulu } }"], + &["{ a0┃: { zulu: RunTimeError } }"], + "0", + )?; assert_insert_seq( &["{ ab┃c: { camelCase } }"], - &["{ abz┃c: { camelCase } }"], + &["{ abz┃c: { camelCase: RunTimeError } }"], "z", )?; - assert_insert_seq(&["{ ┃camelCase: { z } }"], &["{ x┃camelCase: { z } }"], "x")?; + assert_insert_seq( + &["{ ┃camelCase: { z } }"], + &["{ x┃camelCase: { z: RunTimeError } }"], + "x", + )?; - assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: \"┃\" } }"], ":\"")?; + assert_insert_seq( + &["{ a: { zulu┃ } }"], + &["{ a: { zulu┃: RunTimeError } }"], + ":\"", + )?; assert_insert_seq( &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase: \"┃\" } }"], + &["{ abc: { camelCase┃: RunTimeError } }"], ":\"", )?; assert_insert_seq( &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z: \"┃\" } }"], + &["{ camelCase: { z┃: RunTimeError } }"], ":\"", )?; @@ -1179,15 +1195,19 @@ pub mod test_ed_update { "ul", )?; - assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: 1┃ } }"], ":1")?; + assert_insert_seq( + &["{ a: { zulu┃ } }"], + &["{ a: { zulu1┃: RunTimeError } }"], + ":1", + )?; assert_insert_seq( &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase: 0┃ } }"], + &["{ abc: { camelCase0┃: RunTimeError } }"], ":0", )?; assert_insert_seq( &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z: 45┃ } }"], + &["{ camelCase: { z45┃: RunTimeError } }"], ":45", )?; @@ -1198,15 +1218,19 @@ pub mod test_ed_update { "77", )?; - assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: { ┃ } } }"], ":{")?; + assert_insert_seq( + &["{ a: { zulu┃ } }"], + &["{ a: { zulu┃: RunTimeError } }"], + ":{", + )?; assert_insert_seq( &["{ abc: { camelCase┃ } }"], - &["{ abc: { camelCase: { ┃ } } }"], + &["{ abc: { camelCase┃: RunTimeError } }"], ":{", )?; assert_insert_seq( &["{ camelCase: { z┃ } }"], - &["{ camelCase: { z: { ┃ } } }"], + &["{ camelCase: { z┃: RunTimeError } }"], ":{", )?; @@ -1233,17 +1257,17 @@ pub mod test_ed_update { assert_insert_seq( &["{ a┃: { bcD: { eFgHij: { k15 } } } }"], - &["{ a4┃: { bcD: { eFgHij: { k15 } } } }"], + &["{ a4┃: { bcD: { eFgHij: { k15: RunTimeError } } } }"], "4", )?; assert_insert_seq( &["{ ┃a: { bcD: { eFgHij: { k15 } } } }"], - &["{ y┃a: { bcD: { eFgHij: { k15 } } } }"], + &["{ y┃a: { bcD: { eFgHij: { k15: RunTimeError } } } }"], "y", )?; assert_insert_seq( &["{ a: { bcD: { eF┃gHij: { k15 } } } }"], - &["{ a: { bcD: { eFxyz┃gHij: { k15 } } } }"], + &["{ a: { bcD: { eFxyz┃gHij: { k15: RunTimeError } } } }"], "xyz", )?; @@ -1268,23 +1292,23 @@ pub mod test_ed_update { assert_insert_seq_ignore(&["{ ┃}"], IGNORE_CHARS)?; assert_insert_seq_ignore(&["{ ┃ }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃a }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃abc }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ ┃a: RunTimeError }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ ┃abc: RunTimeError }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ a }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a ┃}"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["┃{ a: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ a: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a:┃ RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a15 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a15 }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ a15 }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ a15 ┃}"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["┃{ a15: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a15: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ a15: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a15:┃ RunTimeError }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{┃ camelCase }"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ camelCase ┃}"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["┃{ camelCase: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ camelCase: ┃RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ camelCase: RunTimeError }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ camelCase:┃ RunTimeError }"], IGNORE_CHARS)?; assert_insert_seq_ignore(&["┃{ a: \"\" }"], IGNORE_CHARS)?; assert_insert_seq_ignore(&["{┃ a: \"\" }"], IGNORE_CHARS)?; @@ -1360,17 +1384,17 @@ pub mod test_ed_update { assert_insert_seq_ignore(&["┃{ a: { } }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore(&["{ ┃a: { } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a ┃} }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a }┃ }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a } ┃}"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a } }┃"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a } }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a } }"], "1")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ RunTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: RunTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: RunTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃RunTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: R┃unTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: Ru┃nTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: RunTimeError } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: RunTimeError } }"], "1")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: RunTimeError } }"], "1")?; assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\"┃ } }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃\"\" } }"], IGNORE_NO_LTR)?; @@ -1460,39 +1484,39 @@ pub mod test_ed_update { )?; assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase ┃} } } } } } } }"], + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase:┃ RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } ┃} } } } } } }"], + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: R┃unTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }┃"], + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }┃"], IGNORE_NO_LTR, )?; assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } ┃} } }"], + &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeEr┃ror } } } } } } } }"], IGNORE_NO_LTR, )?; assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: {┃ e: { p: { camelCase } } } } } } } }"], + &["{ g: { oi: { ng: { d: { e: {┃ e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; assert_insert_seq_ignore( - &["{ g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase } } } } } } } }"], + &["{ g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; assert_insert_seq_ignore( - &["{┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], + &["{┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; assert_insert_seq_ignore( - &["┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], + &["┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], IGNORE_NO_LTR, )?; assert_insert_seq_ignore( - &["{ ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], + &["{ ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], "2", )?; Ok(()) diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs index 4b95b1dc62..d0d42ad1b6 100644 --- a/editor/src/lang/ast.rs +++ b/editor/src/lang/ast.rs @@ -1,11 +1,15 @@ #![allow(clippy::manual_map)] +use std::collections::{HashMap, HashSet}; +use std::hash::BuildHasherDefault; + use crate::lang::pattern::{Pattern2, PatternId}; use crate::lang::pool::Pool; use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone}; use crate::lang::types::{Type2, TypeId}; use arraystring::{typenum::U30, ArrayString}; use roc_can::expr::Recursive; +use roc_collections::all::WyHash; use roc_module::low_level::LowLevel; use roc_module::operator::CalledVia; use roc_module::symbol::Symbol; @@ -135,9 +139,9 @@ pub enum Expr2 { body_id: NodeId, // 4B }, LetFunction { - def: NodeId, // 4B - body_var: Variable, // 8B - body_id: NodeId, // 4B + def_id: NodeId, // 4B + body_var: Variable, // 8B + body_id: NodeId, // 4B }, LetValue { def_id: NodeId, // 4B @@ -217,21 +221,46 @@ pub enum Expr2 { } #[derive(Debug)] -pub struct ValueDef { - pub pattern: PatternId, // 4B - pub expr_type: Option<(TypeId, Rigids)>, // ? - pub expr_var: Variable, // 4B +pub enum ValueDef { + WithAnnotation { + pattern_id: PatternId, // 4B + expr_id: ExprId, // 4B + type_id: TypeId, + rigids: Rigids, + expr_var: Variable, // 4B + }, + NoAnnotation { + pattern_id: PatternId, // 4B + expr_id: ExprId, // 4B + expr_var: Variable, // 4B + }, } impl ShallowClone for ValueDef { fn shallow_clone(&self) -> Self { - Self { - pattern: self.pattern, - expr_type: match &self.expr_type { - Some((id, rigids)) => Some((*id, rigids.shallow_clone())), - None => None, + match self { + Self::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => Self::WithAnnotation { + pattern_id: *pattern_id, + expr_id: *expr_id, + type_id: *type_id, + rigids: rigids.shallow_clone(), + expr_var: *expr_var, + }, + Self::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => Self::NoAnnotation { + pattern_id: *pattern_id, + expr_id: *expr_id, + expr_var: *expr_var, }, - expr_var: self.expr_var, } } } @@ -287,8 +316,68 @@ impl ShallowClone for FunctionDef { #[derive(Debug)] pub struct Rigids { - pub named: PoolVec<(PoolStr, Variable)>, // 8B - pub unnamed: PoolVec, // 8B + pub names: PoolVec<(Option, Variable)>, // 8B + padding: [u8; 1], +} + +#[allow(clippy::needless_collect)] +impl Rigids { + pub fn new( + named: HashMap<&str, Variable, BuildHasherDefault>, + unnamed: HashSet>, + pool: &mut Pool, + ) -> Self { + let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool); + + let mut temp_names = Vec::new(); + + temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); + + temp_names.extend(unnamed.iter().map(|var| (None, *var))); + + for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) { + let poolstr = opt_name.map(|name| PoolStr::new(name, pool)); + + pool[node_id] = (poolstr, variable); + } + + Self { + names, + padding: Default::default(), + } + } + + pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> { + let named = self + .names + .iter(pool) + .filter_map(|(opt_pool_str, var)| { + if let Some(pool_str) = opt_pool_str { + Some((*pool_str, *var)) + } else { + None + } + }) + .collect::>(); + + PoolVec::new(named.into_iter(), pool) + } + + pub fn unnamed(&self, pool: &mut Pool) -> PoolVec { + let unnamed = self + .names + .iter(pool) + .filter_map(|(opt_pool_str, var)| { + if opt_pool_str.is_none() { + Some(*var) + } else { + None + } + }) + .collect::>(); + + PoolVec::new(unnamed.into_iter(), pool) + } } /// This is overflow data from a Closure variant, which needs to store @@ -474,8 +563,8 @@ fn size_of_expr() { impl ShallowClone for Rigids { fn shallow_clone(&self) -> Self { Self { - named: self.named.shallow_clone(), - unnamed: self.unnamed.shallow_clone(), + names: self.names.shallow_clone(), + padding: self.padding, } } } diff --git a/editor/src/lang/constrain.rs b/editor/src/lang/constrain.rs index 106eea2f1f..b813d76153 100644 --- a/editor/src/lang/constrain.rs +++ b/editor/src/lang/constrain.rs @@ -1,7 +1,7 @@ use bumpalo::{collections::Vec as BumpVec, Bump}; use crate::lang::{ - ast::{Expr2, RecordField, WhenBranch}, + ast::{Expr2, RecordField, ValueDef, WhenBranch}, expr::Env, pattern::{DestructType, Pattern2, PatternState2, RecordDestruct}, pool::{Pool, PoolStr, PoolVec, ShallowClone}, @@ -9,8 +9,11 @@ use crate::lang::{ }; use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{BumpMap, BumpMapDefault, Index}; -use roc_module::{ident::TagName, symbol::Symbol}; +use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; +use roc_module::{ + ident::{Lowercase, TagName}, + symbol::Symbol, +}; use roc_region::all::Region; use roc_types::{ subs::Variable, @@ -754,6 +757,156 @@ pub fn constrain_expr<'a>( // exhautiveness checking happens when converting to mono::Expr exists(arena, flex_vars, And(constraints)) } + Expr2::LetValue { + def_id, + body_id, + body_var, + } => { + let value_def = env.pool.get(*def_id); + let body = env.pool.get(*body_id); + + let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region); + + match value_def { + ValueDef::WithAnnotation { .. } => todo!("implement {:?}", value_def), + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => { + let pattern = env.pool.get(*pattern_id); + + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + flex_vars.push(*body_var); + + let expr_type = Type2::Variable(*expr_var); + + let pattern_expected = PExpected::NoExpectation(expr_type.shallow_clone()); + let mut state = PatternState2 { + headers: BumpMap::new_in(arena), + vars: BumpVec::with_capacity_in(1, arena), + constraints: BumpVec::with_capacity_in(1, arena), + }; + + constrain_pattern(arena, env, pattern, region, pattern_expected, &mut state); + state.vars.push(*expr_var); + + let def_expr = env.pool.get(*expr_id); + + let constrained_def = Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint: Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), // always empty + flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments + def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments! + defs_constraint: And(state.constraints), + ret_constraint: constrain_expr( + arena, + env, + def_expr, + Expected::NoExpectation(expr_type), + region, + ), + })), + ret_constraint: body_con, + })); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + and_constraints.push(constrained_def); + and_constraints.push(Eq( + Type2::Variable(*body_var), + expected, + Category::Storage(std::file!(), std::line!()), + // TODO: needs to be ret region + region, + )); + + exists(arena, flex_vars, And(and_constraints)) + } + } + } + Expr2::Update { + symbol, + updates, + ext_var, + record_var, + } => { + let field_types = PoolVec::with_capacity(updates.len() as u32, env.pool); + let mut flex_vars = BumpVec::with_capacity_in(updates.len() + 2, arena); + let mut cons = BumpVec::with_capacity_in(updates.len() + 1, arena); + let mut record_key_updates = SendMap::default(); + + for (record_field_id, field_type_node_id) in + updates.iter_node_ids().zip(field_types.iter_node_ids()) + { + let record_field = env.pool.get(record_field_id); + + match record_field { + RecordField::LabeledValue(pool_str, var, node_id) => { + let expr = env.pool.get(*node_id); + + let (field_type, field_con) = constrain_field_update( + arena, + env, + *var, + pool_str.as_str(env.pool).into(), + expr, + ); + + let field_type_id = env.pool.add(field_type); + + env.pool[field_type_node_id] = + (*pool_str, types::RecordField::Required(field_type_id)); + + record_key_updates.insert(pool_str.as_str(env.pool).into(), Region::zero()); + + flex_vars.push(*var); + cons.push(field_con); + } + e => todo!("{:?}", e), + } + } + + let fields_type = Type2::Record(field_types, env.pool.add(Type2::Variable(*ext_var))); + let record_type = Type2::Variable(*record_var); + + // NOTE from elm compiler: fields_type is separate so that Error propagates better + let fields_con = Eq( + record_type.shallow_clone(), + Expected::NoExpectation(fields_type), + Category::Record, + region, + ); + let record_con = Eq( + record_type.shallow_clone(), + expected, + Category::Record, + region, + ); + + flex_vars.push(*record_var); + flex_vars.push(*ext_var); + + let con = Lookup( + *symbol, + Expected::ForReason( + Reason::RecordUpdateKeys(*symbol, record_key_updates), + record_type, + region, + ), + region, + ); + + // ensure constraints are solved in this order, gives better errors + cons.insert(0, fields_con); + cons.insert(1, con); + cons.insert(2, record_con); + + exists(arena, flex_vars, And(cons)) + } _ => todo!("implement constraints for {:?}", expr), } } @@ -785,6 +938,22 @@ fn constrain_field<'a>( (field_type, constraint) } +#[inline(always)] +fn constrain_field_update<'a>( + arena: &'a Bump, + env: &mut Env, + field_var: Variable, + field: Lowercase, + expr: &Expr2, +) -> (Type2, Constraint<'a>) { + let field_type = Type2::Variable(field_var); + let reason = Reason::RecordUpdateValue(field); + let field_expected = Expected::ForReason(reason, field_type.shallow_clone(), Region::zero()); + let con = constrain_expr(arena, env, expr, field_expected, Region::zero()); + + (field_type, con) +} + fn constrain_empty_record<'a>(expected: Expected, region: Region) -> Constraint<'a> { Constraint::Eq(Type2::EmptyRec, expected, Category::Record, region) } diff --git a/editor/src/lang/def.rs b/editor/src/lang/def.rs index c56115eb03..0cd3b25ba0 100644 --- a/editor/src/lang/def.rs +++ b/editor/src/lang/def.rs @@ -46,10 +46,13 @@ impl Def { 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::Value(value_def) => match value_def { + ValueDef::WithAnnotation { pattern_id, .. } + | ValueDef::NoAnnotation { pattern_id, .. } => { + let pattern2 = &pool[*pattern_id]; + output.extend(symbols_from_pattern(pool, pattern2)); + } + }, Def::Function(function_def) => match function_def { FunctionDef::NoAnnotation { name, .. } | FunctionDef::WithAnnotation { name, .. } => { @@ -79,7 +82,7 @@ impl ShallowClone for Def { /// but no Expr canonicalization has happened yet. Also, it has had spaces /// and nesting resolved, and knows whether annotations are standalone or not. #[derive(Debug)] -enum PendingDef<'a> { +pub enum PendingDef<'a> { /// A standalone annotation with no body AnnotationOnly( &'a Located>, @@ -315,23 +318,7 @@ fn from_pending_alias<'a>( } } - let named = PoolVec::with_capacity(named_rigids.len() as u32, env.pool); - let unnamed = PoolVec::with_capacity(unnamed_rigids.len() as u32, env.pool); - - for (node_id, (name, variable)) in named.iter_node_ids().zip(named_rigids) { - let poolstr = PoolStr::new(name, env.pool); - - env.pool[node_id] = (poolstr, variable); - } - - for (node_id, rigid) in unnamed.iter_node_ids().zip(unnamed_rigids) { - env.pool[node_id] = rigid; - } - - let rigids = Rigids { - named: named.shallow_clone(), - unnamed, - }; + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); let annotation = match signature { Signature::Value { annotation } => annotation, @@ -355,6 +342,8 @@ fn from_pending_alias<'a>( rec_type_union.substitute_alias(env.pool, symbol, Type2::Variable(rec_var)); let annotation_id = env.add(rec_type_union, ann.region); + let named = rigids.named(env.pool); + scope.add_alias(env.pool, symbol, named, annotation_id); } else { env.problem(Problem::CyclicAlias(symbol, name.region, vec![])); @@ -362,6 +351,8 @@ fn from_pending_alias<'a>( } } else { let annotation_id = env.add(annotation, ann.region); + let named = rigids.named(env.pool); + scope.add_alias(env.pool, symbol, named, annotation_id); } @@ -407,21 +398,7 @@ fn canonicalize_pending_def<'a>( output.references.referenced_aliases.insert(symbol); } - let rigids = { - let named = PoolVec::with_capacity(named_rigids.len() as u32, env.pool); - let unnamed = PoolVec::with_capacity(unnamed_rigids.len() as u32, env.pool); - - for (node_id, (name, variable)) in named.iter_node_ids().zip(named_rigids) { - let poolstr = PoolStr::new(name, env.pool); - env.pool[node_id] = (poolstr, variable); - } - - for (node_id, rigid) in unnamed.iter_node_ids().zip(unnamed_rigids) { - env.pool[node_id] = rigid; - } - - Rigids { named, unnamed } - }; + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); let annotation = match signature { Signature::Value { annotation } => annotation, @@ -470,21 +447,7 @@ fn canonicalize_pending_def<'a>( output.references.referenced_aliases.insert(symbol); } - let rigids = { - let named = PoolVec::with_capacity(named_rigids.len() as u32, env.pool); - let unnamed = PoolVec::with_capacity(unnamed_rigids.len() as u32, env.pool); - - for (node_id, (name, variable)) in named.iter_node_ids().zip(named_rigids) { - let poolstr = PoolStr::new(name, env.pool); - env.pool[node_id] = (poolstr, variable); - } - - for (node_id, rigid) in unnamed.iter_node_ids().zip(unnamed_rigids) { - env.pool[node_id] = rigid; - } - - Rigids { named, unnamed } - }; + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); // bookkeeping for tail-call detection. If we're assigning to an // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. @@ -624,9 +587,11 @@ fn canonicalize_pending_def<'a>( }; let annotation = env.add(annotation, loc_ann.region); - let value_def = ValueDef { - pattern: loc_can_pattern, - expr_type: Some((annotation, rigids)), + let value_def = ValueDef::WithAnnotation { + pattern_id: loc_can_pattern, + expr_id: env.pool.add(loc_can_expr), + type_id: annotation, + rigids: rigids, expr_var: env.var_store.fresh(), }; @@ -745,9 +710,9 @@ fn canonicalize_pending_def<'a>( } _ => { - let value_def = ValueDef { - pattern: loc_can_pattern, - expr_type: None, + let value_def = ValueDef::NoAnnotation { + pattern_id: loc_can_pattern, + expr_id: env.pool.add(loc_can_expr), expr_var: env.var_store.fresh(), }; diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs index 2a9626bd5b..936da9242d 100644 --- a/editor/src/lang/expr.rs +++ b/editor/src/lang/expr.rs @@ -1,19 +1,28 @@ #![allow(clippy::all)] #![allow(dead_code)] #![allow(unused_imports)] -use crate::lang::ast::expr2_to_string; -use crate::lang::ast::RecordField; -use crate::lang::ast::{ClosureExtra, Expr2, ExprId, FloatVal, IntStyle, IntVal, WhenBranch}; -use crate::lang::def::References; -use crate::lang::pattern::to_pattern2; +use bumpalo::{collections::Vec as BumpVec, Bump}; +use inlinable_string::InlinableString; +use std::collections::HashMap; + +use crate::lang::ast::{ + expr2_to_string, ClosureExtra, Expr2, ExprId, FloatVal, IntStyle, IntVal, RecordField, + WhenBranch, +}; +use crate::lang::def::{ + canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References, +}; +use crate::lang::pattern::{to_pattern2, Pattern2, PatternId}; use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; use crate::lang::scope::Scope; -use crate::lang::types::{Alias, Type2, TypeId}; -use bumpalo::Bump; -use inlinable_string::InlinableString; +use crate::lang::types::{Alias, Annotation2, Type2, TypeId}; + use roc_can::expr::Recursive; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_can::operator::desugar_expr; +use roc_collections::all::default_hasher; use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::Lowercase; use roc_module::ident::ModuleName; use roc_module::low_level::LowLevel; use roc_module::operator::CalledVia; @@ -21,14 +30,63 @@ use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_parse::ast; use roc_parse::ast::StrLiteral; use roc_parse::parser::{loc, Parser, State, SyntaxError}; +use roc_parse::pattern::PatternType; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; +#[derive(Clone, Debug, PartialEq, Default)] +pub struct IntroducedVariables { + // Rigids must be unique within a type annoation. + // E.g. in `identity : a -> a`, there should only be one + // variable (a rigid one, with name "a"). + // Hence `rigids : Map` + // + // But then between annotations, the same name can occur multiple times, + // but a variable can only have one name. Therefore + // `ftv : Map`. + pub wildcards: Vec, + pub var_by_name: MutMap, + pub name_by_var: MutMap, + pub host_exposed_aliases: MutMap, +} + +impl IntroducedVariables { + pub fn insert_named(&mut self, name: Lowercase, var: Variable) { + self.var_by_name.insert(name.clone(), var); + self.name_by_var.insert(var, name); + } + + pub fn insert_wildcard(&mut self, var: Variable) { + self.wildcards.push(var); + } + + pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { + self.host_exposed_aliases.insert(symbol, var); + } + + pub fn union(&mut self, other: &Self) { + self.wildcards.extend(other.wildcards.iter().cloned()); + self.var_by_name.extend(other.var_by_name.clone()); + self.name_by_var.extend(other.name_by_var.clone()); + self.host_exposed_aliases + .extend(other.host_exposed_aliases.clone()); + } + + pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { + self.var_by_name.get(name) + } + + pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { + self.name_by_var.get(&var) + } +} + #[derive(Clone, Default, Debug, PartialEq)] pub struct Output { pub references: References, pub tail_call: Option, + pub introduced_variables: IntroducedVariables, pub aliases: MutMap>, pub non_closures: MutSet, } @@ -53,6 +111,8 @@ pub struct Env<'a> { pub pool: &'a mut Pool, pub arena: &'a Bump, + pub problems: BumpVec<'a, Problem>, + pub dep_idents: MutMap, pub module_ids: &'a ModuleIds, pub ident_ids: IdentIds, @@ -82,6 +142,7 @@ impl<'a> Env<'a> { home, arena, pool, + problems: BumpVec::new_in(arena), var_store, dep_idents, module_ids, @@ -102,8 +163,8 @@ impl<'a> Env<'a> { id } - pub fn problem(&mut self, _problem: Problem) { - todo!(); + pub fn problem(&mut self, problem: Problem) { + self.problems.push(problem); } pub fn set_region(&mut self, _node_id: NodeId, _region: Region) { @@ -236,7 +297,16 @@ pub fn str_to_expr2<'a>( region: Region, ) -> Result<(Expr2, self::Output), SyntaxError<'a>> { match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { - Ok(loc_expr) => Ok(to_expr2(env, scope, arena.alloc(loc_expr.value), region)), + Ok(loc_expr) => { + let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); + + Ok(to_expr2( + env, + scope, + arena.alloc(desugared_loc_expr.value), + region, + )) + } Err(fail) => Err(fail), } } @@ -780,7 +850,50 @@ pub fn to_expr2<'a>( } Defs(loc_defs, loc_ret) => { - todo!("{:?} {:?}", loc_defs, loc_ret) + let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( + env, + Output::default(), + &scope, + loc_defs, + PatternType::DefExpr, + ); + + // The def as a whole is a tail call iff its return expression is a tail call. + // Use its output as a starting point because its tail_call already has the right answer! + let (ret_expr, mut output) = to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region); + + output + .introduced_variables + .union(&defs_output.introduced_variables); + + output.references.union_mut(defs_output.references); + + // Now that we've collected all the references, check to see if any of the new idents + // we defined went unused by the return expression. If any were unused, report it. + for (symbol, region) in symbols_introduced { + if !output.references.has_lookup(symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + let (can_defs, output) = sort_can_defs(env, unsorted, output); + + match can_defs { + Ok(decls) => { + let mut expr = ret_expr; + + for declaration in decls.into_iter().rev() { + expr = decl_to_let(env.pool, env.var_store, declaration, expr); + } + + (expr, output) + } + Err(_err) => { + // TODO: fix this to be something from Expr2 + // (RuntimeError(err), output) + todo!() + } + } } PrecedenceConflict { .. } => { @@ -1262,11 +1375,10 @@ fn canonicalize_lookup( Var(symbol) } - Err(_problem) => { - // env.problem(Problem::RuntimeError(problem.clone())); + Err(problem) => { + env.problem(Problem::RuntimeError(problem.clone())); - // RuntimeError(problem) - todo!() + RuntimeError() } } } else { @@ -1278,14 +1390,12 @@ fn canonicalize_lookup( Var(symbol) } - Err(_problem) => { + Err(problem) => { // Either the module wasn't imported, or // it was imported but it doesn't expose this ident. - // env.problem(Problem::RuntimeError(problem.clone())); + env.problem(Problem::RuntimeError(problem.clone())); - // RuntimeError(problem) - - todo!() + RuntimeError() } } }; @@ -1294,3 +1404,59 @@ fn canonicalize_lookup( (can_expr, output) } + +fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret: Expr2) -> Expr2 { + match decl { + Declaration::Declare(def) => match def { + Def::AnnotationOnly { .. } => todo!(), + Def::Value(value_def) => { + let def_id = pool.add(value_def); + let body_id = pool.add(ret); + + Expr2::LetValue { + def_id, + body_id, + body_var: var_store.fresh(), + } + } + Def::Function(function_def) => { + let def_id = pool.add(function_def); + let body_id = pool.add(ret); + + Expr2::LetFunction { + def_id, + body_id, + body_var: var_store.fresh(), + } + } + }, + Declaration::DeclareRec(defs) => { + let mut function_defs = vec![]; + + for def in defs { + match def { + Def::AnnotationOnly { .. } => todo!(), + Def::Function(function_def) => function_defs.push(function_def), + Def::Value(_) => unreachable!(), + } + } + + let body_id = pool.add(ret); + + Expr2::LetRec { + defs: PoolVec::new(function_defs.into_iter(), pool), + body_var: var_store.fresh(), + body_id, + } + } + Declaration::InvalidCycle(_entries, _) => { + // TODO: replace with something from Expr2 + // Expr::RuntimeError(RuntimeError::CircularDef(entries)) + todo!() + } + Declaration::Builtin(_) => { + // Builtins should only be added to top-level decls, not to let-exprs! + unreachable!() + } + } +} diff --git a/editor/src/lang/module.rs b/editor/src/lang/module.rs index a8b33481df..c0494d3193 100644 --- a/editor/src/lang/module.rs +++ b/editor/src/lang/module.rs @@ -2,7 +2,7 @@ #![allow(dead_code)] #![allow(unused_imports)] #![allow(unused_variables)] -use crate::lang::ast::{FunctionDef, ValueDef}; +use crate::lang::ast::{Expr2, FunctionDef, ValueDef}; use crate::lang::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; use crate::lang::expr::Env; use crate::lang::expr::Output; @@ -249,10 +249,11 @@ pub fn canonicalize_module_defs<'a>( let runtime_error = RuntimeError::ExposedButNotDefined(symbol); let value_def = { - let pattern = env.pool.add(Pattern2::Identifier(symbol)); - ValueDef { - pattern, - expr_type: None, + let pattern_id = env.pool.add(Pattern2::Identifier(symbol)); + let expr_id = env.pool.add(Expr2::RuntimeError()); + ValueDef::NoAnnotation { + pattern_id, + expr_id, expr_var: env.var_store.fresh(), } }; diff --git a/editor/tests/solve_expr2.rs b/editor/tests/solve_expr2.rs index 9e9d593e5d..a25c88b077 100644 --- a/editor/tests/solve_expr2.rs +++ b/editor/tests/solve_expr2.rs @@ -117,7 +117,7 @@ fn infer_eq(actual: &str, expected_str: &str) { let content = subs.get(var).content; let interns = Interns { - module_ids, + module_ids: env.module_ids.clone(), all_ident_ids: dep_idents, }; @@ -300,3 +300,31 @@ fn constrain_when() { "[ Blue, Purple ]*", ) } + +#[test] +fn constrain_let_value() { + infer_eq( + indoc!( + r#" + person = { name: "roc" } + + person + "# + ), + "{ name : Str }", + ) +} + +#[test] +fn constrain_update() { + infer_eq( + indoc!( + r#" + person = { name: "roc" } + + { person & name: "bird" } + "# + ), + "{ name : Str }", + ) +}