diff --git a/Cargo.lock b/Cargo.lock index 71b9a3e355..0690cc0fb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -242,10 +242,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.0", ] [[package]] @@ -1393,6 +1405,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.17" @@ -2645,7 +2663,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1" dependencies = [ - "bitvec", + "bitvec 0.22.3", "packed_struct_codegen", "serde", ] @@ -3092,6 +3110,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "radix_trie" version = "0.2.1" @@ -3271,6 +3295,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "remove_dir_all" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882f368737489ea543bc5c340e6f3d34a28c39980bd9a979e47322b26f60ac40" +dependencies = [ + "libc", + "log", + "num_cpus", + "rayon", + "winapi", +] + [[package]] name = "renderdoc-sys" version = "0.7.1" @@ -3409,6 +3446,7 @@ dependencies = [ name = "roc_can" version = "0.1.0" dependencies = [ + "bitvec 1.0.0", "bumpalo", "indoc", "pretty_assertions", @@ -3735,6 +3773,7 @@ dependencies = [ "roc_reporting", "roc_solve", "roc_target", + "roc_test_utils", "roc_types", "roc_unify", "tempfile", @@ -3953,6 +3992,7 @@ name = "roc_test_utils" version = "0.1.0" dependencies = [ "pretty_assertions", + "remove_dir_all 0.7.0", ] [[package]] @@ -4463,7 +4503,7 @@ dependencies = [ "libc", "rand", "redox_syscall", - "remove_dir_all", + "remove_dir_all 0.5.3", "winapi", ] @@ -5580,6 +5620,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "x11-clipboard" version = "0.5.3" diff --git a/README.md b/README.md index 2b094900b3..c0c5f98702 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,13 @@ For NQueens, input 10 in the terminal and press enter. **Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic. -## Sponsor +## Sponsors -We are very grateful for our sponsor [NoRedInk](https://www.noredink.com/). +We are very grateful for our sponsors [NoRedInk](https://www.noredink.com/) and [rwx](https://www.rwx.com). -NoRedInk logo +[NoRedInk logo](https://www.noredink.com/) +     +[rwx logo](https://www.rwx.com) ## Applications and Platforms diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index f15b128827..096a405d5c 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -75,7 +75,7 @@ use crate::mem_pool::shallow_clone::ShallowClone; // Ranks are used to limit the number of type variables considered for generalization. Only those inside // of the let (so those used in inferring the type of `\x -> x`) are considered. -#[derive(PartialEq, Debug, Clone)] +#[derive(Debug, Clone)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), BadPattern(Region, PatternCategory, ErrorType, PExpected), diff --git a/cli/src/build.rs b/cli/src/build.rs index 0ccf118178..be2bd0736e 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -1,7 +1,7 @@ use bumpalo::Bump; use roc_build::{ link::{link, rebuild_host, LinkType}, - program, + program::{self, Problems}, }; use roc_builtins::bitcode; use roc_load::LoadingProblem; @@ -21,25 +21,9 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) { )); } -pub enum BuildOutcome { - NoProblems, - OnlyWarnings, - 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, + pub problems: Problems, pub total_time: Duration, } @@ -184,7 +168,7 @@ pub fn build_file<'a>( // This only needs to be mutable for report_problems. This can't be done // inside a nested scope without causing a borrow error! let mut loaded = loaded; - program::report_problems_monomorphized(&mut loaded); + let problems = program::report_problems_monomorphized(&mut loaded); let loaded = loaded; let code_gen_timing = program::gen_from_mono_module( @@ -243,7 +227,7 @@ pub fn build_file<'a>( // Step 2: link the precompiled host and compiled app let link_start = SystemTime::now(); - let outcome = if surgically_link { + let problems = if surgically_link { roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path) .map_err(|err| { todo!( @@ -251,12 +235,12 @@ pub fn build_file<'a>( err ); })?; - BuildOutcome::NoProblems + problems } else if matches!(link_type, LinkType::None) { // Just copy the object file to the output folder. binary_path.set_extension(app_extension); std::fs::copy(app_o_file, &binary_path).unwrap(); - BuildOutcome::NoProblems + problems } else { let mut inputs = vec![ host_input_path.as_path().to_str().unwrap(), @@ -281,11 +265,15 @@ pub fn build_file<'a>( todo!("gracefully handle error after `ld` spawned"); })?; - // TODO change this to report whether there were errors or warnings! if exit_status.success() { - BuildOutcome::NoProblems + problems } else { - BuildOutcome::Errors + let mut problems = problems; + + // Add an error for `ld` failing + problems.errors += 1; + + problems } }; let linking_time = link_start.elapsed().unwrap(); @@ -298,7 +286,7 @@ pub fn build_file<'a>( Ok(BuiltFile { binary_path, - outcome, + problems, total_time, }) } @@ -318,7 +306,7 @@ fn spawn_rebuild_thread( let thread_local_target = target.clone(); std::thread::spawn(move || { if !precompiled { - print!("πŸ”¨ Rebuilding host... "); + println!("πŸ”¨ Rebuilding host..."); } let rebuild_host_start = SystemTime::now(); @@ -350,10 +338,6 @@ fn spawn_rebuild_thread( } let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); - if !precompiled { - println!("Done!"); - } - rebuild_host_end.as_millis() }) } @@ -364,7 +348,7 @@ pub fn check_file( src_dir: PathBuf, roc_file_path: PathBuf, emit_timings: bool, -) -> Result { +) -> Result<(program::Problems, Duration), LoadingProblem> { let compilation_start = SystemTime::now(); // only used for generating errors. We don't do code generation, so hardcoding should be fine @@ -437,5 +421,8 @@ pub fn check_file( println!("Finished checking in {} ms\n", compilation_end.as_millis(),); } - Ok(program::report_problems_typechecked(&mut loaded)) + Ok(( + program::report_problems_typechecked(&mut loaded), + compilation_end, + )) } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 792071fbcd..25f831d1f0 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate const_format; -use build::{BuildOutcome, BuiltFile}; +use build::BuiltFile; use bumpalo::Bump; use clap::{App, AppSettings, Arg, ArgMatches}; use roc_build::link::LinkType; @@ -24,6 +24,7 @@ mod format; pub use format::format; pub const CMD_BUILD: &str = "build"; +pub const CMD_RUN: &str = "run"; pub const CMD_REPL: &str = "repl"; pub const CMD_EDIT: &str = "edit"; pub const CMD_DOCS: &str = "docs"; @@ -49,10 +50,12 @@ pub const ROC_DIR: &str = "ROC_DIR"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; +const VERSION: &str = include_str!("../../version.txt"); + pub fn build_app<'a>() -> App<'a> { let app = App::new("roc") - .version(concatcp!(include_str!("../../version.txt"), "\n")) - .about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!") + .version(concatcp!(VERSION, "\n")) + .about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!") .subcommand(App::new(CMD_BUILD) .about("Build a binary from the given .roc file, but don't run it") .arg( @@ -140,8 +143,16 @@ pub fn build_app<'a>() -> App<'a> { .subcommand(App::new(CMD_REPL) .about("Launch the interactive Read Eval Print Loop (REPL)") ) + .subcommand(App::new(CMD_RUN) + .about("Run a .roc file even if it has build errors") + .arg( + Arg::new(ROC_FILE) + .about("The .roc file of an app to run") + .required(true), + ) + ) .subcommand(App::new(CMD_FORMAT) - .about("Format Roc code") + .about("Format a .roc file using standard Roc formatting") .arg( Arg::new(DIRECTORY_OR_FILES) .index(1) @@ -155,10 +166,9 @@ pub fn build_app<'a>() -> App<'a> { ) ) .subcommand(App::new(CMD_VERSION) - .about("Print version information") - ) + .about(concatcp!("Print the Roc compiler’s version, which is currently ", VERSION))) .subcommand(App::new(CMD_CHECK) - .about("When developing, it's recommended to run `check` before `build`. It may provide a useful error message in cases where `build` panics") + .about("Check the code for problems, but doesn’t build or run it") .arg( Arg::new(FLAG_TIME) .long(FLAG_TIME) @@ -167,7 +177,7 @@ pub fn build_app<'a>() -> App<'a> { ) .arg( Arg::new(ROC_FILE) - .about("The .roc file of an app to run") + .about("The .roc file of an app to check") .required(true), ) ) @@ -193,19 +203,19 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::new(FLAG_OPT_SIZE) .long(FLAG_OPT_SIZE) - .about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)") + .about("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)") .required(false), ) .arg( Arg::new(FLAG_DEV) .long(FLAG_DEV) - .about("Make compilation as fast as possible. (Runtime performance may suffer)") + .about("Make compilation finish as soon as possible, at the expense of runtime performance.") .required(false), ) .arg( Arg::new(FLAG_DEBUG) .long(FLAG_DEBUG) - .about("Store LLVM debug information in the generated program") + .about("Store LLVM debug information in the generated program.") .requires(ROC_FILE) .required(false), ) @@ -231,18 +241,10 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::new(FLAG_PRECOMPILED) .long(FLAG_PRECOMPILED) - .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)") + .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)") .possible_values(["true", "false"]) .required(false), ) - .arg( - Arg::new(FLAG_TARGET) - .long(FLAG_TARGET) - .about("Choose a different target") - .default_value(Target::default().as_str()) - .possible_values(Target::OPTIONS) - .required(false), - ) .arg( Arg::new(ROC_FILE) .about("The .roc file of an app to build and run") @@ -280,6 +282,7 @@ pub fn docs(files: Vec) { pub enum BuildConfig { BuildOnly, BuildAndRun { roc_file_arg_index: usize }, + BuildAndRunIfNoErrors { roc_file_arg_index: usize }, } pub enum FormatMode { @@ -387,7 +390,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { match res_binary_path { Ok(BuiltFile { binary_path, - outcome, + problems, total_time, }) => { match config { @@ -402,56 +405,128 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { std::mem::forget(arena); println!( - "πŸŽ‰ Built {} in {} ms", - generated_filename.to_str().unwrap(), - total_time.as_millis() + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms while successfully building:\n\n {}", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + generated_filename.to_str().unwrap() ); // Return a nonzero exit code if there were problems - Ok(outcome.status_code()) + Ok(problems.exit_code()) } BuildAndRun { roc_file_arg_index } => { - let mut cmd = match triple.architecture { - Architecture::Wasm32 => { - // If possible, report the generated executable name relative to the current dir. - let generated_filename = binary_path - .strip_prefix(env::current_dir().unwrap()) - .unwrap_or(&binary_path); - - // No need to waste time freeing this memory, - // since the process is about to exit anyway. - std::mem::forget(arena); - - let args = std::env::args() - .skip(roc_file_arg_index) - .collect::>(); - - run_with_wasmer(generated_filename, &args); - return Ok(0); - } - _ => Command::new(&binary_path), - }; - - if let Architecture::Wasm32 = triple.architecture { - cmd.arg(binary_path); + if problems.errors > 0 || problems.warnings > 0 { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + "─".repeat(80) + ); } - // Forward all the arguments after the .roc file argument - // to the new process. This way, you can do things like: - // - // roc app.roc foo bar baz - // - // ...and have it so that app.roc will receive only `foo`, - // `bar`, and `baz` as its arguments. - for (index, arg) in std::env::args().enumerate() { - if index > roc_file_arg_index { - cmd.arg(arg); + roc_run( + arena, + &original_cwd, + triple, + roc_file_arg_index, + &binary_path, + ) + } + BuildAndRunIfNoErrors { roc_file_arg_index } => { + if problems.errors == 0 { + if problems.warnings > 0 { + println!( + "\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m", + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + "─".repeat(80) + ); } - } - match outcome { - BuildOutcome::Errors => Ok(outcome.status_code()), - _ => roc_run(cmd.current_dir(original_cwd)), + roc_run( + arena, + &original_cwd, + triple, + roc_file_arg_index, + &binary_path, + ) + } else { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with: \x1B[32mroc run {}\x1B[39m", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + filename + ); + + Ok(problems.exit_code()) } } } @@ -468,11 +543,55 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { } #[cfg(target_family = "unix")] -fn roc_run(cmd: &mut Command) -> io::Result { +fn roc_run( + arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it! + cwd: &Path, + triple: Triple, + roc_file_arg_index: usize, + binary_path: &Path, +) -> io::Result { use std::os::unix::process::CommandExt; + let mut cmd = match triple.architecture { + Architecture::Wasm32 => { + // If possible, report the generated executable name relative to the current dir. + let generated_filename = binary_path + .strip_prefix(env::current_dir().unwrap()) + .unwrap_or(binary_path); + + // No need to waste time freeing this memory, + // since the process is about to exit anyway. + std::mem::forget(arena); + + let args = std::env::args() + .skip(roc_file_arg_index) + .collect::>(); + + run_with_wasmer(generated_filename, &args); + return Ok(0); + } + _ => Command::new(&binary_path), + }; + + if let Architecture::Wasm32 = triple.architecture { + cmd.arg(binary_path); + } + + // Forward all the arguments after the .roc file argument + // to the new process. This way, you can do things like: + // + // roc app.roc foo bar baz + // + // ...and have it so that app.roc will receive only `foo`, + // `bar`, and `baz` as its arguments. + for (index, arg) in std::env::args().enumerate() { + if index > roc_file_arg_index { + cmd.arg(arg); + } + } + // This is much faster than spawning a subprocess if we're on a UNIX system! - let err = cmd.exec(); + let err = cmd.current_dir(cwd).exec(); // If exec actually returned, it was definitely an error! (Otherwise, // this process would have been replaced by the other one, and we'd diff --git a/cli/src/main.rs b/cli/src/main.rs index 6a5c984ef3..b38445980b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,8 @@ use roc_cli::build::check_file; use roc_cli::{ build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, - CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, ROC_FILE, + CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, + ROC_FILE, }; use roc_load::LoadingProblem; use std::fs::{self, FileType}; @@ -27,7 +28,10 @@ fn main() -> io::Result<()> { Some(arg_index) => { let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! - build(&matches, BuildConfig::BuildAndRun { roc_file_arg_index }) + build( + &matches, + BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index }, + ) } None => { @@ -37,6 +41,21 @@ fn main() -> io::Result<()> { } } } + Some((CMD_RUN, matches)) => { + match matches.index_of(ROC_FILE) { + Some(arg_index) => { + let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! + + build(matches, BuildConfig::BuildAndRun { roc_file_arg_index }) + } + + None => { + eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command."); + + Ok(1) + } + } + } Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?), Some((CMD_CHECK, matches)) => { let arena = bumpalo::Bump::new(); @@ -47,9 +66,35 @@ fn main() -> io::Result<()> { let src_dir = roc_file_path.parent().unwrap().to_owned(); match check_file(&arena, src_dir, roc_file_path, emit_timings) { - Ok(number_of_errors) => { - let exit_code = if number_of_errors != 0 { 1 } else { 0 }; - Ok(exit_code) + Ok((problems, total_time)) => { + println!( + "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.", + if problems.errors == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.errors, + if problems.errors == 1 { + "error" + } else { + "errors" + }, + if problems.warnings == 0 { + 32 // green + } else { + 33 // yellow + }, + problems.warnings, + if problems.warnings == 1 { + "warning" + } else { + "warnings" + }, + total_time.as_millis(), + ); + + Ok(problems.exit_code()) } Err(LoadingProblem::FormattedReport(report)) => { diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 32ef62de18..649f299edb 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -61,13 +61,19 @@ mod cli_run { .replace(ANSI_STYLE_CODES.bold, "") .replace(ANSI_STYLE_CODES.underline, "") .replace(ANSI_STYLE_CODES.reset, "") + .replace(ANSI_STYLE_CODES.color_reset, "") } fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat()); let err = compile_out.stdout.trim(); let err = strip_colors(err); - assert_multiline_str_eq!(err, expected.into()); + + // e.g. "1 error and 0 warnings found in 123 ms." + let (before_first_digit, _) = err.split_at(err.rfind("found in ").unwrap()); + let err = format!("{}found in ms.", before_first_digit); + + assert_multiline_str_eq!(err.as_str(), expected.into()); } fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { @@ -175,8 +181,8 @@ mod cli_run { }; if !&out.stdout.ends_with(expected_ending) { panic!( - "expected output to end with {:?} but instead got {:#?}", - expected_ending, out.stdout + "expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}", + expected_ending, out.stdout, out.stderr ); } assert!(out.status.success()); @@ -844,7 +850,7 @@ mod cli_run { &[], indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ I cannot find a `d` value @@ -858,7 +864,9 @@ mod cli_run { I8 F64 - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } @@ -870,14 +878,16 @@ mod cli_run { &[], indoc!( r#" - ── MISSING DEFINITION ────────────────────────────────────────────────────────── + ── MISSING DEFINITION ────────────────── tests/known_bad/ExposedNotDefined.roc ─ bar is listed as exposed, but it isn't defined in this module. You can fix this by adding a definition for bar, or by removing it from exposes. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } @@ -889,7 +899,7 @@ mod cli_run { &[], indoc!( r#" - ── UNUSED IMPORT ─────────────────────────────────────────────────────────────── + ── UNUSED IMPORT ──────────────────────────── tests/known_bad/UnusedImport.roc ─ Nothing from Symbol is used in this module. @@ -898,7 +908,9 @@ mod cli_run { Since Symbol isn't used, you don't need to import it. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 0 errors and 1 warning found in ms."# ), ); } @@ -910,7 +922,7 @@ mod cli_run { &[], indoc!( r#" - ── UNKNOWN GENERATES FUNCTION ────────────────────────────────────────────────── + ── UNKNOWN GENERATES FUNCTION ─────── tests/known_bad/UnknownGeneratesWith.roc ─ I don't know how to generate the foobar function. @@ -920,7 +932,9 @@ mod cli_run { Only specific functions like `after` and `map` can be generated.Learn more about hosted modules at TODO. - ────────────────────────────────────────────────────────────────────────────────"# + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# ), ); } diff --git a/compiler/README.md b/compiler/README.md index e57984562d..d26c7e862e 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -167,6 +167,20 @@ For a more detailed understanding of the compilation phases, see the `Phase`, `B ## Debugging intermediate representations +### Debugging the typechecker + +Setting the following environment variables: + +- `ROC_PRINT_UNIFICATIONS` prints all type unifications that are done, + before and after the unification. +- `ROC_PRINT_MISMATCHES` prints all type mismatches hit during unification. +- `ROC_PRETTY_PRINT_ALIAS_CONTENTS` expands the contents of aliases during + pretty-printing of types. + +Note that this is only relevant during debug builds. Eventually we should have +some better debugging tools here, see https://github.com/rtfeldman/roc/issues/2486 +for one. + ### The mono IR If you observe a miscomplication, you may first want to check the generated mono diff --git a/compiler/alias_analysis/src/lib.rs b/compiler/alias_analysis/src/lib.rs index d492f3a12a..572a438847 100644 --- a/compiler/alias_analysis/src/lib.rs +++ b/compiler/alias_analysis/src/lib.rs @@ -279,7 +279,8 @@ fn build_entry_point( let block = builder.add_block(); // to the modelling language, the arguments appear out of thin air - let argument_type = build_tuple_type(&mut builder, layout.arguments)?; + let argument_type = + build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?; // does not make any assumptions about the input // let argument = builder.add_unknown_with(block, &[], argument_type)?; @@ -308,7 +309,11 @@ fn build_entry_point( let block = builder.add_block(); - let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?; + let type_id = layout_spec( + &mut builder, + &Layout::struct_no_name_order(layouts), + &WhenRecursive::Unreachable, + )?; let argument = builder.add_unknown_with(block, &[], type_id)?; @@ -352,8 +357,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet>)> let arg_type_id = layout_spec( &mut builder, &Layout::struct_no_name_order(&argument_layouts), + &WhenRecursive::Unreachable, )?; - let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?; + let ret_type_id = layout_spec(&mut builder, &proc.ret_layout, &WhenRecursive::Unreachable)?; let spec = builder.build(arg_type_id, ret_type_id, root)?; @@ -457,10 +463,14 @@ fn stmt_spec<'a>( let mut type_ids = Vec::new(); for p in parameters.iter() { - type_ids.push(layout_spec(builder, &p.layout)?); + type_ids.push(layout_spec( + builder, + &p.layout, + &WhenRecursive::Unreachable, + )?); } - let ret_type_id = layout_spec(builder, layout)?; + let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let jp_arg_type_id = builder.add_tuple_type(&type_ids)?; @@ -500,14 +510,14 @@ fn stmt_spec<'a>( builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id)) } Jump(id, symbols) => { - let ret_type_id = layout_spec(builder, layout)?; + let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let argument = build_tuple_value(builder, env, block, symbols)?; let jpid = env.join_points[id]; builder.add_jump(block, jpid, argument, ret_type_id) } RuntimeError(_) => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_terminate(block, type_id) } @@ -556,11 +566,15 @@ fn build_recursive_tuple_type( builder.add_tuple_type(&field_types) } -fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result { +fn build_tuple_type( + builder: &mut impl TypeContext, + layouts: &[Layout], + when_recursive: &WhenRecursive, +) -> Result { let mut field_types = Vec::new(); for field in layouts.iter() { - field_types.push(layout_spec(builder, field)?); + field_types.push(layout_spec(builder, field, when_recursive)?); } builder.add_tuple_type(&field_types) @@ -691,7 +705,7 @@ fn call_spec( .map(|symbol| env.symbols[symbol]) .collect(); - let result_type = layout_spec(builder, ret_layout)?; + let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -761,7 +775,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -782,7 +797,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -806,7 +822,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -828,10 +845,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -851,10 +870,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -879,7 +900,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -903,10 +925,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -936,10 +960,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -975,10 +1001,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -1010,7 +1038,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -1087,11 +1116,13 @@ fn call_spec( ) }; - let output_element_type = layout_spec(builder, &output_element_layout)?; + let output_element_type = + layout_spec(builder, &output_element_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; let state_layout = Layout::Builtin(Builtin::List(&output_element_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; add_loop(builder, block, state_type, init_state, loop_body) } @@ -1108,7 +1139,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::Bool); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_num(builder, block)?; @@ -1127,7 +1159,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::Bool); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_num(builder, block)?; @@ -1139,7 +1172,8 @@ fn call_spec( // ListFindUnsafe returns { value: v, found: Bool=Int1 } let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)]; let output_layout = Layout::struct_no_name_order(&output_layouts); - let output_type = layout_spec(builder, &output_layout)?; + let output_type = + layout_spec(builder, &output_layout, &WhenRecursive::Unreachable)?; let loop_body = |builder: &mut FuncDefBuilder, block, output| { let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; @@ -1201,7 +1235,7 @@ fn lowlevel_spec( ) -> Result { use LowLevel::*; - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let mode = update_mode.to_bytes(); let update_mode_var = UpdateModeVar(&mode); @@ -1323,8 +1357,8 @@ fn lowlevel_spec( } DictEmpty => match layout { Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { - let key_id = layout_spec(builder, key_layout)?; - let value_id = layout_spec(builder, value_layout)?; + let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?; + let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?; new_dict(builder, block, key_id, value_id) } _ => unreachable!("empty array does not have a list layout"), @@ -1367,7 +1401,7 @@ fn lowlevel_spec( // TODO overly pessimstic let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect(); - let result_type = layout_spec(builder, layout)?; + let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -1478,7 +1512,8 @@ fn expr_spec<'a>( let value_id = match tag_layout { UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types(builder, tags)?; + let variant_types = + non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?; let value_id = build_tuple_value(builder, env, block, arguments)?; return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); } @@ -1592,7 +1627,7 @@ fn expr_spec<'a>( builder.add_get_tuple_field(block, value_id, *index as u32) } Array { elem_layout, elems } => { - let type_id = layout_spec(builder, elem_layout)?; + let type_id = layout_spec(builder, elem_layout, &WhenRecursive::Unreachable)?; let list = new_list(builder, block, type_id)?; @@ -1619,19 +1654,19 @@ fn expr_spec<'a>( EmptyArray => match layout { Layout::Builtin(Builtin::List(element_layout)) => { - let type_id = layout_spec(builder, element_layout)?; + let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?; new_list(builder, block, type_id) } _ => unreachable!("empty array does not have a list layout"), }, Reset { symbol, .. } => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let value_id = env.symbols[symbol]; builder.add_unknown_with(block, &[value_id], type_id) } RuntimeErrorFunction(_) => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_terminate(block, type_id) } @@ -1658,18 +1693,24 @@ fn literal_spec( } } -fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result { - layout_spec_help(builder, layout, &WhenRecursive::Unreachable) +fn layout_spec( + builder: &mut impl TypeContext, + layout: &Layout, + when_recursive: &WhenRecursive, +) -> Result { + layout_spec_help(builder, layout, when_recursive) } fn non_recursive_variant_types( builder: &mut impl TypeContext, tags: &[&[Layout]], + // If there is a recursive pointer latent within this layout, coming from a containing layout. + when_recursive: &WhenRecursive, ) -> Result> { let mut result = Vec::with_capacity(tags.len()); for tag in tags.iter() { - result.push(build_tuple_type(builder, tag)?); + result.push(build_tuple_type(builder, tag, when_recursive)?); } Ok(result) @@ -1701,7 +1742,7 @@ fn layout_spec_help( builder.add_tuple_type(&[]) } UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types(builder, tags)?; + let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?; builder.add_union_type(&variant_types) } UnionLayout::Recursive(_) diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 518bc0c3e4..3d8ac5e456 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -28,7 +28,7 @@ const LLVM_VERSION: &str = "12"; // them after type checking (like Elm does) so we can complete the entire // `roc check` process without needing to monomorphize. /// Returns the number of problems reported. -pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize { +pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> Problems { report_problems_help( loaded.total_problems(), &loaded.sources, @@ -39,7 +39,7 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize ) } -pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { +pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> Problems { report_problems_help( loaded.total_problems(), &loaded.sources, @@ -50,6 +50,23 @@ pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { ) } +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Problems { + pub errors: usize, + pub warnings: usize, +} + +impl Problems { + pub fn exit_code(&self) -> i32 { + // 0 means no problems, 1 means errors, 2 means warnings + if self.errors > 0 { + 1 + } else { + self.warnings.min(1) as i32 + } + } +} + fn report_problems_help( total_problems: usize, sources: &MutMap)>, @@ -57,7 +74,7 @@ fn report_problems_help( can_problems: &mut MutMap>, type_problems: &mut MutMap>, mono_problems: &mut MutMap>, -) -> usize { +) -> Problems { use roc_reporting::report::{ can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*, DEFAULT_PALETTE, @@ -144,13 +161,13 @@ fn report_problems_help( if errors.is_empty() { problems_reported = warnings.len(); - for warning in warnings { + for warning in warnings.iter() { println!("\n{}\n", warning); } } else { problems_reported = errors.len(); - for error in errors { + for error in errors.iter() { println!("\n{}\n", error); } } @@ -165,7 +182,10 @@ fn report_problems_help( println!("{}\u{001B}[0m\n", Report::horizontal_rule(&palette)); } - problems_reported + Problems { + errors: errors.len(), + warnings: warnings.len(), + } } #[cfg(not(feature = "llvm"))] diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index 159b8429a3..b1d092053e 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -17,6 +17,7 @@ roc_builtins = { path = "../builtins" } ven_graph = { path = "../../vendor/pathfinding" } bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" +bitvec = "1" [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/compiler/can/src/abilities.rs b/compiler/can/src/abilities.rs index b162a1355f..43f958eece 100644 --- a/compiler/can/src/abilities.rs +++ b/compiler/can/src/abilities.rs @@ -85,6 +85,10 @@ impl AbilitiesStore { ); } + pub fn is_ability(&self, ability: Symbol) -> bool { + self.members_of_ability.contains_key(&ability) + } + /// Records a specialization of `ability_member` with specialized type `implementing_type`. /// Entries via this function are considered a source of truth. It must be ensured that a /// specialization is validated before being registered here. diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 97b74f2101..b6bdff3e87 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,6 +1,6 @@ use crate::env::Env; use crate::scope::Scope; -use roc_collections::all::{ImMap, MutMap, MutSet, SendMap, VecSet}; +use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet}; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader}; @@ -8,10 +8,11 @@ use roc_problem::can::ShadowKind; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::{ - Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, TypeExtension, + name_type_var, Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, + TypeExtension, }; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Annotation { pub typ: Type, pub introduced_variables: IntroducedVariables, @@ -32,6 +33,20 @@ impl<'a> NamedOrAbleVariable<'a> { NamedOrAbleVariable::Able(av) => av.first_seen, } } + + pub fn name(&self) -> &Lowercase { + match self { + NamedOrAbleVariable::Named(nv) => &nv.name, + NamedOrAbleVariable::Able(av) => &av.name, + } + } + + pub fn variable(&self) -> Variable { + match self { + NamedOrAbleVariable::Named(nv) => nv.variable, + NamedOrAbleVariable::Able(av) => av.variable, + } + } } /// A named type variable, not bound to an ability. @@ -53,14 +68,14 @@ pub struct AbleVariable { pub first_seen: Region, } -#[derive(Clone, Debug, PartialEq, Default)] +#[derive(Clone, Debug, Default)] pub struct IntroducedVariables { pub wildcards: Vec>, pub lambda_sets: Vec, pub inferred: Vec>, pub named: VecSet, pub able: VecSet, - pub host_exposed_aliases: MutMap, + pub host_exposed_aliases: VecMap, } impl IntroducedVariables { @@ -125,7 +140,7 @@ impl IntroducedVariables { self.lambda_sets.extend(other.lambda_sets.iter().copied()); self.inferred.extend(other.inferred.iter().copied()); self.host_exposed_aliases - .extend(other.host_exposed_aliases.clone()); + .extend(other.host_exposed_aliases.iter().map(|(k, v)| (*k, *v))); self.named.extend(other.named.iter().cloned()); self.able.extend(other.able.iter().cloned()); @@ -148,19 +163,13 @@ impl IntroducedVariables { .map(|(_, var)| var) } + pub fn iter_named(&self) -> impl Iterator { + (self.named.iter().map(NamedOrAbleVariable::Named)) + .chain(self.able.iter().map(NamedOrAbleVariable::Able)) + } + pub fn named_var_by_name(&self, name: &Lowercase) -> Option { - if let Some(nav) = self - .named - .iter() - .find(|nv| &nv.name == name) - .map(NamedOrAbleVariable::Named) - { - return Some(nav); - } - self.able - .iter() - .find(|av| &av.name == name) - .map(NamedOrAbleVariable::Able) + self.iter_named().find(|v| v.name() == name) } pub fn collect_able(&self) -> Vec { @@ -187,37 +196,8 @@ fn malformed(env: &mut Env, region: Region, name: &str) { env.problem(roc_problem::can::Problem::RuntimeError(problem)); } +/// Canonicalizes a top-level type annotation. pub fn canonicalize_annotation( - env: &mut Env, - scope: &mut Scope, - annotation: &roc_parse::ast::TypeAnnotation, - region: Region, - var_store: &mut VarStore, -) -> Annotation { - let mut introduced_variables = IntroducedVariables::default(); - let mut references = VecSet::default(); - let mut aliases = SendMap::default(); - - let typ = can_annotation_help( - env, - annotation, - region, - scope, - var_store, - &mut introduced_variables, - &mut aliases, - &mut references, - ); - - Annotation { - typ, - introduced_variables, - references, - aliases, - } -} - -pub fn canonicalize_annotation_with_possible_clauses( env: &mut Env, scope: &mut Scope, annotation: &TypeAnnotation, @@ -418,6 +398,13 @@ pub fn find_type_def_symbols( result } +fn find_fresh_var_name(introduced_variables: &IntroducedVariables) -> Lowercase { + name_type_var(0, &mut introduced_variables.iter_named(), |var, str| { + var.name().as_str() == str + }) + .0 +} + #[allow(clippy::too_many_arguments)] fn can_annotation_help( env: &mut Env, @@ -439,7 +426,7 @@ fn can_annotation_help( let arg_ann = can_annotation_help( env, &arg.value, - region, + arg.region, scope, var_store, introduced_variables, @@ -477,6 +464,21 @@ fn can_annotation_help( references.insert(symbol); + if scope.abilities_store.is_ability(symbol) { + let fresh_ty_var = find_fresh_var_name(introduced_variables); + + env.problem(roc_problem::can::Problem::AbilityUsedAsType( + fresh_ty_var.clone(), + symbol, + region, + )); + + // Generate an variable bound to the ability so we can keep compiling. + let var = var_store.fresh(); + introduced_variables.insert_able(fresh_ty_var, Loc::at(region, var), symbol); + return Type::Variable(var); + } + for arg in *type_arguments { let arg_ann = can_annotation_help( env, @@ -828,8 +830,7 @@ fn can_annotation_help( Where(_annotation, clauses) => { debug_assert!(!clauses.is_empty()); - // Has clauses are allowed only on the top level of an ability member signature (for - // now), which we handle elsewhere. + // Has clauses are allowed only on the top level of a signature, which we handle elsewhere. env.problem(roc_problem::can::Problem::IllegalHasClause { region: Region::across_all(clauses.iter().map(|clause| &clause.region)), }); diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index 58d7b1f065..c858834263 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -601,7 +601,7 @@ impl Constraints { roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8); -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub enum Constraint { Eq( EitherIndex, @@ -643,13 +643,13 @@ pub enum Constraint { ), } -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct DefTypes { pub types: Slice, pub loc_symbols: Slice<(Symbol, Region)>, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct LetConstraint { pub rigid_vars: Slice, pub flex_vars: Slice, @@ -657,7 +657,7 @@ pub struct LetConstraint { pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct IncludesTag { pub type_index: Index, pub tag_name: TagName, diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index cd4d2c79d5..4d764d337e 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1,18 +1,20 @@ use crate::abilities::MemberVariables; use crate::annotation::canonicalize_annotation; -use crate::annotation::canonicalize_annotation_with_possible_clauses; use crate::annotation::IntroducedVariables; use crate::env::Env; use crate::expr::ClosureData; use crate::expr::Expr::{self, *}; -use crate::expr::{canonicalize_expr, local_successors_with_duplicates, Output, Recursive}; +use crate::expr::{canonicalize_expr, Output, Recursive}; use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern}; use crate::procedure::References; +use crate::reference_matrix::ReferenceMatrix; +use crate::reference_matrix::TopologicalSort; use crate::scope::create_alias; use crate::scope::Scope; -use roc_collections::all::ImSet; -use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap}; +use roc_collections::{default_hasher, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::Lowercase; +use roc_module::symbol::IdentId; +use roc_module::symbol::ModuleId; use roc_module::symbol::Symbol; use roc_parse::ast; use roc_parse::ast::AbilityMember; @@ -28,9 +30,9 @@ use roc_types::types::LambdaSet; use roc_types::types::{Alias, Type}; use std::collections::HashMap; use std::fmt::Debug; -use ven_graph::{strongly_connected_components, topological_sort}; +use ven_graph::topological_sort; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Def { pub loc_pattern: Loc, pub loc_expr: Loc, @@ -39,7 +41,7 @@ pub struct Def { pub annotation: Option, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Annotation { pub signature: Type, pub introduced_variables: IntroducedVariables, @@ -57,7 +59,7 @@ pub struct CanDefs { /// A Def that has had patterns and type annnotations canonicalized, /// 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, Clone, PartialEq)] +#[derive(Debug, Clone)] enum PendingValueDef<'a> { /// A standalone annotation with no body AnnotationOnly( @@ -80,7 +82,7 @@ enum PendingValueDef<'a> { ), } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] enum PendingTypeDef<'a> { /// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively. Alias { @@ -98,6 +100,7 @@ enum PendingTypeDef<'a> { /// An invalid alias, that is ignored in the rest of the pipeline /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` /// with an incorrect pattern + #[allow(dead_code)] InvalidAlias { kind: AliasKind }, /// An invalid ability, that is ignored in the rest of the pipeline. @@ -106,7 +109,7 @@ enum PendingTypeDef<'a> { } // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum Declaration { Declare(Def), @@ -142,7 +145,7 @@ fn sort_type_defs_before_introduction( let all_successors_with_self = |symbol: &Symbol| referenced_symbols[symbol].iter().copied(); - strongly_connected_components(&defined_symbols, all_successors_with_self) + ven_graph::strongly_connected_components(&defined_symbols, all_successors_with_self) }; // then sort the strongly connected components @@ -241,10 +244,12 @@ pub fn canonicalize_defs<'a>( let pending_type_defs = type_defs .into_iter() .filter_map(|loc_def| { - to_pending_type_def(env, loc_def.value, &mut scope).map(|(new_output, pending_def)| { - output.union(new_output); - pending_def - }) + to_pending_type_def(env, loc_def.value, &mut scope, pattern_type).map( + |(new_output, pending_def)| { + output.union(new_output); + pending_def + }, + ) }) .collect::>(); @@ -316,8 +321,14 @@ pub fn canonicalize_defs<'a>( match type_defs.remove(&type_name).unwrap() { TypeDef::AliasLike(name, vars, ann, kind) => { let symbol = name.value; - let can_ann = - canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store); + let can_ann = canonicalize_annotation( + env, + &mut scope, + &ann.value, + ann.region, + var_store, + &abilities_in_scope, + ); // Does this alias reference any abilities? For now, we don't permit that. let ability_references = can_ann @@ -335,8 +346,7 @@ pub fn canonicalize_defs<'a>( // Record all the annotation's references in output.references.lookups for symbol in can_ann.references { - output.references.type_lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + output.references.insert_type_lookup(symbol); } let mut can_vars: Vec> = Vec::with_capacity(vars.len()); @@ -435,7 +445,7 @@ pub fn canonicalize_defs<'a>( let mut can_members = Vec::with_capacity(members.len()); for member in members { - let member_annot = canonicalize_annotation_with_possible_clauses( + let member_annot = canonicalize_annotation( env, &mut scope, &member.typ.value, @@ -446,8 +456,7 @@ pub fn canonicalize_defs<'a>( // Record all the annotation's references in output.references.lookups for symbol in member_annot.references { - output.references.type_lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + output.references.insert_type_lookup(symbol); } let name_region = member.name.region; @@ -471,6 +480,10 @@ pub fn canonicalize_defs<'a>( } }; + if pattern_type == PatternType::TopLevelDef { + env.top_level_symbols.insert(member_sym); + } + // What variables in the annotation are bound to the parent ability, and what variables // are bound to some other ability? let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) = @@ -566,9 +579,17 @@ pub fn canonicalize_defs<'a>( // once we've finished assembling the entire scope. let mut pending_value_defs = Vec::with_capacity(value_defs.len()); for loc_def in value_defs.into_iter() { - match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) { + let mut new_output = Output::default(); + match to_pending_value_def( + env, + var_store, + loc_def.value, + &mut scope, + &mut new_output, + pattern_type, + ) { None => { /* skip */ } - Some((new_output, pending_def)) => { + Some(pending_def) => { // store the top-level defs, used to ensure that closures won't capture them if let PatternType::TopLevelDef = pattern_type { match &pending_def { @@ -603,6 +624,7 @@ pub fn canonicalize_defs<'a>( var_store, &mut refs_by_symbol, &mut aliases, + &abilities_in_scope, ); // TODO we should do something with these references; they include @@ -641,12 +663,143 @@ pub fn canonicalize_defs<'a>( ) } +#[derive(Debug)] +struct DefOrdering { + home: ModuleId, + symbol_to_id: Vec<(IdentId, u32)>, + + // an length x length matrix indicating who references who + references: ReferenceMatrix, + + // references without looking into closure bodies. + // Used to spot definitely-wrong recursion + direct_references: ReferenceMatrix, + + length: u32, +} + +impl DefOrdering { + fn with_capacity(home: ModuleId, capacity: usize) -> Self { + Self { + home, + symbol_to_id: Vec::with_capacity(capacity), + references: ReferenceMatrix::new(capacity), + direct_references: ReferenceMatrix::new(capacity), + length: capacity as u32, + } + } + + fn from_defs_by_symbol( + env: &Env, + can_defs_by_symbol: &MutMap, + refs_by_symbol: &MutMap, + ) -> Self { + let mut this = Self::with_capacity(env.home, can_defs_by_symbol.len()); + + for (i, symbol) in can_defs_by_symbol.keys().enumerate() { + debug_assert_eq!(env.home, symbol.module_id()); + + this.symbol_to_id.push((symbol.ident_id(), i as u32)); + } + + for (symbol, (_, references)) in refs_by_symbol.iter() { + let def_id = this.get_id(*symbol).unwrap(); + + for referenced in references.value_lookups() { + this.register_reference(def_id, *referenced); + this.register_direct_reference(def_id, *referenced); + } + + for referenced in references.calls() { + this.register_reference(def_id, *referenced); + this.register_direct_reference(def_id, *referenced); + } + + if let Some(references) = env.closures.get(symbol) { + for referenced in references.value_lookups() { + this.register_reference(def_id, *referenced); + } + + for referenced in references.calls() { + this.register_reference(def_id, *referenced); + } + } + } + + this + } + + fn get_id(&self, symbol: Symbol) -> Option { + if symbol.module_id() != self.home { + return None; + } + + let target = symbol.ident_id(); + + for (ident_id, def_id) in self.symbol_to_id.iter() { + if target == *ident_id { + return Some(*def_id); + } + } + + None + } + + fn get_symbol(&self, id: u32) -> Option { + for (ident_id, def_id) in self.symbol_to_id.iter() { + if id == *def_id { + return Some(Symbol::new(self.home, *ident_id)); + } + } + + None + } + + fn register_direct_reference(&mut self, id: u32, referenced: Symbol) { + if let Some(ref_id) = self.get_id(referenced) { + self.direct_references + .set_row_col(id as usize, ref_id as usize, true); + } + } + + fn register_reference(&mut self, id: u32, referenced: Symbol) { + if let Some(ref_id) = self.get_id(referenced) { + self.references + .set_row_col(id as usize, ref_id as usize, true); + } + } + + fn is_self_recursive(&self, id: u32) -> bool { + debug_assert!(id < self.length); + + // id'th row, id'th column + let index = (id * self.length) + id; + + self.references.get(index as usize) + } + + #[inline(always)] + fn successors(&self, id: u32) -> impl Iterator + '_ { + self.references + .references_for(id as usize) + .map(|x| x as u32) + } + + #[inline(always)] + fn successors_without_self(&self, id: u32) -> impl Iterator + '_ { + self.successors(id).filter(move |x| *x != id) + } +} + #[inline(always)] pub fn sort_can_defs( env: &mut Env<'_>, defs: CanDefs, mut output: Output, ) -> (Result, RuntimeError>, Output) { + let def_ids = + DefOrdering::from_defs_by_symbol(env, &defs.can_defs_by_symbol, &defs.refs_by_symbol); + let CanDefs { refs_by_symbol, mut can_defs_by_symbol, @@ -657,153 +810,18 @@ pub fn sort_can_defs( output.aliases.insert(symbol, alias); } - let mut defined_symbols: Vec = Vec::new(); - - for symbol in can_defs_by_symbol.keys() { - defined_symbols.push(*symbol); - } - - // Use topological sort to reorder the defs based on their dependencies to one another. - // This way, during code gen, no def will refer to a value that hasn't been initialized yet. - // As a bonus, the topological sort also reveals any cycles between the defs, allowing - // us to give a CircularAssignment error for invalid (mutual) recursion, and a `DeclareRec` for mutually - // recursive definitions. - - // All successors that occur in the body of a symbol. - let all_successors_without_self = |symbol: &Symbol| -> Vec { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); - - // if the current symbol is a closure, peek into its body - if let Some(References { value_lookups, .. }) = env.closures.get(symbol) { - let home = env.home; - - for lookup in value_lookups.iter() { - if lookup != symbol && lookup.module_id() == home { - // DO NOT register a self-call behind a lambda! - // - // We allow `boom = \_ -> boom {}`, but not `x = x` - loc_succ.push(*lookup); - } - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); - - loc_succ.sort(); - loc_succ.dedup(); - - loc_succ - } - None => vec![], - } - }; - - // All successors that occur in the body of a symbol, including the symbol itself - // This is required to determine whether a symbol is recursive. Recursive symbols - // (that are not faulty) always need a DeclareRec, even if there is just one symbol in the - // group - let mut all_successors_with_self = |symbol: &Symbol| -> Vec { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); - - // if the current symbol is a closure, peek into its body - if let Some(References { value_lookups, .. }) = env.closures.get(symbol) { - for lookup in value_lookups.iter() { - loc_succ.push(*lookup); - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); - - loc_succ.sort(); - loc_succ.dedup(); - - loc_succ - } - None => vec![], - } - }; - - // If a symbol is a direct successor of itself, there is an invalid cycle. - // The difference with the function above is that this one does not look behind lambdas, - // but does consider direct self-recursion. - let direct_successors = |symbol: &Symbol| -> Vec { - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - let mut loc_succ = local_successors_with_duplicates(references, &env.closures); - - // NOTE: if the symbol is a closure we DONT look into its body - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols.contains(key)); - - // NOTE: direct recursion does matter here: `x = x` is invalid recursion! - - loc_succ.sort(); - loc_succ.dedup(); - - loc_succ - } - None => vec![], - } - }; - // TODO also do the same `addDirects` check elm/compiler does, so we can // report an error if a recursive definition can't possibly terminate! - match ven_graph::topological_sort_into_groups( - defined_symbols.as_slice(), - all_successors_without_self, - ) { - Ok(groups) => { + match def_ids.references.topological_sort_into_groups() { + TopologicalSort::Groups { groups } => { let mut declarations = Vec::new(); // groups are in reversed order for group in groups.into_iter().rev() { group_to_declaration( + &def_ids, &group, &env.closures, - &mut all_successors_with_self, &mut can_defs_by_symbol, &mut declarations, ); @@ -811,7 +829,10 @@ pub fn sort_can_defs( (Ok(declarations), output) } - Err((mut groups, nodes_in_cycle)) => { + TopologicalSort::HasCycles { + mut groups, + nodes_in_cycle, + } => { let mut declarations = Vec::new(); let mut problems = Vec::new(); @@ -826,8 +847,11 @@ pub fn sort_can_defs( // // foo = if b then foo else bar - for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) - { + let sccs = def_ids + .references + .strongly_connected_components(&nodes_in_cycle); + + for cycle in sccs { // check whether the cycle is faulty, which is when it has // a direct successor in the current cycle. This catches things like: // @@ -838,13 +862,10 @@ pub fn sort_can_defs( // p = q // q = p let is_invalid_cycle = match cycle.get(0) { - Some(symbol) => { - let mut succs = direct_successors(symbol); - - succs.retain(|key| cycle.contains(key)); - - !succs.is_empty() - } + Some(def_id) => def_ids + .direct_references + .references_for(*def_id as usize) + .any(|key| cycle.contains(&(key as u32))), None => false, }; @@ -852,18 +873,19 @@ pub fn sort_can_defs( // We want to show the entire cycle in the error message, so expand it out. let mut entries = Vec::new(); - for symbol in &cycle { - match refs_by_symbol.get(symbol) { + for def_id in &cycle { + let symbol = def_ids.get_symbol(*def_id).unwrap(); + match refs_by_symbol.get(&symbol) { None => unreachable!( r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, symbol, refs_by_symbol ), Some((region, _)) => { let expr_region = - can_defs_by_symbol.get(symbol).unwrap().loc_expr.region; + can_defs_by_symbol.get(&symbol).unwrap().loc_expr.region; let entry = CycleEntry { - symbol: *symbol, + symbol, symbol_region: *region, expr_region, }; @@ -911,7 +933,7 @@ pub fn sort_can_defs( // for each symbol in this group for symbol in &groups[*group_id] { // find its successors - for succ in all_successors_without_self(symbol) { + for succ in def_ids.successors_without_self(*symbol) { // and add its group to the result match symbol_to_group_index.get(&succ) { Some(index) => { @@ -935,9 +957,9 @@ pub fn sort_can_defs( let group = &groups[*group_id]; group_to_declaration( + &def_ids, group, &env.closures, - &mut all_successors_with_self, &mut can_defs_by_symbol, &mut declarations, ); @@ -957,22 +979,14 @@ pub fn sort_can_defs( } fn group_to_declaration( - group: &[Symbol], + def_ids: &DefOrdering, + group: &[u32], closures: &MutMap, - successors: &mut dyn FnMut(&Symbol) -> Vec, can_defs_by_symbol: &mut MutMap, declarations: &mut Vec, ) { use Declaration::*; - // We want only successors in the current group, otherwise definitions get duplicated - let filtered_successors = |symbol: &Symbol| -> Vec { - let mut result = successors(symbol); - - result.retain(|key| group.contains(key)); - result - }; - // Patterns like // // { x, y } = someDef @@ -982,27 +996,34 @@ fn group_to_declaration( // for a definition, so every definition is only inserted (thus typechecked and emitted) once let mut seen_pattern_regions: Vec = Vec::with_capacity(2); - for cycle in strongly_connected_components(group, filtered_successors) { - if cycle.len() == 1 { - let symbol = &cycle[0]; + let sccs = def_ids.references.strongly_connected_components(group); - match can_defs_by_symbol.remove(symbol) { + for cycle in sccs { + if cycle.len() == 1 { + let def_id = cycle[0]; + let symbol = def_ids.get_symbol(def_id).unwrap(); + + match can_defs_by_symbol.remove(&symbol) { Some(mut new_def) => { - // Determine recursivity of closures that are not tail-recursive + // there is only one definition in this cycle, so we only have + // to check whether it recurses with itself; there is nobody else + // to recurse with, or they would also be in this cycle. + let is_self_recursive = def_ids.is_self_recursive(def_id); + if let Closure(ClosureData { recursive: recursive @ Recursive::NotRecursive, .. }) = &mut new_def.loc_expr.value { - *recursive = closure_recursivity(*symbol, closures); + if is_self_recursive { + *recursive = Recursive::Recursive + } } - let is_recursive = successors(symbol).contains(symbol); - if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { seen_pattern_regions.push(new_def.loc_pattern.region); - if is_recursive { + if is_self_recursive { declarations.push(DeclareRec(vec![new_def])); } else { declarations.push(Declare(new_def)); @@ -1015,7 +1036,8 @@ fn group_to_declaration( let mut can_defs = Vec::new(); // Topological sort gives us the reverse of the sorting we want! - for symbol in cycle.into_iter().rev() { + for def_id in cycle.into_iter().rev() { + let symbol = def_ids.get_symbol(def_id).unwrap(); match can_defs_by_symbol.remove(&symbol) { Some(mut new_def) => { // Determine recursivity of closures that are not tail-recursive @@ -1146,6 +1168,7 @@ fn canonicalize_pending_value_def<'a>( var_store: &mut VarStore, refs_by_symbol: &mut MutMap, aliases: &mut ImMap, + abilities_in_scope: &[Symbol], ) -> Output { use PendingValueDef::*; @@ -1157,14 +1180,19 @@ fn canonicalize_pending_value_def<'a>( AnnotationOnly(_, loc_can_pattern, loc_ann) => { // annotation sans body cannot introduce new rigids that are visible in other annotations // but the rigids can show up in type error messages, so still register them - let type_annotation = - canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); + let type_annotation = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + abilities_in_scope, + ); // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { - output.references.type_lookups.insert(*symbol); - output.references.referenced_type_defs.insert(*symbol); + output.references.insert_type_lookup(*symbol); } add_annotation_aliases(&type_annotation, aliases); @@ -1278,13 +1306,18 @@ fn canonicalize_pending_value_def<'a>( } TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { - let type_annotation = - canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); + let type_annotation = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + abilities_in_scope, + ); // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { - output.references.type_lookups.insert(*symbol); - output.references.referenced_type_defs.insert(*symbol); + output.references.insert_type_lookup(*symbol); } add_annotation_aliases(&type_annotation, aliases); @@ -1363,7 +1396,7 @@ fn canonicalize_pending_value_def<'a>( // Recursion doesn't count as referencing. (If it did, all recursive functions // would result in circular def errors!) refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { - refs.value_lookups.remove(&symbol); + refs.remove_value_lookup(&symbol); }); // renamed_closure_def = Some(&symbol); @@ -1503,7 +1536,7 @@ fn canonicalize_pending_value_def<'a>( // Recursion doesn't count as referencing. (If it did, all recursive functions // would result in circular def errors!) refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { - refs.value_lookups.remove(&symbol); + refs.remove_value_lookup(&symbol); }); loc_can_expr.value = Closure(ClosureData { @@ -1598,8 +1631,7 @@ pub fn can_defs_with_return<'a>( // 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_value_lookup(symbol) - && !output.references.has_type_lookup(symbol) + if !output.references.has_type_or_value_lookup(symbol) && !scope.abilities_store.is_specialization_name(symbol) { env.problem(Problem::UnusedDef(symbol, region)); @@ -1647,7 +1679,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap) -> let mut stack = Vec::new(); if let Some(references) = closures.get(&symbol) { - for v in references.calls.iter() { + for v in references.calls() { stack.push(*v); } @@ -1663,7 +1695,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap) -> // if it calls any functions if let Some(nested_references) = closures.get(&nested_symbol) { // add its called to the stack - for v in nested_references.calls.iter() { + for v in nested_references.calls() { stack.push(*v); } } @@ -1679,6 +1711,7 @@ fn to_pending_type_def<'a>( env: &mut Env<'a>, def: &'a ast::TypeDef<'a>, scope: &mut Scope, + pattern_type: PatternType, ) -> Option<(Output, PendingTypeDef<'a>)> { use ast::TypeDef::*; @@ -1762,6 +1795,19 @@ fn to_pending_type_def<'a>( } } + Ability { + header, members, .. + } if pattern_type != PatternType::TopLevelDef => { + let header_region = header.region(); + let region = Region::span_across( + &header_region, + &members.last().map(|m| m.region()).unwrap_or(header_region), + ); + env.problem(Problem::AbilityNotOnToplevel { region }); + + Some((Output::default(), PendingTypeDef::InvalidAbility)) + } + Ability { header: TypeHeader { name, vars }, members, @@ -1811,41 +1857,46 @@ fn to_pending_value_def<'a>( var_store: &mut VarStore, def: &'a ast::ValueDef<'a>, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, -) -> Option<(Output, PendingValueDef<'a>)> { +) -> Option> { use ast::ValueDef::*; match def { Annotation(loc_pattern, loc_ann) => { // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - Some(( - output, - PendingValueDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), + Some(PendingValueDef::AnnotationOnly( + loc_pattern, + loc_can_pattern, + loc_ann, )) } Body(loc_pattern, loc_expr) => { // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - Some(( - output, - PendingValueDef::Body(loc_pattern, loc_can_pattern, loc_expr), + Some(PendingValueDef::Body( + loc_pattern, + loc_can_pattern, + loc_expr, )) } @@ -1864,18 +1915,21 @@ fn to_pending_value_def<'a>( // { x, y ? False } = rec // // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = canonicalize_def_header_pattern( + let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, + output, pattern_type, &body_pattern.value, body_pattern.region, ); - Some(( - output, - PendingValueDef::TypedBody(body_pattern, loc_can_pattern, ann_type, body_expr), + Some(PendingValueDef::TypedBody( + body_pattern, + loc_can_pattern, + ann_type, + body_expr, )) } else { // the pattern of the annotation does not match the pattern of the body direc @@ -1919,7 +1973,8 @@ fn correct_mutual_recursive_type_alias<'a>( // TODO investigate should this be in a loop? let defined_symbols: Vec = original_aliases.keys().copied().collect(); - let cycles = strongly_connected_components(&defined_symbols, all_successors_with_self); + let cycles = + ven_graph::strongly_connected_components(&defined_symbols, all_successors_with_self); let mut solved_aliases = ImMap::default(); for cycle in cycles { diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 3695757117..2e58dc1bd1 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -4,7 +4,7 @@ use crate::env::Env; use crate::expr::{ClosureData, Expr, Recursive}; use crate::pattern::Pattern; use crate::scope::Scope; -use roc_collections::all::{SendMap, VecSet}; +use roc_collections::{SendMap, VecSet}; use roc_module::called_via::CalledVia; use roc_module::ident::TagName; use roc_module::symbol::Symbol; diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index 1d63c283c1..8c3f3fb8b1 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -1,5 +1,5 @@ use crate::procedure::References; -use roc_collections::all::{MutMap, VecSet}; +use roc_collections::{MutMap, VecSet}; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; diff --git a/compiler/can/src/expected.rs b/compiler/can/src/expected.rs index aad8bd42b4..df156e00fc 100644 --- a/compiler/can/src/expected.rs +++ b/compiler/can/src/expected.rs @@ -2,7 +2,7 @@ use crate::pattern::Pattern; use roc_region::all::{Loc, Region}; use roc_types::types::{AnnotationSource, PReason, Reason}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub enum Expected { NoExpectation(T), FromAnnotation(Loc, usize, AnnotationSource, T), @@ -10,7 +10,7 @@ pub enum Expected { } /// Like Expected, but for Patterns. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub enum PExpected { NoExpectation(T), ForReason(PReason, T, Region), diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index fb14cf2db1..2dade79cd6 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -9,7 +9,7 @@ use crate::num::{ use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; use crate::scope::Scope; -use roc_collections::all::{MutMap, MutSet, SendMap, VecSet}; +use roc_collections::{MutSet, SendMap, VecMap, VecSet}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -23,12 +23,12 @@ use roc_types::types::{Alias, LambdaSet, Type}; use std::fmt::{Debug, Display}; use std::{char, u32}; -#[derive(Clone, Default, Debug, PartialEq)] +#[derive(Clone, Default, Debug)] pub struct Output { pub references: References, pub tail_call: Option, pub introduced_variables: IntroducedVariables, - pub aliases: SendMap, + pub aliases: VecMap, pub non_closures: VecSet, } @@ -62,7 +62,7 @@ impl Display for IntValue { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum Expr { // Literals @@ -194,7 +194,7 @@ pub enum Expr { // Compiles, but will crash if reached RuntimeError(RuntimeError), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct ClosureData { pub function_type: Variable, pub closure_type: Variable, @@ -271,7 +271,7 @@ impl AccessorData { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Field { pub var: Variable, // The region of the full `foo: f bar`, rather than just `f bar` @@ -286,7 +286,7 @@ pub enum Recursive { TailRecursive = 2, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct WhenBranch { pub patterns: Vec>, pub value: Loc, @@ -487,8 +487,7 @@ pub fn canonicalize_expr<'a>( } Ok((name, opaque_def)) => { let argument = Box::new(args.pop().unwrap()); - output.references.referenced_type_defs.insert(name); - output.references.type_lookups.insert(name); + output.references.insert_type_lookup(name); let (type_arguments, lambda_set_variables, specialized_def_type) = freshen_opaque_def(var_store, opaque_def); @@ -518,7 +517,7 @@ pub fn canonicalize_expr<'a>( let expr = match fn_expr.value { Var(symbol) => { - output.references.calls.insert(symbol); + output.references.insert_call(symbol); // we're tail-calling a symbol by name, check if it's the tail-callable symbol output.tail_call = match &env.tailcallable_symbol { @@ -628,26 +627,23 @@ pub fn canonicalize_expr<'a>( let mut can_args = Vec::with_capacity(loc_arg_patterns.len()); let mut output = Output::default(); - let mut bound_by_argument_patterns = MutSet::default(); - for loc_pattern in loc_arg_patterns.iter() { - let (new_output, can_arg) = canonicalize_pattern( + let can_argument_pattern = canonicalize_pattern( env, var_store, &mut scope, + &mut output, FunctionArg, &loc_pattern.value, loc_pattern.region, ); - bound_by_argument_patterns - .extend(new_output.references.bound_symbols.iter().copied()); - - output.union(new_output); - - can_args.push((var_store.fresh(), can_arg)); + can_args.push((var_store.fresh(), can_argument_pattern)); } + let bound_by_argument_patterns: Vec<_> = + output.references.bound_symbols().copied().collect(); + let (loc_body_expr, new_output) = canonicalize_expr( env, var_store, @@ -656,18 +652,14 @@ pub fn canonicalize_expr<'a>( &loc_body_expr.value, ); - let mut captured_symbols: MutSet = new_output - .references - .value_lookups - .iter() - .copied() - .collect(); + let mut captured_symbols: MutSet = + new_output.references.value_lookups().copied().collect(); // filter out the closure's name itself captured_symbols.remove(&symbol); // symbols bound either in this pattern or deeper down are not captured! - captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); + captured_symbols.retain(|s| !new_output.references.bound_symbols().any(|x| x == s)); captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); // filter out top-level symbols @@ -685,7 +677,7 @@ pub fn canonicalize_expr<'a>( // filter out aliases debug_assert!(captured_symbols .iter() - .all(|s| !output.references.referenced_type_defs.contains(s))); + .all(|s| !output.references.references_type_def(*s))); // captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s)); // filter out functions that don't close over anything @@ -703,7 +695,7 @@ pub fn canonicalize_expr<'a>( // We shouldn't ultimately count arguments as referenced locals. Otherwise, // we end up with weird conclusions like the expression (\x -> x + 1) // references the (nonexistent) local variable x! - output.references.value_lookups.remove(sub_symbol); + output.references.remove_value_lookup(sub_symbol); } } @@ -1040,17 +1032,16 @@ fn canonicalize_when_branch<'a>( // TODO report symbols not bound in all patterns for loc_pattern in branch.patterns.iter() { - let (new_output, can_pattern) = canonicalize_pattern( + let can_pattern = canonicalize_pattern( env, var_store, &mut scope, + output, WhenBranch, &loc_pattern.value, loc_pattern.region, ); - output.union(new_output); - patterns.push(can_pattern); } @@ -1078,10 +1069,8 @@ fn canonicalize_when_branch<'a>( for (symbol, region) in scope.symbols() { let symbol = *symbol; - if !output.references.has_value_lookup(symbol) - && !output.references.has_type_lookup(symbol) - && !branch_output.references.has_value_lookup(symbol) - && !branch_output.references.has_type_lookup(symbol) + if !output.references.has_type_or_value_lookup(symbol) + && !branch_output.references.has_type_or_value_lookup(symbol) && !original_scope.contains_symbol(symbol) && !scope.abilities_store.is_specialization_name(symbol) { @@ -1102,34 +1091,6 @@ fn canonicalize_when_branch<'a>( ) } -pub fn local_successors_with_duplicates<'a>( - references: &'a References, - closures: &'a MutMap, -) -> Vec { - let mut answer: Vec<_> = references.value_lookups.iter().copied().collect(); - - let mut stack: Vec<_> = references.calls.iter().copied().collect(); - let mut seen = Vec::new(); - - while let Some(symbol) = stack.pop() { - if seen.contains(&symbol) { - continue; - } - - if let Some(references) = closures.get(&symbol) { - answer.extend(references.value_lookups.iter().copied()); - stack.extend(references.calls.iter().copied()); - - seen.push(symbol); - } - } - - answer.sort(); - answer.dedup(); - - answer -} - enum CanonicalizeRecordProblem { InvalidOptionalValue { field_name: Lowercase, @@ -1255,7 +1216,7 @@ fn canonicalize_var_lookup( // Look it up in scope! match scope.lookup(&(*ident).into(), region) { Ok(symbol) => { - output.references.value_lookups.insert(symbol); + output.references.insert_value_lookup(symbol); Var(symbol) } @@ -1270,7 +1231,7 @@ fn canonicalize_var_lookup( // Look it up in the env! match env.qualified_lookup(module_name, ident, region) { Ok(symbol) => { - output.references.value_lookups.insert(symbol); + output.references.insert_value_lookup(symbol); Var(symbol) } @@ -1726,7 +1687,7 @@ fn flatten_str_lines<'a>( Interpolated(loc_expr) => { if is_valid_interpolation(loc_expr.value) { // Interpolations desugar to Str.concat calls - output.references.calls.insert(Symbol::STR_CONCAT); + output.references.insert_call(Symbol::STR_CONCAT); if !buf.is_empty() { segments.push(StrSegment::Plaintext(buf.into())); diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index f22d1a1fe6..4e90bf4d4d 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -15,5 +15,6 @@ pub mod num; pub mod operator; pub mod pattern; pub mod procedure; +mod reference_matrix; pub mod scope; pub mod string; diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 974230051c..b34aba4fc5 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -7,7 +7,7 @@ use crate::operator::desugar_def; use crate::pattern::Pattern; use crate::scope::Scope; use bumpalo::Bump; -use roc_collections::all::{MutMap, SendMap, VecSet}; +use roc_collections::{MutMap, SendMap, VecSet}; use roc_module::ident::Lowercase; use roc_module::ident::{Ident, TagName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; @@ -302,8 +302,7 @@ pub fn canonicalize_module_defs<'a>( // 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_value_lookup(symbol) - && !output.references.has_type_lookup(symbol) + if !output.references.has_type_or_value_lookup(symbol) && !exposed_symbols.contains(&symbol) && !scope.abilities_store.is_specialization_name(symbol) { @@ -329,11 +328,11 @@ pub fn canonicalize_module_defs<'a>( let mut referenced_types = VecSet::default(); // Gather up all the symbols that were referenced across all the defs' lookups. - referenced_values.extend(output.references.value_lookups); - referenced_types.extend(output.references.type_lookups); + referenced_values.extend(output.references.value_lookups().copied()); + referenced_types.extend(output.references.type_lookups().copied()); // Gather up all the symbols that were referenced across all the defs' calls. - referenced_values.extend(output.references.calls); + referenced_values.extend(output.references.calls().copied()); // Gather up all the symbols that were referenced from other modules. referenced_values.extend(env.qualified_value_lookups.iter().copied()); @@ -528,11 +527,11 @@ pub fn canonicalize_module_defs<'a>( } // Incorporate any remaining output.lookups entries into references. - referenced_values.extend(output.references.value_lookups); - referenced_types.extend(output.references.type_lookups); + referenced_values.extend(output.references.value_lookups().copied()); + referenced_types.extend(output.references.type_lookups().copied()); // Incorporate any remaining output.calls entries into references. - referenced_values.extend(output.references.calls); + referenced_values.extend(output.references.calls().copied()); // Gather up all the symbols that were referenced from other modules. referenced_values.extend(env.qualified_value_lookups.iter().copied()); diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index e620f41f00..9804707b2c 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -17,7 +17,7 @@ use roc_types::types::{LambdaSet, Type}; /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum Pattern { Identifier(Symbol), AppliedTag { @@ -82,7 +82,7 @@ pub enum Pattern { MalformedPattern(MalformedPatternProblem, Region), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct RecordDestruct { pub var: Variable, pub label: Lowercase, @@ -90,7 +90,7 @@ pub struct RecordDestruct { pub typ: DestructType, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum DestructType { Required, Optional(Variable, Loc), @@ -156,13 +156,13 @@ pub fn canonicalize_def_header_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, -) -> (Output, Loc) { +) -> Loc { use roc_parse::ast::Pattern::*; - let mut output = Output::default(); match pattern { // Identifiers that shadow ability members may appear (and may only appear) at the header of a def. Identifier(name) => match scope.introduce_or_shadow_ability_member( @@ -172,7 +172,7 @@ pub fn canonicalize_def_header_pattern<'a>( region, ) { Ok((symbol, shadowing_ability_member)) => { - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); let can_pattern = match shadowing_ability_member { // A fresh identifier. None => Pattern::Identifier(symbol), @@ -182,7 +182,7 @@ pub fn canonicalize_def_header_pattern<'a>( specializes: ability_member_name, }, }; - (output, Loc::at(region, can_pattern)) + Loc::at(region, can_pattern) } Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { @@ -190,13 +190,13 @@ pub fn canonicalize_def_header_pattern<'a>( shadow: shadow.clone(), kind: ShadowKind::Variable, })); - output.references.bound_symbols.insert(new_symbol); + output.references.insert_bound(new_symbol); let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol); - (output, Loc::at(region, can_pattern)) + Loc::at(region, can_pattern) } }, - _ => canonicalize_pattern(env, var_store, scope, pattern_type, pattern, region), + _ => canonicalize_pattern(env, var_store, scope, output, pattern_type, pattern, region), } } @@ -204,14 +204,14 @@ pub fn canonicalize_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, + output: &mut Output, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, -) -> (Output, Loc) { +) -> Loc { use roc_parse::ast::Pattern::*; use PatternType::*; - let mut output = Output::default(); let can_pattern = match pattern { Identifier(name) => match scope.introduce( (*name).into(), @@ -220,7 +220,7 @@ pub fn canonicalize_pattern<'a>( region, ) { Ok(symbol) => { - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); Pattern::Identifier(symbol) } @@ -230,7 +230,7 @@ pub fn canonicalize_pattern<'a>( shadow: shadow.clone(), kind: ShadowKind::Variable, })); - output.references.bound_symbols.insert(new_symbol); + output.references.insert_bound(new_symbol); Pattern::Shadowed(original_region, shadow, new_symbol) } @@ -266,17 +266,16 @@ pub fn canonicalize_pattern<'a>( Apply(tag, patterns) => { let mut can_patterns = Vec::with_capacity(patterns.len()); for loc_pattern in *patterns { - let (new_output, can_pattern) = canonicalize_pattern( + let can_pattern = canonicalize_pattern( env, var_store, scope, + output, pattern_type, &loc_pattern.value, loc_pattern.region, ); - output.union(new_output); - can_patterns.push((var_store.fresh(), can_pattern)); } @@ -318,8 +317,7 @@ pub fn canonicalize_pattern<'a>( let (type_arguments, lambda_set_variables, specialized_def_type) = freshen_opaque_def(var_store, opaque_def); - output.references.referenced_type_defs.insert(opaque); - output.references.type_lookups.insert(opaque); + output.references.insert_type_lookup(opaque); Pattern::UnwrappedOpaque { whole_var: var_store.fresh(), @@ -443,7 +441,15 @@ pub fn canonicalize_pattern<'a>( } SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { - return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region) + return canonicalize_pattern( + env, + var_store, + scope, + output, + pattern_type, + sub_pattern, + region, + ) } RecordDestructure(patterns) => { let ext_var = var_store.fresh(); @@ -461,7 +467,7 @@ pub fn canonicalize_pattern<'a>( region, ) { Ok(symbol) => { - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); destructs.push(Loc { region: loc_pattern.region, @@ -493,17 +499,16 @@ pub fn canonicalize_pattern<'a>( RequiredField(label, loc_guard) => { // a guard does not introduce the label into scope! let symbol = scope.ignore(label.into(), &mut env.ident_ids); - let (new_output, can_guard) = canonicalize_pattern( + let can_guard = canonicalize_pattern( env, var_store, scope, + output, pattern_type, &loc_guard.value, loc_guard.region, ); - output.union(new_output); - destructs.push(Loc { region: loc_pattern.region, value: RecordDestruct { @@ -532,7 +537,7 @@ pub fn canonicalize_pattern<'a>( ); // an optional field binds the symbol! - output.references.bound_symbols.insert(symbol); + output.references.insert_bound(symbol); output.union(expr_output); @@ -598,13 +603,10 @@ pub fn canonicalize_pattern<'a>( } }; - ( - output, - Loc { - region, - value: can_pattern, - }, - ) + Loc { + region, + value: can_pattern, + } } /// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 3aa1b92153..15ab82ff9f 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -1,11 +1,10 @@ use crate::expr::Expr; use crate::pattern::Pattern; -use roc_collections::all::VecSet; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Procedure { pub name: Option>, pub is_self_tail_recursive: bool, @@ -39,40 +38,147 @@ impl Procedure { } } -/// These are all ordered sets because they end up getting traversed in a graph search -/// to determine how defs should be ordered. We want builds to be reproducible, -/// so it's important that building the same code gives the same order every time! -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Debug, Default, Clone, Copy)] +struct ReferencesBitflags(u8); + +impl ReferencesBitflags { + const VALUE_LOOKUP: Self = ReferencesBitflags(1); + const TYPE_LOOKUP: Self = ReferencesBitflags(2); + const CALL: Self = ReferencesBitflags(4); + const BOUND: Self = ReferencesBitflags(8); +} + +#[derive(Clone, Debug, Default)] pub struct References { - pub bound_symbols: VecSet, - pub type_lookups: VecSet, - pub value_lookups: VecSet, - /// Aliases or opaque types referenced - pub referenced_type_defs: VecSet, - pub calls: VecSet, + symbols: Vec, + bitflags: Vec, } impl References { - pub fn new() -> References { + pub fn new() -> Self { Self::default() } - pub fn union_mut(&mut self, other: &References) { - self.value_lookups - .extend(other.value_lookups.iter().copied()); - self.type_lookups.extend(other.type_lookups.iter().copied()); - self.calls.extend(other.calls.iter().copied()); - self.bound_symbols - .extend(other.bound_symbols.iter().copied()); - self.referenced_type_defs - .extend(other.referenced_type_defs.iter().copied()); + pub fn union_mut(&mut self, other: &Self) { + for (k, v) in other.symbols.iter().zip(other.bitflags.iter()) { + self.insert(*k, *v); + } } + // iterators + + fn retain<'a, P: Fn(&'a ReferencesBitflags) -> bool>( + &'a self, + pred: P, + ) -> impl Iterator { + self.symbols + .iter() + .zip(self.bitflags.iter()) + .filter_map(move |(a, b)| if pred(b) { Some(a) } else { None }) + } + + pub fn value_lookups(&self) -> impl Iterator { + self.retain(|b| b.0 & ReferencesBitflags::VALUE_LOOKUP.0 > 0) + } + + pub fn type_lookups(&self) -> impl Iterator { + self.retain(|b| b.0 & ReferencesBitflags::TYPE_LOOKUP.0 > 0) + } + + pub fn bound_symbols(&self) -> impl Iterator { + self.retain(|b| b.0 & ReferencesBitflags::BOUND.0 > 0) + } + + pub fn calls(&self) -> impl Iterator { + self.retain(|b| b.0 & ReferencesBitflags::CALL.0 > 0) + } + + // insert + + fn insert(&mut self, symbol: Symbol, flags: ReferencesBitflags) { + match self.symbols.iter().position(|x| *x == symbol) { + None => { + self.symbols.push(symbol); + self.bitflags.push(flags); + } + Some(index) => { + // idea: put some debug_asserts in here? + self.bitflags[index].0 |= flags.0; + } + } + } + + pub fn insert_value_lookup(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::VALUE_LOOKUP); + } + + pub fn insert_type_lookup(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::TYPE_LOOKUP); + } + + pub fn insert_bound(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::BOUND); + } + + pub fn insert_call(&mut self, symbol: Symbol) { + self.insert(symbol, ReferencesBitflags::CALL); + } + + // remove + + pub fn remove_value_lookup(&mut self, symbol: &Symbol) { + match self.symbols.iter().position(|x| x == symbol) { + None => { + // it's not in there; do nothing + } + Some(index) => { + // idea: put some debug_asserts in here? + self.bitflags[index].0 ^= ReferencesBitflags::VALUE_LOOKUP.0; + } + } + } + + // contains + pub fn has_value_lookup(&self, symbol: Symbol) -> bool { - self.value_lookups.contains(&symbol) + // println!("has a value lookup? {} {:?}", self.symbols.len(), symbol); + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & ReferencesBitflags::VALUE_LOOKUP.0 > 0 { + return true; + } + } + + false } - pub fn has_type_lookup(&self, symbol: Symbol) -> bool { - self.type_lookups.contains(&symbol) + fn has_type_lookup(&self, symbol: Symbol) -> bool { + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & ReferencesBitflags::TYPE_LOOKUP.0 > 0 { + return true; + } + } + + false + } + + pub fn has_type_or_value_lookup(&self, symbol: Symbol) -> bool { + let mask = ReferencesBitflags::VALUE_LOOKUP.0 | ReferencesBitflags::TYPE_LOOKUP.0; + let it = self.symbols.iter().zip(self.bitflags.iter()); + + for (a, b) in it { + if *a == symbol && b.0 & mask > 0 { + return true; + } + } + + false + } + + pub fn references_type_def(&self, symbol: Symbol) -> bool { + self.has_type_lookup(symbol) } } diff --git a/compiler/can/src/reference_matrix.rs b/compiler/can/src/reference_matrix.rs new file mode 100644 index 0000000000..eaadee556c --- /dev/null +++ b/compiler/can/src/reference_matrix.rs @@ -0,0 +1,244 @@ +// see if we get better performance with different integer types +pub(crate) type Element = usize; +pub(crate) type BitVec = bitvec::vec::BitVec; +pub(crate) type BitSlice = bitvec::prelude::BitSlice; + +/// A square boolean matrix used to store relations +/// +/// We use this for sorting definitions so every definition is defined before it is used. +/// This functionality is also used to spot and report invalid recursion. +#[derive(Debug)] +pub(crate) struct ReferenceMatrix { + bitvec: BitVec, + length: usize, +} + +impl ReferenceMatrix { + pub fn new(length: usize) -> Self { + Self { + bitvec: BitVec::repeat(false, length * length), + length, + } + } + + pub fn references_for(&self, row: usize) -> impl Iterator + '_ { + self.row_slice(row).iter_ones() + } + + #[inline(always)] + fn row_slice(&self, row: usize) -> &BitSlice { + &self.bitvec[row * self.length..][..self.length] + } + + #[inline(always)] + pub fn set_row_col(&mut self, row: usize, col: usize, value: bool) { + self.bitvec.set(row * self.length + col, value) + } + + #[inline(always)] + pub fn get(&self, index: usize) -> bool { + self.bitvec[index] + } +} + +// Topological sort and strongly-connected components +// +// Adapted from the Pathfinding crate v2.0.3 by Samuel Tardieu , +// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 +// +// The original source code can be found at: https://github.com/samueltardieu/pathfinding +// +// Thank you, Samuel! +impl ReferenceMatrix { + pub fn topological_sort_into_groups(&self) -> TopologicalSort { + if self.length == 0 { + return TopologicalSort::Groups { groups: Vec::new() }; + } + + let mut preds_map: Vec = vec![0; self.length]; + + // this is basically summing the columns, I don't see a better way to do it + for row in self.bitvec.chunks(self.length) { + for succ in row.iter_ones() { + preds_map[succ] += 1; + } + } + + let mut groups = Vec::>::new(); + + // the initial group contains all symbols with no predecessors + let mut prev_group: Vec = preds_map + .iter() + .enumerate() + .filter_map(|(node, &num_preds)| { + if num_preds == 0 { + Some(node as u32) + } else { + None + } + }) + .collect(); + + if prev_group.is_empty() { + let remaining: Vec = (0u32..self.length as u32).collect(); + + return TopologicalSort::HasCycles { + groups: Vec::new(), + nodes_in_cycle: remaining, + }; + } + + while preds_map.iter().any(|x| *x > 0) { + let mut next_group = Vec::::new(); + for node in &prev_group { + for succ in self.references_for(*node as usize) { + { + let num_preds = preds_map.get_mut(succ).unwrap(); + *num_preds = num_preds.saturating_sub(1); + if *num_preds > 0 { + continue; + } + } + + // NOTE: we use -1 to mark nodes that have no predecessors, but are already + // part of an earlier group. That ensures nodes are added to just 1 group + let count = preds_map[succ]; + preds_map[succ] = -1; + + if count > -1 { + next_group.push(succ as u32); + } + } + } + groups.push(std::mem::replace(&mut prev_group, next_group)); + if prev_group.is_empty() { + let remaining: Vec = (0u32..self.length as u32) + .filter(|i| preds_map[*i as usize] > 0) + .collect(); + + return TopologicalSort::HasCycles { + groups, + nodes_in_cycle: remaining, + }; + } + } + groups.push(prev_group); + + TopologicalSort::Groups { groups } + } + + /// Get the strongly-connected components of the set of input nodes. + pub fn strongly_connected_components(&self, nodes: &[u32]) -> Vec> { + let mut params = Params::new(self.length, nodes); + + 'outer: loop { + for (node, value) in params.preorders.iter().enumerate() { + if let Preorder::Removed = value { + continue; + } + + recurse_onto(self.length, &self.bitvec, node, &mut params); + + continue 'outer; + } + + break params.scc; + } + } +} + +pub(crate) enum TopologicalSort { + /// There were no cycles, all nodes have been partitioned into groups + Groups { groups: Vec> }, + /// Cycles were found. All nodes that are not part of a cycle have been partitioned + /// into groups. The other elements are in the `cyclic` vector. However, there may be + /// many cycles, or just one big one. Use strongly-connected components to find out + /// exactly what the cycles are and how they fit into the groups. + HasCycles { + groups: Vec>, + nodes_in_cycle: Vec, + }, +} + +#[derive(Clone, Copy)] +enum Preorder { + Empty, + Filled(usize), + Removed, +} + +struct Params { + preorders: Vec, + c: usize, + p: Vec, + s: Vec, + scc: Vec>, + scca: Vec, +} + +impl Params { + fn new(length: usize, group: &[u32]) -> Self { + let mut preorders = vec![Preorder::Removed; length]; + + for value in group { + preorders[*value as usize] = Preorder::Empty; + } + + Self { + preorders, + c: 0, + s: Vec::new(), + p: Vec::new(), + scc: Vec::new(), + scca: Vec::new(), + } + } +} + +fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) { + params.preorders[v] = Preorder::Filled(params.c); + + params.c += 1; + + params.s.push(v as u32); + params.p.push(v as u32); + + for w in bitvec[v * length..][..length].iter_ones() { + if !params.scca.contains(&(w as u32)) { + match params.preorders[w] { + Preorder::Filled(pw) => loop { + let index = *params.p.last().unwrap(); + + match params.preorders[index as usize] { + Preorder::Empty => unreachable!(), + Preorder::Filled(current) => { + if current > pw { + params.p.pop(); + } else { + break; + } + } + Preorder::Removed => {} + } + }, + Preorder::Empty => recurse_onto(length, bitvec, w, params), + Preorder::Removed => {} + } + } + } + + if params.p.last() == Some(&(v as u32)) { + params.p.pop(); + + let mut component = Vec::new(); + while let Some(node) = params.s.pop() { + component.push(node); + params.scca.push(node); + params.preorders[node as usize] = Preorder::Removed; + if node as usize == v { + break; + } + } + params.scc.push(component); + } +} diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index b71061f7b2..76a08d276a 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -29,44 +29,60 @@ pub struct Scope { home: ModuleId, } -impl Scope { - pub fn new(home: ModuleId, var_store: &mut VarStore) -> Scope { - use roc_types::solved_types::{BuiltinAlias, FreeVars}; - let solved_aliases = roc_types::builtin_aliases::aliases(); - let mut aliases = SendMap::default(); +fn add_aliases(var_store: &mut VarStore) -> SendMap { + use roc_types::solved_types::{BuiltinAlias, FreeVars}; - for (symbol, builtin_alias) in solved_aliases { - let BuiltinAlias { region, vars, typ } = builtin_alias; + let solved_aliases = roc_types::builtin_aliases::aliases(); + let mut aliases = SendMap::default(); - let mut free_vars = FreeVars::default(); - let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); + for (symbol, builtin_alias) in solved_aliases { + let BuiltinAlias { region, vars, typ } = builtin_alias; - let mut variables = Vec::new(); - // make sure to sort these variables to make them line up with the type arguments - let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); - type_variables.sort(); - for (loc_name, (_, var)) in vars.iter().zip(type_variables) { - variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var))); - } + let mut free_vars = FreeVars::default(); + let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); - let alias = Alias { - region, - typ, - lambda_set_variables: Vec::new(), - recursion_variables: MutSet::default(), - type_variables: variables, - // TODO(opaques): replace when opaques are included in the stdlib - kind: AliasKind::Structural, - }; - - aliases.insert(symbol, alias); + let mut variables = Vec::new(); + // make sure to sort these variables to make them line up with the type arguments + let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); + type_variables.sort(); + for (loc_name, (_, var)) in vars.iter().zip(type_variables) { + variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var))); } + let alias = Alias { + region, + typ, + lambda_set_variables: Vec::new(), + recursion_variables: MutSet::default(), + type_variables: variables, + // TODO(opaques): replace when opaques are included in the stdlib + kind: AliasKind::Structural, + }; + + aliases.insert(symbol, alias); + } + + aliases +} + +impl Scope { + pub fn new(home: ModuleId, _var_store: &mut VarStore) -> Scope { Scope { home, idents: Symbol::default_in_scope(), symbols: SendMap::default(), - aliases, + aliases: SendMap::default(), + // TODO(abilities): default abilities in scope + abilities_store: AbilitiesStore::default(), + } + } + + pub fn new_with_aliases(home: ModuleId, var_store: &mut VarStore) -> Scope { + Scope { + home, + idents: Symbol::default_in_scope(), + symbols: SendMap::default(), + aliases: add_aliases(var_store), // TODO(abilities): default abilities in scope abilities_store: AbilitiesStore::default(), } diff --git a/compiler/can/tests/can_inline.rs b/compiler/can/tests/can_inline.rs deleted file mode 100644 index ac26cb90a3..0000000000 --- a/compiler/can/tests/can_inline.rs +++ /dev/null @@ -1,110 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -extern crate bumpalo; -extern crate roc_can; -extern crate roc_parse; -extern crate roc_region; - -mod helpers; - -#[cfg(test)] -mod can_inline { - use crate::helpers::{can_expr_with, test_home}; - use bumpalo::Bump; - use roc_can::expr::inline_calls; - use roc_can::expr::Expr::{self, *}; - use roc_can::scope::Scope; - use roc_types::subs::VarStore; - - fn assert_inlines_to(input: &str, expected: Expr, var_store: &mut VarStore) { - let arena = Bump::new(); - let scope = &mut Scope::new(test_home(), var_store); - let actual_out = can_expr_with(&arena, test_home(), input); - let actual = inline_calls(var_store, scope, actual_out.loc_expr.value); - - assert_eq!(actual, expected); - } - - #[test] - fn inline_empty_record() { - // fn inline_list_len() { - let var_store = &mut VarStore::default(); - - assert_inlines_to( - indoc!( - r#" - {} - "# - ), - EmptyRecord, - var_store, - ); - - // TODO testing with hardcoded variables is very brittle. - // Should find a better way to test this! - // (One idea would be to traverse both Exprs and zero out all the Variables, - // so they always pass equality.) - // let aliases = SendMap::default(); - // assert_inlines_to( - // indoc!( - // r#" - // Int.isZero 5 - // "# - // ), - // LetNonRec( - // Box::new(Def { - // loc_pattern: Located { - // region: Region::zero(), - // value: Pattern::Identifier(Symbol::ARG_1), - // }, - // pattern_vars: SendMap::default(), - // loc_expr: Located { - // region: Region::new(0, 0, 11, 12), - // value: Num(unsafe { Variable::unsafe_test_debug_variable(7) }, 5), - // }, - // expr_var: unsafe { Variable::unsafe_test_debug_variable(8) }, - // annotation: None, - // }), - // Box::new(Located { - // region: Region::zero(), - // value: Expr::Call( - // Box::new(( - // unsafe { Variable::unsafe_test_debug_variable(138) }, - // Located { - // region: Region::zero(), - // value: Expr::Var(Symbol::BOOL_EQ), - // }, - // unsafe { Variable::unsafe_test_debug_variable(139) }, - // )), - // vec![ - // ( - // unsafe { Variable::unsafe_test_debug_variable(140) }, - // Located { - // region: Region::zero(), - // value: Var(Symbol::ARG_1), - // }, - // ), - // ( - // unsafe { Variable::unsafe_test_debug_variable(141) }, - // Located { - // region: Region::zero(), - // value: Int( - // unsafe { Variable::unsafe_test_debug_variable(137) }, - // 0, - // ), - // }, - // ), - // ], - // CalledVia::Space, - // ), - // }), - // unsafe { Variable::unsafe_test_debug_variable(198) }, - // aliases, - // ), - // var_store, - // ) - } -} diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 83481c882b..838280f73d 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -20,11 +20,32 @@ mod test_can { use roc_region::all::{Position, Region}; use std::{f64, i64}; - fn assert_can(input: &str, expected: Expr) { + fn assert_can_runtime_error(input: &str, expected: RuntimeError) { let arena = Bump::new(); let actual_out = can_expr_with(&arena, test_home(), input); - assert_eq!(actual_out.loc_expr.value, expected); + match actual_out.loc_expr.value { + Expr::RuntimeError(actual) => { + assert_eq!(expected, actual); + } + actual => { + panic!("Expected a Float, but got: {:?}", actual); + } + } + } + + fn assert_can_string(input: &str, expected: &str) { + let arena = Bump::new(); + let actual_out = can_expr_with(&arena, test_home(), input); + + match actual_out.loc_expr.value { + Expr::Str(actual) => { + assert_eq!(expected, &*actual); + } + actual => { + panic!("Expected a Float, but got: {:?}", actual); + } + } } fn assert_can_float(input: &str, expected: f64) { @@ -69,10 +90,6 @@ mod test_can { } } - fn expr_str(contents: &str) -> Expr { - Expr::Str(contents.into()) - } - // NUMBER LITERALS #[test] @@ -81,14 +98,14 @@ mod test_can { let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidInt( + RuntimeError::InvalidInt( IntErrorKind::Overflow, Base::Decimal, Region::zero(), string.into_boxed_str(), - )), + ), ); } @@ -98,14 +115,14 @@ mod test_can { let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidInt( + RuntimeError::InvalidInt( IntErrorKind::Underflow, Base::Decimal, Region::zero(), string.into(), - )), + ), ); } @@ -114,13 +131,9 @@ mod test_can { let string = format!("{}1.0", f64::MAX); let region = Region::zero(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::PositiveInfinity, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::PositiveInfinity, region, string.into()), ); } @@ -129,13 +142,9 @@ mod test_can { let string = format!("{}1.0", f64::MIN); let region = Region::zero(); - assert_can( + assert_can_runtime_error( &string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::NegativeInfinity, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::NegativeInfinity, region, string.into()), ); } @@ -144,13 +153,9 @@ mod test_can { let string = "1.1.1"; let region = Region::zero(); - assert_can( + assert_can_runtime_error( string.clone(), - RuntimeError(RuntimeError::InvalidFloat( - FloatErrorKind::Error, - region, - string.into(), - )), + RuntimeError::InvalidFloat(FloatErrorKind::Error, region, string.into()), ); } @@ -1582,27 +1587,27 @@ mod test_can { #[test] fn string_with_valid_unicode_escapes() { - assert_can(r#""x\u(00A0)x""#, expr_str("x\u{00A0}x")); - assert_can(r#""x\u(101010)x""#, expr_str("x\u{101010}x")); + assert_can_string(r#""x\u(00A0)x""#, "x\u{00A0}x"); + assert_can_string(r#""x\u(101010)x""#, "x\u{101010}x"); } #[test] fn block_string() { - assert_can( + assert_can_string( r#" """foobar""" "#, - expr_str("foobar"), + "foobar", ); - assert_can( + assert_can_string( indoc!( r#" """foo bar""" "# ), - expr_str("foo\nbar"), + "foo\nbar", ); } diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index 6c1a2b78c4..391f48e488 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -220,99 +220,3 @@ macro_rules! mut_map { } }; } - -#[derive(Clone, Debug, PartialEq)] -pub struct VecSet { - elements: Vec, -} - -impl Default for VecSet { - fn default() -> Self { - Self { - elements: Vec::new(), - } - } -} - -impl VecSet { - pub fn with_capacity(capacity: usize) -> Self { - Self { - elements: Vec::with_capacity(capacity), - } - } - - pub fn len(&self) -> usize { - self.elements.len() - } - - pub fn is_empty(&self) -> bool { - self.elements.is_empty() - } - - pub fn swap_remove(&mut self, index: usize) -> T { - self.elements.swap_remove(index) - } - - pub fn insert(&mut self, value: T) -> bool { - if self.elements.contains(&value) { - true - } else { - self.elements.push(value); - - false - } - } - - pub fn contains(&self, value: &T) -> bool { - self.elements.contains(value) - } - - pub fn remove(&mut self, value: &T) { - match self.elements.iter().position(|x| x == value) { - None => { - // just do nothing - } - Some(index) => { - self.elements.swap_remove(index); - } - } - } - - pub fn iter(&self) -> impl Iterator { - self.elements.iter() - } -} - -impl Extend for VecSet { - fn extend>(&mut self, iter: T) { - let it = iter.into_iter(); - let hint = it.size_hint(); - - match hint { - (0, Some(0)) => { - // done, do nothing - } - (1, Some(1)) | (2, Some(2)) => { - for value in it { - self.insert(value); - } - } - _ => { - self.elements.extend(it); - - self.elements.sort(); - self.elements.dedup(); - } - } - } -} - -impl IntoIterator for VecSet { - type Item = T; - - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.elements.into_iter() - } -} diff --git a/compiler/collections/src/lib.rs b/compiler/collections/src/lib.rs index 16f8d165dc..f0f98b5e64 100644 --- a/compiler/collections/src/lib.rs +++ b/compiler/collections/src/lib.rs @@ -4,3 +4,9 @@ pub mod all; pub mod soa; +mod vec_map; +mod vec_set; + +pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; +pub use vec_map::VecMap; +pub use vec_set::VecSet; diff --git a/compiler/collections/src/vec_map.rs b/compiler/collections/src/vec_map.rs new file mode 100644 index 0000000000..57ab5c31b8 --- /dev/null +++ b/compiler/collections/src/vec_map.rs @@ -0,0 +1,133 @@ +#[derive(Debug, Clone)] +pub struct VecMap { + keys: Vec, + values: Vec, +} + +impl Default for VecMap { + fn default() -> Self { + Self { + keys: Vec::new(), + values: Vec::new(), + } + } +} + +impl VecMap { + pub fn with_capacity(capacity: usize) -> Self { + Self { + keys: Vec::with_capacity(capacity), + values: Vec::with_capacity(capacity), + } + } + + pub fn len(&self) -> usize { + debug_assert_eq!(self.keys.len(), self.values.len()); + self.keys.len() + } + + pub fn is_empty(&self) -> bool { + debug_assert_eq!(self.keys.len(), self.values.len()); + self.keys.is_empty() + } + + pub fn swap_remove(&mut self, index: usize) -> (K, V) { + let k = self.keys.swap_remove(index); + let v = self.values.swap_remove(index); + + (k, v) + } + + pub fn insert(&mut self, key: K, mut value: V) -> Option { + match self.keys.iter().position(|x| x == &key) { + Some(index) => { + std::mem::swap(&mut value, &mut self.values[index]); + + Some(value) + } + None => { + self.keys.push(key); + self.values.push(value); + + None + } + } + } + + pub fn contains(&self, key: &K) -> bool { + self.keys.contains(key) + } + + pub fn remove(&mut self, key: &K) { + match self.keys.iter().position(|x| x == key) { + None => { + // just do nothing + } + Some(index) => { + self.swap_remove(index); + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.keys.iter().zip(self.values.iter()) + } + + pub fn values(&self) -> impl Iterator { + self.values.iter() + } +} + +impl Extend<(K, V)> for VecMap { + #[inline(always)] + fn extend>(&mut self, iter: T) { + let it = iter.into_iter(); + let hint = it.size_hint(); + + match hint { + (0, Some(0)) => { + // done, do nothing + } + (1, Some(1)) | (2, Some(2)) => { + for (k, v) in it { + self.insert(k, v); + } + } + (_min, _opt_max) => { + // TODO do this with sorting and dedup? + for (k, v) in it { + self.insert(k, v); + } + } + } + } +} + +impl IntoIterator for VecMap { + type Item = (K, V); + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + keys: self.keys.into_iter(), + values: self.values.into_iter(), + } + } +} + +pub struct IntoIter { + keys: std::vec::IntoIter, + values: std::vec::IntoIter, +} + +impl Iterator for IntoIter { + type Item = (K, V); + + fn next(&mut self) -> Option { + match (self.keys.next(), self.values.next()) { + (Some(k), Some(v)) => Some((k, v)), + _ => None, + } + } +} diff --git a/compiler/collections/src/vec_set.rs b/compiler/collections/src/vec_set.rs new file mode 100644 index 0000000000..4869084677 --- /dev/null +++ b/compiler/collections/src/vec_set.rs @@ -0,0 +1,95 @@ +#[derive(Clone, Debug, PartialEq)] +pub struct VecSet { + elements: Vec, +} + +impl Default for VecSet { + fn default() -> Self { + Self { + elements: Vec::new(), + } + } +} + +impl VecSet { + pub fn with_capacity(capacity: usize) -> Self { + Self { + elements: Vec::with_capacity(capacity), + } + } + + pub fn len(&self) -> usize { + self.elements.len() + } + + pub fn is_empty(&self) -> bool { + self.elements.is_empty() + } + + pub fn swap_remove(&mut self, index: usize) -> T { + self.elements.swap_remove(index) + } + + pub fn insert(&mut self, value: T) -> bool { + if self.elements.contains(&value) { + true + } else { + self.elements.push(value); + + false + } + } + + pub fn contains(&self, value: &T) -> bool { + self.elements.contains(value) + } + + pub fn remove(&mut self, value: &T) { + match self.elements.iter().position(|x| x == value) { + None => { + // just do nothing + } + Some(index) => { + self.elements.swap_remove(index); + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.elements.iter() + } +} + +impl Extend for VecSet { + fn extend>(&mut self, iter: T) { + let it = iter.into_iter(); + let hint = it.size_hint(); + + match hint { + (0, Some(0)) => { + // done, do nothing + } + (1, Some(1)) | (2, Some(2)) => { + for value in it { + self.insert(value); + } + } + _ => { + self.elements.extend(it); + + self.elements.sort(); + self.elements.dedup(); + } + } + } +} + +impl IntoIterator for VecSet { + type Item = T; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.elements.into_iter() + } +} diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index c9b41c1da8..1b820bf762 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1684,18 +1684,18 @@ fn instantiate_rigids( let mut new_rigid_variables: Vec = Vec::new(); let mut rigid_substitution: MutMap = MutMap::default(); - for named in introduced_vars.named.iter() { + for named in introduced_vars.iter_named() { use std::collections::hash_map::Entry::*; - match ftv.entry(named.name.clone()) { + match ftv.entry(named.name().clone()) { Occupied(occupied) => { let existing_rigid = occupied.get(); - rigid_substitution.insert(named.variable, *existing_rigid); + rigid_substitution.insert(named.variable(), *existing_rigid); } Vacant(vacant) => { // It's possible to use this rigid in nested defs - vacant.insert(named.variable); - new_rigid_variables.push(named.variable); + vacant.insert(named.variable()); + new_rigid_variables.push(named.variable()); } } } diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index ce79180f75..504d59b96a 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -1809,7 +1809,39 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( for (i, field_layout) in field_layouts.iter().enumerate() { if let Layout::RecursivePointer = field_layout { - panic!("non-recursive tag unions cannot contain naked recursion pointers!"); + let recursive_union_layout = match when_recursive { + WhenRecursive::Unreachable => { + panic!("non-recursive tag unions cannot contain naked recursion pointers!"); + } + WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout, + }; + + // This field is a pointer to the recursive pointer. + let field_ptr = env + .builder + .build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field") + .unwrap(); + + // This is the actual pointer to the recursive data. + let field_value = env.builder.build_load(field_ptr, "load_recursive_pointer"); + + debug_assert!(field_value.is_pointer_value()); + + // therefore we must cast it to our desired type + let union_type = + basic_type_from_layout(env, &Layout::Union(*recursive_union_layout)); + let recursive_ptr_field_value = + cast_basic_basic(env.builder, field_value, union_type); + + modify_refcount_layout_help( + env, + parent, + layout_ids, + mode.to_call_mode(fn_val), + when_recursive, + recursive_ptr_field_value, + &Layout::RecursivePointer, + ) } else if field_layout.contains_refcounted() { let field_ptr = env .builder diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index c830876dc3..0ad99742cb 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -7,7 +7,7 @@ use roc_collections::all::MutMap; use roc_module::ident::Ident; use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; -use roc_mono::code_gen_help::{CodeGenHelp, REFCOUNT_MAX}; +use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX}; use roc_mono::ir::{ BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Param, Proc, ProcLayout, Stmt, @@ -270,6 +270,13 @@ impl<'a> WasmBackend<'a> { self.storage.stack_frame_size, self.storage.stack_frame_pointer, ); + + if DEBUG_LOG_SETTINGS.storage_map { + println!("\nStorage:"); + for (sym, storage) in self.storage.symbol_storage_map.iter() { + println!("{:?} => {:?}", sym, storage); + } + } } fn append_proc_debug_name(&mut self, sym: Symbol) { @@ -1609,8 +1616,9 @@ impl<'a> WasmBackend<'a> { ); } - /// Generate a refcount increment procedure and return its Wasm function index - pub fn gen_refcount_inc_for_zig(&mut self, layout: Layout<'a>) -> u32 { + /// Generate a refcount helper procedure and return a pointer (table index) to it + /// This allows it to be indirectly called from Zig code + pub fn get_refcount_fn_ptr(&mut self, layout: Layout<'a>, op: HelperOp) -> i32 { let ident_ids = self .interns .all_ident_ids @@ -1619,7 +1627,7 @@ impl<'a> WasmBackend<'a> { let (proc_symbol, new_specializations) = self .helper_proc_gen - .gen_refcount_inc_proc(ident_ids, layout); + .gen_refcount_proc(ident_ids, layout, op); // If any new specializations were created, register their symbol data for (spec_sym, spec_layout) in new_specializations.into_iter() { @@ -1632,6 +1640,7 @@ impl<'a> WasmBackend<'a> { .position(|lookup| lookup.name == proc_symbol && lookup.layout.arguments[0] == layout) .unwrap(); - self.fn_index_offset + proc_index as u32 + let wasm_fn_index = self.fn_index_offset + proc_index as u32; + self.get_fn_table_index(wasm_fn_index) } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 6689680ac4..7cca526acb 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -253,6 +253,7 @@ pub struct WasmDebugLogSettings { helper_procs_ir: bool, let_stmt_ir: bool, instructions: bool, + storage_map: bool, pub keep_test_binary: bool, } @@ -262,5 +263,6 @@ pub const DEBUG_LOG_SETTINGS: WasmDebugLogSettings = WasmDebugLogSettings { helper_procs_ir: false && cfg!(debug_assertions), let_stmt_ir: false && cfg!(debug_assertions), instructions: false && cfg!(debug_assertions), + storage_map: false && cfg!(debug_assertions), keep_test_binary: false && cfg!(debug_assertions), }; diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index b472d07df5..367154caa5 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -3,6 +3,7 @@ use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_error_macros::internal_error; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; +use roc_mono::code_gen_help::HelperOp; use roc_mono::ir::{HigherOrderLowLevel, PassedFunction, ProcLayout}; use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::low_level::HigherOrder; @@ -1014,58 +1015,73 @@ pub fn call_higher_order_lowlevel<'a>( }; let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source); - let inc_fn_idx = backend.gen_refcount_inc_for_zig(closure_data_layout); - let wrapper_fn_ptr = backend.get_fn_table_index(wrapper_fn_idx); - let inc_fn_ptr = backend.get_fn_table_index(inc_fn_idx); + let inc_fn_ptr = match closure_data_layout { + Layout::Struct { + field_layouts: &[], .. + } => { + // Our code gen would ignore the Unit arg, but the Zig builtin passes a pointer for it! + // That results in an exception (type signature mismatch in indirect call). + // The workaround is to use I32 layout, treating the (ignored) pointer as an integer. + backend.get_refcount_fn_ptr(Layout::Builtin(Builtin::Int(IntWidth::I32)), HelperOp::Inc) + } + _ => backend.get_refcount_fn_ptr(closure_data_layout, HelperOp::Inc), + }; match op { - // List.map : List elem_x, (elem_x -> elem_ret) -> List elem_ret - ListMap { xs } => { - let list_layout_in = backend.storage.symbol_layouts[xs]; + ListMap { xs } => list_map_n( + bitcode::LIST_MAP, + backend, + &[*xs], + return_sym, + *return_layout, + wrapper_fn_ptr, + inc_fn_ptr, + closure_data_exists, + *captured_environment, + *owns_captured_environment, + ), - let (elem_x, elem_ret) = match (list_layout_in, return_layout) { - ( - Layout::Builtin(Builtin::List(elem_x)), - Layout::Builtin(Builtin::List(elem_ret)), - ) => (elem_x, elem_ret), - _ => unreachable!("invalid layout for List.map arguments"), - }; - let elem_x_size = elem_x.stack_size(TARGET_INFO); - let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO); + ListMap2 { xs, ys } => list_map_n( + bitcode::LIST_MAP2, + backend, + &[*xs, *ys], + return_sym, + *return_layout, + wrapper_fn_ptr, + inc_fn_ptr, + closure_data_exists, + *captured_environment, + *owns_captured_environment, + ), - let cb = &mut backend.code_builder; + ListMap3 { xs, ys, zs } => list_map_n( + bitcode::LIST_MAP3, + backend, + &[*xs, *ys, *zs], + return_sym, + *return_layout, + wrapper_fn_ptr, + inc_fn_ptr, + closure_data_exists, + *captured_environment, + *owns_captured_environment, + ), - // Load return pointer & argument values - // Wasm signature: (i32, i64, i64, i32, i32, i32, i32, i32, i32, i32) -> nil - backend.storage.load_symbols(cb, &[return_sym]); - backend.storage.load_symbol_zig(cb, *xs); // list with capacity = 2 x i64 args - cb.i32_const(wrapper_fn_ptr); - if closure_data_exists { - backend.storage.load_symbols(cb, &[*captured_environment]); - } else { - // Normally, a zero-size arg would be eliminated in code gen, but Zig expects one! - cb.i32_const(0); // null pointer - } - cb.i32_const(inc_fn_ptr); - cb.i32_const(*owns_captured_environment as i32); - cb.i32_const(elem_ret_align as i32); // used for allocating the new list - cb.i32_const(elem_x_size as i32); - cb.i32_const(elem_ret_size as i32); + ListMap4 { xs, ys, zs, ws } => list_map_n( + bitcode::LIST_MAP4, + backend, + &[*xs, *ys, *zs, *ws], + return_sym, + *return_layout, + wrapper_fn_ptr, + inc_fn_ptr, + closure_data_exists, + *captured_environment, + *owns_captured_environment, + ), - let num_wasm_args = 10; // 1 return pointer + 8 Zig args + list 2nd i64 - let has_return_val = false; - backend.call_zig_builtin_after_loading_args( - bitcode::LIST_MAP, - num_wasm_args, - has_return_val, - ); - } - - ListMap2 { .. } - | ListMap3 { .. } - | ListMap4 { .. } - | ListMapWithIndex { .. } + ListMapWithIndex { .. } | ListKeepIf { .. } | ListWalk { .. } | ListWalkUntil { .. } @@ -1079,3 +1095,71 @@ pub fn call_higher_order_lowlevel<'a>( | DictWalk { .. } => todo!("{:?}", op), } } + +fn unwrap_list_elem_layout(list_layout: Layout<'_>) -> &Layout<'_> { + match list_layout { + Layout::Builtin(Builtin::List(x)) => x, + e => internal_error!("expected List layout, got {:?}", e), + } +} + +#[allow(clippy::too_many_arguments)] +fn list_map_n<'a>( + zig_fn_name: &'static str, + backend: &mut WasmBackend<'a>, + arg_symbols: &[Symbol], + return_sym: Symbol, + return_layout: Layout<'a>, + wrapper_fn_ptr: i32, + inc_fn_ptr: i32, + closure_data_exists: bool, + captured_environment: Symbol, + owns_captured_environment: bool, +) { + let arg_elem_layouts = Vec::from_iter_in( + arg_symbols + .iter() + .map(|sym| *unwrap_list_elem_layout(backend.storage.symbol_layouts[sym])), + backend.env.arena, + ); + + let elem_ret = unwrap_list_elem_layout(return_layout); + let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO); + + let cb = &mut backend.code_builder; + + backend.storage.load_symbols(cb, &[return_sym]); + + for s in arg_symbols { + backend.storage.load_symbol_zig(cb, *s); + } + cb.i32_const(wrapper_fn_ptr); + if closure_data_exists { + backend.storage.load_symbols(cb, &[captured_environment]); + } else { + // load_symbols assumes that a zero-size arg should be eliminated in code gen, + // but that's a specialization that our Zig code doesn't have! Pass a null pointer. + cb.i32_const(0); + } + cb.i32_const(inc_fn_ptr); + cb.i32_const(owns_captured_environment as i32); + cb.i32_const(elem_ret_align as i32); + for el in arg_elem_layouts.iter() { + cb.i32_const(el.stack_size(TARGET_INFO) as i32); + } + cb.i32_const(elem_ret_size as i32); + + // If we have lists of different lengths, we may need to decrement + let num_wasm_args = if arg_elem_layouts.len() > 1 { + for el in arg_elem_layouts.iter() { + let ptr = backend.get_refcount_fn_ptr(*el, HelperOp::Dec); + backend.code_builder.i32_const(ptr); + } + 7 + arg_elem_layouts.len() * 4 + } else { + 7 + arg_elem_layouts.len() * 3 + }; + + let has_return_val = false; + backend.call_zig_builtin_after_loading_args(zig_fn_name, num_wasm_args, has_return_val); +} diff --git a/compiler/gen_wasm/src/wasm_module/code_builder.rs b/compiler/gen_wasm/src/wasm_module/code_builder.rs index c6d7646a19..30db30702c 100644 --- a/compiler/gen_wasm/src/wasm_module/code_builder.rs +++ b/compiler/gen_wasm/src/wasm_module/code_builder.rs @@ -62,7 +62,7 @@ struct VmBlock<'a> { impl std::fmt::Debug for VmBlock<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{:?}", self.opcode)) + f.write_fmt(format_args!("{:?} {:?}", self.opcode, self.value_stack)) } } @@ -608,7 +608,7 @@ impl<'a> CodeBuilder<'a> { log_instruction!( "{:10}\t\t{:?}", format!("{:?}", opcode), - self.current_stack() + self.vm_block_stack ); } @@ -635,7 +635,7 @@ impl<'a> CodeBuilder<'a> { "{:10}\t{}\t{:?}", format!("{:?}", opcode), immediate, - self.current_stack() + self.vm_block_stack ); } @@ -648,7 +648,7 @@ impl<'a> CodeBuilder<'a> { format!("{:?}", opcode), align, offset, - self.current_stack() + self.vm_block_stack ); } @@ -752,7 +752,7 @@ impl<'a> CodeBuilder<'a> { "{:10}\t{}\t{:?}", format!("{:?}", CALL), function_index, - self.current_stack() + self.vm_block_stack ); } @@ -823,7 +823,7 @@ impl<'a> CodeBuilder<'a> { "{:10}\t{}\t{:?}", format!("{:?}", opcode), x, - self.current_stack() + self.vm_block_stack ); } pub fn i32_const(&mut self, x: i32) { diff --git a/compiler/ident/src/lib.rs b/compiler/ident/src/lib.rs index 85342346db..5b5dbefcdf 100644 --- a/compiler/ident/src/lib.rs +++ b/compiler/ident/src/lib.rs @@ -202,8 +202,19 @@ impl From<&str> for IdentStr { } impl From for IdentStr { - fn from(str: String) -> Self { - Self::from_str(&str) + fn from(string: String) -> Self { + if string.len() <= Self::SMALL_STR_BYTES { + Self::from_str(string.as_str()) + } else { + // Take over the string's heap allocation + let length = string.len(); + let elements = string.as_ptr(); + + // Make sure the existing string doesn't get dropped. + std::mem::forget(string); + + Self { elements, length } + } } } diff --git a/compiler/load_internal/.gitignore b/compiler/load_internal/.gitignore new file mode 100644 index 0000000000..cad2309100 --- /dev/null +++ b/compiler/load_internal/.gitignore @@ -0,0 +1 @@ +/tmp \ No newline at end of file diff --git a/compiler/load_internal/Cargo.toml b/compiler/load_internal/Cargo.toml index 3d876623f5..28641d1c1b 100644 --- a/compiler/load_internal/Cargo.toml +++ b/compiler/load_internal/Cargo.toml @@ -33,3 +33,4 @@ tempfile = "3.2.0" pretty_assertions = "1.0.0" maplit = "1.0.2" indoc = "1.0.3" +roc_test_utils = { path = "../../test_utils" } \ No newline at end of file diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 9259818027..4af83a07c8 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -10,7 +10,7 @@ use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_can::def::Declaration; use roc_can::module::{canonicalize_module_defs, Module}; -use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet, VecSet}; +use roc_collections::{default_hasher, BumpMap, MutMap, MutSet, VecSet}; use roc_constrain::module::{ constrain_builtin_imports, constrain_module, ExposedByModule, ExposedForModule, ExposedModuleTypes, diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index 5aaace9f19..6f1bfffee3 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -89,11 +89,11 @@ mod test_load { buf } - fn multiple_modules(files: Vec<(&str, &str)>) -> Result { + fn multiple_modules(subdir: &str, files: Vec<(&str, &str)>) -> Result { let arena = Bump::new(); let arena = &arena; - match multiple_modules_help(arena, files) { + match multiple_modules_help(subdir, arena, files) { Err(io_error) => panic!("IO trouble: {:?}", io_error), Ok(Err(LoadingProblem::FormattedReport(buf))) => Err(buf), Ok(Err(loading_problem)) => Err(format!("{:?}", loading_problem)), @@ -112,13 +112,11 @@ mod test_load { )); } - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty(),); Ok(loaded_module) } @@ -126,18 +124,21 @@ mod test_load { } fn multiple_modules_help<'a>( + subdir: &str, arena: &'a Bump, mut files: Vec<(&str, &str)>, ) -> Result>, std::io::Error> { use std::fs::{self, File}; use std::io::Write; - use tempfile::tempdir; let mut file_handles: Vec<_> = Vec::new(); - // create a temporary directory - let dir = tempdir()?; + // Use a deterministic temporary directory. + // We can't have all tests use "tmp" because tests run in parallel, + // so append the test name to the tmp path. + let tmp = format!("tmp/{}", subdir); + let dir = roc_test_utils::TmpDir::new(&tmp); let app_module = files.pop().unwrap(); @@ -173,8 +174,6 @@ mod test_load { ) }; - dir.close()?; - Ok(result) } @@ -208,13 +207,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty()); let expected_name = loaded_module .interns @@ -261,13 +258,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty()); for decl in loaded_module.declarations_by_id.remove(&home).unwrap() { match decl { @@ -341,7 +336,7 @@ mod test_load { ), ]; - assert!(multiple_modules(modules).is_ok()); + assert!(multiple_modules("import_transitive_alias", modules).is_ok()); } #[test] @@ -365,13 +360,11 @@ mod test_load { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); + assert!(loaded_module + .type_problems + .remove(&home) + .unwrap_or_default() + .is_empty(),); let def_count: usize = loaded_module .declarations_by_id @@ -584,12 +577,12 @@ mod test_load { ), )]; - match multiple_modules(modules) { + match multiple_modules("parse_problem", modules) { Err(report) => assert_eq!( report, indoc!( " - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ──────────────────────────────────── tmp/parse_problem/Main ─ I cannot find the end of this list: @@ -651,10 +644,14 @@ mod test_load { ), )]; - match multiple_modules(modules) { + match multiple_modules("platform_does_not_exist", modules) { Err(report) => { - assert!(report.contains("FILE NOT FOUND")); - assert!(report.contains("zzz-does-not-exist/Package-Config.roc")); + assert!(report.contains("FILE NOT FOUND"), "report=({})", report); + assert!( + report.contains("zzz-does-not-exist/Package-Config.roc"), + "report=({})", + report + ); } Ok(_) => unreachable!("we expect failure here"), } @@ -694,7 +691,7 @@ mod test_load { ), ]; - match multiple_modules(modules) { + match multiple_modules("platform_parse_error", modules) { Err(report) => { assert!(report.contains("NOT END OF FILE")); assert!(report.contains("blah 1 2 3 # causing a parse error on purpose")); @@ -738,7 +735,7 @@ mod test_load { ), ]; - assert!(multiple_modules(modules).is_ok()); + assert!(multiple_modules("platform_exposes_main_return_by_pointer_issue", modules).is_ok()); } #[test] @@ -768,12 +765,13 @@ mod test_load { ), ]; - let err = multiple_modules(modules).unwrap_err(); + let err = multiple_modules("opaque_wrapped_unwrapped_outside_defining_module", modules) + .unwrap_err(); assert_eq!( err, indoc!( r#" - ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ────────────────────────────────────────── + ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─ ...rapped_outside_defining_module/Main ─ The unwrapped opaque type Age referenced here: @@ -787,7 +785,7 @@ mod test_load { Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! - ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ────────────────────────────────────────── + ── OPAQUE TYPE DECLARED OUTSIDE SCOPE ─ ...rapped_outside_defining_module/Main ─ The unwrapped opaque type Age referenced here: @@ -801,7 +799,7 @@ mod test_load { Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! - ── UNUSED IMPORT ─────────────────────────────────────────────────────────────── + ── UNUSED IMPORT ─── tmp/opaque_wrapped_unwrapped_outside_defining_module/Main ─ Nothing from Age is used in this module. @@ -850,13 +848,13 @@ mod test_load { ), ]; - match multiple_modules(modules) { + match multiple_modules("issue_2863_module_type_does_not_exist", modules) { Err(report) => { assert_eq!( report, indoc!( " - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ────────── tmp/issue_2863_module_type_does_not_exist/Main ─ I cannot find a `DoesNotExist` value diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 1056f9eeaf..2b4347d3d9 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -204,6 +204,12 @@ impl<'a> From<&'a str> for Lowercase { } } +impl<'a> From<&'a Lowercase> for &'a str { + fn from(lowercase: &'a Lowercase) -> Self { + lowercase.as_str() + } +} + impl<'a> From for Lowercase { fn from(string: String) -> Self { Self(string.into()) diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 0e3e604c2a..36f0f0c77b 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -244,22 +244,22 @@ impl LowLevelWrapperType { Symbol::LIST_JOIN => CanBeReplacedBy(ListJoin), Symbol::LIST_RANGE => CanBeReplacedBy(ListRange), Symbol::LIST_MAP => WrapperIsRequired, - Symbol::LIST_MAP2 => CanBeReplacedBy(ListMap2), - Symbol::LIST_MAP3 => CanBeReplacedBy(ListMap3), - Symbol::LIST_MAP4 => CanBeReplacedBy(ListMap4), - Symbol::LIST_MAP_WITH_INDEX => CanBeReplacedBy(ListMapWithIndex), - Symbol::LIST_KEEP_IF => CanBeReplacedBy(ListKeepIf), - Symbol::LIST_WALK => CanBeReplacedBy(ListWalk), - Symbol::LIST_WALK_UNTIL => CanBeReplacedBy(ListWalkUntil), - Symbol::LIST_WALK_BACKWARDS => CanBeReplacedBy(ListWalkBackwards), - Symbol::LIST_KEEP_OKS => CanBeReplacedBy(ListKeepOks), - Symbol::LIST_KEEP_ERRS => CanBeReplacedBy(ListKeepErrs), - Symbol::LIST_SORT_WITH => CanBeReplacedBy(ListSortWith), + Symbol::LIST_MAP2 => WrapperIsRequired, + Symbol::LIST_MAP3 => WrapperIsRequired, + Symbol::LIST_MAP4 => WrapperIsRequired, + Symbol::LIST_MAP_WITH_INDEX => WrapperIsRequired, + Symbol::LIST_KEEP_IF => WrapperIsRequired, + Symbol::LIST_WALK => WrapperIsRequired, + Symbol::LIST_WALK_UNTIL => WrapperIsRequired, + Symbol::LIST_WALK_BACKWARDS => WrapperIsRequired, + Symbol::LIST_KEEP_OKS => WrapperIsRequired, + Symbol::LIST_KEEP_ERRS => WrapperIsRequired, + Symbol::LIST_SORT_WITH => WrapperIsRequired, Symbol::LIST_SUBLIST => CanBeReplacedBy(ListSublist), Symbol::LIST_DROP_AT => CanBeReplacedBy(ListDropAt), Symbol::LIST_SWAP => CanBeReplacedBy(ListSwap), - Symbol::LIST_ANY => CanBeReplacedBy(ListAny), - Symbol::LIST_ALL => CanBeReplacedBy(ListAll), + Symbol::LIST_ANY => WrapperIsRequired, + Symbol::LIST_ALL => WrapperIsRequired, Symbol::LIST_FIND => WrapperIsRequired, Symbol::DICT_LEN => CanBeReplacedBy(DictSize), Symbol::DICT_EMPTY => CanBeReplacedBy(DictEmpty), @@ -272,7 +272,7 @@ impl LowLevelWrapperType { Symbol::DICT_UNION => CanBeReplacedBy(DictUnion), Symbol::DICT_INTERSECTION => CanBeReplacedBy(DictIntersection), Symbol::DICT_DIFFERENCE => CanBeReplacedBy(DictDifference), - Symbol::DICT_WALK => CanBeReplacedBy(DictWalk), + Symbol::DICT_WALK => WrapperIsRequired, Symbol::SET_FROM_LIST => CanBeReplacedBy(SetFromList), Symbol::NUM_ADD => CanBeReplacedBy(NumAdd), Symbol::NUM_ADD_WRAP => CanBeReplacedBy(NumAddWrap), diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 2ffdd2494d..3244e04fbb 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -25,7 +25,7 @@ const ARG_2: Symbol = Symbol::ARG_2; pub const REFCOUNT_MAX: usize = 0; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum HelperOp { +pub enum HelperOp { Inc, Dec, DecRef(JoinPointId), @@ -185,16 +185,16 @@ impl<'a> CodeGenHelp<'a> { /// Generate a refcount increment procedure, *without* a Call expression. /// *This method should be rarely used* - only when the proc is to be called from Zig. /// Otherwise you want to generate the Proc and the Call together, using another method. - /// This only supports the 'inc' operation, as it's the only real use case we have. - pub fn gen_refcount_inc_proc( + pub fn gen_refcount_proc( &mut self, ident_ids: &mut IdentIds, layout: Layout<'a>, + op: HelperOp, ) -> (Symbol, Vec<'a, (Symbol, ProcLayout<'a>)>) { let mut ctx = Context { new_linker_data: Vec::new_in(self.arena), recursive_union: None, - op: HelperOp::Inc, + op, }; let proc_name = self.find_or_create_proc(ident_ids, &mut ctx, layout); diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index ef46ad8564..176f875d41 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -107,7 +107,9 @@ pub fn refcount_generic<'a>( match layout { Layout::Builtin(Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal) => { - unreachable!("Not refcounted: {:?}", layout) + // Generate a dummy function that immediately returns Unit + // Some higher-order Zig builtins *always* call an RC function on List elements. + rc_return_stmt(root, ident_ids, ctx) } Layout::Builtin(Builtin::Str) => refcount_str(root, ident_ids, ctx), Layout::Builtin(Builtin::List(elem_layout)) => { diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 5bb784d3c4..72b10cc7f5 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -108,7 +108,7 @@ pub struct EntryPoint<'a> { #[derive(Clone, Copy, Debug)] pub struct PartialProcId(usize); -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct PartialProcs<'a> { /// maps a function name (symbol) to an index symbols: Vec<'a, Symbol>, @@ -190,7 +190,7 @@ impl<'a> PartialProcs<'a> { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct PartialProc<'a> { pub annotation: Variable, pub pattern_symbols: &'a [Symbol], @@ -3499,15 +3499,24 @@ pub fn with_hole<'a>( OpaqueRef { argument, .. } => { let (arg_var, loc_arg_expr) = *argument; - with_hole( - env, - loc_arg_expr.value, - arg_var, - procs, - layout_cache, - assigned, - hole, - ) + + match can_reuse_symbol(env, procs, &loc_arg_expr.value) { + // Opaques decay to their argument. + ReuseSymbol::Value(real_name) => { + let mut result = hole.clone(); + substitute_in_exprs(arena, &mut result, assigned, real_name); + result + } + _ => with_hole( + env, + loc_arg_expr.value, + arg_var, + procs, + layout_cache, + assigned, + hole, + ), + } } Record { @@ -4750,6 +4759,7 @@ fn get_specialization<'a>( symbol: Symbol, ) -> Option { use roc_solve::ability::type_implementing_member; + use roc_solve::solve::instantiate_rigids; use roc_unify::unify::unify; match env.abilities_store.member_def(symbol) { @@ -4759,6 +4769,7 @@ fn get_specialization<'a>( } Some(member) => { let snapshot = env.subs.snapshot(); + instantiate_rigids(env.subs, member.signature_var); let (_, must_implement_ability) = unify( env.subs, symbol_var, diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index eef312cab3..fd764f069d 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -311,6 +311,50 @@ impl<'a> UnionLayout<'a> { .append(alloc.intersperse(tags_doc, ", ")) .append(alloc.text("]")) } + Recursive(tags) => { + let tags_doc = tags.iter().map(|fields| { + alloc.text("C ").append(alloc.intersperse( + fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + )) + }); + alloc + .text("[") + .append(alloc.intersperse(tags_doc, ", ")) + .append(alloc.text("]")) + } + NonNullableUnwrapped(fields) => { + let fields_doc = alloc.text("C ").append(alloc.intersperse( + fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + )); + alloc + .text("[") + .append(fields_doc) + .append(alloc.text("]")) + } + NullableUnwrapped { + nullable_id, + other_fields, + } => { + let fields_doc = alloc.text("C ").append( + alloc.intersperse( + other_fields + .iter() + .map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + ), + ); + let tags_doc = if nullable_id { + alloc.concat(vec![alloc.text(", "), fields_doc]) + } else { + alloc.concat(vec![fields_doc, alloc.text(", ")]) + }; + alloc + .text("[") + .append(tags_doc) + .append(alloc.text("]")) + } _ => alloc.text("TODO"), } } @@ -1731,7 +1775,7 @@ fn layout_from_flat_type<'a>( debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); - Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) + Ok(layout_from_tag_union(env, &tags)) } FunctionOrTagUnion(tag_name, _, ext_var) => { debug_assert!( @@ -1742,7 +1786,7 @@ fn layout_from_flat_type<'a>( let union_tags = UnionTags::from_tag_name_index(tag_name); let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var); - Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) + Ok(layout_from_tag_union(env, &tags)) } RecursiveTagUnion(rec_var, tags, ext_var) => { let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); @@ -2071,23 +2115,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool { } fn union_sorted_tags_help_new<'a>( - arena: &'a Bump, + env: &mut Env<'a, '_>, tags_list: &[(&'_ TagName, &[Variable])], opt_rec_var: Option, - subs: &Subs, - target_info: TargetInfo, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! - let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena); + let mut tags_list = Vec::from_iter_in(tags_list.iter(), env.arena); tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; - match tags_list.len() { 0 => { // trying to instantiate a type with no values @@ -2098,18 +2133,19 @@ fn union_sorted_tags_help_new<'a>( let tag_name = tag_name.clone(); // just one tag in the union (but with arguments) can be a struct - let mut layouts = Vec::with_capacity_in(tags_list.len(), arena); + let mut layouts = Vec::with_capacity_in(tags_list.len(), env.arena); // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int match tag_name { TagName::Private(Symbol::NUM_AT_NUM) => { let var = arguments[0]; - layouts - .push(unwrap_num_tag(subs, var, target_info).expect("invalid num layout")); + layouts.push( + unwrap_num_tag(env.subs, var, env.target_info).expect("invalid num layout"), + ); } _ => { for &var in arguments { - match Layout::from_var(&mut env, var) { + match Layout::from_var(env, var) { Ok(layout) => { layouts.push(layout); } @@ -2129,8 +2165,8 @@ fn union_sorted_tags_help_new<'a>( } layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -2151,7 +2187,7 @@ fn union_sorted_tags_help_new<'a>( } num_tags => { // default path - let mut answer = Vec::with_capacity_in(tags_list.len(), arena); + let mut answer = Vec::with_capacity_in(tags_list.len(), env.arena); let mut has_any_arguments = false; let mut nullable: Option<(TagIdIntType, TagName)> = None; @@ -2174,17 +2210,19 @@ fn union_sorted_tags_help_new<'a>( continue; } - let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); + let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, env.arena); for &var in arguments { - match Layout::from_var(&mut env, var) { + match Layout::from_var(env, var) { Ok(layout) => { has_any_arguments = true; // make sure to not unroll recursive types! let self_recursion = opt_rec_var.is_some() - && subs.get_root_key_without_compacting(var) - == subs.get_root_key_without_compacting(opt_rec_var.unwrap()) + && env.subs.get_root_key_without_compacting(var) + == env + .subs + .get_root_key_without_compacting(opt_rec_var.unwrap()) && is_recursive_tag_union(&layout); if self_recursion { @@ -2207,8 +2245,8 @@ fn union_sorted_tags_help_new<'a>( } arg_layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -2229,7 +2267,7 @@ fn union_sorted_tags_help_new<'a>( 3..=MAX_ENUM_SIZE if !has_any_arguments => { // type can be stored in a byte // needs the sorted tag names to determine the tag_id - let mut tag_names = Vec::with_capacity_in(answer.len(), arena); + let mut tag_names = Vec::with_capacity_in(answer.len(), env.arena); for (tag_name, _) in answer { tag_names.push(tag_name); @@ -2488,27 +2526,15 @@ pub fn union_sorted_tags_help<'a>( } } -fn layout_from_newtype<'a>( - arena: &'a Bump, - tags: &UnsortedUnionTags, - subs: &Subs, - target_info: TargetInfo, -) -> Layout<'a> { - debug_assert!(tags.is_newtype_wrapper(subs)); +fn layout_from_newtype<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> { + debug_assert!(tags.is_newtype_wrapper(env.subs)); - let (tag_name, var) = tags.get_newtype(subs); + let (tag_name, var) = tags.get_newtype(env.subs); if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { - unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") + unwrap_num_tag(env.subs, var, env.target_info).expect("invalid Num argument") } else { - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; - - match Layout::from_var(&mut env, var) { + match Layout::from_var(env, var) { Ok(layout) => layout, Err(LayoutProblem::UnresolvedTypeVar(_)) => { // If we encounter an unbound type var (e.g. `Ok *`) @@ -2525,16 +2551,11 @@ fn layout_from_newtype<'a>( } } -fn layout_from_tag_union<'a>( - arena: &'a Bump, - tags: &UnsortedUnionTags, - subs: &Subs, - target_info: TargetInfo, -) -> Layout<'a> { +fn layout_from_tag_union<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> { use UnionVariant::*; - if tags.is_newtype_wrapper(subs) { - return layout_from_newtype(arena, tags, subs, target_info); + if tags.is_newtype_wrapper(env.subs) { + return layout_from_newtype(env, tags); } let tags_vec = &tags.tags; @@ -2545,12 +2566,11 @@ fn layout_from_tag_union<'a>( let &var = arguments.iter().next().unwrap(); - unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") + unwrap_num_tag(env.subs, var, env.target_info).expect("invalid Num argument") } _ => { let opt_rec_var = None; - let variant = - union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, target_info); + let variant = union_sorted_tags_help_new(env, tags_vec, opt_rec_var); match variant { Never => Layout::VOID, @@ -2576,7 +2596,7 @@ fn layout_from_tag_union<'a>( NonRecursive { sorted_tag_layouts: tags, } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); tag_layouts.extend(tags.iter().map(|r| r.1)); Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice())) @@ -2585,7 +2605,7 @@ fn layout_from_tag_union<'a>( Recursive { sorted_tag_layouts: tags, } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); tag_layouts.extend(tags.iter().map(|r| r.1)); debug_assert!(tag_layouts.len() > 1); @@ -2597,7 +2617,7 @@ fn layout_from_tag_union<'a>( nullable_name: _, sorted_tag_layouts: tags, } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); tag_layouts.extend(tags.iter().map(|r| r.1)); Layout::Union(UnionLayout::NullableWrapped { diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 959a294e9d..83d3bbfe34 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -278,6 +278,12 @@ pub struct AbilityMember<'a> { pub typ: Loc>, } +impl AbilityMember<'_> { + pub fn region(&self) -> Region { + Region::across_all([self.name.region, self.typ.region].iter()) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum TypeDef<'a> { /// A type alias. This is like a standalone annotation, except the pattern diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index f8d1684418..e28177345f 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -135,6 +135,10 @@ pub enum Problem { loc_name: Loc, ability: Symbol, }, + AbilityNotOnToplevel { + region: Region, + }, + AbilityUsedAsType(Lowercase, Symbol, Region), } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index e30be26df4..dbe6f50c5d 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -79,7 +79,7 @@ pub struct IncompleteAbilityImplementation { pub missing_members: Vec>, } -#[derive(PartialEq, Debug, Clone)] +#[derive(Debug, Clone)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), BadPattern(Region, PatternCategory, ErrorType, PExpected), diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 54bd3626d6..da03df77e2 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5951,4 +5951,86 @@ mod solve_expr { "{ tag : [ A, B ] }a -> { tag : [ A, B ] }a", ) } + + #[test] + fn ability_constrained_in_non_member_check() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ hashEq ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq : a, a -> Bool | a has Hash + hashEq = \x, y -> hash x == hash y + "# + ), + "a, a -> Bool | a has Hash", + ) + } + + #[test] + fn ability_constrained_in_non_member_infer() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ hashEq ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq = \x, y -> hash x == hash y + "# + ), + "a, a -> Bool | a has Hash", + ) + } + + #[test] + fn ability_constrained_in_non_member_infer_usage() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hashEq = \x, y -> hash x == hash y + + Id := U64 + hash = \$Id n -> n + + result = hashEq ($Id 100) ($Id 101) + "# + ), + "Bool", + ) + } + + #[test] + fn ability_constrained_in_non_member_multiple_specializations() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + "U64", + ) + } } diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs index 9b0b1f0ed5..f268f0ae2b 100644 --- a/compiler/test_gen/src/gen_abilities.rs +++ b/compiler/test_gen/src/gen_abilities.rs @@ -84,3 +84,135 @@ fn alias_member_specialization() { u64 ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_usage() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : a, a -> U64 | a has Hash + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + result = mulHashes ($Id 5) ($Id 7) + "# + ), + 35, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_usage_inferred() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + result = mulHashes ($Id 5) ($Id 7) + "# + ), + 35, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_multiple_specializations() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : a, b -> U64 | a has Hash, b has Hash + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + 300, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_constrained_in_non_member_multiple_specializations_inferred() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + 300, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_used_as_type_still_compiles() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : Hash, Hash -> U64 + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + 300, + u64 + ) +} diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 517e3d9c58..5bd5e36919 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1104,7 +1104,7 @@ fn list_map_closure() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map4_group() { assert_evals_to!( indoc!( @@ -1112,13 +1112,13 @@ fn list_map4_group() { List.map4 [1,2,3] [3,2,1] [2,1,3] [3,1,2] (\a, b, c, d -> Group a b c d) "# ), - RocList::from_slice(&[(1, 3, 2, 3), (2, 2, 1, 1), (3, 1, 3, 2)]), - RocList<(i64, i64, i64, i64)> + RocList::from_slice(&[[1, 3, 2, 3], [2, 2, 1, 1], [3, 1, 3, 2]]), + RocList<[i64; 4]> ); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map4_different_length() { assert_evals_to!( indoc!( @@ -1137,7 +1137,7 @@ fn list_map4_different_length() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map3_group() { assert_evals_to!( indoc!( @@ -1151,7 +1151,7 @@ fn list_map3_group() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map3_different_length() { assert_evals_to!( indoc!( @@ -1169,7 +1169,7 @@ fn list_map3_different_length() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map2_pair() { assert_evals_to!( indoc!( @@ -1184,13 +1184,13 @@ fn list_map2_pair() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn list_map2_different_lengths() { assert_evals_to!( indoc!( r#" List.map2 - ["a", "b", "lllllllllllllongnggg" ] + ["a", "b", "lllllllllllllooooooooongnggg" ] ["b"] (\a, b -> Str.concat a b) "# diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 5b857a1fbc..25ade5e934 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -301,8 +301,8 @@ fn refcount_different_rosetrees_inc() { (Pointer, Pointer), &[ Live(2), // s - Live(2), // s1 Live(3), // i1 + Live(2), // s1 Live(1), // [i1, i1] Live(1), // i2 Live(1), // [s1, s1] diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index eefcb272cf..48dc0880d2 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -1580,3 +1580,28 @@ fn issue_2725_alias_polymorphic_lambda() { i64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn opaque_assign_to_symbol() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + Variable := U8 + + fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ] + fromUtf8 = \char -> + Ok ($Variable char) + + out = + when fromUtf8 98 is + Ok ($Variable n) -> n + _ -> 1 + "# + ), + 98, + u8 + ) +} diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 03bc6b7a1e..99252b15c5 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -106,8 +106,8 @@ fn create_llvm_module<'a>( use roc_problem::can::Problem::*; for problem in can_problems.into_iter() { - // Ignore "unused" problems match problem { + // Ignore "unused" problems UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) @@ -122,6 +122,8 @@ fn create_llvm_module<'a>( delayed_errors.push(buf.clone()); lines.push(buf); } + // We should be able to compile even when abilities are used as types + AbilityUsedAsType(..) => {} _ => { let report = can_problem(&alloc, &line_info, module_path.clone(), problem); let mut buf = String::new(); diff --git a/compiler/test_mono/generated/issue_2810.txt b/compiler/test_mono/generated/issue_2810.txt new file mode 100644 index 0000000000..d5b11bcac2 --- /dev/null +++ b/compiler/test_mono/generated/issue_2810.txt @@ -0,0 +1,6 @@ +procedure Test.0 (): + let Test.16 : [C TODO, C ] = SystemTool ; + let Test.14 : TODO = Job Test.16; + let Test.13 : [C TODO, C ] = FromJob Test.14; + let Test.4 : TODO = Job Test.13; + ret Test.4; diff --git a/compiler/test_mono/generated/opaque_assign_to_symbol.txt b/compiler/test_mono/generated/opaque_assign_to_symbol.txt new file mode 100644 index 0000000000..8e0c7fe63b --- /dev/null +++ b/compiler/test_mono/generated/opaque_assign_to_symbol.txt @@ -0,0 +1,10 @@ +procedure : `#UserApp.fromUtf8` [C {}, C U8] +procedure = `#UserApp.fromUtf8` (`#UserApp.char`): + let `#UserApp.3` : [C {}, C U8] = Ok `#UserApp.4`; + ret `#UserApp.3`; + +procedure : `#UserApp.out` [C {}, C U8] +procedure = `#UserApp.out` (): + let `#UserApp.2` : U8 = 98i64; + let `#UserApp.1` : [C {}, C U8] = CallByName `#UserApp.fromUtf8` `#UserApp.2`; + ret `#UserApp.1`; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index e64a36f72e..fbb2612793 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -129,7 +129,7 @@ fn compiles_to_ir(test_name: &str, src: &str) { println!("Ignoring {} canonicalization problems", can_problems.len()); } - assert_eq!(type_problems, Vec::new()); + assert!(type_problems.is_empty()); assert_eq!(mono_problems, Vec::new()); debug_assert_eq!(exposed_to_host.values.len(), 1); @@ -1283,6 +1283,23 @@ fn issue_2583_specialize_errors_behind_unified_branches() { ) } +#[mono_test] +fn issue_2810() { + indoc!( + r#" + Command : [ Command Tool ] + + Job : [ Job Command ] + + Tool : [ SystemTool, FromJob Job ] + + a : Job + a = Job (Command (FromJob (Job (Command SystemTool)))) + a + "# + ) +} + #[mono_test] fn issue_2811() { indoc!( @@ -1313,6 +1330,23 @@ fn specialize_ability_call() { ) } +#[mono_test] +fn opaque_assign_to_symbol() { + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + Variable := U8 + + fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ] + fromUtf8 = \char -> + Ok ($Variable char) + + out = fromUtf8 98 + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 998684d777..f9e11471f3 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -248,7 +248,12 @@ fn name_root( subs: &mut Subs, taken: &mut MutSet, ) -> u32 { - let (generated_name, new_letters_used) = name_type_var(letters_used, taken); + let (generated_name, new_letters_used) = + name_type_var(letters_used, &mut taken.iter(), |var, str| { + var.as_str() == str + }); + + taken.insert(generated_name.clone()); set_root_name(root, generated_name, subs); @@ -307,6 +312,8 @@ pub fn content_to_string( write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary); + ctx.able_variables.sort(); + ctx.able_variables.dedup(); for (i, (var, ability)) in ctx.able_variables.into_iter().enumerate() { buf.push_str(if i == 0 { " | " } else { ", " }); buf.push_str(var); @@ -443,7 +450,9 @@ fn write_content<'a>( } // useful for debugging - if false { + if cfg!(debug_assertions) + && std::env::var("ROC_PRETTY_PRINT_ALIAS_CONTENTS").is_ok() + { buf.push_str("[[ but really "); let content = subs.get_content_without_compacting(*_actual); write_content(env, ctx, content, subs, buf, parens); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 1aac8b45bd..a909fde93d 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -772,7 +772,15 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: AliasKind::Opaque => "Opaque", }; - write!(f, "{}({:?}, {:?}, {:?})", wrap, name, slice, actual) + write!( + f, + "{}({:?}, {:?}, <{:?}>{:?})", + wrap, + name, + slice, + actual, + SubsFmtContent(subs.get_content_without_compacting(*actual), subs) + ) } Content::RangedNumber(typ, range) => { let slice = subs.get_subs_slice(*range); @@ -833,7 +841,16 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f let (it, new_ext) = tags.sorted_iterator_and_ext(subs, *ext); for (name, slice) in it { - write!(f, "{:?} {:?}, ", name, slice)?; + write!(f, "{:?} ", name)?; + for var in slice { + write!( + f, + "<{:?}>{:?} ", + var, + SubsFmtContent(subs.get_content_without_compacting(*var), subs) + )?; + } + write!(f, ", ")?; } write!(f, "]<{:?}>", new_ext) @@ -1889,7 +1906,14 @@ impl Subs { } pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec)> { - occurs(self, &[], var) + occurs(self, &[], var, false) + } + + pub fn occurs_including_recursion_vars( + &self, + var: Variable, + ) -> Result<(), (Variable, Vec)> { + occurs(self, &[], var, true) } pub fn mark_tag_union_recursive( @@ -2859,6 +2883,7 @@ fn occurs( subs: &Subs, seen: &[Variable], input_var: Variable, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> { use self::Content::*; use self::FlatType::*; @@ -2882,47 +2907,77 @@ fn occurs( new_seen.push(root_var); match flat_type { - Apply(_, args) => { - short_circuit(subs, root_var, &new_seen, subs.get_subs_slice(*args).iter()) - } + Apply(_, args) => short_circuit( + subs, + root_var, + &new_seen, + subs.get_subs_slice(*args).iter(), + include_recursion_var, + ), Func(arg_vars, closure_var, ret_var) => { let it = once(ret_var) .chain(once(closure_var)) .chain(subs.get_subs_slice(*arg_vars).iter()); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } Record(vars_by_field, ext_var) => { let slice = SubsSlice::new(vars_by_field.variables_start, vars_by_field.length); let it = once(ext_var).chain(subs.get_subs_slice(slice).iter()); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } TagUnion(tags, ext_var) => { for slice_index in tags.variables() { let slice = subs[slice_index]; for var_index in slice { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help( + subs, + root_var, + &new_seen, + var, + include_recursion_var, + )?; } } - short_circuit_help(subs, root_var, &new_seen, *ext_var) + short_circuit_help( + subs, + root_var, + &new_seen, + *ext_var, + include_recursion_var, + ) } FunctionOrTagUnion(_, _, ext_var) => { let it = once(ext_var); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } - RecursiveTagUnion(_rec_var, tags, ext_var) => { - // TODO rec_var is excluded here, verify that this is correct + RecursiveTagUnion(rec_var, tags, ext_var) => { + if include_recursion_var { + new_seen.push(subs.get_root_key_without_compacting(*rec_var)); + } for slice_index in tags.variables() { let slice = subs[slice_index]; for var_index in slice { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help( + subs, + root_var, + &new_seen, + var, + include_recursion_var, + )?; } } - short_circuit_help(subs, root_var, &new_seen, *ext_var) + short_circuit_help( + subs, + root_var, + &new_seen, + *ext_var, + include_recursion_var, + ) } EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()), } @@ -2933,7 +2988,7 @@ fn occurs( for var_index in args.into_iter() { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help(subs, root_var, &new_seen, var, include_recursion_var)?; } Ok(()) @@ -2942,7 +2997,7 @@ fn occurs( let mut new_seen = seen.to_owned(); new_seen.push(root_var); - short_circuit_help(subs, root_var, &new_seen, *typ)?; + short_circuit_help(subs, root_var, &new_seen, *typ, include_recursion_var)?; // _range_vars excluded because they are not explicitly part of the type. Ok(()) @@ -2957,12 +3012,13 @@ fn short_circuit<'a, T>( root_key: Variable, seen: &[Variable], iter: T, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> where T: Iterator, { for var in iter { - short_circuit_help(subs, root_key, seen, *var)?; + short_circuit_help(subs, root_key, seen, *var, include_recursion_var)?; } Ok(()) @@ -2974,8 +3030,9 @@ fn short_circuit_help( root_key: Variable, seen: &[Variable], var: Variable, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> { - if let Err((v, mut vec)) = occurs(subs, seen, var) { + if let Err((v, mut vec)) = occurs(subs, seen, var, include_recursion_var) { vec.push(root_key); return Err((v, vec)); } @@ -3660,10 +3717,14 @@ fn flat_type_to_err_type( } fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase { - let (name, new_index) = name_type_var(state.normals, &mut state.taken); + let (name, new_index) = name_type_var(state.normals, &mut state.taken.iter(), |var, str| { + var.as_str() == str + }); state.normals = new_index; + state.taken.insert(name.clone()); + name } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 8f5f68636e..0f3536cd54 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -2328,26 +2328,33 @@ fn write_type_ext(ext: TypeExt, buf: &mut String) { static THE_LETTER_A: u32 = 'a' as u32; -pub fn name_type_var(letters_used: u32, taken: &mut MutSet) -> (Lowercase, u32) { +pub fn name_type_var bool>( + letters_used: u32, + taken: &mut impl Iterator, + mut predicate: F, +) -> (Lowercase, u32) { // TODO we should arena-allocate this String, // so all the strings in the entire pass only require ~1 allocation. - let mut generated_name = String::with_capacity((letters_used as usize) / 26 + 1); + let mut buf = String::with_capacity((letters_used as usize) / 26 + 1); - let mut remaining = letters_used as i32; - while remaining >= 0 { - generated_name.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap()); - remaining -= 26; - } + let is_taken = { + let mut remaining = letters_used as i32; - let generated_name = generated_name.into(); + while remaining >= 0 { + buf.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap()); + remaining -= 26; + } - if taken.contains(&generated_name) { + let generated_name: &str = buf.as_str(); + + taken.any(|item| predicate(&item, generated_name)) + }; + + if is_taken { // If the generated name is already taken, try again. - name_type_var(letters_used + 1, taken) + name_type_var(letters_used + 1, taken, predicate) } else { - taken.insert(generated_name.clone()); - - (generated_name, letters_used + 1) + (buf.into(), letters_used + 1) } } diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 794f9ad00b..88bf2456ca 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,11 +1,12 @@ use bitflags::bitflags; -use roc_error_macros::{internal_error, todo_abilities}; +use roc_error_macros::internal_error; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; use roc_types::subs::{ AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, - RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, + RecordFields, Subs, SubsFmtContent, SubsIndex, SubsSlice, UnionTags, Variable, + VariableSubsSlice, }; use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, RecordField}; @@ -274,12 +275,28 @@ pub fn unify_pool( } } +/// Set `ROC_PRINT_UNIFICATIONS` in debug runs to print unifications as they start and complete as +/// a tree to stderr. +/// NOTE: Only run this on individual tests! Run on multiple threads, this would clobber each others' output. #[cfg(debug_assertions)] -fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: bool) { +fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option<&Outcome>) { + static mut UNIFICATION_DEPTH: usize = 0; + if std::env::var("ROC_PRINT_UNIFICATIONS").is_ok() { - let time = if before_unified { "START" } else { "END" }; - // if true, print the types that are unified. - // + let prefix = match opt_outcome { + None => "❔", + Some(outcome) if outcome.mismatches.is_empty() => "βœ…", + Some(_) => "❌", + }; + + let depth = unsafe { UNIFICATION_DEPTH }; + let indent = 2; + let (use_depth, new_depth) = if opt_outcome.is_none() { + (depth, depth + indent) + } else { + (depth - indent, depth - indent) + }; + // NOTE: names are generated here (when creating an error type) and that modifies names // generated by pretty_print.rs. So many test will fail with changes in variable names when // this block runs. @@ -294,22 +311,25 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: boo let content_2 = subs.get(ctx.second).content; let mode = if ctx.mode.is_eq() { "~" } else { "+=" }; eprintln!( - "{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}", - time, + "{}{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}", + " ".repeat(use_depth), + prefix, ctx.first, ctx.second, ctx.first, - roc_types::subs::SubsFmtContent(&content_1, subs), + SubsFmtContent(&content_1, subs), mode, ctx.second, - roc_types::subs::SubsFmtContent(&content_2, subs), + SubsFmtContent(&content_2, subs), ); + + unsafe { UNIFICATION_DEPTH = new_depth }; } } fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { #[cfg(debug_assertions)] - debug_print_unified_types(subs, &ctx, true); + debug_print_unified_types(subs, &ctx, None); let result = match &ctx.first_desc.content { FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, None, &ctx.second_desc.content), @@ -349,7 +369,7 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { }; #[cfg(debug_assertions)] - debug_print_unified_types(subs, &ctx, false); + debug_print_unified_types(subs, &ctx, Some(&result)); result } @@ -369,9 +389,12 @@ fn unify_ranged_number( // Ranged number wins merge(subs, ctx, RangedNumber(real_var, range_vars)) } - RecursionVar { .. } | RigidVar(..) | Alias(..) | Structure(..) => { - unify_pool(subs, pool, real_var, ctx.second, ctx.mode) - } + RecursionVar { .. } + | RigidVar(..) + | Alias(..) + | Structure(..) + | RigidAbleVar(..) + | FlexAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), &RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); if outcome.mismatches.is_empty() { @@ -382,9 +405,6 @@ fn unify_ranged_number( // TODO: We should probably check that "range_vars" and "other_range_vars" intersect } Error => merge(subs, ctx, Error), - FlexAbleVar(..) | RigidAbleVar(..) => { - todo_abilities!("I don't think this can be reached yet") - } }; if !outcome.mismatches.is_empty() { @@ -451,8 +471,8 @@ fn unify_alias( RecursionVar { structure, .. } if !either_is_opaque => { unify_pool(subs, pool, real_var, *structure, ctx.mode) } - RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), - RigidAbleVar (_, ability) | FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => { + RigidVar(_) | RigidAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => { // Opaque type wins let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind)); outcome.must_implement_ability.push(MustImplementAbility { typ: symbol, ability: *ability }); @@ -557,7 +577,15 @@ fn unify_structure( RecursionVar { structure, .. } => match flat_type { FlatType::TagUnion(_, _) => { // unify the structure with this unrecursive tag union - unify_pool(subs, pool, ctx.first, *structure, ctx.mode) + let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + + if outcome.mismatches.is_empty() { + outcome.union(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + } + + outcome } FlatType::RecursiveTagUnion(rec, _, _) => { debug_assert!(is_recursion_var(subs, *rec)); @@ -566,7 +594,15 @@ fn unify_structure( } FlatType::FunctionOrTagUnion(_, _, _) => { // unify the structure with this unrecursive tag union - unify_pool(subs, pool, ctx.first, *structure, ctx.mode) + let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + + if outcome.mismatches.is_empty() { + outcome.union(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + } + + outcome } // Only tag unions can be recursive; everything else is an error. _ => mismatch!( @@ -624,6 +660,57 @@ fn unify_structure( } } +/// Ensures that a non-recursive tag union, when unified with a recursion var to become a recursive +/// tag union, properly contains a recursion variable that recurses on itself. +// +// When might this not be the case? For example, in the code +// +// Indirect : [ Indirect ConsList ] +// +// ConsList : [ Nil, Cons Indirect ] +// +// l : ConsList +// l = Cons (Indirect (Cons (Indirect Nil))) +// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a +// # ~~~~~~~~~~~~~~~~~~~~~ region-b +// l +// +// Suppose `ConsList` has the expanded type `[ Nil, Cons [ Indirect ] ] as `. +// After unifying the tag application annotated "region-b" with the recursion variable ``, +// the tentative total-type of the application annotated "region-a" would be +// ` = [ Nil, Cons [ Indirect ] ] as `. That is, the type of the recursive tag union +// would be inlined at the site "v", rather than passing through the correct recursion variable +// "rec" first. +// +// This is not incorrect from a type perspective, but causes problems later on for e.g. layout +// determination, which expects recursion variables to be placed correctly. Attempting to detect +// this during layout generation does not work so well because it may be that there *are* recursive +// tag unions that should be inlined, and not pass through recursion variables. So instead, try to +// resolve these cases here. +// +// See tests labeled "issue_2810" for more examples. +fn fix_tag_union_recursion_variable( + subs: &mut Subs, + ctx: &Context, + tag_union_promoted_to_recursive: Variable, + recursion_var: &Content, +) -> Outcome { + debug_assert!(matches!( + subs.get_content_without_compacting(tag_union_promoted_to_recursive), + Structure(FlatType::RecursiveTagUnion(..)) + )); + + let has_recursing_recursive_variable = subs + .occurs_including_recursion_vars(tag_union_promoted_to_recursive) + .is_err(); + + if !has_recursing_recursive_variable { + merge(subs, ctx, *recursion_var) + } else { + Outcome::default() + } +} + fn unify_record( subs: &mut Subs, pool: &mut Pool, diff --git a/examples/benchmarks/Closure.roc b/examples/benchmarks/Closure.roc index d6bf6b3ff8..1a78d99867 100644 --- a/examples/benchmarks/Closure.roc +++ b/examples/benchmarks/Closure.roc @@ -20,32 +20,32 @@ toUnitBorrowed = \x -> Str.countGraphemes x foo = \f, x -> f x # --- -closure2 : {} -> Task.Task {} [] -closure2 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.map (\_ -> x) - |> Task.map toUnit - -toUnit = \_ -> {} - -# --- -closure3 : {} -> Task.Task {} [] -closure3 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {})) - -# --- -closure4 : {} -> Task.Task {} [] -closure4 = \_ -> - x : Str - x = "a long string such that it's malloced" - - Task.succeed {} - |> Task.after (\_ -> Task.succeed x) - |> Task.map (\_ -> {}) +# closure2 : {} -> Task.Task {} [] +# closure2 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.map (\_ -> x) +# |> Task.map toUnit +# +# toUnit = \_ -> {} +# +# # --- +# closure3 : {} -> Task.Task {} [] +# closure3 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {})) +# +# # --- +# closure4 : {} -> Task.Task {} [] +# closure4 = \_ -> +# x : Str +# x = "a long string such that it's malloced" +# +# Task.succeed {} +# |> Task.after (\_ -> Task.succeed x) +# |> Task.map (\_ -> {}) diff --git a/getting_started/linux_x86.md b/getting_started/linux_x86.md index f499209607..bbc672377a 100644 --- a/getting_started/linux_x86.md +++ b/getting_started/linux_x86.md @@ -24,9 +24,9 @@ # Rust. If you installed rust in this terminal you'll need to open a new one first! ./roc examples/hello-world/rust-platform/helloRust.roc # Zig - ./roc examples/hello-world/zig-platform/helloZig.roc + ./roc examples/hello-world/zig-platform/helloZig.roc --linker=legacy # C - ./roc examples/hello-world/c-platform/helloC.roc + ./roc examples/hello-world/c-platform/helloC.roc --linker=legacy ``` 0. See [here](../README.md#examples) for the other examples. diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index bc718a7c6c..b1825c7288 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -86,12 +86,17 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a>( +/// +/// If we pass through aliases, the top-level alias that should be displayed to the user is passed +/// back as an option. +/// +/// Returns (new type containers, optional alias content, real content). +fn unroll_newtypes_and_aliases<'a>( env: &Env<'a, 'a>, mut content: &'a Content, -) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { +) -> (Vec<'a, NewtypeKind<'a>>, Option<&'a Content>, &'a Content) { let mut newtype_containers = Vec::with_capacity_in(1, env.arena); - let mut force_alias_content = None; + let mut alias_content = None; loop { match content { Content::Structure(FlatType::TagUnion(tags, _)) @@ -118,18 +123,19 @@ fn unroll_newtypes<'a>( } Content::Alias(_, _, real_var, _) => { // We need to pass through aliases too, because their underlying types may have - // unrolled newtypes. In such cases return the list of unrolled newtypes, but keep - // the content as the alias for readability. For example, + // unrolled newtypes. For example, // T : { a : Str } // v : T // v = { a : "value" } // v - // Here we need the newtype container to be `[RecordField(a)]`, but the content to - // remain as the alias `T`. - force_alias_content = Some(content); + // Here we need the newtype container to be `[RecordField(a)]`. + // + // At the end of the day what we should show to the user is the alias content, not + // what's inside, so keep that around too. + alias_content = Some(content); content = env.subs.get_content_without_compacting(*real_var); } - _ => return (newtype_containers, force_alias_content.unwrap_or(content)), + _ => return (newtype_containers, alias_content, content), } } } @@ -140,8 +146,8 @@ fn apply_newtypes<'a>( mut expr: Expr<'a>, ) -> Expr<'a> { let arena = env.arena; - // Reverse order of what we receieve from `unroll_newtypes` since we want the deepest - // container applied first. + // Reverse order of what we receieve from `unroll_newtypes_and_aliases` since + // we want the deepest container applied first. for container in newtype_containers.into_iter().rev() { match container { NewtypeKind::Tag(tag_name) => { @@ -162,13 +168,6 @@ fn apply_newtypes<'a>( expr } -fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { - while let Content::Alias(_, _, real, _) = content { - content = env.subs.get_content_without_compacting(*real); - } - content -} - fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); @@ -278,13 +277,18 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( layout: &Layout<'a>, content: &'a Content, ) -> Result, ToAstProblem> { - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); + let (newtype_containers, alias_content, raw_content) = + unroll_newtypes_and_aliases(env, content); macro_rules! num_helper { ($ty:ty) => { app.call_function(main_fn_name, |_, num: $ty| { - num_to_ast(env, number_literal_to_ast(env.arena, num), content) + num_to_ast( + env, + number_literal_to_ast(env.arena, num), + // We determine the number from what the alias looks like. + alias_content.unwrap_or(raw_content), + ) }) }; } @@ -292,17 +296,17 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( let result = match layout { Layout::Builtin(Builtin::Bool) => Ok(app .call_function(main_fn_name, |mem: &A::Memory, num: bool| { - bool_to_ast(env, mem, num, content) + bool_to_ast(env, mem, num, raw_content) })), Layout::Builtin(Builtin::Int(int_width)) => { use IntWidth::*; - let result = match (content, int_width) { + let result = match (raw_content, int_width) { (Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)), U8) => num_helper!(u8), (_, U8) => { // This is not a number, it's a tag union or something else app.call_function(main_fn_name, |mem: &A::Memory, num: u8| { - byte_to_ast(env, mem, num, content) + byte_to_ast(env, mem, num, raw_content) }) } // The rest are numbers... for now @@ -344,14 +348,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function( main_fn_name, |mem: &A::Memory, (addr, len): (usize, usize)| { - list_to_ast(env, mem, addr, len, elem_layout, content) + list_to_ast(env, mem, addr, len, elem_layout, raw_content) }, )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } Layout::Struct { field_layouts, .. } => { - let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content { + let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match raw_content { Content::Structure(FlatType::Record(fields, _)) => { Ok(struct_to_ast(env, mem, addr, *fields)) } @@ -413,7 +417,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( main_fn_name, size as usize, |mem: &'a A::Memory, addr: usize| { - addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Unreachable, + raw_content, + ) }, )) } @@ -432,7 +443,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( addr, layout, WhenRecursive::Loop(*layout), - content, + raw_content, ) }, )) @@ -447,7 +458,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( main_fn_name, size as usize, |mem: &A::Memory, addr| { - addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Unreachable, + raw_content, + ) }, )) } @@ -493,9 +511,10 @@ fn addr_to_ast<'a, M: ReplAppMemory>( }}; } - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); - let expr = match (content, layout) { + let (newtype_containers, _alias_content, raw_content) = + unroll_newtypes_and_aliases(env, content); + + let expr = match (raw_content, layout) { (Content::Structure(FlatType::Func(_, _, _)), _) | (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION, (_, Layout::Builtin(Builtin::Bool)) => { @@ -503,7 +522,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( // num is always false at the moment. let num: bool = mem.deref_bool(addr); - bool_to_ast(env, mem, num, content) + bool_to_ast(env, mem, num, raw_content) } (_, Layout::Builtin(Builtin::Int(int_width))) => { use IntWidth::*; @@ -534,14 +553,14 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let elem_addr = mem.deref_usize(addr); let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize); - list_to_ast(env, mem, elem_addr, len, elem_layout, content) + list_to_ast(env, mem, elem_addr, len, elem_layout, raw_content) } (_, Layout::Builtin(Builtin::Str)) => { let string = mem.deref_str(addr); let arena_str = env.arena.alloc_str(string); Expr::Str(StrLiteral::PlainLine(arena_str)) } - (_, Layout::Struct{field_layouts, ..}) => match content { + (_, Layout::Struct{field_layouts, ..}) => match raw_content { Content::Structure(FlatType::Record(fields, _)) => { struct_to_ast(env, mem, addr, *fields) } @@ -566,7 +585,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } }, (_, Layout::RecursivePointer) => { - match (content, when_recursive) { + match (raw_content, when_recursive) { (Content::RecursionVar { structure, opt_name: _, @@ -580,7 +599,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( (_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => { let union_layout = UnionLayout::NonRecursive(union_layouts); - let tags = match content { + let tags = match raw_content { Content::Structure(FlatType::TagUnion(tags, _)) => tags, other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other), }; @@ -614,7 +633,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => { - let (rec_var, tags) = match content { + let (rec_var, tags) = match raw_content { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), _ => unreachable!("any other content would have a different layout"), }; @@ -644,7 +663,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -672,7 +691,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -706,7 +725,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } } (_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -803,7 +822,8 @@ fn list_to_ast<'a, M: ReplAppMemory>( for index in 0..len { let offset_bytes = index * elem_size; let elem_addr = addr + offset_bytes; - let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); + let (newtype_containers, _alias_content, elem_content) = + unroll_newtypes_and_aliases(env, elem_content); let expr = addr_to_ast( env, mem, diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index 4e8f698c29..efe32c4ca1 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -47,7 +47,7 @@ pub fn compile_to_mono<'a>( target_info: TargetInfo, palette: Palette, ) -> Result, Vec> { - let filename = PathBuf::from("REPL.roc"); + let filename = PathBuf::from(""); let src_dir = Path::new("fake/test/path"); let module_src = arena.alloc(promote_expr_to_module(src)); diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index b8a1971058..e8c51537bc 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1138,3 +1138,42 @@ fn issue_2818() { r" : {} -> List Str", ) } + +#[test] +fn issue_2810_recursive_layout_inside_nonrecursive() { + expect_success( + indoc!( + r#" + Command : [ Command Tool ] + + Job : [ Job Command ] + + Tool : [ SystemTool, FromJob Job ] + + a : Job + a = Job (Command (FromJob (Job (Command SystemTool)))) + a + "# + ), + "Job (Command (FromJob (Job (Command SystemTool)))) : Job", + ) +} + +#[test] +fn render_nullable_unwrapped_passing_through_alias() { + expect_success( + indoc!( + r#" + Deep : [ L DeepList ] + + DeepList : [ Nil, Cons Deep ] + + v : DeepList + v = (Cons (L (Cons (L (Cons (L Nil)))))) + + v + "# + ), + "Cons (L (Cons (L (Cons (L Nil))))) : DeepList", + ) +} diff --git a/reporting/.gitignore b/reporting/.gitignore new file mode 100644 index 0000000000..cad2309100 --- /dev/null +++ b/reporting/.gitignore @@ -0,0 +1 @@ +/tmp \ No newline at end of file diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 4e627cb2dc..9d755bee9c 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -45,6 +45,8 @@ const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE"; const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAUSE"; const ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE: &str = "ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE"; const ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULTIPLE VARIABLES"; +const ABILITY_NOT_ON_TOPLEVEL: &str = "ABILITY NOT ON TOP-LEVEL"; +const ABILITY_USED_AS_TYPE: &str = "ABILITY USED AS TYPE"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -642,7 +644,9 @@ pub fn can_problem<'b>( alloc.region(lines.convert_region(region)), alloc.concat([ alloc.keyword("has"), - alloc.reflow(" clauses can only be specified on the top-level type annotation of an ability member."), + alloc.reflow( + " clauses can only be specified on the top-level type annotations.", + ), ]), ]); title = ILLEGAL_HAS_CLAUSE.to_string(); @@ -735,6 +739,46 @@ pub fn can_problem<'b>( title = ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE.to_string(); severity = Severity::RuntimeError; } + + Problem::AbilityNotOnToplevel { region } => { + doc = alloc.stack(vec![ + alloc.concat(vec![alloc.reflow( + "This ability definition is not on the top-level of a module:", + )]), + alloc.region(lines.convert_region(region)), + alloc.reflow("Abilities can only be defined on the top-level of a Roc module."), + ]); + title = ABILITY_NOT_ON_TOPLEVEL.to_string(); + severity = Severity::RuntimeError; + } + + Problem::AbilityUsedAsType(suggested_var_name, ability, region) => { + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("You are attempting to use the ability "), + alloc.symbol_unqualified(ability), + alloc.reflow(" as a type directly:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.reflow( + "Abilities can only be used in type annotations to constrain type variables.", + ), + alloc + .hint("") + .append(alloc.reflow("Perhaps you meant to include a ")) + .append(alloc.keyword("has")) + .append(alloc.reflow(" annotation, like")), + alloc.type_block(alloc.concat(vec![ + alloc.type_variable(suggested_var_name), + alloc.space(), + alloc.keyword("has"), + alloc.space(), + alloc.symbol_unqualified(ability), + ])), + ]); + title = ABILITY_USED_AS_TYPE.to_string(); + severity = Severity::RuntimeError; + } }; Report { diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 0351636408..3e0a301298 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1767,7 +1767,7 @@ pub enum Problem { FieldsMissing(Vec), TagTypo(TagName, Vec), TagsMissing(Vec), - BadRigidVar(Lowercase, ErrorType), + BadRigidVar(Lowercase, ErrorType, Option), OptionalRequiredMismatch(Lowercase), OpaqueComparedToNonOpaque, } @@ -1872,7 +1872,7 @@ fn diff_is_wildcard_comparison<'b>( ) -> bool { let Comparison { problems, .. } = to_comparison(alloc, actual, expected); match problems.last() { - Some(Problem::BadRigidVar(v1, ErrorType::RigidVar(v2))) => { + Some(Problem::BadRigidVar(v1, ErrorType::RigidVar(v2), None)) => { v1.as_str() == WILDCARD && v2.as_str() == WILDCARD } _ => false, @@ -2143,6 +2143,32 @@ fn to_diff<'b>( same(alloc, parens, type1) } + (RigidVar(x), other) | (other, RigidVar(x)) => { + let (left, left_able) = to_doc(alloc, Parens::InFn, type1); + let (right, right_able) = to_doc(alloc, Parens::InFn, type2); + + Diff { + left, + right, + status: Status::Different(vec![Problem::BadRigidVar(x, other, None)]), + left_able, + right_able, + } + } + + (RigidAbleVar(x, ab), other) | (other, RigidAbleVar(x, ab)) => { + let (left, left_able) = to_doc(alloc, Parens::InFn, type1); + let (right, right_able) = to_doc(alloc, Parens::InFn, type2); + + Diff { + left, + right, + status: Status::Different(vec![Problem::BadRigidVar(x, other, Some(ab))]), + left_able, + right_able, + } + } + (Function(args1, _, ret1), Function(args2, _, ret2)) => { if args1.len() == args2.len() { let mut status = Status::Similar; @@ -2325,7 +2351,6 @@ fn to_diff<'b>( }; let problems = match pair { - (RigidVar(x), other) | (other, RigidVar(x)) => vec![Problem::BadRigidVar(x, other)], (a, b) if (is_int(&a) && is_float(&b)) || (is_float(&a) && is_int(&b)) => { vec![Problem::IntFloat] } @@ -2751,6 +2776,7 @@ fn ext_to_status(ext1: &TypeExt, ext2: &TypeExt) -> Status { Status::Different(vec![Problem::BadRigidVar( x.clone(), ErrorType::RigidVar(y.clone()), + None, )]) } } @@ -3128,15 +3154,25 @@ fn type_problem_to_pretty<'b>( alloc.tip().append(line) } - (BadRigidVar(x, tipe), expectation) => { + (BadRigidVar(x, tipe, opt_ability), expectation) => { use ErrorType::*; let bad_rigid_var = |name: Lowercase, a_thing| { + let kind_of_value = match opt_ability { + Some(ability) => alloc.concat([ + alloc.reflow("any value implementing the "), + alloc.symbol_unqualified(ability), + alloc.reflow(" ability"), + ]), + None => alloc.reflow("any type of value"), + }; alloc .tip() .append(alloc.reflow("The type annotation uses the type variable ")) .append(alloc.type_variable(name)) - .append(alloc.reflow(" to say that this definition can produce any type of value. But in the body I see that it will only produce ")) + .append(alloc.reflow(" to say that this definition can produce ") + .append(kind_of_value) + .append(alloc.reflow(". But in the body I see that it will only produce "))) .append(a_thing) .append(alloc.reflow(" of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general?")) }; diff --git a/reporting/src/report.rs b/reporting/src/report.rs index a97537e18c..2f26a35c75 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -3,7 +3,7 @@ use roc_module::ident::{Lowercase, ModuleName, TagName, Uppercase}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_region::all::LineColumnRegion; use std::fmt; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; pub use crate::error::canonicalize::can_problem; @@ -60,6 +60,46 @@ pub fn cycle<'b>( .annotate(Annotation::TypeBlock) } +const HEADER_WIDTH: usize = 80; + +pub fn pretty_header(title: &str) -> String { + let title_width = title.len() + 4; + let header = format!("── {} {}", title, "─".repeat(HEADER_WIDTH - title_width)); + header +} + +pub fn pretty_header_with_path(title: &str, path: &Path) -> String { + let cwd = std::env::current_dir().unwrap(); + let relative_path = match path.strip_prefix(cwd) { + Ok(p) => p, + _ => path, + } + .to_str() + .unwrap(); + + let title_width = title.len() + 4; + let relative_path_width = relative_path.len() + 3; + let available_path_width = HEADER_WIDTH - title_width - 1; + + // If path is too long to fit in 80 characters with everything else then truncate it + let path_width = relative_path_width.min(available_path_width); + let path_trim = relative_path_width - path_width; + let path = if path_trim > 0 { + format!("...{}", &relative_path[(path_trim + 3)..]) + } else { + relative_path.to_string() + }; + + let header = format!( + "── {} {} {} ─", + title, + "─".repeat(HEADER_WIDTH - (title_width + path_width)), + path + ); + + header +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Severity { /// This will cause a runtime error if some code get srun @@ -129,11 +169,11 @@ impl<'b> Report<'b> { if self.title.is_empty() { self.doc } else { - let header = format!( - "── {} {}", - self.title, - "─".repeat(80 - (self.title.len() + 4)) - ); + let header = if self.filename == PathBuf::from("") { + crate::report::pretty_header(&self.title) + } else { + crate::report::pretty_header_with_path(&self.title, &self.filename) + }; alloc.stack([alloc.text(header).annotate(Annotation::Header), self.doc]) } @@ -215,6 +255,7 @@ pub struct StyleCodes { pub bold: &'static str, pub underline: &'static str, pub reset: &'static str, + pub color_reset: &'static str, } pub const ANSI_STYLE_CODES: StyleCodes = StyleCodes { @@ -228,6 +269,7 @@ pub const ANSI_STYLE_CODES: StyleCodes = StyleCodes { bold: "\u{001b}[1m", underline: "\u{001b}[4m", reset: "\u{001b}[0m", + color_reset: "\u{1b}[39m", }; macro_rules! html_color { @@ -247,6 +289,7 @@ pub const HTML_STYLE_CODES: StyleCodes = StyleCodes { bold: "", underline: "", reset: "", + color_reset: "", }; // define custom allocator struct so we can `impl RocDocAllocator` custom helpers diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 34aa95bf8f..269e0598ff 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -151,7 +151,7 @@ pub fn can_expr_with<'a>( // rules multiple times unnecessarily. let loc_expr = operator::desugar_expr(arena, &loc_expr); - let mut scope = Scope::new(home, &mut var_store); + let mut scope = Scope::new_with_aliases(home, &mut var_store); let dep_idents = IdentIds::exposed_builtins(0); let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default()); let (loc_expr, output) = canonicalize_expr( diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index b87fe6218f..8d25660f64 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -41,7 +41,7 @@ mod test_reporting { Report { title: "".to_string(), doc, - filename: filename_from_string(r"\code\proj\Main.roc"), + filename: filename_from_string(r"/code/proj/Main.roc"), severity: Severity::RuntimeError, } } @@ -61,12 +61,12 @@ mod test_reporting { } fn run_load_and_infer<'a>( + subdir: &str, arena: &'a Bump, src: &'a str, ) -> (String, Result>) { use std::fs::File; use std::io::Write; - use tempfile::tempdir; let module_src = if src.starts_with("app") { // this is already a module @@ -78,7 +78,12 @@ mod test_reporting { let exposed_types = Default::default(); let loaded = { - let dir = tempdir().unwrap(); + // Use a deterministic temporary directory. + // We can't have all tests use "tmp" because tests run in parallel, + // so append the test name to the tmp path. + let tmp = format!("tmp/{}", subdir); + let dir = roc_test_utils::TmpDir::new(&tmp); + let filename = PathBuf::from("Test.roc"); let file_path = dir.path().join(filename); let full_file_path = file_path.clone(); @@ -94,8 +99,6 @@ mod test_reporting { ); drop(file); - dir.close().unwrap(); - result }; @@ -103,6 +106,7 @@ mod test_reporting { } fn infer_expr_help_new<'a>( + subdir: &str, arena: &'a Bump, expr_src: &'a str, ) -> Result< @@ -116,7 +120,7 @@ mod test_reporting { ), LoadingProblem<'a>, > { - let (module_src, result) = run_load_and_infer(arena, expr_src); + let (module_src, result) = run_load_and_infer(subdir, arena, expr_src); let LoadedModule { module_id: home, mut can_problems, @@ -199,17 +203,17 @@ mod test_reporting { )) } - fn list_reports_new(arena: &Bump, src: &str, finalize_render: F) -> String + fn list_reports_new(subdir: &str, arena: &Bump, src: &str, finalize_render: F) -> String where F: FnOnce(RocDocBuilder<'_>, &mut String), { use ven_pretty::DocAllocator; - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); let mut buf = String::new(); - match infer_expr_help_new(arena, src) { + match infer_expr_help_new(subdir, arena, src) { Err(LoadingProblem::FormattedReport(fail)) => fail, Ok((module_src, type_problems, can_problems, mono_problems, home, interns)) => { let lines = LineInfo::new(&module_src); @@ -359,7 +363,7 @@ mod test_reporting { let src_lines: Vec<&str> = src.split('\n').collect(); let lines = LineInfo::new(src); - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); match infer_expr_help(arena, src) { Err(parse_err) => { @@ -424,7 +428,7 @@ mod test_reporting { let state = State::new(src.as_bytes()); - let filename = filename_from_string(r"\code\proj\Main.roc"); + let filename = filename_from_string(r"/code/proj/Main.roc"); let src_lines: Vec<&str> = src.split('\n').collect(); let lines = LineInfo::new(src); @@ -514,7 +518,7 @@ mod test_reporting { assert_eq!(readable, expected_rendering); } - fn new_report_problem_as(src: &str, expected_rendering: &str) { + fn new_report_problem_as(subdir: &str, src: &str, expected_rendering: &str) { let arena = Bump::new(); let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| { @@ -523,7 +527,7 @@ mod test_reporting { .expect("list_reports") }; - let buf = list_reports_new(&arena, src, finalize_render); + let buf = list_reports_new(subdir, &arena, src, finalize_render); // convenient to copy-paste the generated message if buf != expected_rendering { @@ -558,7 +562,7 @@ mod test_reporting { ), indoc!( r#" - ── NOT EXPOSED ───────────────────────────────────────────────────────────────── + ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ The List module does not expose `isempty`: @@ -589,7 +593,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `y` is not used anywhere in your code. @@ -618,7 +622,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `i` name is first defined here: @@ -655,7 +659,7 @@ mod test_reporting { // Booly is called a "variable" indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `Booly` name is first defined here: @@ -670,7 +674,7 @@ mod test_reporting { Since these aliases have the same name, it's easy to use the wrong one on accident. Give one of them a new name. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Booly` is not used anywhere in your code. @@ -680,7 +684,7 @@ mod test_reporting { If you didn't intend on using `Booly` then remove it so future readers of your code don't wonder why it is there. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Booly` is not used anywhere in your code. @@ -775,7 +779,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Using != and == together requires parentheses, to clarify how they should be grouped. @@ -807,7 +811,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `bar` value @@ -835,7 +839,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `true` value @@ -871,7 +875,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Using more than one == like this requires parentheses, to clarify how things should be grouped. @@ -901,7 +905,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED ARGUMENT ───────────────────────────────────────────────────────────── + ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ `box` doesn't use `htmlChildren`. @@ -914,7 +918,7 @@ mod test_reporting { at the start of a variable name is a way of saying that the variable is not used. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `y` is not used anywhere in your code. @@ -1000,7 +1004,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `theAdmin` value @@ -1074,7 +1078,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` condition needs to be a Bool: @@ -1104,7 +1108,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` guard condition needs to be a Bool: @@ -1133,7 +1137,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -1164,7 +1168,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 3rd branch of this `if` does not match all the previous branches: @@ -1197,7 +1201,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd branch of this `when` does not match all the previous branches: @@ -1230,7 +1234,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This list contains elements with different types: @@ -1264,7 +1268,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ I cannot update the `.foo` field like this: @@ -1298,7 +1302,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR TYPE ─────────────────────────────────────────────────────────────── + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ I'm inferring a weird self-referential type for `g`: @@ -1327,7 +1331,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR TYPE ─────────────────────────────────────────────────────────────── + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ I'm inferring a weird self-referential type for `f`: @@ -1359,7 +1363,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -1397,7 +1401,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -1435,7 +1439,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -1473,7 +1477,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the `then` branch of this `if` expression: @@ -1511,7 +1515,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -1548,7 +1552,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -1584,7 +1588,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ The `x` value is not a function, but it was given 1 argument: @@ -1610,7 +1614,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ The `f` function expects 1 argument, but it got 2 instead: @@ -1636,7 +1640,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── + ── TOO FEW ARGS ────────────────────────────────────────── /code/proj/Main.roc ─ The `f` function expects 2 arguments, but it got only 1: @@ -1661,7 +1665,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -1692,7 +1696,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern in this `when` does not match the previous ones: @@ -1722,7 +1726,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -1752,7 +1756,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -1783,7 +1787,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `foo` value @@ -1848,7 +1852,7 @@ mod test_reporting { // Just putting this here. We should probably handle or-patterns better indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -1880,7 +1884,7 @@ mod test_reporting { // Maybe this should specifically say the pattern doesn't work? indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -1912,7 +1916,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: @@ -1947,7 +1951,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer pattern is malformed: @@ -1972,7 +1976,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float pattern is malformed: @@ -1997,7 +2001,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer pattern is malformed: @@ -2022,7 +2026,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer pattern is malformed: @@ -2047,7 +2051,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer pattern is malformed: @@ -2073,7 +2077,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -2123,7 +2127,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the `else` branch of this `if` expression: @@ -2161,7 +2165,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2200,7 +2204,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2240,7 +2244,7 @@ mod test_reporting { ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `ok` value @@ -2275,7 +2279,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `ok` is not used anywhere in your code. @@ -2285,7 +2289,7 @@ mod test_reporting { If you didn't intend on using `ok` then remove it so future readers of your code don't wonder why it is there. - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2321,7 +2325,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR DEFINITION ───────────────────────────────────────────────────────── + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ The `f` value is defined directly in terms of itself, causing an infinite loop. @@ -2345,7 +2349,7 @@ mod test_reporting { ), indoc!( r#" - ── CIRCULAR DEFINITION ───────────────────────────────────────────────────────── + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ The `foo` definition is causing a very tricky infinite loop: @@ -2377,7 +2381,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `x` record doesn’t have a `foo` field: @@ -2403,7 +2407,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `x` record doesn’t have a `foo` field: @@ -2440,7 +2444,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `r` record doesn’t have a `foo` field: @@ -2472,7 +2476,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `x` record doesn’t have a `foo` field: @@ -2506,7 +2510,7 @@ mod test_reporting { // TODO also suggest fields with the correct type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2535,7 +2539,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2567,7 +2571,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -2599,7 +2603,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2637,7 +2641,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -2680,7 +2684,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern does not cover all the possibilities: @@ -2715,7 +2719,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -2751,7 +2755,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2782,7 +2786,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2814,7 +2818,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2847,7 +2851,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2880,7 +2884,7 @@ mod test_reporting { // Tip: Looks like a record field guard is not exhaustive. Learn more about record pattern matches at TODO. indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2913,7 +2917,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2943,7 +2947,7 @@ mod test_reporting { ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -2974,7 +2978,7 @@ mod test_reporting { ), indoc!( r#" - ── REDUNDANT PATTERN ─────────────────────────────────────────────────────────── + ── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern is redundant: @@ -3006,7 +3010,7 @@ mod test_reporting { // de-aliases the alias to give a better error message indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is not what I expect: @@ -3048,7 +3052,7 @@ mod test_reporting { // should not report Bar as unused! indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `Foo` alias is recursive in an invalid way: @@ -3067,7 +3071,7 @@ mod test_reporting { Recursion in aliases is only allowed if recursion happens behind a tag. - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ `Bar` is not used anywhere in your code. @@ -3097,7 +3101,7 @@ mod test_reporting { // should not report Bar as unused! indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `Foo` alias is self-recursive in an invalid way: @@ -3121,7 +3125,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3149,7 +3153,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3181,7 +3185,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3220,7 +3224,7 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record defines the `.x` field twice! @@ -3257,8 +3261,8 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE FIELD NAME ──────────────────────────────────────────────────────── - + ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ + This record type defines the `.foo` field twice! 1β”‚ a : { foo : Num.I64, bar : {}, foo : Str } @@ -3289,8 +3293,8 @@ mod test_reporting { ), indoc!( r#" - ── DUPLICATE TAG NAME ────────────────────────────────────────────────────────── - + ── DUPLICATE TAG NAME ──────────────────────────────────── /code/proj/Main.roc ─ + This tag union type defines the `Foo` tag twice! 1β”‚ a : [ Foo Num.I64, Bar {}, Foo Str ] @@ -3322,7 +3326,7 @@ mod test_reporting { ), indoc!( r#" - ── NAMING PROBLEM ────────────────────────────────────────────────────────────── + ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This annotation does not match the definition immediately following it: @@ -3364,7 +3368,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern in the definition of `MyAlias` is not what I expect: @@ -3373,7 +3377,7 @@ mod test_reporting { Only type variables like `a` or `value` can occur in this position. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `MyAlias` is not used anywhere in your code. @@ -3400,7 +3404,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This pattern in the definition of `Age` is not what I expect: @@ -3426,8 +3430,8 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── - + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ + The `Num` alias expects 1 type argument, but it got 2 instead: 1β”‚ a : Num.Num Num.I64 Num.F64 @@ -3452,8 +3456,8 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── - + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ + The `Num` alias expects 1 type argument, but it got 2 instead: 1β”‚ f : Str -> Num.Num Num.I64 Num.F64 @@ -3480,7 +3484,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO FEW TYPE ARGUMENTS ────────────────────────────────────────────────────── + ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ The `Pair` alias expects 2 type arguments, but it got 1 instead: @@ -3508,7 +3512,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Pair` alias expects 2 type arguments, but it got 3 instead: @@ -3535,7 +3539,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED TYPE ALIAS PARAMETER ───────────────────────────────────────────────── + ── UNUSED TYPE ALIAS PARAMETER ─────────────────────────── /code/proj/Main.roc ─ The `a` type parameter is not used in the `Foo` alias definition: @@ -3561,7 +3565,7 @@ mod test_reporting { ), indoc!( r#" - ── ARGUMENTS BEFORE EQUALS ───────────────────────────────────────────────────── + ── ARGUMENTS BEFORE EQUALS ─────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition, but I got stuck here: @@ -3590,7 +3594,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -3631,7 +3635,7 @@ mod test_reporting { // TODO do not show recursion var if the recursion var does not render on the surface of a type indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -3673,7 +3677,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too big: @@ -3685,7 +3689,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too small: @@ -3697,7 +3701,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too big: @@ -3709,7 +3713,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal is too small: @@ -3739,7 +3743,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal is too big: @@ -3751,7 +3755,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal is too small: @@ -3788,7 +3792,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -3800,7 +3804,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer literal contains an invalid digit: @@ -3812,7 +3816,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer literal contains an invalid digit: @@ -3824,7 +3828,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer literal contains an invalid digit: @@ -3858,7 +3862,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This hex integer literal contains no digits: @@ -3870,7 +3874,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This octal integer literal contains no digits: @@ -3882,7 +3886,7 @@ mod test_reporting { Tip: Learn more about number literals at TODO - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This binary integer literal contains no digits: @@ -3910,7 +3914,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal contains an invalid digit: @@ -3945,7 +3949,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This expression cannot be updated: @@ -3968,7 +3972,7 @@ mod test_reporting { ), indoc!( r#" - ── MODULE NOT IMPORTED ───────────────────────────────────────────────────────── + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ The `Foo` module is not imported: @@ -3997,7 +4001,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `add` is not what I expect: @@ -4029,7 +4033,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is weird: @@ -4062,7 +4066,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: @@ -4097,7 +4101,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `f` is weird: @@ -4134,7 +4138,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -4169,7 +4173,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -4204,7 +4208,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to this function is not what I expect: @@ -4242,7 +4246,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -4277,7 +4281,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -4306,7 +4310,7 @@ mod test_reporting { ), indoc!( r#" - ── BAD OPTIONAL VALUE ────────────────────────────────────────────────────────── + ── BAD OPTIONAL VALUE ──────────────────────────────────── /code/proj/Main.roc ─ This record uses an optional value for the `.y` field in an incorrect context! @@ -4348,7 +4352,7 @@ mod test_reporting { ), indoc!( r#" - ── REDUNDANT PATTERN ─────────────────────────────────────────────────────────── + ── REDUNDANT PATTERN ───────────────────────────────────── /code/proj/Main.roc ─ The 3rd pattern is redundant: @@ -4399,7 +4403,7 @@ mod test_reporting { ), indoc!( r#" - ── UNUSED ARGUMENT ───────────────────────────────────────────────────────────── + ── UNUSED ARGUMENT ─────────────────────────────────────── /code/proj/Main.roc ─ `f` doesn't use `foo`. @@ -4425,7 +4429,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: @@ -4450,7 +4454,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: @@ -4474,7 +4478,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -4497,7 +4501,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this expression: @@ -4524,7 +4528,7 @@ mod test_reporting { ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -4557,7 +4561,7 @@ mod test_reporting { ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 3 arguments: @@ -4580,7 +4584,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TAG UNION TYPE ─────────────────────────────────────────────────── + ── UNFINISHED TAG UNION TYPE ───────────────────────────── /code/proj/Main.roc ─ I just started parsing a tag union type, but I got stuck here: @@ -4604,7 +4608,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TAG UNION TYPE ─────────────────────────────────────────────────── + ── UNFINISHED TAG UNION TYPE ───────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4628,7 +4632,7 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── + ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4653,7 +4657,7 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── + ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -4678,7 +4682,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I just started parsing a record type, but I got stuck here: @@ -4703,7 +4707,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4729,7 +4733,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4753,7 +4757,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I just started parsing a record type, but I got stuck on this field name: @@ -4779,7 +4783,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED RECORD TYPE ────────────────────────────────────────────────────── + ── UNFINISHED RECORD TYPE ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a record type, but I got stuck here: @@ -4800,7 +4804,7 @@ mod test_reporting { "f : { foo \t }", indoc!( r#" - ── TAB CHARACTER ─────────────────────────────────────────────────────────────── + ── TAB CHARACTER ───────────────────────────────────────── /code/proj/Main.roc ─ I encountered a tab character @@ -4819,7 +4823,7 @@ mod test_reporting { "# comment with a \t\n4", indoc!( " - ── TAB CHARACTER ─────────────────────────────────────────────────────────────── + ── TAB CHARACTER ───────────────────────────────────────── /code/proj/Main.roc ─ I encountered a tab character @@ -4843,7 +4847,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -4866,7 +4870,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type in parentheses, but I got stuck here: @@ -4895,7 +4899,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -4930,7 +4934,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -4964,7 +4968,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -4989,7 +4993,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -5025,7 +5029,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am confused by this type name: @@ -5049,7 +5053,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING FINAL EXPRESSION ──────────────────────────────────────────────────── + ── MISSING FINAL EXPRESSION ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition's final expression, but I got stuck here: @@ -5082,7 +5086,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED INLINE ALIAS ───────────────────────────────────────────────────── + ── UNFINISHED INLINE ALIAS ─────────────────────────────── /code/proj/Main.roc ─ I just started parsing an inline type alias, but I got stuck here: @@ -5108,7 +5112,7 @@ mod test_reporting { ), indoc!( r#" - ── DOUBLE COMMA ──────────────────────────────────────────────────────────────── + ── DOUBLE COMMA ────────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a function argument type, but I encountered two commas in a row: @@ -5135,7 +5139,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type, but I got stuck here: @@ -5162,7 +5166,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED TYPE ───────────────────────────────────────────────────────────── + ── UNFINISHED TYPE ─────────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a type, but I got stuck here: @@ -5189,7 +5193,7 @@ mod test_reporting { ), indoc!( r#" - ── WEIRD TAG NAME ────────────────────────────────────────────────────────────── + ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a tag union type, but I got stuck here: @@ -5219,7 +5223,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `myDict` definition: @@ -5256,7 +5260,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `myDict` definition: @@ -5292,7 +5296,7 @@ mod test_reporting { ), indoc!( r#" - ── IF GUARD NO CONDITION ─────────────────────────────────────────────────────── + ── IF GUARD NO CONDITION ───────────────────────────────── /code/proj/Main.roc ─ I just started parsing an if guard, but there is no guard condition: @@ -5321,7 +5325,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED PATTERN ────────────────────────────────────────────────────────── + ── UNFINISHED PATTERN ──────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a pattern, but I got stuck here: @@ -5350,7 +5354,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING EXPRESSION ────────────────────────────────────────────────────────── + ── MISSING EXPRESSION ──────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a definition, but I got stuck here: @@ -5377,7 +5381,7 @@ mod test_reporting { ), indoc!( r#" - ── MISSING ARROW ─────────────────────────────────────────────────────────────── + ── MISSING ARROW ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a `when` expression, but got stuck here: @@ -5414,7 +5418,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED ARGUMENT LIST ──────────────────────────────────────────────────── + ── UNFINISHED ARGUMENT LIST ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a function argument list, but I got stuck at this comma: @@ -5439,7 +5443,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED ARGUMENT LIST ──────────────────────────────────────────────────── + ── UNFINISHED ARGUMENT LIST ────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a function argument list, but I got stuck at this comma: @@ -5467,7 +5471,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I got stuck here: @@ -5518,7 +5522,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I got stuck here: @@ -5546,7 +5550,7 @@ mod test_reporting { ), indoc!( r#" - ── UNEXPECTED ARROW ──────────────────────────────────────────────────────────── + ── UNEXPECTED ARROW ────────────────────────────────────── /code/proj/Main.roc ─ I am parsing a `when` expression right now, but this arrow is confusing me: @@ -5589,7 +5593,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED IF ─────────────────────────────────────────────────────────────── + ── UNFINISHED IF ───────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an `if` expression, but I got stuck here: @@ -5613,7 +5617,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED IF ─────────────────────────────────────────────────────────────── + ── UNFINISHED IF ───────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an `if` expression, but I got stuck here: @@ -5636,7 +5640,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through started parsing a list, but I got stuck here: @@ -5660,7 +5664,7 @@ mod test_reporting { ), indoc!( r#" - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── + ── UNFINISHED LIST ─────────────────────────────────────── /code/proj/Main.roc ─ I am partway through started parsing a list, but I got stuck here: @@ -5688,7 +5692,7 @@ mod test_reporting { ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This float literal contains an invalid digit: @@ -5710,7 +5714,7 @@ mod test_reporting { r#""abc\u(zzzz)def""#, indoc!( r#" - ── WEIRD CODE POINT ──────────────────────────────────────────────────────────── + ── WEIRD CODE POINT ────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a unicode code point, but I got stuck here: @@ -5732,7 +5736,7 @@ mod test_reporting { r#""abc\(32)def""#, indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This string interpolation is invalid: @@ -5754,7 +5758,7 @@ mod test_reporting { r#""abc\u(110000)def""#, indoc!( r#" - ── INVALID UNICODE ───────────────────────────────────────────────────────────── + ── INVALID UNICODE ─────────────────────────────────────── /code/proj/Main.roc ─ This unicode code point is invalid: @@ -5773,7 +5777,7 @@ mod test_reporting { r#""abc\qdef""#, indoc!( r#" - ── WEIRD ESCAPE ──────────────────────────────────────────────────────────────── + ── WEIRD ESCAPE ────────────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing a string literal, but I got stuck here: @@ -5801,7 +5805,7 @@ mod test_reporting { r#""there is no end"#, indoc!( r#" - ── ENDLESS STRING ────────────────────────────────────────────────────────────── + ── ENDLESS STRING ──────────────────────────────────────── /code/proj/Main.roc ─ I cannot find the end of this string: @@ -5821,7 +5825,7 @@ mod test_reporting { r#""""there is no end"#, indoc!( r#" - ── ENDLESS STRING ────────────────────────────────────────────────────────────── + ── ENDLESS STRING ──────────────────────────────────────── /code/proj/Main.roc ─ I cannot find the end of this block string: @@ -5848,7 +5852,7 @@ mod test_reporting { ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -5877,7 +5881,7 @@ mod test_reporting { report_problem_as( &format!(r#"if True then "abc" else 1 {} 2"#, $op), &format!( -r#"── TYPE MISMATCH ─────────────────────────────────────────────────────────────── +r#"── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `if` has an `else` branch with a different type from its `then` branch: @@ -5923,7 +5927,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `foo` record doesn’t have a `if` field: @@ -5946,7 +5950,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NOT EXPOSED ───────────────────────────────────────────────────────────────── + ── NOT EXPOSED ─────────────────────────────────────────── /code/proj/Main.roc ─ The Num module does not expose `if`: @@ -5974,7 +5978,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -5997,7 +6001,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a private tag here: @@ -6022,7 +6026,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this field access: @@ -6045,7 +6049,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this field access: @@ -6068,7 +6072,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am very confused by this field access @@ -6093,7 +6097,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I trying to parse a record field access here: @@ -6116,7 +6120,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NAMING PROBLEM ────────────────────────────────────────────────────────────── + ── NAMING PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse an identifier here: @@ -6173,7 +6177,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `bar` value @@ -6202,7 +6206,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6228,7 +6232,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6256,7 +6260,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6286,7 +6290,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + ── UNKNOWN OPERATOR ────────────────────────────────────── /code/proj/Main.roc ─ This looks like an operator, but it's not one I recognize! @@ -6316,7 +6320,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD PROVIDES ────────────────────────────────────────────────────────────── + ── WEIRD PROVIDES ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a provides list, but I got stuck here: @@ -6353,7 +6357,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── BAD REQUIRES ──────────────────────────────────────────────────────────────── + ── BAD REQUIRES ────────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but I got stuck here: @@ -6381,7 +6385,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD IMPORTS ─────────────────────────────────────────────────────────────── + ── WEIRD IMPORTS ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but I got stuck here: @@ -6408,7 +6412,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD EXPOSES ─────────────────────────────────────────────────────────────── + ── WEIRD EXPOSES ───────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing an `exposes` list, but I got stuck here: @@ -6436,7 +6440,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD MODULE NAME ─────────────────────────────────────────────────────────── + ── WEIRD MODULE NAME ───────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but got stuck here: @@ -6462,7 +6466,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── WEIRD APP NAME ────────────────────────────────────────────────────────────── + ── WEIRD APP NAME ──────────────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a header, but got stuck here: @@ -6488,7 +6492,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 2 arguments: @@ -6513,7 +6517,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── + ── TOO MANY ARGS ───────────────────────────────────────── /code/proj/Main.roc ─ This value is not a function, but it was given 2 arguments: @@ -6539,7 +6543,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `x` definition: @@ -6569,7 +6573,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6594,7 +6598,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6619,7 +6623,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PARENTHESES ────────────────────────────────────────────────────── + ── UNFINISHED PARENTHESES ──────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6645,7 +6649,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── + ── NEED MORE INDENTATION ───────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a pattern in parentheses, but I got stuck here: @@ -6671,7 +6675,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED PATTERN ────────────────────────────────────────────────────────── + ── UNFINISHED PATTERN ──────────────────────────────────── /code/proj/Main.roc ─ I just started parsing a pattern, but I got stuck here: @@ -6698,7 +6702,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── + ── NEED MORE INDENTATION ───────────────────────────────── /code/proj/Main.roc ─ I am partway through parsing a type in parentheses, but I got stuck here: @@ -6727,7 +6731,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `map` is not what I expect: @@ -6759,7 +6763,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ Underscore patterns are not allowed in definitions @@ -6782,7 +6786,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This `expect` condition needs to be a Bool: @@ -6813,7 +6817,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `mul` is not what I expect: @@ -6828,7 +6832,7 @@ I need all branches in an `if` to have the same type! Num * - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `mult` definition: @@ -6861,7 +6865,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `mul` is not what I expect: @@ -6876,7 +6880,7 @@ I need all branches in an `if` to have the same type! Num a - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `mult` definition: @@ -6915,7 +6919,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO FEW TYPE ARGUMENTS ────────────────────────────────────────────────────── + ── TOO FEW TYPE ARGUMENTS ──────────────────────────────── /code/proj/Main.roc ─ The `Result` alias expects 2 type arguments, but it got 1 instead: @@ -6947,7 +6951,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TOO MANY TYPE ARGUMENTS ───────────────────────────────────────────────────── + ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Result` alias expects 2 type arguments, but it got 3 instead: @@ -6973,7 +6977,7 @@ I need all branches in an `if` to have the same type! // TODO: We should tell the user that we inferred `_` as `a` indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7011,7 +7015,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7047,7 +7051,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7087,7 +7091,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `f` definition: @@ -7123,7 +7127,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NOT AN INLINE ALIAS ───────────────────────────────────────────────────────── + ── NOT AN INLINE ALIAS ─────────────────────────────────── /code/proj/Main.roc ─ The inline type after this `as` is not a type alias: @@ -7147,7 +7151,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── QUALIFIED ALIAS NAME ──────────────────────────────────────────────────────── + ── QUALIFIED ALIAS NAME ────────────────────────────────── /code/proj/Main.roc ─ This type alias has a qualified name: @@ -7171,7 +7175,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE ARGUMENT NOT LOWERCASE ───────────────────────────────────────────────── + ── TYPE ARGUMENT NOT LOWERCASE ─────────────────────────── /code/proj/Main.roc ─ This alias type argument is not lowercase: @@ -7199,7 +7203,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `isEmpty` is not what I expect: @@ -7239,7 +7243,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `c` is not what I expect: @@ -7276,7 +7280,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7303,7 +7307,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7329,7 +7333,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `F` alias is self-recursive in an invalid way: @@ -7358,7 +7362,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `job` is weird: @@ -7397,7 +7401,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `job` definition: @@ -7432,7 +7436,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ `Nested` is a nested datatype. Here is one recursive usage of it: @@ -7464,7 +7468,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── NESTED DATATYPE ───────────────────────────────────────────────────────────── + ── NESTED DATATYPE ─────────────────────────────────────── /code/proj/Main.roc ─ `Nested` is a nested datatype. Here is one recursive usage of it: @@ -7507,7 +7511,7 @@ I need all branches in an `if` to have the same type! ), bad_type, number, $suffix), &format!(indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `use` is not what I expect: @@ -7570,7 +7574,7 @@ I need all branches in an `if` to have the same type! ), number, bad_suffix, number, $suffix), &format!(indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -7619,7 +7623,7 @@ I need all branches in an `if` to have the same type! // TODO: link to number suffixes indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -7646,7 +7650,7 @@ I need all branches in an `if` to have the same type! // TODO: link to number suffixes indoc!( r#" - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ This integer literal contains an invalid digit: @@ -7672,7 +7676,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ This number literal is an integer, but it has a float suffix: @@ -7693,7 +7697,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CONFLICTING NUMBER SUFFIX ─────────────────────────────────────────────────── + ── CONFLICTING NUMBER SUFFIX ───────────────────────────── /code/proj/Main.roc ─ This number literal is a float, but it has an integer suffix: @@ -7710,7 +7714,7 @@ I need all branches in an `if` to have the same type! "256u8", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7730,7 +7734,7 @@ I need all branches in an `if` to have the same type! "-1u8", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7750,7 +7754,7 @@ I need all branches in an `if` to have the same type! "65536u16", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7770,7 +7774,7 @@ I need all branches in an `if` to have the same type! "-1u16", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7790,7 +7794,7 @@ I need all branches in an `if` to have the same type! "4_294_967_296u32", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7810,7 +7814,7 @@ I need all branches in an `if` to have the same type! "-1u32", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7830,7 +7834,7 @@ I need all branches in an `if` to have the same type! "18_446_744_073_709_551_616u64", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7850,7 +7854,7 @@ I need all branches in an `if` to have the same type! "-1u64", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7870,7 +7874,7 @@ I need all branches in an `if` to have the same type! "-1u128", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7890,7 +7894,7 @@ I need all branches in an `if` to have the same type! "128i8", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7910,7 +7914,7 @@ I need all branches in an `if` to have the same type! "-129i8", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7930,7 +7934,7 @@ I need all branches in an `if` to have the same type! "32768i16", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7950,7 +7954,7 @@ I need all branches in an `if` to have the same type! "-32769i16", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -7970,7 +7974,7 @@ I need all branches in an `if` to have the same type! "2_147_483_648i32", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -7990,7 +7994,7 @@ I need all branches in an `if` to have the same type! "-2_147_483_649i32", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -8010,7 +8014,7 @@ I need all branches in an `if` to have the same type! "9_223_372_036_854_775_808i64", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -8030,7 +8034,7 @@ I need all branches in an `if` to have the same type! "-9_223_372_036_854_775_809i64", indoc!( r#" - ── NUMBER UNDERFLOWS SUFFIX ──────────────────────────────────────────────────── + ── NUMBER UNDERFLOWS SUFFIX ────────────────────────────── /code/proj/Main.roc ─ This integer literal underflows the type indicated by its suffix: @@ -8050,7 +8054,7 @@ I need all branches in an `if` to have the same type! "170_141_183_460_469_231_731_687_303_715_884_105_728i128", indoc!( r#" - ── NUMBER OVERFLOWS SUFFIX ───────────────────────────────────────────────────── + ── NUMBER OVERFLOWS SUFFIX ─────────────────────────────── /code/proj/Main.roc ─ This integer literal overflows the type indicated by its suffix: @@ -8076,7 +8080,7 @@ I need all branches in an `if` to have the same type! // be used as ... because of its literal value" indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8106,7 +8110,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8137,7 +8141,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd argument to `get` is not what I expect: @@ -8168,7 +8172,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st pattern in this `when` is causing a mismatch: @@ -8200,7 +8204,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `R` alias is self-recursive in an invalid way: @@ -8227,7 +8231,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `R` alias is self-recursive in an invalid way: @@ -8255,7 +8259,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── CYCLIC ALIAS ──────────────────────────────────────────────────────────────── + ── CYCLIC ALIAS ────────────────────────────────────────── /code/proj/Main.roc ─ The `Foo` alias is recursive in an invalid way: @@ -8306,7 +8310,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: @@ -8331,7 +8335,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: @@ -8345,7 +8349,7 @@ I need all branches in an `if` to have the same type! Note: It looks like there are no opaque types declared in this scope yet! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Age` is not used anywhere in your code. @@ -8372,7 +8376,7 @@ I need all branches in an `if` to have the same type! // Apply(Error(OtherModule), [ $Age, 21 ]) indoc!( r#" - ── OPAQUE TYPE NOT APPLIED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT APPLIED ─────────────────────────────── /code/proj/Main.roc ─ This opaque type is not applied to an argument: @@ -8381,7 +8385,7 @@ I need all branches in an `if` to have the same type! Note: Opaque types always wrap exactly one argument! - ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ I am trying to parse a qualified name here: @@ -8412,7 +8416,7 @@ I need all branches in an `if` to have the same type! // raise that declaration to the outer scope. indoc!( r#" - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Age` is not used anywhere in your code. @@ -8422,7 +8426,7 @@ I need all branches in an `if` to have the same type! If you didn't intend on using `Age` then remove it so future readers of your code don't wonder why it is there. - ── OPAQUE TYPE NOT DEFINED ───────────────────────────────────────────────────── + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ The opaque type Age referenced here is not defined: @@ -8447,7 +8451,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── MODULE NOT IMPORTED ───────────────────────────────────────────────────────── + ── MODULE NOT IMPORTED ─────────────────────────────────── /code/proj/Main.roc ─ The `Task` module is not imported: @@ -8483,7 +8487,7 @@ I need all branches in an `if` to have the same type! // that the argument be a U8, and linking to the definitin! indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -8516,7 +8520,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression is used in an unexpected way: @@ -8550,7 +8554,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of the `v` definition: @@ -8592,7 +8596,7 @@ I need all branches in an `if` to have the same type! // probably wants to change "Age" to "@Age"! indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This pattern is being used in an unexpected way: @@ -8631,7 +8635,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 2nd pattern in this `when` does not match the previous ones: @@ -8666,7 +8670,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -8700,7 +8704,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -8737,7 +8741,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `y` is not what I expect: @@ -8774,7 +8778,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ This `when` does not cover all the possibilities: @@ -8802,7 +8806,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── INVALID_EXTENSION_TYPE ────────────────────────────────────────────────────── + ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ This record extension type is invalid: @@ -8827,7 +8831,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── INVALID_EXTENSION_TYPE ────────────────────────────────────────────────────── + ── INVALID_EXTENSION_TYPE ──────────────────────────────── /code/proj/Main.roc ─ This tag union extension type is invalid: @@ -8858,7 +8862,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `UnknownType` value @@ -8872,12 +8876,12 @@ I need all branches in an `if` to have the same type! Box Ok - ── UNRECOGNIZED NAME ─────────────────────────────────────────────────────────── - + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ + I cannot find a `UnknownType` value 3β”‚ insertHelper : UnknownType, Type -> Type - ^^^^ + ^^^^^^^^^^^ Did you mean one of these? @@ -8903,7 +8907,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8921,6 +8925,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_demands_not_indented_with_first() { new_report_problem_as( + "ability_demands_not_indented_with_first", indoc!( r#" Eq has @@ -8932,7 +8937,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ─── tmp/ability_demands_not_indented_with_first/Test.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8949,6 +8954,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_demand_value_has_args() { new_report_problem_as( + "ability_demand_value_has_args", indoc!( r#" Eq has @@ -8959,7 +8965,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ───────────── tmp/ability_demand_value_has_args/Test.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -8986,7 +8992,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNFINISHED ABILITY ────────────────────────────────────────────────────────── + ── UNFINISHED ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ I was partway through parsing an ability definition, but I got stuck here: @@ -9013,7 +9019,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: @@ -9039,7 +9045,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: @@ -9065,7 +9071,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has 2 unbound type variables. @@ -9093,7 +9099,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: @@ -9119,7 +9125,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + ── UNBOUND TYPE VARIABLE ───────────────────────────────── /code/proj/Main.roc ─ The definition of `I` has an unbound type variable: @@ -9136,32 +9142,33 @@ I need all branches in an `if` to have the same type! #[test] fn ability_bad_type_parameter() { new_report_problem_as( + "ability_bad_type_parameter", indoc!( r#" + app "test" provides [] to "./platform" + Hash a b c has hash : a -> U64 | a has Hash - - 1 "# ), indoc!( r#" - ── ABILITY HAS TYPE VARIABLES ────────────────────────────────────────────────── + ── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─ The definition of the `Hash` ability includes type variables: - 4β”‚ Hash a b c has - ^^^^^ + 3β”‚ Hash a b c has + ^^^^^ Abilities cannot depend on type variables, but their member values can! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Hash` is not used anywhere in your code. - 4β”‚ Hash a b c has - ^^^^ + 3β”‚ Hash a b c has + ^^^^ If you didn't intend on using `Hash` then remove it so future readers of your code don't wonder why it is there. @@ -9173,6 +9180,7 @@ I need all branches in an `if` to have the same type! #[test] fn alias_in_has_clause() { new_report_problem_as( + "alias_in_has_clause", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9182,7 +9190,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── HAS CLAUSE IS NOT AN ABILITY ──────────────────────────────────────────────── + ── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─ The type referenced in this "has" clause is not an ability: @@ -9196,6 +9204,7 @@ I need all branches in an `if` to have the same type! #[test] fn shadowed_type_variable_in_has_clause() { new_report_problem_as( + "shadowed_type_variable_in_has_clause", indoc!( r#" app "test" provides [ ab1 ] to "./platform" @@ -9205,7 +9214,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ The `a` name is first defined here: @@ -9227,24 +9236,26 @@ I need all branches in an `if` to have the same type! #[test] fn alias_using_ability() { new_report_problem_as( + "alias_using_ability", indoc!( r#" + app "test" provides [ a ] to "./platform" + Ability has ab : a -> {} | a has Ability Alias : Ability a : Alias - a "# ), indoc!( r#" - ── ALIAS USES ABILITY ────────────────────────────────────────────────────────── + ── ALIAS USES ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ The definition of the `Alias` aliases references the ability `Ability`: - 6β”‚ Alias : Ability - ^^^^^ + 5β”‚ Alias : Ability + ^^^^^ Abilities are not types, but you can add an ability constraint to a type variable `a` by writing @@ -9253,12 +9264,12 @@ I need all branches in an `if` to have the same type! at the end of the type. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `ab` is not used anywhere in your code. - 4β”‚ Ability has ab : a -> {} | a has Ability - ^^ + 3β”‚ Ability has ab : a -> {} | a has Ability + ^^ If you didn't intend on using `ab` then remove it so future readers of your code don't wonder why it is there. @@ -9270,41 +9281,32 @@ I need all branches in an `if` to have the same type! #[test] fn ability_shadows_ability() { new_report_problem_as( + "ability_shadows_ability", indoc!( r#" - Ability has ab : a -> Num.U64 | a has Ability + app "test" provides [ ab ] to "./platform" - Ability has ab1 : a -> Num.U64 | a has Ability + Ability has ab : a -> U64 | a has Ability - 1 + Ability has ab1 : a -> U64 | a has Ability "# ), indoc!( r#" - ── DUPLICATE NAME ────────────────────────────────────────────────────────────── - + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + The `Ability` name is first defined here: - - 4β”‚ Ability has ab : a -> Num.U64 | a has Ability - ^^^^^^^ - + + 3β”‚ Ability has ab : a -> U64 | a has Ability + ^^^^^^^ + But then it's defined a second time here: - - 6β”‚ Ability has ab1 : a -> Num.U64 | a has Ability - ^^^^^^^ - + + 5β”‚ Ability has ab1 : a -> U64 | a has Ability + ^^^^^^^ + Since these abilities have the same name, it's easy to use the wrong one on accident. Give one of them a new name. - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `ab` is not used anywhere in your code. - - 4β”‚ Ability has ab : a -> Num.U64 | a has Ability - ^^ - - If you didn't intend on using `ab` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9313,6 +9315,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_does_not_bind_ability() { new_report_problem_as( + "ability_member_does_not_bind_ability", indoc!( r#" app "test" provides [ ] to "./platform" @@ -9322,7 +9325,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY MEMBER MISSING HAS CLAUSE ─────────────────────────────────────────── + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ The definition of the ability member `ab` does not include a `has` clause binding a type variable to the ability `Ability`: @@ -9337,7 +9340,7 @@ I need all branches in an `if` to have the same type! Otherwise, the function does not need to be part of the ability! - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `Ability` is not used anywhere in your code. @@ -9347,7 +9350,7 @@ I need all branches in an `if` to have the same type! If you didn't intend on using `Ability` then remove it so future readers of your code don't wonder why it is there. - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `ab` is not used anywhere in your code. @@ -9364,6 +9367,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_binds_extra_ability() { new_report_problem_as( + "ability_member_binds_extra_ability", indoc!( r#" app "test" provides [ eq ] to "./platform" @@ -9374,7 +9378,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ──────────────────────────────────── + ── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ────────────── /code/proj/Main.roc ─ The definition of the ability member `hash` includes a has clause binding an ability it is not a part of: @@ -9387,7 +9391,7 @@ I need all branches in an `if` to have the same type! Hint: Did you mean to bind the `Hash` ability instead? - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `hash` is not used anywhere in your code. @@ -9404,6 +9408,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_binds_parent_twice() { new_report_problem_as( + "ability_member_binds_parent_twice", indoc!( r#" app "test" provides [ ] to "./platform" @@ -9413,7 +9418,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ───────────────────────────────────── + ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────── /code/proj/Main.roc ─ The definition of the ability member `eq` includes multiple variables bound to the `Eq`` ability:` @@ -9427,7 +9432,7 @@ I need all branches in an `if` to have the same type! Hint: Did you mean to only bind `a` to `Eq`? - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ `eq` is not used anywhere in your code. @@ -9442,28 +9447,53 @@ I need all branches in an `if` to have the same type! } #[test] - fn has_clause_outside_of_ability() { + fn has_clause_not_on_toplevel() { new_report_problem_as( + "has_clause_outside_of_ability", indoc!( r#" - app "test" provides [ hash, f ] to "./platform" + app "test" provides [ f ] to "./platform" - Hash has hash : a -> Num.U64 | a has Hash + Hash has hash : (a | a has Hash) -> Num.U64 f : a -> Num.U64 | a has Hash "# ), indoc!( r#" - ── ILLEGAL HAS CLAUSE ────────────────────────────────────────────────────────── + ── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─ A `has` clause is not allowed here: - 5β”‚ f : a -> Num.U64 | a has Hash - ^^^^^^^^^^ + 3β”‚ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^^^^^^^ - `has` clauses can only be specified on the top-level type annotation of - an ability member. + `has` clauses can only be specified on the top-level type annotations. + + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ + + The definition of the ability member `hash` does not include a `has` + clause binding a type variable to the ability `Hash`: + + 3β”‚ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^ + + Ability members must include a `has` clause binding a type variable to + an ability, like + + a has Hash + + Otherwise, the function does not need to be part of the ability! + + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + + `hash` is not used anywhere in your code. + + 3β”‚ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^ + + If you didn't intend on using `hash` then remove it so future readers of + your code don't wonder why it is there. "# ), ) @@ -9472,6 +9502,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_with_non_implementing_type() { new_report_problem_as( + "ability_specialization_with_non_implementing_type", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9483,7 +9514,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: @@ -9510,6 +9541,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_does_not_match_type() { new_report_problem_as( + "ability_specialization_does_not_match_type", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9523,7 +9555,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: @@ -9545,6 +9577,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_is_incomplete() { new_report_problem_as( + "ability_specialization_is_incomplete", indoc!( r#" app "test" provides [ eq, le ] to "./platform" @@ -9560,7 +9593,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ─────────────────────────────────────────── + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ The type `Id` does not fully implement the ability `Eq`. The following specializations are missing: @@ -9584,6 +9617,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_overly_generalized() { new_report_problem_as( + "ability_specialization_overly_generalized", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9596,7 +9630,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This specialization of `hash` is overly general: @@ -9625,6 +9659,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_conflicting_specialization_types() { new_report_problem_as( + "ability_specialization_conflicting_specialization_types", indoc!( r#" app "test" provides [ eq ] to "./platform" @@ -9640,7 +9675,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `eq`: @@ -9667,6 +9702,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_checked_against_annotation() { new_report_problem_as( + "ability_specialization_checked_against_annotation", indoc!( r#" app "test" provides [ hash ] to "./platform" @@ -9682,7 +9718,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with the body of this definition: @@ -9698,7 +9734,7 @@ I need all branches in an `if` to have the same type! U32 - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ Something is off with this specialization of `hash`: @@ -9720,6 +9756,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_specialization_called_with_non_specializing() { new_report_problem_as( + "ability_specialization_called_with_non_specializing", indoc!( r#" app "test" provides [ noGoodVeryBadTerrible ] to "./platform" @@ -9742,7 +9779,7 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ The 1st argument to `hash` is not what I expect: @@ -9757,7 +9794,7 @@ I need all branches in an `if` to have the same type! a | a has Hash - ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ This expression has a type that does not implement the abilities it's expected to: @@ -9784,4 +9821,137 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn ability_not_on_toplevel() { + new_report_problem_as( + "ability_not_on_toplevel", + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + Hash has + hash : a -> U64 | a has Hash + + 123 + "# + ), + indoc!( + r#" + ── ABILITY NOT ON TOP-LEVEL ────────────────────────────── /code/proj/Main.roc ─ + + This ability definition is not on the top-level of a module: + + 4β”‚> Hash has + 5β”‚> hash : a -> U64 | a has Hash + + Abilities can only be defined on the top-level of a Roc module. + "# + ), + ) + } + + #[test] + fn expression_generalization_to_ability_is_an_error() { + new_report_problem_as( + "expression_generalization_to_ability_is_an_error", + indoc!( + r#" + app "test" provides [ hash, hashable ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + hash = \$Id n -> n + + hashable : a | a has Hash + hashable = $Id 15 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `hashable` definition: + + 9β”‚ hashable : a | a has Hash + 10β”‚ hashable = $Id 15 + ^^^^^^ + + This Id opaque wrapping has the type: + + Id + + But the type annotation on `hashable` says it should be: + + a | a has Hash + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any value implementing the `Hash` ability. But in + the body I see that it will only produce a `Id` value of a single + specific type. Maybe change the type annotation to be more specific? + Maybe change the code to be more general? + "# + ), + ) + } + + #[test] + fn ability_value_annotations_are_an_error() { + new_report_problem_as( + "ability_value_annotations_are_an_error", + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + mulHashes : Hash, Hash -> U64 + mulHashes = \x, y -> hash x * hash y + + Id := U64 + hash = \$Id n -> n + + Three := {} + hash = \$Three _ -> 3 + + result = mulHashes ($Id 100) ($Three {}) + "# + ), + indoc!( + r#" + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ + + You are attempting to use the ability `Hash` as a type directly: + + 6β”‚ mulHashes : Hash, Hash -> U64 + ^^^^ + + Abilities can only be used in type annotations to constrain type + variables. + + Hint: Perhaps you meant to include a `has` annotation, like + + a has Hash + + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ + + You are attempting to use the ability `Hash` as a type directly: + + 6β”‚ mulHashes : Hash, Hash -> U64 + ^^^^ + + Abilities can only be used in type annotations to constrain type + variables. + + Hint: Perhaps you meant to include a `has` annotation, like + + b has Hash + "# + ), + ) + } } diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index d71698cd2f..1a2efaceb1 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -8,5 +8,6 @@ description = "Utility functions used all over the code base." [dependencies] pretty_assertions = "1.0.0" +remove_dir_all = "0.7.0" [dev-dependencies] diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index bca9060a5d..f5e00b7817 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -16,3 +16,33 @@ macro_rules! assert_multiline_str_eq { $crate::_pretty_assert_eq!($crate::DebugAsDisplay($a), $crate::DebugAsDisplay($b)) }; } + +/** + * Creates a temporary empty directory that gets deleted when this goes out of scope. + */ +pub struct TmpDir { + path: std::path::PathBuf, +} + +impl TmpDir { + pub fn new(dir: &str) -> TmpDir { + let path = std::path::Path::new(dir); + // ensure_empty_dir will fail if the dir doesn't already exist + std::fs::create_dir_all(path).unwrap(); + remove_dir_all::ensure_empty_dir(&path).unwrap(); + + let mut pathbuf = std::path::PathBuf::new(); + pathbuf.push(path); + TmpDir { path: pathbuf } + } + + pub fn path(&self) -> &std::path::Path { + self.path.as_path() + } +} + +impl Drop for TmpDir { + fn drop(&mut self) { + remove_dir_all::remove_dir_all(&self.path).unwrap(); + } +} diff --git a/version.txt b/version.txt index 27ffb11fc3..3fed276742 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ - \ No newline at end of file +(built from source) \ No newline at end of file