Merge branch 'trunk' of github.com:rtfeldman/roc into nix_flake_M1

This commit is contained in:
Anton-4 2022-04-23 11:08:20 +02:00
commit 6fbacd09be
No known key found for this signature in database
GPG Key ID: C954D6E0F9C0ABFD
60 changed files with 2503 additions and 1295 deletions

59
Cargo.lock generated
View File

@ -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"

View File

@ -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).
<img src="https://www.noredink.com/assets/logo-red-black-f6989d7567cf90b349409137595e99c52d036d755b4403d25528e0fd83a3b084.svg" height="60" alt="NoRedInk logo"/>
[<img src="https://www.noredink.com/assets/logo-red-black-f6989d7567cf90b349409137595e99c52d036d755b4403d25528e0fd83a3b084.svg" height="60" alt="NoRedInk logo"/>](https://www.noredink.com/)
&nbsp;&nbsp;&nbsp;&nbsp;
[<img src="https://www.rwx.com/build/_assets/rwx_banner_transparent_cropped-RYV7W2KL.svg" height="60" alt="rwx logo"/>](https://www.rwx.com)
## Applications and Platforms

View File

@ -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<ErrorType>),
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),

View File

@ -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<usize, LoadingProblem> {
) -> 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,
))
}

View File

@ -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 compilers 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 doesnt 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<PathBuf>) {
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<i32> {
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<i32> {
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::<Vec<_>>();
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<i32> {
}
#[cfg(target_family = "unix")]
fn roc_run(cmd: &mut Command) -> io::Result<i32> {
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<i32> {
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::<Vec<_>>();
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

View File

@ -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)) => {

View File

@ -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 <ignored for test> 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 <ignored for test> 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 <ignored for test> 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 <ignored for test> 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 <ignored for test> ms."#
),
);
}

View File

@ -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<ModuleId, (PathBuf, Box<str>)>,
@ -57,7 +74,7 @@ fn report_problems_help(
can_problems: &mut MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
type_problems: &mut MutMap<ModuleId, Vec<roc_solve::solve::TypeError>>,
mono_problems: &mut MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
) -> 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"))]

View File

@ -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"

View File

@ -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.

View File

@ -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<Loc<Variable>>,
pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Loc<Variable>>,
pub named: VecSet<NamedVariable>,
pub able: VecSet<AbleVariable>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
pub host_exposed_aliases: VecMap<Symbol, Variable>,
}
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<Item = NamedOrAbleVariable> {
(self.named.iter().map(NamedOrAbleVariable::Named))
.chain(self.able.iter().map(NamedOrAbleVariable::Able))
}
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<NamedOrAbleVariable> {
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<Variable> {
@ -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)),
});

View File

@ -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<Type, Variable>,
@ -643,13 +643,13 @@ pub enum Constraint {
),
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[derive(Debug, Clone, Copy, Default)]
pub struct DefTypes {
pub types: Slice<Type>,
pub loc_symbols: Slice<(Symbol, Region)>,
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct LetConstraint {
pub rigid_vars: Slice<Variable>,
pub flex_vars: Slice<Variable>,
@ -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<Type>,
pub tag_name: TagName,

View File

@ -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<Pattern>,
pub loc_expr: Loc<Expr>,
@ -39,7 +41,7 @@ pub struct Def {
pub annotation: Option<Annotation>,
}
#[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
@ -318,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
@ -337,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<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
@ -437,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,
@ -448,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;
@ -473,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<_>) =
@ -568,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 {
@ -605,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
@ -643,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<Symbol, Def>,
refs_by_symbol: &MutMap<Symbol, (Region, References)>,
) -> 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<u32> {
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<Symbol> {
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<Item = u32> + '_ {
self.references
.references_for(id as usize)
.map(|x| x as u32)
}
#[inline(always)]
fn successors_without_self(&self, id: u32) -> impl Iterator<Item = u32> + '_ {
self.successors(id).filter(move |x| *x != id)
}
}
#[inline(always)]
pub fn sort_can_defs(
env: &mut Env<'_>,
defs: CanDefs,
mut output: Output,
) -> (Result<Vec<Declaration>, 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,
@ -659,153 +810,18 @@ pub fn sort_can_defs(
output.aliases.insert(symbol, alias);
}
let mut defined_symbols: Vec<Symbol> = 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<Symbol> {
// 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<Symbol> {
// 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<Symbol> {
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,
);
@ -813,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();
@ -828,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:
//
@ -840,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,
};
@ -854,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,
};
@ -913,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) => {
@ -937,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,
);
@ -959,22 +979,14 @@ pub fn sort_can_defs(
}
fn group_to_declaration(
group: &[Symbol],
def_ids: &DefOrdering,
group: &[u32],
closures: &MutMap<Symbol, References>,
successors: &mut dyn FnMut(&Symbol) -> Vec<Symbol>,
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
declarations: &mut Vec<Declaration>,
) {
use Declaration::*;
// We want only successors in the current group, otherwise definitions get duplicated
let filtered_successors = |symbol: &Symbol| -> Vec<Symbol> {
let mut result = successors(symbol);
result.retain(|key| group.contains(key));
result
};
// Patterns like
//
// { x, y } = someDef
@ -984,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<Region> = 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));
@ -1017,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
@ -1148,6 +1168,7 @@ fn canonicalize_pending_value_def<'a>(
var_store: &mut VarStore,
refs_by_symbol: &mut MutMap<Symbol, (Region, References)>,
aliases: &mut ImMap<Symbol, Alias>,
abilities_in_scope: &[Symbol],
) -> Output {
use PendingValueDef::*;
@ -1159,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);
@ -1280,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);
@ -1365,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);
@ -1505,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 {
@ -1600,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));
@ -1649,7 +1679,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap<Symbol, References>) ->
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);
}
@ -1665,7 +1695,7 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap<Symbol, References>) ->
// 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);
}
}
@ -1827,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<PendingValueDef<'a>> {
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,
))
}
@ -1880,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
@ -1935,7 +1973,8 @@ fn correct_mutual_recursive_type_alias<'a>(
// TODO investigate should this be in a loop?
let defined_symbols: Vec<Symbol> = 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 {

View File

@ -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;

View File

@ -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};

View File

@ -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<T> {
NoExpectation(T),
FromAnnotation(Loc<Pattern>, usize, AnnotationSource, T),
@ -10,7 +10,7 @@ pub enum Expected<T> {
}
/// Like Expected, but for Patterns.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone)]
pub enum PExpected<T> {
NoExpectation(T),
ForReason(PReason, T, Region),

View File

@ -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<Symbol>,
pub introduced_variables: IntroducedVariables,
pub aliases: SendMap<Symbol, Alias>,
pub aliases: VecMap<Symbol, Alias>,
pub non_closures: VecSet<Symbol>,
}
@ -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<Loc<Pattern>>,
pub value: Loc<Expr>,
@ -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<Symbol> = new_output
.references
.value_lookups
.iter()
.copied()
.collect();
let mut captured_symbols: MutSet<Symbol> =
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<Symbol, References>,
) -> Vec<Symbol> {
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()));

View File

@ -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;

View File

@ -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());

View File

@ -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<Expr>),
@ -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<Pattern>) {
) -> Loc<Pattern> {
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<Pattern>) {
) -> Loc<Pattern> {
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

View File

@ -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<Box<str>>,
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<Symbol>,
pub type_lookups: VecSet<Symbol>,
pub value_lookups: VecSet<Symbol>,
/// Aliases or opaque types referenced
pub referenced_type_defs: VecSet<Symbol>,
pub calls: VecSet<Symbol>,
symbols: Vec<Symbol>,
bitflags: Vec<ReferencesBitflags>,
}
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<Item = &'a Symbol> {
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<Item = &Symbol> {
self.retain(|b| b.0 & ReferencesBitflags::VALUE_LOOKUP.0 > 0)
}
pub fn type_lookups(&self) -> impl Iterator<Item = &Symbol> {
self.retain(|b| b.0 & ReferencesBitflags::TYPE_LOOKUP.0 > 0)
}
pub fn bound_symbols(&self) -> impl Iterator<Item = &Symbol> {
self.retain(|b| b.0 & ReferencesBitflags::BOUND.0 > 0)
}
pub fn calls(&self) -> impl Iterator<Item = &Symbol> {
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)
}
}

View File

@ -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<Element>;
pub(crate) type BitSlice = bitvec::prelude::BitSlice<Element>;
/// 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<Item = usize> + '_ {
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 <sam@rfc1149.net>,
// 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<i64> = 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::<Vec<u32>>::new();
// the initial group contains all symbols with no predecessors
let mut prev_group: Vec<u32> = 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<u32> = (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::<u32>::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<u32> = (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<Vec<u32>> {
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<Vec<u32>> },
/// 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<Vec<u32>>,
nodes_in_cycle: Vec<u32>,
},
}
#[derive(Clone, Copy)]
enum Preorder {
Empty,
Filled(usize),
Removed,
}
struct Params {
preorders: Vec<Preorder>,
c: usize,
p: Vec<u32>,
s: Vec<u32>,
scc: Vec<Vec<u32>>,
scca: Vec<u32>,
}
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);
}
}

View File

@ -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<Symbol, Alias> {
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(),
}

View File

@ -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,
// )
}
}

View File

@ -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",
);
}

View File

@ -220,99 +220,3 @@ macro_rules! mut_map {
}
};
}
#[derive(Clone, Debug, PartialEq)]
pub struct VecSet<T> {
elements: Vec<T>,
}
impl<T> Default for VecSet<T> {
fn default() -> Self {
Self {
elements: Vec::new(),
}
}
}
impl<T: PartialEq> VecSet<T> {
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<Item = &T> {
self.elements.iter()
}
}
impl<A: Ord> Extend<A> for VecSet<A> {
fn extend<T: IntoIterator<Item = A>>(&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<T> IntoIterator for VecSet<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.elements.into_iter()
}
}

View File

@ -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;

View File

@ -0,0 +1,133 @@
#[derive(Debug, Clone)]
pub struct VecMap<K, V> {
keys: Vec<K>,
values: Vec<V>,
}
impl<K, V> Default for VecMap<K, V> {
fn default() -> Self {
Self {
keys: Vec::new(),
values: Vec::new(),
}
}
}
impl<K: PartialEq, V> VecMap<K, V> {
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<V> {
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<Item = (&K, &V)> {
self.keys.iter().zip(self.values.iter())
}
pub fn values(&self) -> impl Iterator<Item = &V> {
self.values.iter()
}
}
impl<K: Ord, V> Extend<(K, V)> for VecMap<K, V> {
#[inline(always)]
fn extend<T: IntoIterator<Item = (K, V)>>(&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<K, V> IntoIterator for VecMap<K, V> {
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
keys: self.keys.into_iter(),
values: self.values.into_iter(),
}
}
}
pub struct IntoIter<K, V> {
keys: std::vec::IntoIter<K>,
values: std::vec::IntoIter<V>,
}
impl<K, V> Iterator for IntoIter<K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
match (self.keys.next(), self.values.next()) {
(Some(k), Some(v)) => Some((k, v)),
_ => None,
}
}
}

View File

@ -0,0 +1,95 @@
#[derive(Clone, Debug, PartialEq)]
pub struct VecSet<T> {
elements: Vec<T>,
}
impl<T> Default for VecSet<T> {
fn default() -> Self {
Self {
elements: Vec::new(),
}
}
}
impl<T: PartialEq> VecSet<T> {
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<Item = &T> {
self.elements.iter()
}
}
impl<A: Ord> Extend<A> for VecSet<A> {
fn extend<T: IntoIterator<Item = A>>(&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<T> IntoIterator for VecSet<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.elements.into_iter()
}
}

View File

@ -1684,18 +1684,18 @@ fn instantiate_rigids(
let mut new_rigid_variables: Vec<Variable> = Vec::new();
let mut rigid_substitution: MutMap<Variable, Variable> = 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());
}
}
}

View File

@ -202,8 +202,19 @@ impl From<&str> for IdentStr {
}
impl From<String> 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 }
}
}
}

1
compiler/load_internal/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/tmp

View File

@ -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" }

View File

@ -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,

View File

@ -89,11 +89,11 @@ mod test_load {
buf
}
fn multiple_modules(files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
fn multiple_modules(subdir: &str, files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
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<Result<LoadedModule, roc_load_internal::file::LoadingProblem<'a>>, 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

View File

@ -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<String> for Lowercase {
fn from(string: String) -> Self {
Self(string.into())

View File

@ -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],
@ -4750,6 +4750,7 @@ fn get_specialization<'a>(
symbol: Symbol,
) -> Option<Symbol> {
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 +4760,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,

View File

@ -138,6 +138,7 @@ pub enum Problem {
AbilityNotOnToplevel {
region: Region,
},
AbilityUsedAsType(Lowercase, Symbol, Region),
}
#[derive(Clone, Debug, PartialEq)]

View File

@ -79,7 +79,7 @@ pub struct IncompleteAbilityImplementation {
pub missing_members: Vec<Loc<Symbol>>,
}
#[derive(PartialEq, Debug, Clone)]
#[derive(Debug, Clone)]
pub enum TypeError {
BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),

View File

@ -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",
)
}
}

View File

@ -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
)
}

View File

@ -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]

View File

@ -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();

View File

@ -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);

View File

@ -248,7 +248,12 @@ fn name_root(
subs: &mut Subs,
taken: &mut MutSet<Lowercase>,
) -> 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);

View File

@ -3660,10 +3660,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
}

View File

@ -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>) -> (Lowercase, u32) {
pub fn name_type_var<I, F: FnMut(&I, &str) -> bool>(
letters_used: u32,
taken: &mut impl Iterator<Item = I>,
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)
}
}

View File

@ -1,5 +1,5 @@
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, *};
@ -274,12 +274,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,8 +310,9 @@ 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,
@ -304,12 +321,14 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: boo
ctx.second,
roc_types::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 +368,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 +388,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 +404,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 +470,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 });

View File

@ -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 (\_ -> {})

View File

@ -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.

View File

@ -47,7 +47,7 @@ pub fn compile_to_mono<'a>(
target_info: TargetInfo,
palette: Palette,
) -> Result<MonomorphizedModule<'a>, Vec<String>> {
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));

1
reporting/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/tmp

View File

@ -46,6 +46,7 @@ const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAU
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>,
@ -643,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();
@ -748,6 +751,34 @@ pub fn can_problem<'b>(
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 {

View File

@ -1767,7 +1767,7 @@ pub enum Problem {
FieldsMissing(Vec<Lowercase>),
TagTypo(TagName, Vec<TagName>),
TagsMissing(Vec<TagName>),
BadRigidVar(Lowercase, ErrorType),
BadRigidVar(Lowercase, ErrorType, Option<Symbol>),
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?"))
};

View File

@ -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: "<span style='font-weight: bold'>",
underline: "<span style='text-decoration: underline'>",
reset: "</span>",
color_reset: "</span>",
};
// define custom allocator struct so we can `impl RocDocAllocator` custom helpers

View File

@ -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(

File diff suppressed because it is too large Load Diff

View File

@ -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]

View File

@ -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();
}
}

View File

@ -1 +1 @@
<to be filled by CI on nightly release>
(built from source)