diff --git a/AUTHORS b/AUTHORS index 58357ab2bd..a52e26ba97 100644 --- a/AUTHORS +++ b/AUTHORS @@ -65,3 +65,6 @@ Mats Sigge <> Drew Lazzeri Tom Dohrmann Elijah Schow +Derek Gustafson +Philippe Vinchon +Pierre-Henri Trivier diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index d66b8e0648..7e20c2a7e1 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -37,7 +37,7 @@ If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.ne ### Editor -The editor is a WIP and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on. +The editor is a :construction:WIP:construction: and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on. `cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below. #### Nvidia GPU @@ -196,20 +196,24 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include" ### LLVM installation on Windows -Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to build LLVM from source -on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMacKenzie) (thank you, Ian!), here's what worked for me: +**Warning** While `cargo build` works on windows, linking roc programs does not yet, see issue #2608. This also means the repl, the editor and many tests will not work on windows. +Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to follow the steps below: -1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work tool; the Build Tools are just the CLI tools, which is all I wanted) -1. In the installation configuration, under "additional components" I had to check both "C++ ATL for latest v142 build tools (x86 & x64)" and also "C++/CLI support for v142 build tools" [note: as of September 2021 this should no longer be necessary - the next time anyone tries this, please try it without this step and make a PR to delete this step if it's no longer needed!] -1. I launched the "x64 Native Tools Command Prompt for Visual Studio 2019" application (note: not the similarly-named "x86" one!) -1. Make sure [Python 2.7](https://www.python.org/) and [CMake 3.17](http://cmake.org/) are installed on your system. -1. I followed most of the steps under LLVM's [building from source instructions](https://github.com/llvm/llvm-project#getting-the-source-code-and-building-llvm) up to the `cmake -G ...` command, which didn't work for me. Instead, at that point I did the following step. -1. I ran `cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm` to generate a NMake makefile. -1. Once that completed, I ran `nmake` to build LLVM. (This took about 2 hours on my laptop.) -1. Finally, I set an environment variable `LLVM_SYS_100_PREFIX` to point to the `build` directory where I ran the `cmake` command. +1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work too; the Build Tools are just the CLI tools, which is all I wanted) +1. Download the custom LLVM 7z archive [here](https://github.com/PLC-lang/llvm-package-windows/releases/tag/v12.0.1). +1. [Download 7-zip](https://www.7-zip.org/) to be able to extract this archive. +1. Extract the 7z file to where you want to permanently keep the folder. +1. In powershell, set the `LLVM_SYS_120_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-changes-to-environment-variables) to make this a permanent environment variable): +``` +[Environment]::SetEnvironmentVariable( + "Path", + [Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\anton\Downloads\LLVM-12.0.1-win64\bin", + "User" +) +``` -Once all that was done, `cargo` ran successfully for Roc! +Once all that was done, `cargo build` ran successfully for Roc! ### Build speed on WSL/WSL2 diff --git a/Cargo.lock b/Cargo.lock index 6409366ecb..b249456252 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1222,7 +1222,7 @@ checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061" dependencies = [ "byteorder", "dynasm", - "memmap2 0.5.0", + "memmap2 0.5.3", ] [[package]] @@ -2095,9 +2095,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" dependencies = [ "libc", ] @@ -3263,6 +3263,16 @@ dependencies = [ "libc", ] +[[package]] +name = "roc_alias_analysis" +version = "0.1.0" +dependencies = [ + "morphic_lib", + "roc_collections", + "roc_module", + "roc_mono", +] + [[package]] name = "roc_ast" version = "0.1.0" @@ -3286,6 +3296,7 @@ dependencies = [ "roc_unify", "snafu", "ven_graph", + "winapi", ] [[package]] @@ -3346,6 +3357,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_types", + "static_assertions", "ven_graph", ] @@ -3415,6 +3427,7 @@ dependencies = [ name = "roc_constrain" version = "0.1.0" dependencies = [ + "arrayvec 0.7.2", "roc_builtins", "roc_can", "roc_collections", @@ -3558,6 +3571,7 @@ dependencies = [ "bumpalo", "inkwell 0.1.0", "morphic_lib", + "roc_alias_analysis", "roc_builtins", "roc_collections", "roc_error_macros", @@ -3594,7 +3608,7 @@ dependencies = [ "bumpalo", "clap 3.0.0-beta.5", "iced-x86", - "memmap2 0.5.0", + "memmap2 0.5.3", "object 0.26.2", "roc_build", "roc_collections", @@ -3822,13 +3836,6 @@ dependencies = [ [[package]] name = "roc_std" version = "0.1.0" -dependencies = [ - "indoc", - "libc", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", -] [[package]] name = "roc_target" @@ -3927,7 +3934,7 @@ checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" [[package]] name = "rustyline" version = "9.1.1" -source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc" +source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -3950,7 +3957,7 @@ dependencies = [ [[package]] name = "rustyline-derive" version = "0.6.0" -source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc" +source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0" dependencies = [ "quote", "syn", diff --git a/Cargo.toml b/Cargo.toml index bbca25568c..f02c317fbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "compiler/solve", "compiler/fmt", "compiler/mono", + "compiler/alias_analysis", "compiler/test_mono", "compiler/load", "compiler/gen_llvm", @@ -38,7 +39,6 @@ members = [ "repl_eval", "repl_test", "repl_wasm", - "roc_std", "test_utils", "utils", "docs", @@ -50,6 +50,8 @@ exclude = [ # The tests will still correctly build them. "cli_utils", "compiler/test_mono_macros", + # `cargo build` would cause roc_std to be built with default features which errors on windows + "roc_std", ] # Needed to be able to run `cargo run -p roc_cli --no-default-features` - # see www/build.sh for more. diff --git a/Earthfile b/Earthfile index 8fa4e4f1b7..d11c9d1b6d 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.57.0-slim-bullseye # make sure to update nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages +FROM rust:1.58.0-slim-bullseye # make sure to update nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages WORKDIR /earthbuild prep-debian: @@ -93,7 +93,7 @@ test-rust: RUN --mount=type=cache,target=$SCCACHE_DIR \ repl_test/test_wasm.sh && sccache --show-stats # run i386 (32-bit linux) cli tests - RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc + RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc RUN --mount=type=cache,target=$SCCACHE_DIR \ cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats diff --git a/TUTORIAL.md b/TUTORIAL.md index 5049d19f82..790916dfa7 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -1623,12 +1623,11 @@ If you like, you can always annotate your functions as accepting open records. H always be the nicest choice. For example, let's say you have a `User` type alias, like so: ```coffee -User : - { - email : Str, - firstName : Str, - lastName : Str, - } +User : { + email : Str, + firstName : Str, + lastName : Str, +} ``` This defines `User` to be a closed record, which in practice is the most common way records named `User` @@ -1661,12 +1660,11 @@ Since open records have a type variable (like `*` in `{ email : Str }*` or `a` i type variable to the `User` type alias: ```coffee -User a : - { - email : Str, - firstName : Str, - lastName : Str, - }a +User a : { + email : Str, + firstName : Str, + lastName : Str, +}a ``` Notice that the `a` type variable appears not only in `User a` but also in `}a` at the end of the diff --git a/ast/Cargo.toml b/ast/Cargo.toml index f0b82d7ca4..86d203cee4 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -21,10 +21,14 @@ roc_target = { path = "../compiler/roc_target" } roc_error_macros = { path = "../error_macros" } arrayvec = "0.7.2" bumpalo = { version = "3.8.0", features = ["collections"] } -libc = "0.2.106" page_size = "0.4.2" snafu = { version = "0.6.10", features = ["backtraces"] } ven_graph = { path = "../vendor/pathfinding" } +libc = "0.2.106" [dev-dependencies] indoc = "1.0.3" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3.9", features = ["memoryapi"]} + diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 6c5af07eaf..bd00bbbda5 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1,7 +1,7 @@ use bumpalo::{collections::Vec as BumpVec, Bump}; use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; +use roc_collections::all::{BumpMap, BumpMapDefault, HumanIndex, SendMap}; use roc_module::{ ident::{Lowercase, TagName}, symbol::Symbol, @@ -163,7 +163,7 @@ pub fn constrain_expr<'a>( let elem_expected = Expected::ForReason( Reason::ElemInList { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, list_elem_type.shallow_clone(), region, @@ -339,7 +339,7 @@ pub fn constrain_expr<'a>( let reason = Reason::FnArg { name: opt_symbol, - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region); @@ -538,7 +538,7 @@ pub fn constrain_expr<'a>( name.clone(), arity, AnnotationSource::TypedIfBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), num_branches, region: ann_source.region(), }, @@ -559,7 +559,7 @@ pub fn constrain_expr<'a>( name, arity, AnnotationSource::TypedIfBranch { - index: Index::zero_based(branches.len()), + index: HumanIndex::zero_based(branches.len()), num_branches, region: ann_source.region(), }, @@ -596,7 +596,7 @@ pub fn constrain_expr<'a>( body, Expected::ForReason( Reason::IfBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), total_branches: branches.len(), }, Type2::Variable(*expr_var), @@ -616,7 +616,7 @@ pub fn constrain_expr<'a>( final_else_expr, Expected::ForReason( Reason::IfBranch { - index: Index::zero_based(branches.len()), + index: HumanIndex::zero_based(branches.len()), total_branches: branches.len() + 1, }, Type2::Variable(*expr_var), @@ -691,7 +691,7 @@ pub fn constrain_expr<'a>( when_branch, PExpected::ForReason( PReason::WhenMatch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, cond_type.shallow_clone(), pattern_region, @@ -700,7 +700,7 @@ pub fn constrain_expr<'a>( name.clone(), *arity, AnnotationSource::TypedWhenBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), region: ann_source.region(), }, typ.shallow_clone(), @@ -733,14 +733,14 @@ pub fn constrain_expr<'a>( when_branch, PExpected::ForReason( PReason::WhenMatch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, cond_type.shallow_clone(), pattern_region, ), Expected::ForReason( Reason::WhenBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, branch_type.shallow_clone(), // TODO: when_branch.value.region, @@ -1065,7 +1065,7 @@ pub fn constrain_expr<'a>( let reason = Reason::LowLevelOpArg { op: *op, - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero()); @@ -1681,7 +1681,7 @@ fn constrain_tag_pattern<'a>( let expected = PExpected::ForReason( PReason::TagArg { tag_name: tag_name.clone(), - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, pattern_type, region, diff --git a/ast/src/mem_pool/pool.rs b/ast/src/mem_pool/pool.rs index 6641627c76..e28dbc33bd 100644 --- a/ast/src/mem_pool/pool.rs +++ b/ast/src/mem_pool/pool.rs @@ -10,12 +10,10 @@ /// /// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied. /// This is important for performance. -use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; use std::any::type_name; use std::ffi::c_void; use std::marker::PhantomData; use std::mem::{align_of, size_of, MaybeUninit}; -use std::ptr::null; pub const NODE_BYTES: usize = 32; @@ -108,14 +106,32 @@ impl Pool { // addresses from the OS which will be lazily translated into // physical memory one 4096-byte page at a time, once we actually // try to read or write in that page's address range. - libc::mmap( - null::() as *mut c_void, - bytes_to_mmap, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, - 0, - 0, - ) + #[cfg(unix)] + { + use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; + + libc::mmap( + std::ptr::null_mut(), + bytes_to_mmap, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + 0, + 0, + ) + } + #[cfg(windows)] + { + use winapi::um::memoryapi::VirtualAlloc; + use winapi::um::winnt::PAGE_READWRITE; + use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE}; + + VirtualAlloc( + std::ptr::null_mut(), + bytes_to_mmap, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE, + ) + } } as *mut [MaybeUninit; NODE_BYTES]; // This is our actual capacity, in nodes. @@ -230,10 +246,24 @@ impl std::ops::IndexMut> for Pool { impl Drop for Pool { fn drop(&mut self) { unsafe { - libc::munmap( - self.nodes as *mut c_void, - NODE_BYTES * self.capacity as usize, - ); + #[cfg(unix)] + { + libc::munmap( + self.nodes as *mut c_void, + NODE_BYTES * self.capacity as usize, + ); + } + #[cfg(windows)] + { + use winapi::um::memoryapi::VirtualFree; + use winapi::um::winnt::MEM_RELEASE; + + VirtualFree( + self.nodes as *mut c_void, + NODE_BYTES * self.capacity as usize, + MEM_RELEASE, + ); + } } } } diff --git a/ast/src/roc_file.rs b/ast/src/roc_file.rs deleted file mode 100644 index 5299921c62..0000000000 --- a/ast/src/roc_file.rs +++ /dev/null @@ -1,133 +0,0 @@ -use bumpalo::collections::Vec; -use bumpalo::Bump; -use roc_fmt::def::fmt_def; -use roc_fmt::module::fmt_module; -use roc_parse::ast::{Def, Module}; -use roc_parse::module::module_defs; -use roc_parse::parser; -use roc_parse::parser::{Parser, SyntaxError}; -use roc_region::all::Located; -use std::ffi::OsStr; -use std::path::Path; -use std::{fs, io}; - -#[derive(Debug)] -pub struct File<'a> { - path: &'a Path, - module_header: Module<'a>, - content: Vec<'a, Located>>, -} - -#[derive(Debug)] -pub enum ReadError<'a> { - Read(std::io::Error), - ParseDefs(SyntaxError<'a>), - ParseHeader(SyntaxError<'a>), - DoesntHaveRocExtension, -} - -impl<'a> File<'a> { - pub fn read(path: &'a Path, arena: &'a Bump) -> Result, ReadError<'a>> { - if path.extension() != Some(OsStr::new("roc")) { - return Err(ReadError::DoesntHaveRocExtension); - } - - let bytes = fs::read(path).map_err(ReadError::Read)?; - - let allocation = arena.alloc(bytes); - - let module_parse_state = parser::State::new(allocation); - let parsed_module = roc_parse::module::parse_header(arena, module_parse_state); - - match parsed_module { - Ok((module, state)) => { - let parsed_defs = module_defs().parse(arena, state); - - match parsed_defs { - Ok((_, defs, _)) => Ok(File { - path, - module_header: module, - content: defs, - }), - Err((_, error, _)) => Err(ReadError::ParseDefs(error)), - } - } - Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))), - } - } - - pub fn fmt(&self) -> String { - let arena = Bump::new(); - let mut formatted_file = String::new(); - - let mut module_header_buf = bumpalo::collections::String::new_in(&arena); - fmt_module(&mut module_header_buf, &self.module_header); - - formatted_file.push_str(module_header_buf.as_str()); - - for def in &self.content { - let mut def_buf = bumpalo::collections::String::new_in(&arena); - - fmt_def(&mut def_buf, &def.value, 0); - - formatted_file.push_str(def_buf.as_str()); - } - - formatted_file - } - - pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> { - let formatted_file = self.fmt(); - - fs::write(write_path, formatted_file) - } - - pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> { - self.fmt_then_write_to( - self.path - .with_file_name(new_name) - .with_extension("roc") - .as_path(), - ) - } - - pub fn fmt_then_write(&self) -> io::Result<()> { - self.fmt_then_write_to(self.path) - } -} - -#[cfg(test)] -mod test_file { - use crate::lang::roc_file; - use bumpalo::Bump; - use std::path::Path; - - #[test] - fn read_and_fmt_simple_roc_module() { - let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc"); - - let arena = Bump::new(); - - let file = roc_file::File::read(simple_module_path, &arena) - .expect("Could not read SimpleUnformatted.roc in test_file test"); - - assert_eq!( - file.fmt(), - indoc!( - r#" - interface Simple - exposes [ - v, x - ] - imports [] - - v : Str - - v = "Value!" - - x : Int - x = 4"# - ) - ); - } -} diff --git a/cli/src/lib.rs b/cli/src/lib.rs index b790552538..e154eeb83e 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -34,7 +34,7 @@ pub const FLAG_DEV: &str = "dev"; pub const FLAG_OPTIMIZE: &str = "optimize"; pub const FLAG_OPT_SIZE: &str = "opt-size"; pub const FLAG_LIB: &str = "lib"; -pub const FLAG_BACKEND: &str = "backend"; +pub const FLAG_TARGET: &str = "target"; pub const FLAG_TIME: &str = "time"; pub const FLAG_LINK: &str = "roc-linker"; pub const FLAG_PRECOMPILED: &str = "precompiled-host"; @@ -42,7 +42,6 @@ pub const FLAG_VALGRIND: &str = "valgrind"; pub const FLAG_CHECK: &str = "check"; pub const ROC_FILE: &str = "ROC_FILE"; pub const ROC_DIR: &str = "ROC_DIR"; -pub const BACKEND: &str = "BACKEND"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; @@ -76,12 +75,11 @@ pub fn build_app<'a>() -> App<'a> { .required(false), ) .arg( - Arg::new(FLAG_BACKEND) - .long(FLAG_BACKEND) - .about("Choose a different backend") - // .requires(BACKEND) - .default_value(Backend::default().as_str()) - .possible_values(Backend::OPTIONS) + 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( @@ -212,12 +210,11 @@ pub fn build_app<'a>() -> App<'a> { .required(false), ) .arg( - Arg::new(FLAG_BACKEND) - .long(FLAG_BACKEND) - .about("Choose a different backend") - // .requires(BACKEND) - .default_value(Backend::default().as_str()) - .possible_values(Backend::OPTIONS) + 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( @@ -273,12 +270,12 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { use std::str::FromStr; use BuildConfig::*; - let backend = match matches.value_of(FLAG_BACKEND) { - Some(name) => Backend::from_str(name).unwrap(), - None => Backend::default(), + let target = match matches.value_of(FLAG_TARGET) { + Some(name) => Target::from_str(name).unwrap(), + None => Target::default(), }; - let target = backend.to_triple(); + let triple = target.to_triple(); let arena = Bump::new(); let filename = matches.value_of(ROC_FILE).unwrap(); @@ -306,10 +303,10 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { let surgically_link = matches.is_present(FLAG_LINK); let precompiled = matches.is_present(FLAG_PRECOMPILED); - if surgically_link && !roc_linker::supported(&link_type, &target) { + if surgically_link && !roc_linker::supported(&link_type, &triple) { panic!( "Link type, {:?}, with target, {}, not supported by roc linker", - link_type, target + link_type, triple ); } @@ -338,7 +335,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { let target_valgrind = matches.is_present(FLAG_VALGRIND); let res_binary_path = build_file( &arena, - &target, + &triple, src_dir, path, opt_level, @@ -377,7 +374,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { Ok(outcome.status_code()) } BuildAndRun { roc_file_arg_index } => { - let mut cmd = match target.architecture { + 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 @@ -398,7 +395,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { _ => Command::new(&binary_path), }; - if let Architecture::Wasm32 = target.architecture { + if let Architecture::Wasm32 = triple.architecture { cmd.arg(binary_path); } @@ -503,43 +500,43 @@ fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) { println!("Running wasm files not support"); } -enum Backend { +enum Target { Host, X86_32, X86_64, Wasm32, } -impl Default for Backend { +impl Default for Target { fn default() -> Self { - Backend::Host + Target::Host } } -impl Backend { +impl Target { const fn as_str(&self) -> &'static str { match self { - Backend::Host => "host", - Backend::X86_32 => "x86_32", - Backend::X86_64 => "x86_64", - Backend::Wasm32 => "wasm32", + Target::Host => "host", + Target::X86_32 => "x86_32", + Target::X86_64 => "x86_64", + Target::Wasm32 => "wasm32", } } /// NOTE keep up to date! const OPTIONS: &'static [&'static str] = &[ - Backend::Host.as_str(), - Backend::X86_32.as_str(), - Backend::X86_64.as_str(), - Backend::Wasm32.as_str(), + Target::Host.as_str(), + Target::X86_32.as_str(), + Target::X86_64.as_str(), + Target::Wasm32.as_str(), ]; fn to_triple(&self) -> Triple { let mut triple = Triple::unknown(); match self { - Backend::Host => Triple::host(), - Backend::X86_32 => { + Target::Host => Triple::host(), + Target::X86_32 => { triple.architecture = Architecture::X86_32(X86_32Architecture::I386); triple.binary_format = BinaryFormat::Elf; @@ -548,13 +545,13 @@ impl Backend { triple } - Backend::X86_64 => { + Target::X86_64 => { triple.architecture = Architecture::X86_64; triple.binary_format = BinaryFormat::Elf; triple } - Backend::Wasm32 => { + Target::Wasm32 => { triple.architecture = Architecture::Wasm32; triple.binary_format = BinaryFormat::Wasm; @@ -564,21 +561,21 @@ impl Backend { } } -impl std::fmt::Display for Backend { +impl std::fmt::Display for Target { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.as_str()) } } -impl std::str::FromStr for Backend { +impl std::str::FromStr for Target { type Err = (); fn from_str(s: &str) -> Result { match s { - "host" => Ok(Backend::Host), - "x86_32" => Ok(Backend::X86_32), - "x86_64" => Ok(Backend::X86_64), - "wasm32" => Ok(Backend::Wasm32), + "host" => Ok(Target::Host), + "x86_32" => Ok(Target::X86_32), + "x86_64" => Ok(Target::X86_64), + "wasm32" => Ok(Target::Wasm32), _ => Err(()), } } diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 9e7aee0d8b..164bdd86ce 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -194,7 +194,7 @@ mod cli_run { ) { assert_eq!(input_file, None, "Wasm does not support input files"); let mut flags = flags.to_vec(); - flags.push("--backend=wasm32"); + flags.push("--target=wasm32"); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat()); if !compile_out.stderr.is_empty() { @@ -565,7 +565,7 @@ mod cli_run { &file_name, benchmark.stdin, benchmark.executable_filename, - &["--backend=x86_32"], + &["--target=x86_32"], benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.expected_ending, benchmark.use_valgrind, @@ -575,7 +575,7 @@ mod cli_run { &file_name, benchmark.stdin, benchmark.executable_filename, - &["--backend=x86_32", "--optimize"], + &["--target=x86_32", "--optimize"], benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.expected_ending, benchmark.use_valgrind, diff --git a/cli_utils/Cargo.toml b/cli_utils/Cargo.toml index a39dadba80..6f90db6737 100644 --- a/cli_utils/Cargo.toml +++ b/cli_utils/Cargo.toml @@ -20,4 +20,6 @@ serde = { version = "1.0.130", features = ["derive"] } serde-xml-rs = "0.5.1" strip-ansi-escapes = "0.1.1" tempfile = "3.2.0" + +[target.'cfg(unix)'.dependencies] rlimit = "0.6.2" diff --git a/cli_utils/src/bench_utils.rs b/cli_utils/src/bench_utils.rs index b01ec495fd..d14de367bd 100644 --- a/cli_utils/src/bench_utils.rs +++ b/cli_utils/src/bench_utils.rs @@ -1,11 +1,12 @@ use crate::helpers::{example_file, run_cmd, run_roc}; use criterion::{black_box, measurement::Measurement, BenchmarkGroup}; -use rlimit::{setrlimit, Resource}; -use std::path::Path; +use std::{path::Path, thread}; + +const CFOLD_STACK_SIZE: usize = 8192 * 100000; fn exec_bench_w_input( file: &Path, - stdin_str: &str, + stdin_str: &'static str, executable_filename: &str, expected_ending: &str, bench_group_opt: Option<&mut BenchmarkGroup>, @@ -31,7 +32,7 @@ fn exec_bench_w_input( fn check_cmd_output( file: &Path, - stdin_str: &str, + stdin_str: &'static str, executable_filename: &str, expected_ending: &str, ) { @@ -41,11 +42,16 @@ fn check_cmd_output( .unwrap() .to_string(); - if cmd_str.contains("cfold") { - increase_stack_limit(); - } + let out = if cmd_str.contains("cfold") { + let child = thread::Builder::new() + .stack_size(CFOLD_STACK_SIZE) + .spawn(move || run_cmd(&cmd_str, &[stdin_str], &[])) + .unwrap(); - let out = run_cmd(&cmd_str, &[stdin_str], &[]); + child.join().unwrap() + } else { + run_cmd(&cmd_str, &[stdin_str], &[]) + }; if !&out.stdout.ends_with(expected_ending) { panic!( @@ -69,7 +75,20 @@ fn bench_cmd( .to_string(); if cmd_str.contains("cfold") { - increase_stack_limit(); + #[cfg(unix)] + use rlimit::{setrlimit, Resource}; + #[cfg(unix)] + setrlimit( + Resource::STACK, + CFOLD_STACK_SIZE as u64, + CFOLD_STACK_SIZE as u64, + ) + .expect("Failed to increase stack limit."); + + #[cfg(windows)] + println!("Skipping the cfold benchmark on windows, I can't adjust the stack size and use criterion at the same time."); + #[cfg(windows)] + return; } if let Some(bench_group) = bench_group_opt { @@ -85,12 +104,6 @@ fn bench_cmd( } } -fn increase_stack_limit() { - let new_stack_limit = 8192 * 100000; - setrlimit(Resource::STACK, new_stack_limit, new_stack_limit) - .expect("Failed to increase stack limit."); -} - pub fn bench_nqueens(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( &example_file("benchmarks", "NQueens.roc"), diff --git a/compiler/alias_analysis/Cargo.toml b/compiler/alias_analysis/Cargo.toml new file mode 100644 index 0000000000..34cfb7666a --- /dev/null +++ b/compiler/alias_analysis/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["The Roc Contributors"] +edition = "2018" +license = "UPL-1.0" +name = "roc_alias_analysis" +version = "0.1.0" + +[dependencies] +morphic_lib = {path = "../../vendor/morphic_lib"} +roc_collections = {path = "../collections"} +roc_module = {path = "../module"} +roc_mono = {path = "../mono"} + diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/alias_analysis/src/lib.rs similarity index 98% rename from compiler/mono/src/alias_analysis.rs rename to compiler/alias_analysis/src/lib.rs index f25e3e3b08..f9d38cc708 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/alias_analysis/src/lib.rs @@ -8,11 +8,11 @@ use roc_collections::all::{MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; -use crate::ir::{ +use roc_mono::ir::{ Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, OptLevel, Proc, Stmt, }; -use crate::layout::{Builtin, Layout, RawFunctionLayout, UnionLayout}; +use roc_mono::layout::{Builtin, Layout, RawFunctionLayout, UnionLayout}; // just using one module for now pub const MOD_APP: ModName = ModName(b"UserApp"); @@ -110,7 +110,7 @@ fn bytes_as_ascii(bytes: &[u8]) -> String { pub fn spec_program<'a, I>( opt_level: OptLevel, - entry_point: crate::ir::EntryPoint<'a>, + entry_point: roc_mono::ir::EntryPoint<'a>, procs: I, ) -> Result where @@ -266,7 +266,7 @@ fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId) } fn build_entry_point( - layout: crate::ir::ProcLayout, + layout: roc_mono::ir::ProcLayout, func_name: FuncName, host_exposed_functions: &[([u8; SIZE], &[Layout])], ) -> Result { @@ -363,7 +363,7 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet>)> #[derive(Default)] struct Env<'a> { symbols: MutMap, - join_points: MutMap, + join_points: MutMap, type_names: MutSet>, } @@ -711,7 +711,7 @@ fn call_spec( passed_function, .. }) => { - use crate::low_level::HigherOrder::*; + use roc_mono::low_level::HigherOrder::*; let array = passed_function.specialization_id.to_bytes(); let spec_var = CalleeSpecVar(&array); @@ -1196,7 +1196,7 @@ fn lowlevel_spec( block: BlockId, layout: &Layout, op: &LowLevel, - update_mode: crate::ir::UpdateModeId, + update_mode: roc_mono::ir::UpdateModeId, arguments: &[Symbol], ) -> Result { use LowLevel::*; @@ -1258,22 +1258,21 @@ fn lowlevel_spec( builder.add_bag_get(block, bag) } - ListSet => { + ListReplaceUnsafe => { let list = env.symbols[&arguments[0]]; let to_insert = env.symbols[&arguments[2]]; let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; - // decrement the overwritten element - let overwritten = builder.add_bag_get(block, bag)?; - let _unit = builder.add_recursive_touch(block, overwritten)?; - - let _unit = builder.add_update(block, update_mode_var, cell)?; + let _unit1 = builder.add_touch(block, cell)?; + let _unit2 = builder.add_update(block, update_mode_var, cell)?; builder.add_bag_insert(block, bag, to_insert)?; - with_new_heap_cell(builder, block, bag) + let old_value = builder.add_bag_get(block, bag)?; + let new_list = with_new_heap_cell(builder, block, bag)?; + builder.add_make_tuple(block, &[new_list, old_value]) } ListSwap => { let list = env.symbols[&arguments[0]]; diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index f569cae620..b031092df5 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -46,6 +46,10 @@ pub fn link( operating_system: OperatingSystem::Darwin, .. } => link_macos(target, output_path, input_paths, link_type), + Triple { + operating_system: OperatingSystem::Windows, + .. + } => link_windows(target, output_path, input_paths, link_type), _ => panic!("TODO gracefully handle unsupported target: {:?}", target), } } @@ -1049,6 +1053,15 @@ fn link_wasm32( Ok((child, output_path)) } +fn link_windows( + _target: &Triple, + _output_path: PathBuf, + _input_paths: &[&str], + _link_type: LinkType, +) -> io::Result<(Child, PathBuf)> { + todo!("Add windows support to the surgical linker. See issue #2608.") +} + #[cfg(feature = "llvm")] pub fn module_to_dylib( module: &inkwell::module::Module, diff --git a/compiler/build/src/target.rs b/compiler/build/src/target.rs index a49aff892b..5610a768ad 100644 --- a/compiler/build/src/target.rs +++ b/compiler/build/src/target.rs @@ -41,6 +41,11 @@ pub fn target_triple_str(target: &Triple) -> &'static str { operating_system: OperatingSystem::Darwin, .. } => "x86_64-unknown-darwin10", + Triple { + architecture: Architecture::X86_64, + operating_system: OperatingSystem::Windows, + .. + } => "x86_64-pc-windows-gnu", _ => panic!("TODO gracefully handle unsupported target: {:?}", target), } } diff --git a/compiler/builtins/bitcode/README.md b/compiler/builtins/bitcode/README.md index 604f3a53d0..1585c2e8fb 100644 --- a/compiler/builtins/bitcode/README.md +++ b/compiler/builtins/bitcode/README.md @@ -7,7 +7,7 @@ To add a builtin: 2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }` 3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first argument is the function, the second is the name of it in LLVM. 4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevant area and add your new function. -5. You can now your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`! +5. You can now use your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`! ## How it works diff --git a/compiler/builtins/bitcode/src/dec.zig b/compiler/builtins/bitcode/src/dec.zig index ca24c12641..e8ec71e07a 100644 --- a/compiler/builtins/bitcode/src/dec.zig +++ b/compiler/builtins/bitcode/src/dec.zig @@ -26,21 +26,19 @@ pub const RocDec = extern struct { return .{ .num = num * one_point_zero_i128 }; } - // TODO: There's got to be a better way to do this other than converting to Str pub fn fromF64(num: f64) ?RocDec { - var digit_bytes: [19]u8 = undefined; // 19 = max f64 digits + '.' + '-' + var result: f64 = num * comptime @intToFloat(f64, one_point_zero_i128); - var fbs = std.io.fixedBufferStream(digit_bytes[0..]); - std.fmt.formatFloatDecimal(num, .{}, fbs.writer()) catch - return null; - - var dec = RocDec.fromStr(RocStr.init(&digit_bytes, fbs.pos)); - - if (dec) |d| { - return d; - } else { + if (result > comptime @intToFloat(f64, math.maxInt(i128))) { return null; } + + if (result < comptime @intToFloat(f64, math.minInt(i128))) { + return null; + } + + var ret: RocDec = .{ .num = @floatToInt(i128, result) }; + return ret; } pub fn fromStr(roc_str: RocStr) ?RocDec { @@ -729,6 +727,11 @@ test "fromF64" { try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?); } +test "fromF64 overflow" { + var dec = RocDec.fromF64(1e308); + try expectEqual(dec, null); +} + test "fromStr: empty" { var roc_str = RocStr.init("", 0); var dec = RocDec.fromStr(roc_str); diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 70925554f1..f9ef75410f 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -1256,95 +1256,56 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt return output; } -pub fn listSetInPlace( - bytes: ?[*]u8, +pub fn listReplaceInPlace( + list: RocList, index: usize, element: Opaque, element_width: usize, - dec: Dec, -) callconv(.C) ?[*]u8 { + out_element: ?[*]u8, +) callconv(.C) RocList { // INVARIANT: bounds checking happens on the roc side // // at the time of writing, the function is implemented roughly as - // `if inBounds then LowLevelListGet input index item else input` + // `if inBounds then LowLevelListReplace input index item else input` // so we don't do a bounds check here. Hence, the list is also non-empty, // because inserting into an empty list is always out of bounds - - return listSetInPlaceHelp(bytes, index, element, element_width, dec); + return listReplaceInPlaceHelp(list, index, element, element_width, out_element); } -pub fn listSet( - bytes: ?[*]u8, - length: usize, +pub fn listReplace( + list: RocList, alignment: u32, index: usize, element: Opaque, element_width: usize, - dec: Dec, -) callconv(.C) ?[*]u8 { + out_element: ?[*]u8, +) callconv(.C) RocList { // INVARIANT: bounds checking happens on the roc side // // at the time of writing, the function is implemented roughly as - // `if inBounds then LowLevelListGet input index item else input` + // `if inBounds then LowLevelListReplace input index item else input` // so we don't do a bounds check here. Hence, the list is also non-empty, // because inserting into an empty list is always out of bounds - const ptr: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), bytes)); - - if ((ptr - 1)[0] == utils.REFCOUNT_ONE) { - return listSetInPlaceHelp(bytes, index, element, element_width, dec); - } else { - return listSetImmutable(bytes, length, alignment, index, element, element_width, dec); - } + return listReplaceInPlaceHelp(list.makeUnique(alignment, element_width), index, element, element_width, out_element); } -inline fn listSetInPlaceHelp( - bytes: ?[*]u8, +inline fn listReplaceInPlaceHelp( + list: RocList, index: usize, element: Opaque, element_width: usize, - dec: Dec, -) ?[*]u8 { + out_element: ?[*]u8, +) RocList { // the element we will replace - var element_at_index = (bytes orelse undefined) + (index * element_width); + var element_at_index = (list.bytes orelse undefined) + (index * element_width); - // decrement its refcount - dec(element_at_index); + // copy out the old element + @memcpy(out_element orelse undefined, element_at_index, element_width); // copy in the new element @memcpy(element_at_index, element orelse undefined, element_width); - return bytes; -} - -inline fn listSetImmutable( - old_bytes: ?[*]u8, - length: usize, - alignment: u32, - index: usize, - element: Opaque, - element_width: usize, - dec: Dec, -) ?[*]u8 { - const data_bytes = length * element_width; - - var new_bytes = utils.allocateWithRefcount(data_bytes, alignment); - - @memcpy(new_bytes, old_bytes orelse undefined, data_bytes); - - // the element we will replace - var element_at_index = new_bytes + (index * element_width); - - // decrement its refcount - dec(element_at_index); - - // copy in the new element - @memcpy(element_at_index, element orelse undefined, element_width); - - // consume RC token of original - utils.decref(old_bytes, data_bytes, alignment); - - //return list; - return new_bytes; + return list; } pub fn listFindUnsafe( diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index c047588d92..5388804416 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -49,8 +49,8 @@ comptime { exportListFn(list.listConcat, "concat"); exportListFn(list.listSublist, "sublist"); exportListFn(list.listDropAt, "drop_at"); - exportListFn(list.listSet, "set"); - exportListFn(list.listSetInPlace, "set_in_place"); + exportListFn(list.listReplace, "replace"); + exportListFn(list.listReplaceInPlace, "replace_in_place"); exportListFn(list.listSwap, "swap"); exportListFn(list.listAny, "any"); exportListFn(list.listAll, "all"); diff --git a/compiler/builtins/build.rs b/compiler/builtins/build.rs index d71d03b4fc..3e8fc6ab5c 100644 --- a/compiler/builtins/build.rs +++ b/compiler/builtins/build.rs @@ -50,12 +50,17 @@ fn main() { ); // OBJECT FILES + #[cfg(windows)] + const BUILTINS_HOST_FILE: &str = "builtins-host.obj"; + + #[cfg(not(windows))] + const BUILTINS_HOST_FILE: &str = "builtins-host.o"; generate_object_file( &bitcode_path, "BUILTINS_HOST_O", "object", - "builtins-host.o", + BUILTINS_HOST_FILE, ); generate_object_file( @@ -104,7 +109,7 @@ fn generate_object_file( println!("Moving zig object `{}` to: {}", zig_object, dest_obj); // we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too) - run_command(&bitcode_path, "cp", &[src_obj, dest_obj]); + fs::copy(src_obj, dest_obj).expect("Failed to copy object file."); } fn generate_bc_file( diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 1345156d0f..5be3ecb830 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -354,8 +354,8 @@ pub const LIST_RANGE: &str = "roc_builtins.list.range"; pub const LIST_REVERSE: &str = "roc_builtins.list.reverse"; pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with"; pub const LIST_CONCAT: &str = "roc_builtins.list.concat"; -pub const LIST_SET: &str = "roc_builtins.list.set"; -pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place"; +pub const LIST_REPLACE: &str = "roc_builtins.list.replace"; +pub const LIST_REPLACE_IN_PLACE: &str = "roc_builtins.list.replace_in_place"; pub const LIST_ANY: &str = "roc_builtins.list.any"; pub const LIST_ALL: &str = "roc_builtins.list.all"; pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 7d373fe06b..351adc432b 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -1056,6 +1056,19 @@ pub fn types() -> MutMap { Box::new(result_type(flex(TVAR1), list_was_empty.clone())), ); + // replace : List elem, Nat, elem -> { list: List elem, value: elem } + add_top_level_function_type!( + Symbol::LIST_REPLACE, + vec![list_type(flex(TVAR1)), nat_type(), flex(TVAR1)], + Box::new(SolvedType::Record { + fields: vec![ + ("list".into(), RecordField::Required(list_type(flex(TVAR1)))), + ("value".into(), RecordField::Required(flex(TVAR1))), + ], + ext: Box::new(SolvedType::EmptyRecord), + }), + ); + // set : List elem, Nat, elem -> List elem add_top_level_function_type!( Symbol::LIST_SET, diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index 0a83a9fc0c..159b8429a3 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -16,6 +16,7 @@ roc_types = { path = "../types" } roc_builtins = { path = "../builtins" } ven_graph = { path = "../../vendor/pathfinding" } bumpalo = { version = "3.8.0", features = ["collections"] } +static_assertions = "1.1.0" [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index de0812c4a2..6a0052d4a9 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -29,6 +29,8 @@ pub struct IntroducedVariables { // but a variable can only have one name. Therefore // `ftv : SendMap`. pub wildcards: Vec, + pub lambda_sets: Vec, + pub inferred: Vec, pub var_by_name: SendMap, pub name_by_var: SendMap, pub host_exposed_aliases: MutMap, @@ -44,12 +46,22 @@ impl IntroducedVariables { self.wildcards.push(var); } + pub fn insert_inferred(&mut self, var: Variable) { + self.inferred.push(var); + } + + fn insert_lambda_set(&mut self, var: Variable) { + self.lambda_sets.push(var); + } + pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { self.host_exposed_aliases.insert(symbol, var); } pub fn union(&mut self, other: &Self) { self.wildcards.extend(other.wildcards.iter().cloned()); + self.lambda_sets.extend(other.lambda_sets.iter().cloned()); + self.inferred.extend(other.inferred.iter().cloned()); self.var_by_name.extend(other.var_by_name.clone()); self.name_by_var.extend(other.name_by_var.clone()); self.host_exposed_aliases @@ -280,7 +292,9 @@ fn can_annotation_help( references, ); - let closure = Type::Variable(var_store.fresh()); + let lambda_set = var_store.fresh(); + introduced_variables.insert_lambda_set(lambda_set); + let closure = Type::Variable(lambda_set); Type::Function(args, Box::new(closure), Box::new(ret)) } @@ -326,6 +340,7 @@ fn can_annotation_help( let (type_arguments, lambda_set_variables, actual) = instantiate_and_freshen_alias_type( var_store, + introduced_variables, &alias.type_variables, args, &alias.lambda_set_variables, @@ -612,6 +627,9 @@ fn can_annotation_help( // Inference variables aren't bound to a rigid or a wildcard, so all we have to do is // make a fresh unconstrained variable, and let the type solver fill it in for us 🤠 let var = var_store.fresh(); + + introduced_variables.insert_inferred(var); + Type::Variable(var) } Malformed(string) => { @@ -628,6 +646,7 @@ fn can_annotation_help( pub fn instantiate_and_freshen_alias_type( var_store: &mut VarStore, + introduced_variables: &mut IntroducedVariables, type_variables: &[Loc<(Lowercase, Variable)>], type_arguments: Vec, lambda_set_variables: &[LambdaSet], @@ -657,6 +676,7 @@ pub fn instantiate_and_freshen_alias_type( if let Type::Variable(var) = typ.0 { let fresh = var_store.fresh(); substitutions.insert(var, Type::Variable(fresh)); + introduced_variables.insert_lambda_set(fresh); new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh))); } else { unreachable!("at this point there should be only vars in there"); @@ -681,8 +701,12 @@ pub fn freshen_opaque_def( .map(|_| Type::Variable(var_store.fresh())) .collect(); + // TODO this gets ignored; is that a problem + let mut introduced_variables = IntroducedVariables::default(); + instantiate_and_freshen_alias_type( var_store, + &mut introduced_variables, &opaque.type_variables, fresh_arguments, &opaque.lambda_set_variables, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 290f822eeb..aa537b9931 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -57,6 +57,7 @@ pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] { Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL], Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD], Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT], + Symbol::LIST_SET => &[Symbol::LIST_REPLACE], _ => &[], } } @@ -102,6 +103,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option STR_TO_I8 => str_to_num, LIST_LEN => list_len, LIST_GET => list_get, + LIST_REPLACE => list_replace, LIST_SET => list_set, LIST_APPEND => list_append, LIST_FIRST => list_first, @@ -2304,6 +2306,91 @@ fn list_get(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.replace : List elem, Nat, elem -> { list: List elem, value: elem } +fn list_replace(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_list = Symbol::ARG_1; + let arg_index = Symbol::ARG_2; + let arg_elem = Symbol::ARG_3; + let bool_var = var_store.fresh(); + let len_var = var_store.fresh(); + let elem_var = var_store.fresh(); + let list_arg_var = var_store.fresh(); + let ret_record_var = var_store.fresh(); + let ret_result_var = var_store.fresh(); + + let list_field = Field { + var: list_arg_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(Expr::Var(arg_list))), + }; + + let value_field = Field { + var: elem_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(Expr::Var(arg_elem))), + }; + + // Perform a bounds check. If it passes, run LowLevel::ListReplaceUnsafe. + // Otherwise, return the list unmodified. + let body = If { + cond_var: bool_var, + branch_var: ret_result_var, + branches: vec![( + // if-condition + no_region( + // index < List.len list + RunLowLevel { + op: LowLevel::NumLt, + args: vec![ + (len_var, Var(arg_index)), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_arg_var, Var(arg_list))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // then-branch + no_region( + // List.replaceUnsafe list index elem + RunLowLevel { + op: LowLevel::ListReplaceUnsafe, + args: vec![ + (list_arg_var, Var(arg_list)), + (len_var, Var(arg_index)), + (elem_var, Var(arg_elem)), + ], + ret_var: ret_record_var, + }, + ), + )], + final_else: Box::new( + // else-branch + no_region(record( + vec![("list".into(), list_field), ("value".into(), value_field)], + var_store, + )), + ), + }; + + defn( + symbol, + vec![ + (list_arg_var, Symbol::ARG_1), + (len_var, Symbol::ARG_2), + (elem_var, Symbol::ARG_3), + ], + var_store, + body, + ret_result_var, + ) +} + /// List.set : List elem, Nat, elem -> List elem /// /// List.set : @@ -2318,9 +2405,27 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let len_var = var_store.fresh(); let elem_var = var_store.fresh(); + let replace_record_var = var_store.fresh(); let list_arg_var = var_store.fresh(); // Uniqueness type Attr differs between let list_ret_var = var_store.fresh(); // the arg list and the returned list + let replace_function = ( + var_store.fresh(), + Loc::at_zero(Expr::Var(Symbol::LIST_REPLACE)), + var_store.fresh(), + replace_record_var, + ); + + let replace_call = Expr::Call( + Box::new(replace_function), + vec![ + (list_arg_var, Loc::at_zero(Var(arg_list))), + (len_var, Loc::at_zero(Var(arg_index))), + (elem_var, Loc::at_zero(Var(arg_elem))), + ], + CalledVia::Space, + ); + // Perform a bounds check. If it passes, run LowLevel::ListSet. // Otherwise, return the list unmodified. let body = If { @@ -2347,18 +2452,16 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def { }, ), // then-branch - no_region( - // List.setUnsafe list index - RunLowLevel { - op: LowLevel::ListSet, - args: vec![ - (list_arg_var, Var(arg_list)), - (len_var, Var(arg_index)), - (elem_var, Var(arg_elem)), - ], - ret_var: list_ret_var, - }, - ), + no_region(Access { + record_var: replace_record_var, + ext_var: var_store.fresh(), + field_var: list_ret_var, + loc_expr: Box::new(no_region( + // List.replaceUnsafe list index elem + replace_call, + )), + field: "list".into(), + }), )], final_else: Box::new( // else-branch diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index e36bfc22a6..8ffd1ec896 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -1,175 +1,482 @@ use crate::expected::{Expected, PExpected}; -use roc_collections::all::{MutSet, SendMap}; -use roc_module::{ident::TagName, symbol::Symbol}; +use roc_collections::soa::{Index, Slice}; +use roc_module::ident::TagName; +use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; +use roc_types::subs::Variable; use roc_types::types::{Category, PatternCategory, Type}; -use roc_types::{subs::Variable, types::VariableDetail}; -/// A presence constraint is an additive constraint that defines the lower bound -/// of a type. For example, `Present(t1, IncludesTag(A, []))` means that the -/// type `t1` must contain at least the tag `A`. The additive nature of these -/// constraints makes them behaviorally different from unification-based constraints. -#[derive(Debug, Clone, PartialEq)] -pub enum PresenceConstraint { - IncludesTag(TagName, Vec, Region, PatternCategory), - IsOpen, - Pattern(Region, PatternCategory, PExpected), +#[derive(Debug)] +pub struct Constraints { + pub constraints: Vec, + pub types: Vec, + pub variables: Vec, + pub def_types: Vec<(Symbol, Loc>)>, + pub let_constraints: Vec, + pub categories: Vec, + pub pattern_categories: Vec, + pub expectations: Vec>, + pub pattern_expectations: Vec>, + pub includes_tags: Vec, + pub strings: Vec<&'static str>, } +impl Default for Constraints { + fn default() -> Self { + Self::new() + } +} + +impl Constraints { + pub fn new() -> Self { + let constraints = Vec::new(); + let mut types = Vec::new(); + let variables = Vec::new(); + let def_types = Vec::new(); + let let_constraints = Vec::new(); + let mut categories = Vec::with_capacity(16); + let mut pattern_categories = Vec::with_capacity(16); + let expectations = Vec::new(); + let pattern_expectations = Vec::new(); + let includes_tags = Vec::new(); + let strings = Vec::new(); + + types.extend([Type::EmptyRec, Type::EmptyTagUnion]); + + categories.extend([ + Category::Record, + Category::ForeignCall, + Category::OpaqueArg, + Category::Lambda, + Category::ClosureSize, + Category::StrInterpolation, + Category::If, + Category::When, + Category::Float, + Category::Int, + Category::Num, + Category::List, + Category::Str, + Category::Character, + ]); + + pattern_categories.extend([ + PatternCategory::Record, + PatternCategory::EmptyRecord, + PatternCategory::PatternGuard, + PatternCategory::PatternDefault, + PatternCategory::Set, + PatternCategory::Map, + PatternCategory::Str, + PatternCategory::Num, + PatternCategory::Int, + PatternCategory::Float, + PatternCategory::Character, + ]); + + Self { + constraints, + types, + variables, + def_types, + let_constraints, + categories, + pattern_categories, + expectations, + pattern_expectations, + includes_tags, + strings, + } + } + + pub const EMPTY_RECORD: Index = Index::new(0); + pub const EMPTY_TAG_UNION: Index = Index::new(1); + + pub const CATEGORY_RECORD: Index = Index::new(0); + pub const CATEGORY_FOREIGNCALL: Index = Index::new(1); + pub const CATEGORY_OPAQUEARG: Index = Index::new(2); + pub const CATEGORY_LAMBDA: Index = Index::new(3); + pub const CATEGORY_CLOSURESIZE: Index = Index::new(4); + pub const CATEGORY_STRINTERPOLATION: Index = Index::new(5); + pub const CATEGORY_IF: Index = Index::new(6); + pub const CATEGORY_WHEN: Index = Index::new(7); + pub const CATEGORY_FLOAT: Index = Index::new(8); + pub const CATEGORY_INT: Index = Index::new(9); + pub const CATEGORY_NUM: Index = Index::new(10); + pub const CATEGORY_LIST: Index = Index::new(11); + pub const CATEGORY_STR: Index = Index::new(12); + pub const CATEGORY_CHARACTER: Index = Index::new(13); + + pub const PCATEGORY_RECORD: Index = Index::new(0); + pub const PCATEGORY_EMPTYRECORD: Index = Index::new(1); + pub const PCATEGORY_PATTERNGUARD: Index = Index::new(2); + pub const PCATEGORY_PATTERNDEFAULT: Index = Index::new(3); + pub const PCATEGORY_SET: Index = Index::new(4); + pub const PCATEGORY_MAP: Index = Index::new(5); + pub const PCATEGORY_STR: Index = Index::new(6); + pub const PCATEGORY_NUM: Index = Index::new(7); + pub const PCATEGORY_INT: Index = Index::new(8); + pub const PCATEGORY_FLOAT: Index = Index::new(9); + pub const PCATEGORY_CHARACTER: Index = Index::new(10); + + #[inline(always)] + pub fn push_type(&mut self, typ: Type) -> Index { + match typ { + Type::EmptyRec => Self::EMPTY_RECORD, + Type::EmptyTagUnion => Self::EMPTY_TAG_UNION, + other => Index::push_new(&mut self.types, other), + } + } + + #[inline(always)] + pub fn push_expected_type(&mut self, expected: Expected) -> Index> { + Index::push_new(&mut self.expectations, expected) + } + + #[inline(always)] + pub fn push_category(&mut self, category: Category) -> Index { + match category { + Category::Record => Self::CATEGORY_RECORD, + Category::ForeignCall => Self::CATEGORY_FOREIGNCALL, + Category::OpaqueArg => Self::CATEGORY_OPAQUEARG, + Category::Lambda => Self::CATEGORY_LAMBDA, + Category::ClosureSize => Self::CATEGORY_CLOSURESIZE, + Category::StrInterpolation => Self::CATEGORY_STRINTERPOLATION, + Category::If => Self::CATEGORY_IF, + Category::When => Self::CATEGORY_WHEN, + Category::Float => Self::CATEGORY_FLOAT, + Category::Int => Self::CATEGORY_INT, + Category::Num => Self::CATEGORY_NUM, + Category::List => Self::CATEGORY_LIST, + Category::Str => Self::CATEGORY_STR, + Category::Character => Self::CATEGORY_CHARACTER, + other => Index::push_new(&mut self.categories, other), + } + } + + #[inline(always)] + pub fn push_pattern_category(&mut self, category: PatternCategory) -> Index { + match category { + PatternCategory::Record => Self::PCATEGORY_RECORD, + PatternCategory::EmptyRecord => Self::PCATEGORY_EMPTYRECORD, + PatternCategory::PatternGuard => Self::PCATEGORY_PATTERNGUARD, + PatternCategory::PatternDefault => Self::PCATEGORY_PATTERNDEFAULT, + PatternCategory::Set => Self::PCATEGORY_SET, + PatternCategory::Map => Self::PCATEGORY_MAP, + PatternCategory::Str => Self::PCATEGORY_STR, + PatternCategory::Num => Self::PCATEGORY_NUM, + PatternCategory::Int => Self::PCATEGORY_INT, + PatternCategory::Float => Self::PCATEGORY_FLOAT, + PatternCategory::Character => Self::PCATEGORY_CHARACTER, + other => Index::push_new(&mut self.pattern_categories, other), + } + } + + pub fn equal_types( + &mut self, + typ: Type, + expected: Expected, + category: Category, + region: Region, + ) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.expectations, expected); + let category_index = Self::push_category(self, category); + + Constraint::Eq(type_index, expected_index, category_index, region) + } + + pub fn equal_pattern_types( + &mut self, + typ: Type, + expected: PExpected, + category: PatternCategory, + region: Region, + ) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.pattern_expectations, expected); + let category_index = Self::push_pattern_category(self, category); + + Constraint::Pattern(type_index, expected_index, category_index, region) + } + + pub fn pattern_presence( + &mut self, + typ: Type, + expected: PExpected, + category: PatternCategory, + region: Region, + ) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + let expected_index = Index::push_new(&mut self.pattern_expectations, expected); + let category_index = Index::push_new(&mut self.pattern_categories, category); + + Constraint::PatternPresence(type_index, expected_index, category_index, region) + } + + pub fn is_open_type(&mut self, typ: Type) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + + Constraint::IsOpenType(type_index) + } + + pub fn includes_tag( + &mut self, + typ: Type, + tag_name: TagName, + types: I, + category: PatternCategory, + region: Region, + ) -> Constraint + where + I: IntoIterator, + { + let type_index = Index::push_new(&mut self.types, typ); + let category_index = Index::push_new(&mut self.pattern_categories, category); + let types_slice = Slice::extend_new(&mut self.types, types); + + let includes_tag = IncludesTag { + type_index, + tag_name, + types: types_slice, + pattern_category: category_index, + region, + }; + + let includes_tag_index = Index::push_new(&mut self.includes_tags, includes_tag); + + Constraint::IncludesTag(includes_tag_index) + } + + fn variable_slice(&mut self, it: I) -> Slice + where + I: IntoIterator, + { + let start = self.variables.len(); + self.variables.extend(it); + let length = self.variables.len() - start; + + Slice::new(start as _, length as _) + } + + fn def_types_slice(&mut self, it: I) -> Slice<(Symbol, Loc>)> + where + I: IntoIterator)>, + { + let start = self.def_types.len(); + + for (symbol, loc_type) in it { + let Loc { region, value } = loc_type; + let type_index = Index::push_new(&mut self.types, value); + + self.def_types.push((symbol, Loc::at(region, type_index))); + } + + let length = self.def_types.len() - start; + + Slice::new(start as _, length as _) + } + + pub fn exists(&mut self, flex_vars: I, defs_constraint: Constraint) -> Constraint + where + I: IntoIterator, + { + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + + self.constraints.push(defs_constraint); + self.constraints.push(Constraint::True); + + let let_contraint = LetConstraint { + rigid_vars: Slice::default(), + flex_vars: self.variable_slice(flex_vars), + def_types: Slice::default(), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } + + pub fn exists_many(&mut self, flex_vars: I, defs_constraint: C) -> Constraint + where + I: IntoIterator, + C: IntoIterator, + C::IntoIter: ExactSizeIterator, + { + let defs_constraint = self.and_constraint(defs_constraint); + + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + self.constraints.push(defs_constraint); + self.constraints.push(Constraint::True); + + let let_contraint = LetConstraint { + rigid_vars: Slice::default(), + flex_vars: self.variable_slice(flex_vars), + def_types: Slice::default(), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } + + pub fn let_constraint( + &mut self, + rigid_vars: I1, + flex_vars: I2, + def_types: I3, + defs_constraint: Constraint, + ret_constraint: Constraint, + ) -> Constraint + where + I1: IntoIterator, + I2: IntoIterator, + I3: IntoIterator)>, + { + let defs_and_ret_constraint = Index::new(self.constraints.len() as _); + + self.constraints.push(defs_constraint); + self.constraints.push(ret_constraint); + + let let_contraint = LetConstraint { + rigid_vars: self.variable_slice(rigid_vars), + flex_vars: self.variable_slice(flex_vars), + def_types: self.def_types_slice(def_types), + defs_and_ret_constraint, + }; + + let let_index = Index::new(self.let_constraints.len() as _); + self.let_constraints.push(let_contraint); + + Constraint::Let(let_index) + } + + pub fn and_constraint(&mut self, constraints: I) -> Constraint + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + { + let mut it = constraints.into_iter(); + + match it.len() { + 0 => Constraint::True, + 1 => it.next().unwrap(), + _ => { + let start = self.constraints.len() as u32; + + self.constraints.extend(it); + + let end = self.constraints.len() as u32; + + let slice = Slice::new(start, (end - start) as u16); + + Constraint::And(slice) + } + } + } + + pub fn lookup( + &mut self, + symbol: Symbol, + expected: Expected, + region: Region, + ) -> Constraint { + Constraint::Lookup( + symbol, + Index::push_new(&mut self.expectations, expected), + region, + ) + } + pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool { + match constraint { + Constraint::Eq(..) => false, + Constraint::Store(..) => false, + Constraint::Lookup(..) => false, + Constraint::Pattern(..) => false, + Constraint::True => false, + Constraint::SaveTheEnvironment => true, + Constraint::Let(index) => { + let let_constraint = &self.let_constraints[index.index()]; + + let offset = let_constraint.defs_and_ret_constraint.index(); + let defs_constraint = &self.constraints[offset]; + let ret_constraint = &self.constraints[offset + 1]; + + self.contains_save_the_environment(defs_constraint) + || self.contains_save_the_environment(ret_constraint) + } + Constraint::And(slice) => { + let constraints = &self.constraints[slice.indices()]; + + constraints + .iter() + .any(|c| self.contains_save_the_environment(c)) + } + Constraint::IsOpenType(_) => false, + Constraint::IncludesTag(_) => false, + Constraint::PatternPresence(_, _, _, _) => false, + } + } + + pub fn store( + &mut self, + typ: Type, + variable: Variable, + filename: &'static str, + line_number: u32, + ) -> Constraint { + let type_index = Index::push_new(&mut self.types, typ); + let string_index = Index::push_new(&mut self.strings, filename); + + Constraint::Store(type_index, variable, string_index, line_number) + } +} + +static_assertions::assert_eq_size!([u8; 3 * 8], Constraint); + #[derive(Debug, Clone, PartialEq)] pub enum Constraint { - Eq(Type, Expected, Category, Region), - Store(Type, Variable, &'static str, u32), - Lookup(Symbol, Expected, Region), - Pattern(Region, PatternCategory, Type, PExpected), + Eq(Index, Index>, Index, Region), + Store(Index, Variable, Index<&'static str>, u32), + Lookup(Symbol, Index>, Region), + Pattern( + Index, + Index>, + Index, + Region, + ), True, // Used for things that always unify, e.g. blanks and runtime errors SaveTheEnvironment, - Let(Box), - And(Vec), - Present(Type, PresenceConstraint), + Let(Index), + And(Slice), + /// Presence constraints + IsOpenType(Index), // Theory; always applied to a variable? if yes the use that + IncludesTag(Index), + PatternPresence( + Index, + Index>, + Index, + Region, + ), } #[derive(Debug, Clone, PartialEq)] pub struct LetConstraint { - pub rigid_vars: Vec, - pub flex_vars: Vec, - pub def_types: SendMap>, - pub defs_constraint: Constraint, - pub ret_constraint: Constraint, + pub rigid_vars: Slice, + pub flex_vars: Slice, + pub def_types: Slice<(Symbol, Loc>)>, + pub defs_and_ret_constraint: Index<(Constraint, Constraint)>, } -// VALIDATE - -#[derive(Default, Clone)] -struct Declared { - pub rigid_vars: MutSet, - pub flex_vars: MutSet, -} - -impl Constraint { - pub fn validate(&self) -> bool { - let mut unbound = Default::default(); - - validate_help(self, &Declared::default(), &mut unbound); - - if !unbound.type_variables.is_empty() { - panic!("found unbound type variables {:?}", &unbound.type_variables); - } - - if !unbound.lambda_set_variables.is_empty() { - panic!( - "found unbound lambda set variables {:?}", - &unbound.lambda_set_variables - ); - } - - if !unbound.recursion_variables.is_empty() { - panic!( - "found unbound recursion variables {:?}", - &unbound.recursion_variables - ); - } - - true - } - - pub fn contains_save_the_environment(&self) -> bool { - match self { - Constraint::Eq(_, _, _, _) => false, - Constraint::Store(_, _, _, _) => false, - Constraint::Lookup(_, _, _) => false, - Constraint::Pattern(_, _, _, _) => false, - Constraint::True => false, - Constraint::SaveTheEnvironment => true, - Constraint::Let(boxed) => { - boxed.ret_constraint.contains_save_the_environment() - || boxed.defs_constraint.contains_save_the_environment() - } - Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), - Constraint::Present(_, _) => false, - } - } -} - -fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) { - for var in &detail.type_variables { - if !(declared.rigid_vars.contains(var) || declared.flex_vars.contains(var)) { - accum.type_variables.insert(*var); - } - } - - // lambda set variables are always flex - for var in &detail.lambda_set_variables { - if declared.rigid_vars.contains(var) { - panic!("lambda set variable {:?} is declared as rigid", var); - } - - if !declared.flex_vars.contains(var) { - accum.lambda_set_variables.push(*var); - } - } - - // recursion vars should be always rigid - for var in &detail.recursion_variables { - if declared.flex_vars.contains(var) { - panic!("recursion variable {:?} is declared as flex", var); - } - - if !declared.rigid_vars.contains(var) { - accum.recursion_variables.insert(*var); - } - } -} - -fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut VariableDetail) { - use Constraint::*; - - match constraint { - True | SaveTheEnvironment | Lookup(_, _, _) => { /* nothing */ } - Store(typ, var, _, _) => { - subtract(declared, &typ.variables_detail(), accum); - - if !declared.flex_vars.contains(var) { - accum.type_variables.insert(*var); - } - } - Constraint::Eq(typ, expected, _, _) => { - subtract(declared, &typ.variables_detail(), accum); - subtract(declared, &expected.get_type_ref().variables_detail(), accum); - } - Constraint::Pattern(_, _, typ, expected) => { - subtract(declared, &typ.variables_detail(), accum); - subtract(declared, &expected.get_type_ref().variables_detail(), accum); - } - Constraint::Let(letcon) => { - let mut declared = declared.clone(); - declared - .rigid_vars - .extend(letcon.rigid_vars.iter().copied()); - declared.flex_vars.extend(letcon.flex_vars.iter().copied()); - - validate_help(&letcon.defs_constraint, &declared, accum); - validate_help(&letcon.ret_constraint, &declared, accum); - } - Constraint::And(inner) => { - for c in inner { - validate_help(c, declared, accum); - } - } - Constraint::Present(typ, constr) => { - subtract(declared, &typ.variables_detail(), accum); - match constr { - PresenceConstraint::IncludesTag(_, tys, _, _) => { - for ty in tys { - subtract(declared, &ty.variables_detail(), accum); - } - } - PresenceConstraint::IsOpen => {} - PresenceConstraint::Pattern(_, _, expected) => { - subtract(declared, &typ.variables_detail(), accum); - subtract(declared, &expected.get_type_ref().variables_detail(), accum); - } - } - } - } +#[derive(Debug, Clone, PartialEq)] +pub struct IncludesTag { + pub type_index: Index, + pub tag_name: TagName, + pub types: Slice, + pub pattern_category: Index, + pub region: Region, } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index c69719372b..1b5108dfe9 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -399,7 +399,7 @@ pub fn sort_can_defs( ) -> (Result, RuntimeError>, Output) { let CanDefs { refs_by_symbol, - can_defs_by_symbol, + mut can_defs_by_symbol, aliases, } = defs; @@ -583,7 +583,7 @@ pub fn sort_can_defs( &group, &env.closures, &mut all_successors_with_self, - &can_defs_by_symbol, + &mut can_defs_by_symbol, &mut declarations, ); } @@ -717,7 +717,7 @@ pub fn sort_can_defs( group, &env.closures, &mut all_successors_with_self, - &can_defs_by_symbol, + &mut can_defs_by_symbol, &mut declarations, ); } @@ -739,7 +739,7 @@ fn group_to_declaration( group: &[Symbol], closures: &MutMap, successors: &mut dyn FnMut(&Symbol) -> ImSet, - can_defs_by_symbol: &MutMap, + can_defs_by_symbol: &mut MutMap, declarations: &mut Vec, ) { use Declaration::*; @@ -759,57 +759,60 @@ fn group_to_declaration( // Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function), // normally `someDef` would be inserted twice. We use the region of the pattern as a unique key // for a definition, so every definition is only inserted (thus typechecked and emitted) once - let mut seen_pattern_regions: ImSet = ImSet::default(); + let mut seen_pattern_regions: Vec = Vec::with_capacity(2); for cycle in strongly_connected_components(group, filtered_successors) { if cycle.len() == 1 { let symbol = &cycle[0]; - if let Some(can_def) = can_defs_by_symbol.get(symbol) { - let mut new_def = can_def.clone(); - - // Determine recursivity of closures that are not tail-recursive - if let Closure(ClosureData { - recursive: recursive @ Recursive::NotRecursive, - .. - }) = &mut new_def.loc_expr.value - { - *recursive = closure_recursivity(*symbol, closures); - } - - let is_recursive = successors(symbol).contains(symbol); - - if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { - if is_recursive { - declarations.push(DeclareRec(vec![new_def.clone()])); - } else { - declarations.push(Declare(new_def.clone())); - } - seen_pattern_regions.insert(new_def.loc_pattern.region); - } - } - } else { - let mut can_defs = Vec::new(); - - // Topological sort gives us the reverse of the sorting we want! - for symbol in cycle.into_iter().rev() { - if let Some(can_def) = can_defs_by_symbol.get(&symbol) { - let mut new_def = can_def.clone(); - + match can_defs_by_symbol.remove(symbol) { + Some(mut new_def) => { // Determine recursivity of closures that are not tail-recursive if let Closure(ClosureData { recursive: recursive @ Recursive::NotRecursive, .. }) = &mut new_def.loc_expr.value { - *recursive = closure_recursivity(symbol, closures); + *recursive = closure_recursivity(*symbol, closures); } + let is_recursive = successors(symbol).contains(symbol); + if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { - can_defs.push(new_def.clone()); - } + seen_pattern_regions.push(new_def.loc_pattern.region); - seen_pattern_regions.insert(new_def.loc_pattern.region); + if is_recursive { + declarations.push(DeclareRec(vec![new_def])); + } else { + declarations.push(Declare(new_def)); + } + } + } + None => roc_error_macros::internal_error!("def not available {:?}", symbol), + } + } else { + let mut can_defs = Vec::new(); + + // Topological sort gives us the reverse of the sorting we want! + for symbol in cycle.into_iter().rev() { + match can_defs_by_symbol.remove(&symbol) { + Some(mut new_def) => { + // Determine recursivity of closures that are not tail-recursive + if let Closure(ClosureData { + recursive: recursive @ Recursive::NotRecursive, + .. + }) = &mut new_def.loc_expr.value + { + *recursive = closure_recursivity(symbol, closures); + } + + if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { + seen_pattern_regions.push(new_def.loc_pattern.region); + + can_defs.push(new_def); + } + } + None => roc_error_macros::internal_error!("def not available {:?}", symbol), } } @@ -861,6 +864,32 @@ fn pattern_to_vars_by_symbol( } } +fn single_can_def( + loc_can_pattern: Loc, + loc_can_expr: Loc, + expr_var: Variable, + opt_loc_annotation: Option>, + pattern_vars: SendMap, +) -> Def { + let def_annotation = opt_loc_annotation.map(|loc_annotation| Annotation { + signature: loc_annotation.value.typ, + introduced_variables: loc_annotation.value.introduced_variables, + aliases: loc_annotation.value.aliases, + region: loc_annotation.region, + }); + + Def { + expr_var, + loc_pattern: loc_can_pattern, + loc_expr: Loc { + region: loc_can_expr.region, + value: loc_can_expr.value, + }, + pattern_vars, + annotation: def_annotation, + } +} + // TODO trim down these arguments! #[allow(clippy::too_many_arguments)] #[allow(clippy::cognitive_complexity)] @@ -884,25 +913,25 @@ fn canonicalize_pending_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 ann = + let type_annotation = canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); // Record all the annotation's references in output.references.lookups - for symbol in ann.references { - output.references.lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + for symbol in type_annotation.references.iter() { + output.references.lookups.insert(*symbol); + output.references.referenced_type_defs.insert(*symbol); } - aliases.extend(ann.aliases.clone()); + aliases.extend(type_annotation.aliases.clone()); - output.introduced_variables.union(&ann.introduced_variables); + output + .introduced_variables + .union(&type_annotation.introduced_variables); pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); - let typ = ann.typ; - - let arity = typ.arity(); + let arity = type_annotation.typ.arity(); let problem = match &loc_can_pattern.value { Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed { @@ -960,33 +989,44 @@ fn canonicalize_pending_def<'a>( } }; - for (_, (symbol, _)) in scope.idents() { - if !vars_by_symbol.contains_key(symbol) { - continue; - } - - // We could potentially avoid some clones here by using Rc strategically, - // but the total amount of cloning going on here should typically be minimal. - can_defs_by_symbol.insert( - *symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - region: loc_can_expr.region, - // TODO try to remove this .clone()! - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: Some(Annotation { - signature: typ.clone(), - introduced_variables: output.introduced_variables.clone(), - aliases: ann.aliases.clone(), - region: loc_ann.region, - }), - }, + if let Pattern::Identifier(symbol) = loc_can_pattern.value { + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Some(Loc::at(loc_ann.region, type_annotation)), + vars_by_symbol.clone(), ); + can_defs_by_symbol.insert(symbol, def); + } else { + for (_, (symbol, _)) in scope.idents() { + if !vars_by_symbol.contains_key(symbol) { + continue; + } + + // We could potentially avoid some clones here by using Rc strategically, + // but the total amount of cloning going on here should typically be minimal. + can_defs_by_symbol.insert( + *symbol, + Def { + expr_var, + // TODO try to remove this .clone()! + loc_pattern: loc_can_pattern.clone(), + loc_expr: Loc { + region: loc_can_expr.region, + // TODO try to remove this .clone()! + value: loc_can_expr.value.clone(), + }, + pattern_vars: vars_by_symbol.clone(), + annotation: Some(Annotation { + signature: type_annotation.typ.clone(), + introduced_variables: output.introduced_variables.clone(), + aliases: type_annotation.aliases.clone(), + region: loc_ann.region, + }), + }, + ); + } } } @@ -995,23 +1035,23 @@ fn canonicalize_pending_def<'a>( InvalidAlias { .. } => { // invalid aliases and opaques (shadowed, incorrect patterns) get ignored } - TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { - let ann = + 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); // Record all the annotation's references in output.references.lookups - for symbol in ann.references { - output.references.lookups.insert(symbol); - output.references.referenced_type_defs.insert(symbol); + for symbol in type_annotation.references.iter() { + output.references.lookups.insert(*symbol); + output.references.referenced_type_defs.insert(*symbol); } - let typ = ann.typ; - - for (symbol, alias) in ann.aliases.clone() { + for (symbol, alias) in type_annotation.aliases.clone() { aliases.insert(symbol, alias); } - output.introduced_variables.union(&ann.introduced_variables); + output + .introduced_variables + .union(&type_annotation.introduced_variables); // bookkeeping for tail-call detection. If we're assigning to an // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. @@ -1038,118 +1078,115 @@ fn canonicalize_pending_def<'a>( // reset the tailcallable_symbol env.tailcallable_symbol = outer_identifier; - // see below: a closure needs a fresh References! - let mut is_closure = false; - // First, make sure we are actually assigning an identifier instead of (for example) a tag. // // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, // which also implies it's not a self tail call! // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - if let ( - &ast::Pattern::Identifier(_name), - &Pattern::Identifier(ref defined_symbol), - &Closure(ClosureData { + if let Pattern::Identifier(symbol) = loc_can_pattern.value { + if let &Closure(ClosureData { function_type, closure_type, closure_ext_var, return_type, - name: ref symbol, + name: ref closure_name, ref arguments, loc_body: ref body, ref captured_symbols, .. - }), - ) = ( - &loc_pattern.value, - &loc_can_pattern.value, - &loc_can_expr.value, - ) { - is_closure = true; - - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. (We'll re-insert it later.) - let references = env.closures.remove(symbol).unwrap_or_else(|| { - panic!( - "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", - symbol, env.closures - ) - }); - - // Re-insert the closure into the map, under its defined name. - // closures don't have a name, and therefore pick a fresh symbol. But in this - // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` - // and we want to reference it by that name. - env.closures.insert(*defined_symbol, references); - - // The closure is self tail recursive iff it tail calls itself (by defined name). - let is_recursive = match can_output.tail_call { - Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive, - _ => Recursive::NotRecursive, - }; - - // Recursion doesn't count as referencing. (If it did, all recursive functions - // would result in circular def errors!) - refs_by_symbol - .entry(*defined_symbol) - .and_modify(|(_, refs)| { - refs.lookups = refs.lookups.without(defined_symbol); + }) = &loc_can_expr.value + { + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. (We'll re-insert it later.) + let references = env.closures.remove(closure_name).unwrap_or_else(|| { + panic!( + "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", + closure_name, env.closures + ) }); - // renamed_closure_def = Some(&defined_symbol); - loc_can_expr.value = Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: *defined_symbol, - captured_symbols: captured_symbols.clone(), - recursive: is_recursive, - arguments: arguments.clone(), - loc_body: body.clone(), - }); - } + // Re-insert the closure into the map, under its defined name. + // closures don't have a name, and therefore pick a fresh symbol. But in this + // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` + // and we want to reference it by that name. + env.closures.insert(symbol, references); - // Store the referenced locals in the refs_by_symbol map, so we can later figure out - // which defined names reference each other. - for (_, (symbol, region)) in scope.idents() { - if !vars_by_symbol.contains_key(symbol) { - continue; - } + // The closure is self tail recursive iff it tail calls itself (by defined name). + let is_recursive = match can_output.tail_call { + Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive, + _ => Recursive::NotRecursive, + }; + + // 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.lookups = refs.lookups.without(&symbol); + }); + + // renamed_closure_def = Some(&symbol); + loc_can_expr.value = Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name: symbol, + captured_symbols: captured_symbols.clone(), + recursive: is_recursive, + arguments: arguments.clone(), + loc_body: body.clone(), + }); - let refs = // Functions' references don't count in defs. // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its // parent commit for the bug this fixed! - if is_closure { - References::new() - } else { - can_output.references.clone() - }; + let refs = References::new(); - refs_by_symbol.insert(*symbol, (*region, refs)); + refs_by_symbol.insert(symbol, (loc_can_pattern.region, refs)); + } else { + let refs = can_output.references; + refs_by_symbol.insert(symbol, (loc_ann.region, refs)); + } - can_defs_by_symbol.insert( - *symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - region: loc_can_expr.region, - // TODO try to remove this .clone()! - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: Some(Annotation { - signature: typ.clone(), - introduced_variables: output.introduced_variables.clone(), - aliases: ann.aliases.clone(), - region: loc_ann.region, - }), - }, + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Some(Loc::at(loc_ann.region, type_annotation)), + vars_by_symbol.clone(), ); + can_defs_by_symbol.insert(symbol, def); + } else { + for (_, (symbol, region)) in scope.idents() { + if !vars_by_symbol.contains_key(symbol) { + continue; + } + + let refs = can_output.references.clone(); + + refs_by_symbol.insert(*symbol, (*region, refs)); + + can_defs_by_symbol.insert( + *symbol, + Def { + expr_var, + // TODO try to remove this .clone()! + loc_pattern: loc_can_pattern.clone(), + loc_expr: Loc { + region: loc_can_expr.region, + // TODO try to remove this .clone()! + value: loc_can_expr.value.clone(), + }, + pattern_vars: vars_by_symbol.clone(), + annotation: Some(Annotation { + signature: type_annotation.typ.clone(), + introduced_variables: type_annotation.introduced_variables.clone(), + aliases: type_annotation.aliases.clone(), + region: loc_ann.region, + }), + }, + ); + } } } // If we have a pattern, then the def has a body (that is, it's not a @@ -1181,108 +1218,105 @@ fn canonicalize_pending_def<'a>( // reset the tailcallable_symbol env.tailcallable_symbol = outer_identifier; - // see below: a closure needs a fresh References! - let mut is_closure = false; - // First, make sure we are actually assigning an identifier instead of (for example) a tag. // // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, // which also implies it's not a self tail call! // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - if let ( - &ast::Pattern::Identifier(_name), - &Pattern::Identifier(ref defined_symbol), - &Closure(ClosureData { + if let Pattern::Identifier(symbol) = loc_can_pattern.value { + if let &Closure(ClosureData { function_type, closure_type, closure_ext_var, return_type, - name: ref symbol, + name: ref closure_name, ref arguments, loc_body: ref body, ref captured_symbols, .. - }), - ) = ( - &loc_pattern.value, - &loc_can_pattern.value, - &loc_can_expr.value, - ) { - is_closure = true; - - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. (We'll re-insert it later.) - let references = env.closures.remove(symbol).unwrap_or_else(|| { - panic!( - "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", - symbol, env.closures - ) - }); - - // Re-insert the closure into the map, under its defined name. - // closures don't have a name, and therefore pick a fresh symbol. But in this - // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` - // and we want to reference it by that name. - env.closures.insert(*defined_symbol, references); - - // The closure is self tail recursive iff it tail calls itself (by defined name). - let is_recursive = match can_output.tail_call { - Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive, - _ => Recursive::NotRecursive, - }; - - // Recursion doesn't count as referencing. (If it did, all recursive functions - // would result in circular def errors!) - refs_by_symbol - .entry(*defined_symbol) - .and_modify(|(_, refs)| { - refs.lookups = refs.lookups.without(defined_symbol); + }) = &loc_can_expr.value + { + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. (We'll re-insert it later.) + let references = env.closures.remove(closure_name).unwrap_or_else(|| { + panic!( + "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", + closure_name, env.closures + ) }); - loc_can_expr.value = Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: *defined_symbol, - captured_symbols: captured_symbols.clone(), - recursive: is_recursive, - arguments: arguments.clone(), - loc_body: body.clone(), - }); - } + // Re-insert the closure into the map, under its defined name. + // closures don't have a name, and therefore pick a fresh symbol. But in this + // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` + // and we want to reference it by that name. + env.closures.insert(symbol, references); + + // The closure is self tail recursive iff it tail calls itself (by defined name). + let is_recursive = match can_output.tail_call { + Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive, + _ => Recursive::NotRecursive, + }; + + // 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.lookups = refs.lookups.without(&symbol); + }); + + loc_can_expr.value = Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name: symbol, + captured_symbols: captured_symbols.clone(), + recursive: is_recursive, + arguments: arguments.clone(), + loc_body: body.clone(), + }); - // Store the referenced locals in the refs_by_symbol map, so we can later figure out - // which defined names reference each other. - for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) { - let refs = // Functions' references don't count in defs. // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its // parent commit for the bug this fixed! - if is_closure { - References::new() - } else { - can_output.references.clone() - }; + let refs = References::new(); + refs_by_symbol.insert(symbol, (loc_pattern.region, refs)); + } else { + let refs = can_output.references.clone(); + refs_by_symbol.insert(symbol, (loc_pattern.region, refs)); + } - refs_by_symbol.insert(symbol, (region, refs)); - - can_defs_by_symbol.insert( - symbol, - Def { - expr_var, - // TODO try to remove this .clone()! - loc_pattern: loc_can_pattern.clone(), - loc_expr: Loc { - // TODO try to remove this .clone()! - region: loc_can_expr.region, - value: loc_can_expr.value.clone(), - }, - pattern_vars: vars_by_symbol.clone(), - annotation: None, - }, + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + None, + vars_by_symbol.clone(), ); + can_defs_by_symbol.insert(symbol, def); + } else { + // Store the referenced locals in the refs_by_symbol map, so we can later figure out + // which defined names reference each other. + for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) { + let refs = can_output.references.clone(); + refs_by_symbol.insert(symbol, (region, refs)); + + can_defs_by_symbol.insert( + symbol, + Def { + expr_var, + // TODO try to remove this .clone()! + loc_pattern: loc_can_pattern.clone(), + loc_expr: Loc { + // TODO try to remove this .clone()! + region: loc_can_expr.region, + value: loc_can_expr.value.clone(), + }, + pattern_vars: vars_by_symbol.clone(), + annotation: None, + }, + ); + } } output.union(can_output); diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index efd74751bb..b5874be5bb 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -161,13 +161,13 @@ where } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Index(usize); +pub struct HumanIndex(usize); -impl Index { - pub const FIRST: Self = Index(0); +impl HumanIndex { + pub const FIRST: Self = HumanIndex(0); pub fn zero_based(i: usize) -> Self { - Index(i) + HumanIndex(i) } pub fn to_zero_based(self) -> usize { @@ -175,7 +175,7 @@ impl Index { } pub fn one_based(i: usize) -> Self { - Index(i - 1) + HumanIndex(i - 1) } pub fn ordinal(self) -> std::string::String { diff --git a/compiler/collections/src/lib.rs b/compiler/collections/src/lib.rs index 885d50b458..16f8d165dc 100644 --- a/compiler/collections/src/lib.rs +++ b/compiler/collections/src/lib.rs @@ -3,3 +3,4 @@ #![allow(clippy::large_enum_variant)] pub mod all; +pub mod soa; diff --git a/compiler/collections/src/soa.rs b/compiler/collections/src/soa.rs new file mode 100644 index 0000000000..563dba4f67 --- /dev/null +++ b/compiler/collections/src/soa.rs @@ -0,0 +1,119 @@ +use std::usize; + +#[derive(PartialEq, Eq)] +pub struct Index { + index: u32, + _marker: std::marker::PhantomData, +} + +impl Clone for Index { + fn clone(&self) -> Self { + Self { + index: self.index, + _marker: self._marker, + } + } +} + +impl Copy for Index {} + +impl std::fmt::Debug for Index { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Index({})", self.index) + } +} + +impl Index { + pub const fn new(index: u32) -> Self { + Self { + index, + _marker: std::marker::PhantomData, + } + } + + pub const fn index(&self) -> usize { + self.index as usize + } + + pub fn push_new(vector: &mut Vec, value: T) -> Index { + let index = Self::new(vector.len() as _); + + vector.push(value); + + index + } +} + +#[derive(PartialEq, Eq)] +pub struct Slice { + start: u32, + length: u16, + _marker: std::marker::PhantomData, +} + +impl Clone for Slice { + fn clone(&self) -> Self { + Self { + start: self.start, + length: self.length, + _marker: self._marker, + } + } +} + +impl Copy for Slice {} + +impl std::fmt::Debug for Slice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Slice(start = {}, length = {})", self.start, self.length) + } +} + +impl Default for Slice { + fn default() -> Self { + Self { + start: Default::default(), + length: Default::default(), + _marker: Default::default(), + } + } +} + +impl Slice { + pub const fn new(start: u32, length: u16) -> Self { + Self { + start, + length, + _marker: std::marker::PhantomData, + } + } + + pub fn extend_new(vector: &mut Vec, values: I) -> Slice + where + I: IntoIterator, + { + let start = vector.len() as u32; + + vector.extend(values); + + let end = vector.len() as u32; + + Self::new(start, (end - start) as u16) + } + + pub const fn len(&self) -> usize { + self.length as _ + } + + pub const fn is_empty(&self) -> bool { + self.length == 0 + } + + pub const fn indices(&self) -> std::ops::Range { + self.start as usize..(self.start as usize + self.length as usize) + } + + pub fn into_iter(&self) -> impl Iterator> { + self.indices().map(|i| Index::new(i as _)) + } +} diff --git a/compiler/constrain/Cargo.toml b/compiler/constrain/Cargo.toml index 82e7426c0d..d5c4059c63 100644 --- a/compiler/constrain/Cargo.toml +++ b/compiler/constrain/Cargo.toml @@ -14,3 +14,4 @@ roc_parse = { path = "../parse" } roc_types = { path = "../types" } roc_can = { path = "../can" } roc_builtins = { path = "../builtins" } +arrayvec = "0.7.2" diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index cf1a907845..16cb4b25da 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,8 +1,7 @@ -use roc_can::constraint::Constraint::{self, *}; -use roc_can::constraint::LetConstraint; +use arrayvec::ArrayVec; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::Expected::{self, *}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; -use roc_collections::all::SendMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::Region; @@ -12,8 +11,10 @@ use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, Category}; #[must_use] +#[inline(always)] pub fn add_numeric_bound_constr( - constrs: &mut Vec, + constraints: &mut Constraints, + num_constraints: &mut impl Extend, num_type: Type, bound: impl TypedNumericBound, region: Region, @@ -27,12 +28,12 @@ pub fn add_numeric_bound_constr( 0 => total_num_type, 1 => { let actual_type = Variable(range[0]); - constrs.push(Eq( - total_num_type.clone(), - Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region), - category, - region, - )); + let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region); + let because_suffix = + constraints.equal_types(total_num_type.clone(), expected, category, region); + + num_constraints.extend([because_suffix]); + total_num_type } _ => RangedNumber(Box::new(total_num_type), range), @@ -41,6 +42,7 @@ pub fn add_numeric_bound_constr( #[inline(always)] pub fn int_literal( + constraints: &mut Constraints, num_var: Variable, precision_var: Variable, expected: Expected, @@ -49,31 +51,35 @@ pub fn int_literal( ) -> Constraint { let reason = Reason::IntLiteral; - let mut constrs = Vec::with_capacity(3); - // Always add the bound first; this improves the resolved type quality in case it's an alias - // like "U8". + // Always add the bound first; this improves the resolved type quality in case it's an alias like "U8". + let mut constrs = ArrayVec::<_, 3>::new(); let num_type = add_numeric_bound_constr( + constraints, &mut constrs, Variable(num_var), bound, region, Category::Num, ); - constrs.extend(vec![ - Eq( + + constrs.extend([ + constraints.equal_types( num_type.clone(), ForReason(reason, num_int(Type::Variable(precision_var)), region), Category::Int, region, ), - Eq(num_type, expected, Category::Int, region), + constraints.equal_types(num_type, expected, Category::Int, region), ]); - exists(vec![num_var], And(constrs)) + // TODO the precision_var is not part of the exists here; for float it is. Which is correct? + let and_constraint = constraints.and_constraint(constrs); + constraints.exists([num_var], and_constraint) } #[inline(always)] pub fn float_literal( + constraints: &mut Constraints, num_var: Variable, precision_var: Variable, expected: Expected, @@ -82,29 +88,33 @@ pub fn float_literal( ) -> Constraint { let reason = Reason::FloatLiteral; - let mut constrs = Vec::with_capacity(3); + let mut constrs = ArrayVec::<_, 3>::new(); let num_type = add_numeric_bound_constr( + constraints, &mut constrs, Variable(num_var), bound, region, Category::Float, ); - constrs.extend(vec![ - Eq( + + constrs.extend([ + constraints.equal_types( num_type.clone(), ForReason(reason, num_float(Type::Variable(precision_var)), region), Category::Float, region, ), - Eq(num_type, expected, Category::Float, region), + constraints.equal_types(num_type, expected, Category::Float, region), ]); - exists(vec![num_var, precision_var], And(constrs)) + let and_constraint = constraints.and_constraint(constrs); + constraints.exists([num_var, precision_var], and_constraint) } #[inline(always)] pub fn num_literal( + constraints: &mut Constraints, num_var: Variable, expected: Expected, region: Region, @@ -112,23 +122,20 @@ pub fn num_literal( ) -> Constraint { let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); - let mut constrs = Vec::with_capacity(3); - let num_type = - add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num); - constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]); + let mut constrs = ArrayVec::<_, 2>::new(); + let num_type = add_numeric_bound_constr( + constraints, + &mut constrs, + open_number_type, + bound, + region, + Category::Num, + ); - exists(vec![num_var], And(constrs)) -} + constrs.extend([constraints.equal_types(num_type, expected, Category::Num, region)]); -#[inline(always)] -pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars, - def_types: SendMap::default(), - defs_constraint: constraint, - ret_constraint: Constraint::True, - })) + let and_constraint = constraints.and_constraint(constrs); + constraints.exists([num_var], and_constraint) } #[inline(always)] diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index da99c6c50e..0a97de4327 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -3,15 +3,14 @@ use crate::builtins::{ }; use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; -use roc_can::constraint::Constraint::{self, *}; -use roc_can::constraint::LetConstraint; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::{Declaration, Def}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; use roc_can::expr::{ClosureData, Field, WhenBranch}; use roc_can::pattern::Pattern; -use roc_collections::all::{ImMap, Index, MutSet, SendMap}; +use roc_collections::all::{HumanIndex, ImMap, MutMap, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; @@ -37,26 +36,16 @@ impl Info { } } -#[inline(always)] -pub fn exists(flex_vars: Vec, constraint: Constraint) -> Constraint { - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars, - def_types: SendMap::default(), - defs_constraint: constraint, - ret_constraint: Constraint::True, - })) -} - pub struct Env { /// Whenever we encounter a user-defined type variable (a "rigid" var for short), /// for example `a` in the annotation `identity : a -> a`, we add it to this /// map so that expressions within that annotation can share these vars. - pub rigids: ImMap, + pub rigids: MutMap, pub home: ModuleId, } fn constrain_untyped_args( + constraints: &mut Constraints, env: &Env, arguments: &[(Variable, Loc)], closure_type: Type, @@ -74,6 +63,7 @@ fn constrain_untyped_args( pattern_types.push(pattern_type); constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -91,21 +81,24 @@ fn constrain_untyped_args( } pub fn constrain_expr( + constraints: &mut Constraints, env: &Env, region: Region, expr: &Expr, expected: Expected, ) -> Constraint { match expr { - &Int(var, precision, _, _, bound) => int_literal(var, precision, expected, region, bound), - &Num(var, _, _, bound) => num_literal(var, expected, region, bound), - &Float(var, precision, _, _, bound) => { - float_literal(var, precision, expected, region, bound) + &Int(var, precision, _, _, bound) => { + int_literal(constraints, var, precision, expected, region, bound) } - EmptyRecord => constrain_empty_record(region, expected), + &Num(var, _, _, bound) => num_literal(constraints, var, expected, region, bound), + &Float(var, precision, _, _, bound) => { + float_literal(constraints, var, precision, expected, region, bound) + } + EmptyRecord => constrain_empty_record(constraints, region, expected), Expr::Record { record_var, fields } => { if fields.is_empty() { - constrain_empty_record(region, expected) + constrain_empty_record(constraints, region, expected) } else { let mut field_exprs = SendMap::default(); let mut field_types = SendMap::default(); @@ -113,18 +106,19 @@ pub fn constrain_expr( // Constraints need capacity for each field // + 1 for the record itself + 1 for record var - let mut constraints = Vec::with_capacity(2 + fields.len()); + let mut rec_constraints = Vec::with_capacity(2 + fields.len()); for (label, field) in fields { let field_var = field.var; let loc_field_expr = &field.loc_expr; - let (field_type, field_con) = constrain_field(env, field_var, &*loc_field_expr); + let (field_type, field_con) = + constrain_field(constraints, env, field_var, &*loc_field_expr); field_vars.push(field_var); field_exprs.insert(label.clone(), loc_field_expr); field_types.insert(label.clone(), RecordField::Required(field_type)); - constraints.push(field_con); + rec_constraints.push(field_con); } let record_type = Type::Record( @@ -134,11 +128,17 @@ pub fn constrain_expr( // lifetime parameter on `Type` Box::new(Type::EmptyRec), ); - let record_con = Eq(record_type, expected.clone(), Category::Record, region); - constraints.push(record_con); + let record_con = constraints.equal_types( + record_type, + expected.clone(), + Category::Record, + region, + ); + + rec_constraints.push(record_con); // variable to store in the AST - let stored_con = Eq( + let stored_con = constraints.equal_types( Type::Variable(*record_var), expected, Category::Storage(std::file!(), std::line!()), @@ -146,9 +146,10 @@ pub fn constrain_expr( ); field_vars.push(*record_var); - constraints.push(stored_con); + rec_constraints.push(stored_con); - exists(field_vars, And(constraints)) + let and_constraint = constraints.and_constraint(rec_constraints); + constraints.exists(field_vars, and_constraint) } } Update { @@ -162,6 +163,7 @@ pub fn constrain_expr( let mut cons = Vec::with_capacity(updates.len() + 1); for (field_name, Field { var, loc_expr, .. }) in updates.clone() { let (var, tipe, con) = constrain_field_update( + constraints, env, var, loc_expr.region, @@ -177,18 +179,19 @@ pub fn constrain_expr( let record_type = Type::Variable(*record_var); // NOTE from elm compiler: fields_type is separate so that Error propagates better - let fields_con = Eq( + let fields_con = constraints.equal_types( record_type.clone(), NoExpectation(fields_type), Category::Record, region, ); - let record_con = Eq(record_type.clone(), expected, Category::Record, region); + let record_con = + constraints.equal_types(record_type.clone(), expected, Category::Record, region); vars.push(*record_var); vars.push(*ext_var); - let con = Lookup( + let con = constraints.lookup( *symbol, ForReason( Reason::RecordUpdateKeys( @@ -209,45 +212,55 @@ pub fn constrain_expr( cons.insert(1, con); cons.insert(2, record_con); - exists(vars, And(cons)) + let and_constraint = constraints.and_constraint(cons); + constraints.exists(vars, and_constraint) } - Str(_) => Eq(str_type(), expected, Category::Str, region), - SingleQuote(_) => Eq(num_u32(), expected, Category::Character, region), + Str(_) => constraints.equal_types(str_type(), expected, Category::Str, region), + SingleQuote(_) => constraints.equal_types(num_u32(), expected, Category::Character, region), List { elem_var, loc_elems, } => { if loc_elems.is_empty() { - exists( - vec![*elem_var], - Eq(empty_list_type(*elem_var), expected, Category::List, region), - ) + let eq = constraints.equal_types( + empty_list_type(*elem_var), + expected, + Category::List, + region, + ); + constraints.exists(vec![*elem_var], eq) } else { let list_elem_type = Type::Variable(*elem_var); - let mut constraints = Vec::with_capacity(1 + loc_elems.len()); + let mut list_constraints = Vec::with_capacity(1 + loc_elems.len()); for (index, loc_elem) in loc_elems.iter().enumerate() { let elem_expected = ForReason( Reason::ElemInList { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, list_elem_type.clone(), loc_elem.region, ); - let constraint = - constrain_expr(env, loc_elem.region, &loc_elem.value, elem_expected); + let constraint = constrain_expr( + constraints, + env, + loc_elem.region, + &loc_elem.value, + elem_expected, + ); - constraints.push(constraint); + list_constraints.push(constraint); } - constraints.push(Eq( + list_constraints.push(constraints.equal_types( list_type(list_elem_type), expected, Category::List, region, )); - exists(vec![*elem_var], And(constraints)) + let and_constraint = constraints.and_constraint(list_constraints); + constraints.exists([*elem_var], and_constraint) } } Call(boxed, loc_args, called_via) => { @@ -269,7 +282,8 @@ pub fn constrain_expr( arity: loc_args.len() as u8, }; - let fn_con = constrain_expr(env, loc_fn.region, &loc_fn.value, fn_expected); + let fn_con = + constrain_expr(constraints, env, loc_fn.region, &loc_fn.value, fn_expected); // The function's return type let ret_type = Variable(*ret_var); @@ -293,10 +307,16 @@ pub fn constrain_expr( let reason = Reason::FnArg { name: opt_symbol, - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), region); - let arg_con = constrain_expr(env, loc_arg.region, &loc_arg.value, expected_arg); + let arg_con = constrain_expr( + constraints, + env, + loc_arg.region, + &loc_arg.value, + expected_arg, + ); vars.push(*arg_var); arg_types.push(arg_type); @@ -315,19 +335,19 @@ pub fn constrain_expr( let category = Category::CallResult(opt_symbol, *called_via); - exists( - vars, - And(vec![ - fn_con, - Eq(fn_type, expected_fn_type, category.clone(), fn_region), - And(arg_cons), - Eq(ret_type, expected, category, region), - ]), - ) + let and_cons = [ + fn_con, + constraints.equal_types(fn_type, expected_fn_type, category.clone(), fn_region), + constraints.and_constraint(arg_cons), + constraints.equal_types(ret_type, expected, category, region), + ]; + + let and_constraint = constraints.and_constraint(and_cons); + constraints.exists(vars, and_constraint) } Var(symbol) => { // make lookup constraint to lookup this symbol's type in the environment - Lookup(*symbol, expected, region) + constraints.lookup(*symbol, expected, region) } Closure(ClosureData { function_type: fn_var, @@ -349,8 +369,13 @@ pub fn constrain_expr( let closure_type = Type::Variable(closure_var); let return_type = Type::Variable(ret_var); - let (mut vars, pattern_state, function_type) = - constrain_untyped_args(env, arguments, closure_type, return_type.clone()); + let (mut vars, pattern_state, function_type) = constrain_untyped_args( + constraints, + env, + arguments, + closure_type, + return_type.clone(), + ); vars.push(ret_var); vars.push(closure_var); @@ -358,8 +383,13 @@ pub fn constrain_expr( vars.push(*fn_var); let body_type = NoExpectation(return_type); - let ret_constraint = - constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type); + let ret_constraint = constrain_expr( + constraints, + env, + loc_body_expr.region, + &loc_body_expr.value, + body_type, + ); // make sure the captured symbols are sorted! debug_assert_eq!(captured_symbols.clone(), { @@ -369,6 +399,7 @@ pub fn constrain_expr( }); let closure_constraint = constrain_closure_size( + constraints, *name, region, captured_symbols, @@ -377,28 +408,28 @@ pub fn constrain_expr( &mut vars, ); - exists( - vars, - And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: pattern_state.vars, - def_types: pattern_state.headers, - defs_constraint: And(pattern_state.constraints), - ret_constraint, - })), - // "the closure's type is equal to expected type" - Eq(function_type.clone(), expected, Category::Lambda, region), - // "fn_var is equal to the closure's type" - fn_var is used in code gen - Eq( - Type::Variable(*fn_var), - NoExpectation(function_type), - Category::Storage(std::file!(), std::line!()), - region, - ), - closure_constraint, - ]), - ) + let pattern_state_constraints = constraints.and_constraint(pattern_state.constraints); + let cons = [ + constraints.let_constraint( + [], + pattern_state.vars, + pattern_state.headers, + pattern_state_constraints, + ret_constraint, + ), + // "the closure's type is equal to expected type" + constraints.equal_types(function_type.clone(), expected, Category::Lambda, region), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + constraints.equal_types( + Type::Variable(*fn_var), + NoExpectation(function_type), + Category::Storage(std::file!(), std::line!()), + region, + ), + closure_constraint, + ]; + + constraints.exists_many(vars, cons) } Expect(loc_cond, continuation) => { @@ -408,16 +439,22 @@ pub fn constrain_expr( }; let cond_con = constrain_expr( + constraints, env, loc_cond.region, &loc_cond.value, expect_bool(loc_cond.region), ); - let continuation_con = - constrain_expr(env, continuation.region, &continuation.value, expected); + let continuation_con = constrain_expr( + constraints, + env, + continuation.region, + &continuation.value, + expected, + ); - exists(vec![], And(vec![cond_con, continuation_con])) + constraints.exists_many([], [cond_con, continuation_con]) } If { @@ -434,7 +471,7 @@ pub fn constrain_expr( // TODO why does this cond var exist? is it for error messages? let first_cond_region = branches[0].0.region; - let cond_var_is_bool_con = Eq( + let cond_var_is_bool_con = constraints.equal_types( Type::Variable(*cond_var), expect_bool(first_cond_region), Category::If, @@ -448,6 +485,7 @@ pub fn constrain_expr( let num_branches = branches.len() + 1; for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { let cond_con = constrain_expr( + constraints, env, loc_cond.region, &loc_cond.value, @@ -455,6 +493,7 @@ pub fn constrain_expr( ); let then_con = constrain_expr( + constraints, env, loc_body.region, &loc_body.value, @@ -462,7 +501,7 @@ pub fn constrain_expr( name.clone(), arity, AnnotationSource::TypedIfBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), num_branches, region: ann_source.region(), }, @@ -475,6 +514,7 @@ pub fn constrain_expr( } let else_con = constrain_expr( + constraints, env, final_else.region, &final_else.value, @@ -482,7 +522,7 @@ pub fn constrain_expr( name, arity, AnnotationSource::TypedIfBranch { - index: Index::zero_based(branches.len()), + index: HumanIndex::zero_based(branches.len()), num_branches, region: ann_source.region(), }, @@ -490,7 +530,7 @@ pub fn constrain_expr( ), ); - let ast_con = Eq( + let ast_con = constraints.equal_types( Type::Variable(*branch_var), NoExpectation(tipe), Category::Storage(std::file!(), std::line!()), @@ -500,11 +540,12 @@ pub fn constrain_expr( branch_cons.push(ast_con); branch_cons.push(else_con); - exists(vec![*cond_var, *branch_var], And(branch_cons)) + constraints.exists_many([*cond_var, *branch_var], branch_cons) } _ => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { let cond_con = constrain_expr( + constraints, env, loc_cond.region, &loc_cond.value, @@ -512,12 +553,13 @@ pub fn constrain_expr( ); let then_con = constrain_expr( + constraints, env, loc_body.region, &loc_body.value, ForReason( Reason::IfBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), total_branches: branches.len(), }, Type::Variable(*branch_var), @@ -529,12 +571,13 @@ pub fn constrain_expr( branch_cons.push(then_con); } let else_con = constrain_expr( + constraints, env, final_else.region, &final_else.value, ForReason( Reason::IfBranch { - index: Index::zero_based(branches.len()), + index: HumanIndex::zero_based(branches.len()), total_branches: branches.len() + 1, }, Type::Variable(*branch_var), @@ -542,7 +585,7 @@ pub fn constrain_expr( ), ); - branch_cons.push(Eq( + branch_cons.push(constraints.equal_types( Type::Variable(*branch_var), expected, Category::Storage(std::file!(), std::line!()), @@ -550,7 +593,7 @@ pub fn constrain_expr( )); branch_cons.push(else_con); - exists(vec![*cond_var, *branch_var], And(branch_cons)) + constraints.exists_many([*cond_var, *branch_var], branch_cons) } } } @@ -565,14 +608,15 @@ pub fn constrain_expr( let cond_var = *cond_var; let cond_type = Variable(cond_var); let expr_con = constrain_expr( + constraints, env, region, &loc_cond.value, NoExpectation(cond_type.clone()), ); - let mut constraints = Vec::with_capacity(branches.len() + 1); - constraints.push(expr_con); + let mut branch_constraints = Vec::with_capacity(branches.len() + 1); + branch_constraints.push(expr_con); match &expected { FromAnnotation(name, arity, ann_source, _typ) => { @@ -587,12 +631,13 @@ pub fn constrain_expr( Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( + constraints, env, when_branch.value.region, when_branch, PExpected::ForReason( PReason::WhenMatch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, cond_type.clone(), pattern_region, @@ -601,19 +646,24 @@ pub fn constrain_expr( name.clone(), *arity, AnnotationSource::TypedWhenBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), region: ann_source.region(), }, typ.clone(), ), ); - constraints.push(branch_con); + branch_constraints.push(branch_con); } - constraints.push(Eq(typ, expected, Category::When, region)); + branch_constraints.push(constraints.equal_types( + typ, + expected, + Category::When, + region, + )); - return exists(vec![cond_var, *expr_var], And(constraints)); + return constraints.exists_many([cond_var, *expr_var], branch_constraints); } _ => { @@ -624,19 +674,20 @@ pub fn constrain_expr( let pattern_region = Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( + constraints, env, region, when_branch, PExpected::ForReason( PReason::WhenMatch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, cond_type.clone(), pattern_region, ), ForReason( Reason::WhenBranch { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, branch_type.clone(), when_branch.value.region, @@ -646,20 +697,26 @@ pub fn constrain_expr( branch_cons.push(branch_con); } - constraints.push(And(vec![ - // Record the original conditional expression's constraint. - // Each branch's pattern must have the same type - // as the condition expression did. - And(branch_cons), - // The return type of each branch must equal - // the return type of the entire when-expression. - Eq(branch_type, expected, Category::When, region), - ])); + // Deviation: elm adds another layer of And nesting + // + // Record the original conditional expression's constraint. + // Each branch's pattern must have the same type + // as the condition expression did. + // + // The return type of each branch must equal the return type of + // the entire when-expression. + branch_cons.push(constraints.equal_types( + branch_type, + expected, + Category::When, + region, + )); + branch_constraints.push(constraints.and_constraint(branch_cons)); } } // exhautiveness checking happens when converting to mono::Expr - exists(vec![cond_var, *expr_var], And(constraints)) + constraints.exists_many([cond_var, *expr_var], branch_constraints) } Access { record_var, @@ -683,7 +740,7 @@ pub fn constrain_expr( let category = Category::Access(field.clone()); - let record_con = Eq( + let record_con = constraints.equal_types( Type::Variable(*record_var), record_expected.clone(), category.clone(), @@ -691,22 +748,20 @@ pub fn constrain_expr( ); let constraint = constrain_expr( + constraints, &Env { home: env.home, - rigids: ImMap::default(), + rigids: MutMap::default(), }, region, &loc_expr.value, record_expected, ); - exists( - vec![*record_var, field_var, ext_var], - And(vec![ - constraint, - Eq(field_type, expected, category, region), - record_con, - ]), + let eq = constraints.equal_types(field_type, expected, category, region); + constraints.exists_many( + [*record_var, field_var, ext_var], + [constraint, eq, record_con], ) } Accessor { @@ -731,7 +786,7 @@ pub fn constrain_expr( let category = Category::Accessor(field.clone()); let record_expected = Expected::NoExpectation(record_type.clone()); - let record_con = Eq( + let record_con = constraints.equal_types( Type::Variable(*record_var), record_expected, category.clone(), @@ -749,37 +804,44 @@ pub fn constrain_expr( Box::new(field_type), ); - exists( - vec![*record_var, *function_var, *closure_var, field_var, ext_var], - And(vec![ - Eq(function_type.clone(), expected, category.clone(), region), - Eq( - function_type, - NoExpectation(Variable(*function_var)), - category, - region, - ), - record_con, - ]), + let cons = [ + constraints.equal_types(function_type.clone(), expected, category.clone(), region), + constraints.equal_types( + function_type, + NoExpectation(Variable(*function_var)), + category, + region, + ), + record_con, + ]; + + constraints.exists_many( + [*record_var, *function_var, *closure_var, field_var, ext_var], + cons, ) } LetRec(defs, loc_ret, var) => { - let body_con = constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone()); + let body_con = constrain_expr( + constraints, + env, + loc_ret.region, + &loc_ret.value, + expected.clone(), + ); - exists( - vec![*var], - And(vec![ - constrain_recursive_defs(env, defs, body_con), - // Record the type of tne entire def-expression in the variable. - // Code gen will need that later! - Eq( - Type::Variable(*var), - expected, - Category::Storage(std::file!(), std::line!()), - loc_ret.region, - ), - ]), - ) + let cons = [ + constrain_recursive_defs(constraints, env, defs, body_con), + // Record the type of tne entire def-expression in the variable. + // Code gen will need that later! + constraints.equal_types( + Type::Variable(*var), + expected, + Category::Storage(std::file!(), std::line!()), + loc_ret.region, + ), + ]; + + constraints.exists_many([*var], cons) } LetNonRec(def, loc_ret, var) => { let mut stack = Vec::with_capacity(1); @@ -793,24 +855,28 @@ pub fn constrain_expr( loc_ret = new_loc_ret; } - let mut body_con = - constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone()); + let mut body_con = constrain_expr( + constraints, + env, + loc_ret.region, + &loc_ret.value, + expected.clone(), + ); while let Some((def, var, ret_region)) = stack.pop() { - body_con = exists( - vec![*var], - And(vec![ - constrain_def(env, def, body_con), - // Record the type of the entire def-expression in the variable. - // Code gen will need that later! - Eq( - Type::Variable(*var), - expected.clone(), - Category::Storage(std::file!(), std::line!()), - ret_region, - ), - ]), - ) + let cons = [ + constrain_def(constraints, env, def, body_con), + // Record the type of the entire def-expression in the variable. + // Code gen will need that later! + constraints.equal_types( + Type::Variable(*var), + expected.clone(), + Category::Storage(std::file!(), std::line!()), + ret_region, + ), + ]; + + body_con = constraints.exists_many([*var], cons) } body_con @@ -827,6 +893,7 @@ pub fn constrain_expr( for (var, loc_expr) in arguments { let arg_con = constrain_expr( + constraints, env, loc_expr.region, &loc_expr.value, @@ -838,7 +905,7 @@ pub fn constrain_expr( types.push(Type::Variable(*var)); } - let union_con = Eq( + let union_con = constraints.equal_types( Type::TagUnion( vec![(name.clone(), types)], Box::new(Type::Variable(*ext_var)), @@ -850,7 +917,7 @@ pub fn constrain_expr( }, region, ); - let ast_con = Eq( + let ast_con = constraints.equal_types( Type::Variable(*variant_var), expected, Category::Storage(std::file!(), std::line!()), @@ -862,7 +929,7 @@ pub fn constrain_expr( arg_cons.push(union_con); arg_cons.push(ast_con); - exists(vars, And(arg_cons)) + constraints.exists_many(vars, arg_cons) } ZeroArgumentTag { variant_var, @@ -877,6 +944,7 @@ pub fn constrain_expr( for (var, loc_expr) in arguments { let arg_con = constrain_expr( + constraints, env, loc_expr.region, &loc_expr.value, @@ -888,7 +956,7 @@ pub fn constrain_expr( types.push(Type::Variable(*var)); } - let union_con = Eq( + let union_con = constraints.equal_types( Type::FunctionOrTagUnion( name.clone(), *closure_name, @@ -901,7 +969,7 @@ pub fn constrain_expr( }, region, ); - let ast_con = Eq( + let ast_con = constraints.equal_types( Type::Variable(*variant_var), expected, Category::Storage(std::file!(), std::line!()), @@ -913,7 +981,7 @@ pub fn constrain_expr( arg_cons.push(union_con); arg_cons.push(ast_con); - exists(vars, And(arg_cons)) + constraints.exists_many(vars, arg_cons) } OpaqueRef { @@ -937,6 +1005,7 @@ pub fn constrain_expr( // Constrain the argument let arg_con = constrain_expr( + constraints, env, arg_loc_expr.region, &arg_loc_expr.value, @@ -945,7 +1014,7 @@ pub fn constrain_expr( // Link the entire wrapped opaque type (with the now-constrained argument) to the // expected type - let opaque_con = Eq( + let opaque_con = constraints.equal_types( opaque_type, expected.clone(), Category::OpaqueWrap(*name), @@ -955,7 +1024,7 @@ pub fn constrain_expr( // Link the entire wrapped opaque type (with the now-constrained argument) to the type // variables of the opaque type // TODO: better expectation here - let link_type_variables_con = Eq( + let link_type_variables_con = constraints.equal_types( arg_type, Expected::NoExpectation((**specialized_def_type).clone()), Category::OpaqueArg, @@ -963,7 +1032,7 @@ pub fn constrain_expr( ); // Store the entire wrapped opaque type in `opaque_var` - let storage_con = Eq( + let storage_con = constraints.equal_types( Type::Variable(*opaque_var), expected, Category::Storage(std::file!(), std::line!()), @@ -979,14 +1048,9 @@ pub fn constrain_expr( v.0.expect_variable("all lambda sets should be fresh variables here") })); - exists( + constraints.exists_many( vars, - And(vec![ - arg_con, - opaque_con, - link_type_variables_con, - storage_con, - ]), + [arg_con, opaque_con, link_type_variables_con, storage_con], ) } @@ -1007,10 +1071,10 @@ pub fn constrain_expr( let mut add_arg = |index, arg_type: Type, arg| { let reason = Reason::LowLevelOpArg { op: *op, - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); - let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg); + let arg_con = constrain_expr(constraints, env, Region::zero(), arg, expected_arg); arg_types.push(arg_type); arg_cons.push(arg_con); @@ -1024,13 +1088,10 @@ pub fn constrain_expr( let category = Category::LowLevelOpResult(*op); - exists( - vars, - And(vec![ - And(arg_cons), - Eq(ret_type, expected, category, region), - ]), - ) + // Deviation: elm uses an additional And here + let eq = constraints.equal_types(ret_type, expected, category, region); + arg_cons.push(eq); + constraints.exists_many(vars, arg_cons) } ForeignCall { args, @@ -1053,10 +1114,10 @@ pub fn constrain_expr( let mut add_arg = |index, arg_type: Type, arg| { let reason = Reason::ForeignCallArg { foreign_symbol: foreign_symbol.clone(), - arg_index: Index::zero_based(index), + arg_index: HumanIndex::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), Region::zero()); - let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg); + let arg_con = constrain_expr(constraints, env, Region::zero(), arg, expected_arg); arg_types.push(arg_type); arg_cons.push(arg_con); @@ -1070,30 +1131,34 @@ pub fn constrain_expr( let category = Category::ForeignCall; - exists( - vars, - And(vec![ - And(arg_cons), - Eq(ret_type, expected, category, region), - ]), - ) + // Deviation: elm uses an additional And here + let eq = constraints.equal_types(ret_type, expected, category, region); + arg_cons.push(eq); + constraints.exists_many(vars, arg_cons) } RuntimeError(_) => { // Runtime Errors have no constraints because they're going to crash. - True + Constraint::True } } } #[inline(always)] fn constrain_when_branch( + constraints: &mut Constraints, env: &Env, region: Region, when_branch: &WhenBranch, pattern_expected: PExpected, expr_expected: Expected, ) -> Constraint { - let ret_constraint = constrain_expr(env, region, &when_branch.value.value, expr_expected); + let ret_constraint = constrain_expr( + constraints, + env, + region, + &when_branch.value.value, + expr_expected, + ); let mut state = PatternState { headers: SendMap::default(), @@ -1105,6 +1170,7 @@ fn constrain_when_branch( // then unify that variable with the expectation? for loc_pattern in &when_branch.patterns { constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1115,6 +1181,7 @@ fn constrain_when_branch( if let Some(loc_guard) = &when_branch.guard { let guard_constraint = constrain_expr( + constraints, env, region, &loc_guard.value, @@ -1126,51 +1193,75 @@ fn constrain_when_branch( ); // must introduce the headers from the pattern before constraining the guard - Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Constraint::And(state.constraints), - ret_constraint: Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: Vec::new(), - def_types: SendMap::default(), - defs_constraint: guard_constraint, - ret_constraint, - })), - })) - } else { - Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Constraint::And(state.constraints), + let state_constraints = constraints.and_constraint(state.constraints); + let inner = constraints.let_constraint( + [], + [], + SendMap::default(), + guard_constraint, ret_constraint, - })) + ); + + constraints.let_constraint([], state.vars, state.headers, state_constraints, inner) + } else { + let state_constraints = constraints.and_constraint(state.constraints); + constraints.let_constraint( + [], + state.vars, + state.headers, + state_constraints, + ret_constraint, + ) } } -fn constrain_field(env: &Env, field_var: Variable, loc_expr: &Loc) -> (Type, Constraint) { +fn constrain_field( + constraints: &mut Constraints, + env: &Env, + field_var: Variable, + loc_expr: &Loc, +) -> (Type, Constraint) { let field_type = Variable(field_var); let field_expected = NoExpectation(field_type.clone()); - let constraint = constrain_expr(env, loc_expr.region, &loc_expr.value, field_expected); + let constraint = constrain_expr( + constraints, + env, + loc_expr.region, + &loc_expr.value, + field_expected, + ); (field_type, constraint) } #[inline(always)] -fn constrain_empty_record(region: Region, expected: Expected) -> Constraint { - Eq(EmptyRec, expected, Category::Record, region) +fn constrain_empty_record( + constraints: &mut Constraints, + region: Region, + expected: Expected, +) -> Constraint { + let expected_index = constraints.push_expected_type(expected); + + Constraint::Eq( + Constraints::EMPTY_RECORD, + expected_index, + Constraints::CATEGORY_RECORD, + region, + ) } /// Constrain top-level module declarations #[inline(always)] -pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { +pub fn constrain_decls( + constraints: &mut Constraints, + home: ModuleId, + decls: &[Declaration], +) -> Constraint { let mut constraint = Constraint::SaveTheEnvironment; let mut env = Env { home, - rigids: ImMap::default(), + rigids: MutMap::default(), }; for decl in decls.iter().rev() { @@ -1180,10 +1271,10 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { match decl { Declaration::Declare(def) | Declaration::Builtin(def) => { - constraint = constrain_def(&env, def, constraint); + constraint = constrain_def(constraints, &env, def, constraint); } Declaration::DeclareRec(defs) => { - constraint = constrain_recursive_defs(&env, defs, constraint); + constraint = constrain_recursive_defs(constraints, &env, defs, constraint); } Declaration::InvalidCycle(_) => { // invalid cycles give a canonicalization error. we skip them here. @@ -1193,12 +1284,17 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint { } // this assert make the "root" of the constraint wasn't dropped - debug_assert!(constraint.contains_save_the_environment()); + debug_assert!(constraints.contains_save_the_environment(&constraint)); constraint } -fn constrain_def_pattern(env: &Env, loc_pattern: &Loc, expr_type: Type) -> PatternState { +fn constrain_def_pattern( + constraints: &mut Constraints, + env: &Env, + loc_pattern: &Loc, + expr_type: Type, +) -> PatternState { let pattern_expected = PExpected::NoExpectation(expr_type); let mut state = PatternState { @@ -1208,6 +1304,7 @@ fn constrain_def_pattern(env: &Env, loc_pattern: &Loc, expr_type: Type) }; constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1218,28 +1315,35 @@ fn constrain_def_pattern(env: &Env, loc_pattern: &Loc, expr_type: Type) state } -fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { +fn constrain_def( + constraints: &mut Constraints, + env: &Env, + def: &Def, + body_con: Constraint, +) -> Constraint { let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); - let mut def_pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone()); + let mut def_pattern_state = + constrain_def_pattern(constraints, env, &def.loc_pattern, expr_type.clone()); def_pattern_state.vars.push(expr_var); - let mut new_rigids = Vec::new(); - - let expr_con = match &def.annotation { + match &def.annotation { Some(annotation) => { let arity = annotation.signature.arity(); let rigids = &env.rigids; let mut ftv = rigids.clone(); - let signature = instantiate_rigids( + let InstantiateRigids { + signature, + new_rigid_variables, + new_infer_variables, + } = instantiate_rigids( &annotation.signature, &annotation.introduced_variables, - &mut new_rigids, - &mut ftv, &def.loc_pattern, + &mut ftv, &mut def_pattern_state.headers, ); @@ -1257,7 +1361,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { signature.clone(), ); - def_pattern_state.constraints.push(Eq( + def_pattern_state.constraints.push(constraints.equal_types( expr_type, annotation_expected.clone(), Category::Storage(std::file!(), std::line!()), @@ -1320,7 +1424,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { let pattern_expected = PExpected::ForReason( PReason::TypedArg { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), opt_name: opt_label, }, pattern_type.clone(), @@ -1328,6 +1432,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { ); constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1342,7 +1447,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { def_pattern_state.vars.push(*pattern_var); pattern_types.push(Type::Variable(*pattern_var)); - let pattern_con = Eq( + let pattern_con = constraints.equal_types( Type::Variable(*pattern_var), Expected::NoExpectation(loc_ann.clone()), Category::Storage(std::file!(), std::line!()), @@ -1354,6 +1459,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { } let closure_constraint = constrain_closure_size( + constraints, *name, region, captured_symbols, @@ -1371,91 +1477,143 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { ret_type.clone(), ); - let ret_constraint = - constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type); + let ret_constraint = constrain_expr( + constraints, + env, + loc_body_expr.region, + &loc_body_expr.value, + body_type, + ); vars.push(*fn_var); - let defs_constraint = And(state.constraints); + let defs_constraint = constraints.and_constraint(state.constraints); - exists( - vars, - And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint, - ret_constraint, - })), - Eq( - Type::Variable(closure_var), - Expected::FromAnnotation( - def.loc_pattern.clone(), - arity, - AnnotationSource::TypedBody { - region: annotation.region, - }, - *signature_closure_type.clone(), - ), - Category::ClosureSize, - region, + let cons = [ + constraints.let_constraint( + [], + state.vars, + state.headers, + defs_constraint, + ret_constraint, + ), + constraints.equal_types( + Type::Variable(closure_var), + Expected::FromAnnotation( + def.loc_pattern.clone(), + arity, + AnnotationSource::TypedBody { + region: annotation.region, + }, + *signature_closure_type.clone(), ), - Store(signature.clone(), *fn_var, std::file!(), std::line!()), - Store(signature, expr_var, std::file!(), std::line!()), - Store(ret_type, ret_var, std::file!(), std::line!()), - closure_constraint, - ]), + Category::ClosureSize, + region, + ), + constraints.store(signature.clone(), *fn_var, std::file!(), std::line!()), + constraints.store(signature, expr_var, std::file!(), std::line!()), + constraints.store(ret_type, ret_var, std::file!(), std::line!()), + closure_constraint, + ]; + + let expr_con = constraints.exists_many(vars, cons); + + constrain_def_make_constraint( + constraints, + new_rigid_variables, + new_infer_variables, + expr_con, + body_con, + def_pattern_state, ) } _ => { let expected = annotation_expected; - let ret_constraint = - constrain_expr(env, def.loc_expr.region, &def.loc_expr.value, expected); + let ret_constraint = constrain_expr( + constraints, + env, + def.loc_expr.region, + &def.loc_expr.value, + expected, + ); - And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: vec![], - def_types: SendMap::default(), - defs_constraint: True, + let cons = [ + constraints.let_constraint( + [], + [], + SendMap::default(), + Constraint::True, ret_constraint, - })), + ), // Store type into AST vars. We use Store so errors aren't reported twice - Store(signature, expr_var, std::file!(), std::line!()), - ]) + constraints.store(signature, expr_var, std::file!(), std::line!()), + ]; + let expr_con = constraints.and_constraint(cons); + + constrain_def_make_constraint( + constraints, + new_rigid_variables, + new_infer_variables, + expr_con, + body_con, + def_pattern_state, + ) } } } None => { // no annotation, so no extra work with rigids - constrain_expr( + let expr_con = constrain_expr( + constraints, env, def.loc_expr.region, &def.loc_expr.value, NoExpectation(expr_type), + ); + + constrain_def_make_constraint( + constraints, + vec![], + vec![], + expr_con, + body_con, + def_pattern_state, ) } - }; + } +} - Let(Box::new(LetConstraint { - rigid_vars: new_rigids, - flex_vars: def_pattern_state.vars, - def_types: def_pattern_state.headers, - defs_constraint: Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), // always empty - flex_vars: Vec::new(), // empty, because our functions have no arguments - def_types: SendMap::default(), // empty, because our functions have no arguments! - defs_constraint: And(def_pattern_state.constraints), - ret_constraint: expr_con, - })), - ret_constraint: body_con, - })) +fn constrain_def_make_constraint( + constraints: &mut Constraints, + new_rigid_variables: Vec, + new_infer_variables: Vec, + expr_con: Constraint, + body_con: Constraint, + def_pattern_state: PatternState, +) -> Constraint { + let and_constraint = constraints.and_constraint(def_pattern_state.constraints); + + let def_con = constraints.let_constraint( + [], + new_infer_variables, + SendMap::default(), // empty, because our functions have no arguments! + and_constraint, + expr_con, + ); + + constraints.let_constraint( + new_rigid_variables, + def_pattern_state.vars, + def_pattern_state.headers, + def_con, + body_con, + ) } fn constrain_closure_size( + constraints: &mut Constraints, name: Symbol, region: Region, captured_symbols: &[(Symbol, Variable)], @@ -1477,7 +1635,7 @@ fn constrain_closure_size( tag_arguments.push(Type::Variable(*var)); // make the variable equal to the looked-up type of symbol - captured_symbols_constraints.push(Constraint::Lookup( + captured_symbols_constraints.push(constraints.lookup( *symbol, Expected::NoExpectation(Type::Variable(*var)), Region::zero(), @@ -1498,7 +1656,7 @@ fn constrain_closure_size( ) }; - let finalizer = Eq( + let finalizer = constraints.equal_types( Type::Variable(closure_var), NoExpectation(closure_type), Category::ClosureSize, @@ -1507,60 +1665,79 @@ fn constrain_closure_size( captured_symbols_constraints.push(finalizer); - Constraint::And(captured_symbols_constraints) + constraints.and_constraint(captured_symbols_constraints) +} + +pub struct InstantiateRigids { + pub signature: Type, + pub new_rigid_variables: Vec, + pub new_infer_variables: Vec, } fn instantiate_rigids( annotation: &Type, introduced_vars: &IntroducedVariables, - new_rigids: &mut Vec, - ftv: &mut ImMap, // rigids defined before the current annotation loc_pattern: &Loc, + ftv: &mut MutMap, // rigids defined before the current annotation headers: &mut SendMap>, -) -> Type { +) -> InstantiateRigids { let mut annotation = annotation.clone(); + let mut new_rigid_variables = Vec::new(); + let mut rigid_substitution: ImMap = ImMap::default(); - - let outside_rigids: MutSet = ftv.values().copied().collect(); - for (name, var) in introduced_vars.var_by_name.iter() { - if let Some(existing_rigid) = ftv.get(name) { - rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); - } else { - // It's possible to use this rigid in nested defs - ftv.insert(name.clone(), *var); + use std::collections::hash_map::Entry::*; + + match ftv.entry(name.clone()) { + Occupied(occupied) => { + let existing_rigid = occupied.get(); + rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); + } + Vacant(vacant) => { + // It's possible to use this rigid in nested defs + vacant.insert(*var); + new_rigid_variables.push(*var); + } } } + // wildcards are always freshly introduced in this annotation + new_rigid_variables.extend(introduced_vars.wildcards.iter().copied()); + + // lambda set vars are always freshly introduced in this annotation + new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied()); + + let new_infer_variables = introduced_vars.inferred.clone(); + // Instantiate rigid variables if !rigid_substitution.is_empty() { annotation.substitute(&rigid_substitution); } - if let Some(new_headers) = crate::pattern::headers_from_annotation( - &loc_pattern.value, - &Loc::at(loc_pattern.region, annotation.clone()), - ) { - for (symbol, loc_type) in new_headers { - for var in loc_type.value.variables() { - // a rigid is only new if this annotation is the first occurrence of this rigid - if !outside_rigids.contains(&var) { - new_rigids.push(var); - } - } - headers.insert(symbol, loc_type); - } + let loc_annotation_ref = Loc::at(loc_pattern.region, &annotation); + if let Pattern::Identifier(symbol) = loc_pattern.value { + headers.insert(symbol, Loc::at(loc_pattern.region, annotation.clone())); + } else if let Some(new_headers) = + crate::pattern::headers_from_annotation(&loc_pattern.value, &loc_annotation_ref) + { + headers.extend(new_headers) } - for (i, wildcard) in introduced_vars.wildcards.iter().enumerate() { - ftv.insert(format!("*{}", i).into(), *wildcard); + InstantiateRigids { + signature: annotation, + new_rigid_variables, + new_infer_variables, } - - annotation } -fn constrain_recursive_defs(env: &Env, defs: &[Def], body_con: Constraint) -> Constraint { +fn constrain_recursive_defs( + constraints: &mut Constraints, + env: &Env, + defs: &[Def], + body_con: Constraint, +) -> Constraint { rec_defs_help( + constraints, env, defs, body_con, @@ -1570,6 +1747,7 @@ fn constrain_recursive_defs(env: &Env, defs: &[Def], body_con: Constraint) -> Co } pub fn rec_defs_help( + constraints: &mut Constraints, env: &Env, defs: &[Def], body_con: Constraint, @@ -1580,14 +1758,15 @@ pub fn rec_defs_help( let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); - let mut def_pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone()); + let mut def_pattern_state = + constrain_def_pattern(constraints, env, &def.loc_pattern, expr_type.clone()); def_pattern_state.vars.push(expr_var); - let mut new_rigids = Vec::new(); match &def.annotation { None => { let expr_con = constrain_expr( + constraints, env, def.loc_expr.region, &def.loc_expr.value, @@ -1595,13 +1774,13 @@ pub fn rec_defs_help( ); // TODO investigate if this let can be safely removed - let def_con = Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: Vec::new(), // empty because Roc function defs have no args - def_types: SendMap::default(), // empty because Roc function defs have no args - defs_constraint: True, // I think this is correct, once again because there are no args - ret_constraint: expr_con, - })); + let def_con = constraints.let_constraint( + [], + [], // empty because Roc function defs have no args + SendMap::default(), // empty because Roc function defs have no args + Constraint::True, // I think this is correct, once again because there are no args + expr_con, + ); flex_info.vars = def_pattern_state.vars; flex_info.constraints.push(def_con); @@ -1612,15 +1791,20 @@ pub fn rec_defs_help( let arity = annotation.signature.arity(); let mut ftv = env.rigids.clone(); - let signature = instantiate_rigids( + let InstantiateRigids { + signature, + new_rigid_variables, + new_infer_variables, + } = instantiate_rigids( &annotation.signature, &annotation.introduced_variables, - &mut new_rigids, - &mut ftv, &def.loc_pattern, + &mut ftv, &mut def_pattern_state.headers, ); + flex_info.vars.extend(new_infer_variables); + let annotation_expected = FromAnnotation( def.loc_pattern.clone(), arity, @@ -1687,7 +1871,7 @@ pub fn rec_defs_help( let pattern_expected = PExpected::ForReason( PReason::TypedArg { - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), opt_name: opt_label, }, pattern_type.clone(), @@ -1695,6 +1879,7 @@ pub fn rec_defs_help( ); constrain_pattern( + constraints, env, &loc_pattern.value, loc_pattern.region, @@ -1709,7 +1894,7 @@ pub fn rec_defs_help( def_pattern_state.vars.push(*pattern_var); pattern_types.push(Type::Variable(*pattern_var)); - let pattern_con = Eq( + let pattern_con = constraints.equal_types( Type::Variable(*pattern_var), Expected::NoExpectation(loc_ann.clone()), Category::Storage(std::file!(), std::line!()), @@ -1721,6 +1906,7 @@ pub fn rec_defs_help( } let closure_constraint = constrain_closure_size( + constraints, *name, region, captured_symbols, @@ -1736,6 +1922,7 @@ pub fn rec_defs_help( ); let body_type = NoExpectation(ret_type.clone()); let expr_con = constrain_expr( + constraints, env, loc_body_expr.region, &loc_body_expr.value, @@ -1744,64 +1931,81 @@ pub fn rec_defs_help( vars.push(*fn_var); - let def_con = exists( - vars, - And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: And(state.constraints), - ret_constraint: expr_con, - })), - Eq(fn_type.clone(), expected.clone(), Category::Lambda, region), - // "fn_var is equal to the closure's type" - fn_var is used in code gen - // Store type into AST vars. We use Store so errors aren't reported twice - Store(signature.clone(), *fn_var, std::file!(), std::line!()), - Store(signature, expr_var, std::file!(), std::line!()), - Store(ret_type, ret_var, std::file!(), std::line!()), - closure_constraint, - ]), - ); + let state_constraints = constraints.and_constraint(state.constraints); + let cons = [ + constraints.let_constraint( + [], + state.vars, + state.headers, + state_constraints, + expr_con, + ), + constraints.equal_types( + fn_type.clone(), + expected.clone(), + Category::Lambda, + region, + ), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + // Store type into AST vars. We use Store so errors aren't reported twice + constraints.store( + signature.clone(), + *fn_var, + std::file!(), + std::line!(), + ), + constraints.store(signature, expr_var, std::file!(), std::line!()), + constraints.store(ret_type, ret_var, std::file!(), std::line!()), + closure_constraint, + ]; - rigid_info.vars.extend(&new_rigids); + let and_constraint = constraints.and_constraint(cons); + let def_con = constraints.exists(vars, and_constraint); - rigid_info.constraints.push(Let(Box::new(LetConstraint { - rigid_vars: new_rigids, - flex_vars: def_pattern_state.vars, - def_types: SendMap::default(), // no headers introduced (at this level) - defs_constraint: def_con, - ret_constraint: True, - }))); + rigid_info.vars.extend(&new_rigid_variables); + + rigid_info.constraints.push(constraints.let_constraint( + new_rigid_variables, + def_pattern_state.vars, + SendMap::default(), // no headers introduced (at this level) + def_con, + Constraint::True, + )); rigid_info.def_types.extend(def_pattern_state.headers); } _ => { let expected = annotation_expected; - let ret_constraint = - constrain_expr(env, def.loc_expr.region, &def.loc_expr.value, expected); + let ret_constraint = constrain_expr( + constraints, + env, + def.loc_expr.region, + &def.loc_expr.value, + expected, + ); - let def_con = And(vec![ - Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: vec![], - def_types: SendMap::default(), - defs_constraint: True, + let cons = [ + constraints.let_constraint( + [], + [], + SendMap::default(), + Constraint::True, ret_constraint, - })), + ), // Store type into AST vars. We use Store so errors aren't reported twice - Store(signature, expr_var, std::file!(), std::line!()), - ]); + constraints.store(signature, expr_var, std::file!(), std::line!()), + ]; + let def_con = constraints.and_constraint(cons); - rigid_info.vars.extend(&new_rigids); + rigid_info.vars.extend(&new_rigid_variables); - rigid_info.constraints.push(Let(Box::new(LetConstraint { - rigid_vars: new_rigids, - flex_vars: def_pattern_state.vars, - def_types: SendMap::default(), // no headers introduced (at this level) - defs_constraint: def_con, - ret_constraint: True, - }))); + rigid_info.constraints.push(constraints.let_constraint( + new_rigid_variables, + def_pattern_state.vars, + SendMap::default(), // no headers introduced (at this level) + def_con, + Constraint::True, + )); rigid_info.def_types.extend(def_pattern_state.headers); } } @@ -1809,29 +2013,42 @@ pub fn rec_defs_help( } } - Let(Box::new(LetConstraint { - rigid_vars: rigid_info.vars, - flex_vars: Vec::new(), - def_types: rigid_info.def_types, - defs_constraint: True, - ret_constraint: Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: flex_info.vars, - def_types: flex_info.def_types.clone(), - defs_constraint: Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: Vec::new(), - def_types: flex_info.def_types, - defs_constraint: True, - ret_constraint: And(flex_info.constraints), - })), - ret_constraint: And(vec![And(rigid_info.constraints), body_con]), - })), - })) + let flex_constraints = constraints.and_constraint(flex_info.constraints); + let inner_inner = constraints.let_constraint( + [], + [], + flex_info.def_types.clone(), + Constraint::True, + flex_constraints, + ); + + let rigid_constraints = { + let mut temp = rigid_info.constraints; + temp.push(body_con); + + constraints.and_constraint(temp) + }; + + let inner = constraints.let_constraint( + [], + flex_info.vars, + flex_info.def_types, + inner_inner, + rigid_constraints, + ); + + constraints.let_constraint( + rigid_info.vars, + [], + rigid_info.def_types, + Constraint::True, + inner, + ) } #[inline(always)] fn constrain_field_update( + constraints: &mut Constraints, env: &Env, var: Variable, region: Region, @@ -1841,7 +2058,7 @@ fn constrain_field_update( let field_type = Type::Variable(var); let reason = Reason::RecordUpdateValue(field); let expected = ForReason(reason, field_type.clone(), region); - let con = constrain_expr(env, loc_expr.region, &loc_expr.value, expected); + let con = constrain_expr(constraints, env, loc_expr.region, &loc_expr.value, expected); (var, field_type, con) } diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 12975d977a..79640a2d30 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -1,6 +1,5 @@ -use crate::expr::constrain_decls; use roc_builtins::std::StdLib; -use roc_can::constraint::{Constraint, LetConstraint}; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::Declaration; use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_module::symbol::{ModuleId, Symbol}; @@ -17,13 +16,12 @@ pub enum ExposedModuleTypes { Valid(MutMap, MutMap), } -pub struct ConstrainedModule { - pub unused_imports: MutMap, - pub constraint: Constraint, -} - -pub fn constrain_module(declarations: &[Declaration], home: ModuleId) -> Constraint { - constrain_decls(home, declarations) +pub fn constrain_module( + constraints: &mut Constraints, + declarations: &[Declaration], + home: ModuleId, +) -> Constraint { + crate::expr::constrain_decls(constraints, home, declarations) } #[derive(Debug, Clone)] @@ -33,11 +31,11 @@ pub struct Import { } pub fn constrain_imported_values( + constraints: &mut Constraints, imports: Vec, body_con: Constraint, var_store: &mut VarStore, ) -> (Vec, Constraint) { - use Constraint::*; let mut def_types = SendMap::default(); let mut rigid_vars = Vec::new(); @@ -84,24 +82,19 @@ pub fn constrain_imported_values( ( rigid_vars.clone(), - Let(Box::new(LetConstraint { - rigid_vars, - flex_vars: Vec::new(), - def_types, - defs_constraint: True, - ret_constraint: body_con, - })), + constraints.let_constraint(rigid_vars, [], def_types, Constraint::True, body_con), ) } /// Run pre_constrain_imports to get imported_symbols and imported_aliases. pub fn constrain_imports( + constraints: &mut Constraints, imported_symbols: Vec, constraint: Constraint, var_store: &mut VarStore, ) -> Constraint { let (_introduced_rigids, constraint) = - constrain_imported_values(imported_symbols, constraint, var_store); + constrain_imported_values(constraints, imported_symbols, constraint, var_store); // TODO determine what to do with those rigids // for var in introduced_rigids { diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 200f38dc74..0a543728bd 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -1,10 +1,10 @@ use crate::builtins; use crate::expr::{constrain_expr, Env}; -use roc_can::constraint::{Constraint, PresenceConstraint}; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::{DestructType, RecordDestruct}; -use roc_collections::all::{Index, SendMap}; +use roc_collections::all::{HumanIndex, SendMap}; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; @@ -27,7 +27,7 @@ pub struct PatternState { /// definition has an annotation, we instead now add `x => Int`. pub fn headers_from_annotation( pattern: &Pattern, - annotation: &Loc, + annotation: &Loc<&Type>, ) -> Option>> { let mut headers = SendMap::default(); // Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int` @@ -44,12 +44,13 @@ pub fn headers_from_annotation( fn headers_from_annotation_help( pattern: &Pattern, - annotation: &Loc, + annotation: &Loc<&Type>, headers: &mut SendMap>, ) -> bool { match pattern { Identifier(symbol) | Shadowed(_, _, symbol) => { - headers.insert(*symbol, annotation.clone()); + let typ = Loc::at(annotation.region, annotation.value.clone()); + headers.insert(*symbol, typ); true } Underscore @@ -106,7 +107,7 @@ fn headers_from_annotation_help( .all(|(arg_pattern, arg_type)| { headers_from_annotation_help( &arg_pattern.1.value, - &Loc::at(annotation.region, arg_type.clone()), + &Loc::at(annotation.region, arg_type), headers, ) }) @@ -135,12 +136,13 @@ fn headers_from_annotation_help( && type_arguments.len() == pat_type_arguments.len() && lambda_set_variables.len() == pat_lambda_set_variables.len() => { - headers.insert(*opaque, annotation.clone()); + let typ = Loc::at(annotation.region, annotation.value.clone()); + headers.insert(*opaque, typ); let (_, argument_pat) = &**argument; headers_from_annotation_help( &argument_pat.value, - &Loc::at(annotation.region, (**actual).clone()), + &Loc::at(annotation.region, actual), headers, ) } @@ -153,6 +155,7 @@ fn headers_from_annotation_help( /// initialize the Vecs in PatternState using with_capacity /// based on its knowledge of their lengths. pub fn constrain_pattern( + constraints: &mut Constraints, env: &Env, pattern: &Pattern, region: Region, @@ -167,20 +170,18 @@ pub fn constrain_pattern( // A -> "" // _ -> "" // so, we know that "x" (in this case, a tag union) must be open. - state.constraints.push(Constraint::Present( - expected.get_type(), - PresenceConstraint::IsOpen, - )); + state + .constraints + .push(constraints.is_open_type(expected.get_type())); } UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => { // Erroneous patterns don't add any constraints. } Identifier(symbol) | Shadowed(_, _, symbol) => { - state.constraints.push(Constraint::Present( - expected.get_type_ref().clone(), - PresenceConstraint::IsOpen, - )); + state + .constraints + .push(constraints.is_open_type(expected.get_type_ref().clone())); state.headers.insert( *symbol, Loc { @@ -196,6 +197,7 @@ pub fn constrain_pattern( let num_type = builtins::num_num(Type::Variable(var)); let num_type = builtins::add_numeric_bound_constr( + constraints, &mut state.constraints, num_type, bound, @@ -203,11 +205,11 @@ pub fn constrain_pattern( Category::Num, ); - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Num, + state.constraints.push(constraints.equal_pattern_types( num_type, expected, + PatternCategory::Num, + region, )); } @@ -215,6 +217,7 @@ pub fn constrain_pattern( // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. let num_type = builtins::add_numeric_bound_constr( + constraints, &mut state.constraints, Type::Variable(num_var), bound, @@ -225,7 +228,7 @@ pub fn constrain_pattern( // Link the free num var with the int var and our expectation. let int_type = builtins::num_int(Type::Variable(precision_var)); - state.constraints.push(Constraint::Eq( + state.constraints.push(constraints.equal_types( num_type, // TODO check me if something breaks! Expected::NoExpectation(int_type), Category::Int, @@ -233,11 +236,11 @@ pub fn constrain_pattern( )); // Also constrain the pattern against the num var, again to reuse aliases if they're present. - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Int, + state.constraints.push(constraints.equal_pattern_types( Type::Variable(num_var), expected, + PatternCategory::Int, + region, )); } @@ -245,6 +248,7 @@ pub fn constrain_pattern( // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. let num_type = builtins::add_numeric_bound_constr( + constraints, &mut state.constraints, Type::Variable(num_var), bound, @@ -255,7 +259,7 @@ pub fn constrain_pattern( // Link the free num var with the float var and our expectation. let float_type = builtins::num_float(Type::Variable(precision_var)); - state.constraints.push(Constraint::Eq( + state.constraints.push(constraints.equal_types( num_type.clone(), // TODO check me if something breaks! Expected::NoExpectation(float_type), Category::Float, @@ -263,29 +267,29 @@ pub fn constrain_pattern( )); // Also constrain the pattern against the num var, again to reuse aliases if they're present. - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Float, + state.constraints.push(constraints.equal_pattern_types( num_type, // TODO check me if something breaks! expected, + PatternCategory::Float, + region, )); } StrLiteral(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Str, + state.constraints.push(constraints.equal_pattern_types( builtins::str_type(), expected, + PatternCategory::Str, + region, )); } SingleQuote(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Character, + state.constraints.push(constraints.equal_pattern_types( builtins::num_u32(), expected, + PatternCategory::Character, + region, )); } @@ -322,36 +326,39 @@ pub fn constrain_pattern( let field_type = match typ { DestructType::Guard(guard_var, loc_guard) => { - state.constraints.push(Constraint::Present( + state.constraints.push(constraints.pattern_presence( Type::Variable(*guard_var), - PresenceConstraint::Pattern( - region, - PatternCategory::PatternGuard, - PExpected::ForReason( - PReason::PatternGuard, - pat_type.clone(), - loc_guard.region, - ), + PExpected::ForReason( + PReason::PatternGuard, + pat_type.clone(), + loc_guard.region, ), + PatternCategory::PatternGuard, + region, )); state.vars.push(*guard_var); - constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state); + constrain_pattern( + constraints, + env, + &loc_guard.value, + loc_guard.region, + expected, + state, + ); RecordField::Demanded(pat_type) } DestructType::Optional(expr_var, loc_expr) => { - state.constraints.push(Constraint::Present( + state.constraints.push(constraints.pattern_presence( Type::Variable(*expr_var), - PresenceConstraint::Pattern( - region, - PatternCategory::PatternDefault, - PExpected::ForReason( - PReason::OptionalField, - pat_type.clone(), - loc_expr.region, - ), + PExpected::ForReason( + PReason::OptionalField, + pat_type.clone(), + loc_expr.region, ), + PatternCategory::PatternDefault, + region, )); state.vars.push(*expr_var); @@ -362,8 +369,13 @@ pub fn constrain_pattern( loc_expr.region, ); - let expr_con = - constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected); + let expr_con = constrain_expr( + constraints, + env, + loc_expr.region, + &loc_expr.value, + expr_expected, + ); state.constraints.push(expr_con); RecordField::Optional(pat_type) @@ -381,16 +393,18 @@ pub fn constrain_pattern( let record_type = Type::Record(field_types, Box::new(ext_type)); - let whole_con = Constraint::Eq( + let whole_con = constraints.equal_types( Type::Variable(*whole_var), Expected::NoExpectation(record_type), Category::Storage(std::file!(), std::line!()), region, ); - let record_con = Constraint::Present( + let record_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, PatternCategory::Record, expected), + expected, + PatternCategory::Record, + region, ); state.constraints.push(whole_con); @@ -412,29 +426,36 @@ pub fn constrain_pattern( let expected = PExpected::ForReason( PReason::TagArg { tag_name: tag_name.clone(), - index: Index::zero_based(index), + index: HumanIndex::zero_based(index), }, pattern_type, region, ); - constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state); + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + expected, + state, + ); } let pat_category = PatternCategory::Ctor(tag_name.clone()); - let whole_con = Constraint::Present( + let whole_con = constraints.includes_tag( expected.clone().get_type(), - PresenceConstraint::IncludesTag( - tag_name.clone(), - argument_types.clone(), - region, - pat_category.clone(), - ), + tag_name.clone(), + argument_types.clone(), + pat_category.clone(), + region, ); - let tag_con = Constraint::Present( + let tag_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, pat_category, expected), + expected, + pat_category, + region, ); state.vars.push(*whole_var); @@ -466,6 +487,7 @@ pub fn constrain_pattern( // First, add a constraint for the argument "who" let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone()); constrain_pattern( + constraints, env, &loc_arg_pattern.value, loc_arg_pattern.region, @@ -474,7 +496,7 @@ pub fn constrain_pattern( ); // Next, link `whole_var` to the opaque type of "@Id who" - let whole_con = Constraint::Eq( + let whole_con = constraints.equal_types( Type::Variable(*whole_var), Expected::NoExpectation(opaque_type), Category::Storage(std::file!(), std::line!()), @@ -484,7 +506,7 @@ pub fn constrain_pattern( // Link the entire wrapped opaque type (with the now-constrained argument) to the type // variables of the opaque type // TODO: better expectation here - let link_type_variables_con = Constraint::Eq( + let link_type_variables_con = constraints.equal_types( (**specialized_def_type).clone(), Expected::NoExpectation(arg_pattern_type), Category::OpaqueWrap(*opaque), @@ -492,9 +514,11 @@ pub fn constrain_pattern( ); // Next, link `whole_var` (the type of "@Id who") to the expected type - let opaque_pattern_con = Constraint::Present( + let opaque_pattern_con = constraints.pattern_presence( Type::Variable(*whole_var), - PresenceConstraint::Pattern(region, PatternCategory::Opaque(*opaque), expected), + expected, + PatternCategory::Opaque(*opaque), + region, ); state diff --git a/compiler/exhaustive/src/lib.rs b/compiler/exhaustive/src/lib.rs index c80b9b01d0..8ca1c78298 100644 --- a/compiler/exhaustive/src/lib.rs +++ b/compiler/exhaustive/src/lib.rs @@ -1,4 +1,4 @@ -use roc_collections::all::{Index, MutMap}; +use roc_collections::all::{HumanIndex, MutMap}; use roc_module::ident::{Lowercase, TagIdIntType, TagName}; use roc_region::all::Region; use roc_std::RocDec; @@ -70,7 +70,7 @@ pub enum Error { Redundant { overall_region: Region, branch_region: Region, - index: Index, + index: HumanIndex, }, } diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 4380af17e2..4c389f7147 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -293,6 +293,9 @@ impl<'a> Formattable for TypeAnnotation<'a> { SpaceBefore(ann, spaces) => { buf.newline(); + + buf.indent(indent); + fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); ann.format_with_options(buf, parens, Newlines::No, indent) } diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index 1d99edbcd6..23667d9f4c 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -55,7 +55,6 @@ impl<'a> Buf<'a> { pub fn push_str_allow_spaces(&mut self, s: &str) { debug_assert!(!self.beginning_of_line); - debug_assert!(!s.contains('\n')); self.flush_spaces(); diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index a49914abce..edea7957d3 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2984,6 +2984,18 @@ mod test_fmt { )); } + #[test] + fn multiline_higher_order_function() { + expr_formats_same(indoc!( + r#" + foo : + (Str -> Bool) -> Bool + + 42 + "# + )); + } + #[test] /// Test that everything under examples/ is formatted correctly /// If this test fails on your diff, it probably means you need to re-format the examples. diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 96dfaaf58e..26335be9ff 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -7,6 +7,7 @@ license = "UPL-1.0" edition = "2018" [dependencies] +roc_alias_analysis = { path = "../alias_analysis" } roc_collections = { path = "../collections" } roc_module = { path = "../module" } roc_builtins = { path = "../builtins" } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index dd233f6667..6c552ce278 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -13,7 +13,7 @@ use crate::llvm::build_list::{ self, allocate_list, empty_polymorphic_list, list_all, list_any, list_append, list_concat, list_contains, list_drop_at, list_find_unsafe, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map4, - list_map_with_index, list_prepend, list_range, list_repeat, list_reverse, list_set, + list_map_with_index, list_prepend, list_range, list_repeat, list_replace_unsafe, list_reverse, list_single, list_sort_with, list_sublist, list_swap, }; use crate::llvm::build_str::{ @@ -710,7 +710,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>( top_level: ProcLayout<'a>, ) -> (&'static str, FunctionValue<'ctx>) { let it = top_level.arguments.iter().copied(); - let bytes = roc_mono::alias_analysis::func_name_bytes_help(symbol, it, &top_level.result); + let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, &top_level.result); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); @@ -4045,7 +4045,7 @@ pub fn build_proc_headers<'a, 'ctx, 'env>( // Populate Procs further and get the low-level Expr from the canonical Expr let mut headers = Vec::with_capacity_in(procedures.len(), env.arena); for ((symbol, layout), proc) in procedures { - let name_bytes = roc_mono::alias_analysis::func_name_bytes(&proc); + let name_bytes = roc_alias_analysis::func_name_bytes(&proc); let func_name = FuncName(&name_bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); @@ -4110,7 +4110,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( let it = procedures.iter().map(|x| x.1); - let solutions = match roc_mono::alias_analysis::spec_program(opt_level, entry_point, it) { + let solutions = match roc_alias_analysis::spec_program(opt_level, entry_point, it) { Err(e) => panic!("Error in alias analysis: {}", e), Ok(solutions) => solutions, }; @@ -4118,7 +4118,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( let solutions = env.arena.alloc(solutions); let mod_solutions = solutions - .mod_solutions(roc_mono::alias_analysis::MOD_APP) + .mod_solutions(roc_alias_analysis::MOD_APP) .unwrap(); // Add all the Proc headers to the module. @@ -4470,11 +4470,8 @@ pub fn build_proc<'a, 'ctx, 'env>( // * roc__mainForHost_1_Update_result_size() -> i64 let it = top_level.arguments.iter().copied(); - let bytes = roc_mono::alias_analysis::func_name_bytes_help( - symbol, - it, - &top_level.result, - ); + let bytes = + roc_alias_analysis::func_name_bytes_help(symbol, it, &top_level.result); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); @@ -5666,12 +5663,12 @@ fn run_low_level<'a, 'ctx, 'env>( wrapper_struct, ) } - ListSet => { + ListReplaceUnsafe => { let list = load_symbol(scope, &args[0]); let index = load_symbol(scope, &args[1]); let (element, element_layout) = load_symbol_and_layout(scope, &args[2]); - list_set( + list_replace_unsafe( env, layout_ids, list, diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 3f865b77aa..dca6e49466 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -291,52 +291,70 @@ pub fn list_drop_at<'a, 'ctx, 'env>( ) } -/// List.set : List elem, Nat, elem -> List elem -pub fn list_set<'a, 'ctx, 'env>( +/// List.replace_unsafe : List elem, Nat, elem -> { list: List elem, value: elem } +pub fn list_replace_unsafe<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - layout_ids: &mut LayoutIds<'a>, + _layout_ids: &mut LayoutIds<'a>, list: BasicValueEnum<'ctx>, index: IntValue<'ctx>, element: BasicValueEnum<'ctx>, element_layout: &Layout<'a>, update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { - let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout); + let element_type = basic_type_from_layout(env, element_layout); + let element_ptr = env + .builder + .build_alloca(element_type, "output_element_as_opaque"); - let (length, bytes) = load_list( - env.builder, - list.into_struct_value(), - env.context.i8_type().ptr_type(AddressSpace::Generic), - ); - - let new_bytes = match update_mode { - UpdateMode::InPlace => call_bitcode_fn( + // Assume the bounds have already been checked earlier + // (e.g. by List.replace or List.set, which wrap List.#replaceUnsafe) + let new_list = match update_mode { + UpdateMode::InPlace => call_list_bitcode_fn( env, &[ - bytes.into(), + pass_list_cc(env, list), index.into(), pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), - dec_element_fn.as_global_value().as_pointer_value().into(), + pass_as_opaque(env, element_ptr), ], - bitcode::LIST_SET_IN_PLACE, + bitcode::LIST_REPLACE_IN_PLACE, ), - UpdateMode::Immutable => call_bitcode_fn( + UpdateMode::Immutable => call_list_bitcode_fn( env, &[ - bytes.into(), - length.into(), + pass_list_cc(env, list), env.alignment_intvalue(element_layout), index.into(), pass_element_as_opaque(env, element, *element_layout), layout_width(env, element_layout), - dec_element_fn.as_global_value().as_pointer_value().into(), + pass_as_opaque(env, element_ptr), ], - bitcode::LIST_SET, + bitcode::LIST_REPLACE, ), }; - store_list(env, new_bytes.into_pointer_value(), length) + // Load the element and returned list into a struct. + let old_element = env.builder.build_load(element_ptr, "load_element"); + + let result = env + .context + .struct_type( + &[super::convert::zig_list_type(env).into(), element_type], + false, + ) + .const_zero(); + + let result = env + .builder + .build_insert_value(result, new_list, 0, "insert_list") + .unwrap(); + + env.builder + .build_insert_value(result, old_element, 1, "insert_value") + .unwrap() + .into_struct_value() + .into() } fn bounds_check_comparison<'ctx>( diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index a58a0544b3..fb24a94327 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -260,14 +260,14 @@ impl<'a> LowLevelCall<'a> { _ => internal_error!("invalid storage for List"), }, - ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat - | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap - | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk - | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith - | ListSublist | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe - | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe - | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | DictWalk - | SetFromList | SetToDict => { + ListGetUnsafe | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains + | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2 | ListMap3 + | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil + | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist + | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty + | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues + | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList + | SetToDict | ListReplaceUnsafe => { todo!("{:?}", self.lowlevel); } diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 442c9375e6..816aaf3fa0 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -5,14 +5,14 @@ use crossbeam::deque::{Injector, Stealer, Worker}; use crossbeam::thread; use parking_lot::Mutex; use roc_builtins::std::StdLib; -use roc_can::constraint::Constraint; +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}; use roc_constrain::module::{ - constrain_imports, pre_constrain_imports, ConstrainableImports, Import, + constrain_imports, constrain_module, pre_constrain_imports, ConstrainableImports, + ExposedModuleTypes, Import, SubsByModule, }; -use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::symbol::{ IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified, @@ -294,6 +294,7 @@ fn start_phase<'a>( module, ident_ids, module_timing, + constraints, constraint, var_store, imported_modules, @@ -306,6 +307,7 @@ fn start_phase<'a>( module, ident_ids, module_timing, + constraints, constraint, var_store, imported_modules, @@ -456,7 +458,8 @@ struct ConstrainedModule { module: Module, declarations: Vec, imported_modules: MutMap, - constraint: Constraint, + constraints: Constraints, + constraint: ConstraintSoa, ident_ids: IdentIds, var_store: VarStore, dep_idents: MutMap, @@ -567,7 +570,7 @@ enum Msg<'a> { }, FinishedAllTypeChecking { solved_subs: Solved, - exposed_vars_by_symbol: MutMap, + exposed_vars_by_symbol: Vec<(Symbol, Variable)>, exposed_aliases_by_symbol: MutMap, exposed_values: Vec, dep_idents: MutMap, @@ -793,7 +796,8 @@ enum BuildTask<'a> { ident_ids: IdentIds, imported_symbols: Vec, module_timing: ModuleTiming, - constraint: Constraint, + constraints: Constraints, + constraint: ConstraintSoa, var_store: VarStore, declarations: Vec, dep_idents: MutMap, @@ -1100,7 +1104,7 @@ fn load<'a>( ) -> Result, LoadingProblem<'a>> { // When compiling to wasm, we cannot spawn extra threads // so we have a single-threaded implementation - if cfg!(target_family = "wasm") { + if true || cfg!(target_family = "wasm") { load_single_threaded( arena, load_start, @@ -2318,7 +2322,7 @@ fn finish( solved: Solved, exposed_values: Vec, exposed_aliases_by_symbol: MutMap, - exposed_vars_by_symbol: MutMap, + exposed_vars_by_symbol: Vec<(Symbol, Variable)>, dep_idents: MutMap, documentation: MutMap, ) -> LoadedModule { @@ -2548,6 +2552,7 @@ fn load_module<'a>( Loc::at_zero(ExposedName::new("isEmpty")), Loc::at_zero(ExposedName::new("get")), Loc::at_zero(ExposedName::new("set")), + Loc::at_zero(ExposedName::new("replace")), Loc::at_zero(ExposedName::new("append")), Loc::at_zero(ExposedName::new("map")), Loc::at_zero(ExposedName::new("len")), @@ -2637,6 +2642,7 @@ fn load_module<'a>( get : List a, Nat -> Result a [ OutOfBounds ]* set : List a, Nat, a -> List a + replace : List a, Nat, a -> { list : List a, value : a } append : List a, a -> List a prepend : List a, a -> List a len : List a -> Nat @@ -4271,7 +4277,8 @@ impl<'a> BuildTask<'a> { module: Module, ident_ids: IdentIds, module_timing: ModuleTiming, - constraint: Constraint, + constraints: Constraints, + constraint: ConstraintSoa, var_store: VarStore, imported_modules: MutMap, exposed_types: &mut SubsByModule, @@ -4301,6 +4308,7 @@ impl<'a> BuildTask<'a> { module, ident_ids, imported_symbols, + constraints, constraint, var_store, declarations, @@ -4317,7 +4325,8 @@ fn run_solve<'a>( ident_ids: IdentIds, mut module_timing: ModuleTiming, imported_symbols: Vec, - constraint: Constraint, + mut constraints: Constraints, + constraint: ConstraintSoa, mut var_store: VarStore, decls: Vec, dep_idents: MutMap, @@ -4328,7 +4337,12 @@ fn run_solve<'a>( // Finish constraining the module by wrapping the existing Constraint // in the ones we just computed. We can do this off the main thread. - let constraint = constrain_imports(imported_symbols, constraint, &mut var_store); + let constraint = constrain_imports( + &mut constraints, + imported_symbols, + constraint, + &mut var_store, + ); let constrain_end = SystemTime::now(); @@ -4341,25 +4355,25 @@ fn run_solve<'a>( .. } = module; - if false { - debug_assert!(constraint.validate(), "{:?}", &constraint); - } + // TODO + // if false { debug_assert!(constraint.validate(), "{:?}", &constraint); } let (solved_subs, solved_env, problems) = - roc_solve::module::run_solve(aliases, rigid_variables, constraint, var_store); + roc_solve::module::run_solve(&constraints, constraint, rigid_variables, var_store); - let mut exposed_vars_by_symbol: MutMap = solved_env.vars_by_symbol.clone(); - exposed_vars_by_symbol.retain(|k, _| exposed_symbols.contains(k)); + let exposed_vars_by_symbol: Vec<_> = solved_env + .vars_by_symbol() + .filter(|(k, _)| exposed_symbols.contains(k)) + .collect(); - let solved_types = - roc_solve::module::make_solved_types(&solved_env, &solved_subs, &exposed_vars_by_symbol); + let solved_types = roc_solve::module::make_solved_types(&solved_subs, &exposed_vars_by_symbol); let solved_module = SolvedModule { exposed_vars_by_symbol, exposed_symbols: exposed_symbols.into_iter().collect::>(), solved_types, problems, - aliases: solved_env.aliases, + aliases, }; // Record the final timings @@ -4490,7 +4504,9 @@ fn canonicalize_and_constrain<'a>( } }; - let constraint = constrain_module(&module_output.declarations, module_id); + let mut constraints = Constraints::new(); + let constraint = + constrain_module(&mut constraints, &module_output.declarations, module_id); let module = Module { module_id, @@ -4506,6 +4522,7 @@ fn canonicalize_and_constrain<'a>( declarations: module_output.declarations, imported_modules, var_store, + constraints, constraint, ident_ids: module_output.ident_ids, dep_idents, @@ -4988,6 +5005,7 @@ fn run_task<'a>( module, module_timing, imported_symbols, + constraints, constraint, var_store, ident_ids, @@ -4999,6 +5017,7 @@ fn run_task<'a>( ident_ids, module_timing, imported_symbols, + constraints, constraint, var_store, declarations, diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 940328dbb5..e2d3f34464 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -25,9 +25,9 @@ pub enum LowLevel { StrToNum, ListLen, ListGetUnsafe, - ListSet, ListSingle, ListRepeat, + ListReplaceUnsafe, ListReverse, ListConcat, ListContains, @@ -229,7 +229,7 @@ impl LowLevelWrapperType { Symbol::STR_TO_I8 => WrapperIsRequired, Symbol::LIST_LEN => CanBeReplacedBy(ListLen), Symbol::LIST_GET => WrapperIsRequired, - Symbol::LIST_SET => WrapperIsRequired, + Symbol::LIST_REPLACE => WrapperIsRequired, Symbol::LIST_SINGLE => CanBeReplacedBy(ListSingle), Symbol::LIST_REPEAT => CanBeReplacedBy(ListRepeat), Symbol::LIST_REVERSE => CanBeReplacedBy(ListReverse), diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index b61e02d970..da8c5e3fac 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1142,6 +1142,7 @@ define_builtins! { 55 LIST_SORT_ASC: "sortAsc" 56 LIST_SORT_DESC: "sortDesc" 57 LIST_SORT_DESC_COMPARE: "#sortDescCompare" + 58 LIST_REPLACE: "replace" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" // the Result.Result type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index df1bc9c731..b49c17eb47 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -934,7 +934,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { // - other refcounted arguments are Borrowed match op { ListLen | StrIsEmpty | StrCountGraphemes => arena.alloc_slice_copy(&[borrowed]), - ListSet => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), + ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListConcat => arena.alloc_slice_copy(&[owned, owned]), StrConcat => arena.alloc_slice_copy(&[owned, borrowed]), diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index cafbf8bbf9..ceaea825c6 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -1,5 +1,5 @@ use crate::ir::DestructType; -use roc_collections::all::Index; +use roc_collections::all::HumanIndex; use roc_exhaustive::{ is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, }; @@ -189,7 +189,7 @@ fn to_nonredundant_rows( return Err(Error::Redundant { overall_region, branch_region: region, - index: Index::zero_based(checked_rows.len()), + index: HumanIndex::zero_based(checked_rows.len()), }); } } diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index b48c0e6fd6..d927845749 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -2,7 +2,6 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] -pub mod alias_analysis; pub mod borrow; pub mod code_gen_help; pub mod inc_dec; diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 130b382636..9df0d4f794 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -711,9 +711,8 @@ where let cur_indent = INDENT.with(|i| *i.borrow()); println!( - "@{:>5}:{:<5}: {}{:<50}", - state.line, - state.column, + "{:>5?}: {}{:<50}", + state.pos(), &indent_text[..cur_indent * 2], self.message ); @@ -728,9 +727,8 @@ where }; println!( - "@{:>5}:{:<5}: {}{:<50} {:<15} {:?}", - state.line, - state.column, + "{:<5?}: {}{:<50} {:<15} {:?}", + state.pos(), &indent_text[..cur_indent * 2], self.message, format!("{:?}", progress), @@ -1217,7 +1215,11 @@ macro_rules! collection_trailing_sep_e { $indent_problem ) ), - $crate::blankspace::space0_e($min_indent, $indent_problem) + $crate::blankspace::space0_e( + // we use min_indent=0 because we want to parse incorrectly indented closing braces + // and later fix these up in the formatter. + 0 /* min_indent */, + $indent_problem) ).parse(arena, state)?; let (_,_, state) = @@ -1404,21 +1406,6 @@ where } } -pub fn check_indent<'a, TE, E>(min_indent: u32, to_problem: TE) -> impl Parser<'a, (), E> -where - TE: Fn(Position) -> E, - E: 'a, -{ - move |_arena, state: State<'a>| { - dbg!(state.indent_column, min_indent); - if state.indent_column < min_indent { - Err((NoProgress, to_problem(state.pos()), state)) - } else { - Ok((NoProgress, (), state)) - } - } -} - #[macro_export] macro_rules! word1_check_indent { ($word:expr, $word_problem:expr, $min_indent:expr, $indent_problem:expr) => { diff --git a/compiler/parse/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast b/compiler/parse/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast new file mode 100644 index 0000000000..74ccc748aa --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast @@ -0,0 +1,74 @@ +Defs( + [ + @0-58 Body( + @0-7 Malformed( + "my_list", + ), + @10-58 List( + Collection { + items: [ + @16-17 SpaceBefore( + Num( + "0", + ), + [ + Newline, + ], + ), + @23-48 SpaceBefore( + List( + Collection { + items: [ + @33-34 SpaceBefore( + Var { + module_name: "", + ident: "a", + }, + [ + Newline, + ], + ), + @44-45 SpaceBefore( + Var { + module_name: "", + ident: "b", + }, + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + ), + [ + Newline, + ], + ), + @54-55 SpaceBefore( + Num( + "1", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + ), + ), + ], + @59-61 SpaceBefore( + Num( + "42", + ), + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/list_closing_indent_not_enough.expr.roc b/compiler/parse/tests/snapshots/pass/list_closing_indent_not_enough.expr.roc new file mode 100644 index 0000000000..cdfc50f2e0 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/list_closing_indent_not_enough.expr.roc @@ -0,0 +1,9 @@ +my_list = [ + 0, + [ + a, + b, +], + 1, +] +42 diff --git a/compiler/parse/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast b/compiler/parse/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast new file mode 100644 index 0000000000..5af6f4259c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast @@ -0,0 +1,42 @@ +Defs( + [ + @0-26 Body( + @0-7 Malformed( + "my_list", + ), + @10-26 List( + Collection { + items: [ + @16-17 SpaceBefore( + Num( + "0", + ), + [ + Newline, + ], + ), + @23-24 SpaceBefore( + Num( + "1", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + ), + ), + ], + @27-29 SpaceBefore( + Num( + "42", + ), + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.roc b/compiler/parse/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.roc new file mode 100644 index 0000000000..24b7269dec --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.roc @@ -0,0 +1,5 @@ +my_list = [ + 0, + 1 +] +42 diff --git a/compiler/parse/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast b/compiler/parse/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast new file mode 100644 index 0000000000..99d1bf828f --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast @@ -0,0 +1,42 @@ +Defs( + [ + @0-27 Body( + @0-7 Malformed( + "my_list", + ), + @10-27 List( + Collection { + items: [ + @16-17 SpaceBefore( + Num( + "0", + ), + [ + Newline, + ], + ), + @23-24 SpaceBefore( + Num( + "1", + ), + [ + Newline, + ], + ), + ], + final_comments: [ + Newline, + ], + }, + ), + ), + ], + @28-30 SpaceBefore( + Num( + "42", + ), + [ + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.roc b/compiler/parse/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.roc new file mode 100644 index 0000000000..f6e475d2df --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.roc @@ -0,0 +1,5 @@ +my_list = [ + 0, + 1, +] +42 diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 8b60876c94..684e129497 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -75,19 +75,32 @@ mod test_parse { let mut base = std::path::PathBuf::from("tests"); base.push("snapshots"); let pass_or_fail_names = list(&base); + let mut extra_test_files = std::collections::HashSet::new(); for res in pass_or_fail_names { assert!(res == "pass" || res == "fail"); let res_dir = base.join(&res); for file in list(&res_dir) { - if let Some(file) = file.strip_suffix(".roc") { - assert!(tests.contains(format!("{}/{}", &res, file).as_str()), "{}", file); - } else if let Some(file) = file.strip_suffix(".result-ast") { - assert!(tests.contains(format!("{}/{}", &res, file).as_str()), "{}", file); + let test = if let Some(test) = file.strip_suffix(".roc") { + test + } else if let Some(test) = file.strip_suffix(".result-ast") { + test } else { - panic!("unexpected test file found: {}", file); + panic!("unexpected file found in tests/snapshots: {}", file); + }; + let test_name = format!("{}/{}", &res, test); + if !tests.contains(test_name.as_str()) { + extra_test_files.insert(test_name); } } } + + if extra_test_files.len() > 0 { + eprintln!("Found extra test files:"); + for file in extra_test_files { + eprintln!("{}", file); + } + panic!("Add entries for these in the `snapshot_tests!` macro in test_parse.rs"); + } } $( @@ -109,6 +122,7 @@ mod test_parse { snapshot_tests! { fail/type_argument_no_arrow.expr, fail/type_double_comma.expr, + pass/list_closing_indent_not_enough.expr, pass/add_var_with_spaces.expr, pass/add_with_spaces.expr, pass/annotated_record_destructure.expr, @@ -154,6 +168,8 @@ mod test_parse { pass/int_with_underscore.expr, pass/interface_with_newline.header, pass/lowest_float.expr, + pass/list_closing_same_indent_no_trailing_comma.expr, + pass/list_closing_same_indent_with_trailing_comma.expr, pass/lowest_int.expr, pass/malformed_ident_due_to_underscore.expr, pass/malformed_pattern_field_access.expr, // See https://github.com/rtfeldman/roc/issues/399 @@ -278,15 +294,11 @@ mod test_parse { let result = func(&input); let actual_result = if should_pass { - eprintln!("The source code for this test did not successfully parse!\n"); - - result.unwrap() + result.expect("The source code for this test did not successfully parse!") } else { - eprintln!( - "The source code for this test successfully parsed, but it was not expected to!\n" - ); - - result.unwrap_err() + result.expect_err( + "The source code for this test successfully parsed, but it was not expected to!", + ) }; if std::env::var("ROC_PARSER_SNAPSHOT_TEST_OVERWRITE").is_ok() { diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index 72c69112dc..a767cfb2e7 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -1,5 +1,5 @@ use crate::solve; -use roc_can::constraint::Constraint; +use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -12,20 +12,17 @@ pub struct SolvedModule { pub solved_types: MutMap, pub aliases: MutMap, pub exposed_symbols: Vec, - pub exposed_vars_by_symbol: MutMap, + pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, pub problems: Vec, } pub fn run_solve( - aliases: MutMap, + constraints: &Constraints, + constraint: ConstraintSoa, rigid_variables: MutMap, - constraint: Constraint, var_store: VarStore, ) -> (Solved, solve::Env, Vec) { - let env = solve::Env { - vars_by_symbol: MutMap::default(), - aliases, - }; + let env = solve::Env::default(); let mut subs = Subs::new_from_varstore(var_store); @@ -38,46 +35,17 @@ pub fn run_solve( let mut problems = Vec::new(); // Run the solver to populate Subs. - let (solved_subs, solved_env) = solve::run(&env, &mut problems, subs, &constraint); + let (solved_subs, solved_env) = solve::run(constraints, &env, &mut problems, subs, &constraint); (solved_subs, solved_env, problems) } pub fn make_solved_types( - solved_env: &solve::Env, solved_subs: &Solved, - exposed_vars_by_symbol: &MutMap, + exposed_vars_by_symbol: &[(Symbol, Variable)], ) -> MutMap { let mut solved_types = MutMap::default(); - for (symbol, alias) in solved_env.aliases.iter() { - let mut args = Vec::with_capacity(alias.type_variables.len()); - - for loc_named_var in alias.type_variables.iter() { - let (name, var) = &loc_named_var.value; - - args.push((name.clone(), SolvedType::new(solved_subs, *var))); - } - - let mut lambda_set_variables = Vec::with_capacity(alias.lambda_set_variables.len()); - for set in alias.lambda_set_variables.iter() { - lambda_set_variables.push(roc_types::solved_types::SolvedLambdaSet( - SolvedType::from_type(solved_subs, &set.0), - )); - } - - let solved_type = SolvedType::from_type(solved_subs, &alias.typ); - let solved_alias = SolvedType::Alias( - *symbol, - args, - lambda_set_variables, - Box::new(solved_type), - alias.kind, - ); - - solved_types.insert(*symbol, solved_alias); - } - // exposed_vars_by_symbol contains the Variables for all the Symbols // this module exposes. We want to convert those into flat SolvedType // annotations which are decoupled from our Subs, because that's how diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 065c6ef040..e0603652a6 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,7 +1,9 @@ +use bumpalo::Bump; use roc_can::constraint::Constraint::{self, *}; -use roc_can::constraint::PresenceConstraint; +use roc_can::constraint::{Constraints, LetConstraint}; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::MutMap; +use roc_collections::soa::{Index, Slice}; use roc_module::ident::TagName; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; @@ -12,7 +14,7 @@ use roc_types::subs::{ }; use roc_types::types::Type::{self, *}; use roc_types::types::{ - gather_fields_unsorted_iter, Alias, AliasKind, Category, ErrorType, PatternCategory, + gather_fields_unsorted_iter, AliasKind, Category, ErrorType, PatternCategory, }; use roc_unify::unify::{unify, Mode, Unified::*}; use std::collections::hash_map::Entry; @@ -78,8 +80,37 @@ pub enum TypeError { #[derive(Clone, Debug, Default)] pub struct Env { - pub vars_by_symbol: MutMap, - pub aliases: MutMap, + symbols: Vec, + variables: Vec, +} + +impl Env { + pub fn vars_by_symbol(&self) -> impl Iterator + '_ { + let it1 = self.symbols.iter().copied(); + let it2 = self.variables.iter().copied(); + + it1.zip(it2) + } + + fn get_var_by_symbol(&self, symbol: &Symbol) -> Option { + self.symbols + .iter() + .position(|s| s == symbol) + .map(|index| self.variables[index]) + } + + fn insert_symbol_var_if_vacant(&mut self, symbol: Symbol, var: Variable) { + match self.symbols.iter().position(|s| *s == symbol) { + None => { + // symbol is not in vars_by_symbol yet; insert it + self.symbols.push(symbol); + self.variables.push(var); + } + Some(_) => { + // do nothing + } + } + } } const DEFAULT_POOLS: usize = 8; @@ -140,18 +171,20 @@ struct State { } pub fn run( + constraints: &Constraints, env: &Env, problems: &mut Vec, mut subs: Subs, constraint: &Constraint, ) -> (Solved, Env) { - let env = run_in_place(env, problems, &mut subs, constraint); + let env = run_in_place(constraints, env, problems, &mut subs, constraint); (Solved(subs), env) } /// Modify an existing subs in-place instead pub fn run_in_place( + constraints: &Constraints, env: &Env, problems: &mut Vec, subs: &mut Subs, @@ -163,7 +196,12 @@ pub fn run_in_place( mark: Mark::NONE.next(), }; let rank = Rank::toplevel(); + + let arena = Bump::new(); + let state = solve( + &arena, + constraints, env, state, rank, @@ -177,23 +215,34 @@ pub fn run_in_place( state.env } -enum After { - CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), -} - enum Work<'a> { Constraint { - env: Env, + env: &'a Env, rank: Rank, constraint: &'a Constraint, - after: Option, }, - /// Something to be done after a constraint and all its dependencies are fully solved. - After(After), + CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), + /// The ret_con part of a let constraint that does NOT introduces rigid and/or flex variables + LetConNoVariables { + env: &'a Env, + rank: Rank, + let_con: &'a LetConstraint, + }, + /// The ret_con part of a let constraint that introduces rigid and/or flex variables + /// + /// These introduced variables must be generalized, hence this variant + /// is more complex than `LetConNoVariables`. + LetConIntroducesVariables { + env: &'a Env, + rank: Rank, + let_con: &'a LetConstraint, + }, } #[allow(clippy::too_many_arguments)] fn solve( + arena: &Bump, + constraints: &Constraints, env: &Env, mut state: State, rank: Rank, @@ -203,48 +252,174 @@ fn solve( subs: &mut Subs, constraint: &Constraint, ) -> State { - let mut stack = vec![Work::Constraint { - env: env.clone(), + let initial = Work::Constraint { + env, rank, constraint, - after: None, - }]; + }; + + let mut stack = vec![initial]; while let Some(work_item) = stack.pop() { let (env, rank, constraint) = match work_item { - Work::After(After::CheckForInfiniteTypes(def_vars)) => { - for (symbol, loc_var) in def_vars.iter() { - check_for_infinite_type(subs, problems, *symbol, *loc_var); - } - // No constraint to be solved - continue; - } Work::Constraint { env, rank, constraint, - after, } => { - // Push the `after` on first so that we look at it immediately after finishing all - // the children of this constraint. - if let Some(after) = after { - stack.push(Work::After(after)); - } + // the default case; actually solve this constraint (env, rank, constraint) } + Work::CheckForInfiniteTypes(def_vars) => { + // after a LetCon, we must check if any of the variables that we introduced + // loop back to themselves after solving the ret_constraint + for (symbol, loc_var) in def_vars.iter() { + check_for_infinite_type(subs, problems, *symbol, *loc_var); + } + + continue; + } + Work::LetConNoVariables { env, rank, let_con } => { + // NOTE be extremely careful with shadowing here + let offset = let_con.defs_and_ret_constraint.index(); + let ret_constraint = &constraints.constraints[offset + 1]; + + // Add a variable for each def to new_vars_by_env. + let local_def_vars = LocalDefVarsVec::from_def_types( + constraints, + rank, + pools, + cached_aliases, + subs, + let_con.def_types, + ); + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); + } + + stack.push(Work::CheckForInfiniteTypes(local_def_vars)); + stack.push(Work::Constraint { + env: arena.alloc(new_env), + rank, + constraint: ret_constraint, + }); + + continue; + } + Work::LetConIntroducesVariables { env, rank, let_con } => { + // NOTE be extremely careful with shadowing here + let offset = let_con.defs_and_ret_constraint.index(); + let ret_constraint = &constraints.constraints[offset + 1]; + + let next_rank = rank.next(); + + let mark = state.mark; + let saved_env = state.env; + + let young_mark = mark; + let visit_mark = young_mark.next(); + let final_mark = visit_mark.next(); + + // Add a variable for each def to local_def_vars. + let local_def_vars = LocalDefVarsVec::from_def_types( + constraints, + next_rank, + pools, + cached_aliases, + subs, + let_con.def_types, + ); + + debug_assert_eq!( + { + let offenders = pools + .get(next_rank) + .iter() + .filter(|var| { + subs.get_rank(**var).into_usize() > next_rank.into_usize() + }) + .collect::>(); + + let result = offenders.len(); + + if result > 0 { + dbg!(&subs, &offenders, &let_con.def_types); + } + + result + }, + 0 + ); + + // pop pool + generalize(subs, young_mark, visit_mark, next_rank, pools); + + pools.get_mut(next_rank).clear(); + + // check that things went well + debug_assert!({ + // NOTE the `subs.redundant` check is added for the uniqueness + // inference, and does not come from elm. It's unclear whether this is + // a bug with uniqueness inference (something is redundant that + // shouldn't be) or that it just never came up in elm. + let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; + + let failing: Vec<_> = rigid_vars + .iter() + .filter(|&var| !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE) + .collect(); + + if !failing.is_empty() { + println!("Rigids {:?}", &rigid_vars); + println!("Failing {:?}", failing); + } + + failing.is_empty() + }); + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); + } + + // Note that this vars_by_symbol is the one returned by the + // previous call to solve() + let state_for_ret_con = State { + env: saved_env, + mark: final_mark, + }; + + // Now solve the body, using the new vars_by_symbol which includes + // the assignments' name-to-variable mappings. + stack.push(Work::CheckForInfiniteTypes(local_def_vars)); + stack.push(Work::Constraint { + env: arena.alloc(new_env), + rank, + constraint: ret_constraint, + }); + + state = state_for_ret_con; + + continue; + } }; state = match constraint { True => state, SaveTheEnvironment => { - // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment let mut copy = state; - copy.env = env; + copy.env = env.clone(); copy } - Eq(typ, expectation, category, region) => { + Eq(type_index, expectation_index, category_index, region) => { + let typ = &constraints.types[type_index.index()]; + let expectation = &constraints.expectations[expectation_index.index()]; + let category = &constraints.categories[category_index.index()]; + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); let expected = type_to_var( subs, @@ -283,7 +458,9 @@ fn solve( } } } - Store(source, target, _filename, _linenr) => { + Store(source_index, target, _filename, _linenr) => { + let source = &constraints.types[source_index.index()]; + // a special version of Eq that is used to store types in the AST. // IT DOES NOT REPORT ERRORS! let actual = type_to_var(subs, rank, pools, cached_aliases, source); @@ -311,8 +488,8 @@ fn solve( } } } - Lookup(symbol, expectation, region) => { - match env.vars_by_symbol.get(symbol) { + Lookup(symbol, expectation_index, region) => { + match env.get_var_by_symbol(symbol) { Some(var) => { // Deep copy the vars associated with this symbol before unifying them. // Otherwise, suppose we have this: @@ -335,7 +512,9 @@ fn solve( // then we copy from that module's Subs into our own. If the value // is being looked up in this module, then we use our Subs as both // the source and destination. - let actual = deep_copy_var(subs, rank, pools, *var); + let actual = deep_copy_var_in(subs, rank, pools, var, arena); + let expectation = &constraints.expectations[expectation_index.index()]; + let expected = type_to_var( subs, rank, @@ -343,6 +522,7 @@ fn solve( cached_aliases, expectation.get_type_ref(), ); + match unify(subs, actual, expected, Mode::EQ) { Success(vars) => { introduce(subs, rank, pools, &vars); @@ -383,20 +563,24 @@ fn solve( } } } - And(sub_constraints) => { - for sub_constraint in sub_constraints.iter().rev() { + And(slice) => { + let it = constraints.constraints[slice.indices()].iter().rev(); + for sub_constraint in it { stack.push(Work::Constraint { - env: env.clone(), + env, rank, constraint: sub_constraint, - after: None, }) } state } - Pattern(region, category, typ, expectation) - | Present(typ, PresenceConstraint::Pattern(region, category, expectation)) => { + Pattern(type_index, expectation_index, category_index, region) + | PatternPresence(type_index, expectation_index, category_index, region) => { + let typ = &constraints.types[type_index.index()]; + let expectation = &constraints.pattern_expectations[expectation_index.index()]; + let category = &constraints.pattern_categories[category_index.index()]; + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); let expected = type_to_var( subs, @@ -407,7 +591,7 @@ fn solve( ); let mode = match constraint { - Present(_, _) => Mode::PRESENT, + PatternPresence(..) => Mode::PRESENT, _ => Mode::EQ, }; @@ -440,225 +624,89 @@ fn solve( } } } - Let(let_con) => { - match &let_con.ret_constraint { - True if let_con.rigid_vars.is_empty() => { - introduce(subs, rank, pools, &let_con.flex_vars); + Let(index) => { + let let_con = &constraints.let_constraints[index.index()]; - // If the return expression is guaranteed to solve, - // solve the assignments themselves and move on. - stack.push(Work::Constraint { - env, - rank, - constraint: &let_con.defs_constraint, - after: None, - }); - state + let offset = let_con.defs_and_ret_constraint.index(); + let defs_constraint = &constraints.constraints[offset]; + let ret_constraint = &constraints.constraints[offset + 1]; + + let flex_vars = &constraints.variables[let_con.flex_vars.indices()]; + let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; + + if matches!(&ret_constraint, True) && let_con.rigid_vars.is_empty() { + introduce(subs, rank, pools, flex_vars); + + // If the return expression is guaranteed to solve, + // solve the assignments themselves and move on. + stack.push(Work::Constraint { + env, + rank, + constraint: defs_constraint, + }); + + state + } else if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() { + // items are popped from the stack in reverse order. That means that we'll + // first solve then defs_constraint, and then (eventually) the ret_constraint. + // + // Note that the LetConSimple gets the current env and rank, + // and not the env/rank from after solving the defs_constraint + stack.push(Work::LetConNoVariables { env, rank, let_con }); + stack.push(Work::Constraint { + env, + rank, + constraint: defs_constraint, + }); + + state + } else { + // work in the next pool to localize header + let next_rank = rank.next(); + + // introduce variables + for &var in rigid_vars.iter().chain(flex_vars.iter()) { + subs.set_rank(var, next_rank); } - ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { - // TODO: make into `WorkItem` with `After` - let state = solve( - &env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - // Add a variable for each def to new_vars_by_env. - let mut local_def_vars = - LocalDefVarsVec::with_length(let_con.def_types.len()); - - for (symbol, loc_type) in let_con.def_types.iter() { - let var = - type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); - - local_def_vars.push(( - *symbol, - Loc { - value: var, - region: loc_type.region, - }, - )); - } - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - match new_env.vars_by_symbol.entry(*symbol) { - Entry::Occupied(_) => { - // keep the existing value - } - Entry::Vacant(vacant) => { - vacant.insert(loc_var.value); - } - } - } - - stack.push(Work::Constraint { - env: new_env, - rank, - constraint: ret_con, - after: Some(After::CheckForInfiniteTypes(local_def_vars)), - }); - - state + // determine the next pool + if next_rank.into_usize() < pools.len() { + // Nothing to do, we already accounted for the next rank, no need to + // adjust the pools + } else { + // we should be off by one at this point + debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); + pools.extend_to(next_rank.into_usize()); } - ret_con => { - let rigid_vars = &let_con.rigid_vars; - let flex_vars = &let_con.flex_vars; - // work in the next pool to localize header - let next_rank = rank.next(); + let pool: &mut Vec = pools.get_mut(next_rank); - // introduce variables - for &var in rigid_vars.iter().chain(flex_vars.iter()) { - subs.set_rank(var, next_rank); - } + // Replace the contents of this pool with rigid_vars and flex_vars + pool.clear(); + pool.reserve(rigid_vars.len() + flex_vars.len()); + pool.extend(rigid_vars.iter()); + pool.extend(flex_vars.iter()); - // determine the next pool - if next_rank.into_usize() < pools.len() { - // Nothing to do, we already accounted for the next rank, no need to - // adjust the pools - } else { - // we should be off by one at this point - debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); - pools.extend_to(next_rank.into_usize()); - } + // run solver in next pool - let pool: &mut Vec = pools.get_mut(next_rank); + // items are popped from the stack in reverse order. That means that we'll + // first solve then defs_constraint, and then (eventually) the ret_constraint. + // + // Note that the LetConSimple gets the current env and rank, + // and not the env/rank from after solving the defs_constraint + stack.push(Work::LetConIntroducesVariables { env, rank, let_con }); + stack.push(Work::Constraint { + env, + rank: next_rank, + constraint: defs_constraint, + }); - // Replace the contents of this pool with rigid_vars and flex_vars - pool.clear(); - pool.reserve(rigid_vars.len() + flex_vars.len()); - pool.extend(rigid_vars.iter()); - pool.extend(flex_vars.iter()); - - // run solver in next pool - - // Add a variable for each def to local_def_vars. - let mut local_def_vars = - LocalDefVarsVec::with_length(let_con.def_types.len()); - - for (symbol, loc_type) in let_con.def_types.iter() { - let def_type = &loc_type.value; - - let var = type_to_var(subs, next_rank, pools, cached_aliases, def_type); - - local_def_vars.push(( - *symbol, - Loc { - value: var, - region: loc_type.region, - }, - )); - } - - // Solve the assignments' constraints first. - // TODO: make into `WorkItem` with `After` - let State { - env: saved_env, - mark, - } = solve( - &env, - state, - next_rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - let young_mark = mark; - let visit_mark = young_mark.next(); - let final_mark = visit_mark.next(); - - debug_assert_eq!( - { - let offenders = pools - .get(next_rank) - .iter() - .filter(|var| { - let current_rank = - subs.get_rank(roc_types::subs::Variable::clone(var)); - - current_rank.into_usize() > next_rank.into_usize() - }) - .collect::>(); - - let result = offenders.len(); - - if result > 0 { - dbg!(&subs, &offenders, &let_con.def_types); - } - - result - }, - 0 - ); - - // pop pool - generalize(subs, young_mark, visit_mark, next_rank, pools); - - pools.get_mut(next_rank).clear(); - - // check that things went well - debug_assert!({ - // NOTE the `subs.redundant` check is added for the uniqueness - // inference, and does not come from elm. It's unclear whether this is - // a bug with uniqueness inference (something is redundant that - // shouldn't be) or that it just never came up in elm. - let failing: Vec<_> = rigid_vars - .iter() - .filter(|&var| { - !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE - }) - .collect(); - - if !failing.is_empty() { - println!("Rigids {:?}", &rigid_vars); - println!("Failing {:?}", failing); - } - - failing.is_empty() - }); - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - match new_env.vars_by_symbol.entry(*symbol) { - Entry::Occupied(_) => { - // keep the existing value - } - Entry::Vacant(vacant) => { - vacant.insert(loc_var.value); - } - } - } - - // Note that this vars_by_symbol is the one returned by the - // previous call to solve() - let state_for_ret_con = State { - env: saved_env, - mark: final_mark, - }; - - // Now solve the body, using the new vars_by_symbol which includes - // the assignments' name-to-variable mappings. - stack.push(Work::Constraint { - env: new_env, - rank, - constraint: ret_con, - after: Some(After::CheckForInfiniteTypes(local_def_vars)), - }); - - state_for_ret_con - } + state } } - Present(typ, PresenceConstraint::IsOpen) => { + IsOpenType(type_index) => { + let typ = &constraints.types[type_index.index()]; + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); let mut new_desc = subs.get(actual); match new_desc.content { @@ -680,13 +728,24 @@ fn solve( } } } - Present( - typ, - PresenceConstraint::IncludesTag(tag_name, tys, region, pattern_category), - ) => { + IncludesTag(index) => { + let includes_tag = &constraints.includes_tags[index.index()]; + + let roc_can::constraint::IncludesTag { + type_index, + tag_name, + types, + pattern_category, + region, + } = includes_tag; + + let typ = &constraints.types[type_index.index()]; + let tys = &constraints.types[types.indices()]; + let pattern_category = &constraints.pattern_categories[pattern_category.index()]; + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); let tag_ty = Type::TagUnion( - vec![(tag_name.clone(), tys.clone())], + vec![(tag_name.clone(), tys.to_vec())], Box::new(Type::EmptyTagUnion), ); let includes = type_to_var(subs, rank, pools, cached_aliases, &tag_ty); @@ -756,24 +815,49 @@ impl LocalDefVarsVec { } } +impl LocalDefVarsVec<(Symbol, Loc)> { + fn from_def_types( + constraints: &Constraints, + rank: Rank, + pools: &mut Pools, + cached_aliases: &mut MutMap, + subs: &mut Subs, + def_types_slice: Slice<(Symbol, Loc>)>, + ) -> Self { + let def_types = &constraints.def_types[def_types_slice.indices()]; + + let mut local_def_vars = Self::with_length(def_types.len()); + + for (symbol, loc_type_index) in def_types.iter() { + let typ = &constraints.types[loc_type_index.value.index()]; + let var = type_to_var(subs, rank, pools, cached_aliases, typ); + + local_def_vars.push(( + *symbol, + Loc { + value: var, + region: loc_type_index.region, + }, + )); + } + + local_def_vars + } +} + use std::cell::RefCell; std::thread_local! { /// Scratchpad arena so we don't need to allocate a new one all the time - static SCRATCHPAD: RefCell = RefCell::new(bumpalo::Bump::with_capacity(4 * 1024)); + static SCRATCHPAD: RefCell> = RefCell::new(Some(bumpalo::Bump::with_capacity(4 * 1024))); } fn take_scratchpad() -> bumpalo::Bump { - let mut result = bumpalo::Bump::new(); - SCRATCHPAD.with(|f| { - result = f.replace(bumpalo::Bump::new()); - }); - - result + SCRATCHPAD.with(|f| f.take().unwrap()) } fn put_scratchpad(scratchpad: bumpalo::Bump) { SCRATCHPAD.with(|f| { - f.replace(scratchpad); + f.replace(Some(scratchpad)); }); } @@ -956,7 +1040,7 @@ fn type_to_variable<'a>( return reserved; } else { // for any other rank, we need to copy; it takes care of adjusting the rank - return deep_copy_var(subs, rank, pools, reserved); + return deep_copy_var_in(subs, rank, pools, reserved, arena); } } @@ -1024,6 +1108,7 @@ fn type_to_variable<'a>( } } +#[inline(always)] fn alias_to_var<'a>( subs: &mut Subs, rank: Rank, @@ -1053,6 +1138,7 @@ fn alias_to_var<'a>( } } +#[inline(always)] fn roc_result_to_var<'a>( subs: &mut Subs, rank: Rank, @@ -1801,10 +1887,14 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { } } -fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { - let mut arena = take_scratchpad(); - - let mut visited = bumpalo::collections::Vec::with_capacity_in(4 * 1024, &arena); +fn deep_copy_var_in( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + var: Variable, + arena: &Bump, +) -> Variable { + let mut visited = bumpalo::collections::Vec::with_capacity_in(256, arena); let copy = deep_copy_var_help(subs, rank, pools, &mut visited, var); @@ -1820,9 +1910,6 @@ fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) } } - arena.reset(); - put_scratchpad(arena); - copy } @@ -2061,6 +2148,7 @@ fn deep_copy_var_help( } } +#[inline(always)] fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable { let descriptor = Descriptor { content, diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 956760a504..9d8129665c 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5516,4 +5516,24 @@ mod solve_expr { r#"Id [ A, B, C { a : Str }e ] -> Str"#, ) } + + #[test] + fn lambda_set_within_alias_is_quantified() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ effectAlways ] to "./platform" + + Effect a : [ @Effect ({} -> a) ] + + effectAlways : a -> Effect a + effectAlways = \x -> + inner = \{} -> x + + @Effect inner + "# + ), + r#"a -> Effect a"#, + ) + } } diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 8a6f142c64..8eb784c2b8 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1763,6 +1763,97 @@ fn get_int_list_oob() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn replace_unique_int_list() { + assert_evals_to!( + indoc!( + r#" + record = List.replace [ 12, 9, 7, 1, 5 ] 2 33 + record.list + "# + ), + RocList::from_slice(&[12, 9, 33, 1, 5]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn replace_unique_int_list_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + record = List.replace [ 12, 9, 7, 1, 5 ] 5 33 + record.value + "# + ), + 33, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn replace_unique_int_list_get_old_value() { + assert_evals_to!( + indoc!( + r#" + record = List.replace [ 12, 9, 7, 1, 5 ] 2 33 + record.value + "# + ), + 7, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn replace_unique_get_large_value() { + assert_evals_to!( + indoc!( + r#" + list : List { a : U64, b: U64, c: U64, d: U64 } + list = [ { a: 1, b: 2, c: 3, d: 4 }, { a: 5, b: 6, c: 7, d: 8 }, { a: 9, b: 10, c: 11, d: 12 } ] + record = List.replace list 1 { a: 13, b: 14, c: 15, d: 16 } + record.value + "# + ), + (5, 6, 7, 8), + (u64, u64, u64, u64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn replace_shared_int_list() { + assert_evals_to!( + indoc!( + r#" + wrapper = \shared -> + # This should not mutate the original + replaced = (List.replace shared 1 7.7).list + x = + when List.get replaced 1 is + Ok num -> num + Err _ -> 0 + + y = + when List.get shared 1 is + Ok num -> num + Err _ -> 0 + + { x, y } + + wrapper [ 2.1, 4.3 ] + "# + ), + (7.7, 4.3), + (f64, f64) + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn get_set_unique_int_list() { diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index bc416fe16a..6824f4abd0 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -382,7 +382,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa _ => write_parens!(write_parens, buf, { write_symbol(env, *symbol, buf); - for var_index in args.into_iter() { + for var_index in args.named_type_arguments() { let var = subs[var_index]; buf.push(' '); write_content( diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 298fd32be8..6bc9b7ed9e 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -2,7 +2,7 @@ use crate::pretty_print::Parens; use crate::subs::{ GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, }; -use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap}; +use roc_collections::all::{HumanIndex, ImMap, ImSet, MutSet, SendMap}; use roc_error_macros::internal_error; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; @@ -1203,14 +1203,14 @@ pub struct TagUnionStructure<'a> { pub enum PReason { TypedArg { opt_name: Option, - index: Index, + index: HumanIndex, }, WhenMatch { - index: Index, + index: HumanIndex, }, TagArg { tag_name: TagName, - index: Index, + index: HumanIndex, }, PatternGuard, OptionalField, @@ -1219,12 +1219,12 @@ pub enum PReason { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AnnotationSource { TypedIfBranch { - index: Index, + index: HumanIndex, num_branches: usize, region: Region, }, TypedWhenBranch { - index: Index, + index: HumanIndex, region: Region, }, TypedBody { @@ -1246,7 +1246,7 @@ impl AnnotationSource { pub enum Reason { FnArg { name: Option, - arg_index: Index, + arg_index: HumanIndex, }, FnCall { name: Option, @@ -1254,28 +1254,28 @@ pub enum Reason { }, LowLevelOpArg { op: LowLevel, - arg_index: Index, + arg_index: HumanIndex, }, ForeignCallArg { foreign_symbol: ForeignSymbol, - arg_index: Index, + arg_index: HumanIndex, }, FloatLiteral, IntLiteral, NumLiteral, StrInterpolation, WhenBranch { - index: Index, + index: HumanIndex, }, WhenGuard, ExpectCondition, IfCondition, IfBranch { - index: Index, + index: HumanIndex, total_branches: usize, }, ElemInList { - index: Index, + index: HumanIndex, }, RecordUpdateValue(Lowercase), RecordUpdateKeys(Symbol, SendMap), diff --git a/editor/README.md b/editor/README.md index 9032e4acb3..e462661928 100644 --- a/editor/README.md +++ b/editor/README.md @@ -1,4 +1,7 @@ + +## :construction: Work In Progress :construction: + The editor is a work in progress, only a limited subset of Roc expressions are currently supported. Unlike most editors, we use projectional or structural editing to edit the [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) directly. This will allow for cool features like excellent auto-complete, refactoring and never needing to format your code. @@ -68,6 +71,7 @@ Important folders/files outside the editor folder: - ast/src/lang/core/ast.rs - ast/src/lang/env.rs + ## Contributing We welcome new contributors :heart: and are happy to help you get started. diff --git a/examples/hello-web/README.md b/examples/hello-web/README.md index f0beccdb6b..4401c8d367 100644 --- a/examples/hello-web/README.md +++ b/examples/hello-web/README.md @@ -3,7 +3,7 @@ To run, go to the project home directory and run: ```bash -$ cargo run -- build --backend=wasm32 examples/hello-web/Hello.roc +$ cargo run -- build --target=wasm32 examples/hello-web/Hello.roc ``` Then `cd` into the example directory and run any web server that can handle WebAssembly. diff --git a/getting_started/windows.md b/getting_started/windows.md index ae0556833d..de3a7e1cbc 100644 --- a/getting_started/windows.md +++ b/getting_started/windows.md @@ -1,2 +1,2 @@ -Windows is not yet supported, we have a big project in the works that will make it easier to achieve this. +Windows is not yet supported, we have a big project in the works (see issue #2608) that will allow this. Until then we recommend using Ubuntu through the "Windows Subsystem for Linux". \ No newline at end of file diff --git a/linker/Cargo.toml b/linker/Cargo.toml index 7543f9438b..c95a3ee5e2 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -24,7 +24,7 @@ roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.8.0", features = ["collections"] } clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] } -memmap2 = "0.5.0" +memmap2 = "0.5.3" object = { version = "0.26.2", features = ["read", "write"] } serde = { version = "1.0.130", features = ["derive"] } bincode = "1.3.3" diff --git a/linker/src/lib.rs b/linker/src/lib.rs index b9d6ee8519..9dc6d76caa 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -20,7 +20,6 @@ use std::io; use std::io::{BufReader, BufWriter}; use std::mem; use std::os::raw::c_char; -use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::process::Command; use std::time::{Duration, SystemTime}; @@ -367,9 +366,7 @@ fn preprocess_impl( Some(section) => { let file_offset = match section.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -494,9 +491,7 @@ fn preprocess_impl( for sec in text_sections { let (file_offset, compressed) = match sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -626,9 +621,7 @@ fn preprocess_impl( }; let dyn_offset = match dyn_sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -714,9 +707,7 @@ fn preprocess_impl( }; let symtab_offset = match symtab_sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -738,9 +729,7 @@ fn preprocess_impl( }; let dynsym_offset = match dynsym_sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -759,9 +748,7 @@ fn preprocess_impl( { match sec.compressed_file_range() { Ok( - range - @ - CompressedFileRange { + range @ CompressedFileRange { format: CompressionFormat::None, .. }, @@ -1627,9 +1614,14 @@ fn surgery_impl( let flushing_data_duration = flushing_data_start.elapsed().unwrap(); // Make sure the final executable has permision to execute. - let mut perms = fs::metadata(out_filename)?.permissions(); - perms.set_mode(perms.mode() | 0o111); - fs::set_permissions(out_filename, perms)?; + // TODO windows alternative? + #[cfg(target_family = "unix")] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(out_filename)?.permissions(); + perms.set_mode(perms.mode() | 0o111); + fs::set_permissions(out_filename, perms)?; + } let total_duration = total_start.elapsed().unwrap(); diff --git a/nix/sources.json b/nix/sources.json index a7c8c261c0..04dc2dbb42 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -5,10 +5,10 @@ "homepage": "https://github.com/nmattia/niv", "owner": "nmattia", "repo": "niv", - "rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c", - "sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm", + "rev": "9cb7ef336bb71fd1ca84fc7f2dff15ef4b033f2a", + "sha256": "1ajyqr8zka1zlb25jx1v4xys3zqmdy3prbm1vxlid6ah27a8qnzh", "type": "tarball", - "url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz", + "url": "https://github.com/nmattia/niv/archive/9cb7ef336bb71fd1ca84fc7f2dff15ef4b033f2a.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "nixpkgs": { @@ -17,10 +17,10 @@ "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "fe6f208d68ac254873b659db9676d44dea9b0555", - "sha256": "0ybvy1zx97k811bz73xmgsb41d33i2kr2dfqcxzq9m9h958178nq", + "rev": "ed02c2ba0384b2800db41333045a6fb781f12aac", + "sha256": "040rawxqbpblxpsq73qxlk25my2cm0g3gx1pksiacsj15q5fi84q", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/fe6f208d68ac254873b659db9676d44dea9b0555.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/ed02c2ba0384b2800db41333045a6fb781f12aac.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "nixpkgs-unstable": { @@ -29,10 +29,10 @@ "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ea171bc81fcb3c6f21deeb46dbc10000087777ef", - "sha256": "15hh28c98kb6pf7wgydc07bx2ivq04a2cay5mhwnqk5cpa8dbiap", + "rev": "684c73c9e6ac8f4d0c6dea3251292e758ac375b5", + "sha256": "0hl2nzizn4pwd3sn9gxkngzn88k9in01xm14afpj7716j8y0j2qa", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/ea171bc81fcb3c6f21deeb46dbc10000087777ef.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/684c73c9e6ac8f4d0c6dea3251292e758ac375b5.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index 77383b8df7..b56556c5e4 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -18,8 +18,8 @@ bumpalo = { version = "3.8.0", features = ["collections"] } const_format = "0.2.22" inkwell = {path = "../vendor/inkwell"} libloading = "0.7.1" -rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} -rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"} +rustyline = {git = "https://github.com/rtfeldman/rustyline", rev = "e74333c"} +rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", rev = "e74333c"} target-lexicon = "0.12.2" # TODO: make llvm optional @@ -31,7 +31,7 @@ roc_load = {path = "../compiler/load"} roc_mono = {path = "../compiler/mono"} roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} -roc_std = {path = "../roc_std"} +roc_std = {path = "../roc_std", default-features = false} roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index d455af342d..b5d949e323 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -519,11 +519,11 @@ fn four_element_record() { ); } -// #[test] -// fn multiline_string() { -// // If a string contains newlines, format it as a multiline string in the output -// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\""); -// } +#[test] +fn multiline_string() { + // If a string contains newlines, format it as a multiline string in the output + expect_success(r#""\n\nhi!\n\n""#, "\"\n\nhi!\n\n\" : Str"); +} #[test] fn list_of_3_field_records() { diff --git a/repl_wasm/build.rs b/repl_wasm/build.rs index 3a1ffed494..eb820852c8 100644 --- a/repl_wasm/build.rs +++ b/repl_wasm/build.rs @@ -1,3 +1,4 @@ +use std::env; use std::ffi::OsStr; use std::path::Path; use std::process::Command; @@ -11,6 +12,15 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=src/{}.c", PLATFORM_FILENAME); + // When we build on Netlify, zig is not installed (but also not used, + // since all we're doing is generating docs), so we can skip the steps + // that require having zig installed. + if env::var_os("NO_ZIG_INSTALLED").is_some() { + // We still need to do the other things before this point, because + // setting the env vars is needed for other parts of the build. + return; + } + std::fs::create_dir_all("./data").unwrap(); // Build a pre-linked binary with platform, builtins and all their libc dependencies diff --git a/repl_www/README.md b/repl_www/README.md index c7f7242c7e..b5fa57b571 100644 --- a/repl_www/README.md +++ b/repl_www/README.md @@ -25,7 +25,7 @@ python3 -m http.server ``` ### 3. Open your browser -You should be able to find the Roc REPL at http://127.0.0.1:8000 (or wherever your web server said when it started up.) +You should be able to find the Roc REPL at http://127.0.0.1:8000/repl (or whatever port your web server mentioned when it started up.) **Warning:** This is work in progress! Not all language features are implemented yet, error messages don't look nice yet, up/down arrows don't work for history, etc. diff --git a/repl_www/build.sh b/repl_www/build.sh index aad87b3fc0..419369c689 100755 --- a/repl_www/build.sh +++ b/repl_www/build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -eux +set -euxo pipefail if [[ ! -d repl_www ]] then @@ -14,9 +14,13 @@ then cargo install wasm-pack fi -WWW_DIR="repl_www/build" -mkdir -p $WWW_DIR -cp repl_www/public/* $WWW_DIR +# output directory is first argument or default +WWW_ROOT="${1:-repl_www/build}" +mkdir -p $WWW_ROOT + +# End up with `repl/index.html` and everything else at the root directory. +# We want all the assets to be at the root because the files auto-generated by `cargo` expect them to be there. +cp -r repl_www/public/* $WWW_ROOT # When debugging the REPL, use `REPL_DEBUG=1 repl_www/build.sh` if [ -n "${REPL_DEBUG:-}" ] @@ -28,10 +32,10 @@ else wasm-pack build --target web repl_wasm fi -cp repl_wasm/pkg/*.wasm $WWW_DIR +cp repl_wasm/pkg/*.wasm $WWW_ROOT # Copy the JS from wasm_bindgen, replacing its invalid `import` statement with a `var`. # The JS import from the invalid path 'env', seems to be generated when there are unresolved symbols. BINDGEN_FILE="roc_repl_wasm.js" -echo 'var __wbg_star0 = { now: Date.now };' > $WWW_DIR/$BINDGEN_FILE -grep -v '^import' repl_wasm/pkg/$BINDGEN_FILE >> $WWW_DIR/$BINDGEN_FILE +echo 'var __wbg_star0 = { now: Date.now };' > $WWW_ROOT/$BINDGEN_FILE +grep -v '^import' repl_wasm/pkg/$BINDGEN_FILE >> $WWW_ROOT/$BINDGEN_FILE diff --git a/repl_www/public/index.html b/repl_www/public/index.html deleted file mode 100644 index e08ec457a1..0000000000 --- a/repl_www/public/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - Roc REPL - - -
-
-

The rockin' Roc REPL

-
- -
-
-
-
-
- -
- -
-
- - - diff --git a/repl_www/public/repl.css b/repl_www/public/repl.css new file mode 100644 index 0000000000..f39bccbff6 --- /dev/null +++ b/repl_www/public/repl.css @@ -0,0 +1,72 @@ +html { + height: 100%; +} +body { + height: 100%; + background-color: #222; + color: #ccc; + font-family: sans-serif; + font-size: 18px; +} +.body-wrapper { + display: flex; + flex-direction: column; + max-width: 900px; + height: 100%; + margin: 0 auto; + padding: 0 24px; +} +h1 { + margin: 32px auto; + color: #eee; + text-align: center; +} +li { + margin: 8px; +} +section.history { + flex: 1; +} +.scroll-wrap { + position: relative; + height: 100%; +} +.scroll { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow: auto; +} +#history-text { + margin: 16px 0; + padding: 8px; +} +#history-text .input { + margin-bottom: 8px; +} +#history-text .output { + margin-bottom: 16px; +} +#history-text .output-ok { + color: #0f8; +} +#history-text .output-error { + color: #f00; +} +.code { + font-family: "Courier New", Courier, monospace; + background-color: #111; + color: #fff; +} +section.source { + display: flex; + flex-direction: column; +} + +section.source textarea { + height: 32px; + padding: 8px; + margin-bottom: 16px; +} diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 8c22bdc9f8..008a08c8e7 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -2,8 +2,8 @@ window.js_create_app = js_create_app; window.js_run_app = js_run_app; window.js_get_result_and_memory = js_get_result_and_memory; -import * as roc_repl_wasm from "./roc_repl_wasm.js"; -import { getMockWasiImports } from "./wasi.js"; +import * as roc_repl_wasm from "/roc_repl_wasm.js"; +import { getMockWasiImports } from "/wasi.js"; // ---------------------------------------------------------------------------- // REPL state diff --git a/repl_www/public/repl/index.html b/repl_www/public/repl/index.html new file mode 100644 index 0000000000..5bc4ac7e76 --- /dev/null +++ b/repl_www/public/repl/index.html @@ -0,0 +1,29 @@ + + + + + + Roc REPL + + + + +
+
+

The rockin' Roc REPL

+
+ +
+
+
+
+
+ +
+ +
+
+ + + + diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 4e97c33803..9fd69a8153 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1,5 +1,5 @@ use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{Index, MutSet, SendMap}; +use roc_collections::all::{HumanIndex, MutSet, SendMap}; use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -350,7 +350,7 @@ fn to_expr_report<'b>( num_branches, .. } if num_branches == 2 => alloc.concat(vec![ - alloc.keyword(if index == Index::FIRST { + alloc.keyword(if index == HumanIndex::FIRST { "then" } else { "else" @@ -1384,7 +1384,7 @@ fn to_pattern_report<'b>( } } PReason::WhenMatch { index } => { - if index == Index::FIRST { + if index == HumanIndex::FIRST { let doc = alloc.stack(vec![ alloc .text("The 1st pattern in this ") diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 544c440164..a9eea79d7a 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -1,7 +1,7 @@ extern crate bumpalo; use self::bumpalo::Bump; -use roc_can::constraint::Constraint; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::env::Env; use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; @@ -28,14 +28,12 @@ pub fn test_home() -> ModuleId { pub fn infer_expr( subs: Subs, problems: &mut Vec, + constraints: &Constraints, constraint: &Constraint, expr_var: Variable, ) -> (Content, Subs) { - let env = solve::Env { - aliases: MutMap::default(), - vars_by_symbol: MutMap::default(), - }; - let (solved, _) = solve::run(&env, problems, subs, constraint); + let env = solve::Env::default(); + let (solved, _) = solve::run(constraints, &env, problems, subs, constraint); let content = solved .inner() @@ -99,6 +97,7 @@ pub struct CanExprOut { pub var_store: VarStore, pub var: Variable, pub constraint: Constraint, + pub constraints: Constraints, } #[derive(Debug)] @@ -155,9 +154,11 @@ pub fn can_expr_with<'a>( &loc_expr.value, ); + let mut constraints = Constraints::new(); let constraint = constrain_expr( + &mut constraints, &roc_constrain::expr::Env { - rigids: ImMap::default(), + rigids: MutMap::default(), home, }, loc_expr.region, @@ -177,7 +178,7 @@ pub fn can_expr_with<'a>( //load builtin values let (_introduced_rigids, constraint) = - constrain_imported_values(imports, constraint, &mut var_store); + constrain_imported_values(&mut constraints, imports, constraint, &mut var_store); let mut all_ident_ids = MutMap::default(); @@ -203,6 +204,7 @@ pub fn can_expr_with<'a>( interns, var, constraint, + constraints, }) } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index cb10807af5..ea9df598eb 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -62,6 +62,7 @@ mod test_reporting { output, var_store, var, + constraints, constraint, home, interns, @@ -79,7 +80,8 @@ mod test_reporting { } let mut unify_problems = Vec::new(); - let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + let (_content, mut subs) = + infer_expr(subs, &mut unify_problems, &constraints, &constraint, var); name_all_type_vars(var, &mut subs); @@ -4490,32 +4492,6 @@ mod test_reporting { ) } - #[test] - fn record_type_indent_end() { - report_problem_as( - indoc!( - r#" - f : { a: Int - } - "# - ), - indoc!( - r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── - - I am partway through parsing a record type, but I got stuck here: - - 1│ f : { a: Int - 2│ } - ^ - - I need this curly brace to be indented more. Try adding more spaces - before it! - "# - ), - ) - } - #[test] fn record_type_keyword_field_name() { report_problem_as( @@ -5451,36 +5427,6 @@ mod test_reporting { ) } - #[test] - fn list_bad_indent() { - report_problem_as( - indoc!( - r#" - x = [ 1, 2, - ] - - x - "# - ), - indoc!( - r#" - ── UNFINISHED LIST ───────────────────────────────────────────────────────────── - - I cannot find the end of this list: - - 1│ x = [ 1, 2, - ^ - - You could change it to something like [ 1, 2, 3 ] or even just []. - Anything where there is an open and a close square bracket, and where - the elements of the list are separated by commas. - - Note: I may be confused by indentation - "# - ), - ) - } - #[test] fn number_double_dot() { report_problem_as( @@ -6469,38 +6415,6 @@ I need all branches in an `if` to have the same type! ) } - #[test] - fn outdented_alias() { - report_problem_as( - indoc!( - r#" - Box item : [ - Box item, - Items item item - ] - - 4 - "# - ), - indoc!( - r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── - - I am partway through parsing a tag union type, but I got stuck here: - - 1│ Box item : [ - 2│ Box item, - 3│ Items item item - 4│ ] - ^ - - I need this square bracket to be indented more. Try adding more spaces - before it! - "# - ), - ) - } - #[test] fn outdented_in_parens() { report_problem_as( @@ -6532,36 +6446,6 @@ I need all branches in an `if` to have the same type! ) } - #[test] - fn outdented_record() { - report_problem_as( - indoc!( - r#" - Box : { - id: Str - } - - 4 - "# - ), - indoc!( - r#" - ── NEED MORE INDENTATION ─────────────────────────────────────────────────────── - - I am partway through parsing a record type, but I got stuck here: - - 1│ Box : { - 2│ id: Str - 3│ } - ^ - - I need this curly brace to be indented more. Try adding more spaces - before it! - "# - ), - ) - } - #[test] fn backpassing_type_error() { report_problem_as( @@ -8558,4 +8442,48 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn let_polymorphism_with_scoped_type_variables() { + report_problem_as( + indoc!( + r#" + f : a -> a + f = \x -> + y : a -> a + y = \z -> z + + n = y 1u8 + x1 = y x + (\_ -> x1) n + + f + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 1st argument to `y` is not what I expect: + + 6│ n = y 1u8 + ^^^ + + This argument is an integer of type: + + U8 + + But `y` needs the 1st argument to be: + + a + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `U8` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "# + ), + ) + } } diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index aa90d30258..bbb0fc5f90 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -183,12 +183,11 @@ is zero-configuration like `elm-format`) formats multi-line record literals (and record types) with a comma at the end of each line, like so: ```elm -user = - { - firstName: "Sam", - lastName: "Sample", - email: "sam@example.com", - } +user = { + firstName: "Sam", + lastName: "Sample", + email: "sam@example.com", +} ``` This is easy to read and leads to tidy version control diffs; no matter how @@ -456,25 +455,22 @@ The key is that each of the error types is a type alias for a Roc *tag union*. Here's how those look: ```elm -Http.Err a : - [ - PageNotFound, - Timeout, - BadPayload Str, - ]a +Http.Err a : [ + PageNotFound, + Timeout, + BadPayload Str, +]a -File.ReadErr a : - [ - FileNotFound, - Corrupted, - BadFormat, - ]a +File.ReadErr a : [ + FileNotFound, + Corrupted, + BadFormat, +]a -File.WriteErr a : - [ - FileNotFound, - DiskFull, - ]a +File.WriteErr a : [ + FileNotFound, + DiskFull, +]a ``` For a side-by-side comparison, here's how we would implement something similar in Elm: @@ -758,86 +754,6 @@ Elm does permit overriding open imports - e.g. if you have `import Foo exposing (bar)`, or `import Foo exposing (..)`, you can still define `bar = ...` in the module. Roc treats this as shadowing and does not allow it. -## Function equality - -In Elm, if you write `(\val -> val) == (\val -> val)`, you currently get a runtime exception -which links to [the `==` docs](https://package.elm-lang.org/packages/elm/core/latest/Basics#==), -which explain why this is the current behavior and what the better version will look like. - -> OCaml also has the "runtime exception if you compare functions for structural equality" -> behavior, but unlike Elm, in OCaml this appears to be the long-term design. - -In Roc, function equality is a compile error, tracked explicitly in the type system. -Here's the type of Roc's equality function: - -```elm -'val, 'val -> Bool -``` - -Whenever a named type variable in Roc has a `'` at the beginning, that means -it is a *functionless* type - a type which cannot involve functions. -If there are any functions in that type, you get a type mismatch. This is true -whether `val` itself is a function, or if it's a type that wraps a function, -like `{ predicate: (Str -> Bool) }` or `List (Bool -> Bool)`. - -So if you write `(\a -> a) == (\a -> a)` in Roc, you'll get a type mismatch. -If you wrap both sides of that `==` in a record or list, you'll still get a -type mismatch. - -If a named type variable has a `'` anywhere in a given type, then it must have a `'` -everywhere in that type. So it would be an error to have a type like `x, 'x -> Bool` -because `x` has a `'` in one place but not everywhere. - -## Standard Data Structures - -Elm has `List`, `Array`, `Set`, and `Dict` in the standard library. - -Roc has all of these except `Array`, and there are some differences in how they work: - -* `List` in Roc uses the term "list" the way Python does: to mean an ordered sequence of elements. Roc's `List` is more like an array, in that all the elements are sequential in memory and can be accessed in constant time. It still uses the `[` `]` syntax for list literals. Also there is no `::` operator because "cons" is not an efficient operation on an array like it is in a linked list. -* `Set` in Roc is like `Set` in Elm: it's shorthand for a `Dict` with keys but no value, and it has a slightly different API. -* `Dict` in Roc is like `Dict` in Elm, except it's backed by hashing rather than ordering. Roc silently computes hash values for any value that can be used with `==`, so instead of a `comparable` constraint on `Set` elements and `Dict` keys, in Roc they instead have the *functionless* constraint indicated with a `'`. - -Roc also has a literal syntax for dictionaries and sets. Here's how to write a `Dict` literal: - -```elm -{: "Sam" => True, "Ali" => False, firstName => False :} -``` - -This expression has the type `Dict Str Bool`, and the `firstName` variable would -necessarily be a `Str` as well. - -The `Dict` literal syntax is for two reasons. First, Roc doesn't have tuples; -without tuples, initializing the above `Dict` would involve an API that looked -something like one of these: - -```elm -Dict.fromList [ { k: "Sam", v: True }, { k: "Ali", v: False }, { k: firstName, v: False } ] - -Dict.fromList [ KV "Sam" True, KV "Ali" False KV firstName False -``` - -This works, but is not nearly as nice to read. - -Additionally, `Dict` literals can compile directly to efficient initialization code -without needing to (hopefully be able to) optimize away the intermediate -`List` involved in `fromList`. - -`{::}` is an empty `Dict`. - -You can write a `Set` literal like this: - -```elm -[: "Sam", "Ali", firstName :] -``` - -The `Set` literal syntax is partly for the initialization benefit, and also -for symmetry with the `Dict` literal syntax. - -`[::]` is an empty `Set`. - -Roc does not have syntax for pattern matching on data structures - not even `[` `]` like Elm does. - ## Operators In Elm, operators are functions. In Roc, all operators are syntax sugar. @@ -1318,51 +1234,6 @@ If you put these into a hypothetical Roc REPL, here's what you'd see: 28 : Int * ``` -## Phantom Types - -[Phantom types](https://medium.com/@ckoster22/advanced-types-in-elm-phantom-types-808044c5946d) -exist in Elm but not in Roc. This is because phantom types can't be defined -using type aliases (in fact, there is a custom error message in Elm if you -try to do this), and Roc only has type aliases. However, in Roc, you can achieve -the same API and runtime performance characteristics as if you had phantom types, -by using *phantom values* instead. - -A phantom value is one which affects types, but which holds no information at runtime. -As an example, let's say I wanted to define a [units library](https://package.elm-lang.org/packages/ianmackenzie/elm-units/latest/) - -a classic example of phantom types. I could do that in Roc like this: - -``` -Quantity units data : [ Quantity units data ] - -km : Num a -> Quantity [ Km ] (Num a) -km = \num -> - Quantity Km num - -cm : Num a -> Quantity [ Cm ] (Num a) -cm = \num -> - Quantity Cm num - -mm : Num a -> Quantity [ Mm ] (Num a) -mm = \num -> - Quantity Mm num - -add : Quantity u (Num a), Quantity u (Num a) -> Quantity u (Num a) -add = \Quantity units a, Quantity _ b -> - Quantity units (a + b) -``` - -From a performance perspective, it's relevant here that `[ Km ]`, `[ Cm ]`, and `[ Mm ]` -are all unions containing a single tag. That means they hold no information at runtime -(they would always destructure to the same tag), which means they can be "unboxed" away - -that is, discarded prior to code generation. - -During code generation, Roc treats `Quantity [ Km ] Int` as equivalent to `Quantity Int`. -Then, because `Quantity Int` is an alias for `[ Quantity Int ]`, it will unbox again -and reduce that all the way down to to `Int`. - -This means that, just like phantom *types*, phantom *values* affect type checking -only, and have no runtime overhead. Rust has a related concept called [phantom data](https://doc.rust-lang.org/nomicon/phantom-data.html). - ## Standard library `elm/core` has these modules: diff --git a/roc_std/Cargo.toml b/roc_std/Cargo.toml index 7565655d4c..03576dbeda 100644 --- a/roc_std/Cargo.toml +++ b/roc_std/Cargo.toml @@ -17,4 +17,4 @@ libc = "0.2.106" [features] default = ["platform"] -platform = [] +platform = [] \ No newline at end of file diff --git a/www/build.sh b/www/build.sh index af62488f96..ae07359997 100755 --- a/www/build.sh +++ b/www/build.sh @@ -11,9 +11,16 @@ cd $SCRIPT_RELATIVE_DIR rm -rf build/ cp -r public/ build/ -# grab the source code and copy it to Netlify's server; if it's not there, fail the build. pushd build +# grab the source code and copy it to Netlify's server; if it's not there, fail the build. wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip + +# grab the pre-compiled REPL and copy it to Netlify's server; if it's not there, fail the build. +wget https://github.com/brian-carroll/mock-repl/files/8167902/roc_repl_wasm.tar.gz +tar xzvf roc_repl_wasm.tar.gz +rm roc_repl_wasm.tar.gz +cp -r ../../repl_www/public/* . + popd # pushd .. diff --git a/www/netlify.sh b/www/netlify.sh new file mode 100644 index 0000000000..bf388c1ea4 --- /dev/null +++ b/www/netlify.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Runs on every Netlify build, to set up the Netlify server. + +set -euxo pipefail + +rustup update +rustup default stable + +# TODO remove this once we actually build the web repl! +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +REPL_WASM_DATA=${SCRIPT_DIR}/../repl_wasm/data/ +mkdir -p ${REPL_WASM_DATA} +touch ${REPL_WASM_DATA}/pre_linked_binary.o + +bash build.sh diff --git a/www/netlify.toml b/www/netlify.toml index 0e3070a0f6..640b5268f9 100644 --- a/www/netlify.toml +++ b/www/netlify.toml @@ -5,14 +5,19 @@ # https://docs.netlify.com/routing/headers/#syntax-for-the-netlify-configuration-file [build] publish = "build/" - command = "bash build.sh" + command = "bash netlify.sh" + # Always build on push - see https://answers.netlify.com/t/builds-cancelled-for-a-new-branch-due-to-no-content-change/17169/2 + ignore = "/bin/false" [[headers]] for = "/*" [headers.values] X-Frame-Options = "DENY" X-XSS-Protection = "1; mode=block" - Content-Security-Policy = "default-src 'self'; img-src *;" + # unsafe-eval is needed for wasm compilation in the repl to work on Safari and Chrome; + # otherwise they block it. + # TODO figure out how to tell Netlify to apply that policy only to the repl, not to everything. + Content-Security-Policy = "default-src 'self'; img-src *; script-src 'self' 'unsafe-eval';" X-Content-Type-Options = "nosniff" # Redirect roc-lang.org/authors to the AUTHORS file in this repo diff --git a/www/public/index.html b/www/public/index.html index 8b6d782d41..b8b5835b99 100644 --- a/www/public/index.html +++ b/www/public/index.html @@ -1,4 +1,4 @@ - + @@ -10,11 +10,12 @@