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).
-
+[](https://www.noredink.com/)
+
+[](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