mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-10 10:02:38 +03:00
Merge branch 'trunk' of github.com:rtfeldman/roc into nix_flake_M1
This commit is contained in:
commit
6fbacd09be
59
Cargo.lock
generated
59
Cargo.lock
generated
@ -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"
|
||||
|
@ -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/)
|
||||
|
||||
[<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
|
||||
|
||||
|
@ -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>),
|
||||
|
@ -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,
|
||||
))
|
||||
}
|
||||
|
247
cli/src/lib.rs
247
cli/src/lib.rs
@ -1,7 +1,7 @@
|
||||
#[macro_use]
|
||||
extern crate const_format;
|
||||
|
||||
use build::{BuildOutcome, BuiltFile};
|
||||
use build::BuiltFile;
|
||||
use bumpalo::Bump;
|
||||
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||
use roc_build::link::LinkType;
|
||||
@ -24,6 +24,7 @@ mod format;
|
||||
pub use format::format;
|
||||
|
||||
pub const CMD_BUILD: &str = "build";
|
||||
pub const CMD_RUN: &str = "run";
|
||||
pub const CMD_REPL: &str = "repl";
|
||||
pub const CMD_EDIT: &str = "edit";
|
||||
pub const CMD_DOCS: &str = "docs";
|
||||
@ -49,10 +50,12 @@ pub const ROC_DIR: &str = "ROC_DIR";
|
||||
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
|
||||
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
|
||||
|
||||
const VERSION: &str = include_str!("../../version.txt");
|
||||
|
||||
pub fn build_app<'a>() -> App<'a> {
|
||||
let app = App::new("roc")
|
||||
.version(concatcp!(include_str!("../../version.txt"), "\n"))
|
||||
.about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!")
|
||||
.version(concatcp!(VERSION, "\n"))
|
||||
.about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!")
|
||||
.subcommand(App::new(CMD_BUILD)
|
||||
.about("Build a binary from the given .roc file, but don't run it")
|
||||
.arg(
|
||||
@ -140,8 +143,16 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
.subcommand(App::new(CMD_REPL)
|
||||
.about("Launch the interactive Read Eval Print Loop (REPL)")
|
||||
)
|
||||
.subcommand(App::new(CMD_RUN)
|
||||
.about("Run a .roc file even if it has build errors")
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.about("The .roc file of an app to run")
|
||||
.required(true),
|
||||
)
|
||||
)
|
||||
.subcommand(App::new(CMD_FORMAT)
|
||||
.about("Format Roc code")
|
||||
.about("Format a .roc file using standard Roc formatting")
|
||||
.arg(
|
||||
Arg::new(DIRECTORY_OR_FILES)
|
||||
.index(1)
|
||||
@ -155,10 +166,9 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
)
|
||||
)
|
||||
.subcommand(App::new(CMD_VERSION)
|
||||
.about("Print version information")
|
||||
)
|
||||
.about(concatcp!("Print the Roc compiler’s version, which is currently ", VERSION)))
|
||||
.subcommand(App::new(CMD_CHECK)
|
||||
.about("When developing, it's recommended to run `check` before `build`. It may provide a useful error message in cases where `build` panics")
|
||||
.about("Check the code for problems, but doesn’t build or run it")
|
||||
.arg(
|
||||
Arg::new(FLAG_TIME)
|
||||
.long(FLAG_TIME)
|
||||
@ -167,7 +177,7 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
)
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.about("The .roc file of an app to run")
|
||||
.about("The .roc file of an app to check")
|
||||
.required(true),
|
||||
)
|
||||
)
|
||||
@ -193,19 +203,19 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
.arg(
|
||||
Arg::new(FLAG_OPT_SIZE)
|
||||
.long(FLAG_OPT_SIZE)
|
||||
.about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)")
|
||||
.about("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_DEV)
|
||||
.long(FLAG_DEV)
|
||||
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
|
||||
.about("Make compilation finish as soon as possible, at the expense of runtime performance.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
.about("Store LLVM debug information in the generated program")
|
||||
.about("Store LLVM debug information in the generated program.")
|
||||
.requires(ROC_FILE)
|
||||
.required(false),
|
||||
)
|
||||
@ -231,18 +241,10 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
.arg(
|
||||
Arg::new(FLAG_PRECOMPILED)
|
||||
.long(FLAG_PRECOMPILED)
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)")
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)")
|
||||
.possible_values(["true", "false"])
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_TARGET)
|
||||
.long(FLAG_TARGET)
|
||||
.about("Choose a different target")
|
||||
.default_value(Target::default().as_str())
|
||||
.possible_values(Target::OPTIONS)
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.about("The .roc file of an app to build and run")
|
||||
@ -280,6 +282,7 @@ pub fn docs(files: Vec<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
|
||||
|
@ -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)) => {
|
||||
|
@ -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."#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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"))]
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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)),
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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};
|
||||
|
@ -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),
|
||||
|
@ -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()));
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
244
compiler/can/src/reference_matrix.rs
Normal file
244
compiler/can/src/reference_matrix.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
}
|
||||
|
@ -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,
|
||||
// )
|
||||
}
|
||||
}
|
@ -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",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
133
compiler/collections/src/vec_map.rs
Normal file
133
compiler/collections/src/vec_map.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
95
compiler/collections/src/vec_set.rs
Normal file
95
compiler/collections/src/vec_set.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
1
compiler/load_internal/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/tmp
|
@ -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" }
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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,
|
||||
|
@ -138,6 +138,7 @@ pub enum Problem {
|
||||
AbilityNotOnToplevel {
|
||||
region: Region,
|
||||
},
|
||||
AbilityUsedAsType(Lowercase, Symbol, Region),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -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>),
|
||||
|
@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 });
|
||||
|
@ -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 (\_ -> {})
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
1
reporting/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/tmp
|
@ -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 {
|
||||
|
@ -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?"))
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
@ -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]
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
<to be filled by CI on nightly release>
|
||||
(built from source)
|
Loading…
Reference in New Issue
Block a user