diff --git a/.cargo/config b/.cargo/config index 584d158f1c..ff64fd130a 100644 --- a/.cargo/config +++ b/.cargo/config @@ -8,3 +8,17 @@ test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --featur # opt-level=s Optimizations should focus more on size than speed # lto=fat Spend extra effort on link-time optimization across crates rustflags = ["-Copt-level=s", "-Clto=fat"] + +[env] +# Debug flags. Keep this up-to-date with compiler/debug_flags/src/lib.rs. +# Set = "1" to turn a debug flag on. +ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0" +ROC_PRINT_UNIFICATIONS = "0" +ROC_PRINT_MISMATCHES = "0" +ROC_VERIFY_RIGID_LET_GENERALIZED = "0" +ROC_PRINT_IR_AFTER_SPECIALIZATION = "0" +ROC_PRINT_IR_AFTER_RESET_REUSE = "0" +ROC_PRINT_IR_AFTER_REFCOUNT = "0" +ROC_DEBUG_ALIAS_ANALYSIS = "0" +ROC_PRINT_LLVM_FN_VERIFICATION = "0" +ROC_PRINT_LOAD_LOG = "0" \ No newline at end of file diff --git a/.llvmenv b/.llvmenv index 4044f90867..02161ca86e 100644 --- a/.llvmenv +++ b/.llvmenv @@ -1 +1 @@ -12.0.0 +13.0.0 diff --git a/AUTHORS b/AUTHORS index 554d987aac..ec21790a44 100644 --- a/AUTHORS +++ b/AUTHORS @@ -77,3 +77,6 @@ Cai Bingjun <62678643+C-BJ@users.noreply.github.com> Jared Cone Sean Hagstrom Kas Buunk +Oskar Hahn +Nuno Ferreira +Mfon Eti-mfon diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index dbd0d14196..6b4019bfa6 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -124,7 +124,7 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev ``` ### Zig -**version: 0.8.0** +**version: 0.9.1** For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations. @@ -136,14 +136,14 @@ If you prefer a package manager, you can try the following: If you want to install it manually, you can also download Zig directly [here](https://ziglang.org/download/). Just make sure you download the right version, the bleeding edge master build is the first download link on this page. ### LLVM -**version: 12.0.x** +**version: 13.0.x** -For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding -`$(brew --prefix llvm@12)/bin` to your `PATH`. You can confirm this worked by -running `llc --version` - it should mention "LLVM version 12.0.0" at the top. +For macOS, you can install LLVM 13 using `brew install llvm@13` and then adding +`$(brew --prefix llvm@13)/bin` to your `PATH`. You can confirm this worked by +running `llc --version` - it should mention "LLVM version 13.0.0" at the top. You may also need to manually specify a prefix env var like so: ``` -export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12 +export LLVM_SYS_130_PREFIX=/usr/local/opt/llvm@13 ``` For Ubuntu and Debian: @@ -151,19 +151,15 @@ For Ubuntu and Debian: sudo apt -y install lsb-release software-properties-common gnupg wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh -./llvm.sh 12 +./llvm.sh 13 ``` -If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`. -By default, the script installs them as `clang-12` and `llvm-as-12`, -respectively. You can address this with symlinks like so: +If you use this script, you'll need to add `clang` to your `PATH`. +By default, the script installs it as `clang-13`. You can address this with symlinks like so: ``` -sudo ln -s /usr/bin/clang-12 /usr/bin/clang +sudo ln -s /usr/bin/clang-13 /usr/bin/clang ``` -``` -sudo ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as -```` There are also alternative installation options at http://releases.llvm.org/download.html @@ -187,9 +183,9 @@ If you encounter `cannot find -lz` run `sudo apt install zlib1g-dev`. If you encounter: ``` error: No suitable version of LLVM was found system-wide or pointed - to by LLVM_SYS_120_PREFIX. + to by LLVM_SYS_130_PREFIX. ``` -Add `export LLVM_SYS_120_PREFIX=/usr/lib/llvm-12` to your `~/.bashrc` or equivalent file for your shell. +Add `export LLVM_SYS_130_PREFIX=/usr/lib/llvm-13` to your `~/.bashrc` or equivalent file for your shell. ### LLVM installation on macOS @@ -208,14 +204,14 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include" 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 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 the custom LLVM 7z archive [here](https://github.com/PLC-lang/llvm-package-windows/releases/tag/v13.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): +1. In powershell, set the `LLVM_SYS_130_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", + [Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\anton\Downloads\LLVM-13.0.1-win64\bin", "User" ) ``` @@ -242,8 +238,8 @@ Create `~/.cargo/config.toml` if it does not exist and add this to it: rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "target-cpu=native"] ``` -Then install `lld` version 12 (e.g. with `$ sudo apt-get install lld-12`) +Then install `lld` version 13 (e.g. with `$ sudo apt-get install lld-13`) and add make sure there's a `ld.lld` executable on your `PATH` which -is symlinked to `lld-12`. +is symlinked to `lld-13`. That's it! Enjoy the faster builds. diff --git a/Cargo.lock b/Cargo.lock index 0690cc0fb7..9f6afa7ccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,12 +4,12 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af0ac006645f86f20f6c6fa4dcaef920bf803df819123626f9440e35835e7d80" +checksum = "24606928a235e73cdef55a0c909719cadd72fce573e5713d58cb2952d8f5794c" dependencies = [ "ab_glyph_rasterizer", - "owned_ttf_parser 0.12.1", + "owned_ttf_parser 0.15.0", ] [[package]] @@ -33,12 +33,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" - [[package]] name = "ahash" version = "0.7.6" @@ -61,14 +55,14 @@ dependencies = [ [[package]] name = "alsa" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c4da790adcb2ce5e758c064b4f3ec17a30349f9961d3e5e6c9688b052a9e18" +checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b" dependencies = [ "alsa-sys", "bitflags", "libc", - "nix 0.20.0", + "nix 0.23.1", ] [[package]] @@ -114,9 +108,9 @@ dependencies = [ [[package]] name = "approx" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] @@ -143,7 +137,7 @@ version = "0.33.3+1.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" dependencies = [ - "libloading 0.7.1", + "libloading 0.7.3", ] [[package]] @@ -159,25 +153,31 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.63" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.27.1", + "object 0.28.3", "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74" + [[package]] name = "bincode" version = "1.3.3" @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.56.0" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ "bitflags", "cexpr", @@ -284,7 +284,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -310,9 +310,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byte-tools" @@ -321,19 +321,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] -name = "bytemuck" -version = "1.7.2" +name = "bytecheck" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" +checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bytemuck" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" +checksum = "562e382481975bc61d11275ac5e62a19abd00b0547d99516a415336f183dcd0e" dependencies = [ "proc-macro2", "quote", @@ -368,14 +389,14 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" dependencies = [ - "rustc_version", + "rustc_version 0.4.0", ] [[package]] name = "cc" -version = "1.0.71" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -388,11 +409,11 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cexpr" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 5.1.2", + "nom", ] [[package]] @@ -425,20 +446,20 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21" dependencies = [ "glob", "libc", - "libloading 0.7.1", + "libloading 0.7.3", ] [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", "textwrap 0.11.0", @@ -447,17 +468,41 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.0-beta.5" +version = "3.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" +checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0" dependencies = [ "atty", "bitflags", + "clap_derive", + "clap_lex", "indexmap", - "os_str_bytes", + "lazy_static", "strsim 0.10.0", "termcolor", - "textwrap 0.14.2", + "textwrap 0.15.0", +] + +[[package]] +name = "clap_derive" +version = "3.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -471,6 +516,7 @@ name = "cli_utils" version = "0.1.0" dependencies = [ "bumpalo", + "const_format", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", "rlimit", "roc_cli", @@ -495,9 +541,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "4.2.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed" +checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db" dependencies = [ "error-code", "str-buf", @@ -513,7 +559,7 @@ dependencies = [ "bitflags", "block", "cocoa-foundation", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "core-graphics 0.22.3", "foreign-types", "libc", @@ -528,7 +574,7 @@ checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" dependencies = [ "bitflags", "block", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "core-graphics-types", "foreign-types", "libc", @@ -558,9 +604,9 @@ dependencies = [ [[package]] name = "combine" -version = "4.6.2" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" dependencies = [ "bytes", "memchr", @@ -569,11 +615,12 @@ dependencies = [ [[package]] name = "confy" version = "0.4.0" -source = "git+https://github.com/rust-cli/confy#664992aecd97b4af0eda8d9d2825885662e1c6b4" +source = "git+https://github.com/rust-cli/confy#642822413139ce38a96b916190e8c7fd5b88e814" dependencies = [ "directories-next", "serde", "serde_yaml", + "thiserror", ] [[package]] @@ -587,10 +634,16 @@ dependencies = [ ] [[package]] -name = "const_format" -version = "0.2.22" +name = "const_fn" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22bc6cd49b0ec407b680c3e380182b6ac63b73991cb7602de350352fc309b614" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" + +[[package]] +name = "const_format" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0936ffe6d0c8d6a51b3b0a73b2acbe925d786f346cf45bfddc8341d79fb7dc8a" dependencies = [ "const_format_proc_macros", ] @@ -638,9 +691,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys 0.8.3", "libc", @@ -677,7 +730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "core-graphics-types", "foreign-types", "libc", @@ -690,7 +743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ "bitflags", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "foreign-types", "libc", ] @@ -720,18 +773,18 @@ dependencies = [ [[package]] name = "coreaudio-sys" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b7e3347be6a09b46aba228d6608386739fb70beff4f61e07422da87b0bb31fa" +checksum = "3dff444d80630d7073077d38d40b4501fd518bd2b922c2a55edcc8b0f7be57e6" dependencies = [ "bindgen", ] [[package]] name = "cpal" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98f45f0a21f617cd2c788889ef710b63f075c949259593ea09c826f1e47a2418" +checksum = "74117836a5124f3629e4b474eed03e479abaf98988b4bb317e29f08cfe0e4116" dependencies = [ "alsa", "core-foundation-sys 0.8.3", @@ -741,12 +794,12 @@ dependencies = [ "lazy_static", "libc", "mach", - "ndk 0.3.0", - "ndk-glue 0.3.0", - "nix 0.20.0", + "ndk 0.6.0", + "ndk-glue 0.6.2", + "nix 0.23.1", "oboe", "parking_lot 0.11.2", - "stdweb", + "stdweb 0.1.3", "thiserror", "web-sys", "winapi", @@ -754,33 +807,33 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] [[package]] name = "cranelift-bforest" -version = "0.74.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ca3560686e7c9c7ed7e0fe77469f2410ba5d7781b1acaa9adc8d8deea28e3e" +checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.74.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf9bf1ffffb6ce3d2e5ebc83549bd2436426c99b31cc550d521364cbe35d276" +checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" dependencies = [ "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli 0.24.0", + "gimli 0.25.0", "log", "regalloc", "smallvec", @@ -789,9 +842,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.74.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cc21936a5a6d07e23849ffe83e5c1f6f50305c074f4b2970ca50c13bf55b821" +checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -799,21 +852,21 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.74.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5b6ffaa87560bebe69a5446449da18090b126037920b0c1c6d5945f72faf6b" +checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" [[package]] name = "cranelift-entity" -version = "0.74.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6b4a8bef04f82e4296782646f733c641d09497df2fabf791323fefaa44c64c" +checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" [[package]] name = "cranelift-frontend" -version = "0.74.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b783b351f966fce33e3c03498cb116d16d97a8f9978164a60920bd0d3a99c" +checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", "log", @@ -823,9 +876,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] @@ -838,10 +891,10 @@ checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", "cast", - "clap 2.33.3", + "clap 2.34.0", "criterion-plot 0.4.4", "csv", - "itertools 0.10.1", + "itertools 0.10.3", "lazy_static", "num-traits", "oorandom", @@ -863,10 +916,10 @@ source = "git+https://github.com/Anton-4/criterion.rs#3e46ad2b234e36928fb5234d36 dependencies = [ "atty", "cast", - "clap 2.33.3", + "clap 2.34.0", "criterion-plot 0.4.3", "csv", - "itertools 0.10.1", + "itertools 0.10.3", "lazy_static", "num-traits", "oorandom", @@ -897,7 +950,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" dependencies = [ "cast", - "itertools 0.10.1", + "itertools 0.10.3", ] [[package]] @@ -916,9 +969,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -937,10 +990,11 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", @@ -950,9 +1004,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -960,9 +1014,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -976,7 +1030,7 @@ checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", - "itoa", + "itoa 0.4.8", "ryu", "serde", ] @@ -992,14 +1046,20 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" dependencies = [ "quote", "syn", ] +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "d3d12" version = "0.4.1" @@ -1007,7 +1067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" dependencies = [ "bitflags", - "libloading 0.7.1", + "libloading 0.7.3", "winapi", ] @@ -1023,12 +1083,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core 0.13.0", - "darling_macro 0.13.0", + "darling_core 0.13.4", + "darling_macro 0.13.4", ] [[package]] @@ -1047,9 +1107,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", @@ -1072,22 +1132,11 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core 0.13.0", - "quote", - "syn", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", + "darling_core 0.13.4", "quote", "syn", ] @@ -1113,7 +1162,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -1128,9 +1177,9 @@ dependencies = [ [[package]] name = "dirs" -version = "3.0.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ "dirs-sys", ] @@ -1147,9 +1196,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -1167,6 +1216,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "dispatch" version = "0.2.0" @@ -1194,7 +1249,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" dependencies = [ - "libloading 0.7.1", + "libloading 0.7.3", ] [[package]] @@ -1209,12 +1264,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - [[package]] name = "dunce" version = "1.0.2" @@ -1223,9 +1272,9 @@ checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" [[package]] name = "dynasm" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", @@ -1238,9 +1287,9 @@ dependencies = [ [[package]] name = "dynasmrt" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" dependencies = [ "byteorder", "dynasm", @@ -1266,21 +1315,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] -name = "enumset" -version = "1.0.8" +name = "enum-iterator" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumset" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" dependencies = [ - "darling 0.13.0", + "darling 0.13.4", "proc-macro2", "quote", "syn", @@ -1310,19 +1379,31 @@ dependencies = [ ] [[package]] -name = "erased-serde" -version = "0.3.16" +name = "errno" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de9ad4541d99dc22b59134e7ff8dc3d6c988c89ecd7324bf10a8362b07a2afa" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ - "serde", + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", ] [[package]] name = "error-code" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" dependencies = [ "libc", "str-buf", @@ -1341,14 +1422,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] -name = "fd-lock" -version = "3.0.2" +name = "fastrand" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "fd-lock" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e245f4c8ec30c6415c56cb132c07e69e74f1942f6b4a4061da748b49f486ca" dependencies = [ "cfg-if 1.0.0", - "libc", - "windows-sys 0.28.0", + "rustix", + "windows-sys 0.30.0", ] [[package]] @@ -1362,9 +1452,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" dependencies = [ "cfg-if 1.0.0", "crc32fast", @@ -1413,9 +1503,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -1428,9 +1518,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -1438,15 +1528,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -1455,18 +1545,16 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-macro" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ - "autocfg", - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -1474,23 +1562,22 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ - "autocfg", "futures-channel", "futures-core", "futures-io", @@ -1500,8 +1587,6 @@ dependencies = [ "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] @@ -1521,7 +1606,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" dependencies = [ "cfg-if 0.1.10", - "serde", ] [[package]] @@ -1535,9 +1619,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -1545,31 +1629,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", "libc", "wasi", ] -[[package]] -name = "ghost" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5bcf1bbeab73aa4cf2fde60a846858dc036163c7c33bec309f8d17de785479" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "gimli" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" dependencies = [ "fallible-iterator", "indexmap", @@ -1590,9 +1663,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "glow" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f04649123493bc2483cbef4daddb45d40bbdae5adb221a63a23efdb0cc99520" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" dependencies = [ "js-sys", "slotmap", @@ -1602,9 +1675,9 @@ dependencies = [ [[package]] name = "glyph_brush" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e3f00b8574a76fb6c50890c48da03946ca50e4372a2778737922666a2238221" +checksum = "a69c65dd1f1fbb6209aa00f78636e436ad0a55b7d8e5de886d00720dcad9c6e2" dependencies = [ "glyph_brush_draw_cache", "glyph_brush_layout", @@ -1616,9 +1689,9 @@ dependencies = [ [[package]] name = "glyph_brush_draw_cache" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2c82074cafb68b9e459c50c655f7eedcb92d6ee7166813802934bc6fc29fa3" +checksum = "6010675390f6889e09a21e2c8b575b3ee25667ea8237a8d59423f73cb8c28610" dependencies = [ "ab_glyph", "crossbeam-channel", @@ -1635,15 +1708,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc32c2334f00ca5ac3695c5009ae35da21da8c62d255b5b96d56e2597a637a38" dependencies = [ "ab_glyph", - "approx 0.5.0", + "approx 0.5.1", "xi-unicode", ] [[package]] name = "gpu-alloc" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e64cbb8d36508d3e19da95e56e196a84f674fc190881f2cc010000798838aa6" +checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" dependencies = [ "bitflags", "gpu-alloc-types", @@ -1660,13 +1733,13 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a237f0419ab10d17006d55c62ac4f689a6bf52c75d3f38b8361d249e8d4b0b" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" dependencies = [ "bitflags", "gpu-descriptor-types", - "hashbrown 0.9.1", + "hashbrown 0.11.2", ] [[package]] @@ -1684,25 +1757,31 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "hashbrown" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -dependencies = [ - "ahash 0.4.7", -] - [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash 0.7.6", + "ahash", "bumpalo", ] +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1732,9 +1811,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iced-x86" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f" +checksum = "158f5204401d08f91d19176112146d75e99b3cf745092e268fa7be33e09adcec" dependencies = [ "lazy_static", "static_assertions 1.1.0", @@ -1748,12 +1827,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "im" -version = "15.0.0" +version = "15.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111c1983f3c5bb72732df25cddacee9b546d08325fb584b5ebd38148be7b0246" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" dependencies = [ "bitmaps", - "rand_core 0.5.1", + "rand_core", "rand_xoshiro", "sized-chunks", "typenum", @@ -1762,12 +1841,12 @@ dependencies = [ [[package]] name = "im-rc" -version = "15.0.0" +version = "15.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" dependencies = [ "bitmaps", - "rand_core 0.5.1", + "rand_core", "rand_xoshiro", "sized-chunks", "typenum", @@ -1776,9 +1855,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg", "hashbrown 0.11.2", @@ -1787,12 +1866,9 @@ dependencies = [ [[package]] name = "indoc" -version = "1.0.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136" -dependencies = [ - "unindent", -] +checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" [[package]] name = "inkwell" @@ -1843,26 +1919,10 @@ dependencies = [ ] [[package]] -name = "inventory" -version = "0.1.10" +name = "io-lifetimes" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0f7efb804ec95e33db9ad49e4252f049e37e8b0a4652e3cd61f7999f2eff7f" -dependencies = [ - "ctor", - "ghost", - "inventory-impl", -] - -[[package]] -name = "inventory-impl" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c094e94816723ab936484666968f5b58060492e880f3c8d00489a1e244fa51" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "9448015e586b611e5d322f6703812bbca2f1e709d5773ecd38ddb4e3bb649504" [[package]] name = "itertools" @@ -1875,9 +1935,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -1888,6 +1948,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "jni" version = "0.19.0" @@ -1919,9 +1985,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -1933,7 +1999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" dependencies = [ "libc", - "libloading 0.7.1", + "libloading 0.7.3", ] [[package]] @@ -1973,9 +2039,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.106" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libloading" @@ -1989,9 +2055,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -1999,9 +2065,9 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.22" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1b8479c593dba88c2741fc50b92e13dbabbbe0bd504d979f244ccc1a5b1c01" +checksum = "11ca136052550448f55df7898c6dbe651c6b574fe38a0d9ea687a9f8088a2e2c" dependencies = [ "cc", ] @@ -2013,10 +2079,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] -name = "llvm-sys" -version = "120.2.3" +name = "linux-raw-sys" +version = "0.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce76f8393b7a607a906087666db398d872db739622e644e58552c198ccdfdf45" +checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" + +[[package]] +name = "llvm-sys" +version = "130.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95eb03b4f7ae21f48ef7c565a3e3aa22c50616aea64645fb1fd7f6f56b51c274" dependencies = [ "cc", "lazy_static", @@ -2027,18 +2099,19 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] @@ -2090,9 +2163,9 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" @@ -2103,15 +2176,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memmap2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" -dependencies = [ - "libc", -] - [[package]] name = "memmap2" version = "0.3.1" @@ -2132,9 +2196,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -2155,9 +2219,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.26" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb74897ce508e6c49156fd1476fc5922cbc6e75183c65e399c765a09122e5130" +checksum = "2f64ad83c969af2e732e907564deb0d0ed393cec4af80776f77dd77a1a427698" dependencies = [ "libmimalloc-sys", ] @@ -2190,12 +2254,11 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" dependencies = [ "adler", - "autocfg", ] [[package]] @@ -2213,9 +2276,9 @@ dependencies = [ [[package]] name = "mio-misc" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ddf05411bb159cdb5801bb10002afb66cb4572be656044315e363460ce69dc2" +checksum = "b47412f3a52115b936ff2a229b803498c7b4d332adeb87c2f1498c9da54c398c" dependencies = [ "crossbeam", "crossbeam-queue", @@ -2234,9 +2297,9 @@ dependencies = [ [[package]] name = "more-asserts" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "morphic_lib" @@ -2250,9 +2313,9 @@ dependencies = [ [[package]] name = "naga" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda66d09f712e1f0a6ab436137da4fac312f78301f6d4ac7cb8bfe96e988734f" +checksum = "806f448a7ce662ca79ef5484ef8f451a9b7c51b8166c95f5a667228b3825a6ca" dependencies = [ "bit-set", "bitflags", @@ -2273,24 +2336,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" dependencies = [ "jni-sys", - "ndk-sys", + "ndk-sys 0.2.2", "num_enum", "thiserror", ] [[package]] name = "ndk" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" dependencies = [ "bitflags", "jni-sys", - "ndk-sys", + "ndk-sys 0.3.0", "num_enum", "thiserror", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "ndk-glue" version = "0.3.0" @@ -2301,22 +2370,23 @@ dependencies = [ "libc", "log", "ndk 0.3.0", - "ndk-macro", - "ndk-sys", + "ndk-macro 0.2.0", + "ndk-sys 0.2.2", ] [[package]] name = "ndk-glue" -version = "0.4.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e9e94628f24e7a3cb5b96a2dc5683acd9230bf11991c2a1677b87695138420" +checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f" dependencies = [ "lazy_static", "libc", "log", - "ndk 0.4.0", - "ndk-macro", - "ndk-sys", + "ndk 0.6.0", + "ndk-context", + "ndk-macro 0.3.0", + "ndk-sys 0.3.0", ] [[package]] @@ -2333,10 +2403,32 @@ dependencies = [ ] [[package]] -name = "ndk-sys" -version = "0.2.1" +name = "ndk-macro" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling 0.13.4", + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] [[package]] name = "nibble_vec" @@ -2373,9 +2465,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" dependencies = [ "bitflags", "cc", @@ -2399,23 +2491,12 @@ dependencies = [ [[package]] name = "nom" -version = "5.1.2" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "memchr", - "version_check", -] - -[[package]] -name = "nom" -version = "7.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] @@ -2426,9 +2507,9 @@ checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi", ] @@ -2446,18 +2527,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -2465,21 +2546,20 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" dependencies = [ - "derivative", "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -2524,17 +2604,6 @@ dependencies = [ "objc", ] -[[package]] -name = "object" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7" -dependencies = [ - "crc32fast", - "indexmap", - "memchr", -] - [[package]] name = "object" version = "0.26.2" @@ -2549,22 +2618,25 @@ dependencies = [ [[package]] name = "object" -version = "0.27.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" dependencies = [ + "crc32fast", + "hashbrown 0.11.2", + "indexmap", "memchr", ] [[package]] name = "oboe" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e15e22bc67e047fe342a32ecba55f555e3be6166b04dd157cd0f803dfa9f48e1" +checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1" dependencies = [ "jni", - "ndk 0.4.0", - "ndk-glue 0.4.0", + "ndk 0.6.0", + "ndk-context", "num-derive", "num-traits", "oboe-sys", @@ -2572,9 +2644,9 @@ dependencies = [ [[package]] name = "oboe-sys" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338142ae5ab0aaedc8275aa8f67f460e43ae0fca76a695a742d56da0a269eadc" +checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" dependencies = [ "cc", ] @@ -2590,9 +2662,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "oorandom" @@ -2614,27 +2686,24 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "ordered-float" -version = "2.8.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c9d06878b3a851e8026ef94bf7fef9ba93062cd412601da4d9cf369b1cc62d" +checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2" dependencies = [ "num-traits", ] [[package]] name = "os_str_bytes" -version = "4.2.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" -dependencies = [ - "memchr", -] +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" [[package]] name = "output_vt100" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ "winapi", ] @@ -2650,11 +2719,11 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60ac8dda2e5cc09bf6480e3b3feff9783db251710c922ae9369a429c51efdeb0" +checksum = "4fb1e509cfe7a12db2a90bfa057dfcdbc55a347f5da677c506b53dd099cfec9d" dependencies = [ - "ttf-parser 0.12.3", + "ttf-parser 0.15.0", ] [[package]] @@ -2695,7 +2764,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9735f7e1e51a3f740bacd5dc2724b61a7806f23597a8736e679f38ee3435d18" dependencies = [ - "approx 0.5.0", + "approx 0.5.1", "num-traits", "palette_derive", "phf", @@ -2731,7 +2800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.3", ] [[package]] @@ -2750,15 +2819,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-sys 0.32.0", + "windows-sys 0.36.1", ] [[package]] @@ -2889,9 +2958,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -2901,9 +2970,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.22" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "plotters" @@ -2947,15 +3016,15 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty_assertions" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" +checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" dependencies = [ "ansi_term", "ctor", @@ -2974,9 +3043,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", @@ -3012,26 +3081,20 @@ version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" dependencies = [ "unicode-xid", ] [[package]] name = "profiling" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9926767b8b8244d7b6b64546585121d193c3d0b4856ccd656b7bfa9deb91ab6a" +checksum = "9145ac0af1d93c638c98c40cf7d25665f427b2a44ad0a99b1dccf3e2f25bb987" [[package]] name = "ptr_meta" @@ -3097,9 +3160,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.10" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -3128,14 +3191,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", - "rand_hc", + "rand_core", ] [[package]] @@ -3145,15 +3207,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.3" @@ -3163,22 +3219,13 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "rand_xoshiro" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core 0.5.1", + "rand_core", ] [[package]] @@ -3189,18 +3236,28 @@ checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" [[package]] name = "raw-window-handle" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" +checksum = "e28f55143d0548dad60bb4fbdc835a3d7ac6acc3324506450c5fdd6e42903a76" dependencies = [ "libc", + "raw-window-handle 0.4.3", +] + +[[package]] +name = "raw-window-handle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +dependencies = [ + "cty", ] [[package]] name = "rayon" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221" dependencies = [ "autocfg", "crossbeam-deque", @@ -3210,34 +3267,34 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall", + "thiserror", ] [[package]] @@ -3253,9 +3310,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", @@ -3276,9 +3333,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "region" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" dependencies = [ "bitflags", "libc", @@ -3308,6 +3365,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "rend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +dependencies = [ + "bytecheck", +] + [[package]] name = "renderdoc-sys" version = "0.7.1" @@ -3330,21 +3396,23 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.6.7" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb135b3e5e3311f0a254bfb00333f4bac9ef1d89888b84242a89eb8722b09a07" +checksum = "517a3034eb2b1499714e9d1e49b2367ad567e07639b69776d35e259d9c27cca6" dependencies = [ - "memoffset", + "bytecheck", + "hashbrown 0.12.1", "ptr_meta", + "rend", "rkyv_derive", "seahash", ] [[package]] name = "rkyv_derive" -version = "0.6.7" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8f489f6b6d8551bb15904293c1ad58a6abafa7d8390d15f7ed05a2afcd87d5" +checksum = "505c209ee04111a006431abf39696e640838364d67a107c559ababaf6fd8c9dd" dependencies = [ "proc-macro2", "quote", @@ -3360,12 +3428,37 @@ dependencies = [ "libc", ] +[[package]] +name = "roc-bindgen" +version = "0.1.0" +dependencies = [ + "bumpalo", + "clap 3.1.17", + "indoc", + "pretty_assertions", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_error_macros", + "roc_load", + "roc_module", + "roc_mono", + "roc_reporting", + "roc_std", + "roc_target", + "roc_types", + "target-lexicon", + "tempfile", + "ven_graph", +] + [[package]] name = "roc_alias_analysis" version = "0.1.0" dependencies = [ "morphic_lib", "roc_collections", + "roc_debug_flags", "roc_module", "roc_mono", ] @@ -3403,7 +3496,7 @@ version = "0.1.0" dependencies = [ "bumpalo", "inkwell 0.1.0", - "libloading 0.7.1", + "libloading 0.7.3", "roc_builtins", "roc_can", "roc_collections", @@ -3427,6 +3520,7 @@ dependencies = [ "serde_json", "target-lexicon", "tempfile", + "wasi_libc_sys", ] [[package]] @@ -3450,16 +3544,15 @@ dependencies = [ "bumpalo", "indoc", "pretty_assertions", - "roc_builtins", "roc_collections", "roc_error_macros", + "roc_exhaustive", "roc_module", "roc_parse", "roc_problem", "roc_region", "roc_types", "static_assertions 1.1.0", - "ven_graph", ] [[package]] @@ -3467,7 +3560,7 @@ name = "roc_cli" version = "0.1.0" dependencies = [ "bumpalo", - "clap 3.0.0-beta.5", + "clap 3.1.17", "cli_utils", "const_format", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", @@ -3493,6 +3586,8 @@ dependencies = [ "roc_target", "roc_test_utils", "serial_test", + "strum", + "strum_macros", "target-lexicon", "tempfile", "wasmer", @@ -3504,7 +3599,7 @@ name = "roc_code_markup" version = "0.1.0" dependencies = [ "bumpalo", - "itertools 0.10.1", + "itertools 0.10.3", "palette", "roc_ast", "roc_module", @@ -3517,6 +3612,7 @@ dependencies = [ name = "roc_collections" version = "0.1.0" dependencies = [ + "bitvec 1.0.0", "bumpalo", "hashbrown 0.11.2", "im", @@ -3539,12 +3635,15 @@ dependencies = [ "roc_types", ] +[[package]] +name = "roc_debug_flags" +version = "0.1.0" + [[package]] name = "roc_docs" version = "0.1.0" dependencies = [ "bumpalo", - "indoc", "peg", "pretty_assertions", "pulldown-cmark", @@ -3562,8 +3661,14 @@ dependencies = [ "roc_target", "roc_types", "snafu", - "tempfile", - "uuid", +] + +[[package]] +name = "roc_docs_cli" +version = "0.1.0" +dependencies = [ + "clap 3.1.17", + "roc_docs", ] [[package]] @@ -3678,6 +3783,7 @@ dependencies = [ "roc_alias_analysis", "roc_builtins", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_mono", @@ -3711,9 +3817,6 @@ dependencies = [ [[package]] name = "roc_ident" version = "0.1.0" -dependencies = [ - "arrayvec 0.7.2", -] [[package]] name = "roc_linker" @@ -3721,7 +3824,7 @@ version = "0.1.0" dependencies = [ "bincode", "bumpalo", - "clap 3.0.0-beta.5", + "clap 3.1.17", "iced-x86", "memmap2 0.5.3", "object 0.26.2", @@ -3756,14 +3859,13 @@ dependencies = [ "crossbeam", "indoc", "maplit", - "morphic_lib", - "num_cpus", "parking_lot 0.12.0", "pretty_assertions", "roc_builtins", "roc_can", "roc_collections", "roc_constrain", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_mono", @@ -3776,7 +3878,6 @@ dependencies = [ "roc_test_utils", "roc_types", "roc_unify", - "tempfile", "ven_pretty", ] @@ -3784,7 +3885,6 @@ dependencies = [ name = "roc_module" version = "0.1.0" dependencies = [ - "arrayvec 0.7.2", "bumpalo", "lazy_static", "roc_collections", @@ -3801,10 +3901,10 @@ version = "0.1.0" dependencies = [ "bumpalo", "hashbrown 0.11.2", - "morphic_lib", "roc_builtins", "roc_can", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_exhaustive", "roc_module", @@ -3816,7 +3916,6 @@ dependencies = [ "roc_types", "roc_unify", "static_assertions 1.1.0", - "ven_graph", "ven_pretty", ] @@ -3862,7 +3961,7 @@ dependencies = [ "bumpalo", "const_format", "inkwell 0.1.0", - "libloading 0.7.1", + "libloading 0.7.3", "roc_build", "roc_builtins", "roc_collections", @@ -3936,7 +4035,6 @@ dependencies = [ "roc_exhaustive", "roc_load", "roc_module", - "roc_mono", "roc_parse", "roc_problem", "roc_region", @@ -3944,7 +4042,6 @@ dependencies = [ "roc_target", "roc_test_utils", "roc_types", - "tempfile", "ven_pretty", ] @@ -3955,11 +4052,15 @@ dependencies = [ "arrayvec 0.7.2", "bumpalo", "indoc", + "lazy_static", "pretty_assertions", + "regex", "roc_builtins", "roc_can", "roc_collections", + "roc_debug_flags", "roc_error_macros", + "roc_exhaustive", "roc_load", "roc_module", "roc_parse", @@ -4001,6 +4102,7 @@ version = "0.1.0" dependencies = [ "bumpalo", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_region", @@ -4014,6 +4116,7 @@ version = "0.1.0" dependencies = [ "bitflags", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_types", @@ -4051,13 +4154,36 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.4", + "semver 1.0.9", +] + +[[package]] +name = "rustix" +version = "0.34.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e74b3f02f2b6eb33790923756784614f456de79d821d6b2670dc7d5fbea807" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "winapi", ] [[package]] @@ -4072,9 +4198,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "rustyline" @@ -4083,7 +4209,7 @@ source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896 dependencies = [ "bitflags", "cfg-if 1.0.0", - "clipboard-win 4.2.2", + "clipboard-win 4.4.1", "dirs-next", "fd-lock", "libc", @@ -4110,9 +4236,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "same-file" @@ -4143,18 +4269,33 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "semver" -version = "0.11.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", ] [[package]] name = "semver" -version = "1.0.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", +] + +[[package]] +name = "semver" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "semver-parser" @@ -4167,9 +4308,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.130" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] @@ -4188,9 +4329,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" dependencies = [ "serde", ] @@ -4207,9 +4348,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -4218,23 +4359,23 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.69" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ - "itoa", + "itoa 1.0.1", "ryu", "serde", ] [[package]] name = "serde_yaml" -version = "0.8.21" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" +checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" dependencies = [ - "dtoa", "indexmap", + "ryu", "serde", "yaml-rust", ] @@ -4274,10 +4415,25 @@ dependencies = [ ] [[package]] -name = "sha2" -version = "0.9.8" +name = "sha1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", @@ -4288,15 +4444,15 @@ dependencies = [ [[package]] name = "shlex" -version = "0.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "siphasher" -version = "0.3.7" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "sized-chunks" @@ -4310,9 +4466,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "slice-deque" @@ -4336,9 +4492,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smithay-client-toolkit" @@ -4361,20 +4517,20 @@ dependencies = [ [[package]] name = "smithay-client-toolkit" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210cf40de565aaaa085face1d860b17f6aee9f76f9d2816307ea2cc45eeb64f3" +checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3" dependencies = [ "bitflags", "dlib 0.5.0", "lazy_static", "log", "memmap2 0.3.1", - "nix 0.22.0", + "nix 0.22.3", "pkg-config", - "wayland-client 0.29.1", - "wayland-cursor 0.29.1", - "wayland-protocols 0.29.1", + "wayland-client 0.29.4", + "wayland-cursor 0.29.4", + "wayland-protocols 0.29.4", ] [[package]] @@ -4383,8 +4539,8 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610b551bd25378bfd2b8e7a0fcbd83d427e8f2f6a40c47ae0f70688e9949dd55" dependencies = [ - "smithay-client-toolkit 0.15.2", - "wayland-client 0.29.1", + "smithay-client-toolkit 0.15.4", + "wayland-client 0.29.4", ] [[package]] @@ -4425,6 +4581,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + [[package]] name = "static_assertions" version = "0.1.1" @@ -4443,6 +4608,55 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version 0.2.3", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "str-buf" version = "1.0.5" @@ -4471,10 +4685,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "syn" -version = "1.0.81" +name = "strum" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" + +[[package]] +name = "strum_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -4489,19 +4722,19 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", + "fastrand", "libc", - "rand", "redox_syscall", "remove_dir_all 0.5.3", "winapi", @@ -4509,9 +4742,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] @@ -4525,7 +4758,7 @@ dependencies = [ "indoc", "inkwell 0.1.0", "libc", - "libloading 0.7.1", + "libloading 0.7.3", "roc_build", "roc_builtins", "roc_can", @@ -4558,7 +4791,6 @@ version = "0.1.0" dependencies = [ "bumpalo", "indoc", - "pretty_assertions", "roc_builtins", "roc_can", "roc_collections", @@ -4590,24 +4822,24 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -4625,14 +4857,42 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" dependencies = [ + "const_fn", "libc", + "standback", + "stdweb 0.4.20", + "time-macros", + "version_check", "winapi", ] +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -4645,9 +4905,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -4660,18 +4920,18 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if 1.0.0", "log", @@ -4682,9 +4942,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -4693,9 +4953,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ "lazy_static", ] @@ -4708,15 +4968,15 @@ checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" [[package]] name = "ttf-parser" -version = "0.12.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" +checksum = "c74c96594835e10fa545e2a51e8709f30b173a092bfd6036ef2cec53376244f3" [[package]] name = "twox-hash" -version = "1.6.1" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f559b464de2e2bdabcac6a210d12e9b5a5973c251e102c44c585c71d51bd78e" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if 1.0.0", "rand", @@ -4731,33 +4991,9 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" [[package]] name = "typenum" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" - -[[package]] -name = "typetag" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422619e1a7299befb977a1f6d8932c499f6151dbcafae715193570860cae8f07" -dependencies = [ - "erased-serde", - "inventory", - "lazy_static", - "serde", - "typetag-impl", -] - -[[package]] -name = "typetag-impl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504f9626fe6cc1c376227864781996668e15c1ff251d222f63ef17f310bf1fec" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" @@ -4776,9 +5012,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -4788,15 +5024,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "unindent" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "utf8parse" @@ -4838,9 +5068,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vte" @@ -4880,11 +5110,15 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi_libc_sys" +version = "0.1.0" + [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -4892,9 +5126,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -4907,9 +5141,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -4919,9 +5153,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4929,9 +5163,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -4942,22 +5176,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "wasmer" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f52e455a01d0fac439cd7a96ba9b519bdc84e923a5b96034054697ebb17cd75" +checksum = "f727a39e7161f7438ddb8eafe571b67c576a8c2fb459f666d9053b5bba4afdea" dependencies = [ "cfg-if 1.0.0", "indexmap", + "js-sys", "loupe", "more-asserts", "target-lexicon", "thiserror", + "wasm-bindgen", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-compiler-singlepass", @@ -4967,14 +5203,15 @@ dependencies = [ "wasmer-engine-universal", "wasmer-types", "wasmer-vm", + "wat", "winapi", ] [[package]] name = "wasmer-compiler" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc86dda6f715f03104800be575a38382b35c3962953af9e9d8722dcf0bd2458f" +checksum = "4e9951599222eb12bd13d4d91bcded0a880e4c22c2dfdabdf5dc7e5e803b7bf3" dependencies = [ "enumset", "loupe", @@ -4991,18 +5228,19 @@ dependencies = [ [[package]] name = "wasmer-compiler-cranelift" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a570746cbec434179e2d53357973a34dfdb208043104e8fac3b7b0023015cf6" +checksum = "44c83273bce44e668f3a2b9ccb7f1193db918b1d6806f64acc5ff71f6ece5f20" dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "gimli 0.24.0", + "gimli 0.25.0", "loupe", "more-asserts", "rayon", "smallvec", + "target-lexicon", "tracing", "wasmer-compiler", "wasmer-types", @@ -5011,9 +5249,9 @@ dependencies = [ [[package]] name = "wasmer-compiler-singlepass" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9429b9f7708c582d855b1787f09c7029ff23fb692550d4a1cc351c8ea84c3014" +checksum = "5432e993840cdb8e6875ddc8c9eea64e7a129579b4706bd91b8eb474d9c4a860" dependencies = [ "byteorder", "dynasm", @@ -5030,9 +5268,9 @@ dependencies = [ [[package]] name = "wasmer-derive" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee7b351bcc1e782997c72dc0b5b328f3ddcad4813b8ce3cac3f25ae5a4ab56b" +checksum = "458dbd9718a837e6dbc52003aef84487d79eedef5fa28c7d28b6784be98ac08e" dependencies = [ "proc-macro-error", "proc-macro2", @@ -5042,14 +5280,15 @@ dependencies = [ [[package]] name = "wasmer-engine" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8454ead320a4017ba36ddd9ab4fbf7776fceea6ab0b79b5e53664a1682569fc3" +checksum = "6ed603a6d037ebbb14014d7f739ae996a78455a4b86c41cfa4e81c590a1253b9" dependencies = [ "backtrace", + "enumset", "lazy_static", "loupe", - "memmap2 0.2.3", + "memmap2 0.5.3", "more-asserts", "rustc-demangle", "serde", @@ -5063,14 +5302,17 @@ dependencies = [ [[package]] name = "wasmer-engine-dylib" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa390d123ebe23d5315c39f6063fcc18319661d03c8000f23d0fe1c011e8135" +checksum = "ccd7fdc60e252a795c849b3f78a81a134783051407e7e279c10b7019139ef8dc" dependencies = [ "cfg-if 1.0.0", + "enum-iterator", + "enumset", "leb128", - "libloading 0.7.1", + "libloading 0.7.3", "loupe", + "object 0.28.3", "rkyv", "serde", "tempfile", @@ -5085,11 +5327,13 @@ dependencies = [ [[package]] name = "wasmer-engine-universal" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dffe8015f08915eb4939ebc8e521cde8246f272f5197ea60d46214ac5aef285" +checksum = "dcff0cd2c01a8de6009fd863b14ea883132a468a24f2d2ee59dc34453d3a31b5" dependencies = [ "cfg-if 1.0.0", + "enum-iterator", + "enumset", "leb128", "loupe", "region", @@ -5103,11 +5347,11 @@ dependencies = [ [[package]] name = "wasmer-object" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c541c985799fc1444702501c15d41becfb066c92d9673defc1c7417fd8739e15" +checksum = "24ce18ac2877050e59580d27ee1a88f3192d7a31e77fbba0852abc7888d6e0b5" dependencies = [ - "object 0.25.3", + "object 0.28.3", "thiserror", "wasmer-compiler", "wasmer-types", @@ -5115,9 +5359,9 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91f75d3c31f8b1f8d818ff49624fc974220243cbc07a2252f408192e97c6b51" +checksum = "659fa3dd6c76f62630deff4ac8c7657b07f0b1e4d7e0f8243a552b9d9b448e24" dependencies = [ "indexmap", "loupe", @@ -5127,14 +5371,26 @@ dependencies = [ ] [[package]] -name = "wasmer-vm" -version = "2.0.0" +name = "wasmer-vfs" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469a12346a4831e7dac639b9646d8c9b24c7d2cf0cf458b77f489edb35060c1f" +checksum = "f02fc47308cf5cf2cc039ec61c098773320b3d3c099434f20580bd143beee63b" +dependencies = [ + "libc", + "thiserror", + "tracing", +] + +[[package]] +name = "wasmer-vm" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdc46158517c2769f9938bc222a7d41b3bb330824196279d8aa2d667cd40641" dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", + "enum-iterator", "indexmap", "libc", "loupe", @@ -5150,31 +5406,30 @@ dependencies = [ [[package]] name = "wasmer-wasi" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a992dcafd11c584f3b8d84b7b4c6af350b63db03cf4452fc38647e8eab45994" +checksum = "3087d48fe015928118ae23f66f05b533e75fbea5dfcd64c75a74b7b5f941cc65" dependencies = [ - "bincode", + "cfg-if 1.0.0", "generational-arena", "getrandom", "libc", - "serde", "thiserror", "tracing", - "typetag", + "wasm-bindgen", "wasmer", + "wasmer-vfs", "wasmer-wasi-types", "winapi", ] [[package]] name = "wasmer-wasi-types" -version = "2.0.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a275df0190f65f9e62f25b6bc8505a55cc643e433c822fb03a5e3e11fe1c29" +checksum = "69adbd8d0d89cd19fb8b1e0252c76e3f72dbc65c944f0db7a9c28c4157fbcd3a" dependencies = [ "byteorder", - "serde", "time", "wasmer-types", ] @@ -5185,6 +5440,26 @@ version = "0.78.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" +[[package]] +name = "wast" +version = "40.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb4f48a8b083dbc50e291e430afb8f524092bb00428957bcc63f49f856c64ac" +dependencies = [ + "leb128", + "memchr", + "unicode-width", +] + +[[package]] +name = "wat" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0401b6395ce0db91629a75b29597ccb66ea29950af9fc859f1bb3a736609c76e" +dependencies = [ + "wast", +] + [[package]] name = "wayland-client" version = "0.28.6" @@ -5203,18 +5478,18 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.29.1" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9108ec1c37f4774d0c2937ba1a6c23d1786b2152c4a13bd9fdb20e42d16e8841" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" dependencies = [ "bitflags", "downcast-rs", "libc", - "nix 0.22.0", + "nix 0.22.3", "scoped-tls", - "wayland-commons 0.29.1", - "wayland-scanner 0.29.1", - "wayland-sys 0.29.1", + "wayland-commons 0.29.4", + "wayland-scanner 0.29.4", + "wayland-sys 0.29.4", ] [[package]] @@ -5231,14 +5506,14 @@ dependencies = [ [[package]] name = "wayland-commons" -version = "0.29.1" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265ef51b3b3e5c9ef098f10425c39624663f459c3821dcaacc4748be975f1beb" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" dependencies = [ - "nix 0.22.0", + "nix 0.22.3", "once_cell", "smallvec", - "wayland-sys 0.29.1", + "wayland-sys 0.29.4", ] [[package]] @@ -5254,12 +5529,12 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.29.1" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c19bb6628daf4097e58b7911481e8371e13318d5a60894779901bd3267407a7" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" dependencies = [ - "nix 0.22.0", - "wayland-client 0.29.1", + "nix 0.22.3", + "wayland-client 0.29.4", "xcursor", ] @@ -5277,14 +5552,14 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.29.1" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3b6f1dc0193072ef4eadcb144da30d58c1f2895516c063804d213310703c8e" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" dependencies = [ "bitflags", - "wayland-client 0.29.1", - "wayland-commons 0.29.1", - "wayland-scanner 0.29.1", + "wayland-client 0.29.4", + "wayland-commons 0.29.4", + "wayland-scanner 0.29.4", ] [[package]] @@ -5300,9 +5575,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.29.1" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaaf2bc85e7b9143159af96bd23d954a5abe391c4376db712320643280fdc6f4" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" dependencies = [ "proc-macro2", "quote", @@ -5322,9 +5597,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.29.1" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba9e06acb775b3007f8d3094438306979e572d1d3b844d7a71557a84b055d959" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" dependencies = [ "dlib 0.5.0", "lazy_static", @@ -5333,9 +5608,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -5343,15 +5618,15 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1577ecc4f6992b9e965878ac594efb24eed2bdf089c11f45b3d1c5f216e2e30" +checksum = "eae7181fe6ba5f4b632a9079cc9e922a64555156c87def72c063f94b180c7d68" dependencies = [ "arrayvec 0.7.2", "js-sys", "log", "parking_lot 0.11.2", - "raw-window-handle", + "raw-window-handle 0.3.4", "smallvec", "wasm-bindgen", "wasm-bindgen-futures", @@ -5363,9 +5638,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdcbfa4885b32c2b1feb2faeb8b6a76065b752b8f08751b82f994e937687f46" +checksum = "35600627b6c718ad0e23ed75fb6140bfe32cdf21c8f539ce3c9ab8180e2cb38e" dependencies = [ "arrayvec 0.7.2", "bitflags", @@ -5376,7 +5651,7 @@ dependencies = [ "naga", "parking_lot 0.11.2", "profiling", - "raw-window-handle", + "raw-window-handle 0.3.4", "smallvec", "thiserror", "wgpu-hal", @@ -5385,9 +5660,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e493835d9edb153d5c8a9d8d016e1811dbe32ddb707a110be1453c7b051d3ec" +checksum = "af28b29ef0b44cd22dd9895d4349b9d5a687df42f58da234871198637eabe328" dependencies = [ "arrayvec 0.7.2", "ash", @@ -5404,7 +5679,7 @@ dependencies = [ "inplace_it", "js-sys", "khronos-egl", - "libloading 0.7.1", + "libloading 0.7.3", "log", "metal", "naga", @@ -5412,7 +5687,7 @@ dependencies = [ "parking_lot 0.11.2", "profiling", "range-alloc", - "raw-window-handle", + "raw-window-handle 0.3.4", "renderdoc-sys", "thiserror", "wasm-bindgen", @@ -5432,9 +5707,9 @@ dependencies = [ [[package]] name = "wgpu_glyph" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c78d49f4d168b245882ce000ee94fc67e744b33760c0119b0dbf8cb3caf20de5" +checksum = "3f5774759a6288a2bf29dd56d04ef79e73c1bc2b1c289fdf25e502a2a7dc3eda" dependencies = [ "bytemuck", "glyph_brush", @@ -5444,9 +5719,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -5486,89 +5761,89 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" +checksum = "030b7ff91626e57a05ca64a07c481973cbb2db774e4852c9c7ca342408c6a99a" dependencies = [ - "windows_aarch64_msvc 0.28.0", - "windows_i686_gnu 0.28.0", - "windows_i686_msvc 0.28.0", - "windows_x86_64_gnu 0.28.0", - "windows_x86_64_msvc 0.28.0", + "windows_aarch64_msvc 0.30.0", + "windows_i686_gnu 0.30.0", + "windows_i686_msvc 0.30.0", + "windows_x86_64_gnu 0.30.0", + "windows_x86_64_msvc 0.30.0", ] [[package]] name = "windows-sys" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] name = "windows_aarch64_msvc" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" +checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca" [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" +checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" +checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" +checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" +checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "winit" @@ -5578,7 +5853,7 @@ checksum = "79610794594d5e86be473ef7763f604f2159cbac8c94debd00df8fb41e86c2f8" dependencies = [ "bitflags", "cocoa", - "core-foundation 0.9.2", + "core-foundation 0.9.3", "core-graphics 0.22.3", "core-video-sys", "dispatch", @@ -5590,11 +5865,11 @@ dependencies = [ "mio-misc", "ndk 0.3.0", "ndk-glue 0.3.0", - "ndk-sys", + "ndk-sys 0.2.2", "objc", "parking_lot 0.11.2", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.3.4", "scopeguard", "smithay-client-toolkit 0.12.3", "wayland-client 0.28.6", @@ -5608,7 +5883,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295" dependencies = [ - "rand_core 0.6.3", + "rand_core", ] [[package]] @@ -5666,14 +5941,14 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" dependencies = [ - "nom 7.1.0", + "nom", ] [[package]] name = "xdg" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a23fe958c70412687039c86f578938b4a0bb50ec788e96bce4d6ab00ddd5803" +checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" dependencies = [ "dirs", ] diff --git a/Cargo.toml b/Cargo.toml index 1e721c0d62..d5c4b7dc18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,10 +26,12 @@ members = [ "compiler/arena_pool", "compiler/test_gen", "compiler/roc_target", + "compiler/debug_flags", "vendor/ena", "vendor/inkwell", "vendor/pathfinding", "vendor/pretty", + "bindgen", "editor", "ast", "cli", @@ -44,7 +46,9 @@ members = [ "test_utils", "utils", "docs", + "docs_cli", "linker", + "wasi-libc-sys", ] exclude = [ "ci/bench-runner", @@ -52,7 +56,7 @@ 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 + # `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` - diff --git a/Earthfile b/Earthfile index 5114347083..6f9287159c 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.58.0-slim-bullseye # make sure to update rust-toolchain.toml and 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.60.0-slim-bullseye # make sure to update rust-toolchain.toml too so that everything uses the same rust version WORKDIR /earthbuild prep-debian: @@ -10,15 +10,16 @@ install-other-libs: RUN apt -y install libxcb-shape0-dev libxcb-xfixes0-dev # for editor clipboard RUN apt -y install libasound2-dev # for editor sounds RUN apt -y install libunwind-dev pkg-config libx11-dev zlib1g-dev + RUN apt -y install unzip # for www/build.sh install-zig-llvm-valgrind-clippy-rustfmt: FROM +install-other-libs # editor RUN apt -y install libxkbcommon-dev # zig - RUN wget -c https://ziglang.org/download/0.8.0/zig-linux-x86_64-0.8.0.tar.xz --no-check-certificate - RUN tar -xf zig-linux-x86_64-0.8.0.tar.xz - RUN ln -s /earthbuild/zig-linux-x86_64-0.8.0/zig /usr/bin/zig + RUN wget -c https://ziglang.org/download/0.9.1/zig-linux-x86_64-0.9.1.tar.xz --no-check-certificate + RUN tar -xf zig-linux-x86_64-0.9.1.tar.xz + RUN ln -s /earthbuild/zig-linux-x86_64-0.9.1/zig /bin/zig # zig builtins wasm tests RUN apt -y install build-essential RUN cargo install wasmer-cli --features "singlepass" @@ -26,11 +27,10 @@ install-zig-llvm-valgrind-clippy-rustfmt: RUN apt -y install lsb-release software-properties-common gnupg RUN wget https://apt.llvm.org/llvm.sh RUN chmod +x llvm.sh - RUN ./llvm.sh 12 - RUN ln -s /usr/bin/clang-12 /usr/bin/clang - RUN ln -s /usr/bin/llvm-as-12 /usr/bin/llvm-as + RUN ./llvm.sh 13 + RUN ln -s /usr/bin/clang-13 /usr/bin/clang # use lld as linker - RUN ln -s /usr/bin/lld-12 /usr/bin/ld.lld + RUN ln -s /usr/bin/lld-13 /usr/bin/ld.lld ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native" # valgrind RUN apt -y install valgrind @@ -53,21 +53,26 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ + COPY --dir bindgen cli cli_utils compiler docs docs_cli editor ast code_markup error_macros highlight utils test_utils reporting repl_cli repl_eval repl_test repl_wasm repl_www roc_std vendor examples linker Cargo.toml Cargo.lock version.txt www wasi-libc-sys ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt COPY --dir compiler/builtins/bitcode ./ RUN cd bitcode && ./run-tests.sh && ./run-wasm-tests.sh -check-clippy: +build-rust-test: FROM +copy-dirs + RUN --mount=type=cache,target=$SCCACHE_DIR \ + cargo test --locked --release --features with_sound --workspace --no-run && sccache --show-stats + +check-clippy: + FROM +build-rust-test RUN cargo clippy -V RUN --mount=type=cache,target=$SCCACHE_DIR \ cargo clippy -- -D warnings check-rustfmt: - FROM +copy-dirs + FROM +build-rust-test RUN cargo fmt --version RUN cargo fmt --all -- --check @@ -77,7 +82,7 @@ check-typos: RUN typos test-rust: - FROM +copy-dirs + FROM +build-rust-test ENV RUST_BACKTRACE=1 # for race condition problem with cli test ENV ROC_NUM_WORKERS=1 @@ -100,6 +105,9 @@ test-rust: # 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 + # make sure doc generation works (that is, make sure build.sh returns status code 0) + RUN bash www/build.sh + verify-no-git-changes: FROM +test-rust diff --git a/LEGAL_DETAILS b/LEGAL_DETAILS index 687df7f907..f5b1e21a11 100644 --- a/LEGAL_DETAILS +++ b/LEGAL_DETAILS @@ -230,232 +230,6 @@ THE SOFTWARE. =========================================================== -* LLVM - https://llvm.org - -This source code can be found in ci/install-ci-libraries.sh and is licensed under the following terms: - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - -=========================================================== - * Volta - https://github.com/volta-cli/volta This source code can be found in cli/tests/helpers.rs and is licensed under the following terms: diff --git a/README.md b/README.md index c0c5f98702..7be87d5bba 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ The core Roc language and standard library include no I/O operations, which give ## Project Goals -Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/docs) is in even earlier stages than the compiler itself. +Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/roc) is in even earlier stages than the compiler itself. Besides the above language design, a separate goal is for Roc to ship with an ambitiously boundary-pushing graphical editor. Not like "an IDE," but rather something that makes people say "I have never seen anything remotely like this outside of Bret Victor demos." diff --git a/TUTORIAL.md b/TUTORIAL.md index 616541fe22..9885bb8e50 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -115,18 +115,19 @@ Create a new file called `Hello.roc` and put this inside it: ```coffee app "hello" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout ] provides [ main ] to pf main = Stdout.line "I'm a Roc application!" ``` -> **NOTE:** This assumes you've put Hello.roc in the root directory of the -> Roc source code. If you'd like to put it somewhere else, you'll need to replace -> `"examples/cli/"` with the path to the `examples/cli/` folder in -> that source code. In the future, Roc will have the tutorial built in, and this -> aside will no longer be necessary! +> **NOTE:** This assumes you've put Hello.roc in the root directory of the Roc +> source code. If you'd like to put it somewhere else, you'll need to replace +> `"examples/interactive/cli-platform"` with the path to the +> `examples/interactive/cli-platform` folder in that source code. In the future, +> Roc will have the tutorial built in, and this aside will no longer be +> necessary! Try running this with: @@ -1202,6 +1203,24 @@ and also `Num.cos 1` and have them all work as expected; the number literal `1` `Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason, you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`. +### Typed Number Literals +When writing a number literal in Roc you can specify the numeric type as a suffix of the literal. +`1u8` specifies `1` as an unsigned 8-bit integer, `5i32` specifies `5` as a signed 32-bit integer, etc. +The full list of possible suffixes includes: +`i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `nat`, `f32`, `f64`, `dec` + +### Hexadecimal Integer Literals +Integer literals can be written in hexadecimal form by prefixing with `0x` followed by hexadecimal characters. +`0xFE` evaluates to decimal `254` +The integer type can be specified as a suffix to the hexadecimal literal, +so `0xC8u8` evaluates to decimal `200` as an unsigned 8-bit integer. + +### Binary Integer Literals +Integer literals can be written in binary form by prefixing with `0b` followed by the 1's and 0's representing +each bit. `0b0000_1000` evaluates to decimal `8` +The integer type can be specified as a suffix to the binary literal, +so `0b0100u8` evaluates to decimal `4` as an unsigned 8-bit integer. + ## Interface modules [ This part of the tutorial has not been written yet. Coming soon! ] @@ -1236,7 +1255,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`: ```coffee app "hello" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout ] provides main to pf ``` @@ -1254,14 +1273,14 @@ without running it by running `roc build Hello.roc`. The remaining lines all involve the *platform* this application is built on: ```coffee -packages { pf: "examples/cli/platform" } +packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout ] provides main to pf ``` -The `packages { pf: "examples/cli/platform" }` part says two things: +The `packages { pf: "examples/interactive/cli-platform" }` part says two things: -- We're going to be using a *package* (that is, a collection of modules) called `"examples/cli/platform"` +- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform"` - We're going to name that package `pf` so we can refer to it more concisely in the future. The `imports [ pf.Stdout ]` line says that we want to import the `Stdout` module @@ -1281,17 +1300,18 @@ calling a function named `line` which is exposed by a module named When we write `imports [ pf.Stdout ]`, it specifies that the `Stdout` module comes from the `pf` package. -Since `pf` was the name we chose for the `examples/cli/platform` package -(when we wrote `packages { pf: "examples/cli/platform" }`), this `imports` line -tells the Roc compiler that when we call `Stdout.line`, it should look for that -`line` function in the `Stdout` module of the `examples/cli/platform` package. +Since `pf` was the name we chose for the `examples/interactive/cli-platform` +package (when we wrote `packages { pf: "examples/interactive/cli-platform" }`), +this `imports` line tells the Roc compiler that when we call `Stdout.line`, it +should look for that `line` function in the `Stdout` module of the +`examples/interactive/cli-platform` package. # Building a Command-Line Interface (CLI) ## Tasks Tasks are technically not part of the Roc language, but they're very common in -platforms. Let's use the CLI platform in `examples/cli` as an example! +platforms. Let's use the CLI platform in `examples/interactive/cli-platform` as an example! In the CLI platform, we have four operations we can do: @@ -1306,7 +1326,7 @@ First, let's do a basic "Hello World" using the tutorial app. ```coffee app "cli-tutorial" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout ] provides [ main ] to pf @@ -1343,7 +1363,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai ```swift app "cli-tutorial" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout, pf.Stdin, pf.Task ] provides [ main ] to pf @@ -1393,7 +1413,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo ```haskell app "cli-tutorial" - packages { pf: "examples/cli/platform" } + packages { pf: "examples/interactive/cli-platform" } imports [ pf.Stdout, pf.Stdin, pf.Task.{ await } ] provides [ main ] to pf @@ -1944,7 +1964,6 @@ Here are various Roc expressions involving operators, and what they desugar to. | `a // b` | `Num.divTrunc a b` | | `a ^ b` | `Num.pow a b` | | `a % b` | `Num.rem a b` | -| `a %% b` | `Num.mod a b` | | `a >> b` | `Num.shr a b` | | `a << b` | `Num.shl a b` | | `-a` | `Num.neg a` | diff --git a/ast/src/canonicalization/module.rs b/ast/src/canonicalization/module.rs index 64c568af5f..634e7e881c 100644 --- a/ast/src/canonicalization/module.rs +++ b/ast/src/canonicalization/module.rs @@ -7,6 +7,7 @@ use roc_can::operator::desugar_def; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::Ident; use roc_module::ident::Lowercase; +use roc_module::symbol::IdentIdsByModule; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_parse::ast; use roc_parse::pattern::PatternType; @@ -48,7 +49,7 @@ pub fn canonicalize_module_defs<'a>( home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, aliases: MutMap, exposed_imports: MutMap, mut exposed_symbols: MutSet, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index bd00bbbda5..5ea89b877b 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -27,7 +27,7 @@ use crate::{ }, env::Env, }, - mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; /// A presence constraint is an additive constraint that defines the lower bound @@ -245,32 +245,13 @@ pub fn constrain_expr<'a>( exists(arena, field_vars, And(constraints)) } } - Expr2::GlobalTag { + Expr2::Tag { variant_var, ext_var, name, arguments, } => { - let tag_name = TagName::Global(name.as_str(env.pool).into()); - - constrain_tag( - arena, - env, - expected, - region, - tag_name, - arguments, - *ext_var, - *variant_var, - ) - } - Expr2::PrivateTag { - name, - arguments, - ext_var, - variant_var, - } => { - let tag_name = TagName::Private(*name); + let tag_name = TagName::Tag(name.as_str(env.pool).into()); constrain_tag( arena, @@ -678,24 +659,28 @@ pub fn constrain_expr<'a>( for (index, when_branch_id) in branches.iter_node_ids().enumerate() { let when_branch = env.pool.get(when_branch_id); - let pattern_region = region; // let pattern_region = Region::across_all( // when_branch.patterns.iter(env.pool).map(|v| &v.region), // ); + let pattern_expected = |sub_pattern, sub_region| { + PExpected::ForReason( + PReason::WhenMatch { + index: HumanIndex::zero_based(index), + sub_pattern, + }, + cond_type.shallow_clone(), + sub_region, + ) + }; + let branch_con = constrain_when_branch( arena, env, // TODO: when_branch.value.region, region, when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.shallow_clone(), - pattern_region, - ), + pattern_expected, Expected::FromAnnotation( name.clone(), *arity, @@ -722,22 +707,26 @@ pub fn constrain_expr<'a>( for (index, when_branch_id) in branches.iter_node_ids().enumerate() { let when_branch = env.pool.get(when_branch_id); - let pattern_region = region; // let pattern_region = // Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + let pattern_expected = |sub_pattern, sub_region| { + PExpected::ForReason( + PReason::WhenMatch { + index: HumanIndex::zero_based(index), + sub_pattern, + }, + cond_type.shallow_clone(), + sub_region, + ) + }; + let branch_con = constrain_when_branch( arena, env, region, when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.shallow_clone(), - pattern_region, - ), + pattern_expected, Expected::ForReason( Reason::WhenBranch { index: HumanIndex::zero_based(index), @@ -1296,7 +1285,7 @@ fn constrain_when_branch<'a>( env: &mut Env, region: Region, when_branch: &WhenBranch, - pattern_expected: PExpected, + pattern_expected: impl Fn(HumanIndex, Region) -> PExpected, expr_expected: Expected, ) -> Constraint<'a> { let when_expr = env.pool.get(when_branch.body); @@ -1311,16 +1300,22 @@ fn constrain_when_branch<'a>( // TODO investigate for error messages, is it better to unify all branches with a variable, // then unify that variable with the expectation? - for pattern_id in when_branch.patterns.iter_node_ids() { + for (sub_pattern, pattern_id) in when_branch.patterns.iter_node_ids().enumerate() { let pattern = env.pool.get(pattern_id); + let pattern_expected = pattern_expected( + HumanIndex::zero_based(sub_pattern), + // TODO: use the proper subpattern region. Not available to us right now. + region, + ); + constrain_pattern( arena, env, pattern, // loc_pattern.region, region, - pattern_expected.shallow_clone(), + pattern_expected, &mut state, true, ); @@ -1609,34 +1604,13 @@ pub fn constrain_pattern<'a>( state.constraints.push(whole_con); state.constraints.push(record_con); } - GlobalTag { + Tag { whole_var, ext_var, tag_name: name, arguments, } => { - let tag_name = TagName::Global(name.as_str(env.pool).into()); - - constrain_tag_pattern( - arena, - env, - region, - expected, - state, - *whole_var, - *ext_var, - arguments, - tag_name, - destruct_position, - ); - } - PrivateTag { - whole_var, - ext_var, - tag_name: name, - arguments, - } => { - let tag_name = TagName::Private(*name); + let tag_name = TagName::Tag(name.as_str(env.pool).into()); constrain_tag_pattern( arena, @@ -1871,8 +1845,8 @@ fn num_float(pool: &mut Pool, range: TypeId) -> Type2 { let num_num_id = pool.add(num_num_type); Type2::Alias( - Symbol::NUM_FLOAT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + Symbol::NUM_FRAC, + PoolVec::new(vec![range].into_iter(), pool), num_num_id, ) } @@ -1881,21 +1855,11 @@ fn num_float(pool: &mut Pool, range: TypeId) -> Type2 { fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 { let range_type = pool.get(range); - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); + let alias_content = range_type.shallow_clone(); - Type2::Alias( + Type2::Opaque( Symbol::NUM_FLOATINGPOINT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + PoolVec::new(vec![range].into_iter(), pool), pool.add(alias_content), ) } @@ -1910,44 +1874,23 @@ fn num_int(pool: &mut Pool, range: TypeId) -> Type2 { Type2::Alias( Symbol::NUM_INT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + PoolVec::new(vec![range].into_iter(), pool), num_num_id, ) } #[inline(always)] fn _num_signed64(pool: &mut Pool) -> Type2 { - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_SIGNED64), - PoolVec::empty(pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - Type2::Alias( Symbol::NUM_SIGNED64, PoolVec::empty(pool), - pool.add(alias_content), + pool.add(Type2::EmptyTagUnion), ) } #[inline(always)] fn num_unsigned32(pool: &mut Pool) -> Type2 { - let alias_content = Type2::TagUnion( - PoolVec::new( - std::iter::once(( - TagName::Private(Symbol::NUM_UNSIGNED32), - PoolVec::empty(pool), - )), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); + let alias_content = Type2::EmptyTagUnion; Type2::Alias( Symbol::NUM_UNSIGNED32, @@ -1960,21 +1903,11 @@ fn num_unsigned32(pool: &mut Pool) -> Type2 { fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 { let range_type = pool.get(range); - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_INTEGER), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); + let alias_content = range_type.shallow_clone(); - Type2::Alias( + Type2::Opaque( Symbol::NUM_INTEGER, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + PoolVec::new(vec![range].into_iter(), pool), pool.add(alias_content), ) } @@ -1983,24 +1916,11 @@ fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 { fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { let range_type = pool.get(type_id); - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_NUM), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); + let alias_content = range_type.shallow_clone(); - Type2::Alias( + Type2::Opaque( Symbol::NUM_NUM, - PoolVec::new( - vec![(PoolStr::new("range", pool), type_id)].into_iter(), - pool, - ), + PoolVec::new(vec![type_id].into_iter(), pool), pool.add(alias_content), ) } @@ -2017,7 +1937,7 @@ pub mod test_constrain { use roc_parse::parser::{SourceError, SyntaxError}; use roc_region::all::Region; use roc_types::{ - pretty_print::{content_to_string, name_all_type_vars}, + pretty_print::name_and_print_var, solved_types::Solved, subs::{Subs, VarStore, Variable}, }; @@ -2130,11 +2050,6 @@ pub mod test_constrain { let subs = solved.inner_mut(); - // name type vars - name_all_type_vars(var, subs); - - let content = subs.get_content_without_compacting(var); - // Connect the ModuleId to it's IdentIds dep_idents.insert(mod_id, env.ident_ids); @@ -2143,7 +2058,7 @@ pub mod test_constrain { all_ident_ids: dep_idents, }; - let actual_str = content_to_string(content, subs, mod_id, &interns); + let actual_str = name_and_print_var(var, subs, mod_id, &interns); assert_eq!(actual_str, expected_str); } @@ -2275,7 +2190,7 @@ pub mod test_constrain { } #[test] - fn constrain_global_tag() { + fn constrain_tag() { infer_eq( indoc!( r#" @@ -2286,18 +2201,6 @@ pub mod test_constrain { ) } - #[test] - fn constrain_private_tag() { - infer_eq( - indoc!( - r#" - @Foo - "# - ), - "[ @Foo ]*", - ) - } - #[test] fn constrain_call_and_accessor() { infer_eq( diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index de03d27dde..c6e5068b26 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -148,18 +148,12 @@ pub enum Expr2 { }, // Sum Types - GlobalTag { + Tag { name: PoolStr, // 4B variant_var: Variable, // 4B ext_var: Variable, // 4B arguments: PoolVec<(Variable, ExprId)>, // 8B }, - PrivateTag { - name: Symbol, // 8B - variant_var: Variable, // 4B - ext_var: Variable, // 4B - arguments: PoolVec<(Variable, ExprId)>, // 8B - }, Blank, // Rendered as empty box in editor // Compiles, but will crash if reached diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 126f885499..3d7230589d 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -173,10 +173,10 @@ pub fn expr_to_expr2<'a>( (expr, output) } - GlobalTag(tag) => { - // a global tag without any arguments + Tag(tag) => { + // a tag without any arguments ( - Expr2::GlobalTag { + Expr2::Tag { name: PoolStr::new(tag, env.pool), variant_var: env.var_store.fresh(), ext_var: env.var_store.fresh(), @@ -185,20 +185,6 @@ pub fn expr_to_expr2<'a>( Output::default(), ) } - PrivateTag(name) => { - // a private tag without any arguments - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - let name = Symbol::new(env.home, ident_id); - ( - Expr2::PrivateTag { - name, - variant_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - arguments: PoolVec::empty(env.pool), - }, - Output::default(), - ) - } RecordUpdate { fields, @@ -557,23 +543,12 @@ pub fn expr_to_expr2<'a>( // We can't call a runtime error; bail out by propagating it! return (fn_expr, output); } - Expr2::GlobalTag { + Expr2::Tag { variant_var, ext_var, name, .. - } => Expr2::GlobalTag { - variant_var, - ext_var, - name, - arguments: args, - }, - Expr2::PrivateTag { - variant_var, - ext_var, - name, - .. - } => Expr2::PrivateTag { + } => Expr2::Tag { variant_var, ext_var, name, diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 13d91a9850..d217fbb937 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -41,18 +41,12 @@ pub enum Pattern2 { StrLiteral(PoolStr), // 8B CharacterLiteral(char), // 4B Underscore, // 0B - GlobalTag { + Tag { whole_var: Variable, // 4B ext_var: Variable, // 4B tag_name: PoolStr, // 8B arguments: PoolVec<(Variable, PatternId)>, // 8B }, - PrivateTag { - whole_var: Variable, // 4B - ext_var: Variable, // 4B - tag_name: Symbol, // 8B - arguments: PoolVec<(Variable, PatternId)>, // 8B - }, RecordDestructure { whole_var: Variable, // 4B ext_var: Variable, // 4B @@ -271,26 +265,15 @@ pub fn to_pattern2<'a>( ptype => unsupported_pattern(env, ptype, region), }, - GlobalTag(name) => { + Tag(name) => { // Canonicalize the tag's name. - Pattern2::GlobalTag { + Pattern2::Tag { whole_var: env.var_store.fresh(), ext_var: env.var_store.fresh(), tag_name: PoolStr::new(name, env.pool), arguments: PoolVec::empty(env.pool), } } - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - - // Canonicalize the tag's name. - Pattern2::PrivateTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: Symbol::new(env.home, ident_id), - arguments: PoolVec::empty(env.pool), - } - } OpaqueRef(..) => todo_opaques!(), @@ -313,22 +296,12 @@ pub fn to_pattern2<'a>( } match tag.value { - GlobalTag(name) => Pattern2::GlobalTag { + Tag(name) => Pattern2::Tag { whole_var: env.var_store.fresh(), ext_var: env.var_store.fresh(), tag_name: PoolStr::new(name, env.pool), arguments: can_patterns, }, - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&name.into()); - - Pattern2::PrivateTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: Symbol::new(env.home, ident_id), - arguments: can_patterns, - } - } _ => unreachable!("Other patterns cannot be applied"), } } @@ -506,7 +479,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { symbols.push(*symbol); } - GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { + Tag { arguments, .. } => { for (_, pat_id) in arguments.iter(pool) { let pat = pool.get(*pat_id); stack.push(pat); @@ -543,7 +516,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult { match pattern { - Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), + Pattern2::Identifier(symbol) => Ok(symbol.as_str(interns).to_string()), other => UnexpectedPattern2Variant { required_pattern2: "Identifier".to_string(), encountered_pattern2: format!("{:?}", other), @@ -567,7 +540,7 @@ pub fn symbols_and_variables_from_pattern( symbols.push((*symbol, variable)); } - GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { + Tag { arguments, .. } => { for (var, pat_id) in arguments.iter(pool) { let pat = pool.get(*pat_id); stack.push((*var, pat)); diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs index 59f727d2f7..feb19f03a9 100644 --- a/ast/src/lang/core/types.rs +++ b/ast/src/lang/core/types.rs @@ -19,11 +19,14 @@ use crate::mem_pool::shallow_clone::ShallowClone; pub type TypeId = NodeId; +const TYPE2_SIZE: () = assert!(std::mem::size_of::() == 3 * 8 + 4); + #[derive(Debug)] pub enum Type2 { Variable(Variable), // 4B - Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad + Alias(Symbol, PoolVec, TypeId), // 24B = 8B + 8B + 4B + pad + Opaque(Symbol, PoolVec, TypeId), // 24B = 8B + 8B + 4B + pad AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad // 24B @@ -46,11 +49,6 @@ pub enum Type2 { Erroneous(Problem2), // 24B } -#[test] -fn type2_size() { - assert_eq!(std::mem::size_of::(), 32); // 24B + pad -} - #[derive(Debug)] pub enum Problem2 { CanonicalizationProblem, @@ -74,6 +72,9 @@ impl ShallowClone for Type2 { Self::Alias(symbol, args, alias_type_id) => { Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone()) } + Self::Opaque(symbol, args, alias_type_id) => { + Self::Opaque(*symbol, args.shallow_clone(), alias_type_id.clone()) + } Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()), Self::Function(args, closure_type_id, ret_type_id) => Self::Function( args.shallow_clone(), @@ -101,7 +102,7 @@ impl Type2 { Variable(v) => { result.insert(*v); } - Alias(_, _, actual) | AsAlias(_, _, actual) => { + Alias(_, _, actual) | AsAlias(_, _, actual) | Opaque(_, _, actual) => { stack.push(pool.get(*actual)); } HostExposedAlias { @@ -690,29 +691,14 @@ fn can_tags<'a>( // a duplicate let new_name = 'inner: loop { match tag { - Tag::Global { name, args } => { + Tag::Apply { name, args } => { let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); } - let tag_name = TagName::Global(name.value.into()); - tag_types.push((tag_name.clone(), arg_types)); - - break 'inner tag_name; - } - Tag::Private { name, args } => { - let ident_id = env.ident_ids.get_or_insert(&name.value.into()); - let symbol = Symbol::new(env.home, ident_id); - - let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); - - for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { - as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); - } - - let tag_name = TagName::Private(symbol); + let tag_name = TagName::Tag(name.value.into()); tag_types.push((tag_name.clone(), arg_types)); break 'inner tag_name; @@ -747,7 +733,7 @@ fn can_tags<'a>( enum TypeApply { Apply(Symbol, PoolVec), - Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), + Alias(Symbol, PoolVec, TypeId), Erroneous(roc_types::types::Problem), } @@ -849,7 +835,17 @@ fn to_type_apply<'a>( // instantiate variables Type2::substitute(env.pool, &substitutions, actual); - TypeApply::Alias(symbol, arguments, actual) + let type_arguments = PoolVec::with_capacity(arguments.len() as u32, env.pool); + + for (node_id, type_id) in arguments + .iter_node_ids() + .zip(type_arguments.iter_node_ids()) + { + let typ = env.pool[node_id].1; + env.pool[type_id] = typ; + } + + TypeApply::Alias(symbol, type_arguments, actual) } None => TypeApply::Apply(symbol, argument_type_ids), } diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index 7e7f5a1670..6afee73494 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -2,7 +2,7 @@ use crate::mem_pool::pool::{NodeId, Pool}; use bumpalo::{collections::Vec as BumpVec, Bump}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase, ModuleName}; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::VarStore; @@ -19,7 +19,7 @@ pub struct Env<'a> { pub problems: BumpVec<'a, Problem>, - pub dep_idents: MutMap, + pub dep_idents: IdentIdsByModule, pub module_ids: &'a ModuleIds, pub ident_ids: IdentIds, pub exposed_ident_ids: IdentIds, @@ -41,7 +41,7 @@ impl<'a> Env<'a> { arena: &'a Bump, pool: &'a mut Pool, var_store: &'a mut VarStore, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, module_ids: &'a ModuleIds, exposed_ident_ids: IdentIds, ) -> Env<'a> { @@ -129,8 +129,8 @@ impl<'a> Env<'a> { region, }, self.ident_ids - .idents() - .map(|(_, string)| string.as_ref().into()) + .ident_strs() + .map(|(_, string)| string.into()) .collect(), )), } @@ -146,11 +146,11 @@ impl<'a> Env<'a> { } None => { let exposed_values = exposed_ids - .idents() + .ident_strs() .filter(|(_, ident)| { - ident.as_ref().starts_with(|c: char| c.is_lowercase()) + ident.starts_with(|c: char| c.is_lowercase()) }) - .map(|(_, ident)| Lowercase::from(ident.as_ref())) + .map(|(_, ident)| Lowercase::from(ident)) .collect(); Err(RuntimeError::ValueNotExposed { module_name, diff --git a/ast/src/lang/scope.rs b/ast/src/lang/scope.rs index b01c818064..c5ee9ae397 100644 --- a/ast/src/lang/scope.rs +++ b/ast/src/lang/scope.rs @@ -12,7 +12,8 @@ use crate::mem_pool::shallow_clone::ShallowClone; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{ - get_module_ident_ids, get_module_ident_ids_mut, IdentIds, Interns, ModuleId, Symbol, + get_module_ident_ids, get_module_ident_ids_mut, IdentIds, IdentIdsByModule, Interns, ModuleId, + Symbol, }; use roc_problem::can::RuntimeError; use roc_region::all::{Loc, Region}; @@ -47,7 +48,7 @@ fn to_type2( SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual, _kind) => { let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool); - for (type_variable_node_id, (lowercase, solved_arg)) in type_variables + for (type_variable_node_id, solved_arg) in type_variables .iter_node_ids() .zip(solved_type_variables.iter()) { @@ -55,7 +56,7 @@ fn to_type2( let node = pool.add(typ2); - pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node); + pool[type_variable_node_id] = node; } let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store); @@ -245,7 +246,7 @@ impl Scope { // use that existing IdentId. Otherwise, create a fresh one. let ident_id = match exposed_ident_ids.get_id(&ident) { Some(ident_id) => ident_id, - None => all_ident_ids.add(ident.clone().into()), + None => all_ident_ids.add_str(ident.as_str()), }; let symbol = Symbol::new(self.home, ident_id); @@ -262,7 +263,7 @@ impl Scope { /// /// Used for record guards like { x: Just _ } pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { - let ident_id = all_ident_ids.add(ident.into()); + let ident_id = all_ident_ids.add_str(ident.as_str()); Symbol::new(self.home, ident_id) } @@ -320,16 +321,12 @@ impl Scope { self.aliases.contains_key(&name) } - pub fn fill_scope( - &mut self, - env: &Env, - all_ident_ids: &mut MutMap, - ) -> ASTResult<()> { + pub fn fill_scope(&mut self, env: &Env, all_ident_ids: &mut IdentIdsByModule) -> ASTResult<()> { let ident_ids = get_module_ident_ids(all_ident_ids, &env.home)?.clone(); - for (_, ident_ref) in ident_ids.idents() { + for (_, ident_ref) in ident_ids.ident_strs() { self.introduce( - ident_ref.as_inline_str().as_str().into(), + ident_ref.into(), &env.exposed_ident_ids, get_module_ident_ids_mut(all_ident_ids, &env.home)?, Region::zero(), diff --git a/ast/src/module.rs b/ast/src/module.rs index 769df5d162..59a02185be 100644 --- a/ast/src/module.rs +++ b/ast/src/module.rs @@ -1,10 +1,9 @@ +use bumpalo::Bump; +use roc_load::{LoadedModule, Threading}; +use roc_target::TargetInfo; use std::path::Path; -use bumpalo::Bump; -use roc_load::LoadedModule; -use roc_target::TargetInfo; - -pub fn load_module(src_file: &Path) -> LoadedModule { +pub fn load_module(src_file: &Path, threading: Threading) -> LoadedModule { let subs_by_module = Default::default(); let arena = Bump::new(); @@ -20,6 +19,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule { subs_by_module, TargetInfo::default_x86_64(), roc_reporting::report::RenderTarget::ColorTerminal, + threading, ); match loaded { diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 096a405d5c..5b05fdb518 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -3,6 +3,7 @@ use bumpalo::Bump; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; +use roc_error_macros::internal_error; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; @@ -868,7 +869,7 @@ fn type_to_variable<'a>( register(subs, rank, pools, content) } - Alias(symbol, args, alias_type_id) => { + Alias(symbol, args, alias_type_id) | Opaque(symbol, args, alias_type_id) => { // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) // different variables (once for each occurrence). The recursion restriction is required @@ -898,7 +899,7 @@ fn type_to_variable<'a>( let mut arg_vars = Vec::with_capacity(args.len()); - for (_, arg_type_id) in args.iter(mempool) { + for arg_type_id in args.iter(mempool) { let arg_type = mempool.get(*arg_type_id); let arg_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); @@ -910,8 +911,12 @@ fn type_to_variable<'a>( let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type); - // TODO(opaques): take opaques into account - let content = Content::Alias(*symbol, arg_vars, alias_var, AliasKind::Structural); + let kind = match typ { + Alias(..) => AliasKind::Structural, + Opaque(..) => AliasKind::Opaque, + _ => internal_error!(), + }; + let content = Content::Alias(*symbol, arg_vars, alias_var, kind); let result = register(subs, rank, pools, content); diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml new file mode 100644 index 0000000000..e8689aeb08 --- /dev/null +++ b/bindgen/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "roc-bindgen" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +repository = "https://github.com/rtfeldman/roc" +edition = "2018" +description = "A CLI for roc-bindgen" + +[[bin]] +name = "roc-bindgen" +path = "src/main.rs" +test = false +bench = false + +[dependencies] +roc_std = { path = "../roc_std" } +roc_can = { path = "../compiler/can" } +roc_mono = { path = "../compiler/mono" } +roc_load = { path = "../compiler/load" } +roc_reporting = { path = "../reporting" } +roc_types = { path = "../compiler/types" } +roc_builtins = { path = "../compiler/builtins" } +roc_module = { path = "../compiler/module" } +roc_collections = { path = "../compiler/collections" } +roc_target = { path = "../compiler/roc_target" } +roc_error_macros = { path = "../error_macros" } +bumpalo = { version = "3.8.0", features = ["collections"] } +ven_graph = { path = "../vendor/pathfinding" } +target-lexicon = "0.12.3" +clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions", "derive"] } + +[dev-dependencies] +pretty_assertions = "1.0.0" +indoc = "1.0.3" +tempfile = "3.2.0" diff --git a/bindgen/src/bindgen.rs b/bindgen/src/bindgen.rs new file mode 100644 index 0000000000..cd12aaffdb --- /dev/null +++ b/bindgen/src/bindgen.rs @@ -0,0 +1,340 @@ +use std::convert::TryInto; + +use crate::structs::Structs; +use crate::types::{TypeId, Types}; +use crate::{enums::Enums, types::RocType}; +use bumpalo::Bump; +use roc_builtins::bitcode::{FloatWidth::*, IntWidth::*}; +use roc_module::ident::{Lowercase, TagName}; +use roc_module::symbol::{Interns, Symbol}; +use roc_mono::layout::{cmp_fields, ext_var_is_empty_tag_union, Builtin, Layout, LayoutCache}; +use roc_types::subs::UnionTags; +use roc_types::{ + subs::{Content, FlatType, Subs, Variable}, + types::RecordField, +}; + +pub struct Env<'a> { + pub arena: &'a Bump, + pub subs: &'a Subs, + pub layout_cache: &'a mut LayoutCache<'a>, + pub interns: &'a Interns, + pub struct_names: Structs, + pub enum_names: Enums, +} + +pub fn add_type<'a>(env: &mut Env<'a>, var: Variable, types: &mut Types) -> TypeId { + let layout = env + .layout_cache + .from_var(env.arena, var, env.subs) + .expect("Something weird ended up in the content"); + + add_type_help(env, layout, var, None, types) +} + +pub fn add_type_help<'a>( + env: &mut Env<'a>, + layout: Layout<'a>, + var: Variable, + opt_name: Option, + types: &mut Types, +) -> TypeId { + let subs = env.subs; + + match subs.get_content_without_compacting(var) { + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) + | Content::RecursionVar { .. } => { + todo!("TODO give a nice error message for a non-concrete type being passed to the host") + } + Content::Structure(FlatType::Record(fields, ext)) => { + let it = fields + .unsorted_iterator(subs, *ext) + .expect("something weird in content") + .flat_map(|(label, field)| { + match field { + RecordField::Required(field_var) | RecordField::Demanded(field_var) => { + Some((label.clone(), field_var)) + } + RecordField::Optional(_) => { + // drop optional fields + None + } + } + }); + + let name = match opt_name { + Some(sym) => sym.as_str(env.interns).to_string(), + None => env.struct_names.get_name(var), + }; + + add_struct(env, name, it, types) + } + Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + debug_assert!(ext_var_is_empty_tag_union(subs, *ext_var)); + + add_tag_union(env, opt_name, tags, var, types) + } + Content::Structure(FlatType::Apply(symbol, _)) => { + if symbol.is_builtin() { + match layout { + Layout::Builtin(builtin) => { + add_builtin_type(env, builtin, var, opt_name, types) + } + _ => { + unreachable!() + } + } + } else { + todo!("Handle non-builtin Apply") + } + } + Content::Structure(FlatType::Func(_, _, _)) => { + todo!() + } + Content::Structure(FlatType::FunctionOrTagUnion(_, _, _)) => { + todo!() + } + Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => { + todo!() + } + Content::Structure(FlatType::Erroneous(_)) => todo!(), + Content::Structure(FlatType::EmptyRecord) => todo!(), + Content::Structure(FlatType::EmptyTagUnion) => { + // This can happen when unwrapping a tag union; don't do anything. + todo!() + } + Content::Alias(name, _, real_var, _) => { + if name.is_builtin() { + match layout { + Layout::Builtin(builtin) => { + add_builtin_type(env, builtin, var, opt_name, types) + } + _ => { + unreachable!() + } + } + } else { + // If this was a non-builtin type alias, we can use that alias name + // in the generated bindings. + add_type_help(env, layout, *real_var, Some(*name), types) + } + } + Content::RangedNumber(_, _) => todo!(), + Content::Error => todo!(), + } +} + +pub fn add_builtin_type<'a>( + env: &mut Env<'a>, + builtin: Builtin<'a>, + var: Variable, + opt_name: Option, + types: &mut Types, +) -> TypeId { + match builtin { + Builtin::Int(width) => match width { + U8 => types.add(RocType::U8), + U16 => types.add(RocType::U16), + U32 => types.add(RocType::U32), + U64 => types.add(RocType::U64), + U128 => types.add(RocType::U128), + I8 => types.add(RocType::I8), + I16 => types.add(RocType::I16), + I32 => types.add(RocType::I32), + I64 => types.add(RocType::I64), + I128 => types.add(RocType::I128), + }, + Builtin::Float(width) => match width { + F32 => types.add(RocType::F32), + F64 => types.add(RocType::F64), + F128 => types.add(RocType::F128), + }, + Builtin::Bool => types.add(RocType::Bool), + Builtin::Decimal => types.add(RocType::RocDec), + Builtin::Str => types.add(RocType::RocStr), + Builtin::Dict(key_layout, val_layout) => { + // TODO FIXME this `var` is wrong - should have a different `var` for key and for val + let key_id = add_type_help(env, *key_layout, var, opt_name, types); + let val_id = add_type_help(env, *val_layout, var, opt_name, types); + let dict_id = types.add(RocType::RocDict(key_id, val_id)); + + types.depends(dict_id, key_id); + types.depends(dict_id, val_id); + + dict_id + } + Builtin::Set(elem_layout) => { + let elem_id = add_type_help(env, *elem_layout, var, opt_name, types); + let set_id = types.add(RocType::RocSet(elem_id)); + + types.depends(set_id, elem_id); + + set_id + } + Builtin::List(elem_layout) => { + let elem_id = add_type_help(env, *elem_layout, var, opt_name, types); + let list_id = types.add(RocType::RocList(elem_id)); + + types.depends(list_id, elem_id); + + list_id + } + } +} + +fn add_struct>( + env: &mut Env<'_>, + name: String, + fields: I, + types: &mut Types, +) -> TypeId { + let subs = env.subs; + let fields_iter = fields.into_iter(); + let mut sortables = bumpalo::collections::Vec::with_capacity_in( + fields_iter.size_hint().1.unwrap_or_default(), + env.arena, + ); + + for (label, field_var) in fields_iter { + sortables.push(( + label, + field_var, + env.layout_cache + .from_var(env.arena, field_var, subs) + .unwrap(), + )); + } + + sortables.sort_by(|(label1, _, layout1), (label2, _, layout2)| { + cmp_fields( + label1, + layout1, + label2, + layout2, + env.layout_cache.target_info, + ) + }); + + let fields = sortables + .into_iter() + .map(|(label, field_var, field_layout)| { + ( + label.to_string(), + add_type_help(env, field_layout, field_var, None, types), + ) + }) + .collect(); + + types.add(RocType::Struct { name, fields }) +} + +fn add_tag_union( + env: &mut Env<'_>, + opt_name: Option, + union_tags: &UnionTags, + var: Variable, + types: &mut Types, +) -> TypeId { + let subs = env.subs; + let mut tags: Vec<(String, Vec)> = union_tags + .iter_from_subs(subs) + .map(|(tag_name, payload_vars)| { + let name_str = match tag_name { + TagName::Tag(uppercase) => uppercase.as_str().to_string(), + TagName::Closure(_) => unreachable!(), + }; + + (name_str, payload_vars.to_vec()) + }) + .collect(); + + if tags.len() == 1 { + let (tag_name, payload_vars) = tags.pop().unwrap(); + + // If there was a type alias name, use that. Otherwise use the tag name. + let name = match opt_name { + Some(sym) => sym.as_str(env.interns).to_string(), + None => tag_name, + }; + + return match payload_vars.len() { + 0 => { + // This is a single-tag union with no payload, e.g. `[ Foo ]` + // so just generate an empty record + types.add(RocType::Struct { + name, + fields: Vec::new(), + }) + } + 1 => { + // This is a single-tag union with 1 payload field, e.g.`[ Foo Str ]`. + // We'll just wrap that. + let var = *payload_vars.get(0).unwrap(); + let content = add_type(env, var, types); + + types.add(RocType::TransparentWrapper { name, content }) + } + _ => { + // This is a single-tag union with multiple payload field, e.g.`[ Foo Str U32 ]`. + // Generate a record. + let fields = payload_vars.iter().enumerate().map(|(index, payload_var)| { + let field_name = format!("f{}", index).into(); + + (field_name, *payload_var) + }); + + add_struct(env, name, fields, types) + } + }; + } + + let name = match opt_name { + Some(sym) => sym.as_str(env.interns).to_string(), + None => env.enum_names.get_name(var), + }; + + // Sort tags alphabetically by tag name + tags.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); + + let tags = tags + .into_iter() + .map(|(tag_name, payload_vars)| { + let payloads = payload_vars + .iter() + .map(|payload_var| add_type(env, *payload_var, types)) + .collect::>(); + + (tag_name, payloads) + }) + .collect(); + + let typ = match env.layout_cache.from_var(env.arena, var, subs).unwrap() { + Layout::Struct { .. } => { + // a single-tag union with multiple payload values, e.g. [ Foo Str Str ] + unreachable!() + } + Layout::Union(_) => todo!(), + Layout::Builtin(builtin) => match builtin { + Builtin::Int(int_width) => RocType::TagUnion { + tag_bytes: int_width.stack_size().try_into().unwrap(), + name, + tags, + }, + Builtin::Bool => RocType::Bool, + Builtin::Float(_) + | Builtin::Decimal + | Builtin::Str + | Builtin::Dict(_, _) + | Builtin::Set(_) + | Builtin::List(_) => unreachable!(), + }, + Layout::Boxed(_) | Layout::LambdaSet(_) | Layout::RecursivePointer => { + unreachable!() + } + }; + + types.add(typ) +} diff --git a/bindgen/src/bindgen_c.rs b/bindgen/src/bindgen_c.rs new file mode 100644 index 0000000000..c19b087c87 --- /dev/null +++ b/bindgen/src/bindgen_c.rs @@ -0,0 +1,40 @@ +use std::io; + +static TEMPLATE: &[u8] = include_bytes!("../templates/template.c"); + +pub fn write_template(writer: &mut impl io::Write) -> io::Result<()> { + writer.write_all(TEMPLATE)?; + + Ok(()) +} + +// pub fn write_bindings(_writer: &mut impl io::Write) -> io::Result<()> { +// extern struct RocStr roc__mainForHost_1_exposed(); + +// int main() { +// struct RocStr str = roc__mainForHost_1_exposed(); + +// // Determine str_len and the str_bytes pointer, +// // taking into account the small string optimization. +// size_t str_len = roc_str_len(str); +// char* str_bytes; + +// if (is_small_str(str)) { +// str_bytes = (char*)&str; +// } else { +// str_bytes = str.bytes; +// } + +// // Write to stdout +// if (write(1, str_bytes, str_len) >= 0) { +// // Writing succeeded! +// return 0; +// } else { +// printf("Error writing to stdout: %s\n", strerror(errno)); + +// return 1; +// } +// } + +// Ok(()) +// } diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs new file mode 100644 index 0000000000..a4eb1006f7 --- /dev/null +++ b/bindgen/src/bindgen_rs.rs @@ -0,0 +1,186 @@ +use crate::types::{RocType, TypeId, Types}; +use std::fmt::{self, Write}; + +pub static TEMPLATE: &[u8] = include_bytes!("../templates/template.rs"); +pub static HEADER: &[u8] = include_bytes!("../templates/header.rs"); +static INDENT: &str = " "; + +pub fn write_types(types: &Types, buf: &mut String) -> fmt::Result { + for id in types.sorted_ids() { + match types.get(id) { + RocType::Struct { name, fields } => write_struct(name, fields, id, types, buf)?, + RocType::TagUnion { + tags, + name, + tag_bytes, + } => { + let is_enumeration = tags.iter().all(|(_, payloads)| payloads.is_empty()); + + match tags.len() { + 0 => { + // Empty tag unions can never come up at runtime, + // and so don't need declared types. + } + 1 => { + if is_enumeration { + // A tag union with one tag is a zero-sized unit type, so + // represent it as a zero-sized struct (e.g. "struct Foo()"). + write_deriving(id, types, buf)?; + buf.write_str("\nstruct ")?; + write_type_name(id, types, buf)?; + buf.write_str("();\n")?; + } else { + // if it wasn't an enumeration + // this is a newtype wrapper around something, + // so write an alias for its contents + todo!(); + } + } + _ => { + if is_enumeration { + write_deriving(id, types, buf)?; + write_enum(name, tags.iter().map(|(name, _)| name), *tag_bytes, buf)?; + } else { + todo!(); + } + } + } + } + RocType::RecursiveTagUnion { .. } => { + todo!(); + } + // These types don't need to be declared in Rust. + RocType::U8 + | RocType::U16 + | RocType::U32 + | RocType::U64 + | RocType::U128 + | RocType::I8 + | RocType::I16 + | RocType::I32 + | RocType::I64 + | RocType::I128 + | RocType::F32 + | RocType::F64 + | RocType::F128 + | RocType::Bool + | RocType::RocDec + | RocType::RocStr + | RocType::RocDict(_, _) + | RocType::RocSet(_) + | RocType::RocList(_) + | RocType::RocBox(_) => {} + RocType::TransparentWrapper { name, content } => { + write_deriving(id, types, buf)?; + write!(buf, "#[repr(transparent)]\npub struct {}(", name)?; + write_type_name(*content, types, buf)?; + buf.write_str(");\n")?; + } + } + } + + Ok(()) +} + +fn write_enum, S: AsRef>( + name: &str, + tags: I, + tag_bytes: u8, + buf: &mut String, +) -> fmt::Result { + // e.g. "#[repr(u8)]\npub enum Foo {\n" + writeln!(buf, "#[repr(u{})]\npub enum {} {{", tag_bytes * 8, name)?; + + for name in tags { + writeln!(buf, "{}{},", INDENT, name.as_ref())?; + } + + buf.write_str("}\n") +} + +fn write_struct( + name: &str, + fields: &[(String, TypeId)], + struct_id: TypeId, + types: &Types, + buf: &mut String, +) -> fmt::Result { + write_deriving(struct_id, types, buf)?; + + writeln!(buf, "#[repr(C)]\npub struct {} {{", name)?; + + for (label, field_id) in fields { + write!(buf, "{}{}: ", INDENT, label.as_str())?; + write_type_name(*field_id, types, buf)?; + buf.write_str(",\n")?; + } + + buf.write_str("}\n") +} + +fn write_type_name(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result { + match types.get(id) { + RocType::U8 => buf.write_str("u8"), + RocType::U16 => buf.write_str("u16"), + RocType::U32 => buf.write_str("u32"), + RocType::U64 => buf.write_str("u64"), + RocType::U128 => buf.write_str("u128"), + RocType::I8 => buf.write_str("i8"), + RocType::I16 => buf.write_str("i16"), + RocType::I32 => buf.write_str("i32"), + RocType::I64 => buf.write_str("i64"), + RocType::I128 => buf.write_str("i128"), + RocType::F32 => buf.write_str("f32"), + RocType::F64 => buf.write_str("f64"), + RocType::F128 => buf.write_str("f128"), + RocType::Bool => buf.write_str("bool"), + RocType::RocDec => buf.write_str("roc_std::RocDec"), + RocType::RocStr => buf.write_str("roc_std::RocStr"), + RocType::RocDict(key_id, val_id) => { + buf.write_str("roc_std::RocDict<")?; + write_type_name(*key_id, types, buf)?; + buf.write_str(", ")?; + write_type_name(*val_id, types, buf)?; + buf.write_char('>') + } + RocType::RocSet(elem_id) => { + buf.write_str("roc_std::RocSet<")?; + write_type_name(*elem_id, types, buf)?; + buf.write_char('>') + } + RocType::RocList(elem_id) => { + buf.write_str("roc_std::RocList<")?; + write_type_name(*elem_id, types, buf)?; + buf.write_char('>') + } + RocType::RocBox(elem_id) => { + buf.write_str("roc_std::RocBox<")?; + write_type_name(*elem_id, types, buf)?; + buf.write_char('>') + } + RocType::Struct { name, .. } + | RocType::TagUnion { name, .. } + | RocType::TransparentWrapper { name, .. } + | RocType::RecursiveTagUnion { name, .. } => buf.write_str(name), + } +} + +fn write_deriving(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result { + let typ = types.get(id); + + buf.write_str("\n#[derive(Clone, PartialEq, PartialOrd, ")?; + + if !typ.has_pointer(types) { + buf.write_str("Copy, ")?; + } + + if !typ.has_tag_union(types) { + buf.write_str("Default, ")?; + } + + if !typ.has_float(types) { + buf.write_str("Eq, Ord, Hash, ")?; + } + + buf.write_str("Debug)]\n") +} diff --git a/bindgen/src/bindgen_zig.rs b/bindgen/src/bindgen_zig.rs new file mode 100644 index 0000000000..d16c500d04 --- /dev/null +++ b/bindgen/src/bindgen_zig.rs @@ -0,0 +1,18 @@ +use std::io; + +static TEMPLATE: &[u8] = include_bytes!("../templates/template.zig"); + +pub fn write_template(writer: &mut impl io::Write) -> io::Result<()> { + writer.write_all(TEMPLATE)?; + + Ok(()) +} + +pub fn write_bindings(_writer: &mut impl io::Write) -> io::Result<()> { + // extern "C" { + // #[link_name = "roc__mainForHost_1_exposed"] + // fn roc_main() -> RocStr; + // } + + Ok(()) +} diff --git a/bindgen/src/enums.rs b/bindgen/src/enums.rs new file mode 100644 index 0000000000..dd1265afb2 --- /dev/null +++ b/bindgen/src/enums.rs @@ -0,0 +1,36 @@ +use roc_collections::MutMap; +use roc_types::subs::Variable; + +#[derive(Copy, Clone, Debug, Default)] +struct EnumId(u64); + +impl EnumId { + pub fn to_name(self) -> String { + format!("U{}", self.0) + } +} + +/// Whenever we register a new tag union type, +/// give it a unique and short name (e.g. U1, U2, U3...) +/// and then from then on, whenever we ask for that +/// same record type, return the same name. +#[derive(Default)] +pub struct Enums { + by_variable: MutMap, + next_id: EnumId, +} + +impl Enums { + pub fn get_name(&mut self, var: Variable) -> String { + match self.by_variable.get(&var) { + Some(struct_id) => struct_id.to_name(), + None => self.next_id().to_name(), + } + } + + fn next_id(&mut self) -> EnumId { + self.next_id.0 += 1; + + self.next_id + } +} diff --git a/bindgen/src/lib.rs b/bindgen/src/lib.rs new file mode 100644 index 0000000000..5edf2775ea --- /dev/null +++ b/bindgen/src/lib.rs @@ -0,0 +1,8 @@ +pub mod bindgen; +pub mod bindgen_c; +pub mod bindgen_rs; +pub mod bindgen_zig; +pub mod enums; +pub mod load; +pub mod structs; +pub mod types; diff --git a/bindgen/src/load.rs b/bindgen/src/load.rs new file mode 100644 index 0000000000..56ba3fa605 --- /dev/null +++ b/bindgen/src/load.rs @@ -0,0 +1,105 @@ +use crate::bindgen::{self, Env}; +use crate::types::Types; +use bumpalo::Bump; +use roc_can::{ + def::{Declaration, Def}, + pattern::Pattern, +}; +use roc_load::{LoadedModule, Threading}; +use roc_mono::layout::LayoutCache; +use roc_reporting::report::RenderTarget; +use std::io; +use std::path::{Path, PathBuf}; +use target_lexicon::Triple; + +pub fn load_types( + full_file_path: PathBuf, + dir: &Path, + threading: Threading, +) -> Result { + // TODO: generate both 32-bit and 64-bit #[cfg] macros if structs are different + // depending on 32-bit vs 64-bit targets. + let target_info = (&Triple::host()).into(); + + let arena = &Bump::new(); + let subs_by_module = Default::default(); + let LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + mut declarations_by_id, + mut solved, + interns, + .. + } = roc_load::load_and_typecheck( + arena, + full_file_path, + dir, + subs_by_module, + target_info, + RenderTarget::Generic, + threading, + ) + .expect("Problem loading platform module"); + + let decls = declarations_by_id.remove(&home).unwrap(); + let subs = solved.inner_mut(); + + let can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + if !can_problems.is_empty() || !type_problems.is_empty() { + todo!( + "Gracefully report compilation problems during bindgen: {:?}, {:?}", + can_problems, + type_problems + ); + } + + let mut layout_cache = LayoutCache::new(target_info); + let mut env = Env { + arena, + layout_cache: &mut layout_cache, + interns: &interns, + struct_names: Default::default(), + enum_names: Default::default(), + subs, + }; + + let mut types = Types::default(); + + for decl in decls.into_iter() { + let defs = match decl { + Declaration::Declare(def) => { + vec![def] + } + Declaration::DeclareRec(defs) => defs, + Declaration::Builtin(..) => { + unreachable!("Builtin decl in userspace module?") + } + Declaration::InvalidCycle(..) => { + vec![] + } + }; + + for Def { + loc_pattern, + pattern_vars, + .. + } in defs.into_iter() + { + if let Pattern::Identifier(sym) = loc_pattern.value { + let var = pattern_vars + .get(&sym) + .expect("Indetifier known but it has no var?"); + + bindgen::add_type(&mut env, *var, &mut types); + } else { + // figure out if we need to export non-identifier defs - when would that + // happen? + } + } + } + + Ok(types) +} diff --git a/bindgen/src/main.rs b/bindgen/src/main.rs new file mode 100644 index 0000000000..978ffcb63f --- /dev/null +++ b/bindgen/src/main.rs @@ -0,0 +1,124 @@ +use clap::Parser; +use roc_bindgen::bindgen_rs; +use roc_bindgen::load::load_types; +use roc_load::Threading; +use std::ffi::OsStr; +use std::fs::File; +use std::io::{ErrorKind, Write}; +use std::path::PathBuf; +use std::process; + +/// Printed in error messages if you try to use an unsupported extension. +const SUPPORTED_EXTENSIONS: &str = ".c, .rs, .zig, and .json"; + +// TODO add an option for --targets so that you can specify +// e.g. 64-bit, 32-bit, *and* 16-bit (which can matter for alignment because of pointers) +#[derive(Debug, Parser)] +#[clap(about)] +struct Opts { + /// The path to the platform's Package-Config.roc file + platform_module: PathBuf, + + /// The output file, e.g. `test.rs` + dest: PathBuf, +} + +enum OutputType { + Rust, + C, + Zig, + Json, +} + +pub fn main() { + let opts = Opts::parse(); + let input_path = opts.platform_module; + let cwd = std::env::current_dir().unwrap(); + let output_path = opts.dest; + let output_type = match output_path.extension().and_then(OsStr::to_str) { + Some("rs") => OutputType::Rust, + Some("c") => OutputType::C, + Some("zig") => OutputType::Zig, + Some("json") => OutputType::Json, + Some(other) => { + eprintln!( + "Unsupported output file extension: \".{}\" - currently supported extensions are {}", + other, + SUPPORTED_EXTENSIONS + ); + + process::exit(1); + } + None => { + eprintln!("The output file path needs to have a file extension in order to tell what output format to use. Currently supported extensions are {}", SUPPORTED_EXTENSIONS); + + process::exit(1); + } + }; + + match load_types(input_path.clone(), &cwd, Threading::AllAvailable) { + Ok(types) => { + let mut buf; + + let result = match output_type { + OutputType::Rust => { + buf = std::str::from_utf8(bindgen_rs::HEADER).unwrap().to_string(); + + bindgen_rs::write_types(&types, &mut buf) + } + OutputType::C => todo!("TODO: Generate bindings for C"), + OutputType::Zig => todo!("TODO: Generate bindings for Zig"), + OutputType::Json => todo!("TODO: Generate bindings for JSON"), + }; + + if let Err(err) = result { + eprintln!( + "Unable to generate binding string {} - {:?}", + output_path.display(), + err + ); + + process::exit(1); + } + + let mut file = File::create(output_path.clone()).unwrap_or_else(|err| { + eprintln!( + "Unable to create output file {} - {:?}", + output_path.display(), + err + ); + + process::exit(1); + }); + + file.write_all(buf.as_bytes()).unwrap_or_else(|err| { + eprintln!( + "Unable to write bindings to output file {} - {:?}", + output_path.display(), + err + ); + + process::exit(1); + }); + + println!( + "🎉 Generated type declarations in:\n\n\t{}", + output_path.display() + ); + } + Err(err) => match err.kind() { + ErrorKind::NotFound => { + eprintln!("Platform module file not found: {}", input_path.display()); + process::exit(1); + } + error => { + eprintln!( + "Error loading platform module file {} - {:?}", + input_path.display(), + error + ); + process::exit(1); + } + }, + } +} diff --git a/bindgen/src/structs.rs b/bindgen/src/structs.rs new file mode 100644 index 0000000000..e82523ddf4 --- /dev/null +++ b/bindgen/src/structs.rs @@ -0,0 +1,36 @@ +use roc_collections::MutMap; +use roc_types::subs::Variable; + +#[derive(Copy, Clone, Debug, Default)] +struct StructId(u64); + +impl StructId { + pub fn to_name(self) -> String { + format!("R{}", self.0) + } +} + +/// Whenever we register a new Roc record type, +/// give it a unique and short name (e.g. R1, R2, R3...) +/// and then from then on, whenever we ask for that +/// same record type, return the same name. +#[derive(Default)] +pub struct Structs { + by_variable: MutMap, + next_id: StructId, +} + +impl Structs { + pub fn get_name(&mut self, var: Variable) -> String { + match self.by_variable.get(&var) { + Some(struct_id) => struct_id.to_name(), + None => self.next_id().to_name(), + } + } + + fn next_id(&mut self) -> StructId { + self.next_id.0 += 1; + + self.next_id + } +} diff --git a/bindgen/src/types.rs b/bindgen/src/types.rs new file mode 100644 index 0000000000..8339e371e1 --- /dev/null +++ b/bindgen/src/types.rs @@ -0,0 +1,319 @@ +use core::mem::align_of; +use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_collections::VecMap; +use roc_std::RocDec; +use roc_target::TargetInfo; +use ven_graph::topological_sort; + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct TypeId(usize); + +#[derive(Default, Debug)] +pub struct Types { + by_id: Vec, + + /// Dependencies - that is, which type depends on which other type. + /// This is important for declaration order in C; we need to output a + /// type declaration earlier in the file than where it gets referenced by another type. + deps: VecMap>, +} + +impl Types { + pub fn with_capacity(cap: usize) -> Self { + Self { + by_id: Vec::with_capacity(cap), + deps: VecMap::with_capacity(cap), + } + } + + pub fn add(&mut self, typ: RocType) -> TypeId { + let id = TypeId(self.by_id.len()); + + self.by_id.push(typ); + + id + } + + pub fn depends(&mut self, id: TypeId, depends_on: TypeId) { + self.deps.get_or_insert(id, Vec::new).push(depends_on); + } + + pub fn get(&self, id: TypeId) -> &RocType { + match self.by_id.get(id.0) { + Some(typ) => typ, + None => unreachable!(), + } + } + + pub fn ids(&self) -> impl ExactSizeIterator { + (0..self.by_id.len()).map(TypeId) + } + + pub fn sorted_ids(&self) -> Vec { + // TODO: instead use the bitvec matrix type we use in the Roc compiler - + // it's more efficient and also would bring us one step closer to dropping + // the dependency on this topological_sort implementation! + topological_sort(self.ids(), |id| match self.deps.get(id) { + Some(dep_ids) => dep_ids.to_vec(), + None => Vec::new(), + }) + .unwrap_or_else(|err| { + unreachable!("Cyclic type definitions: {:?}", err); + }) + } + + pub fn iter(&self) -> impl ExactSizeIterator { + TypesIter { + types: self.by_id.as_slice(), + len: self.by_id.len(), + } + } +} + +struct TypesIter<'a> { + types: &'a [RocType], + len: usize, +} + +impl<'a> ExactSizeIterator for TypesIter<'a> { + fn len(&self) -> usize { + self.len + } +} + +impl<'a> Iterator for TypesIter<'a> { + type Item = &'a RocType; + + fn next(&mut self) -> Option { + let len = self.len; + let answer = self.types.get(self.types.len() - len); + + self.len = len.saturating_sub(1); + + answer + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum RocType { + RocStr, + Bool, + I8, + U8, + I16, + U16, + I32, + U32, + I64, + U64, + I128, + U128, + F32, + F64, + F128, + RocDec, + RocList(TypeId), + RocDict(TypeId, TypeId), + RocSet(TypeId), + RocBox(TypeId), + RecursiveTagUnion { + name: String, + tags: Vec<(String, Vec)>, + }, + TagUnion { + tag_bytes: u8, + name: String, + tags: Vec<(String, Vec)>, + }, + Struct { + name: String, + fields: Vec<(String, TypeId)>, + }, + /// Either a single-tag union or a single-field record + TransparentWrapper { + name: String, + content: TypeId, + }, +} + +impl RocType { + /// Useful when determining whether to derive Copy in a Rust type. + pub fn has_pointer(&self, types: &Types) -> bool { + match self { + RocType::Bool + | RocType::I8 + | RocType::U8 + | RocType::I16 + | RocType::U16 + | RocType::I32 + | RocType::U32 + | RocType::I64 + | RocType::U64 + | RocType::I128 + | RocType::U128 + | RocType::F32 + | RocType::F64 + | RocType::F128 + | RocType::RocDec => false, + RocType::RocStr + | RocType::RocList(_) + | RocType::RocDict(_, _) + | RocType::RocSet(_) + | RocType::RocBox(_) + | RocType::RecursiveTagUnion { .. } => true, + RocType::TagUnion { tags, .. } => tags + .iter() + .any(|(_, payloads)| payloads.iter().any(|id| types.get(*id).has_pointer(types))), + RocType::Struct { fields, .. } => fields + .iter() + .any(|(_, id)| types.get(*id).has_pointer(types)), + RocType::TransparentWrapper { content, .. } => types.get(*content).has_pointer(types), + } + } + + /// Useful when determining whether to derive Eq, Ord, and Hash in a Rust type. + pub fn has_float(&self, types: &Types) -> bool { + match self { + RocType::F32 | RocType::F64 | RocType::F128 => true, + RocType::RocStr + | RocType::Bool + | RocType::I8 + | RocType::U8 + | RocType::I16 + | RocType::U16 + | RocType::I32 + | RocType::U32 + | RocType::I64 + | RocType::U64 + | RocType::I128 + | RocType::U128 + | RocType::RocDec => false, + RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => { + types.get(*id).has_float(types) + } + RocType::RocDict(key_id, val_id) => { + types.get(*key_id).has_float(types) || types.get(*val_id).has_float(types) + } + RocType::RecursiveTagUnion { tags, .. } | RocType::TagUnion { tags, .. } => tags + .iter() + .any(|(_, payloads)| payloads.iter().any(|id| types.get(*id).has_float(types))), + RocType::Struct { fields, .. } => { + fields.iter().any(|(_, id)| types.get(*id).has_float(types)) + } + RocType::TransparentWrapper { content, .. } => types.get(*content).has_float(types), + } + } + + /// Useful when determining whether to derive Default in a Rust type. + pub fn has_tag_union(&self, types: &Types) -> bool { + match self { + RocType::RecursiveTagUnion { .. } | RocType::TagUnion { .. } => true, + RocType::RocStr + | RocType::Bool + | RocType::I8 + | RocType::U8 + | RocType::I16 + | RocType::U16 + | RocType::I32 + | RocType::U32 + | RocType::I64 + | RocType::U64 + | RocType::I128 + | RocType::U128 + | RocType::F32 + | RocType::F64 + | RocType::F128 + | RocType::RocDec => false, + RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => { + types.get(*id).has_tag_union(types) + } + RocType::RocDict(key_id, val_id) => { + types.get(*key_id).has_tag_union(types) || types.get(*val_id).has_tag_union(types) + } + RocType::Struct { fields, .. } => fields + .iter() + .any(|(_, id)| types.get(*id).has_tag_union(types)), + RocType::TransparentWrapper { content, .. } => types.get(*content).has_tag_union(types), + } + } + + pub fn alignment(&self, types: &Types, target_info: TargetInfo) -> usize { + match self { + RocType::RocStr + | RocType::RocList(_) + | RocType::RocDict(_, _) + | RocType::RocSet(_) + | RocType::RocBox(_) => target_info.ptr_alignment_bytes(), + RocType::RocDec => align_of::(), + RocType::Bool => align_of::(), + RocType::TagUnion { tags, .. } => { + // The smallest alignment this could possibly have is based on the number of tags - e.g. + // 0 tags is an empty union (so, alignment 0), 1-255 tags has a u8 tag (so, alignment 1), etc. + let mut align = align_for_tag_count(tags.len()); + + for (_, payloads) in tags { + for id in payloads { + align = align.max(types.get(*id).alignment(types, target_info)); + } + } + + align + } + RocType::RecursiveTagUnion { tags, .. } => { + // The smallest alignment this could possibly have is based on the number of tags - e.g. + // 0 tags is an empty union (so, alignment 0), 1-255 tags has a u8 tag (so, alignment 1), etc. + // + // Unlike a regular tag union, a recursive one also includes a pointer. + let ptr_align = target_info.ptr_alignment_bytes(); + let mut align = ptr_align.max(align_for_tag_count(tags.len())); + + for (_, payloads) in tags { + for id in payloads { + align = align.max(types.get(*id).alignment(types, target_info)); + } + } + + align + } + RocType::Struct { fields, .. } => fields.iter().fold(0, |align, (_, id)| { + align.max(types.get(*id).alignment(types, target_info)) + }), + RocType::I8 => IntWidth::I8.alignment_bytes(target_info) as usize, + RocType::U8 => IntWidth::U8.alignment_bytes(target_info) as usize, + RocType::I16 => IntWidth::I16.alignment_bytes(target_info) as usize, + RocType::U16 => IntWidth::U16.alignment_bytes(target_info) as usize, + RocType::I32 => IntWidth::I32.alignment_bytes(target_info) as usize, + RocType::U32 => IntWidth::U32.alignment_bytes(target_info) as usize, + RocType::I64 => IntWidth::I64.alignment_bytes(target_info) as usize, + RocType::U64 => IntWidth::U64.alignment_bytes(target_info) as usize, + RocType::I128 => IntWidth::I128.alignment_bytes(target_info) as usize, + RocType::U128 => IntWidth::U128.alignment_bytes(target_info) as usize, + RocType::F32 => FloatWidth::F32.alignment_bytes(target_info) as usize, + RocType::F64 => FloatWidth::F64.alignment_bytes(target_info) as usize, + RocType::F128 => FloatWidth::F128.alignment_bytes(target_info) as usize, + RocType::TransparentWrapper { content, .. } => { + types.get(*content).alignment(types, target_info) + } + } + } +} + +fn align_for_tag_count(num_tags: usize) -> usize { + if num_tags == 0 { + // empty tag union + 0 + } else if num_tags < u8::MAX as usize { + align_of::() + } else if num_tags < u16::MAX as usize { + align_of::() + } else if num_tags < u32::MAX as usize { + align_of::() + } else if num_tags < u64::MAX as usize { + align_of::() + } else { + panic!( + "Too many tags. You can't have more than {} tags in a tag union!", + u64::MAX + ); + } +} diff --git a/bindgen/templates/header.rs b/bindgen/templates/header.rs new file mode 100644 index 0000000000..df926e1bfc --- /dev/null +++ b/bindgen/templates/header.rs @@ -0,0 +1,6 @@ +// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc-bindgen` CLI + +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] diff --git a/bindgen/templates/template.c b/bindgen/templates/template.c new file mode 100644 index 0000000000..4e28b6d042 --- /dev/null +++ b/bindgen/templates/template.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +void* roc_alloc(size_t size, unsigned int alignment) { return malloc(size); } + +void* roc_realloc(void* ptr, size_t new_size, size_t old_size, unsigned int alignment) { + return realloc(ptr, new_size); +} + +void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); } + +void roc_panic(void* ptr, unsigned int alignment) { + char* msg = (char*)ptr; + fprintf(stderr, + "Application crashed with message\n\n %s\n\nShutting down\n", msg); + exit(0); +} + +void* roc_memcpy(void* dest, const void* src, size_t n) { + return memcpy(dest, src, n); +} + +void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); } + +/////////////////////////////////////////////////////////////////////////// +// +// roc_std +// +/////////////////////////////////////////////////////////////////////////// + +struct RocStr { + char* bytes; + size_t len; +}; + +bool is_small_str(struct RocStr str) { return ((ssize_t)str.len) < 0; } + +// Determine the length of the string, taking into +// account the small string optimization +size_t roc_str_len(struct RocStr str) { + char* bytes = (char*)&str; + char last_byte = bytes[sizeof(str) - 1]; + char last_byte_xored = last_byte ^ 0b10000000; + size_t small_len = (size_t)(last_byte_xored); + size_t big_len = str.len; + + // Avoid branch misprediction costs by always + // determining both small_len and big_len, + // so this compiles to a cmov instruction. + if (is_small_str(str)) { + return small_len; + } else { + return big_len; + } +} diff --git a/bindgen/templates/template.rs b/bindgen/templates/template.rs new file mode 100644 index 0000000000..3ca441f5d2 --- /dev/null +++ b/bindgen/templates/template.rs @@ -0,0 +1,79 @@ +#![allow(non_snake_case)] + +use core::ffi::c_void; + +// TODO don't have these depend on the libc crate; instead, use default +// allocator, built-in memset, etc. + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + use std::ffi::CStr; + use std::os::raw::c_char; + + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +//////////////////////////////////////////////////////////////////////////// +// +// TODO: rust_main should be removed once we use surgical linking everywhere. +// It's just a workaround to get cargo to build an object file the way +// the non-surgical linker needs it to. The surgical linker works on +// executables, not object files, so this workaround is not needed there. +// +//////////////////////////////////////////////////////////////////////////// +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + use roc_std::RocStr; + + unsafe { + let roc_str = roc_main(); + + let len = roc_str.len(); + let str_bytes = roc_str.as_bytes().as_ptr() as *const libc::c_void; + + if libc::write(1, str_bytes, len) < 0 { + panic!("Writing to stdout failed!"); + } + } + + // Exit code + 0 +} diff --git a/bindgen/templates/template.zig b/bindgen/templates/template.zig new file mode 100644 index 0000000000..dc2e9e5001 --- /dev/null +++ b/bindgen/templates/template.zig @@ -0,0 +1,71 @@ +const std = @import("std"); +const str = @import("str"); + +comptime { + // This is a workaround for https://github.com/ziglang/zig/issues/8218 + // which is only necessary on macOS. + // + // Once that issue is fixed, we can undo the changes in + // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing + // -fcompiler-rt in link.rs instead of doing this. Note that this + // workaround is present in many host.zig files, so make sure to undo + // it everywhere! + if (std.builtin.os.tag == .macos) { + _ = @import("compiler_rt"); + } +} + +const Align = 2 * @alignOf(usize); +extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; +extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; + +const DEBUG: bool = false; + +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { + if (DEBUG) { + var ptr = malloc(size); + const stdout = std.io.getStdOut().writer(); + stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable; + return ptr; + } else { + return malloc(size); + } +} + +export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; + } + + return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); +} + +export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; + } + + free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); +} + +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + _ = tag_id; + + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void { + return memcpy(dst, src, size); +} + +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { + return memset(dst, value, size); +} diff --git a/bindgen/tests/gen_rs.rs b/bindgen/tests/gen_rs.rs new file mode 100644 index 0000000000..5208d11246 --- /dev/null +++ b/bindgen/tests/gen_rs.rs @@ -0,0 +1,402 @@ +#[macro_use] +extern crate pretty_assertions; + +#[macro_use] +extern crate indoc; + +use roc_bindgen::bindgen_rs; +use roc_bindgen::load::load_types; +use roc_load::Threading; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn generate_bindings(decl_src: &str) -> String { + use tempfile::tempdir; + + let mut src = indoc!( + r#" + platform "main" + requires {} { nothing : {} } + exposes [] + packages {} + imports [] + provides [ main ] + + "# + ) + .to_string(); + + src.push_str(decl_src); + + let types = { + let dir = tempdir().expect("Unable to create tempdir"); + let filename = PathBuf::from("Package-Config.roc"); + let file_path = dir.path().join(filename); + let full_file_path = file_path.clone(); + let mut file = File::create(file_path).unwrap(); + writeln!(file, "{}", &src).unwrap(); + + let result = load_types(full_file_path, dir.path(), Threading::Single); + + dir.close().expect("Unable to close tempdir"); + + result.expect("had problems loading") + }; + + // Reuse the `src` allocation since we're done with it. + let mut buf = src; + buf.clear(); + + bindgen_rs::write_types(&types, &mut buf).expect("I/O error when writing bindgen string"); + + buf +} + +#[test] +fn record_aliased() { + let module = indoc!( + r#" + MyRcd : { a : U64, b : U128 } + + main : MyRcd + main = { a: 1u64, b: 2u128 } + "# + ); + + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, PartialEq, PartialOrd, Copy, Default, Eq, Ord, Hash, Debug)] + #[repr(C)] + pub struct MyRcd { + b: u128, + a: u64, + } + "# + ) + ); +} + +#[test] +fn nested_record_aliased() { + let module = indoc!( + r#" + Outer : { x : Inner, y : Str, z : List U8 } + + Inner : { a : U16, b : F32 } + + main : Outer + main = { x: { a: 5, b: 24 }, y: "foo", z: [ 1, 2 ] } + "# + ); + + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, PartialEq, PartialOrd, Default, Debug)] + #[repr(C)] + pub struct Outer { + y: roc_std::RocStr, + z: roc_std::RocList, + x: Inner, + } + + #[derive(Clone, PartialEq, PartialOrd, Copy, Default, Debug)] + #[repr(C)] + pub struct Inner { + b: f32, + a: u16, + } + "# + ) + ); +} + +#[test] +fn record_anonymous() { + let module = "main = { a: 1u64, b: 2u128 }"; + + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, PartialEq, PartialOrd, Copy, Default, Eq, Ord, Hash, Debug)] + #[repr(C)] + pub struct R1 { + b: u128, + a: u64, + } + "# + ) + ); +} + +#[test] +fn nested_record_anonymous() { + let module = r#"main = { x: { a: 5u16, b: 24f32 }, y: "foo", z: [ 1u8, 2 ] }"#; + + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, PartialEq, PartialOrd, Default, Debug)] + #[repr(C)] + pub struct R1 { + y: roc_std::RocStr, + z: roc_std::RocList, + x: R2, + } + + #[derive(Clone, PartialEq, PartialOrd, Copy, Default, Debug)] + #[repr(C)] + pub struct R2 { + b: f32, + a: u16, + } + "# + ) + ); +} + +#[test] +#[ignore] +fn tag_union_aliased() { + let module = indoc!( + r#" + MyTagUnion : [ Foo U64, Bar U128 ] + + main : MyTagUnion + main = Foo 123 + "# + ); + + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[repr(C)] + pub struct MyTagUnion { + tag: tag_MyTagUnion, + variant: variant_MyTagUnion, + } + + #[repr(C)] + union variant_MyTagUnion { + Bar: u128, + Foo: std::mem::ManuallyDrop>, + } + + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] + #[repr(C)] + pub struct Payload2 { + _0: V0, + _1: V1, + } + + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] + #[repr(u8)] + pub enum tag_MyTagUnion { + Bar, + Foo, + } + + impl MyTagUnion { + pub fn tag(&self) -> tag_MyTagUnion { + self.tag + } + + /// Assume this is the tag named Foo, and return a reference to its payload. + pub unsafe fn as_Foo(&self) -> &Payload2 { + &*self.variant.Foo + } + + /// Assume this is the tag named Foo, and return a mutable reference to its payload. + pub unsafe fn as_mut_Foo(&mut self) -> &mut Payload2 { + &mut *self.variant.Foo + } + + /// Assume this is the tag named Bar, and return a reference to its payload. + pub unsafe fn as_Bar(&self) -> u128 { + self.variant.Bar + } + + /// Assume this is the tag named Bar, and return a mutable reference to its payload. + pub unsafe fn as_mut_Bar(&mut self) -> &mut u128 { + &mut self.variant.Bar + } + + /// Construct a tag named Foo, with the appropriate payload + pub fn Foo(_0: roc_std::RocStr, _1: i32) -> Self { + Self { + tag: tag_MyTagUnion::Foo, + variant: variant_MyTagUnion { + Foo: std::mem::ManuallyDrop::new(Payload2 { _0, _1 }), + }, + } + } + + /// Construct a tag named Bar, with the appropriate payload + pub fn Bar(arg0: u128) -> Self { + Self { + tag: tag_MyTagUnion::Bar, + variant: variant_MyTagUnion { Bar: arg0 }, + } + } + } + + impl Drop for MyTagUnion { + fn drop(&mut self) { + match self.tag { + tag_MyTagUnion::Bar => {} + tag_MyTagUnion::Foo => unsafe { std::mem::ManuallyDrop::drop(&mut self.variant.Foo) }, + } + } + } + + impl PartialEq for MyTagUnion { + fn eq(&self, other: &Self) -> bool { + if self.tag != other.tag { + return false; + } + + unsafe { + match self.tag { + tag_MyTagUnion::Bar => self.variant.Bar == other.variant.Bar, + tag_MyTagUnion::Foo => self.variant.Foo == other.variant.Foo, + } + } + } + } + + impl Eq for MyTagUnion {} + + impl PartialOrd for MyTagUnion { + fn partial_cmp(&self, other: &Self) -> Option { + match self.tag.partial_cmp(&other.tag) { + Some(core::cmp::Ordering::Equal) => {} + not_eq => return not_eq, + } + + unsafe { + match self.tag { + tag_MyTagUnion::Bar => self.variant.Bar.partial_cmp(&other.variant.Bar), + tag_MyTagUnion::Foo => self.variant.Foo.partial_cmp(&other.variant.Foo), + } + } + } + } + + impl Ord for MyTagUnion { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match self.tag.cmp(&other.tag) { + core::cmp::Ordering::Equal => {} + not_eq => return not_eq, + } + + unsafe { + match self.tag { + tag_MyTagUnion::Bar => self.variant.Bar.cmp(&other.variant.Bar), + tag_MyTagUnion::Foo => self.variant.Foo.cmp(&other.variant.Foo), + } + } + } + } + "# + ) + ); +} + +#[test] +fn tag_union_enumeration() { + let module = indoc!( + r#" + MyTagUnion : [ Blah, Foo, Bar, ] + + main : MyTagUnion + main = Foo + "# + ); + + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, PartialEq, PartialOrd, Copy, Eq, Ord, Hash, Debug)] + #[repr(u8)] + pub enum MyTagUnion { + Bar, + Blah, + Foo, + } + "# + ) + ); +} + +#[test] +fn single_tag_union_with_payloads() { + let module = indoc!( + r#" + UserId : [ Id U32 Str ] + + main : UserId + main = Id 42 "blah" + "# + ); + + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, PartialEq, PartialOrd, Default, Eq, Ord, Hash, Debug)] + #[repr(C)] + pub struct UserId { + f1: roc_std::RocStr, + f0: u32, + } + "# + ) + ); +} + +#[test] +fn single_tag_union_with_one_payload_field() { + let module = indoc!( + r#" + UserId : [ Id Str ] + + main : UserId + main = Id "blah" + "# + ); + + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, PartialEq, PartialOrd, Default, Eq, Ord, Hash, Debug)] + #[repr(transparent)] + pub struct UserId(roc_std::RocStr); + "# + ) + ); +} diff --git a/ci/bench-runner/Cargo.lock b/ci/bench-runner/Cargo.lock index e0517c28db..3a58084af0 100644 --- a/ci/bench-runner/Cargo.lock +++ b/ci/bench-runner/Cargo.lock @@ -65,28 +65,26 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.0.0-beta.2" +version = "3.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d" dependencies = [ "atty", "bitflags", "clap_derive", + "clap_lex", "indexmap", "lazy_static", - "os_str_bytes", "strsim", "termcolor", "textwrap", - "unicode-width", - "vec_map", ] [[package]] name = "clap_derive" -version = "3.0.0-beta.2" +version = "3.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" dependencies = [ "heck", "proc-macro-error", @@ -95,6 +93,15 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -109,12 +116,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -188,9 +192,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "os_str_bytes" -version = "2.4.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" [[package]] name = "proc-macro-error" @@ -300,24 +304,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "unicode-xid" @@ -331,17 +320,11 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasm-bindgen" diff --git a/ci/bench-runner/Cargo.toml b/ci/bench-runner/Cargo.toml index 31fcd9cdda..02e775065f 100644 --- a/ci/bench-runner/Cargo.toml +++ b/ci/bench-runner/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "3.0.0-beta.2" +clap = { version = "3.1.15", features = ["derive"] } regex = "1.5.4" is_executable = "1.0.1" ring = "0.16.20" diff --git a/ci/bench-runner/src/main.rs b/ci/bench-runner/src/main.rs index efccc69497..6cab1cf003 100644 --- a/ci/bench-runner/src/main.rs +++ b/ci/bench-runner/src/main.rs @@ -1,4 +1,4 @@ -use clap::{AppSettings, Clap}; +use clap::Parser; use data_encoding::HEXUPPER; use is_executable::IsExecutable; use regex::Regex; @@ -160,8 +160,7 @@ fn remove(file_or_folder: &str) { .unwrap_or_else(|_| panic!("Something went wrong trying to remove {}", file_or_folder)); } -#[derive(Clap)] -#[clap(setting = AppSettings::ColoredHelp)] +#[derive(Parser)] struct OptionalArgs { /// How many times to repeat the benchmarks. A single benchmark has to fail every for a regression to be reported. #[clap(long, default_value = "3")] diff --git a/ci/install-ci-libraries.sh b/ci/install-ci-libraries.sh deleted file mode 100755 index 09dc7c01c7..0000000000 --- a/ci/install-ci-libraries.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash -################################################################################ -# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -# See https://llvm.org/LICENSE.txt for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -################################################################################ -# -# This script will install the llvm toolchain on the different -# Debian and Ubuntu versions - -set -eux - -# read optional command line argument -LLVM_VERSION=10 -if [ "$#" -eq 1 ]; then - LLVM_VERSION=$1 -fi - -DISTRO=$(lsb_release -is) -VERSION=$(lsb_release -sr) -DIST_VERSION="${DISTRO}_${VERSION}" - -if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root!" - exit 1 -fi - -declare -A LLVM_VERSION_PATTERNS -LLVM_VERSION_PATTERNS[9]="-9" -LLVM_VERSION_PATTERNS[10]="-10" -LLVM_VERSION_PATTERNS[11]="" - -if [ ! ${LLVM_VERSION_PATTERNS[$LLVM_VERSION]+_} ]; then - echo "This script does not support LLVM version $LLVM_VERSION" - exit 3 -fi - -LLVM_VERSION_STRING=${LLVM_VERSION_PATTERNS[$LLVM_VERSION]} - -# find the right repository name for the distro and version -case "$DIST_VERSION" in - Debian_9* ) REPO_NAME="deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch$LLVM_VERSION_STRING main" ;; - Debian_10* ) REPO_NAME="deb http://apt.llvm.org/buster/ llvm-toolchain-buster$LLVM_VERSION_STRING main" ;; - Debian_unstable ) REPO_NAME="deb http://apt.llvm.org/unstable/ llvm-toolchain$LLVM_VERSION_STRING main" ;; - Debian_testing ) REPO_NAME="deb http://apt.llvm.org/unstable/ llvm-toolchain$LLVM_VERSION_STRING main" ;; - Ubuntu_16.04 ) REPO_NAME="deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial$LLVM_VERSION_STRING main" ;; - Ubuntu_18.04 ) REPO_NAME="deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic$LLVM_VERSION_STRING main" ;; - Ubuntu_18.10 ) REPO_NAME="deb http://apt.llvm.org/cosmic/ llvm-toolchain-cosmic$LLVM_VERSION_STRING main" ;; - Ubuntu_19.04 ) REPO_NAME="deb http://apt.llvm.org/disco/ llvm-toolchain-disco$LLVM_VERSION_STRING main" ;; - Ubuntu_19.10 ) REPO_NAME="deb http://apt.llvm.org/eoan/ llvm-toolchain-eoan$LLVM_VERSION_STRING main" ;; - Ubuntu_20.04 ) REPO_NAME="deb http://apt.llvm.org/focal/ llvm-toolchain-focal$LLVM_VERSION_STRING main" ;; - * ) - echo "Distribution '$DISTRO' in version '$VERSION' is not supported by this script (${DIST_VERSION})." - exit 2 -esac - - -# install everything -wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - -add-apt-repository "${REPO_NAME}" -apt-get update -apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc6-dbg libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev - -wget https://sourceware.org/pub/valgrind/valgrind-3.16.1.tar.bz2 -tar -xf valgrind-3.16.1.tar.bz2 -mv valgrind-3.16.1 ~ -pushd ~/valgrind-3.16.1 -apt-get install -y autotools-dev automake -./autogen.sh -./configure -make -j`nproc` -sudo make install -popd - -# Report current valgrind version, to confirm it installed properly -valgrind --version - -# install zig - can't use apt-get since we require at least a specific commit later then the most recent tag (0.6.0) -wget -c https://ziglang.org/download/0.7.1/zig-linux-x86_64-0.7.1.tar.xz --no-check-certificate -tar -xf zig-linux-x86_64-0.7.1.tar.xz -ln -s "$PWD/zig-linux-x86_64-0.7.1/zig" /usr/local/bin/zig - -# test sccache -./ci/sccache -V -# copy sccache to prevent current working dir problems -cp ./ci/sccache /usr/local/bin/sccache diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4b5d13d1c1..93d598462f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,14 +15,11 @@ test = false bench = false [features] -default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor", "llvm"] +default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"] wasm32-cli-run = ["target-wasm32", "run-wasm32"] i386-cli-run = ["target-x86"] -# TODO: change to roc_repl_cli/llvm once roc_repl can run without llvm. -llvm = ["roc_build/llvm", "roc_repl_cli"] - editor = ["roc_editor"] run-wasm32 = ["wasmer", "wasmer-wasi"] @@ -61,37 +58,39 @@ roc_error_macros = { path = "../error_macros" } roc_editor = { path = "../editor", optional = true } roc_linker = { path = "../linker" } roc_repl_cli = { path = "../repl_cli", optional = true } -clap = { version = "3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } +clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions"] } const_format = "0.2.22" bumpalo = { version = "3.8.0", features = ["collections"] } mimalloc = { version = "0.1.26", default-features = false } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" tempfile = "3.2.0" -wasmer-wasi = { version = "2.0.0", optional = true } +wasmer-wasi = { version = "2.2.1", optional = true } # Wasmer singlepass compiler only works on x86_64. [target.'cfg(target_arch = "x86_64")'.dependencies] -wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-singlepass", "default-universal"] } +wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["singlepass", "universal"] } [target.'cfg(not(target_arch = "x86_64"))'.dependencies] -wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] } +wasmer = { version = "2.2.1", optional = true, default-features = false, features = ["cranelift", "universal"] } [dev-dependencies] -wasmer-wasi = "2.0.0" +wasmer-wasi = "2.2.1" pretty_assertions = "1.0.0" roc_test_utils = { path = "../test_utils" } indoc = "1.0.3" serial_test = "0.5.1" criterion = { git = "https://github.com/Anton-4/criterion.rs"} cli_utils = { path = "../cli_utils" } +strum = "0.24.0" +strum_macros = "0.24" # Wasmer singlepass compiler only works on x86_64. [target.'cfg(target_arch = "x86_64")'.dev-dependencies] -wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] } +wasmer = { version = "2.2.1", default-features = false, features = ["singlepass", "universal"] } [target.'cfg(not(target_arch = "x86_64"))'.dev-dependencies] -wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } +wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] } [[bench]] name = "time_bench" diff --git a/cli/src/build.rs b/cli/src/build.rs index be2bd0736e..72f9a53c05 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -4,7 +4,7 @@ use roc_build::{ program::{self, Problems}, }; use roc_builtins::bitcode; -use roc_load::LoadingProblem; +use roc_load::{LoadingProblem, Threading}; use roc_mono::ir::OptLevel; use roc_reporting::report::RenderTarget; use roc_target::TargetInfo; @@ -40,6 +40,7 @@ pub fn build_file<'a>( surgically_link: bool, precompiled: bool, target_valgrind: bool, + threading: Threading, ) -> Result> { let compilation_start = SystemTime::now(); let target_info = TargetInfo::from(target); @@ -55,6 +56,7 @@ pub fn build_file<'a>( target_info, // TODO: expose this from CLI? RenderTarget::ColorTerminal, + threading, )?; use target_lexicon::Architecture; @@ -348,6 +350,7 @@ pub fn check_file( src_dir: PathBuf, roc_file_path: PathBuf, emit_timings: bool, + threading: Threading, ) -> Result<(program::Problems, Duration), LoadingProblem> { let compilation_start = SystemTime::now(); @@ -366,6 +369,7 @@ pub fn check_file( target_info, // TODO: expose this from CLI? RenderTarget::ColorTerminal, + threading, )?; let buf = &mut String::with_capacity(1024); diff --git a/cli/src/format.rs b/cli/src/format.rs index ea77c5f1dc..e279eddd05 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -2,29 +2,17 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; use crate::FormatMode; -use bumpalo::collections::Vec; use bumpalo::Bump; use roc_error_macros::{internal_error, user_error}; use roc_fmt::def::fmt_def; use roc_fmt::module::fmt_module; -use roc_fmt::Buf; -use roc_module::called_via::{BinOp, UnaryOp}; -use roc_parse::ast::{ - AbilityMember, AssignedField, Collection, Expr, Has, HasClause, Pattern, Spaced, StrLiteral, - StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch, -}; -use roc_parse::header::{ - AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, -}; +use roc_fmt::spaces::RemoveSpaces; +use roc_fmt::{Ast, Buf}; use roc_parse::{ - ast::{Def, Module}, - ident::UppercaseIdent, module::{self, module_defs}, parser::{Parser, SyntaxError}, state::State, }; -use roc_region::all::{Loc, Region}; fn flatten_directories(files: std::vec::Vec) -> std::vec::Vec { let mut to_flatten = files; @@ -166,12 +154,6 @@ pub fn format(files: std::vec::Vec, mode: FormatMode) -> Result<(), Str Ok(()) } -#[derive(Debug, PartialEq)] -struct Ast<'a> { - module: Module<'a>, - defs: Vec<'a, Loc>>, -} - fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result, SyntaxError<'a>> { let (module, state) = module::parse_header(arena, State::new(src.as_bytes())) .map_err(|e| SyntaxError::Header(e.problem))?; @@ -189,581 +171,3 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) { buf.fmt_end_of_file(); } - -/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting. -/// -/// Currently this consists of: -/// * Removing newlines -/// * Removing comments -/// * Removing parens in Exprs -/// -/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting) -/// - but there are currently several bugs where they're _not_ preserved. -/// TODO: ensure formatting retains comments -trait RemoveSpaces<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self; -} - -impl<'a> RemoveSpaces<'a> for Ast<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - Ast { - module: self.module.remove_spaces(arena), - defs: { - let mut defs = Vec::with_capacity_in(self.defs.len(), arena); - for d in &self.defs { - defs.push(d.remove_spaces(arena)) - } - defs - }, - } - } -} - -impl<'a> RemoveSpaces<'a> for Module<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match self { - Module::Interface { header } => Module::Interface { - header: InterfaceHeader { - name: header.name.remove_spaces(arena), - exposes: header.exposes.remove_spaces(arena), - imports: header.imports.remove_spaces(arena), - before_header: &[], - after_interface_keyword: &[], - before_exposes: &[], - after_exposes: &[], - before_imports: &[], - after_imports: &[], - }, - }, - Module::App { header } => Module::App { - header: AppHeader { - name: header.name.remove_spaces(arena), - packages: header.packages.remove_spaces(arena), - imports: header.imports.remove_spaces(arena), - provides: header.provides.remove_spaces(arena), - provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)), - to: header.to.remove_spaces(arena), - before_header: &[], - after_app_keyword: &[], - before_packages: &[], - after_packages: &[], - before_imports: &[], - after_imports: &[], - before_provides: &[], - after_provides: &[], - before_to: &[], - after_to: &[], - }, - }, - Module::Platform { header } => Module::Platform { - header: PlatformHeader { - name: header.name.remove_spaces(arena), - requires: header.requires.remove_spaces(arena), - exposes: header.exposes.remove_spaces(arena), - packages: header.packages.remove_spaces(arena), - imports: header.imports.remove_spaces(arena), - provides: header.provides.remove_spaces(arena), - before_header: &[], - after_platform_keyword: &[], - before_requires: &[], - after_requires: &[], - before_exposes: &[], - after_exposes: &[], - before_packages: &[], - after_packages: &[], - before_imports: &[], - after_imports: &[], - before_provides: &[], - after_provides: &[], - }, - }, - Module::Hosted { header } => Module::Hosted { - header: HostedHeader { - name: header.name.remove_spaces(arena), - exposes: header.exposes.remove_spaces(arena), - imports: header.imports.remove_spaces(arena), - generates: header.generates.remove_spaces(arena), - generates_with: header.generates_with.remove_spaces(arena), - before_header: &[], - after_hosted_keyword: &[], - before_exposes: &[], - after_exposes: &[], - before_imports: &[], - after_imports: &[], - before_generates: &[], - after_generates: &[], - before_with: &[], - after_with: &[], - }, - }, - } - } -} - -impl<'a> RemoveSpaces<'a> for &'a str { - fn remove_spaces(&self, _arena: &'a Bump) -> Self { - self - } -} - -impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)), - Spaced::SpaceBefore(a, _) => a.remove_spaces(arena), - Spaced::SpaceAfter(a, _) => a.remove_spaces(arena), - } - } -} - -impl<'a> RemoveSpaces<'a> for ExposedName<'a> { - fn remove_spaces(&self, _arena: &'a Bump) -> Self { - *self - } -} - -impl<'a> RemoveSpaces<'a> for ModuleName<'a> { - fn remove_spaces(&self, _arena: &'a Bump) -> Self { - *self - } -} - -impl<'a> RemoveSpaces<'a> for PackageName<'a> { - fn remove_spaces(&self, _arena: &'a Bump) -> Self { - *self - } -} - -impl<'a> RemoveSpaces<'a> for To<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - To::ExistingPackage(a) => To::ExistingPackage(a), - To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)), - } - } -} - -impl<'a> RemoveSpaces<'a> for TypedIdent<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - TypedIdent { - ident: self.ident.remove_spaces(arena), - spaces_before_colon: &[], - ann: self.ann.remove_spaces(arena), - } - } -} - -impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - PlatformRequires { - rigids: self.rigids.remove_spaces(arena), - signature: self.signature.remove_spaces(arena), - } - } -} - -impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> { - fn remove_spaces(&self, _arena: &'a Bump) -> Self { - *self - } -} - -impl<'a> RemoveSpaces<'a> for PackageEntry<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - PackageEntry { - shorthand: self.shorthand, - spaces_after_shorthand: &[], - package_name: self.package_name.remove_spaces(arena), - } - } -} - -impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)), - ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)), - } - } -} - -impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - self.as_ref().map(|a| a.remove_spaces(arena)) - } -} - -impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - let res = self.value.remove_spaces(arena); - Loc::at(Region::zero(), res) - } -} - -impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - (self.0.remove_spaces(arena), self.1.remove_spaces(arena)) - } -} - -impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - let mut items = Vec::with_capacity_in(self.items.len(), arena); - for item in self.items { - items.push(item.remove_spaces(arena)); - } - Collection::with_items(items.into_bump_slice()) - } -} - -impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - let mut items = Vec::with_capacity_in(self.len(), arena); - for item in *self { - let res = item.remove_spaces(arena); - items.push(res); - } - items.into_bump_slice() - } -} - -impl<'a> RemoveSpaces<'a> for UnaryOp { - fn remove_spaces(&self, _arena: &'a Bump) -> Self { - *self - } -} - -impl<'a> RemoveSpaces<'a> for BinOp { - fn remove_spaces(&self, _arena: &'a Bump) -> Self { - *self - } -} - -impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - arena.alloc((*self).remove_spaces(arena)) - } -} - -impl<'a> RemoveSpaces<'a> for TypeDef<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - use TypeDef::*; - - match *self { - Alias { - header: TypeHeader { name, vars }, - ann, - } => Alias { - header: TypeHeader { - name: name.remove_spaces(arena), - vars: vars.remove_spaces(arena), - }, - ann: ann.remove_spaces(arena), - }, - Opaque { - header: TypeHeader { name, vars }, - typ, - } => Opaque { - header: TypeHeader { - name: name.remove_spaces(arena), - vars: vars.remove_spaces(arena), - }, - typ: typ.remove_spaces(arena), - }, - Ability { - header: TypeHeader { name, vars }, - loc_has, - members, - } => Ability { - header: TypeHeader { - name: name.remove_spaces(arena), - vars: vars.remove_spaces(arena), - }, - loc_has: loc_has.remove_spaces(arena), - members: members.remove_spaces(arena), - }, - } - } -} - -impl<'a> RemoveSpaces<'a> for ValueDef<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - use ValueDef::*; - - match *self { - Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)), - Body(a, b) => Body( - arena.alloc(a.remove_spaces(arena)), - arena.alloc(b.remove_spaces(arena)), - ), - AnnotatedBody { - ann_pattern, - ann_type, - comment: _, - body_pattern, - body_expr, - } => AnnotatedBody { - ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)), - ann_type: arena.alloc(ann_type.remove_spaces(arena)), - comment: None, - body_pattern: arena.alloc(body_pattern.remove_spaces(arena)), - body_expr: arena.alloc(body_expr.remove_spaces(arena)), - }, - Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))), - } - } -} - -impl<'a> RemoveSpaces<'a> for Def<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - Def::Type(def) => Def::Type(def.remove_spaces(arena)), - Def::Value(def) => Def::Value(def.remove_spaces(arena)), - Def::NotYetImplemented(a) => Def::NotYetImplemented(a), - Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena), - } - } -} - -impl<'a> RemoveSpaces<'a> for Has<'a> { - fn remove_spaces(&self, _arena: &'a Bump) -> Self { - Has::Has - } -} - -impl<'a> RemoveSpaces<'a> for AbilityMember<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - AbilityMember { - name: self.name.remove_spaces(arena), - typ: self.typ.remove_spaces(arena), - } - } -} - -impl<'a> RemoveSpaces<'a> for WhenBranch<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - WhenBranch { - patterns: self.patterns.remove_spaces(arena), - value: self.value.remove_spaces(arena), - guard: self.guard.remove_spaces(arena), - } - } -} - -impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue( - a.remove_spaces(arena), - arena.alloc([]), - arena.alloc(c.remove_spaces(arena)), - ), - AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue( - a.remove_spaces(arena), - arena.alloc([]), - arena.alloc(c.remove_spaces(arena)), - ), - AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)), - AssignedField::Malformed(a) => AssignedField::Malformed(a), - AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena), - AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena), - } - } -} - -impl<'a> RemoveSpaces<'a> for StrLiteral<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t), - StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)), - StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)), - } - } -} - -impl<'a> RemoveSpaces<'a> for StrSegment<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - StrSegment::Plaintext(t) => StrSegment::Plaintext(t), - StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)), - StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c), - StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)), - } - } -} - -impl<'a> RemoveSpaces<'a> for Expr<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - Expr::Float(a) => Expr::Float(a), - Expr::Num(a) => Expr::Num(a), - Expr::NonBase10Int { - string, - base, - is_negative, - } => Expr::NonBase10Int { - string, - base, - is_negative, - }, - Expr::Str(a) => Expr::Str(a.remove_spaces(arena)), - Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b), - Expr::AccessorFunction(a) => Expr::AccessorFunction(a), - Expr::List(a) => Expr::List(a.remove_spaces(arena)), - Expr::RecordUpdate { update, fields } => Expr::RecordUpdate { - update: arena.alloc(update.remove_spaces(arena)), - fields: fields.remove_spaces(arena), - }, - Expr::Record(a) => Expr::Record(a.remove_spaces(arena)), - Expr::Var { module_name, ident } => Expr::Var { module_name, ident }, - Expr::Underscore(a) => Expr::Underscore(a), - Expr::GlobalTag(a) => Expr::GlobalTag(a), - Expr::PrivateTag(a) => Expr::PrivateTag(a), - Expr::OpaqueRef(a) => Expr::OpaqueRef(a), - Expr::Closure(a, b) => Expr::Closure( - arena.alloc(a.remove_spaces(arena)), - arena.alloc(b.remove_spaces(arena)), - ), - Expr::Defs(a, b) => { - Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))) - } - Expr::Backpassing(a, b, c) => Expr::Backpassing( - arena.alloc(a.remove_spaces(arena)), - arena.alloc(b.remove_spaces(arena)), - arena.alloc(c.remove_spaces(arena)), - ), - Expr::Expect(a, b) => Expr::Expect( - arena.alloc(a.remove_spaces(arena)), - arena.alloc(b.remove_spaces(arena)), - ), - Expr::Apply(a, b, c) => Expr::Apply( - arena.alloc(a.remove_spaces(arena)), - b.remove_spaces(arena), - c, - ), - Expr::BinOps(a, b) => { - Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))) - } - Expr::UnaryOp(a, b) => { - Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena)) - } - Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))), - Expr::When(a, b) => { - Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena)) - } - Expr::ParensAround(a) => { - // The formatter can remove redundant parentheses, so also remove these when normalizing for comparison. - a.remove_spaces(arena) - } - Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b), - Expr::MalformedClosure => Expr::MalformedClosure, - Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a), - Expr::SpaceBefore(a, _) => a.remove_spaces(arena), - Expr::SpaceAfter(a, _) => a.remove_spaces(arena), - Expr::SingleQuote(a) => Expr::Num(a), - } - } -} - -impl<'a> RemoveSpaces<'a> for Pattern<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - Pattern::Identifier(a) => Pattern::Identifier(a), - Pattern::GlobalTag(a) => Pattern::GlobalTag(a), - Pattern::PrivateTag(a) => Pattern::PrivateTag(a), - Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a), - Pattern::Apply(a, b) => Pattern::Apply( - arena.alloc(a.remove_spaces(arena)), - arena.alloc(b.remove_spaces(arena)), - ), - Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)), - Pattern::RequiredField(a, b) => { - Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena))) - } - Pattern::OptionalField(a, b) => { - Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena))) - } - Pattern::NumLiteral(a) => Pattern::NumLiteral(a), - Pattern::NonBase10Literal { - string, - base, - is_negative, - } => Pattern::NonBase10Literal { - string, - base, - is_negative, - }, - Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a), - Pattern::StrLiteral(a) => Pattern::StrLiteral(a), - Pattern::Underscore(a) => Pattern::Underscore(a), - Pattern::Malformed(a) => Pattern::Malformed(a), - Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b), - Pattern::QualifiedIdentifier { module_name, ident } => { - Pattern::QualifiedIdentifier { module_name, ident } - } - Pattern::SpaceBefore(a, _) => a.remove_spaces(arena), - Pattern::SpaceAfter(a, _) => a.remove_spaces(arena), - Pattern::SingleQuote(a) => Pattern::NumLiteral(a), - } - } -} - -impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - TypeAnnotation::Function(a, b) => TypeAnnotation::Function( - arena.alloc(a.remove_spaces(arena)), - arena.alloc(b.remove_spaces(arena)), - ), - TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)), - TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a), - TypeAnnotation::As(a, _, c) => { - TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c) - } - TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record { - fields: fields.remove_spaces(arena), - ext: ext.remove_spaces(arena), - }, - TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion { - ext: ext.remove_spaces(arena), - tags: tags.remove_spaces(arena), - }, - TypeAnnotation::Inferred => TypeAnnotation::Inferred, - TypeAnnotation::Wildcard => TypeAnnotation::Wildcard, - TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where( - arena.alloc(annot.remove_spaces(arena)), - arena.alloc(has_clauses.remove_spaces(arena)), - ), - TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena), - TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena), - TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a), - } - } -} - -impl<'a> RemoveSpaces<'a> for HasClause<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - HasClause { - var: self.var.remove_spaces(arena), - ability: self.ability.remove_spaces(arena), - } - } -} - -impl<'a> RemoveSpaces<'a> for Tag<'a> { - fn remove_spaces(&self, arena: &'a Bump) -> Self { - match *self { - Tag::Global { name, args } => Tag::Global { - name: name.remove_spaces(arena), - args: args.remove_spaces(arena), - }, - Tag::Private { name, args } => Tag::Private { - name: name.remove_spaces(arena), - args: args.remove_spaces(arena), - }, - Tag::Malformed(a) => Tag::Malformed(a), - Tag::SpaceBefore(a, _) => a.remove_spaces(arena), - Tag::SpaceAfter(a, _) => a.remove_spaces(arena), - } - } -} diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 25f831d1f0..49d733ca85 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -3,17 +3,16 @@ extern crate const_format; use build::BuiltFile; use bumpalo::Bump; -use clap::{App, AppSettings, Arg, ArgMatches}; +use clap::{Arg, ArgMatches, Command}; use roc_build::link::LinkType; use roc_error_macros::user_error; -use roc_load::LoadingProblem; +use roc_load::{LoadingProblem, Threading}; use roc_mono::ir::OptLevel; use std::env; +use std::ffi::OsStr; use std::io; use std::path::Path; -use std::path::PathBuf; use std::process; -use std::process::Command; use target_lexicon::BinaryFormat; use target_lexicon::{ Architecture, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture, @@ -35,12 +34,12 @@ pub const CMD_FORMAT: &str = "format"; pub const FLAG_DEBUG: &str = "debug"; pub const FLAG_DEV: &str = "dev"; pub const FLAG_OPTIMIZE: &str = "optimize"; +pub const FLAG_MAX_THREADS: &str = "max-threads"; pub const FLAG_OPT_SIZE: &str = "opt-size"; pub const FLAG_LIB: &str = "lib"; pub const FLAG_NO_LINK: &str = "no-link"; pub const FLAG_TARGET: &str = "target"; pub const FLAG_TIME: &str = "time"; -pub const FLAG_LINK: &str = "roc-linker"; pub const FLAG_LINKER: &str = "linker"; pub const FLAG_PRECOMPILED: &str = "precompiled-host"; pub const FLAG_VALGRIND: &str = "valgrind"; @@ -52,39 +51,87 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; const VERSION: &str = include_str!("../../version.txt"); -pub fn build_app<'a>() -> App<'a> { - let app = App::new("roc") +pub fn build_app<'a>() -> Command<'a> { + let flag_optimize = Arg::new(FLAG_OPTIMIZE) + .long(FLAG_OPTIMIZE) + .help("Optimize the compiled program to run faster. (Optimization takes time to complete.)") + .requires(ROC_FILE) + .required(false); + + let flag_max_threads = Arg::new(FLAG_MAX_THREADS) + .long(FLAG_MAX_THREADS) + .help("Limit the number of threads (and hence cores) used during compilation.") + .requires(ROC_FILE) + .takes_value(true) + .validator(|s| s.parse::()) + .required(false); + + let flag_opt_size = Arg::new(FLAG_OPT_SIZE) + .long(FLAG_OPT_SIZE) + .help("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)") + .required(false); + + let flag_dev = Arg::new(FLAG_DEV) + .long(FLAG_DEV) + .help("Make compilation finish as soon as possible, at the expense of runtime performance.") + .required(false); + + let flag_debug = Arg::new(FLAG_DEBUG) + .long(FLAG_DEBUG) + .help("Store LLVM debug information in the generated program.") + .requires(ROC_FILE) + .required(false); + + let flag_valgrind = Arg::new(FLAG_VALGRIND) + .long(FLAG_VALGRIND) + .help("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.") + .required(false); + + let flag_time = Arg::new(FLAG_TIME) + .long(FLAG_TIME) + .help("Prints detailed compilation time information.") + .required(false); + + let flag_linker = Arg::new(FLAG_LINKER) + .long(FLAG_LINKER) + .help("Sets which linker to use. The surgical linker is enabled by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.") + .possible_values(["surgical", "legacy"]) + .required(false); + + let flag_precompiled = Arg::new(FLAG_PRECOMPILED) + .long(FLAG_PRECOMPILED) + .help("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)") + .possible_values(["true", "false"]) + .required(false); + + let roc_file_to_run = Arg::new(ROC_FILE) + .help("The .roc file of an app to run") + .allow_invalid_utf8(true); + + let args_for_app = Arg::new(ARGS_FOR_APP) + .help("Arguments to pass into the app being run") + .requires(ROC_FILE) + .allow_invalid_utf8(true) + .multiple_values(true); + + let app = Command::new("roc") .version(concatcp!(VERSION, "\n")) .about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!") - .subcommand(App::new(CMD_BUILD) + .subcommand(Command::new(CMD_BUILD) .about("Build a binary from the given .roc file, but don't run it") - .arg( - Arg::new(ROC_FILE) - .about("The .roc file to build") - .required(true), - ) - .arg( - Arg::new(FLAG_OPTIMIZE) - .long(FLAG_OPTIMIZE) - .about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)") - .required(false), - ) - .arg( - Arg::new(FLAG_OPT_SIZE) - .long(FLAG_OPT_SIZE) - .about("Optimize your compiled Roc program to have a small binary size. (Optimization takes time to complete.)") - .required(false), - ) - .arg( - Arg::new(FLAG_DEV) - .long(FLAG_DEV) - .about("Make compilation as fast as possible. (Runtime performance may suffer)") - .required(false), - ) + .arg(flag_optimize.clone()) + .arg(flag_max_threads.clone()) + .arg(flag_opt_size.clone()) + .arg(flag_dev.clone()) + .arg(flag_debug.clone()) + .arg(flag_time.clone()) + .arg(flag_linker.clone()) + .arg(flag_precompiled.clone()) + .arg(flag_valgrind.clone()) .arg( Arg::new(FLAG_TARGET) .long(FLAG_TARGET) - .about("Choose a different target") + .help("Choose a different target") .default_value(Target::default().as_str()) .possible_values(Target::OPTIONS) .required(false), @@ -92,181 +139,99 @@ pub fn build_app<'a>() -> App<'a> { .arg( Arg::new(FLAG_LIB) .long(FLAG_LIB) - .about("Build a C library instead of an executable.") + .help("Build a C library instead of an executable.") .required(false), ) .arg( Arg::new(FLAG_NO_LINK) .long(FLAG_NO_LINK) - .about("Does not link. Instead just outputs the `.o` file") + .help("Does not link. Instead just outputs the `.o` file") .required(false), ) - .arg( - Arg::new(FLAG_DEBUG) - .long(FLAG_DEBUG) - .about("Store LLVM debug information in the generated program") - .required(false), - ) - .arg( - Arg::new(FLAG_TIME) - .long(FLAG_TIME) - .about("Prints detailed compilation time information.") - .required(false), - ) - .arg( - Arg::new(FLAG_LINK) - .long(FLAG_LINK) - .about("Deprecated in favor of --linker") - .required(false), - ) - .arg( - Arg::new(FLAG_LINKER) - .long(FLAG_LINKER) - .about("Sets which linker to use. The surgical linker is enabeld by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.") - .possible_values(["surgical", "legacy"]) - .required(false), - ) - .arg( - Arg::new(FLAG_PRECOMPILED) - .long(FLAG_PRECOMPILED) - .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)") - .possible_values(["true", "false"]) - .required(false), - ) - .arg( - Arg::new(FLAG_VALGRIND) - .long(FLAG_VALGRIND) - .about("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.") - .required(false), - ) - ) - .subcommand(App::new(CMD_REPL) - .about("Launch the interactive Read Eval Print Loop (REPL)") - ) - .subcommand(App::new(CMD_RUN) - .about("Run a .roc file even if it has build errors") .arg( Arg::new(ROC_FILE) - .about("The .roc file of an app to run") + .help("The .roc file to build") + .allow_invalid_utf8(true) .required(true), ) ) - .subcommand(App::new(CMD_FORMAT) + .subcommand(Command::new(CMD_REPL) + .about("Launch the interactive Read Eval Print Loop (REPL)") + ) + .subcommand(Command::new(CMD_RUN) + .about("Run a .roc file even if it has build errors") + .arg(flag_optimize.clone()) + .arg(flag_max_threads.clone()) + .arg(flag_opt_size.clone()) + .arg(flag_dev.clone()) + .arg(flag_debug.clone()) + .arg(flag_time.clone()) + .arg(flag_linker.clone()) + .arg(flag_precompiled.clone()) + .arg(flag_valgrind.clone()) + .arg(roc_file_to_run.clone().required(true)) + .arg(args_for_app.clone()) + ) + .subcommand(Command::new(CMD_FORMAT) .about("Format a .roc file using standard Roc formatting") .arg( Arg::new(DIRECTORY_OR_FILES) .index(1) .multiple_values(true) - .required(false)) + .required(false) + .allow_invalid_utf8(true)) .arg( Arg::new(FLAG_CHECK) .long(FLAG_CHECK) - .about("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.") + .help("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.") .required(false), ) ) - .subcommand(App::new(CMD_VERSION) + .subcommand(Command::new(CMD_VERSION) .about(concatcp!("Print the Roc compiler’s version, which is currently ", VERSION))) - .subcommand(App::new(CMD_CHECK) + .subcommand(Command::new(CMD_CHECK) .about("Check the code for problems, but doesn’t build or run it") - .arg( - Arg::new(FLAG_TIME) - .long(FLAG_TIME) - .about("Prints detailed compilation time information.") - .required(false), - ) + .arg(flag_time.clone()) + .arg(flag_max_threads.clone()) .arg( Arg::new(ROC_FILE) - .about("The .roc file of an app to check") + .help("The .roc file of an app to check") + .allow_invalid_utf8(true) .required(true), ) ) .subcommand( - App::new(CMD_DOCS) + Command::new(CMD_DOCS) .about("Generate documentation for Roc modules (Work In Progress)") .arg(Arg::new(DIRECTORY_OR_FILES) - .index(1) .multiple_values(true) .required(false) - .about("The directory or files to build documentation for") - + .help("The directory or files to build documentation for") + .allow_invalid_utf8(true) ) ) - .setting(AppSettings::TrailingVarArg) - .arg( - Arg::new(FLAG_OPTIMIZE) - .long(FLAG_OPTIMIZE) - .about("Optimize the compiled program to run faster. (Optimization takes time to complete.)") - .requires(ROC_FILE) - .required(false), - ) - .arg( - Arg::new(FLAG_OPT_SIZE) - .long(FLAG_OPT_SIZE) - .about("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)") - .required(false), - ) - .arg( - Arg::new(FLAG_DEV) - .long(FLAG_DEV) - .about("Make compilation finish as soon as possible, at the expense of runtime performance.") - .required(false), - ) - .arg( - Arg::new(FLAG_DEBUG) - .long(FLAG_DEBUG) - .about("Store LLVM debug information in the generated program.") - .requires(ROC_FILE) - .required(false), - ) - .arg( - Arg::new(FLAG_TIME) - .long(FLAG_TIME) - .about("Prints detailed compilation time information.") - .required(false), - ) - .arg( - Arg::new(FLAG_LINK) - .long(FLAG_LINK) - .about("Deprecated in favor of --linker") - .required(false), - ) - .arg( - Arg::new(FLAG_LINKER) - .long(FLAG_LINKER) - .about("Sets which linker to use. The surgical linker is enabeld by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.") - .possible_values(["surgical", "legacy"]) - .required(false), - ) - .arg( - Arg::new(FLAG_PRECOMPILED) - .long(FLAG_PRECOMPILED) - .about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)") - .possible_values(["true", "false"]) - .required(false), - ) - .arg( - Arg::new(ROC_FILE) - .about("The .roc file of an app to build and run") - .required(false), - ) - .arg( - Arg::new(ARGS_FOR_APP) - .about("Arguments to pass into the app being run") - .requires(ROC_FILE) - .multiple_values(true), - ); + .trailing_var_arg(true) + .arg(flag_optimize) + .arg(flag_max_threads.clone()) + .arg(flag_opt_size) + .arg(flag_dev) + .arg(flag_debug) + .arg(flag_time) + .arg(flag_linker) + .arg(flag_precompiled) + .arg(flag_valgrind) + .arg(roc_file_to_run.required(false)) + .arg(args_for_app); if cfg!(feature = "editor") { app.subcommand( - App::new(CMD_EDIT) + Command::new(CMD_EDIT) .about("Launch the Roc editor (Work In Progress)") .arg( Arg::new(DIRECTORY_OR_FILES) - .index(1) .multiple_values(true) .required(false) - .about("(optional) The directory or files to open on launch."), + .help("(optional) The directory or files to open on launch."), ), ) } else { @@ -274,15 +239,11 @@ pub fn build_app<'a>() -> App<'a> { } } -pub fn docs(files: Vec) { - roc_docs::generate_docs_html(files, Path::new("./generated-docs")) -} - #[derive(Debug, PartialEq, Eq)] pub enum BuildConfig { BuildOnly, - BuildAndRun { roc_file_arg_index: usize }, - BuildAndRunIfNoErrors { roc_file_arg_index: usize }, + BuildAndRun, + BuildAndRunIfNoErrors, } pub enum FormatMode { @@ -290,20 +251,17 @@ pub enum FormatMode { CheckOnly, } -pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { +pub fn build( + matches: &ArgMatches, + config: BuildConfig, + triple: Triple, + link_type: LinkType, +) -> io::Result { use build::build_file; - use std::str::FromStr; use BuildConfig::*; - let target = match matches.value_of(FLAG_TARGET) { - Some(name) => Target::from_str(name).unwrap(), - None => Target::default(), - }; - - let triple = target.to_triple(); - let arena = Bump::new(); - let filename = matches.value_of(ROC_FILE).unwrap(); + let filename = matches.value_of_os(ROC_FILE).unwrap(); let original_cwd = std::env::current_dir()?; let opt_level = match ( @@ -320,22 +278,16 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { let emit_debug_info = matches.is_present(FLAG_DEBUG); let emit_timings = matches.is_present(FLAG_TIME); - let link_type = match ( - matches.is_present(FLAG_LIB), - matches.is_present(FLAG_NO_LINK), - ) { - (true, false) => LinkType::Dylib, - (true, true) => user_error!("build can only be one of `--lib` or `--no-link`"), - (false, true) => LinkType::None, - (false, false) => LinkType::Executable, + let threading = match matches + .value_of(FLAG_MAX_THREADS) + .and_then(|s| s.parse::().ok()) + { + None => Threading::AllAvailable, + Some(0) => user_error!("cannot build with at most 0 threads"), + Some(1) => Threading::Single, + Some(n) => Threading::AtMost(n), }; - // TODO remove FLAG_LINK from the code base anytime after the end of May 2022 - if matches.is_present(FLAG_LINK) { - eprintln!("ERROR: The --roc-linker flag has been deprecated because the roc linker is now used automatically where it's supported. (Currently that's only x64 Linux.) No need to use --roc-linker anymore, but you can use the --linker flag to switch linkers."); - process::exit(1); - } - // Use surgical linking when supported, or when explicitly requested with --linker surgical let surgically_link = if matches.is_present(FLAG_LINKER) { matches.value_of(FLAG_LINKER) == Some("surgical") @@ -348,7 +300,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { } else { // When compiling for a different target, default to assuming a precompiled host. // Otherwise compilation would most likely fail! - target != Target::System + triple != Triple::host() }; let path = Path::new(filename); @@ -385,6 +337,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { surgically_link, precompiled, target_valgrind, + threading, ); match res_binary_path { @@ -435,7 +388,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { // Return a nonzero exit code if there were problems Ok(problems.exit_code()) } - BuildAndRun { roc_file_arg_index } => { + BuildAndRun => { if problems.errors > 0 || problems.warnings > 0 { println!( "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m", @@ -466,15 +419,11 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { ); } - roc_run( - arena, - &original_cwd, - triple, - roc_file_arg_index, - &binary_path, - ) + let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default(); + + roc_run(arena, &original_cwd, triple, args, &binary_path) } - BuildAndRunIfNoErrors { roc_file_arg_index } => { + BuildAndRunIfNoErrors => { if problems.errors == 0 { if problems.warnings > 0 { println!( @@ -490,13 +439,9 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { ); } - roc_run( - arena, - &original_cwd, - triple, - roc_file_arg_index, - &binary_path, - ) + let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default(); + + roc_run(arena, &original_cwd, triple, args, &binary_path) } else { println!( "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with: \x1B[32mroc run {}\x1B[39m", @@ -523,7 +468,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { "warnings" }, total_time.as_millis(), - filename + filename.to_string_lossy() ); Ok(problems.exit_code()) @@ -542,17 +487,14 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { } } -#[cfg(target_family = "unix")] -fn roc_run( +fn roc_run<'a, I: IntoIterator>( arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it! cwd: &Path, triple: Triple, - roc_file_arg_index: usize, + args: I, binary_path: &Path, ) -> io::Result { - use std::os::unix::process::CommandExt; - - let mut cmd = match triple.architecture { + match triple.architecture { Architecture::Wasm32 => { // If possible, report the generated executable name relative to the current dir. let generated_filename = binary_path @@ -563,19 +505,44 @@ fn roc_run( // since the process is about to exit anyway. std::mem::forget(arena); - let args = std::env::args() - .skip(roc_file_arg_index) - .collect::>(); + if cfg!(target_family = "unix") { + use std::os::unix::ffi::OsStrExt; - run_with_wasmer(generated_filename, &args); - return Ok(0); + run_with_wasmer( + generated_filename, + args.into_iter().map(|os_str| os_str.as_bytes()), + ); + } else { + run_with_wasmer( + generated_filename, + args.into_iter().map(|os_str| { + os_str.to_str().expect( + "Roc does not currently support passing non-UTF8 arguments to Wasmer.", + ) + }), + ); + } + + Ok(0) + } + _ => { + if cfg!(target_family = "unix") { + roc_run_unix(cwd, args, binary_path) + } else { + roc_run_non_unix(arena, cwd, args, binary_path) + } } - _ => Command::new(&binary_path), - }; - - if let Architecture::Wasm32 = triple.architecture { - cmd.arg(binary_path); } +} + +fn roc_run_unix, S: AsRef>( + cwd: &Path, + args: I, + binary_path: &Path, +) -> io::Result { + use std::os::unix::process::CommandExt; + + let mut cmd = std::process::Command::new(&binary_path); // Forward all the arguments after the .roc file argument // to the new process. This way, you can do things like: @@ -584,10 +551,8 @@ fn roc_run( // // ...and have it so that app.roc will receive only `foo`, // `bar`, and `baz` as its arguments. - for (index, arg) in std::env::args().enumerate() { - if index > roc_file_arg_index { - cmd.arg(arg); - } + for arg in args { + cmd.arg(arg); } // This is much faster than spawning a subprocess if we're on a UNIX system! @@ -599,29 +564,36 @@ fn roc_run( Err(err) } -#[cfg(not(target_family = "unix"))] -fn roc_run(cmd: &mut Command) -> io::Result { - // Run the compiled app - let exit_status = cmd - .spawn() - .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err)) - .wait() - .expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app"); +fn roc_run_non_unix, S: AsRef>( + _arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it! + _cwd: &Path, + _args: I, + _binary_path: &Path, +) -> io::Result { + todo!("TODO support running roc programs on non-UNIX targets"); + // let mut cmd = std::process::Command::new(&binary_path); - // `roc [FILE]` exits with the same status code as the app it ran. - // - // If you want to know whether there were compilation problems - // via status code, use either `roc build` or `roc check` instead! - match exit_status.code() { - Some(code) => Ok(code), - None => { - todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal."); - } - } + // // Run the compiled app + // let exit_status = cmd + // .spawn() + // .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err)) + // .wait() + // .expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app"); + + // // `roc [FILE]` exits with the same status code as the app it ran. + // // + // // If you want to know whether there were compilation problems + // // via status code, use either `roc build` or `roc check` instead! + // match exit_status.code() { + // Some(code) => Ok(code), + // None => { + // todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal."); + // } + // } } #[cfg(feature = "run-wasm32")] -fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) { +fn run_with_wasmer, S: AsRef<[u8]>>(wasm_path: &std::path::Path, args: I) { use wasmer::{Instance, Module, Store}; let store = Store::default(); @@ -652,12 +624,12 @@ fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) { } #[cfg(not(feature = "run-wasm32"))] -fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) { - println!("Running wasm files not support"); +fn run_with_wasmer, S: AsRef<[u8]>>(_wasm_path: &std::path::Path, _args: I) { + println!("Running wasm files is not supported on this target."); } #[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum Target { +pub enum Target { System, Linux32, Linux64, @@ -690,7 +662,7 @@ impl Target { Target::Wasm32.as_str(), ]; - fn to_triple(self) -> Triple { + pub fn to_triple(self) -> Triple { use Target::*; match self { @@ -733,15 +705,15 @@ impl std::fmt::Display for Target { } impl std::str::FromStr for Target { - type Err = (); + type Err = String; - fn from_str(s: &str) -> Result { - match s { + fn from_str(string: &str) -> Result { + match string { "system" => Ok(Target::System), "linux32" => Ok(Target::Linux32), "linux64" => Ok(Target::Linux64), "wasm32" => Ok(Target::Wasm32), - _ => Err(()), + _ => Err(format!("Roc does not know how to compile to {}", string)), } } } diff --git a/cli/src/main.rs b/cli/src/main.rs index b38445980b..4ee66ef296 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,13 +1,17 @@ +use roc_build::link::LinkType; use roc_cli::build::check_file; use roc_cli::{ - build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, - CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, - ROC_FILE, + build_app, format, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, + CMD_FORMAT, CMD_REPL, CMD_RUN, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB, + FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, ROC_FILE, }; -use roc_load::LoadingProblem; +use roc_docs::generate_docs_html; +use roc_error_macros::user_error; +use roc_load::{LoadingProblem, Threading}; use std::fs::{self, FileType}; use std::io; use std::path::{Path, PathBuf}; +use target_lexicon::Triple; #[macro_use] extern crate const_format; @@ -24,48 +28,72 @@ fn main() -> io::Result<()> { let exit_code = match matches.subcommand() { None => { - match matches.index_of(ROC_FILE) { - Some(arg_index) => { - let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! + if matches.is_present(ROC_FILE) { + build( + &matches, + BuildConfig::BuildAndRunIfNoErrors, + Triple::host(), + LinkType::Executable, + ) + } else { + launch_editor(None)?; - build( - &matches, - BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index }, - ) - } - - None => { - launch_editor(None)?; - - Ok(0) - } + Ok(0) } } Some((CMD_RUN, matches)) => { - match matches.index_of(ROC_FILE) { - Some(arg_index) => { - let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is! + if matches.is_present(ROC_FILE) { + build( + matches, + BuildConfig::BuildAndRun, + Triple::host(), + LinkType::Executable, + ) + } else { + eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command."); - build(matches, BuildConfig::BuildAndRun { roc_file_arg_index }) - } - - None => { - eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command."); - - Ok(1) - } + Ok(1) } } - Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?), + Some((CMD_BUILD, matches)) => { + let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default(); + + let link_type = match ( + matches.is_present(FLAG_LIB), + matches.is_present(FLAG_NO_LINK), + ) { + (true, false) => LinkType::Dylib, + (true, true) => user_error!("build can only be one of `--lib` or `--no-link`"), + (false, true) => LinkType::None, + (false, false) => LinkType::Executable, + }; + + Ok(build( + matches, + BuildConfig::BuildOnly, + target.to_triple(), + link_type, + )?) + } Some((CMD_CHECK, matches)) => { let arena = bumpalo::Bump::new(); let emit_timings = matches.is_present(FLAG_TIME); - let filename = matches.value_of(ROC_FILE).unwrap(); + let filename = matches.value_of_os(ROC_FILE).unwrap(); let roc_file_path = PathBuf::from(filename); let src_dir = roc_file_path.parent().unwrap().to_owned(); - match check_file(&arena, src_dir, roc_file_path, emit_timings) { + let threading = match matches + .value_of(roc_cli::FLAG_MAX_THREADS) + .and_then(|s| s.parse::().ok()) + { + None => Threading::AllAvailable, + Some(0) => user_error!("cannot build with at most 0 threads"), + Some(1) => Threading::Single, + Some(n) => Threading::AtMost(n), + }; + + match check_file(&arena, src_dir, roc_file_path, emit_timings, threading) { Ok((problems, total_time)) => { println!( "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.", @@ -108,16 +136,12 @@ fn main() -> io::Result<()> { } } Some((CMD_REPL, _)) => { - #[cfg(feature = "llvm")] { roc_repl_cli::main()?; // Exit 0 if the repl exited normally Ok(0) } - - #[cfg(not(feature = "llvm"))] - todo!("enable roc repl without llvm"); } Some((CMD_EDIT, matches)) => { match matches @@ -166,7 +190,7 @@ fn main() -> io::Result<()> { roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?; } - docs(roc_files); + generate_docs_html(roc_files); Ok(0) } diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 649f299edb..dba199f2fd 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -14,10 +14,29 @@ mod cli_run { known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError, ValgrindErrorXWhat, }; + use const_format::concatcp; use indoc::indoc; + use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN}; use roc_test_utils::assert_multiline_str_eq; use serial_test::serial; + use std::iter; use std::path::{Path, PathBuf}; + use strum::IntoEnumIterator; + use strum_macros::EnumIter; + + const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE); + const VALGRIND_FLAG: &str = concatcp!("--", roc_cli::FLAG_VALGRIND); + const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER); + const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK); + #[allow(dead_code)] + const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET); + + #[derive(Debug, EnumIter)] + enum CliMode { + RocBuild, + RocRun, + Roc, + } #[cfg(not(debug_assertions))] use roc_collections::all::MutMap; @@ -65,7 +84,7 @@ mod cli_run { } fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { - let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat()); + let compile_out = run_roc([CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), &[]); let err = compile_out.stdout.trim(); let err = strip_colors(err); @@ -77,19 +96,41 @@ mod cli_run { } fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { - let flags = &["--check"]; - let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat()); - if expects_success_exit_code { - assert!(out.status.success()); - } else { - assert!(!out.status.success()); - } + let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[]); + + assert_eq!(out.status.success(), expects_success_exit_code); } - fn build_example(file: &Path, flags: &[&str]) -> Out { - let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); - if !compile_out.stderr.is_empty() { - panic!("roc build had stderr: {}", compile_out.stderr); + fn run_roc_on<'a, I: IntoIterator>( + file: &'a Path, + args: I, + stdin: &[&str], + input_file: Option, + ) -> Out { + let compile_out = match input_file { + Some(input_file) => run_roc( + // converting these all to String avoids lifetime issues + args.into_iter().map(|arg| arg.to_string()).chain([ + file.to_str().unwrap().to_string(), + input_file.to_str().unwrap().to_string(), + ]), + stdin, + ), + None => run_roc( + args.into_iter().chain(iter::once(file.to_str().unwrap())), + stdin, + ), + }; + + if !compile_out.stderr.is_empty() && + // If there is any stderr, it should be reporting the runtime and that's it! + !(compile_out.stderr.starts_with("runtime: ") + && compile_out.stderr.ends_with("ms\n")) + { + panic!( + "`roc` command had unexpected stderr: {}", + compile_out.stderr + ); } assert!(compile_out.status.success(), "bad status {:?}", compile_out); @@ -106,86 +147,104 @@ mod cli_run { expected_ending: &str, use_valgrind: bool, ) { - let mut all_flags = vec![]; - all_flags.extend_from_slice(flags); + for cli_mode in CliMode::iter() { + let flags = { + let mut vec = flags.to_vec(); - if use_valgrind { - all_flags.extend_from_slice(&["--valgrind"]); - } + if use_valgrind { + vec.push(VALGRIND_FLAG); + } - build_example(file, &all_flags[..]); - - let out = if use_valgrind && ALLOW_VALGRIND { - let (valgrind_out, raw_xml) = if let Some(input_file) = input_file { - run_with_valgrind( - stdin, - &[ - file.with_file_name(executable_filename).to_str().unwrap(), - input_file.to_str().unwrap(), - ], - ) - } else { - run_with_valgrind( - stdin, - &[file.with_file_name(executable_filename).to_str().unwrap()], - ) + vec.into_iter() }; - if valgrind_out.status.success() { - let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| { - panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr); - }); + let out = match cli_mode { + CliMode::RocBuild => { + run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], None); - if !memory_errors.is_empty() { - for error in memory_errors { - let ValgrindError { - kind, - what: _, - xwhat, - } = error; - println!("Valgrind Error: {}\n", kind); + if use_valgrind && ALLOW_VALGRIND { + let (valgrind_out, raw_xml) = if let Some(ref input_file) = input_file { + run_with_valgrind( + stdin.clone().iter().copied(), + &[ + file.with_file_name(executable_filename).to_str().unwrap(), + input_file.clone().to_str().unwrap(), + ], + ) + } else { + run_with_valgrind( + stdin.clone().iter().copied(), + &[file.with_file_name(executable_filename).to_str().unwrap()], + ) + }; - if let Some(ValgrindErrorXWhat { - text, - leakedbytes: _, - leakedblocks: _, - }) = xwhat - { - println!(" {}", text); + if valgrind_out.status.success() { + let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| { + panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr); + }); + + if !memory_errors.is_empty() { + for error in memory_errors { + let ValgrindError { + kind, + what: _, + xwhat, + } = error; + println!("Valgrind Error: {}\n", kind); + + if let Some(ValgrindErrorXWhat { + text, + leakedbytes: _, + leakedblocks: _, + }) = xwhat + { + println!(" {}", text); + } + } + panic!("Valgrind reported memory errors"); + } + } else { + let exit_code = match valgrind_out.status.code() { + Some(code) => format!("exit code {}", code), + None => "no exit code".to_string(), + }; + + panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr); } - } - panic!("Valgrind reported memory errors"); - } - } else { - let exit_code = match valgrind_out.status.code() { - Some(code) => format!("exit code {}", code), - None => "no exit code".to_string(), - }; - panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr); + valgrind_out + } else if let Some(ref input_file) = input_file { + run_cmd( + file.with_file_name(executable_filename).to_str().unwrap(), + stdin.iter().copied(), + &[input_file.to_str().unwrap()], + ) + } else { + run_cmd( + file.with_file_name(executable_filename).to_str().unwrap(), + stdin.iter().copied(), + &[], + ) + } + } + CliMode::Roc => run_roc_on(file, flags.clone(), stdin, input_file.clone()), + CliMode::RocRun => run_roc_on( + file, + iter::once(CMD_RUN).chain(flags.clone()), + stdin, + input_file.clone(), + ), + }; + + if !&out.stdout.ends_with(expected_ending) { + panic!( + "expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}", + expected_ending, out.stdout, out.stderr + ); } - valgrind_out - } else if let Some(input_file) = input_file { - run_cmd( - file.with_file_name(executable_filename).to_str().unwrap(), - stdin, - &[input_file.to_str().unwrap()], - ) - } else { - run_cmd( - file.with_file_name(executable_filename).to_str().unwrap(), - stdin, - &[], - ) - }; - if !&out.stdout.ends_with(expected_ending) { - panic!( - "expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}", - expected_ending, out.stdout, out.stderr - ); + assert!(out.status.success()); } - assert!(out.status.success()); } #[cfg(feature = "wasm32-cli-run")] @@ -199,9 +258,13 @@ mod cli_run { ) { assert_eq!(input_file, None, "Wasm does not support input files"); let mut flags = flags.to_vec(); - flags.push("--target=wasm32"); + flags.push(concatcp!(TARGET_FLAG, "=wasm32")); - let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat()); + let compile_out = run_roc( + [CMD_BUILD, file.to_str().unwrap()] + .iter() + .chain(flags.as_slice()), + ); if !compile_out.stderr.is_empty() { panic!("{}", compile_out.stderr); } @@ -256,9 +319,9 @@ mod cli_run { return; } } - "hello-gui" => { - // Since this one requires opening a window, we do `roc build` on it but don't run it. - build_example(&file_name, &["--optimize"]); + "hello-gui" | "breakout" => { + // Since these require opening a window, we do `roc build` on them but don't run them. + run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], None); return; } @@ -283,7 +346,7 @@ mod cli_run { &file_name, example.stdin, example.executable_filename, - &["--optimize"], + &[OPTIMIZE_FLAG], example.input_file.and_then(|file| Some(example_file(dir_name, file))), example.expected_ending, example.use_valgrind, @@ -296,7 +359,7 @@ mod cli_run { &file_name, example.stdin, example.executable_filename, - &["--linker", "legacy"], + &[LINKER_FLAG, "legacy"], example.input_file.and_then(|file| Some(example_file(dir_name, file))), example.expected_ending, example.use_valgrind, @@ -394,6 +457,14 @@ mod cli_run { expected_ending: "", use_valgrind: false, }, + breakout:"breakout" => Example { + filename: "breakout.roc", + executable_filename: "breakout", + stdin: &[], + input_file: None, + expected_ending: "", + use_valgrind: false, + }, quicksort:"algorithms" => Example { filename: "quicksort.roc", executable_filename: "quicksort", @@ -505,7 +576,7 @@ mod cli_run { &file_name, benchmark.stdin, benchmark.executable_filename, - &["--optimize"], + &[OPTIMIZE_FLAG], benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.expected_ending, benchmark.use_valgrind, @@ -547,7 +618,7 @@ mod cli_run { &file_name, benchmark.stdin, benchmark.executable_filename, - &["--optimize"], + &[OPTIMIZE_FLAG], benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.expected_ending, ); @@ -579,7 +650,7 @@ mod cli_run { &file_name, benchmark.stdin, benchmark.executable_filename, - &["--target=x86_32"], + [concatcp!(TARGET_FLAG, "=x86_32")], benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.expected_ending, benchmark.use_valgrind, @@ -589,7 +660,7 @@ mod cli_run { &file_name, benchmark.stdin, benchmark.executable_filename, - &["--target=x86_32", "--optimize"], + [concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG], benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.expected_ending, benchmark.use_valgrind, @@ -685,7 +756,7 @@ mod cli_run { stdin: &[], input_file: None, expected_ending: "", - use_valgrind: true, + use_valgrind: false, }, issue2279 => Example { filename: "Issue2279.roc", @@ -808,7 +879,7 @@ mod cli_run { &fixture_file("multi-dep-str", "Main.roc"), &[], "multi-dep-str", - &["--optimize"], + &[OPTIMIZE_FLAG], None, "I am Dep2.str2\n", true, @@ -836,7 +907,7 @@ mod cli_run { &fixture_file("multi-dep-thunk", "Main.roc"), &[], "multi-dep-thunk", - &["--optimize"], + &[OPTIMIZE_FLAG], None, "I am Dep2.value2\n", true, diff --git a/cli/tests/fixtures/multi-dep-str/platform/host.zig b/cli/tests/fixtures/multi-dep-str/platform/host.zig index 837cb9b5a0..d1e0e912fa 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-str/platform/host.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const str = @import("str"); const RocStr = str.RocStr; const testing = std.testing; @@ -14,7 +15,7 @@ comptime { // -fcompiler-rt in link.rs instead of doing this. Note that this // workaround is present in many host.zig files, so make sure to undo // it everywhere! - if (std.builtin.os.tag == .macos) { + if (builtin.os.tag == .macos) { _ = @import("compiler_rt"); } } @@ -22,23 +23,28 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed() RocStr; +extern fn roc__mainForHost_1_exposed_generic(*RocStr) void; -extern fn malloc(size: usize) callconv(.C) ?*c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; +const Align = 2 * @alignOf(usize); +extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; +extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { + _ = alignment; return malloc(size); } -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { + _ = old_size; + _ = alignment; return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); } -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { +export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { + _ = alignment; free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } @@ -50,7 +56,9 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { return memset(dst, value, size); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { + _ = tag_id; + const stderr = std.io.getStdErr().writer(); const msg = @ptrCast([*:0]const u8, c_ptr); stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; @@ -65,14 +73,15 @@ pub export fn main() i32 { // start time var ts1: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable; // actually call roc to populate the callresult - const callresult = roc__mainForHost_1_exposed(); + var callresult = RocStr.empty(); + roc__mainForHost_1_exposed_generic(&callresult); // end time var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable; // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; diff --git a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig index 837cb9b5a0..1bf51a90e9 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const str = @import("str"); const RocStr = str.RocStr; const testing = std.testing; @@ -14,7 +15,7 @@ comptime { // -fcompiler-rt in link.rs instead of doing this. Note that this // workaround is present in many host.zig files, so make sure to undo // it everywhere! - if (std.builtin.os.tag == .macos) { + if (builtin.os.tag == .macos) { _ = @import("compiler_rt"); } } @@ -22,23 +23,27 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed() RocStr; +extern fn roc__mainForHost_1_exposed_generic(*RocStr) void; -extern fn malloc(size: usize) callconv(.C) ?*c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; +extern fn malloc(size: usize) callconv(.C) ?*anyopaque; +extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*anyopaque; extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { + _ = alignment; return malloc(size); } -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { + _ = old_size; + _ = alignment; return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); } -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { +export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { + _ = alignment; free(@alignCast(16, @ptrCast([*]u8, c_ptr))); } @@ -50,7 +55,9 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { return memset(dst, value, size); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { + _ = tag_id; + const stderr = std.io.getStdErr().writer(); const msg = @ptrCast([*:0]const u8, c_ptr); stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; @@ -65,14 +72,15 @@ pub export fn main() i32 { // start time var ts1: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable; // actually call roc to populate the callresult - const callresult = roc__mainForHost_1_exposed(); + var callresult = RocStr.empty(); + roc__mainForHost_1_exposed_generic(&callresult); // end time var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable; // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index c1fa0f03f4..07b8777f33 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" @@ -188,10 +188,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.0", ] [[package]] @@ -346,17 +358,26 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.0-beta.5" +version = "3.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" +checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0" dependencies = [ "atty", "bitflags", + "clap_lex", "indexmap", - "os_str_bytes", "strsim 0.10.0", "termcolor", - "textwrap 0.14.2", + "textwrap 0.15.0", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -958,7 +979,7 @@ checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d" dependencies = [ "cfg-if 1.0.0", "libc", - "windows-sys", + "windows-sys 0.28.0", ] [[package]] @@ -1015,6 +1036,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.17" @@ -1297,7 +1324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46e977036f7f5139d580c7f19ad62df9cb8ebd8410bb569e73585226be80a86f" dependencies = [ "lazy_static", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -1348,26 +1375,26 @@ dependencies = [ name = "inkwell" version = "0.1.0" dependencies = [ - "inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1)", + "inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?branch=master)", ] [[package]] name = "inkwell" version = "0.1.0" -source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5" +source = "git+https://github.com/rtfeldman/inkwell?branch=master#accd406858a40ca2a1463ff77d79f3c5e4c96f4e" dependencies = [ "either", "inkwell_internals", "libc", "llvm-sys", "once_cell", - "parking_lot", + "parking_lot 0.12.0", ] [[package]] name = "inkwell_internals" version = "0.5.0" -source = "git+https://github.com/rtfeldman/inkwell?tag=llvm13-0.release1#e15d665227b2acad4ca949820d80048e09f3f4e5" +source = "git+https://github.com/rtfeldman/inkwell?branch=master#accd406858a40ca2a1463ff77d79f3c5e4c96f4e" dependencies = [ "proc-macro2", "quote", @@ -1496,9 +1523,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "llvm-sys" -version = "120.2.1" +version = "130.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a810627ac62b396f5fd2214ba9bbd8748d4d6efdc4d2c1c1303ea7a75763ce" +checksum = "95eb03b4f7ae21f48ef7c565a3e3aa22c50616aea64645fb1fd7f6f56b51c274" dependencies = [ "cc", "lazy_static", @@ -1509,10 +1536,11 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] @@ -1566,9 +1594,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", ] @@ -1949,12 +1977,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "4.2.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" -dependencies = [ - "memchr", -] +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" [[package]] name = "owned_ttf_parser" @@ -1980,7 +2005,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1" dependencies = [ - "bitvec", + "bitvec 0.22.3", "packed_struct_codegen", "serde", ] @@ -2038,7 +2063,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.2", ] [[package]] @@ -2055,6 +2090,46 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.34.0", +] + +[[package]] +name = "peg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af728fe826811af3b38c37e93de6d104485953ea373d656eebae53d6987fcd2c" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4536be147b770b824895cbad934fccce8e49f14b4c4946eaa46a6e4a12fcdc16" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2280,6 +2355,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "radix_trie" version = "0.2.1" @@ -2451,6 +2532,17 @@ dependencies = [ "libc", ] +[[package]] +name = "roc_alias_analysis" +version = "0.1.0" +dependencies = [ + "morphic_lib", + "roc_collections", + "roc_debug_flags", + "roc_module", + "roc_mono", +] + [[package]] name = "roc_ast" version = "0.1.0" @@ -2468,11 +2560,13 @@ dependencies = [ "roc_parse", "roc_problem", "roc_region", + "roc_reporting", "roc_target", "roc_types", "roc_unify", "snafu", "ven_graph", + "winapi", ] [[package]] @@ -2486,6 +2580,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_constrain", + "roc_error_macros", "roc_gen_dev", "roc_gen_llvm", "roc_gen_wasm", @@ -2504,6 +2599,7 @@ dependencies = [ "serde_json", "target-lexicon", "tempfile", + "wasi_libc_sys", ] [[package]] @@ -2511,6 +2607,7 @@ name = "roc_builtins" version = "0.1.0" dependencies = [ "dunce", + "lazy_static", "roc_collections", "roc_module", "roc_region", @@ -2522,16 +2619,17 @@ dependencies = [ name = "roc_can" version = "0.1.0" dependencies = [ + "bitvec 1.0.0", "bumpalo", - "roc_builtins", "roc_collections", "roc_error_macros", + "roc_exhaustive", "roc_module", "roc_parse", "roc_problem", "roc_region", "roc_types", - "ven_graph", + "static_assertions 1.1.0", ] [[package]] @@ -2539,7 +2637,7 @@ name = "roc_cli" version = "0.1.0" dependencies = [ "bumpalo", - "clap 3.0.0-beta.5", + "clap 3.1.17", "const_format", "mimalloc", "roc_build", @@ -2592,6 +2690,7 @@ dependencies = [ name = "roc_constrain" version = "0.1.0" dependencies = [ + "arrayvec 0.7.2", "roc_builtins", "roc_can", "roc_collections", @@ -2602,21 +2701,28 @@ dependencies = [ "roc_types", ] +[[package]] +name = "roc_debug_flags" +version = "0.1.0" + [[package]] name = "roc_docs" version = "0.1.0" dependencies = [ "bumpalo", + "peg", "pulldown-cmark", "roc_ast", "roc_builtins", "roc_can", "roc_code_markup", "roc_collections", + "roc_highlight", "roc_load", "roc_module", "roc_parse", "roc_region", + "roc_reporting", "roc_target", "roc_types", "snafu", @@ -2671,6 +2777,16 @@ dependencies = [ name = "roc_error_macros" version = "0.1.0" +[[package]] +name = "roc_exhaustive" +version = "0.1.0" +dependencies = [ + "roc_collections", + "roc_module", + "roc_region", + "roc_std", +] + [[package]] name = "roc_fmt" version = "0.1.0" @@ -2696,7 +2812,6 @@ dependencies = [ "roc_mono", "roc_problem", "roc_region", - "roc_reporting", "roc_solve", "roc_target", "roc_types", @@ -2711,8 +2826,10 @@ dependencies = [ "bumpalo", "inkwell 0.1.0", "morphic_lib", + "roc_alias_analysis", "roc_builtins", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_mono", @@ -2735,6 +2852,14 @@ dependencies = [ "roc_target", ] +[[package]] +name = "roc_highlight" +version = "0.1.0" +dependencies = [ + "peg", + "roc_code_markup", +] + [[package]] name = "roc_ident" version = "0.1.0" @@ -2745,9 +2870,9 @@ version = "0.1.0" dependencies = [ "bincode", "bumpalo", - "clap 3.0.0-beta.5", + "clap 3.1.17", "iced-x86", - "memmap2 0.5.0", + "memmap2 0.5.3", "object 0.26.2", "roc_build", "roc_collections", @@ -2760,16 +2885,31 @@ dependencies = [ [[package]] name = "roc_load" version = "0.1.0" +dependencies = [ + "bumpalo", + "roc_builtins", + "roc_collections", + "roc_constrain", + "roc_load_internal", + "roc_module", + "roc_reporting", + "roc_target", + "roc_types", +] + +[[package]] +name = "roc_load_internal" +version = "0.1.0" dependencies = [ "bumpalo", "crossbeam", - "morphic_lib", "num_cpus", - "parking_lot", + "parking_lot 0.12.0", "roc_builtins", "roc_can", "roc_collections", "roc_constrain", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_mono", @@ -2795,7 +2935,7 @@ dependencies = [ "roc_ident", "roc_region", "snafu", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -2804,11 +2944,12 @@ version = "0.1.0" dependencies = [ "bumpalo", "hashbrown 0.11.2", - "morphic_lib", "roc_builtins", "roc_can", "roc_collections", + "roc_debug_flags", "roc_error_macros", + "roc_exhaustive", "roc_module", "roc_problem", "roc_region", @@ -2817,7 +2958,7 @@ dependencies = [ "roc_target", "roc_types", "roc_unify", - "static_assertions", + "static_assertions 1.1.0", "ven_graph", "ven_pretty", ] @@ -2841,13 +2982,14 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_types", ] [[package]] name = "roc_region" version = "0.1.0" dependencies = [ - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -2866,6 +3008,8 @@ dependencies = [ "roc_mono", "roc_parse", "roc_repl_eval", + "roc_reporting", + "roc_std", "roc_target", "roc_types", "rustyline", @@ -2888,6 +3032,7 @@ dependencies = [ "roc_parse", "roc_region", "roc_reporting", + "roc_std", "roc_target", "roc_types", ] @@ -2900,8 +3045,8 @@ dependencies = [ "distance", "roc_can", "roc_collections", + "roc_exhaustive", "roc_module", - "roc_mono", "roc_parse", "roc_problem", "roc_region", @@ -2918,6 +3063,9 @@ dependencies = [ "bumpalo", "roc_can", "roc_collections", + "roc_debug_flags", + "roc_error_macros", + "roc_exhaustive", "roc_module", "roc_region", "roc_types", @@ -2927,6 +3075,9 @@ dependencies = [ [[package]] name = "roc_std" version = "0.1.0" +dependencies = [ + "static_assertions 0.1.1", +] [[package]] name = "roc_target" @@ -2941,10 +3092,11 @@ version = "0.1.0" dependencies = [ "bumpalo", "roc_collections", + "roc_debug_flags", "roc_error_macros", "roc_module", "roc_region", - "static_assertions", + "static_assertions 1.1.0", "ven_ena", ] @@ -2954,6 +3106,8 @@ version = "0.1.0" dependencies = [ "bitflags", "roc_collections", + "roc_debug_flags", + "roc_error_macros", "roc_module", "roc_types", ] @@ -2999,7 +3153,7 @@ dependencies = [ [[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", @@ -3022,7 +3176,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", @@ -3285,6 +3439,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" + [[package]] name = "static_assertions" version = "1.1.0" @@ -3337,9 +3497,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" @@ -3375,9 +3535,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" @@ -3447,7 +3607,7 @@ checksum = "1f559b464de2e2bdabcac6a210d12e9b5a5973c251e102c44c585c71d51bd78e" dependencies = [ "cfg-if 1.0.0", "rand", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -3567,6 +3727,10 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi_libc_sys" +version = "0.1.0" + [[package]] name = "wasm-bindgen" version = "0.2.79" @@ -3798,7 +3962,7 @@ dependencies = [ "arrayvec 0.7.2", "js-sys", "log", - "parking_lot", + "parking_lot 0.11.2", "raw-window-handle", "smallvec", "wasm-bindgen", @@ -3822,7 +3986,7 @@ dependencies = [ "fxhash", "log", "naga", - "parking_lot", + "parking_lot 0.11.2", "profiling", "raw-window-handle", "smallvec", @@ -3857,7 +4021,7 @@ dependencies = [ "metal", "naga", "objc", - "parking_lot", + "parking_lot 0.11.2", "profiling", "range-alloc", "raw-window-handle", @@ -3927,11 +4091,24 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.28.0", + "windows_i686_gnu 0.28.0", + "windows_i686_msvc 0.28.0", + "windows_x86_64_gnu 0.28.0", + "windows_x86_64_msvc 0.28.0", +] + +[[package]] +name = "windows-sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", ] [[package]] @@ -3940,30 +4117,60 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + [[package]] name = "windows_i686_gnu" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + [[package]] name = "windows_i686_msvc" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + [[package]] name = "windows_x86_64_gnu" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + [[package]] name = "windows_x86_64_msvc" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "winit" version = "0.25.0" @@ -3986,7 +4193,7 @@ dependencies = [ "ndk-glue", "ndk-sys", "objc", - "parking_lot", + "parking_lot 0.11.2", "percent-encoding", "raw-window-handle", "scopeguard", @@ -4014,6 +4221,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "x11-clipboard" version = "0.5.3" diff --git a/cli_utils/Cargo.toml b/cli_utils/Cargo.toml index 6f90db6737..a309bde670 100644 --- a/cli_utils/Cargo.toml +++ b/cli_utils/Cargo.toml @@ -20,6 +20,7 @@ serde = { version = "1.0.130", features = ["derive"] } serde-xml-rs = "0.5.1" strip-ansi-escapes = "0.1.1" tempfile = "3.2.0" +const_format = "0.2.22" [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 d14de367bd..8ba4727efd 100644 --- a/cli_utils/src/bench_utils.rs +++ b/cli_utils/src/bench_utils.rs @@ -1,9 +1,13 @@ use crate::helpers::{example_file, run_cmd, run_roc}; +use const_format::concatcp; use criterion::{black_box, measurement::Measurement, BenchmarkGroup}; +use roc_cli::CMD_BUILD; use std::{path::Path, thread}; const CFOLD_STACK_SIZE: usize = 8192 * 100000; +const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE); + fn exec_bench_w_input( file: &Path, stdin_str: &'static str, @@ -11,9 +15,10 @@ fn exec_bench_w_input( expected_ending: &str, bench_group_opt: Option<&mut BenchmarkGroup>, ) { - let flags: &[&str] = &["--optimize"]; - - let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); + let compile_out = run_roc( + [CMD_BUILD, OPTIMIZE_FLAG, file.to_str().unwrap()], + &[stdin_str], + ); if !compile_out.stderr.is_empty() { panic!("{}", compile_out.stderr); @@ -45,12 +50,12 @@ fn check_cmd_output( 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], &[])) + .spawn(move || run_cmd(&cmd_str, [stdin_str], &[])) .unwrap(); child.join().unwrap() } else { - run_cmd(&cmd_str, &[stdin_str], &[]) + run_cmd(&cmd_str, [stdin_str], &[]) }; if !&out.stdout.ends_with(expected_ending) { @@ -93,12 +98,12 @@ fn bench_cmd( if let Some(bench_group) = bench_group_opt { bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| { - b.iter(|| run_cmd(black_box(&cmd_str), black_box(&[stdin_str]), &[])) + b.iter(|| run_cmd(black_box(&cmd_str), black_box([stdin_str]), &[])) }); } else { run_cmd( black_box(file.with_file_name(executable_filename).to_str().unwrap()), - black_box(&[stdin_str]), + black_box([stdin_str]), &[], ); } diff --git a/cli_utils/src/helpers.rs b/cli_utils/src/helpers.rs index e83556176e..96d330a8b6 100644 --- a/cli_utils/src/helpers.rs +++ b/cli_utils/src/helpers.rs @@ -7,6 +7,7 @@ extern crate tempfile; use serde::Deserialize; use serde_xml_rs::from_str; use std::env; +use std::ffi::OsStr; use std::io::Read; use std::io::Write; use std::path::PathBuf; @@ -44,18 +45,34 @@ pub fn path_to_roc_binary() -> PathBuf { path } -#[allow(dead_code)] -pub fn run_roc(args: &[&str]) -> Out { +pub fn run_roc, S: AsRef>(args: I, stdin_vals: &[&str]) -> Out { let mut cmd = Command::new(path_to_roc_binary()); for arg in args { cmd.arg(arg); } - let output = cmd - .output() + let mut child = cmd + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() .expect("failed to execute compiled `roc` binary in CLI test"); + { + let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + + for stdin_str in stdin_vals.iter() { + stdin + .write_all(stdin_str.as_bytes()) + .expect("Failed to write to stdin"); + } + } + + let output = child + .wait_with_output() + .expect("failed to get output for compiled `roc` binary in CLI test"); + Out { stdout: String::from_utf8(output.stdout).unwrap(), stderr: String::from_utf8(output.stderr).unwrap(), @@ -63,8 +80,11 @@ pub fn run_roc(args: &[&str]) -> Out { } } -#[allow(dead_code)] -pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out { +pub fn run_cmd<'a, I: IntoIterator>( + cmd_name: &str, + stdin_vals: I, + args: &[&str], +) -> Out { let mut cmd = Command::new(cmd_name); for arg in args { @@ -99,8 +119,10 @@ pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out { } } -#[allow(dead_code)] -pub fn run_with_valgrind(stdin_vals: &[&str], args: &[&str]) -> (Out, String) { +pub fn run_with_valgrind<'a, I: IntoIterator>( + stdin_vals: I, + args: &[&str], +) -> (Out, String) { //TODO: figure out if there is a better way to get the valgrind executable. let mut cmd = Command::new("valgrind"); let named_tempfile = diff --git a/code_markup/src/colors.rs b/code_markup/src/colors.rs index b2c4c8a298..bf64fd7b95 100644 --- a/code_markup/src/colors.rs +++ b/code_markup/src/colors.rs @@ -1,4 +1,4 @@ -use palette::{FromColor, Hsv, Srgb}; +use palette::{FromColor, Hsv, LinSrgb, Srgb}; pub type RgbaTup = (f32, f32, f32, f32); pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0); @@ -12,11 +12,11 @@ pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup { } pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup { - let rgb = Srgb::from_color(Hsv::new( + let rgb = LinSrgb::from(Srgb::from_color(Hsv::new( hue as f32, (saturation as f32) / 100.0, (brightness as f32) / 100.0, - )); + ))); (rgb.red, rgb.green, rgb.blue, alpha) } diff --git a/code_markup/src/markup/convert/from_expr2.rs b/code_markup/src/markup/convert/from_expr2.rs index 5987541868..c859e5b8af 100644 --- a/code_markup/src/markup/convert/from_expr2.rs +++ b/code_markup/src/markup/convert/from_expr2.rs @@ -88,7 +88,7 @@ pub fn expr2_to_markup<'a>( mark_id_ast_id_map, ) } - Expr2::GlobalTag { name, .. } => new_markup_node( + Expr2::Tag { name, .. } => new_markup_node( with_indent(indent_level, &get_string(env, name)), ast_node_id, HighlightStyle::Type, diff --git a/compiler/README.md b/compiler/README.md index e57984562d..d4c93aab03 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -165,9 +165,19 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`. -## Debugging intermediate representations +## Debugging the compiler -### The mono IR +Please see the [debug flags](./debug_flags/src/lib.rs) for information on how to +ask the compiler to emit debug information during various stages of compilation. + +There are some goals for more sophisticated debugging tools: + +- A nicer unification debugger, see https://github.com/rtfeldman/roc/issues/2486. + Any interest in helping out here is greatly appreciated. + +### General Tips + +#### Miscompilations If you observe a miscomplication, you may first want to check the generated mono IR for your code - maybe there was a problem during specialization or layout @@ -175,13 +185,16 @@ generation. One way to do this is to add a test to `test_mono/src/tests.rs` and run the tests with `cargo test -p test_mono`; this will write the mono IR to a file. -You may also want to set some or all of the following environment variables: +#### Typechecking errors -- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function - specialization to stdout -- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of - reset/reuse isntructions to stdout -- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference - counting instructions to stdout -- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the - information it knows about the mono IR whenever it is printed +First, try to minimize your reproduction into a test that fits in +[`solve_expr`](./solve/tests/solve_expr.rs). + +Once you've done this, check out the `ROC_PRINT_UNIFICATIONS` debug flag. It +will show you where type unification went right and wrong. This is usually +enough to figure out a fix for the bug. + +If that doesn't work and you know your error has something to do with ranks, +you may want to instrument `deep_copy_var_help` in [solve](./solve/src/solve.rs). + +If that doesn't work, chatting on Zulip is always a good strategy. diff --git a/compiler/alias_analysis/Cargo.toml b/compiler/alias_analysis/Cargo.toml index 34cfb7666a..8777595cbf 100644 --- a/compiler/alias_analysis/Cargo.toml +++ b/compiler/alias_analysis/Cargo.toml @@ -10,4 +10,4 @@ morphic_lib = {path = "../../vendor/morphic_lib"} roc_collections = {path = "../collections"} roc_module = {path = "../module"} roc_mono = {path = "../mono"} - +roc_debug_flags = {path = "../debug_flags"} diff --git a/compiler/alias_analysis/src/lib.rs b/compiler/alias_analysis/src/lib.rs index d492f3a12a..2d1ad68ff6 100644 --- a/compiler/alias_analysis/src/lib.rs +++ b/compiler/alias_analysis/src/lib.rs @@ -26,8 +26,16 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] { func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), &proc.ret_layout) } -const DEBUG: bool = false; -const SIZE: usize = if DEBUG { 50 } else { 16 }; +#[inline(always)] +fn debug() -> bool { + use roc_debug_flags::{dbg_do, ROC_DEBUG_ALIAS_ANALYSIS}; + dbg_do!(ROC_DEBUG_ALIAS_ANALYSIS, { + return true; + }); + false +} + +const SIZE: usize = 16; #[derive(Debug, Clone, Copy, Hash)] struct TagUnionId(u64); @@ -87,7 +95,7 @@ where *target = *source; } - if DEBUG { + if debug() { for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() { name_bytes[25 + i] = c as u8; } @@ -175,7 +183,7 @@ where } } - if DEBUG { + if debug() { eprintln!( "{:?}: {:?} with {:?} args", proc.name, @@ -239,7 +247,7 @@ where p.build()? }; - if DEBUG { + if debug() { eprintln!("{}", program.to_source_string()); } @@ -279,7 +287,8 @@ fn build_entry_point( let block = builder.add_block(); // to the modelling language, the arguments appear out of thin air - let argument_type = build_tuple_type(&mut builder, layout.arguments)?; + let argument_type = + build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?; // does not make any assumptions about the input // let argument = builder.add_unknown_with(block, &[], argument_type)?; @@ -308,7 +317,11 @@ fn build_entry_point( let block = builder.add_block(); - let type_id = layout_spec(&mut builder, &Layout::struct_no_name_order(layouts))?; + let type_id = layout_spec( + &mut builder, + &Layout::struct_no_name_order(layouts), + &WhenRecursive::Unreachable, + )?; let argument = builder.add_unknown_with(block, &[], type_id)?; @@ -352,8 +365,9 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet>)> let arg_type_id = layout_spec( &mut builder, &Layout::struct_no_name_order(&argument_layouts), + &WhenRecursive::Unreachable, )?; - let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?; + let ret_type_id = layout_spec(&mut builder, &proc.ret_layout, &WhenRecursive::Unreachable)?; let spec = builder.build(arg_type_id, ret_type_id, root)?; @@ -457,10 +471,14 @@ fn stmt_spec<'a>( let mut type_ids = Vec::new(); for p in parameters.iter() { - type_ids.push(layout_spec(builder, &p.layout)?); + type_ids.push(layout_spec( + builder, + &p.layout, + &WhenRecursive::Unreachable, + )?); } - let ret_type_id = layout_spec(builder, layout)?; + let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let jp_arg_type_id = builder.add_tuple_type(&type_ids)?; @@ -500,14 +518,14 @@ fn stmt_spec<'a>( builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id)) } Jump(id, symbols) => { - let ret_type_id = layout_spec(builder, layout)?; + let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let argument = build_tuple_value(builder, env, block, symbols)?; let jpid = env.join_points[id]; builder.add_jump(block, jpid, argument, ret_type_id) } RuntimeError(_) => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_terminate(block, type_id) } @@ -556,11 +574,15 @@ fn build_recursive_tuple_type( builder.add_tuple_type(&field_types) } -fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result { +fn build_tuple_type( + builder: &mut impl TypeContext, + layouts: &[Layout], + when_recursive: &WhenRecursive, +) -> Result { let mut field_types = Vec::new(); for field in layouts.iter() { - field_types.push(layout_spec(builder, field)?); + field_types.push(layout_spec(builder, field, when_recursive)?); } builder.add_tuple_type(&field_types) @@ -691,7 +713,7 @@ fn call_spec( .map(|symbol| env.symbols[symbol]) .collect(); - let result_type = layout_spec(builder, ret_layout)?; + let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -761,7 +783,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -782,7 +805,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -806,7 +830,8 @@ fn call_spec( }; let state_layout = argument_layouts[0]; - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = state; add_loop(builder, block, state_type, init_state, loop_body) @@ -828,10 +853,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -851,10 +878,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -879,7 +908,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -903,10 +933,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -936,10 +968,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -975,10 +1009,12 @@ fn call_spec( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec(builder, return_layout)?; + let output_element_type = + layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; let state_layout = Layout::Builtin(Builtin::List(return_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; @@ -1010,7 +1046,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0])); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -1087,11 +1124,13 @@ fn call_spec( ) }; - let output_element_type = layout_spec(builder, &output_element_layout)?; + let output_element_type = + layout_spec(builder, &output_element_layout, &WhenRecursive::Unreachable)?; let init_state = new_list(builder, block, output_element_type)?; let state_layout = Layout::Builtin(Builtin::List(&output_element_layout)); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; add_loop(builder, block, state_type, init_state, loop_body) } @@ -1108,7 +1147,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::Bool); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_num(builder, block)?; @@ -1127,7 +1167,8 @@ fn call_spec( }; let state_layout = Layout::Builtin(Builtin::Bool); - let state_type = layout_spec(builder, &state_layout)?; + let state_type = + layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; let init_state = new_num(builder, block)?; @@ -1139,7 +1180,8 @@ fn call_spec( // ListFindUnsafe returns { value: v, found: Bool=Int1 } let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)]; let output_layout = Layout::struct_no_name_order(&output_layouts); - let output_type = layout_spec(builder, &output_layout)?; + let output_type = + layout_spec(builder, &output_layout, &WhenRecursive::Unreachable)?; let loop_body = |builder: &mut FuncDefBuilder, block, output| { let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; @@ -1201,7 +1243,7 @@ fn lowlevel_spec( ) -> Result { use LowLevel::*; - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let mode = update_mode.to_bytes(); let update_mode_var = UpdateModeVar(&mode); @@ -1230,7 +1272,7 @@ fn lowlevel_spec( builder.add_sub_block(block, sub_block) } - NumToFloat => { + NumToFrac => { // just dream up a unit value builder.add_make_tuple(block, &[]) } @@ -1323,8 +1365,8 @@ fn lowlevel_spec( } DictEmpty => match layout { Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { - let key_id = layout_spec(builder, key_layout)?; - let value_id = layout_spec(builder, value_layout)?; + let key_id = layout_spec(builder, key_layout, &WhenRecursive::Unreachable)?; + let value_id = layout_spec(builder, value_layout, &WhenRecursive::Unreachable)?; new_dict(builder, block, key_id, value_id) } _ => unreachable!("empty array does not have a list layout"), @@ -1367,7 +1409,7 @@ fn lowlevel_spec( // TODO overly pessimstic let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect(); - let result_type = layout_spec(builder, layout)?; + let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -1478,7 +1520,8 @@ fn expr_spec<'a>( let value_id = match tag_layout { UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types(builder, tags)?; + let variant_types = + non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?; let value_id = build_tuple_value(builder, env, block, arguments)?; return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); } @@ -1592,7 +1635,7 @@ fn expr_spec<'a>( builder.add_get_tuple_field(block, value_id, *index as u32) } Array { elem_layout, elems } => { - let type_id = layout_spec(builder, elem_layout)?; + let type_id = layout_spec(builder, elem_layout, &WhenRecursive::Unreachable)?; let list = new_list(builder, block, type_id)?; @@ -1619,19 +1662,19 @@ fn expr_spec<'a>( EmptyArray => match layout { Layout::Builtin(Builtin::List(element_layout)) => { - let type_id = layout_spec(builder, element_layout)?; + let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?; new_list(builder, block, type_id) } _ => unreachable!("empty array does not have a list layout"), }, Reset { symbol, .. } => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; let value_id = env.symbols[symbol]; builder.add_unknown_with(block, &[value_id], type_id) } RuntimeErrorFunction(_) => { - let type_id = layout_spec(builder, layout)?; + let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?; builder.add_terminate(block, type_id) } @@ -1658,18 +1701,24 @@ fn literal_spec( } } -fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result { - layout_spec_help(builder, layout, &WhenRecursive::Unreachable) +fn layout_spec( + builder: &mut impl TypeContext, + layout: &Layout, + when_recursive: &WhenRecursive, +) -> Result { + layout_spec_help(builder, layout, when_recursive) } fn non_recursive_variant_types( builder: &mut impl TypeContext, tags: &[&[Layout]], + // If there is a recursive pointer latent within this layout, coming from a containing layout. + when_recursive: &WhenRecursive, ) -> Result> { let mut result = Vec::with_capacity(tags.len()); for tag in tags.iter() { - result.push(build_tuple_type(builder, tag)?); + result.push(build_tuple_type(builder, tag, when_recursive)?); } Ok(result) @@ -1701,7 +1750,7 @@ fn layout_spec_help( builder.add_tuple_type(&[]) } UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types(builder, tags)?; + let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?; builder.add_union_type(&variant_types) } UnionLayout::Recursive(_) diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index 323a820d46..883361fe45 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -20,8 +20,8 @@ roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } roc_load = { path = "../load" } roc_target = { path = "../roc_target" } -roc_gen_llvm = { path = "../gen_llvm", optional = true } -roc_gen_wasm = { path = "../gen_wasm", optional = true } +roc_gen_llvm = { path = "../gen_llvm" } +roc_gen_wasm = { path = "../gen_wasm" } roc_gen_dev = { path = "../gen_dev", default-features = false } roc_reporting = { path = "../../reporting" } roc_error_macros = { path = "../../error_macros" } @@ -29,8 +29,9 @@ roc_std = { path = "../../roc_std", default-features = false } bumpalo = { version = "3.8.0", features = ["collections"] } libloading = "0.7.1" tempfile = "3.2.0" -inkwell = { path = "../../vendor/inkwell", optional = true } -target-lexicon = "0.12.2" +inkwell = { path = "../../vendor/inkwell" } +target-lexicon = "0.12.3" +wasi_libc_sys = { path = "../../wasi-libc-sys" } [target.'cfg(target_os = "macos")'.dependencies] serde_json = "1.0.69" @@ -40,8 +41,4 @@ target-arm = [] target-aarch64 = ["roc_gen_dev/target-aarch64"] target-x86 = [] target-x86_64 = ["roc_gen_dev/target-x86_64"] -target-wasm32 = ["roc_gen_wasm"] - -# This is a separate feature because when we generate docs on Netlify, -# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.) -llvm = ["inkwell", "roc_gen_llvm"] +target-wasm32 = [] diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index a7e32d0328..772502e11c 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -1,9 +1,7 @@ use crate::target::{arch_str, target_zig_str}; -#[cfg(feature = "llvm")] use libloading::{Error, Library}; use roc_builtins::bitcode; use roc_error_macros::internal_error; -// #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; use std::collections::HashMap; use std::env; @@ -72,16 +70,12 @@ fn find_zig_str_path() -> PathBuf { } fn find_wasi_libc_path() -> PathBuf { - let wasi_libc_path = PathBuf::from("compiler/builtins/bitcode/wasi-libc.a"); + use wasi_libc_sys::WASI_LIBC_PATH; - if std::path::Path::exists(&wasi_libc_path) { - return wasi_libc_path; - } - - // when running the tests, we start in the /cli directory - let wasi_libc_path = PathBuf::from("../compiler/builtins/bitcode/wasi-libc.a"); - if std::path::Path::exists(&wasi_libc_path) { - return wasi_libc_path; + // Environment variable defined in wasi-libc-sys/build.rs + let wasi_libc_pathbuf = PathBuf::from(WASI_LIBC_PATH); + if std::path::Path::exists(&wasi_libc_pathbuf) { + return wasi_libc_pathbuf; } panic!("cannot find `wasi-libc.a`") @@ -1065,14 +1059,13 @@ fn link_windows( todo!("Add windows support to the surgical linker. See issue #2608.") } -#[cfg(feature = "llvm")] pub fn module_to_dylib( module: &inkwell::module::Module, target: &Triple, opt_level: OptLevel, ) -> Result { use crate::target::{self, convert_opt_level}; - use inkwell::targets::{CodeModel, FileType, RelocMode}; + use inkwell::targets::{FileType, RelocMode}; let dir = tempfile::tempdir().unwrap(); let filename = PathBuf::from("Test.roc"); @@ -1083,9 +1076,8 @@ pub fn module_to_dylib( // Emit the .o file using position-independent code (PIC) - needed for dylibs let reloc = RelocMode::PIC; - let model = CodeModel::Default; let target_machine = - target::target_machine(target, convert_opt_level(opt_level), reloc, model).unwrap(); + target::target_machine(target, convert_opt_level(opt_level), reloc).unwrap(); target_machine .write_to_file(module, FileType::Object, &app_o_file) @@ -1105,6 +1097,21 @@ pub fn module_to_dylib( // Load the dylib let path = dylib_path.as_path().to_str().unwrap(); + if matches!(target.architecture, Architecture::Aarch64(_)) { + // On AArch64 darwin machines, calling `ldopen` on Roc-generated libs from multiple threads + // sometimes fails with + // cannot dlopen until fork() handlers have completed + // This may be due to codesigning. In any case, spinning until we are able to dlopen seems + // to be okay. + loop { + match unsafe { Library::new(path) } { + Ok(lib) => return Ok(lib), + Err(Error::DlOpen { .. }) => continue, + Err(other) => return Err(other), + } + } + } + unsafe { Library::new(path) } } diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 3d8ac5e456..010a518d79 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -1,6 +1,4 @@ -#[cfg(feature = "llvm")] use roc_gen_llvm::llvm::build::module_from_builtins; -#[cfg(feature = "llvm")] pub use roc_gen_llvm::llvm::build::FunctionIterator; use roc_load::{LoadedModule, MonomorphizedModule}; use roc_module::symbol::{Interns, ModuleId}; @@ -19,15 +17,6 @@ pub struct CodeGenTiming { pub emit_o_file: Duration, } -// TODO: If modules besides this one start needing to know which version of -// llvm we're using, consider moving me somewhere else. -#[cfg(feature = "llvm")] -const LLVM_VERSION: &str = "12"; - -// TODO instead of finding exhaustiveness problems in monomorphization, find -// them after type checking (like Elm does) so we can complete the entire -// `roc check` process without needing to monomorphize. -/// Returns the number of problems reported. pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> Problems { report_problems_help( loaded.total_problems(), @@ -35,7 +24,6 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> Proble &loaded.interns, &mut loaded.can_problems, &mut loaded.type_problems, - &mut loaded.mono_problems, ) } @@ -46,7 +34,6 @@ pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> Problems { &loaded.interns, &mut loaded.can_problems, &mut loaded.type_problems, - &mut Default::default(), ) } @@ -73,11 +60,9 @@ fn report_problems_help( interns: &Interns, can_problems: &mut MutMap>, type_problems: &mut MutMap>, - mono_problems: &mut MutMap>, ) -> Problems { use roc_reporting::report::{ - can_problem, mono_problem, type_problem, Report, RocDocAllocator, Severity::*, - DEFAULT_PALETTE, + can_problem, type_problem, Report, RocDocAllocator, Severity::*, DEFAULT_PALETTE, }; let palette = DEFAULT_PALETTE; @@ -134,25 +119,6 @@ fn report_problems_help( } } } - - let problems = mono_problems.remove(home).unwrap_or_default(); - - for problem in problems { - let report = mono_problem(&alloc, &lines, module_path.clone(), problem); - let severity = report.severity; - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - match severity { - Warning => { - warnings.push(buf); - } - RuntimeError => { - errors.push(buf); - } - } - } } let problems_reported; @@ -188,27 +154,6 @@ fn report_problems_help( } } -#[cfg(not(feature = "llvm"))] -pub fn gen_from_mono_module( - arena: &bumpalo::Bump, - loaded: MonomorphizedModule, - _roc_file_path: &Path, - target: &target_lexicon::Triple, - app_o_file: &Path, - opt_level: OptLevel, - _emit_debug_info: bool, -) -> CodeGenTiming { - match opt_level { - OptLevel::Optimize | OptLevel::Size => { - todo!("Return this error message in a better way: optimized builds not supported without llvm backend"); - } - OptLevel::Normal | OptLevel::Development => { - gen_from_mono_module_dev(arena, loaded, target, app_o_file) - } - } -} - -#[cfg(feature = "llvm")] pub fn gen_from_mono_module( arena: &bumpalo::Bump, loaded: MonomorphizedModule, @@ -235,7 +180,6 @@ pub fn gen_from_mono_module( // TODO how should imported modules factor into this? What if those use builtins too? // TODO this should probably use more helper functions // TODO make this polymorphic in the llvm functions so it can be reused for another backend. -#[cfg(feature = "llvm")] pub fn gen_from_mono_module_llvm( arena: &bumpalo::Bump, loaded: MonomorphizedModule, @@ -249,7 +193,7 @@ pub fn gen_from_mono_module_llvm( use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::context::Context; use inkwell::module::Linkage; - use inkwell::targets::{CodeModel, FileType, RelocMode}; + use inkwell::targets::{FileType, RelocMode}; let code_gen_start = SystemTime::now(); @@ -384,9 +328,11 @@ pub fn gen_from_mono_module_llvm( use target_lexicon::Architecture; match target.architecture { - Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => { - // assemble the .ll into a .bc - let _ = Command::new("llvm-as") + Architecture::X86_64 + | Architecture::X86_32(_) + | Architecture::Aarch64(_) + | Architecture::Wasm32 => { + let ll_to_bc = Command::new("llvm-as") .args(&[ app_ll_dbg_file.to_str().unwrap(), "-o", @@ -395,6 +341,8 @@ pub fn gen_from_mono_module_llvm( .output() .unwrap(); + assert!(ll_to_bc.stderr.is_empty(), "{:#?}", ll_to_bc); + let llc_args = &[ "-relocation-model=pic", "-filetype=obj", @@ -408,26 +356,9 @@ pub fn gen_from_mono_module_llvm( // // different systems name this executable differently, so we shotgun for // the most common ones and then give up. - let _: Result = - Command::new(format!("llc-{}", LLVM_VERSION)) - .args(llc_args) - .output() - .or_else(|_| Command::new("llc").args(llc_args).output()) - .map_err(|_| { - panic!("We couldn't find llc-{} on your machine!", LLVM_VERSION); - }); - } + let bc_to_object = Command::new("llc").args(llc_args).output().unwrap(); - Architecture::Wasm32 => { - // assemble the .ll into a .bc - let _ = Command::new("llvm-as") - .args(&[ - app_ll_dbg_file.to_str().unwrap(), - "-o", - app_o_file.to_str().unwrap(), - ]) - .output() - .unwrap(); + assert!(bc_to_object.stderr.is_empty(), "{:#?}", bc_to_object); } _ => unreachable!(), } @@ -437,10 +368,8 @@ pub fn gen_from_mono_module_llvm( match target.architecture { Architecture::X86_64 | Architecture::X86_32(_) | Architecture::Aarch64(_) => { let reloc = RelocMode::PIC; - let model = CodeModel::Default; let target_machine = - target::target_machine(target, convert_opt_level(opt_level), reloc, model) - .unwrap(); + target::target_machine(target, convert_opt_level(opt_level), reloc).unwrap(); target_machine .write_to_file(env.module, FileType::Object, app_o_file) diff --git a/compiler/build/src/target.rs b/compiler/build/src/target.rs index 6beee37300..e947c1d582 100644 --- a/compiler/build/src/target.rs +++ b/compiler/build/src/target.rs @@ -1,9 +1,7 @@ -#[cfg(feature = "llvm")] use inkwell::{ targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple}, OptimizationLevel, }; -#[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; use target_lexicon::{Architecture, Environment, OperatingSystem, Triple}; @@ -98,7 +96,6 @@ pub fn target_zig_str(target: &Triple) -> &'static str { } } -#[cfg(feature = "llvm")] pub fn init_arch(target: &Triple) { match target.architecture { Architecture::X86_64 | Architecture::X86_32(_) @@ -142,28 +139,35 @@ pub fn arch_str(target: &Triple) -> &'static str { } } -#[cfg(feature = "llvm")] pub fn target_machine( target: &Triple, opt: OptimizationLevel, reloc: RelocMode, - model: CodeModel, ) -> Option { let arch = arch_str(target); init_arch(target); + let code_model = match target.architecture { + // LLVM 12 will not compile our programs without a large code model. + // The reason is not totally clear to me, but my guess is a few special-cases in + // llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (instructions) + // llvm/lib/Target/AArch64/AArch64Subtarget.cpp (GoT tables) + // Revisit when upgrading to LLVM 13. + Architecture::Aarch64(..) => CodeModel::Large, + _ => CodeModel::Default, + }; + Target::from_name(arch).unwrap().create_target_machine( &TargetTriple::create(target_triple_str(target)), "generic", "", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features. opt, reloc, - model, + code_model, ) } -#[cfg(feature = "llvm")] pub fn convert_opt_level(level: OptLevel) -> OptimizationLevel { match level { OptLevel::Development | OptLevel::Normal => OptimizationLevel::None, diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index 2ab95cd260..9954fc4c87 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -4,7 +4,7 @@ Builtins are the functions and modules that are implicitly imported into every m ### module/src/symbol.rs -Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). +Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `rem` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones). Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them. diff --git a/compiler/builtins/bitcode/build.zig b/compiler/builtins/bitcode/build.zig index 3f724041fe..b2860ecf57 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -5,7 +5,7 @@ const CrossTarget = std.zig.CrossTarget; const Arch = std.Target.Cpu.Arch; pub fn build(b: *Builder) void { - // b.setPreferredReleaseMode(builtin.Mode.Debug + // b.setPreferredReleaseMode(.Debug); b.setPreferredReleaseMode(.ReleaseFast); const mode = b.standardReleaseOptions(); @@ -57,8 +57,9 @@ fn generateLlvmIrFile( const obj = b.addObject(object_name, main_path); obj.setBuildMode(mode); obj.strip = true; - obj.emit_llvm_ir = true; - obj.emit_bin = false; + obj.emit_llvm_ir = .emit; + obj.emit_llvm_bc = .emit; + obj.emit_bin = .no_emit; obj.target = target; const ir = b.step(step_name, "Build LLVM ir"); diff --git a/compiler/builtins/bitcode/run-wasm-tests.sh b/compiler/builtins/bitcode/run-wasm-tests.sh index 278a1e618b..74b1712536 100755 --- a/compiler/builtins/bitcode/run-wasm-tests.sh +++ b/compiler/builtins/bitcode/run-wasm-tests.sh @@ -4,6 +4,8 @@ set -euxo pipefail # Test failures will always point at the _start function # Make sure to look at the rest of the stack trace! -warning_about_non_native_binary=$(zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig 2>&1) -wasm_test_binary=$(echo $warning_about_non_native_binary | cut -d' ' -f 3) -wasmer $wasm_test_binary dummyArgForZigTestBinary + +# Zig will try to run the test binary it produced, but it is a wasm object and hence your OS won't +# know how to run it. In the error message, it prints the binary it tried to run. We use some fun +# unix tools to get that path, then feed it to wasmer +zig test -target wasm32-wasi-musl -O ReleaseFast src/main.zig --test-cmd wasmer --test-cmd-bin diff --git a/compiler/builtins/bitcode/src/dec.zig b/compiler/builtins/bitcode/src/dec.zig index b9eba9c0cc..d19cfaca3b 100644 --- a/compiler/builtins/bitcode/src/dec.zig +++ b/compiler/builtins/bitcode/src/dec.zig @@ -139,7 +139,7 @@ pub const RocDec = extern struct { // Format the backing i128 into an array of digit (ascii) characters (u8s) var digit_bytes_storage: [max_digits + 1]u8 = undefined; - var num_digits = std.fmt.formatIntBuf(digit_bytes_storage[0..], num, 10, false, .{}); + var num_digits = std.fmt.formatIntBuf(digit_bytes_storage[0..], num, 10, .lower, .{}); var digit_bytes: [*]u8 = digit_bytes_storage[0..]; // space where we assemble all the characters that make up the final string diff --git a/compiler/builtins/bitcode/src/expect.zig b/compiler/builtins/bitcode/src/expect.zig index 3734b578ce..cc111b7005 100644 --- a/compiler/builtins/bitcode/src/expect.zig +++ b/compiler/builtins/bitcode/src/expect.zig @@ -27,18 +27,8 @@ pub fn expectFailed( // Lock the failures mutex before reading from any of the failures globals, // and then release the lock once we're done modifying things. - - // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d - // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig - // - // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: - // - // failures_mutex.lock(); - // defer failures_mutex.release(); - // - // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 - const held = failures_mutex.acquire(); - defer held.release(); + failures_mutex.lock(); + defer failures_mutex.unlock(); // If we don't have enough capacity to add a failure, allocate a new failures pointer. if (failure_length >= failure_capacity) { @@ -87,17 +77,8 @@ pub fn expectFailedC( } pub fn getExpectFailures() []Failure { - // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d - // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig - // - // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: - // - // failures_mutex.lock(); - // defer failures_mutex.release(); - // - // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 - const held = failures_mutex.acquire(); - defer held.release(); + failures_mutex.lock(); + defer failures_mutex.unlock(); if (failure_length > 0) { // defensively clone failures, in case someone modifies the originals after the mutex has been released. @@ -116,23 +97,14 @@ pub fn getExpectFailures() []Failure { } pub fn getExpectFailuresC() callconv(.C) CSlice { - var bytes = @ptrCast(*c_void, failures); + var bytes = @ptrCast(*anyopaque, failures); return .{ .pointer = bytes, .len = failure_length }; } pub fn deinitFailures() void { - // TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d - // to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig - // - // ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9: - // - // failures_mutex.lock(); - // defer failures_mutex.release(); - // - // 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆 - const held = failures_mutex.acquire(); - defer held.release(); + failures_mutex.lock(); + defer failures_mutex.unlock(); utils.dealloc(@ptrCast([*]u8, failures), @alignOf(Failure)); failure_length = 0; diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 4b52eabbcd..7ec0a88b53 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -1326,7 +1326,6 @@ pub fn listFindUnsafe( data: Opaque, inc_n_data: IncN, data_is_owned: bool, - alignment: u32, element_width: usize, inc: Inc, dec: Dec, @@ -1355,3 +1354,9 @@ pub fn listFindUnsafe( return .{ .value = null, .found = false }; } } + +pub fn listIsUnique( + list: RocList, +) callconv(.C) bool { + return list.isEmpty() or list.isUnique(); +} diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 0c0dfa3b22..c772eafa1a 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const math = std.math; const utils = @import("utils.zig"); const expect = @import("expect.zig"); @@ -55,6 +56,7 @@ comptime { exportListFn(list.listAny, "any"); exportListFn(list.listAll, "all"); exportListFn(list.listFindUnsafe, "find_unsafe"); + exportListFn(list.listIsUnique, "is_unique"); } // Dict Module @@ -104,6 +106,9 @@ comptime { num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max."); num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min."); } + + num.exportRoundF32(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32."); + num.exportRoundF64(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64."); } inline for (FLOATS) |T| { @@ -112,7 +117,6 @@ comptime { num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan."); num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite."); - num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round."); } } @@ -161,6 +165,31 @@ comptime { exportExpectFn(expect.deinitFailuresC, "deinit_failures"); @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); + + if (builtin.target.cpu.arch == .aarch64) { + @export(__roc_force_setjmp, .{ .name = "__roc_force_setjmp", .linkage = .Weak }); + @export(__roc_force_longjmp, .{ .name = "__roc_force_longjmp", .linkage = .Weak }); + } +} + +// Utils continued - SJLJ +// For tests (in particular test_gen), roc_panic is implemented in terms of +// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/rtfeldman/roc/issues/2965), +// so instead we ask Zig to please provide implementations for us, which is does +// (seemingly via musl). +pub extern fn setjmp([*c]c_int) c_int; +pub extern fn longjmp([*c]c_int, c_int) noreturn; +pub extern fn _setjmp([*c]c_int) c_int; +pub extern fn _longjmp([*c]c_int, c_int) noreturn; +pub extern fn sigsetjmp([*c]c_int, c_int) c_int; +pub extern fn siglongjmp([*c]c_int, c_int) noreturn; +pub extern fn longjmperror() void; +// Zig won't expose the externs (and hence link correctly) unless we force them to be used. +fn __roc_force_setjmp(it: [*c]c_int) callconv(.C) c_int { + return setjmp(it); +} +fn __roc_force_longjmp(a0: [*c]c_int, a1: c_int) callconv(.C) noreturn { + longjmp(a0, a1); } // Export helpers - Must be run inside a comptime @@ -193,7 +222,6 @@ fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void { // Custom panic function, as builtin Zig version errors during LLVM verification pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { - const builtin = @import("builtin"); if (builtin.is_test) { std.debug.print("{s}: {?}", .{ message, stacktrace }); } else { diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index 54bcf8ff5a..d25b1fd653 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -24,7 +24,7 @@ pub fn exportParseInt(comptime T: type, comptime name: []const u8) void { const radix = 0; if (std.fmt.parseInt(T, buf.asSlice(), radix)) |success| { return .{ .errorcode = 0, .value = success }; - } else |err| { + } else |_| { return .{ .errorcode = 1, .value = 0 }; } } @@ -37,7 +37,7 @@ pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void { fn func(buf: RocStr) callconv(.C) NumParseResult(T) { if (std.fmt.parseFloat(T, buf.asSlice())) |success| { return .{ .errorcode = 0, .value = success }; - } else |err| { + } else |_| { return .{ .errorcode = 1, .value = 0 }; } } @@ -90,10 +90,19 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void { @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } -pub fn exportRound(comptime T: type, comptime name: []const u8) void { +pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void { comptime var f = struct { - fn func(input: T) callconv(.C) i64 { - return @floatToInt(i64, (@round(input))); + fn func(input: f32) callconv(.C) T { + return @floatToInt(T, (@round(input))); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportRoundF64(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: f64) callconv(.C) T { + return @floatToInt(T, (@round(input))); } }.func; @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 03657bbec3..5eacee0aa5 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -2041,7 +2041,7 @@ test "ReverseUtf8View: empty" { const original_bytes = ""; var iter = ReverseUtf8View.initUnchecked(original_bytes).iterator(); - while (iter.nextCodepoint()) |codepoint| { + while (iter.nextCodepoint()) |_| { try expect(false); } } diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 40e6c5d88c..2c3bdce0db 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -7,17 +7,17 @@ pub fn WithOverflow(comptime T: type) type { } // If allocation fails, this must cxa_throw - it must not return a null pointer! -extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void; +extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque; // This should never be passed a null pointer. // If allocation fails, this must cxa_throw - it must not return a null pointer! -extern fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void; +extern fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque; // This should never be passed a null pointer. -extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void; +extern fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void; // Signals to the host that the program has panicked -extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void; +extern fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void; // should work just like libc memcpy (we can't assume libc is present) extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; @@ -34,31 +34,31 @@ comptime { } } -fn testing_roc_alloc(size: usize, _: u32) callconv(.C) ?*c_void { - return @ptrCast(?*c_void, std.testing.allocator.alloc(u8, size) catch unreachable); +fn testing_roc_alloc(size: usize, _: u32) callconv(.C) ?*anyopaque { + return @ptrCast(?*anyopaque, std.testing.allocator.alloc(u8, size) catch unreachable); } -fn testing_roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, _: u32) callconv(.C) ?*c_void { +fn testing_roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, _: u32) callconv(.C) ?*anyopaque { const ptr = @ptrCast([*]u8, @alignCast(2 * @alignOf(usize), c_ptr)); const slice = ptr[0..old_size]; - return @ptrCast(?*c_void, std.testing.allocator.realloc(slice, new_size) catch unreachable); + return @ptrCast(?*anyopaque, std.testing.allocator.realloc(slice, new_size) catch unreachable); } -fn testing_roc_dealloc(c_ptr: *c_void, _: u32) callconv(.C) void { +fn testing_roc_dealloc(c_ptr: *anyopaque, _: u32) callconv(.C) void { const ptr = @ptrCast([*]u8, @alignCast(2 * @alignOf(usize), c_ptr)); std.testing.allocator.destroy(ptr); } -fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +fn testing_roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { _ = c_ptr; _ = tag_id; @panic("Roc panicked"); } -fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?*c_void { +fn testing_roc_memcpy(dest: *anyopaque, src: *anyopaque, bytes: usize) callconv(.C) ?*anyopaque { const zig_dest = @ptrCast([*]u8, dest); const zig_src = @ptrCast([*]u8, src); @@ -79,7 +79,7 @@ pub fn dealloc(c_ptr: [*]u8, alignment: u32) void { } // must export this explicitly because right now it is not used from zig code -pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { +pub fn panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment }); } @@ -89,7 +89,7 @@ pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void { // indirection because otherwise zig creates an alias to the panic function which our LLVM code // does not know how to deal with -pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void { +pub fn test_panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { _ = c_ptr; _ = alignment; // const cstr = @ptrCast([*:0]u8, c_ptr); @@ -240,7 +240,7 @@ pub fn allocateWithRefcount( } pub const CSlice = extern struct { - pointer: *c_void, + pointer: *anyopaque, len: usize, }; diff --git a/compiler/builtins/bitcode/wasi-libc.a b/compiler/builtins/bitcode/wasi-libc.a deleted file mode 100644 index 70378bcf13..0000000000 Binary files a/compiler/builtins/bitcode/wasi-libc.a and /dev/null differ diff --git a/compiler/builtins/build.rs b/compiler/builtins/build.rs index ff2a83e609..59503cc89a 100644 --- a/compiler/builtins/build.rs +++ b/compiler/builtins/build.rs @@ -36,30 +36,15 @@ fn main() { // LLVM .bc FILES - generate_bc_file(&bitcode_path, &build_script_dir_path, "ir", "builtins-host"); + generate_bc_file(&bitcode_path, "ir", "builtins-host"); if !DEBUG { - generate_bc_file( - &bitcode_path, - &build_script_dir_path, - "ir-wasm32", - "builtins-wasm32", - ); + generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32"); } - generate_bc_file( - &bitcode_path, - &build_script_dir_path, - "ir-i386", - "builtins-i386", - ); + generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386"); - generate_bc_file( - &bitcode_path, - &build_script_dir_path, - "ir-x86_64", - "builtins-x86_64", - ); + generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64"); // OBJECT FILES #[cfg(windows)] @@ -131,35 +116,23 @@ fn generate_object_file( } } -fn generate_bc_file( - bitcode_path: &Path, - build_script_dir_path: &Path, - zig_object: &str, - file_name: &str, -) { +fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) { let mut ll_path = bitcode_path.join(file_name); ll_path.set_extension("ll"); let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path"); println!("Compiling host ir to: {}", dest_ir_host); + let mut bc_path = bitcode_path.join(file_name); + bc_path.set_extension("bc"); + let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path"); + println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit); + run_command( &bitcode_path, &zig_executable(), &["build", zig_object, "-Drelease=true"], ); - - let mut bc_path = bitcode_path.join(file_name); - bc_path.set_extension("bc"); - let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path"); - - println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit); - - run_command( - &build_script_dir_path, - "llvm-as", - &[dest_ir_host, "-o", dest_bc_64bit], - ); } fn run_command>(path: P, command_str: &str, args: I) diff --git a/compiler/builtins/docs/Bool.roc b/compiler/builtins/docs/Bool.roc deleted file mode 100644 index 29d8dbbc90..0000000000 --- a/compiler/builtins/docs/Bool.roc +++ /dev/null @@ -1,90 +0,0 @@ -interface Bool - exposes [ and, isEq, isNotEq, not, or, xor ] - imports [] - -## Returns `False` when given `True`, and vice versa. -not : [True, False] -> [True, False] - -## Returns `True` when given `True` and `True`, and `False` when either argument is `False`. -## -## `a && b` is shorthand for `Bool.and a b` -## -## >>> True && True -## -## >>> True && False -## -## >>> False && True -## -## >>> False && False -## -## ## Performance Notes -## -## In some languages, `&&` and `||` are special-cased in the compiler to skip -## evaluating the expression after the operator under certain circumstances. -## For example, in some languages, `enablePets && likesDogs user` would compile -## to the equivalent of: -## -## if enablePets then -## likesDogs user -## else -## False -## -## In Roc, however, `&&` and `||` are not special. They work the same way as -## other functions. Conditionals like `if` and `when` have a performance cost, -## and sometimes calling a function like `likesDogs user` can be faster across -## the board than doing an `if` to decide whether to skip calling it. -## -## (Naturally, if you expect the `if` to improve performance, you can always add -## one explicitly!) -and : Bool, Bool -> Bool - - -## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`. -## -## `a || b` is shorthand for `Bool.or a b`. -## -## >>> True || True -## -## >>> True || False -## -## >>> False || True -## -## >>> False || False -## -## ## Performance Notes -## -## In some languages, `&&` and `||` are special-cased in the compiler to skip -## evaluating the expression after the operator under certain circumstances. -## In Roc, this is not the case. See the performance notes for [Bool.and] for details. -or : Bool, Bool -> Bool - -## Exclusive or -xor : Bool, Bool -> Bool - -# TODO: removed `'` from signature because parser does not support it yet -# Original signature: `isEq : 'val, 'val -> Bool` -## Returns `True` if the two values are *structurally equal*, and `False` otherwise. -## -## `a == b` is shorthand for `Bool.isEq a b` -## -## Structural equality works as follows: -## -## 1. Global tags are equal if they are the same tag, and also their contents (if any) are equal. -## 2. Private tags are equal if they are the same tag, in the same module, and also their contents (if any) are equal. -## 3. Records are equal if all their fields are equal. -## 4. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal. -## 5. [Num] values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*. -## -## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not -## accept arguments whose types contain functions. -isEq : val, val -> Bool - -# TODO: removed `'` from signature because parser does not support it yet -# Original signature: `isNotEq : 'val, 'val -> Bool` -## Calls [isEq] on the given values, then calls [not] on the result. -## -## `a != b` is shorthand for `Bool.isNotEq a b` -## -## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not -## accept arguments whose types contain functions. -isNotEq : val, val -> Bool diff --git a/compiler/builtins/docs/Dict.roc b/compiler/builtins/docs/Dict.roc deleted file mode 100644 index 0de2d5ad5d..0000000000 --- a/compiler/builtins/docs/Dict.roc +++ /dev/null @@ -1,210 +0,0 @@ -interface Dict - exposes - [ - Dict, - contains, - difference, - empty, - get, - keys, - insert, - intersection, - len, - remove, - single, - union, - values, - walk - ] - imports [] - -## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values. -## -## ### Inserting -## -## The most basic way to use a dictionary is to start with an empty one and then: -## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary. -## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored. -## -## Here's an example of a dictionary which uses a city's name as the key, and its population as the associated value. -## -## populationByCity = -## Dict.empty -## |> Dict.insert "London" 8_961_989 -## |> Dict.insert "Philadelphia" 1_603_797 -## |> Dict.insert "Shanghai" 24_870_895 -## |> Dict.insert "Delhi" 16_787_941 -## |> Dict.insert "Amsterdam" 872_680 -## -## ### Converting to a [List] -## -## We can call [Dict.toList] on `populationByCity` to turn it into a list of key-value pairs: -## -## Dict.toList populationByCity == [ -## { k: "London", v: 8961989 }, -## { k: "Philadelphia", v: 1603797 }, -## { k: "Shanghai", v: 24870895 }, -## { k: "Delhi", v: 16787941 }, -## { k: "Amsterdam", v: 872680 }, -## ] -## -## We can use the similar [Dict.keyList] and [Dict.values] functions to get only the keys or only the values, -## instead of getting these `{ k, v }` records that contain both. -## -## You may notice that these lists have the same order as the original insertion order. This will be true if -## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order. -## Let's see how that looks. -## -## ### Removing -## -## We can remove an element from the dictionary, like so: -## -## populationByCity -## |> Dict.remove "Philadelphia" -## |> Dict.toList -## == -## [ -## { k: "London", v: 8961989 }, -## { k: "Amsterdam", v: 872680 }, -## { k: "Shanghai", v: 24870895 }, -## { k: "Delhi", v: 16787941 }, -## ] -## -## Notice that the order changed! Philadelphia has been not only removed from the list, but Amsterdam - the last -## entry we inserted - has been moved into the spot where Philadelphia was previously. This is exactly what -## [Dict.remove] does: it removes an element and moves the most recent insertion into the vacated spot. -## -## This move is done as a performance optimization, and it lets [remove] have -## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). If you need a removal -## operation which preserves ordering, [Dict.removeShift] will remove the element and then shift everything after it -## over one spot. Be aware that this shifting requires copying every single entry after the removed element, though, -## so it can be massively more costly than [remove]! This makes [remove] the recommended default choice; -## [removeShift] should only be used if maintaining original insertion order is absolutely necessary. -## -## -## ### Removing -## -## ### Equality -## -## When comparing two dictionaries for equality, they are `==` only if their both their contents and their -## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on -## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering (for example, if -## `fn` is `Dict.toList` or calls it internally.) -## -## The [Dict.hasSameContents] function gives an alternative to `==` which ignores ordering -## and returns `True` if both dictionaries have the same keys and associated values. -Dict k v : [ @Dict k v ] # TODO k should require a hashing and equating constraint - -## An empty dictionary. -empty : Dict * * - -size : Dict * * -> Nat - -isEmpty : Dict * * -> Bool - -## Returns a [List] of the dictionary's key/value pairs. -## -## See [walk] to walk over the key/value pairs without creating an intermediate data structure. -toList : Dict k v -> List { k, v } - -## Returns a [List] of the dictionary's keys. -## -## See [keySet] to get a [Set] of keys instead, or [walkKeys] to walk over the keys without creating -## an intermediate data structure. -keyList : Dict key * -> List key - -## Returns a [Set] of the dictionary's keys. -## -## See [keyList] to get a [List] of keys instead, or [walkKeys] to walk over the keys without creating -## an intermediate data structure. -keySet : Dict key * -> Set key - -## Returns a [List] of the dictionary's values. -## -## See [walkValues] to walk over the values without creating an intermediate data structure. -values : Dict * value -> List value - -walk : Dict k v, state, (state, k, v -> state) -> state - -walkKeys : Dict key *, state, (state, key -> state) -> state - -walkValues : Dict * value, state, (state, value -> state) -> state - -## Convert each key and value in the #Dict to something new, by calling a conversion -## function on each of them. Then return a new #Map of the converted keys and values. -## -## >>> Dict.map {{ 3.14 => "pi", 1.0 => "one" }} \{ key, value } -> { key: -## -## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty -## -## `map` functions like this are common in Roc, and they all work similarly. -## See for example [List.map], [Result.map], and `Set.map`. -map : - Dict beforeKey beforeVal, - ({ k: beforeKey, v: beforeVal } -> { k: afterKey, v: afterVal }) - -> Dict afterKey afterVal - -# DESIGN NOTES: The reason for panicking when given NaN is that: -# * If we allowed NaN in, Dict.insert would no longer be idempotent. -# * If we allowed NaN but overrode its semantics to make it feel like "NaN == NaN" we'd need isNaN checks in all hashing operations as well as all equality checks (during collision detection), not just insert. This would be much worse for performance than panicking on insert, which only requires one extra conditional on insert. -# * It's obviously invalid; the whole point of NaN is that an error occurred. Giving a runtime error notifies you when this problem happens. Giving it only on insert is the best for performance, because it means you aren't paying for isNaN checks on lookups as well. - -# TODO: removed `'` from signature because parser does not support it yet -# Original signature: insert : Dict 'key val, 'key, val -> Dict 'key val -## Make sure never to insert a key of *NaN* into a [Dict]! Because *NaN* is -## defined to be unequal to *NaN*, inserting a *NaN* key results in an entry -## that can never be retrieved or removed from the [Dict]. -insert : Dict key val, key, val -> Dict key val - -## Removes a key from the dictionary in [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), without preserving insertion order. -## -## Since the internal [List] which determines the order of operations like [toList] and [walk] cannot have gaps in it, -## whenever an element is removed from the middle of that list, something must be done to eliminate the resulting gap. -## -## * [removeShift] eliminates the gap by shifting over every element after the removed one. This takes [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time), -## and preserves the original ordering. -## * [remove] eliminates the gap by replacing the removed element with the one at the end of the list - that is, the most recent insertion. This takes [constant time](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), but does not preserve the original ordering. -## -## For example, suppose we have a `populationByCity` with these contents: -## -## Dict.toList populationByCity == [ -## { k: "London", v: 8961989 }, -## { k: "Philadelphia", v: 1603797 }, -## { k: "Shanghai", v: 24870895 }, -## { k: "Delhi", v: 16787941 }, -## { k: "Amsterdam", v: 872680 }, -## ] -## -## Using `Dict.remove "Philadelphia"` on this will replace the `"Philadelphia"` entry with the most recent insertion, -## which is `"Amsterdam"` in this case. -## -## populationByCity -## |> Dict.remove "Philadelphia" -## |> Dict.toList -## == -## [ -## { k: "London", v: 8961989 }, -## { k: "Amsterdam", v: 872680 }, -## { k: "Shanghai", v: 24870895 }, -## { k: "Delhi", v: 16787941 }, -## ] -## -## Both [remove] and [removeShift] leave the dictionary with the same contents; they only differ in ordering and in -## performance. Since ordering only affects operations like [toList] and [walk], [remove] is the better default -## choice because it has much better performance characteristics; [removeShift] should only be used when it's -## absolutely necessary for operations like [toList] and [walk] to preserve the exact original insertion order. -remove : Dict k v, k -> Dict k v - -## Removes a key from the dictionary in [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time), while preserving insertion order. -## -## It's better to use [remove] than this by default, since [remove] has [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time), -## which commonly leads [removeShift] to take many times as long to run as [remove] does. However, [remove] does not -## preserve insertion order, so the slower [removeShift] exists only for use cases where it's abolutely necessary for -## ordering-sensitive functions like [toList] and [walk] to preserve the exact original insertion order. -## -## See the [remove] documentation for more details about the differences between [remove] and [removeShift]. -removeShift : Dict k v, k -> Dict k v - -## Returns whether both dictionaries have the same keys, and the same values associated with those keys. -## This is different from `==` in that it disregards the ordering of the keys and values. -hasSameContents : Dict k v, Dict k v -> Bool diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc deleted file mode 100644 index 25ddbc6344..0000000000 --- a/compiler/builtins/docs/List.roc +++ /dev/null @@ -1,705 +0,0 @@ -interface List - exposes - [ - List, - append, - concat, - contains, - drop, - dropAt, - dropLast, - first, - get, - isEmpty, - join, - keepErrs, - keepIf, - keepOks, - last, - len, - map, - map2, - map3, - map4, - mapJoin, - mapOrDrop, - mapWithIndex, - prepend, - product, - range, - repeat, - reverse, - set, - single, - sortWith, - split, - sublist, - sum, - swap, - walk, - walkBackwards, - walkUntil - ] - imports [] - -## Types - -## A sequential list of values. -## -## >>> [ 1, 2, 3 ] # a list of numbers -## >>> [ "a", "b", "c" ] # a list of strings -## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of numbers -## -## The list `[ 1, "a" ]` gives an error, because each element in a list must have -## the same type. If you want to put a mix of [I64] and [Str] values into a list, try this: -## -## ``` -## mixedList : List [ IntElem I64, StrElem Str ]* -## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ] -## ``` -## -## The maximum size of a [List] is limited by the amount of heap memory available -## to the current process. If there is not enough memory available, attempting to -## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html) -## is normally enabled, not having enough memory could result in the list appearing -## to be created just fine, but then crashing later.) -## -## > The theoretical maximum length for a list created in Roc is half of -## > `Num.maxNat`. Attempting to create a list bigger than that -## > in Roc code will always fail, although in practice it is likely to fail -## > at much smaller lengths due to insufficient memory being available. -## -## ## Performance Details -## -## Under the hood, a list is a record containing a `len : Nat` field as well -## as a pointer to a reference count and a flat array of bytes. Unique lists -## store a capacity #Nat instead of a reference count. -## -## ## Shared Lists -## -## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting). -## -## Each time a given list gets referenced, its reference count ("refcount" for short) -## gets incremented. Each time a list goes out of scope, its refcount count gets -## decremented. Once a refcount, has been decremented more times than it has been -## incremented, we know nothing is referencing it anymore, and the list's memory -## will be immediately freed. -## -## Let's look at an example. -## -## ratings = [ 5, 4, 3 ] -## -## { foo: ratings, bar: ratings } -## -## The first line binds the name `ratings` to the list `[ 5, 4, 3 ]`. The list -## begins with a refcount of 1, because so far only `ratings` is referencing it. -## -## The second line alters this refcount. `{ foo: ratings` references -## the `ratings` list, which will result in its refcount getting incremented -## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list, -## which will result in its refcount getting incremented from 1 to 2. -## -## Let's turn this example into a function. -## -## getRatings = \first -> -## ratings = [ first, 4, 3 ] -## -## { foo: ratings, bar: ratings } -## -## getRatings 5 -## -## At the end of the `getRatings` function, when the record gets returned, -## the original `ratings =` binding has gone out of scope and is no longer -## accessible. (Trying to reference `ratings` outside the scope of the -## `getRatings` function would be an error!) -## -## Since `ratings` represented a way to reference the list, and that way is no -## longer accessible, the list's refcount gets decremented when `ratings` goes -## out of scope. It will decrease from 2 back down to 1. -## -## Putting these together, when we call `getRatings 5`, what we get back is -## a record with two fields, `foo`, and `bar`, each of which refers to the same -## list, and that list has a refcount of 1. -## -## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`: -## -## getRatings = \first -> -## ratings = [ first, 4, 3 ] -## -## { foo: ratings, bar: ratings } -## -## (getRatings 5).bar -## -## Now, when this expression returns, only the `bar` field of the record will -## be returned. This will mean that the `foo` field becomes inaccessible, causing -## the list's refcount to get decremented from 2 to 1. At this point, the list is back -## where it started: there is only 1 reference to it. -## -## Finally let's suppose the final line were changed to this: -## -## List.first (getRatings 5).bar -## -## This call to [List.first] means that even the list in the `bar` field has become -## inaccessible. As such, this line will cause the list's refcount to get -## decremented all the way to 0. At that point, nothing is referencing the list -## anymore, and its memory will get freed. -## -## Things are different if this is a list of lists instead of a list of numbers. -## Let's look at a simpler example using [List.first] - first with a list of numbers, -## and then with a list of lists, to see how they differ. -## -## Here's the example using a list of numbers. -## -## nums = [ 1, 2, 3, 4, 5, 6, 7 ] -## -## first = List.first nums -## last = List.last nums -## -## first -## -## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`. -## -## Here's the equivalent code with a list of lists: -## -## lists = [ [ 1 ], [ 2, 3 ], [], [ 4, 5, 6, 7 ] ] -## -## first = List.first lists -## last = List.last lists -## -## first -## -## TODO explain how in the former example, when we go to free `nums` at the end, -## we can free it immediately because there are no other refcounts. However, -## in the case of `lists`, we have to iterate through the list and decrement -## the refcounts of each of its contained lists - because they, too, have -## refcounts! Importantly, because the first element had its refcount incremented -## because the function returned `first`, that element will actually end up -## *not* getting freed at the end - but all the others will be. -## -## In the `lists` example, `lists = [ ... ]` also creates a list with an initial -## refcount of 1. Separately, it also creates several other lists - each with -## their own refcounts - to go inside that list. (The empty list at the end -## does not use heap memory, and thus has no refcount.) -## -## At the end, we once again call [List.first] on the list, but this time -## -## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold. -## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all -## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations. -## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood! -List elem : [ @List elem ] - -## Initialize - -## A list with a single element in it. -## -## This is useful in pipelines, like so: -## -## websites = -## Str.concat domain ".com" -## |> List.single -## -single : elem -> List elem - -## An empty list. -empty : List * - -## Returns a list with the given length, where every element is the given value. -## -## -repeat : elem, Nat -> List elem - -## Returns a list of all the integers between one and another, -## including both of the given numbers. -## -## >>> List.range 2 8 -range : Int a, Int a -> List (Int a) - -## Transform - -## Returns the list with its elements reversed. -## -## >>> List.reverse [ 1, 2, 3 ] -reverse : List elem -> List elem - -## Sorts a list using a function which specifies how two elements are ordered. -## -## When sorting by numeric values, it's more efficient to use [sortAsc] or -## [sortDesc] instead. -sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem - -## Sorts a list in ascending order (lowest to highest), using a function which -## specifies a way to represent each element as a number. -## -## This is more efficient than [sort] because it skips -## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead. -## -## To sort in descending order (highest to lowest), use [List.sortDesc] instead. -sortAsc : List elem, (elem -> Num *) -> List elem - -## Sorts a list in descending order (highest to lowest), using a function which -## specifies a way to represent each element as a number. -## -## This is more efficient than [sort] because it skips -## calculating the `[ Lt, Eq, Gt ]` value and uses the number directly instead. -## -## To sort in ascending order (lowest to highest), use [List.sortAsc] instead. -sortDesc : List elem, (elem -> Num *) -> List elem - -## Convert each element in the list to something new, by calling a conversion -## function on each of them. Then return a new list of the converted values. -## -## > List.map [ 1, 2, 3 ] (\num -> num + 1) -## -## > List.map [ "", "a", "bc" ] Str.isEmpty -## -## `map` functions like this are common in Roc, and they all work similarly. -## See for example `Set.map`, `Dict.map`, and [Result.map]. -map : List before, (before -> after) -> List after - -## Run a transformation function on the first element of each list, -## and use that as the first element in the returned list. -## Repeat until a list runs out of elements. -## -## Some languages have a function named `zip`, which does something similar to -## calling [List.map2] passing two lists and `Pair`: -## -## >>> zipped = List.map2 [ "a", "b", "c" ] [ 1, 2, 3 ] Pair -map2 : List a, List b, (a, b -> c) -> List c - -## Run a transformation function on the first element of each list, -## and use that as the first element in the returned list. -## Repeat until a list runs out of elements. -map3 : List a, List b, List c, (a, b, c -> d) -> List d - -## Run a transformation function on the first element of each list, -## and use that as the first element in the returned list. -## Repeat until a list runs out of elements. -map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e - -## This works like [List.map], except it also passes the index -## of the element to the conversion function. -mapWithIndex : List before, (before, Nat -> after) -> List after - -## This works like [List.map], except at any time you can return `Err` to -## cancel the entire operation immediately, and return that #Err. -mapOrCancel : List before, (before -> Result after err) -> Result (List after) err - -## Like [List.map], except the transformation function specifies whether to -## `Keep` or `Drop` each element from the final [List]. -## -## You may know a similar function named `filterMap` in other languages. -mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after - -## Like [List.map], except the transformation function wraps the return value -## in a list. At the end, all the lists get joined together into one list. -## -## You may know a similar function named `concatMap` in other languages. -mapJoin : List before, (before -> List after) -> List after - -## This works like [List.map], except only the transformed values that are -## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped. -## -## >>> List.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last -## -## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) -## >>> -## >>> List.mapOks [ "", "a", "bc", "", "d", "ef", "" ] -mapOks : List before, (before -> Result after *) -> List after - -## Returns a list with the element at the given index having been transformed by -## the given function. -## -## For a version of this which gives you more control over when to perform -## the transformation, see `List.updater` -## -## ## Performance notes -## -## In particular when updating nested collections, this is potentially much more -## efficient than using [List.get] to obtain the element, transforming it, -## and then putting it back in the same place. -update : List elem, Nat, (elem -> elem) -> List elem - -## A more flexible version of `List.update`, which returns an "updater" function -## that lets you delay performing the update until later. -updater : List elem, Nat -> { elem, new : (elem -> List elem) } - -## If all the elements in the list are #Ok, return a new list containing the -## contents of those #Ok tags. If any elements are #Err, return #Err. -allOks : List (Result ok err) -> Result (List ok) err - -## Add a single element to the end of a list. -## -## >>> List.append [ 1, 2, 3 ] 4 -## -## >>> [ 0, 1, 2 ] -## >>> |> List.append 3 -append : List elem, elem -> List elem - -## Add a single element to the beginning of a list. -## -## >>> List.prepend [ 1, 2, 3 ] 0 -## -## >>> [ 2, 3, 4 ] -## >>> |> List.prepend 1 -prepend : List elem, elem -> List elem - -## Put two lists together. -## -## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ] -## -## >>> [ 0, 1, 2 ] -## >>> |> List.concat [ 3, 4 ] -concat : List elem, List elem -> List elem - -## Join the given lists together into one list. -## -## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ] -## -## >>> List.join [ [], [] ] -## -## >>> List.join [] -join : List (List elem) -> List elem - -## Like [List.join], but only keeps elements tagged with `Ok`. Elements -## tagged with `Err` are dropped. -## -## This can be useful after using an operation that returns a #Result -## on each element of a list, for example [List.first]: -## -## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ] -## >>> |> List.map List.first -## >>> |> List.joinOks -## -## Eventually, `oks` type signature will be `List [Ok elem]* -> List elem`. -## The implementation for that is a lot tricker then `List (Result elem *)` -## so we're sticking with `Result` for now. -oks : List (Result elem *) -> List elem - -## Filter - -## Run the given function on each element of a list, and return all the -## elements for which the function returned `True`. -## -## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2) -## -## ## Performance Details -## -## [List.keepIf] always returns a list that takes up exactly the same amount -## of memory as the original, even if its length decreases. This is because it -## can't know in advance exactly how much space it will need, and if it guesses a -## length that's too low, it would have to re-allocate. -## -## (If you want to do an operation like this which reduces the memory footprint -## of the resulting list, you can do two passes over the lis with [List.walk] - one -## to calculate the precise new size, and another to populate the new list.) -## -## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list. -## If that happens, this function will not allocate any new memory on the heap. -## If all elements in the list end up being kept, Roc will return the original -## list unaltered. -## -keepIf : List elem, (elem -> Bool) -> List elem - -## Run the given function on each element of a list, and return all the -## elements for which the function returned `False`. -## -## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2) -## -## ## Performance Details -## -## `List.dropIf` has the same performance characteristics as [List.keepIf]. -## See its documentation for details on those characteristics! -dropIf : List elem, (elem -> Bool) -> List elem - -## Access - -## Returns the first element in the list, or `ListWasEmpty` if it was empty. -first : List elem -> Result elem [ ListWasEmpty ]* - -## Returns the last element in the list, or `ListWasEmpty` if it was empty. -last : List elem -> Result elem [ ListWasEmpty ]* - -get : List elem, Nat -> Result elem [ OutOfBounds ]* - -max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* - -min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* - -## Modify - -## Replaces the element at the given index with a replacement. -## -## >>> List.set [ "a", "b", "c" ] 1 "B" -## -## If the given index is outside the bounds of the list, returns the original -## list unmodified. -## -## To drop the element at a given index, instead of replacing it, see [List.dropAt]. -set : List elem, Nat, elem -> List elem - -## Drops n elements from the beginning of the list. -drop : List elem, Nat -> List elem - -## Drops the element at the given index from the list. -## -## This has no effect if the given index is outside the bounds of the list. -## -## To replace the element at a given index, instead of dropping it, see [List.set]. -dropAt : List elem, Nat -> List elem - -## Adds a new element to the end of the list. -## -## >>> List.append [ "a", "b" ] "c" -## -## ## Performance Details -## -## When given a Unique list, this adds the new element in-place if possible. -## This is only possible if the list has enough capacity. Otherwise, it will -## have to *clone and grow*. See the section on [capacity](#capacity) in this -## module's documentation. -append : List elem, elem -> List elem - -## Adds a new element to the beginning of the list. -## -## >>> List.prepend [ "b", "c" ] "a" -## -## ## Performance Details -## -## This always clones the entire list, even when given a Unique list. That means -## it runs about as fast as `List.addLast` when both are given a Shared list. -## -## If you have a Unique list instead, [List.append] will run much faster than -## [List.append] except in the specific case where the list has no excess capacity, -## and needs to *clone and grow*. In that uncommon case, both [List.append] and -## [List.append] will run at about the same speed—since [List.append] always -## has to clone and grow. -## -## | Unique list | Shared list | -##---------+--------------------------------+----------------+ -## append | in-place given enough capacity | clone and grow | -## prepend | clone and grow | clone and grow | -prepend : List elem, elem -> List elem - -## Remove the last element from the list. -## -## Returns both the removed element as well as the new list (with the removed -## element missing), or `Err ListWasEmpty` if the list was empty. -## -## Here's one way you can use this: -## -## when List.pop list is -## Ok { others, last } -> ... -## Err ListWasEmpty -> ... -## -## ## Performance Details -## -## Calling `List.pop` on a Unique list runs extremely fast. It's essentially -## the same as a [List.last] except it also returns the [List] it was given, -## with its length decreased by 1. -## -## In contrast, calling `List.pop` on a Shared list creates a new list, then -## copies over every element in the original list except the last one. This -## takes much longer. -dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpty ]* - -## -## Here's one way you can use this: -## -## when List.pop list is -## Ok { others, last } -> ... -## Err ListWasEmpty -> ... -## -## ## Performance Details -## -## When calling either `List.dropFirst` or `List.dropLast` on a Unique list, `List.dropLast` -## runs *much* faster. This is because for `List.dropLast`, removing the last element -## in-place is as easy as reducing the length of the list by 1. In contrast, -## removing the first element from the list involves copying every other element -## in the list into the index before it - which is massively more costly. -## -## In the case of a Shared list, -## -## | Unique list | Shared list | -##-----------+----------------------------------+---------------------------------+ -## dropFirst | [List.last] + length change | [List.last] + clone rest of list | -## dropLast | [List.last] + clone rest of list | [List.last] + clone rest of list | -dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]* - -## Returns the given number of elements from the beginning of the list. -## -## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ] -## -## If there are fewer elements in the list than the requested number, -## returns the entire list. -## -## >>> List.takeFirst 5 [ 1, 2 ] -## -## To *remove* elements from the beginning of the list, use `List.takeLast`. -## -## To remove elements from both the beginning and end of the list, -## use `List.sublist`. -## -## To split the list into two lists, use `List.split`. -## -## ## Performance Details -## -## When given a Unique list, this runs extremely fast. It sets the list's length -## to the given length value, and frees the leftover elements. This runs very -## slightly faster than `List.takeLast`. -## -## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given -## a Unique list, because [List.first] returns the first element as well - -## which introduces a conditional bounds check as well as a memory load. -takeFirst : List elem, Nat -> List elem - -## Returns the given number of elements from the end of the list. -## -## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ] -## -## If there are fewer elements in the list than the requested number, -## returns the entire list. -## -## >>> List.takeLast 5 [ 1, 2 ] -## -## To *remove* elements from the end of the list, use `List.takeFirst`. -## -## To remove elements from both the beginning and end of the list, -## use `List.sublist`. -## -## To split the list into two lists, use `List.split`. -## -## ## Performance Details -## -## When given a Unique list, this runs extremely fast. It moves the list's -## pointer to the index at the given length value, updates its length, -## and frees the leftover elements. This runs very nearly as fast as -## `List.takeFirst` on a Unique list. -## -## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given -## a Unique list, because [List.first] returns the first element as well - -## which introduces a conditional bounds check as well as a memory load. -takeLast : List elem, Nat -> List elem - -## Deconstruct - -## Splits the list into two lists, around the given index. -## -## The returned lists are labeled `before` and `others`. The `before` list will -## contain all the elements whose index in the original list was **less than** -## than the given index, # and the `others` list will be all the others. (This -## means if you give an index of 0, the `before` list will be empty and the -## `others` list will have the same elements as the original list.) -split : List elem, Nat -> { before: List elem, others: List elem } - -## Returns a subsection of the given list, beginning at the `start` index and -## including a total of `len` elements. -## -## If `start` is outside the bounds of the given list, returns the empty list. -## -## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ] -## -## If more elements are requested than exist in the list, returns as many as it can. -## -## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ] -## -## > If you want a sublist which goes all the way to the end of the list, no -## > matter how long the list is, `List.takeLast` can do that more efficiently. -## -## Some languages have a function called **`slice`** which works similarly to this. -sublist : List elem, { start : Nat, len : Nat } -> List elem - -## Build a value using each element in the list. -## -## Starting with a given `state` value, this walks through each element in the -## list from first to last, running a given `step` function on that element -## which updates the `state`. It returns the final `state` at the end. -## -## You can use it in a pipeline: -## -## [ 2, 4, 8 ] -## |> List.walk { start: 0, step: Num.add } -## -## This returns 14 because: -## * `state` starts at 0 (because of `start: 0`) -## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`. -## -## Here is a table of how `state` changes as [List.walk] walks over the elements -## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`. -## -## `state` | `elem` | `step state elem` (`Num.add state elem`) -## --------+--------+----------------------------------------- -## 0 | | -## 0 | 2 | 2 -## 2 | 4 | 6 -## 6 | 8 | 14 -## -## So `state` goes through these changes: -## 1. `0` (because of `start: 0`) -## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1 -## -## [ 1, 2, 3 ] -## |> List.walk { start: 0, step: Num.sub } -## -## This returns -6 because -## -## Note that in other languages, `walk` is sometimes called `reduce`, -## `fold`, `foldLeft`, or `foldl`. -walk : List elem, state, (state, elem -> state) -> state - -## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`, -## `fold`, `foldRight`, or `foldr`. -walkBackwards : List elem, state, (state, elem -> state) -> state - -## Same as [List.walk], except you can stop walking early. -## -## ## Performance Details -## -## Compared to [List.walk], this can potentially visit fewer elements (which can -## improve performance) at the cost of making each step take longer. -## However, the added cost to each step is extremely small, and can easily -## be outweighed if it results in skipping even a small number of elements. -## -## As such, it is typically better for performance to use this over [List.walk] -## if returning `Done` earlier than the last element is expected to be common. -walkUntil : List elem, state, (state, elem -> [ Continue state, Done state ]) -> state - -# Same as [List.walk]Backwards, except you can stop walking early. -walkBackwardsUntil : List elem, state, (state, elem -> [ Continue state, Done state ]) -> state - -## Check - -## Returns the length of the list - the number of elements it contains. -## -## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which -## is exactly equal to the highest valid #I32 value. This means the #U32 this function -## returns can always be safely converted to an #I32 without losing any data. -len : List * -> Nat - -isEmpty : List * -> Bool - -contains : List elem, elem -> Bool - -startsWith : List elem, List elem -> Bool - -endsWith : List elem, List elem -> Bool - -## Run the given predicate on each element of the list, returning `True` if -## any of the elements satisfy it. -any : List elem, (elem -> Bool) -> Bool - -## Run the given predicate on each element of the list, returning `True` if -## all of the elements satisfy it. -all : List elem, (elem -> Bool) -> Bool - -## Returns the first element of the list satisfying a predicate function. -## If no satisfying element is found, an `Err NotFound` is returned. -find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* - -## Apply a function that returns a Result on a list, only successful -## Results are kept and returned unwrapped. -keepOks : List before, (before -> Result after *) -> List after - -## Apply a function that returns a Result on a list, only unsuccessful -## Results are kept and returned unwrapped. -keepErrs : List before, (before -> Result * after) -> List after diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc deleted file mode 100644 index d667d6a005..0000000000 --- a/compiler/builtins/docs/Num.roc +++ /dev/null @@ -1,1332 +0,0 @@ -interface Num - exposes - [ - Num, - Binary64, - Binary32, - Dec, - Decimal, - Float, - FloatingPoint, - F32, - F64, - I8, - I16, - I32, - I64, - I128, - Int, - Integer, - Nat, - Natural, - Signed8, - Signed16, - Signed32, - Signed64, - Signed128, - U8, - U16, - U32, - U64, - U128, - Unsigned8, - Unsigned16, - Unsigned32, - Unsigned64, - Unsigned128, - abs, - acos, - add, - addChecked, - addWrap, - atan, - bitwiseAnd, - bitwiseOr, - bitwiseXor, - ceiling, - compare, - cos, - div, - divTrunc, - floor, - intCast, - isEven, - isGt, - isGte, - isLt, - isLte, - isMultipleOf, - isNegative, - isOdd, - isPositive, - isZero, - log, - maxFloat, - maxI8, - maxU8, - maxI16, - maxU16, - maxI32, - maxU32, - maxI64, - maxU64, - maxI128, - minFloat, - minI8, - minU8, - minI16, - minU16, - minI32, - minU32, - minI64, - minU64, - minI128, - mul, - mulChecked, - mulWrap, - neg, - pow, - powInt, - rem, - round, - shiftLeftBy, - shiftRightBy, - shiftRightZfBy, - sin, - sub, - subChecked, - subWrap, - sqrt, - tan, - toI8, - toI8Checked, - toI16, - toI16Checked, - toI32, - toI32Checked, - toI64, - toI64Checked, - toI128, - toI128Checked, - toU8, - toU8Checked, - toU16, - toU16Checked, - toU32, - toU32Checked, - toU64, - toU64Checked, - toU128, - toU128Checked, - toNat, - toNatChecked, - toFloat, - toStr - ] - imports [] - -## ## Types - -## Represents a number that could be either an [Int] or a [Float]. -## -## This is useful for functions that can work on either, for example #Num.add, whose type is: -## -## ``` -## add : Num a, Num a -> Num a -## ``` -## -## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass -## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`. -## -## Similarly, the number 0x1 (that is, the integer 1 in hexadecimal notation) -## technically has the type `Num (Integer *)`, so when you pass two of them to -## [Num.add], the answer you get is `2 : Num (Integer *)`. -## -## The type [`Float a`]([Float]) is defined to be an alias for `Num (Fraction a)`, -## so `3.0 : Num (Fraction *)` is the same value as `3.0 : Float *`. -## Similarly, the type [`Int a`](#Int) is defined to be an alias for -## `Num (Integer a)`, so `2 : Num (Integer *)` is the same value as -## `2 : Int *`. -## -## In this way, the [Num] type makes it possible to have `1 + 0x1` return -## `2 : Int *` and `1.5 + 1.5` return `3.0 : Frac`. -## -## ## Number Literals -## -## Number literals without decimal points (like `0`, `4` or `360`) -## have the type `Num *` at first, but usually end up taking on -## a more specific type based on how they're used. -## -## For example, in `(1 + List.len myList)`, the `1` has the type `Num *` at first, -## but because `List.len` returns a `Nat`, the `1` ends up changing from -## `Num *` to the more specific `Nat`, and the expression as a whole -## ends up having the type `Nat`. -## -## Sometimes number literals don't become more specific. For example, -## the `Num.toStr` function has the type `Num * -> Str`. This means that -## when calling `Num.toStr (5 + 6)`, the expression `(5 + 6)` -## still has the type `Num *`. When this happens, `Num *` defaults to -## being an [I64] - so this addition expression would overflow -## if either 5 or 6 were replaced with a number big enough to cause -## addition overflow on an [I64] value. -## -## If this default of [I64] is not big enough for your purposes, -## you can add an `i128` to the end of the number literal, like so: -## -## >>> Num.toStr 5_000_000_000i128 -## -## This `i128` suffix specifies that you want this number literal to be -## an [I128] instead of a `Num *`. All the other numeric types have -## suffixes just like `i128`; here are some other examples: -## -## * `215u8` is a `215` value of type [U8] -## * `76.4f32` is a `76.4` value of type [F32] -## * `123.45dec` is a `123.45` value of type [Dec] -## * `12345nat` is a `12345` value of type [Nat] -## -## In practice, these are rarely needed. It's most common to write -## number literals without any suffix. -Num a : [ @Num a ] - -## A decimal number. -## -## [Dec] is the best default choice for representing base-10 decimal numbers -## like currency, because it is base-10 under the hood. In contrast, -## [F64] and [F32] are base-2 under the hood, which can lead to decimal -## precision loss even when doing addition and subtraction. For example, when -## using [F64], running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125, -## whereas when using [Dec], 0.1 + 0.2 returns 0.3. -## -## Under the hood, a [Dec] is an [I128], and operations on it perform -## [base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) -## with 18 decimal places of precision. -## -## This means a [Dec] can represent whole numbers up to slightly over 170 -## quintillion, along with 18 decimal places. (To be precise, it can store -## numbers betwween `-170_141_183_460_469_231_731.687303715884105728` -## and `170_141_183_460_469_231_731.687303715884105727`.) Why 18 -## decimal places? It's the highest number of decimal places where you can still -## convert any [U64] to a [Dec] without losing information. -## -## There are some use cases where [F64] and [F32] can be better choices than [Dec] -## despite their precision issues. For example, in graphical applications they -## can be a better choice for representing coordinates because they take up -## less memory, certain relevant calculations run faster (see performance -## details, below), and decimal precision loss isn't as big a concern when -## dealing with screen coordinates as it is when dealing with currency. -## -## ## Performance -## -## [Dec] typically takes slightly less time than [F64] to perform addition and -## subtraction, but 10-20 times longer to perform multiplication and division. -## [sqrt] and trigonometry are massively slower with [Dec] than with [F64]. -Dec : Float [ @Decimal128 ] - -## A fixed-size number with a fractional component. -## -## Roc fractions come in two flavors: fixed-point base-10 and floating-point base-2. -## -## * [Dec] is a 128-bit [fixed-point](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) base-10 number. It's a great default choice, especially when precision is important - for example when representing currency. With [Dec], 0.1 + 0.2 returns 0.3. -## * [F64] and [F32] are [floating-point](https://en.wikipedia.org/wiki/Floating-point_arithmetic) base-2 numbers. They sacrifice precision for lower memory usage and improved performance on some operations. This makes them a good fit for representing graphical coordinates. With [F64], 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125. -## -## If you don't specify a type, Roc will default to using [Dec] because it's -## the least error-prone overall. For example, suppose you write this: -## -## wasItPrecise = 0.1 + 0.2 == 0.3 -## -## The value of `wasItPrecise` here will be `True`, because Roc uses [Dec] -## by default when there are no types specified. -## -## In contrast, suppose we use `f32` or `f64` for one of these numbers: -## -## wasItPrecise = 0.1f64 + 0.2 == 0.3 -## -## Here, `wasItPrecise` will be `False` because the entire calculation will have -## been done in a base-2 floating point calculation, which causes noticeable -## precision loss in this case. -## -## The floating-point numbers ([F32] and [F64]) also have three values which -## are not ordinary [finite numbers](https://en.wikipedia.org/wiki/Finite_number). -## They are: -## * ∞ ([infinity](https://en.wikipedia.org/wiki/Infinity)) -## * -∞ (negative infinity) -## * *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)) -## -## These values are different from ordinary numbers in that they only occur -## when a floating-point calculation encounters an error. For example: -## * Dividing a positive [F64] by `0.0` returns ∞. -## * Dividing a negative [F64] by `0.0` returns -∞. -## * Dividing a [F64] of `0.0` by `0.0` returns [*NaN*](Num.isNaN). -## -## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) -## floating point standard. Because almost all modern processors are built to -## this standard, deviating from these rules has a significant performance -## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is -## access to hardware-accelerated performance, Roc follows these rules exactly. -## -## There's no literal syntax for these error values, but you can check to see if -## you ended up with one of them by using [isNaN], [isFinite], and [isInfinite]. -## Whenever a function in this module could return one of these values, that -## possibility is noted in the function's documentation. -## -## ## Performance Notes -## -## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32] -## for addition and subtraction. For example, [F32] and [F64] do addition using -## a single CPU floating-point addition instruction, which typically takes a -## few clock cycles to complete. In contrast, [Dec] does addition using a few -## CPU integer arithmetic instructions, each of which typically takes only one -## clock cycle to complete. Exact numbers will vary by CPU, but they should be -## similar overall. -## -## [Dec] is significantly slower for multiplication and division. It not only -## needs to do more arithmetic instructions than [F32] and [F64] do, but also -## those instructions typically take more clock cycles to complete. -## -## With [Num.sqrt] and trigonometry functions like [Num.cos], there is -## an even bigger performance difference. [F32] and [F64] can do these in a -## single instruction, whereas [Dec] needs entire custom procedures - which use -## loops and conditionals. If you need to do performance-critical trigonometry -## or square roots, either [F64] or [F32] is probably a better choice than the -## usual default choice of [Dec], despite the precision problems they bring. -Float a : Num [ @Fraction a ] - -## A fixed-size integer - that is, a number with no fractional component. -## -## Integers come in two flavors: signed and unsigned. Signed integers can be -## negative ("signed" refers to how they can incorporate a minus sign), -## whereas unsigned integers cannot be negative. -## -## Since integers have a fixed size, the size you choose determines both the -## range of numbers it can represent, and also how much memory it takes up. -## -## #U8 is an an example of an integer. It is an unsigned #Int that takes up 8 bits -## (aka 1 byte) in memory. The `U` is for Unsigned and the 8 is for 8 bits. -## Because it has 8 bits to work with, it can store 256 numbers (2^8), -## and because it is unsigned, its min value is 0. This means the 256 numbers -## it can store range from 0 to 255. -## -## #I8 is a signed integer that takes up 8 bits. The `I` is for Integer, since -## integers in mathematics are signed by default. Because it has 8 bits just -## like #U8, it can store 256 numbers (still 2^16), but because it is signed, -## the range is different. Its 256 numbers range from -128 to 127. -## -## Here are some other examples: -## -## * #U16 is like #U8, except it takes up 16 bytes in memory. It can store 65,536 numbers (2^16), ranging from 0 to 65,536. -## * #I16 is like #U16, except it is signed. It can still store the same 65,536 numbers (2^16), ranging from -32,768 to 32,767. -## -## This pattern continues up to #U128 and #I128. -## -## ## Performance notes -## -## In general, using smaller numeric sizes means your program will use less memory. -## However, if a mathematical operation results in an answer that is too big -## or too small to fit in the size available for that answer (which is typically -## the same size as the inputs), then you'll get an overflow error. -## -## As such, minimizing memory usage without causing overflows involves choosing -## number sizes based on your knowledge of what numbers you expect your program -## to encounter at runtime. -## -## Minimizing memory usage does not imply maximum runtime speed! -## CPUs are typically fastest at performing integer operations on integers that -## are the same size as that CPU's native machine word size. That means a 64-bit -## CPU is typically fastest at executing instructions on #U64 and #I64 values, -## whereas a 32-bit CPU is typically fastest on #U32 and #I32 values. -## -## Putting these factors together, here are some reasonable guidelines for optimizing performance through integer size choice: -## -## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly. -## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.) -## * Finally, if a particular numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds. -Int size : Num [ @Integer size ] - -## A signed 8-bit integer, ranging from -128 to 127 -I8 : Int [ @Signed8 ] -U8 : Int [ @Unsigned8 ] -I16 : Int [ @Signed16 ] -U16 : Int [ @Unsigned16 ] -I32 : Int [ @Signed32 ] -U32 : Int [ @Unsigned32 ] -I64 : Int [ @Signed64 ] -U64 : Int [ @Unsigned64 ] -I128 : Int [ @Signed128 ] -U128 : Int [ @Unsigned128 ] - -## A [natural number](https://en.wikipedia.org/wiki/Natural_number) represented -## as a 64-bit unsigned integer on 64-bit systems, a 32-bit unsigned integer -## on 32-bit systems, and so on. -## -## This system-specific size makes it useful for certain data structure -## functions like [List.len], because the number of elements many data strucures -## can hold is also system-specific. For example, the maximum number of elements -## a [List] can hold on a 64-bit system fits in a 64-bit unsigned integer, and -## on a 32-bit system it fits in 32-bit unsigned integer. This makes [Nat] a -## good fit for [List.len] regardless of system. -Nat : Int [ @Natural ] - -## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values. -## -## >>> 1 -## -## >>> 0 -## -## You can optionally put underscores in your #Int literals. -## They have no effect on the number's value, but can make large numbers easier to read. -## -## >>> 1_000_000 -## -## Integers come in two flavors: *signed* and *unsigned*. -## -## * *Unsigned* integers can never be negative. The lowest value they can hold is zero. -## * *Signed* integers can be negative. -## -## Integers also come in different sizes. Choosing a size depends on your performance -## needs and the range of numbers you need to represent. At a high level, the -## general trade-offs are: -## -## * Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! -## * Smaller integer sizes take up less memory. This savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck. -## * Certain CPUs work faster on some numeric sizes than others. If the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly! -## -## Here are the different fixed size integer types: -## -## | Range | Type | Size | -## |--------------------------------------------------------|-------|----------| -## | ` -128` | [I8] | 1 Byte | -## | ` 127` | | | -## |--------------------------------------------------------|-------|----------| -## | ` 0` | [U8] | 1 Byte | -## | ` 255` | | | -## |--------------------------------------------------------|-------|----------| -## | ` -32_768` | [I16] | 2 Bytes | -## | ` 32_767` | | | -## |--------------------------------------------------------|-------|----------| -## | ` 0` | [U16] | 2 Bytes | -## | ` 65_535` | | | -## |--------------------------------------------------------|-------|----------| -## | ` -2_147_483_648` | [I32] | 4 Bytes | -## | ` 2_147_483_647` | | | -## |--------------------------------------------------------|-------|----------| -## | ` 0` | [U32] | 4 Bytes | -## | ` (over 4 billion) 4_294_967_295` | | | -## |--------------------------------------------------------|-------|----------| -## | ` -9_223_372_036_854_775_808` | [I64] | 8 Bytes | -## | ` 9_223_372_036_854_775_807` | | | -## |--------------------------------------------------------|-------|----------| -## | ` 0` | [U64] | 8 Bytes | -## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | -## |--------------------------------------------------------|-------|----------| -## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | [I128]| 16 Bytes | -## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | -## |--------------------------------------------------------|-------|----------| -## | ` (over 340 undecillion) 0` | [U128]| 16 Bytes | -## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | -## -## Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal -## to the size of a memory address, which varies by system. For example, when -## compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a -## 32-bit system, it's the same as [U32]. -## -## A common use for [Nat] is to store the length ("len" for short) of a -## collection like a [List]. 64-bit systems can represent longer -## lists in memory than 32-bit systems can, which is why the length of a list -## is represented as a [Nat] in Roc. -## -## If any operation would result in an [Int] that is either too big -## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`), -## then the operation will *overflow*. When an overflow occurs, the program will crash. -## -## As such, it's very important to design your code not to exceed these bounds! -## If you need to do math outside these bounds, consider using a larger numeric size. -Int size : Num [ @Int size ] - -## Convert - -## Return a negative number when given a positive one, and vice versa. -## -## >>> Num.neg 5 -## -## >>> Num.neg -2.5 -## -## >>> Num.neg 0 -## -## >>> Num.neg 0.0 -## -## This is safe to use with any [Float], but it can cause overflow when used with certain #Int values. -## -## For example, calling #Num.neg on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow. -## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than -## the highest value it can represent. (For this reason, calling #Num.abs on the lowest signed value will also cause overflow.) -## -## Additionally, calling #Num.neg on any unsigned integer (such as any #U64 or #U32 value) other than zero will cause overflow. -## -## (It will never crash when given a [Float], however, because of how floating point numbers represent positive and negative numbers.) -neg : Num a -> Num a - -## Return the absolute value of the number. -## -## * For a positive number, returns the same number. -## * For a negative number, returns the same number except positive. -## * For zero, returns zero. -## -## >>> Num.abs 4 -## -## >>> Num.abs -2.5 -## -## >>> Num.abs 0 -## -## >>> Num.abs 0.0 -## -## This is safe to use with any [Float], but it can cause overflow when used with certain #Int values. -## -## For example, calling #Num.abs on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow. -## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than -## the highest value it can represent. (For this reason, calling #Num.neg on the lowest signed value will also cause overflow.) -## -## Calling this on an unsigned integer (like #U32 or #U64) never does anything. -abs : Num a -> Num a - -## Check - -## The same as using `== 0` on the number. -isZero : Num * -> Bool - -## Positive numbers are greater than 0. -isPositive : Num * -> Bool - -## Negative numbers are less than 0. -isNegative : Num * -> Bool - -## A number is even if dividing it by 2 gives a remainder of 0. -## -## Examples of even numbers: 0, 2, 4, 6, 8, -2, -4, -6, -8 -isEven : Num * -> Bool - -## A number is odd if dividing it by 2 gives a remainder of 1. -## -## Examples of odd numbers: 1, 3, 5, 7, -1, -3, -5, -7 -isOdd : Num * -> Bool - -## Arithmetic - -## Add two numbers of the same type. -## -## (To add an #Int and a [Float], first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to [Float] and the other way around.) -## -## `a + b` is shorthand for `Num.add a b`. -## -## >>> 5 + 7 -## -## >>> Num.add 5 7 -## -## `Num.add` can be convenient in pipelines. -## -## >>> Frac.pi -## >>> |> Num.add 1.0 -## -## If the answer to this operation can't fit in the return value (e.g. an -## [I8] answer that's higher than 127 or lower than -128), the result is an -## *overflow*. For [F64] and [F32], overflow results in an answer of either -## ∞ or -∞. For all other number types, overflow results in a panic. -add : Num a, Num a -> Num a - -## Add two numbers and check for overflow. -## -## This is the same as [Num.add] except if the operation overflows, instead of -## panicking or returning ∞ or -∞, it will return `Err Overflow`. -addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* - -## Add two numbers, clamping on the maximum representable number rather than -## overflowing. -## -## This is the same as [Num.add] except for the saturating behavior if the -## addition is to overflow. -## For example, if `x : U8` is 200 and `y : U8` is 100, `addSaturated x y` will -## yield 255, the maximum value of a `U8`. -addSaturated : Num a, Num a -> Num a - -## Subtract two numbers of the same type. -## -## (To subtract an #Int and a [Float], first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to [Float] and the other way around.) -## -## `a - b` is shorthand for `Num.sub a b`. -## -## >>> 7 - 5 -## -## >>> Num.sub 7 5 -## -## `Num.sub` can be convenient in pipelines. -## -## >>> Frac.pi -## >>> |> Num.sub 2.0 -## -## If the answer to this operation can't fit in the return value (e.g. an -## [I8] answer that's higher than 127 or lower than -128), the result is an -## *overflow*. For [F64] and [F32], overflow results in an answer of either -## ∞ or -∞. For all other number types, overflow results in a panic. -sub : Num a, Num a -> Num a - -## Subtract two numbers and check for overflow. -## -## This is the same as [Num.sub] except if the operation overflows, instead of -## panicking or returning ∞ or -∞, it will return `Err Overflow`. -subChecked : Num a, Num a -> Result (Num a) [ Overflow ]* - -## Subtract two numbers, clamping on the minimum representable number rather -## than overflowing. -## -## This is the same as [Num.sub] except for the saturating behavior if the -## subtraction is to overflow. -## For example, if `x : U8` is 10 and `y : U8` is 20, `subSaturated x y` will -## yield 0, the minimum value of a `U8`. -subSaturated : Num a, Num a -> Num a - -## Multiply two numbers of the same type. -## -## (To multiply an #Int and a [Float], first convert one so that they both have the same type. There are functions in the [`Frac`](/Frac) module that can convert both #Int to [Float] and the other way around.) -## -## `a * b` is shorthand for `Num.mul a b`. -## -## >>> 5 * 7 -## -## >>> Num.mul 5 7 -## -## `Num.mul` can be convenient in pipelines. -## -## >>> Frac.pi -## >>> |> Num.mul 2.0 -## -## If the answer to this operation can't fit in the return value (e.g. an -## [I8] answer that's higher than 127 or lower than -128), the result is an -## *overflow*. For [F64] and [F32], overflow results in an answer of either -## ∞ or -∞. For all other number types, overflow results in a panic. -mul : Num a, Num a -> Num a - -## Multiply two numbers and check for overflow. -## -## This is the same as [Num.mul] except if the operation overflows, instead of -## panicking or returning ∞ or -∞, it will return `Err Overflow`. -mulCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* - -## Convert - -## Convert any [Int] to a specifically-sized [Int], without checking validity. -## These are unchecked bitwise operations, -## so if the source number is outside the target range, then these will -## effectively modulo-wrap around the target range to reach a valid value. -toI8 : Int * -> I8 -toI16 : Int * -> I16 -toI32 : Int * -> I32 -toI64 : Int * -> I64 -toI128 : Int * -> I128 -toU8 : Int * -> U8 -toU16 : Int * -> U16 -toU32 : Int * -> U32 -toU64 : Int * -> U64 -toU128 : Int * -> U128 - -## Convert a [Num] to a [F32]. If the given number can't be precisely represented in a [F32], -## there will be a loss of precision. -toF32 : Num * -> F32 - -## Convert a [Num] to a [F64]. If the given number can't be precisely represented in a [F64], -## there will be a loss of precision. -toF64 : Num * -> F64 - -## Convert any [Int] to a specifically-sized [Int], after checking validity. -## These are checked bitwise operations, -## so if the source number is outside the target range, then these will -## return `Err OutOfBounds`. -toI8Checked : Int * -> Result I8 [ OutOfBounds ]* -toI16Checked : Int * -> Result I16 [ OutOfBounds ]* -toI32Checked : Int * -> Result I32 [ OutOfBounds ]* -toI64Checked : Int * -> Result I64 [ OutOfBounds ]* -toI128Checked : Int * -> Result I128 [ OutOfBounds ]* -toU8Checked : Int * -> Result U8 [ OutOfBounds ]* -toU16Checked : Int * -> Result U16 [ OutOfBounds ]* -toU32Checked : Int * -> Result U32 [ OutOfBounds ]* -toU64Checked : Int * -> Result U64 [ OutOfBounds ]* -toU128Checked : Int * -> Result U128 [ OutOfBounds ]* - -toF32Checked : Num * -> Result F32 [ OutOfBounds ]* -toF64Checked : Num * -> Result F64 [ OutOfBounds ]* - -## Convert a number to a [Str]. -## -## This is the same as calling `Num.format {}` - so for more details on -## exact formatting, see [Num.format]. -## -## >>> Num.toStr 42 -## -## Only [Float] values will include a decimal point, and they will always include one. -## -## >>> Num.toStr 4.2 -## -## >>> Num.toStr 4.0 -## -## When this function is given a non-[finite](Num.isFinite) -## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`. -## -## To get strings in hexadecimal, octal, or binary format, use [Num.format]. -toStr : Num * -> Str - -## Convert a number into a [Str], formatted with the given options. -## -## Default options: -## * `base: Decimal` -## * `notation: Standard` -## * `decimalMark: HideForIntegers "."` -## * `decimalDigits: { min: 0, max: All }` -## * `minIntDigits: 1` -## * `wholeSep: { mark: ",", places: 3 }` -## -## ## Options -## -## -## ### decimalMark -## -## * `AlwaysShow` always shows the decimal mark, no matter what. -## * `HideForIntegers` hides the decimal mark if all the numbers after the decimal mark are 0. -## -## The [Str] included in either of these represents the mark itself. -## -## ### `decimalDigits -## -## With 0 decimal digits, the decimal mark will still be rendered if -## `decimalMark` is set to `AlwaysShow`. -## -## If `max` is less than `min`, then first the number will be truncated to `max` -## digits, and then zeroes will be added afterwards until it reaches `min` digits. -## -## >>> Num.format 1.23 { decPlaces: 0, decPointVis: AlwaysShow } -## -## ### minIntDigits -## -## If the integer portion of number is fewer than this many digits, zeroes will -## be added in front of it until there are at least `minWholeDigits` digits. -## -## If this is set to zero, then numbers less than 1 will begin with `"."` -## rather than `"0."`. -## -## ### wholeSep -## -## Examples: -## -## In some countries (e.g. USA and UK), a comma is used to separate thousands: -## >>> Num.format 1_000_000 { pf: Decimal, wholeSep: { mark: ",", places: 3 } } -## -## Sometimes when rendering bits, it's nice to group them into groups of 4: -## >>> Num.format 1_000_000 { pf: Binary, wholeSep: { mark: " ", places: 4 } } -## -## It's also common to render hexadecimal in groups of 2: -## >>> Num.format 1_000_000 { pf: Hexadecimal, wholeSep: { mark: " ", places: 2 } } -format : - Num *, - { - base ? [ Decimal, Hexadecimal, Octal, Binary ], - notation ? [ Standard, Scientific ], - decimalMark ? [ AlwaysShow Str, HideForIntegers ], - decimalDigits ? { min : U16, max : [ All, Trunc U16, Round U16, Floor U16, Ceil U16 ] }, - minWholeDigits ? U16, - wholeSep ? { mark : Str, places : U64 } - } - -> Str - -## Round off the given float to the nearest integer. -round : Float * -> Int * -ceil : Float * -> Int * -floor : Float * -> Int * -trunc : Float * -> Int * - -## Convert an #Int to a #Nat. If the given number doesn't fit in #Nat, it will be truncated. -## Since #Nat has a different maximum number depending on the system you're building -## for, this may give a different answer on different systems. -## -## For example, on a 32-bit system, [Num.maxNat] will return the same answer as -## #Num.maxU32. This means that calling `Num.toNat 9_000_000_000` on a 32-bit -## system will return #Num.maxU32 instead of 9 billion, because 9 billion is -## higher than #Num.maxU32 and will not fit in a #Nat on a 32-bit system. -## -## However, calling `Num.toNat 9_000_000_000` on a 64-bit system will return -## the #Nat value of 9_000_000_000. This is because on a 64-bit system, #Nat can -## hold up to #Num.maxU64, and 9_000_000_000 is lower than #Num.maxU64. -## -## To convert a [Float] to a #Nat, first call either #Num.round, #Num.ceil, or #Num.floor -## on it, then call this on the resulting #Int. -toNat : Int * -> Nat - -## Convert an #Int to an #I8. If the given number doesn't fit in #I8, it will be truncated. -## -## To convert a [Float] to an #I8, first call either #Num.round, #Num.ceil, or #Num.floor -## on it, then call this on the resulting #Int. -toI8 : Int * -> I8 -toI16 : Int * -> I16 -toI32 : Int * -> I32 -toI64 : Int * -> I64 -toI128 : Int * -> I128 - -## Convert an #Int to an #U8. If the given number doesn't fit in #U8, it will be truncated. -## Crashes if the given number is negative. -toU8 : Int * -> U8 -toU16 : Int * -> U16 -toU32 : Int * -> U32 -toU64 : Int * -> U64 -toU128 : Int * -> U128 - -## Convert a #Num to a #Dec. If the given number can't be precisely represented in a #Dec, -## there will be a loss of precision. -toDec : Num * -> Dec - -## Divide two integers, truncating the result towards zero. -## -## Division by zero is undefined in mathematics. As such, you should make -## sure never to pass zero as the denomaintor to this function! -## -## If zero does get passed as the denominator... -## -## * In a development build, you'll get an assertion failure. -## * In an optimized build, the function will return 0. -## -## `a // b` is shorthand for `Num.divTrunc a b`. -## -## >>> 5 // 7 -## -## >>> Num.divTrunc 5 7 -## -## >>> 8 // -3 -## -## >>> Num.divTrunc 8 -3 -## -## This is the same as the #// operator. -divTrunc : Int a, Int a -> Int a - -## Perform flooring modulo on two integers. -## -## Modulo is the same as remainder when working with positive numbers, -## but if either number is negative, then modulo works differently. -## -## Additionally, flooring modulo uses [Float].floor on the result. -## -## (Use [Float].mod for non-flooring modulo.) -## -## Return `Err DivByZero` if the second integer is zero, because division by zero is undefined in mathematics. -## -## `a %% b` is shorthand for `Int.modFloor a b`. -## -## >>> 5 %% 7 -## -## >>> Int.modFloor 5 7 -## -## >>> -8 %% -3 -## -## >>> Int.modFloor -8 -3 -#modFloor : Int a, Int a -> Result (Int a) [ DivByZero ]* - - -## Bitwise - -xor : Int a, Int a -> Int a - -and : Int a, Int a -> Int a - -not : Int a -> Int a - -## Limits - -## The lowest number that can be stored in a #Nat without underflowing its -## available memory and crashing. -## -## For reference, this is the number zero, because #Nat is -## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. -## Unsigned numbers cannot be negative. -minNat : Nat - -## The highest number that can be stored in a #Nat without overflowing its -## available memory and crashing. -## -## Note that this number varies by systems. For example, when building for a -## 64-bit system, this will be equal to #Num.maxU64, but when building for a -## 32-bit system, this will be equal to #Num.maxU32. -maxNat : Nat - -## The lowest number that can be stored in an #I8 without underflowing its -## available memory and crashing. -## -## For reference, this number is `-128`. -## -## Note that the positive version of this number is larger than #Int.maxI8, -## which means if you call #Num.abs on #Int.minI8, it will overflow and crash! -minI8 : I8 - -## The highest number that can be stored in an #I8 without overflowing its -## available memory and crashing. -## -## For reference, this number is `127`. -## -## Note that this is smaller than the positive version of #Int.minI8, -## which means if you call #Num.abs on #Int.minI8, it will overflow and crash! -maxI8 : I8 - -## The lowest number that can be stored in a #U8 without underflowing its -## available memory and crashing. -## -## For reference, this number is zero, because #U8 is -## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. -## Unsigned numbers cannot be negative. -minU8 : U8 - -## The highest number that can be stored in a #U8 without overflowing its -## available memory and crashing. -## -## For reference, this number is `255`. -maxU8 : U8 - -## The lowest number that can be stored in an #I16 without underflowing its -## available memory and crashing. -## -## For reference, this number is `-32_768`. -## -## Note that the positive version of this number is larger than #Int.maxI16, -## which means if you call #Num.abs on #Int.minI16, it will overflow and crash! -minI16 : I16 - -## The highest number that can be stored in an #I16 without overflowing its -## available memory and crashing. -## -## For reference, this number is `32_767`. -## -## Note that this is smaller than the positive version of #Int.minI16, -## which means if you call #Num.abs on #Int.minI16, it will overflow and crash! -maxI16 : I16 - -## The lowest number that can be stored in a #U16 without underflowing its -## available memory and crashing. -## -## For reference, this number is zero, because #U16 is -## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. -## Unsigned numbers cannot be negative. -minU16 : U16 - -## The highest number that can be stored in a #U16 without overflowing its -## available memory and crashing. -## -## For reference, this number is `65_535`. -maxU16 : U16 - -## The lowest number that can be stored in an #I32 without underflowing its -## available memory and crashing. -## -## For reference, this number is `-2_147_483_648`. -## -## Note that the positive version of this number is larger than #Int.maxI32, -## which means if you call #Num.abs on #Int.minI32, it will overflow and crash! -minI32 : I32 - -## The highest number that can be stored in an #I32 without overflowing its -## available memory and crashing. -## -## For reference, this number is `2_147_483_647`, -## which is over 2 million. -## -## Note that this is smaller than the positive version of #Int.minI32, -## which means if you call #Num.abs on #Int.minI32, it will overflow and crash! -maxI32 : I32 - -## The lowest number that can be stored in a #U32 without underflowing its -## available memory and crashing. -## -## For reference, this number is zero, because #U32 is -## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. -## Unsigned numbers cannot be negative. -minU32 : U32 - -## The highest number that can be stored in a #U32 without overflowing its -## available memory and crashing. -## -## For reference, this number is `4_294_967_295`, -## which is over 4 million. -maxU32 : U32 - -## The min number that can be stored in an #I64 without underflowing its -## available memory and crashing. -## -## For reference, this number is `-`. -## -## Note that the positive version of this number is larger than #Int.maxI64, -## which means if you call #Num.abs on #Int.minI64, it will overflow and crash! -minI64 : I64 - -## The highest number that can be stored in an #I64 without overflowing its -## available memory and crashing. -## -## For reference, this number is ``, -## which is over 2 million. -## -## Note that this is smaller than the positive version of #Int.minI64, -## which means if you call #Num.abs on #Int.minI64, it will overflow and crash! -maxI64 : I64 - -## The lowest number that can be stored in a #U64 without underflowing its -## available memory and crashing. -## -## For reference, this number is zero because #U64 is -## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), -## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. -minU64 : U64 - -## The highest number that can be stored in a #U64 without overflowing its -## available memory and crashing. -## -## For reference, this number is `18_446_744_073_709_551_615`, -## which is over 18 quintillion. -maxU64 : U64 - -## The lowest number that can be stored in an #I128 without underflowing its -## available memory and crashing. -## -## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`. -## -## Note that the positive version of this number is larger than #Int.maxI128, -## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! -minI128 : I128 - -## The highest number that can be stored in an #I128 without overflowing its -## available memory and crashing. -## -## For reference, this number is `170_141_183_460_469_231_731_687_303_715_884_105_727`, -## which is over 2 million. -## -## Note that this is smaller than the positive version of #Int.minI128, -## which means if you call #Num.abs on #Int.minI128, it will overflow and crash! -maxI128 : I128 - -## The lowest supported #F32 value you can have, which is approximately -1.8 × 10^308. -## -## If you go lower than this, your running Roc code will crash - so be careful not to! -minF32 : F32 - -## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308. -## -## If you go higher than this, your running Roc code will crash - so be careful not to! -maxF32 : F32 - -## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308. -## -## If you go lower than this, your running Roc code will crash - so be careful not to! -minF64 : F64 - -## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. -## -## If you go higher than this, your running Roc code will crash - so be careful not to! -maxF64 : F64 - -## The lowest supported #Dec value you can have, -## which is precisely -170_141_183_460_469_231_731.687303715884105728. -## -## If you go lower than this, your running Roc code will crash - so be careful not to! -minDec : Dec - -## The highest supported #Dec value you can have, -## which is precisely 170_141_183_460_469_231_731.687303715884105727. -## -## If you go higher than this, your running Roc code will crash - so be careful not to! -maxDec : Dec - -## Constants - -## An approximation of e, specifically 2.718281828459045. -e : Float * - -## An approximation of pi, specifically 3.141592653589793. -pi : Float * - -## Trigonometry - -cos : Float a -> Float a - -acos : Float a -> Float a - -sin : Float a -> Float a - -asin : Float a -> Float a - -tan : Float a -> Float a - -atan : Float a -> Float a - -## Other Calculations (arithmetic?) - -## Divide one [Float] by another. -## -## `a / b` is shorthand for `Num.div a b`. -## -## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero). -## As such, you should make sure never to pass zero as the denomaintor to this function! -## Calling [div] on a [Dec] denominator of zero will cause a panic. -## -## Calling [div] on [F32] and [F64] values follows these rules: -## * Dividing a positive [F64] or [F32] by zero returns ∞. -## * Dividing a negative [F64] or [F32] by zero returns -∞. -## * Dividing a zero [F64] or [F32] by zero returns [*NaN*](Num.isNaN). -## -## > These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) -## > floating point standard. Because almost all modern processors are built to -## > this standard, deviating from these rules has a significant performance -## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is -## > access to hardware-accelerated performance, Roc follows these rules exactly. -## -## To divide an [Int] and a [Float], first convert the [Int] to a [Float] using -## one of the functions in this module like [toDec]. -## -## >>> 5.0 / 7.0 -## -## >>> Num.div 5 7 -## -## `Num.div` can be convenient in pipelines. -## -## >>> Num.pi -## >>> |> Num.div 2.0 -div : Float a, Float a -> Float a - -## Perform modulo on two [Float]s. -## -## Modulo is the same as remainder when working with positive numbers, -## but if either number is negative, then modulo works differently. -## -## `a % b` is shorthand for `Num.mod a b`. -## -## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero), -## and as such, so is modulo by zero. Because of this, you should make sure never -## to pass zero for the second argument to this function! -## -## Passing [mod] a [Dec] value of zero for its second argument will cause a panic. -## Passing [mod] a [F32] and [F64] value for its second argument will cause it -## to return [*NaN*](Num.isNaN). -## -## >>> 5.0 % 7.0 -## -## >>> Num.mod 5 7 -## -## `Num.mod` can be convenient in pipelines. -## -## >>> Num.pi -## >>> |> Num.mod 2.0 -mod : Float a, Float a -> Float a - -## Raises a [Float] to the power of another [Float]. -## -## ` -## For an #Int alternative to this function, see #Num.raise. -pow : Float a, Float a -> Float a - -## Raises an integer to the power of another, by multiplying the integer by -## itself the given number of times. -## -## This process is known as [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). -## -## For a [Float] alternative to this function, which supports negative exponents, -## see #Num.exp. -## -## >>> Num.exp 5 0 -## -## >>> Num.exp 5 1 -## -## >>> Num.exp 5 2 -## -## >>> Num.exp 5 6 -## -## ## Performance Notes -## -## Be careful! Even though this function takes only a #U8, it is very easy to -## overflow -expBySquaring : Int a, U8 -> Int a - -## Returns an approximation of the absolute value of a [Float]'s square root. -## -## The square root of a negative number is an irrational number, and [Float] only -## supports rational numbers. As such, you should make sure never to pass this -## function a negative number! Calling [sqrt] on a negative [Dec] will cause a panic. -## -## Calling [sqrt] on [F32] and [F64] values follows these rules: -## * Passing a negative [F64] or [F32] returns [*NaN*](Num.isNaN). -## * Passing [*NaN*](Num.isNaN) or -∞ also returns [*NaN*](Num.isNaN). -## * Passing ∞ returns ∞. -## -## > These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) -## > floating point standard. Because almost all modern processors are built to -## > this standard, deviating from these rules has a significant performance -## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is -## > access to hardware-accelerated performance, Roc follows these rules exactly. -## -## >>> Frac.sqrt 4.0 -## -## >>> Frac.sqrt 1.5 -## -## >>> Frac.sqrt 0.0 -## -## >>> Frac.sqrt -4.0f64 -## -## >>> Frac.sqrt -4.0dec -sqrt : Float a -> Float a - -## Bit shifts - -## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) left. -## -## `a << b` is shorthand for `Num.shl a b`. -shl : Int a, Int a -> Int a - -## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) left. -## -## This is called `shlWrap` because any bits shifted -## off the beginning of the number will be wrapped around to -## the end. (In contrast, [shl] replaces discarded bits with zeroes.) -shlWrap : Int a, Int a -> Int a - -## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) right. -## -## `a >> b` is shorthand for `Num.shr a b`. -shr : Int a, Int a -> Int a - -## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right. -## -## This is called `shrWrap` because any bits shifted -## off the end of the number will be wrapped around to -## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.) -shrWrap : Int a, Int a -> Int a - -## [Endianness](https://en.wikipedia.org/wiki/Endianness) -# Endi : [ Big, Little, Native ] - -## The `Endi` argument does not matter for [U8] and [I8], since they have -## only one byte. -# toBytes : Num *, Endi -> List U8 - -## when Num.parseBytes bytes Big is -## Ok { val: f64, rest } -> ... -## Err (ExpectedNum (Float Binary64)) -> ... -# parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ ExpectedNum a ]* - -## when Num.fromBytes bytes Big is -## Ok f64 -> ... -## Err (ExpectedNum (Float Binary64)) -> ... -# fromBytes : List U8, Endi -> Result (Num a) [ ExpectedNum a ]* - -## Comparison - -## Returns `True` if the first number is less than the second. -## -## `a < b` is shorthand for `Num.isLt a b`. -## -## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* -## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -## -## >>> 5 -## >>> |> Num.isLt 6 -isLt : Num a, Num a -> Bool - -## Returns `True` if the first number is less than or equal to the second. -## -## `a <= b` is shorthand for `Num.isLte a b`. -## -## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* -## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -isLte : Num a, Num a -> Bool - -## Returns `True` if the first number is greater than the second. -## -## `a > b` is shorthand for `Num.isGt a b`. -## -## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* -## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -## -## >>> 6 -## >>> |> Num.isGt 5 -isGt : Num a, Num a -> Bool - -## Returns `True` if the first number is greater than or equal to the second. -## -## `a >= b` is shorthand for `Num.isGte a b`. -## -## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* -## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -isGte : Num a, Num a -> Bool - -## Returns the higher of two numbers. -## -## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* -## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -higher : Num a, Num a -> Num a - -## Returns the lower of two numbers. -## -## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* -## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -lower : Num a, Num a -> Num a - -# Branchless implementation that works for all numeric types: -# -# let is_lt = arg1 < arg2; -# let is_eq = arg1 == arg2; -# return (is_lt as i8 - is_eq as i8) + 1; -# -# 1, 1 -> (0 - 1) + 1 == 0 # Eq -# 5, 1 -> (0 - 0) + 1 == 1 # Gt -# 1, 5 -> (1 - 0) + 1 == 2 # Lt - -## Returns `Lt` if the first number is less than the second, `Gt` if -## the first is greater than the second, and `Eq` if they're equal. -## -## Although this can be passed to `List.sort`, you'll get better performance -## by using `List.sortAsc` or `List.sortDesc` instead. -compare : Num a, Num a -> [ Lt, Eq, Gt ] - -## Special Floating-Point Values - -## When given a [F64] or [F32] value, returns `False` if that value is -## [*NaN*](Num.isNaN), ∞ or -∞, and `True` otherwise. -## -## Always returns `True` when given a [Dec]. -## -## This is the opposite of [isInfinite], except when given [*NaN*](Num.isNaN). Both -## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). -isFinite : Float * -> Bool - -## When given a [F64] or [F32] value, returns `True` if that value is either -## ∞ or -∞, and `False` otherwise. -## -## Always returns `False` when given a [Dec]. -## -## This is the opposite of [isFinite], except when given [*NaN*](Num.isNaN). Both -## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). -isInfinite : Float * -> Bool - -## When given a [F64] or [F32] value, returns `True` if that value is -## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `False` otherwise. -## -## Always returns `False` when given a [Dec]. -## -## >>> Num.isNaN 12.3 -## -## >>> Num.isNaN (Num.sqrt -2) -## -## *NaN* is unusual from other numberic values in that: -## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `False` if either argument is *NaN*. -## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `False` if either argument is *NaN*. -## -## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) -## floating point standard. Because almost all modern processors are built to -## this standard, deviating from these rules has a significant performance -## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is -## access to hardware-accelerated performance, Roc follows these rules exactly. -## -## Note that you should never put a *NaN* into a [Set], or use it as the key in -## a [Dict]. The result is entries that can never be removed from those -## collections! See the documentation for [Set.add] and [Dict.insert] for details. -isNaN : Float * -> Bool diff --git a/compiler/builtins/docs/Result.roc b/compiler/builtins/docs/Result.roc deleted file mode 100644 index 0139029572..0000000000 --- a/compiler/builtins/docs/Result.roc +++ /dev/null @@ -1,67 +0,0 @@ -interface Result - exposes - [ - Result, - after, - isOk, - isErr, - map, - mapErr, - withDefault - ] - imports [] - -## The result of an operation that could fail: either the operation went -## okay, or else there was an error of some sort. -Result ok err : [ @Result ok err ] - -## Return True if the result indicates a success, else return False -## -## >>> Result.isOk (Ok 5) -isOk : Result * * -> bool - -## Return True if the result indicates a failure, else return False -## -## >>> Result.isErr (Err "uh oh") -isErr : Result * * -> bool - -## If the result is `Ok`, return the value it holds. Otherwise, return -## the given default value. -## -## >>> Result.withDefault (Ok 7) 42 -## -## >>> Result.withDefault (Err "uh oh") 42 -withDefault : Result ok err, ok -> ok - -## If the result is `Ok`, transform the entire result by running a conversion -## function on the value the `Ok` holds. Then return that new result. -## -## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.) -## -## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num -## -## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num -after : Result before err, (before -> Result after err) -> Result after err - -## If the result is `Ok`, transform the value it holds by running a conversion -## function on it. Then return a new `Ok` holding the transformed value. -## -## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.) -## -## >>> Result.map (Ok 12) Num.negate -## -## >>> Result.map (Err "yipes!") Num.negate -## -## `map` functions like this are common in Roc, and they all work similarly. -## See for example [List.map], `Set.map`, and `Dict.map`. -map : Result before err, (before -> after) -> Result after err - -## If the result is `Err`, transform the value it holds by running a conversion -## function on it. Then return a new `Err` holding the transformed value. -## -## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.) -## -## >>> Result.mapErr (Err "yipes!") Str.isEmpty -## -## >>> Result.mapErr (Ok 12) Str.isEmpty -mapErr : Result ok before, (before -> after) -> Result ok after diff --git a/compiler/builtins/docs/Set.roc b/compiler/builtins/docs/Set.roc deleted file mode 100644 index 98b5b3e286..0000000000 --- a/compiler/builtins/docs/Set.roc +++ /dev/null @@ -1,59 +0,0 @@ -interface Set - exposes - [ - Set, - contains, - difference, - empty, - fromList, - insert, - intersection, - len, - remove, - single, - toList, - union, - walk - ] - imports [] - -## A Set is an unordered collection of unique elements. -Set elem : [ @Set elem ] - -## An empty set. -empty : Set * - -## Check - -isEmpty : Set * -> Bool - -len : Set * -> Nat - -## Modify - -# TODO: removed `'` from signature because parser does not support it yet -# Original signature: `add : Set 'elem, 'elem -> Set 'elem` -## Make sure never to add a *NaN* to a [Set]! Because *NaN* is defined to be -## unequal to *NaN*, adding a *NaN* results in an entry that can never be -## retrieved or removed from the [Set]. -add : Set elem, elem -> Set elem - -## Drops the given element from the set. -# TODO: removed `'` from signature because parser does not support it yet -# Original signature: `drop : Set 'elem, 'elem -> Set 'elem` -drop : Set elem, elem -> Set elem - -## Transform - -## Convert each element in the set to something new, by calling a conversion -## function on each of them. Then return a new set of the converted values. -## -## >>> Set.map {: -1, 1, 3 :} Num.negate -## -## >>> Set.map {: "", "a", "bc" :} Str.isEmpty -## -## `map` functions like this are common in Roc, and they all work similarly. -## See for example [List.map], `Dict.map`, and [Result.map]. -# TODO: removed `'` from signature because parser does not support it yet -# Original signature: `map : Set 'elem, ('before -> 'after) -> Set 'after` -map : Set elem, (before -> after) -> Set after diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc deleted file mode 100644 index e67e4f234a..0000000000 --- a/compiler/builtins/docs/Str.roc +++ /dev/null @@ -1,470 +0,0 @@ -interface Str - exposes - [ - Str, - append, - concat, - countGraphemes, - endsWith, - fromUtf8, - isEmpty, - joinWith, - split, - startsWith, - startsWithCodePt, - toUtf8, - Utf8Problem, - Utf8ByteProblem - ] - imports [] - -## # Types -## -## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks -## to the basics. -## -## _For more advanced use cases like working with raw [code points](https://unicode.org/glossary/#code_point), -## see the [roc/unicode](roc/unicode) package. For locale-specific text -## functions (including uppercasing strings, as capitalization rules vary by locale; -## in English, `"i"` capitalizes to `"I"`, but [in Turkish](https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing), -## the same `"i"` capitalizes to `"İ"` - as well as sorting strings, which also varies -## by locale; `"ö"` is sorted differently in German and Swedish) see the [roc/locale](roc/locale) package._ -## -## ### Unicode -## -## Unicode can represent text values which span multiple languages, symbols, and emoji. -## Here are some valid Roc strings: -## -## "Roc!" -## "鹏" -## "🕊" -## -## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster). -## An extended grapheme cluster represents what a person reading a string might -## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦". -## Because the term "character" means different things in different areas of -## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the -## term "grapheme" as a shorthand for the more precise "extended grapheme cluster." -## -## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it: -## -## Str.countGraphemes "Roc!" -## Str.countGraphemes "折り紙" -## Str.countGraphemes "🕊" -## -## > The `countGraphemes` function walks through the entire string to get its answer, -## > so if you want to check whether a string is empty, you'll get much better performance -## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`. -## -## ### Escape sequences -## -## If you put a `\` in a Roc string literal, it begins an *escape sequence*. -## An escape sequence is a convenient way to insert certain strings into other strings. -## For example, suppose you write this Roc string: -## -## "I took the one less traveled by,\nAnd that has made all the difference." -## -## The `"\n"` in the middle will insert a line break into this string. There are -## other ways of getting a line break in there, but `"\n"` is the most common. -## -## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`. -## That would result in the same string, because the `\u` escape sequence inserts -## [Unicode code points](https://unicode.org/glossary/#code_point) directly into -## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal. -## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always -## followed by a hexadecimal literal inside `{` and `}` like this. -## -## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because -## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you -## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut), -## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\. -## -## Roc strings also support these escape sequences: -## -## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!) -## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string) -## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return) -## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters) -## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters) -## -## You can also use escape sequences to insert named strings into other strings, like so: -## -## name = "Lee" -## city = "Roctown" -## -## greeting = "Hello there, \(name)! Welcome to \(city)." -## -## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`. -## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation), -## and you can use it as many times as you like inside a string. The name -## between the parentheses must refer to a `Str` value that is currently in -## scope, and it must be a name - it can't be an arbitrary expression like a function call. -## -## ### Encoding -## -## Roc strings are not coupled to any particular -## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens, -## they are currently encoded in UTF-8, but this module is intentionally designed -## not to rely on that implementation detail so that a future release of Roc can -## potentially change it without breaking existing Roc applications. (UTF-8 -## seems pretty great today, but so did UTF-16 at an earlier point in history.) -## -## This module has functions to can convert a [Str] to a [List] of raw [code unit](https://unicode.org/glossary/#code_unit) -## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point) -## mentioned earlier) in a particular encoding. If you need encoding-specific functions, -## you should take a look at the [roc/unicode](roc/unicode) package. -## It has many more tools than this module does! - -## A [Unicode](https://unicode.org) text value. -Str : [ @Str ] - -## Convert - -## Convert a [Float] to a decimal string, rounding off to the given number of decimal places. -## -## If you want to keep all the digits, use [Str.num] instead. -decimal : Float *, Nat -> Str - - -## Convert a [Num] to a string. -num : Float *, Nat -> Str - -## Split a string around a separator. -## -## >>> Str.split "1,2,3" "," -## -## Passing `""` for the separator is not useful; it returns the original string -## wrapped in a list. -## -## >>> Str.split "1,2,3" "" -## -## To split a string into its individual graphemes, use `Str.graphemes` -split : Str, Str -> List Str - -## Split a string around newlines. -## -## On strings that use `"\n"` for their line endings, this gives the same answer -## as passing `"\n"` to [Str.split]. However, on strings that use `"\n\r"` (such -## as [in Windows files](https://en.wikipedia.org/wiki/Newline#History)), this -## will consume the entire `"\n\r"` instead of just the `"\n"`. -## -## >>> Str.lines "Hello, World!\nNice to meet you!" -## -## >>> Str.lines "Hello, World!\n\rNice to meet you!" -## -## To split a string using a custom separator, use [Str.split]. For more advanced -## string splitting, use a #Parser. -lines : Str, Str -> List Str - -## Check - -## Returns `True` if the string is empty, and `False` otherwise. -## -## >>> Str.isEmpty "hi!" -## -## >>> Str.isEmpty "" -isEmpty : Str -> Bool - -startsWith : Str, Str -> Bool - -## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point) -## equal to the given [U32], return `True`. Otherwise return `False`. -## -## If the given [Str] is empty, or if the given [U32] is not a valid -## code point, this will return `False`. -## -## **Performance Note:** This runs slightly faster than [Str.startsWith], so -## if you want to check whether a string begins with something that's representable -## in a single code point, you can use (for example) `Str.startsWithCodePt '鹏'` -## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32] -## value `40527`.) This will not work for graphemes which take up multiple code -## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error -## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a -## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead. -startsWithCodePt : Str, U32 -> Bool - -endsWith : Str, Str -> Bool - -contains : Str, Str -> Bool - -anyGraphemes : Str, (Str -> Bool) -> Bool - -allGraphemes : Str, (Str -> Bool) -> Bool - -## Combine - -## Combine a list of strings into a single string. -## -## >>> Str.join [ "a", "bc", "def" ] -join : List Str -> Str - -## Combine a list of strings into a single string, with a separator -## string in between each. -## -## >>> Str.joinWith [ "one", "two", "three" ] ", " -joinWith : List Str, Str -> Str - -## Add to the start of a string until it has at least the given number of -## graphemes. -## -## >>> Str.padGraphemesStart "0" 5 "36" -## -## >>> Str.padGraphemesStart "0" 1 "36" -## -## >>> Str.padGraphemesStart "0" 5 "12345" -## -## >>> Str.padGraphemesStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦" -padGraphemesStart : Str, Nat, Str -> Str - -## Add to the end of a string until it has at least the given number of -## graphemes. -## -## >>> Str.padGraphemesStart "0" 5 "36" -## -## >>> Str.padGraphemesStart "0" 1 "36" -## -## >>> Str.padGraphemesStart "0" 5 "12345" -## -## >>> Str.padGraphemesStart "✈️"" 5 "👩‍👩‍👦‍👦👩‍👩‍👦‍👦👩‍👩‍👦‍👦" -padGraphemesEnd : Str, Nat, Str -> Str - -## Graphemes - -## Split a string into its individual graphemes. -## -## >>> Str.graphemes "1,2,3" -## -## >>> Str.graphemes "👍👍👍" -## -graphemes : Str -> List Str - -## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster) -## in the string. -## -## Str.countGraphemes "Roc!" # 4 -## Str.countGraphemes "七巧板" # 3 -## Str.countGraphemes "🕊" # 1 -countGraphemes : Str -> Nat - -## Reverse the order of the string's individual graphemes. -## -## >>> Str.reverseGraphemes "1-2-3" -## -## >>> Str.reverseGraphemes "🐦✈️"👩‍👩‍👦‍👦" -## -## >>> Str.reversegraphemes "Crème Brûlée" -reverseGraphemes : Str -> Str - -## Returns `True` if the two strings are equal when ignoring case. -## -## >>> Str.caseInsensitiveEq "hi" "Hi" -isCaseInsensitiveEq : Str, Str -> Bool - -isCaseInsensitiveNeq : Str, Str -> Bool - -walkGraphemes : Str, { start: state, step: (state, Str -> state) } -> state -walkGraphemesUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state -walkGraphemesBackwards : Str, { start: state, step: (state, Str -> state) } -> state -walkGraphemesBackwardsUntil : Str, { start: state, step: (state, Str -> [ Continue state, Done state ]) } -> state - -## Returns `True` if the string begins with an uppercase letter. -## -## >>> Str.isCapitalized "Hi" -## -## >>> Str.isCapitalized " Hi" -## -## >>> Str.isCapitalized "hi" -## -## >>> Str.isCapitalized "Česká" -## -## >>> Str.isCapitalized "Э" -## -## >>> Str.isCapitalized "東京" -## -## >>> Str.isCapitalized "🐦" -## -## >>> Str.isCapitalized "" -## -## Since the rules for how to capitalize a string vary by locale, -## (for example, in English, `"i"` capitalizes to `"I"`, but -## [in Turkish](https://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing), -## the same `"i"` capitalizes to `"İ"`) see the [roc/locale](roc/locale) package -## package for functions which capitalize strings. -isCapitalized : Str -> Bool - -## Returns `True` if the string consists entirely of uppercase letters. -## -## >>> Str.isAllUppercase "hi" -## -## >>> Str.isAllUppercase "Hi" -## -## >>> Str.isAllUppercase "HI" -## -## >>> Str.isAllUppercase " Hi" -## -## >>> Str.isAllUppercase "Česká" -## -## >>> Str.isAllUppercase "Э" -## -## >>> Str.isAllUppercase "東京" -## -## >>> Str.isAllUppercase "🐦" -## -## >>> Str.isAllUppercase "" -isAllUppercase : Str -> Bool - -## Returns `True` if the string consists entirely of lowercase letters. -## -## >>> Str.isAllLowercase "hi" -## -## >>> Str.isAllLowercase "Hi" -## -## >>> Str.isAllLowercase "HI" -## -## >>> Str.isAllLowercase " Hi" -## -## >>> Str.isAllLowercase "Česká" -## -## >>> Str.isAllLowercase "Э" -## -## >>> Str.isAllLowercase "東京" -## -## >>> Str.isAllLowercase "🐦" -## -## >>> Str.isAllLowercase "" -isAllLowercase : Str -> Bool - -## Return the string with any blank spaces removed from both the beginning -## as well as the end. -trim : Str -> Str - -## If the given [U32] is a valid [Unicode Scalar Value](http://www.unicode.org/glossary/#unicode_scalar_value), -## return a [Str] containing only that scalar. -fromScalar : U32 -> Result Str [ BadScalar ]* -fromCodePts : List U32 -> Result Str [ BadCodePt U32 ]* -fromUtf8 : List U8 -> Result Str [ BadUtf8 ]* - -## Create a [Str] from bytes encoded as [UTF-16LE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes). -# fromUtf16Le : List U8 -> Result Str [ BadUtf16Le Endi ]* - -# ## Create a [Str] from bytes encoded as [UTF-16BE](https://en.wikipedia.org/wiki/UTF-16#Byte-order_encoding_schemes). -# fromUtf16Be : List U8 -> Result Str [ BadUtf16Be Endi ]* - -# ## Create a [Str] from bytes encoded as UTF-16 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark). -# fromUtf16Bom : List U8 -> Result Str [ BadUtf16 Endi, NoBom ]* - -# ## Create a [Str] from bytes encoded as [UTF-32LE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html) -# fromUtf32Le : List U8 -> Result Str [ BadUtf32Le Endi ]* - -# ## Create a [Str] from bytes encoded as [UTF-32BE](https://web.archive.org/web/20120322145307/http://mail.apps.ietf.org/ietf/charsets/msg01095.html) -# fromUtf32Be : List U8 -> Result Str [ BadUtf32Be Endi ]* - -# ## Create a [Str] from bytes encoded as UTF-32 with a [Byte Order Mark](https://en.wikipedia.org/wiki/Byte_order_mark). -# fromUtf32Bom : List U8 -> Result Str [ BadUtf32 Endi, NoBom ]* - -# ## Convert from UTF-8, substituting the replacement character ("�") for any -# ## invalid sequences encountered. -# fromUtf8Sub : List U8 -> Str -# fromUtf16Sub : List U8, Endi -> Str -# fromUtf16BomSub : List U8 -> Result Str [ NoBom ]* - -## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit). -## (To split the string into a [List] of smaller [Str] values instead of [U8] values, -## see [Str.split] and `Str.graphemes`.) -## -## >>> Str.toUtf8 "👩‍👩‍👦‍👦" -## -## >>> Str.toUtf8 "Roc" -## -## >>> Str.toUtf8 "鹏" -## -## >>> Str.toUtf8 "🐦" -## -## For a more flexible function that walks through each of these [U8] code units -## without creating a [List], see `Str.walkUtf8` and `Str.walkRevUtf8`. -toUtf8 : Str -> List U8 -toUtf16Be : Str -> List U8 -toUtf16Le : Str -> List U8 -# toUtf16Bom : Str, Endi -> List U8 -toUtf32Be : Str -> List U8 -toUtf32Le : Str -> List U8 -# toUtf32Bom : Str, Endi -> List U8 - -# Parsing - -## If the bytes begin with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster) -## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up. -## -## If the bytes do not begin with a valid grapheme, for example because the list was -## empty or began with an invalid grapheme, return `Err`. -parseUtf8Grapheme : List U8 -> Result { grapheme : Str, bytesParsed: Nat } [ InvalidGrapheme ]* - -## If the bytes begin with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point) -## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up. -## -## If the string does not begin with a valid code point, for example because the list was -## empty or began with an invalid code point, return an `Err`. -parseUtf8CodePt : List U8 -> Result { codePt : U32, bytesParsed: Nat } [ InvalidCodePt ]* - -## If the string represents a valid [U8] number, return that number. -## -## For more advanced options, see [parseU8]. -toU8 : Str -> Result U8 [ InvalidU8 ]* -toI8 : Str -> Result I8 [ InvalidI8 ]* -toU16 : Str -> Result U16 [ InvalidU16 ]* -toI16 : Str -> Result I16 [ InvalidI16 ]* -toU32 : Str -> Result U32 [ InvalidU32 ]* -toI32 : Str -> Result I32 [ InvalidI32 ]* -toU64 : Str -> Result U64 [ InvalidU64 ]* -toI64 : Str -> Result I64 [ InvalidI64 ]* -toU128 : Str -> Result U128 [ InvalidU128 ]* -toI128 : Str -> Result I128 [ InvalidI128 ]* -toF64 : Str -> Result U128 [ InvalidF64 ]* -toF32 : Str -> Result I128 [ InvalidF32 ]* -toDec : Str -> Result Dec [ InvalidDec ]* - -## If the string represents a valid number, return that number. -## -## The exact number type to look for will be inferred from usage. -## In the example below, the usage of I64 in the type signature will require that type instead of (Num *). -## -## >>> strToI64 : Str -> Result I64 [ InvalidNumStr ]* -## >>> strToI64 = \inputStr -> -## >>> Str.toNum inputStr -## -## If the string is exactly `"NaN"`, `"∞"`, or `"-∞"`, they will be accepted -## only when converting to [F64] or [F32] numbers, and will be translated accordingly. -## -## This never accepts numbers with underscores or commas in them. For more -## advanced options, see [parseNum]. -toNum : Str -> Result (Num *) [ InvalidNumStr ]* - -## If the string begins with an [Int] or a [finite](Num.isFinite) [Frac], return -## that number along with the rest of the string after it. -## -## The exact number type to look for will be inferred from usage. -## In the example below, the usage of Float64 in the type signature will require that type instead of (Num *). -## -## >>> parseFloat64 : Str -> Result { val: Float64, rest: Str } [ InvalidNumStr ]* -## >>> Str.parseNum input {} -## -## If the string begins with `"NaN"`, `"∞"`, and `"-∞"` (which do not represent -## [finite](Num.isFinite) numbers), they will be accepted only when parsing -## [F64] or [F32] numbers, and translated accordingly. -# parseNum : Str, NumParseConfig -> Result { val : Num *, rest : Str } [ InvalidNumStr ]* - -## Notes: -## * You can allow a decimal mark for integers; they'll only parse if the numbers after it are all 0. -## * For `wholeSep`, `Required` has a payload for how many digits (e.g. "required every 3 digits") -## * For `wholeSep`, `Allowed` allows the separator to appear anywhere. -# NumParseConfig : -# { -# base ? [ Decimal, Hexadecimal, Octal, Binary ], -# notation ? [ Standard, Scientific, Any ], -# decimalMark ? [ Allowed Str, Required Str, Disallowed ], -# decimalDigits ? [ Any, AtLeast U16, Exactly U16 ], -# wholeDigits ? [ Any, AtLeast U16, Exactly U16 ], -# leadingZeroes ? [ Allowed, Disallowed ], -# trailingZeroes ? [ Allowed, Disallowed ], -# wholeSep ? { mark : Str, policy : [ Allowed, Required U64 ] } -# } diff --git a/compiler/builtins/roc/Bool.roc b/compiler/builtins/roc/Bool.roc index 6853cdc5cc..0a2ad7ecfc 100644 --- a/compiler/builtins/roc/Bool.roc +++ b/compiler/builtins/roc/Bool.roc @@ -4,11 +4,81 @@ interface Bool Bool : [ True, False ] +## Returns `True` when given `True` and `True`, and `False` when either argument is `False`. +## +## `a && b` is shorthand for `Bool.and a b` +## +## >>> True && True +## +## >>> True && False +## +## >>> False && True +## +## >>> False && False +## +## ## Performance Notes +## +## In some languages, `&&` and `||` are special-cased in the compiler to skip +## evaluating the expression after the operator under certain circumstances. +## For example, in some languages, `enablePets && likesDogs user` would compile +## to the equivalent of: +## +## if enablePets then +## likesDogs user +## else +## False +## +## In Roc, however, `&&` and `||` are not special. They work the same way as +## other functions. Conditionals like `if` and `when` have a performance cost, +## and sometimes calling a function like `likesDogs user` can be faster across +## the board than doing an `if` to decide whether to skip calling it. +## +## (Naturally, if you expect the `if` to improve performance, you can always add +## one explicitly!) and : Bool, Bool -> Bool + +## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`. +## +## `a || b` is shorthand for `Bool.or a b`. +## +## >>> True || True +## +## >>> True || False +## +## >>> False || True +## +## >>> False || False +## +## ## Performance Notes +## +## In some languages, `&&` and `||` are special-cased in the compiler to skip +## evaluating the expression after the operator under certain circumstances. +## In Roc, this is not the case. See the performance notes for [Bool.and] for details. or : Bool, Bool -> Bool # xor : Bool, Bool -> Bool # currently unimplemented +## Returns `False` when given `True`, and vice versa. not : Bool -> Bool +## Returns `True` if the two values are *structurally equal*, and `False` otherwise. +## +## `a == b` is shorthand for `Bool.isEq a b` +## +## Structural equality works as follows: +## +## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal. +## 2. Records are equal if all their fields are equal. +## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal. +## 4. [Num](Num#Num) values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `False`. See `Num.isNaN` for more about *NaN*. +## +## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not +## accept arguments whose types contain functions. isEq : a, a -> Bool + +## Calls [isEq] on the given values, then calls [not] on the result. +## +## `a != b` is shorthand for `Bool.isNotEq a b` +## +## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not +## accept arguments whose types contain functions. isNotEq : a, a -> Bool diff --git a/compiler/builtins/roc/Dict.roc b/compiler/builtins/roc/Dict.roc index aa457cee0e..d5b1ba07e2 100644 --- a/compiler/builtins/roc/Dict.roc +++ b/compiler/builtins/roc/Dict.roc @@ -20,6 +20,57 @@ interface Dict Bool.{ Bool } ] +## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values. +## +## ### Inserting +## +## The most basic way to use a dictionary is to start with an empty one and then: +## 1. Call [Dict.insert] passing a key and a value, to associate that key with that value in the dictionary. +## 2. Later, call [Dict.get] passing the same key as before, and it will return the value you stored. +## +## Here's an example of a dictionary which uses a city's name as the key, and its population as the associated value. +## +## populationByCity = +## Dict.empty +## |> Dict.insert "London" 8_961_989 +## |> Dict.insert "Philadelphia" 1_603_797 +## |> Dict.insert "Shanghai" 24_870_895 +## |> Dict.insert "Delhi" 16_787_941 +## |> Dict.insert "Amsterdam" 872_680 +## +## ### Accessing keys or values +## +## We can use [Dict.keys] and [Dict.values] functions to get only the keys or only the values. +## +## You may notice that these lists have the same order as the original insertion order. This will be true if +## all you ever do is [insert] and [get] operations on the dictionary, but [remove] operations can change this order. +## Let's see how that looks. +## +## ### Removing +## +## We can remove an element from the dictionary, like so: +## +## populationByCity +## |> Dict.remove "Philadelphia" +## |> Dict.keys +## == +## [ "London", "Amsterdam", "Shanghai", "Delhi" ] +## +## Notice that the order changed! Philadelphia has been not only removed from the list, but Amsterdam - the last +## entry we inserted - has been moved into the spot where Philadelphia was previously. This is exactly what +## [Dict.remove] does: it removes an element and moves the most recent insertion into the vacated spot. +## +## This move is done as a performance optimization, and it lets [remove] have +## [constant time complexity](https://en.wikipedia.org/wiki/Time_complexity#Constant_time). ## +## +## ### Equality +## +## When comparing two dictionaries for equality, they are `==` only if their both their contents and their +## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on +## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering. + + +## An empty dictionary. empty : Dict k v single : k, v -> Dict k v get : Dict k v, k -> Result v [ KeyNotFound ]* @@ -28,7 +79,11 @@ insert : Dict k v, k, v -> Dict k v len : Dict k v -> Nat remove : Dict k v, k -> Dict k v contains : Dict k v, k -> Bool + +## Returns a [List] of the dictionary's keys. keys : Dict k v -> List k + +## Returns a [List] of the dictionary's values. values : Dict k v -> List v union : Dict k v, Dict k v -> Dict k v intersection : Dict k v, Dict k v -> Dict k v diff --git a/compiler/builtins/roc/List.roc b/compiler/builtins/roc/List.roc index 42de7f03ef..33c3cc415d 100644 --- a/compiler/builtins/roc/List.roc +++ b/compiler/builtins/roc/List.roc @@ -56,6 +56,149 @@ interface List Bool.{ Bool } ] +## Types + +## A sequential list of values. +## +## >>> [ 1, 2, 3 ] # a list of numbers +## >>> [ "a", "b", "c" ] # a list of strings +## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of numbers +## +## The maximum size of a [List] is limited by the amount of heap memory available +## to the current process. If there is not enough memory available, attempting to +## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html) +## is normally enabled, not having enough memory could result in the list appearing +## to be created just fine, but then crashing later.) +## +## > The theoretical maximum length for a list created in Roc is half of +## > `Num.maxNat`. Attempting to create a list bigger than that +## > in Roc code will always fail, although in practice it is likely to fail +## > at much smaller lengths due to insufficient memory being available. +## +## ## Performance Details +## +## Under the hood, a list is a record containing a `len : Nat` field as well +## as a pointer to a reference count and a flat array of bytes. Unique lists +## store a capacity #Nat instead of a reference count. +## +## ## Shared Lists +## +## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting). +## +## Each time a given list gets referenced, its reference count ("refcount" for short) +## gets incremented. Each time a list goes out of scope, its refcount count gets +## decremented. Once a refcount, has been decremented more times than it has been +## incremented, we know nothing is referencing it anymore, and the list's memory +## will be immediately freed. +## +## Let's look at an example. +## +## ratings = [ 5, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## The first line binds the name `ratings` to the list `[ 5, 4, 3 ]`. The list +## begins with a refcount of 1, because so far only `ratings` is referencing it. +## +## The second line alters this refcount. `{ foo: ratings` references +## the `ratings` list, which will result in its refcount getting incremented +## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list, +## which will result in its refcount getting incremented from 1 to 2. +## +## Let's turn this example into a function. +## +## getRatings = \first -> +## ratings = [ first, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## getRatings 5 +## +## At the end of the `getRatings` function, when the record gets returned, +## the original `ratings =` binding has gone out of scope and is no longer +## accessible. (Trying to reference `ratings` outside the scope of the +## `getRatings` function would be an error!) +## +## Since `ratings` represented a way to reference the list, and that way is no +## longer accessible, the list's refcount gets decremented when `ratings` goes +## out of scope. It will decrease from 2 back down to 1. +## +## Putting these together, when we call `getRatings 5`, what we get back is +## a record with two fields, `foo`, and `bar`, each of which refers to the same +## list, and that list has a refcount of 1. +## +## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`: +## +## getRatings = \first -> +## ratings = [ first, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## (getRatings 5).bar +## +## Now, when this expression returns, only the `bar` field of the record will +## be returned. This will mean that the `foo` field becomes inaccessible, causing +## the list's refcount to get decremented from 2 to 1. At this point, the list is back +## where it started: there is only 1 reference to it. +## +## Finally let's suppose the final line were changed to this: +## +## List.first (getRatings 5).bar +## +## This call to [List.first] means that even the list in the `bar` field has become +## inaccessible. As such, this line will cause the list's refcount to get +## decremented all the way to 0. At that point, nothing is referencing the list +## anymore, and its memory will get freed. +## +## Things are different if this is a list of lists instead of a list of numbers. +## Let's look at a simpler example using [List.first] - first with a list of numbers, +## and then with a list of lists, to see how they differ. +## +## Here's the example using a list of numbers. +## +## nums = [ 1, 2, 3, 4, 5, 6, 7 ] +## +## first = List.first nums +## last = List.last nums +## +## first +## +## It makes a list, calls [List.first] and [List.last] on it, and then returns `first`. +## +## Here's the equivalent code with a list of lists: +## +## lists = [ [ 1 ], [ 2, 3 ], [], [ 4, 5, 6, 7 ] ] +## +## first = List.first lists +## last = List.last lists +## +## first +## +## TODO explain how in the former example, when we go to free `nums` at the end, +## we can free it immediately because there are no other refcounts. However, +## in the case of `lists`, we have to iterate through the list and decrement +## the refcounts of each of its contained lists - because they, too, have +## refcounts! Importantly, because the first element had its refcount incremented +## because the function returned `first`, that element will actually end up +## *not* getting freed at the end - but all the others will be. +## +## In the `lists` example, `lists = [ ... ]` also creates a list with an initial +## refcount of 1. Separately, it also creates several other lists - each with +## their own refcounts - to go inside that list. (The empty list at the end +## does not use heap memory, and thus has no refcount.) +## +## At the end, we once again call [List.first] on the list, but this time +## +## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold. +## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all +## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations. +## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood! + +## Check if the list is empty. +## +## >>> List.isEmpty [ 1, 2, 3 ] +## +## >>> List.isEmpty [] isEmpty : List a -> Bool isEmpty = \list -> List.len list == 0 @@ -63,22 +206,134 @@ isEmpty = \list -> get : List a, Nat -> Result a [ OutOfBounds ]* replace : List a, Nat, a -> { list : List a, value : a } +## Replaces the element at the given index with a replacement. +## +## >>> List.set [ "a", "b", "c" ] 1 "B" +## +## If the given index is outside the bounds of the list, returns the original +## list unmodified. +## +## To drop the element at a given index, instead of replacing it, see [List.dropAt]. set : List a, Nat, a -> List a set = \list, index, value -> (List.replace list index value).list +## Add a single element to the end of a list. +## +## >>> List.append [ 1, 2, 3 ] 4 +## +## >>> [ 0, 1, 2 ] +## >>> |> List.append 3 append : List a, a -> List a + +## Add a single element to the beginning of a list. +## +## >>> List.prepend [ 1, 2, 3 ] 0 +## +## >>> [ 2, 3, 4 ] +## >>> |> List.prepend 1 prepend : List a, a -> List a + +## Returns the length of the list - the number of elements it contains. +## +## One [List] can store up to 2,147,483,648 elements (just over 2 billion), which +## is exactly equal to the highest valid #I32 value. This means the #U32 this function +## returns can always be safely converted to an #I32 without losing any data. len : List a -> Nat + +## Put two lists together. +## +## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ] +## +## >>> [ 0, 1, 2 ] +## >>> |> List.concat [ 3, 4 ] concat : List a, List a -> List a + +## Returns the last element in the list, or `ListWasEmpty` if it was empty. last : List a -> Result a [ ListWasEmpty ]* + +## A list with a single element in it. +## +## This is useful in pipelines, like so: +## +## websites = +## Str.concat domain ".com" +## |> List.single +## single : a -> List a +## Returns a list with the given length, where every element is the given value. +## +## repeat : a, Nat -> List a + +## Returns the list with its elements reversed. +## +## >>> List.reverse [ 1, 2, 3 ] reverse : List a -> List a + +## Join the given lists together into one list. +## +## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ] +## +## >>> List.join [ [], [] ] +## +## >>> List.join [] join : List (List a) -> List a contains : List a, a -> Bool + +## Build a value using each element in the list. +## +## Starting with a given `state` value, this walks through each element in the +## list from first to last, running a given `step` function on that element +## which updates the `state`. It returns the final `state` at the end. +## +## You can use it in a pipeline: +## +## [ 2, 4, 8 ] +## |> List.walk { start: 0, step: Num.add } +## +## This returns 14 because: +## * `state` starts at 0 (because of `start: 0`) +## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`. +## +## Here is a table of how `state` changes as [List.walk] walks over the elements +## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`. +## +## `state` | `elem` | `step state elem` (`Num.add state elem`) +## --------+--------+----------------------------------------- +## 0 | | +## 0 | 2 | 2 +## 2 | 4 | 6 +## 6 | 8 | 14 +## +## So `state` goes through these changes: +## 1. `0` (because of `start: 0`) +## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1 +## +## [ 1, 2, 3 ] +## |> List.walk { start: 0, step: Num.sub } +## +## This returns -6 because +## +## Note that in other languages, `walk` is sometimes called `reduce`, +## `fold`, `foldLeft`, or `foldl`. walk : List elem, state, (state, elem -> state) -> state + +## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`, +## `fold`, `foldRight`, or `foldr`. walkBackwards : List elem, state, (state, elem -> state) -> state + +## Same as [List.walk], except you can stop walking early. +## +## ## Performance Details +## +## Compared to [List.walk], this can potentially visit fewer elements (which can +## improve performance) at the cost of making each step take longer. +## However, the added cost to each step is extremely small, and can easily +## be outweighed if it results in skipping even a small number of elements. +## +## As such, it is typically better for performance to use this over [List.walk] +## if returning `Done` earlier than the last element is expected to be common. walkUntil : List elem, state, (state, elem -> [ Continue state, Stop state ]) -> state sum : List (Num a) -> Num a @@ -89,40 +344,201 @@ product : List (Num a) -> Num a product = \list -> List.walk list 1 Num.mul +## Run the given predicate on each element of the list, returning `True` if +## any of the elements satisfy it. any : List a, (a -> Bool) -> Bool + +## Run the given predicate on each element of the list, returning `True` if +## all of the elements satisfy it. all : List a, (a -> Bool) -> Bool +## Run the given function on each element of a list, and return all the +## elements for which the function returned `True`. +## +## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2) +## +## ## Performance Details +## +## [List.keepIf] always returns a list that takes up exactly the same amount +## of memory as the original, even if its length decreases. This is because it +## can't know in advance exactly how much space it will need, and if it guesses a +## length that's too low, it would have to re-allocate. +## +## (If you want to do an operation like this which reduces the memory footprint +## of the resulting list, you can do two passes over the lis with [List.walk] - one +## to calculate the precise new size, and another to populate the new list.) +## +## If given a unique list, [List.keepIf] will mutate it in place to assemble the appropriate list. +## If that happens, this function will not allocate any new memory on the heap. +## If all elements in the list end up being kept, Roc will return the original +## list unaltered. +## keepIf : List a, (a -> Bool) -> List a + +## Run the given function on each element of a list, and return all the +## elements for which the function returned `False`. +## +## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2) +## +## ## Performance Details +## +## `List.dropIf` has the same performance characteristics as [List.keepIf]. +## See its documentation for details on those characteristics! dropIf : List a, (a -> Bool) -> List a dropIf = \list, predicate -> List.keepIf list (\e -> Bool.not (predicate e)) +## This works like [List.map], except only the transformed values that are +## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped. +## +## >>> List.keepOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last +## +## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) +## >>> +## >>> List.keepOks [ "", "a", "bc", "", "d", "ef", "" ] keepOks : List before, (before -> Result after *) -> List after + +## This works like [List.map], except only the transformed values that are +## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped. +## +## >>> List.keepErrs [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last +## +## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) +## >>> +## >>> List.keepErrs [ "", "a", "bc", "", "d", "ef", "" ] keepErrs: List before, (before -> Result * after) -> List after + +## Convert each element in the list to something new, by calling a conversion +## function on each of them. Then return a new list of the converted values. +## +## > List.map [ 1, 2, 3 ] (\num -> num + 1) +## +## > List.map [ "", "a", "bc" ] Str.isEmpty map : List a, (a -> b) -> List b + +## Run a transformation function on the first element of each list, +## and use that as the first element in the returned list. +## Repeat until a list runs out of elements. +## +## Some languages have a function named `zip`, which does something similar to +## calling [List.map2] passing two lists and `Pair`: +## +## >>> zipped = List.map2 [ "a", "b", "c" ] [ 1, 2, 3 ] Pair map2 : List a, List b, (a, b -> c) -> List c + +## Run a transformation function on the first element of each list, +## and use that as the first element in the returned list. +## Repeat until a list runs out of elements. map3 : List a, List b, List c, (a, b, c -> d) -> List d + +## Run a transformation function on the first element of each list, +## and use that as the first element in the returned list. +## Repeat until a list runs out of elements. map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e + +## This works like [List.map], except it also passes the index +## of the element to the conversion function. mapWithIndex : List a, (a, Nat -> b) -> List b + +## Returns a list of all the integers between one and another, +## including both of the given numbers. +## +## >>> List.range 2 8 range : Int a, Int a -> List (Int a) sortWith : List a, (a, a -> [ LT, EQ, GT ] ) -> List a + +## Sorts a list in ascending order (lowest to highest), using a function which +## specifies a way to represent each element as a number. +## +## To sort in descending order (highest to lowest), use [List.sortDesc] instead. sortAsc : List (Num a) -> List (Num a) sortAsc = \list -> List.sortWith list Num.compare +## Sorts a list in descending order (highest to lowest), using a function which +## specifies a way to represent each element as a number. +## +## To sort in ascending order (lowest to highest), use [List.sortAsc] instead. sortDesc : List (Num a) -> List (Num a) sortDesc = \list -> List.sortWith list (\a, b -> Num.compare b a) swap : List a, Nat, Nat -> List a +## Returns the first element in the list, or `ListWasEmpty` if it was empty. first : List a -> Result a [ ListWasEmpty ]* +## Remove the first element from the list. +## +## Returns the new list (with the removed element missing). dropFirst : List elem -> List elem + +## Remove the last element from the list. +## +## Returns the new list (with the removed element missing). dropLast : List elem -> List elem +## Returns the given number of elements from the beginning of the list. +## +## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ] +## +## If there are fewer elements in the list than the requested number, +## returns the entire list. +## +## >>> List.takeFirst 5 [ 1, 2 ] +## +## To *remove* elements from the beginning of the list, use `List.takeLast`. +## +## To remove elements from both the beginning and end of the list, +## use `List.sublist`. +## +## To split the list into two lists, use `List.split`. +## +## ## Performance Details +## +## When given a Unique list, this runs extremely fast. It sets the list's length +## to the given length value, and frees the leftover elements. This runs very +## slightly faster than `List.takeLast`. +## +## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given +## a Unique list, because [List.first] returns the first element as well - +## which introduces a conditional bounds check as well as a memory load. takeFirst : List elem, Nat -> List elem + +## Returns the given number of elements from the end of the list. +## +## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ] +## +## If there are fewer elements in the list than the requested number, +## returns the entire list. +## +## >>> List.takeLast 5 [ 1, 2 ] +## +## To *remove* elements from the end of the list, use `List.takeFirst`. +## +## To remove elements from both the beginning and end of the list, +## use `List.sublist`. +## +## To split the list into two lists, use `List.split`. +## +## ## Performance Details +## +## When given a Unique list, this runs extremely fast. It moves the list's +## pointer to the index at the given length value, updates its length, +## and frees the leftover elements. This runs very nearly as fast as +## `List.takeFirst` on a Unique list. +## +## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given +## a Unique list, because [List.first] returns the first element as well - +## which introduces a conditional bounds check as well as a memory load. takeLast : List elem, Nat -> List elem +## Drops n elements from the beginning of the list. drop : List elem, Nat -> List elem + +## Drops the element at the given index from the list. +## +## This has no effect if the given index is outside the bounds of the list. +## +## To replace the element at a given index, instead of dropping it, see [List.set]. dropAt : List elem, Nat -> List elem min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* @@ -163,11 +579,41 @@ maxHelp = \list, initial -> else bestSoFar +## Like [List.map], except the transformation function wraps the return value +## in a list. At the end, all the lists get joined together into one list. +## +## You may know a similar function named `concatMap` in other languages. joinMap : List a, (a -> List b) -> List b joinMap = \list, mapper -> List.walk list [] (\state, elem -> List.concat state (mapper elem)) +## Returns the first element of the list satisfying a predicate function. +## If no satisfying element is found, an `Err NotFound` is returned. find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* + +## Returns a subsection of the given list, beginning at the `start` index and +## including a total of `len` elements. +## +## If `start` is outside the bounds of the given list, returns the empty list. +## +## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ] +## +## If more elements are requested than exist in the list, returns as many as it can. +## +## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ] +## +## > If you want a sublist which goes all the way to the end of the list, no +## > matter how long the list is, `List.takeLast` can do that more efficiently. +## +## Some languages have a function called **`slice`** which works similarly to this. sublist : List elem, { start : Nat, len : Nat } -> List elem intersperse : List elem, elem -> List elem + +## Splits the list into two lists, around the given index. +## +## The returned lists are labeled `before` and `others`. The `before` list will +## contain all the elements whose index in the original list was **less than** +## than the given index, # and the `others` list will be all the others. (This +## means if you give an index of 0, the `before` list will be empty and the +## `others` list will have the same elements as the original list.) split : List elem, Nat -> { before: List elem, others: List elem } diff --git a/compiler/builtins/roc/Num.roc b/compiler/builtins/roc/Num.roc index 66d78f69f7..80fcb7afb0 100644 --- a/compiler/builtins/roc/Num.roc +++ b/compiler/builtins/roc/Num.roc @@ -3,7 +3,7 @@ interface Num [ Num, Int, - Float, + Frac, Integer, FloatingPoint, @@ -62,14 +62,17 @@ interface Num isZero, isEven, isOdd, - toFloat, + toFrac, isPositive, isNegative, rem, + remChecked, div, divChecked, sqrt, + sqrtChecked, log, + logChecked, round, ceiling, floor, @@ -155,30 +158,285 @@ interface Num Bool.{ Bool } ] -Num range : [ @Num range ] +## Represents a number that could be either an [Int] or a [Frac]. +## +## This is useful for functions that can work on either, for example #Num.add, whose type is: +## +## ``` +## add : Num a, Num a -> Num a +## ``` +## +## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass +## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`. +## +## Similarly, the number 0x1 (that is, the integer 1 in hexadecimal notation) +## technically has the type `Num (Integer *)`, so when you pass two of them to +## [Num.add], the answer you get is `2 : Num (Integer *)`. +## +## The type [`Frac a`](#Frac) is defined to be an alias for `Num (Fraction a)`, +## so `3.0 : Num (Fraction *)` is the same value as `3.0 : Frac *`. +## Similarly, the type [`Int a`](#Int) is defined to be an alias for +## `Num (Integer a)`, so `2 : Num (Integer *)` is the same value as +## `2 : Int *`. +## +## In this way, the [Num] type makes it possible to have `1 + 0x1` return +## `2 : Int *` and `1.5 + 1.5` return `3.0 : Frac`. +## +## ## Number Literals +## +## Number literals without decimal points (like `0`, `4` or `360`) +## have the type `Num *` at first, but usually end up taking on +## a more specific type based on how they're used. +## +## For example, in `(1 + List.len myList)`, the `1` has the type `Num *` at first, +## but because `List.len` returns a `Nat`, the `1` ends up changing from +## `Num *` to the more specific `Nat`, and the expression as a whole +## ends up having the type `Nat`. +## +## Sometimes number literals don't become more specific. For example, +## the `Num.toStr` function has the type `Num * -> Str`. This means that +## when calling `Num.toStr (5 + 6)`, the expression `(5 + 6)` +## still has the type `Num *`. When this happens, `Num *` defaults to +## being an [I64] - so this addition expression would overflow +## if either 5 or 6 were replaced with a number big enough to cause +## addition overflow on an [I64] value. +## +## If this default of [I64] is not big enough for your purposes, +## you can add an `i128` to the end of the number literal, like so: +## +## >>> Num.toStr 5_000_000_000i128 +## +## This `i128` suffix specifies that you want this number literal to be +## an [I128] instead of a `Num *`. All the other numeric types have +## suffixes just like `i128`; here are some other examples: +## +## * `215u8` is a `215` value of type [U8] +## * `76.4f32` is a `76.4` value of type [F32] +## * `123.45dec` is a `123.45` value of type [Dec] +## * `12345nat` is a `12345` value of type [Nat] +## +## In practice, these are rarely needed. It's most common to write +## number literals without any suffix. +Num range := range + +## A fixed-size integer - that is, a number with no fractional component. +## +## Integers come in two flavors: signed and unsigned. Signed integers can be +## negative ("signed" refers to how they can incorporate a minus sign), +## whereas unsigned integers cannot be negative. +## +## Since integers have a fixed size, the size you choose determines both the +## range of numbers it can represent, and also how much memory it takes up. +## +## [U8] is an an example of an integer. It is an unsigned [Int] that takes up 8 bits +## (aka 1 byte) in memory. The `U` is for Unsigned and the 8 is for 8 bits. +## Because it has 8 bits to work with, it can store 256 numbers (2^8), +## and because it is unsigned, its min value is 0. This means the 256 numbers +## it can store range from 0 to 255. +## +## [I8] is a signed integer that takes up 8 bits. The `I` is for Integer, since +## integers in mathematics are signed by default. Because it has 8 bits just +## like [U8], it can store 256 numbers (still 2^16), but because it is signed, +## the range is different. Its 256 numbers range from -128 to 127. +## +## Here are some other examples: +## +## * [U16] is like [U8], except it takes up 16 bits in memory. It can store 65,536 numbers (2^16), ranging from 0 to 65,536. +## * [I16] is like [U16], except it is signed. It can still store the same 65,536 numbers (2^16), ranging from -32,768 to 32,767. +## +## This pattern continues up to [U128] and [I128]. +## +## ## Performance notes +## +## In general, using smaller numeric sizes means your program will use less memory. +## However, if a mathematical operation results in an answer that is too big +## or too small to fit in the size available for that answer (which is typically +## the same size as the inputs), then you'll get an overflow error. +## +## As such, minimizing memory usage without causing overflows involves choosing +## number sizes based on your knowledge of what numbers you expect your program +## to encounter at runtime. +## +## Minimizing memory usage does not imply maximum runtime speed! +## CPUs are typically fastest at performing integer operations on integers that +## are the same size as that CPU's native machine word size. That means a 64-bit +## CPU is typically fastest at executing instructions on [U64] and [I64] values, +## whereas a 32-bit CPU is typically fastest on [U32] and [I32] values. +## +## Putting these factors together, here are some reasonable guidelines for optimizing performance through integer size choice: +## +## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly. +## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.) +## * Finally, if a particular numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds. +## +## All number literals without decimal points are compatible with [Int] values. +## +## >>> 1 +## +## >>> 0 +## +## You can optionally put underscores in your [Int] literals. +## They have no effect on the number's value, but can make large numbers easier to read. +## +## >>> 1_000_000 +## +## Integers come in two flavors: *signed* and *unsigned*. +## +## * *Unsigned* integers can never be negative. The lowest value they can hold is zero. +## * *Signed* integers can be negative. +## +## Integers also come in different sizes. Choosing a size depends on your performance +## needs and the range of numbers you need to represent. At a high level, the +## general trade-offs are: +## +## * Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! +## * Smaller integer sizes take up less memory. This savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck. +## * Certain CPUs work faster on some numeric sizes than others. If the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly! +## +## Here are the different fixed size integer types: +## +## | Range | Type | Size | +## |--------------------------------------------------------|-------|----------| +## | ` -128` | [I8] | 1 Byte | +## | ` 127` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | [U8] | 1 Byte | +## | ` 255` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -32_768` | [I16] | 2 Bytes | +## | ` 32_767` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | [U16] | 2 Bytes | +## | ` 65_535` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -2_147_483_648` | [I32] | 4 Bytes | +## | ` 2_147_483_647` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | [U32] | 4 Bytes | +## | ` (over 4 billion) 4_294_967_295` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -9_223_372_036_854_775_808` | [I64] | 8 Bytes | +## | ` 9_223_372_036_854_775_807` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | [U64] | 8 Bytes | +## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | +## |--------------------------------------------------------|-------|----------| +## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | [I128]| 16 Bytes | +## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | +## |--------------------------------------------------------|-------|----------| +## | ` (over 340 undecillion) 0` | [U128]| 16 Bytes | +## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | +## +## Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal +## to the size of a memory address, which varies by system. For example, when +## compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a +## 32-bit system, it's the same as [U32]. +## +## A common use for [Nat] is to store the length ("len" for short) of a +## collection like a [List]. 64-bit systems can represent longer +## lists in memory than 32-bit systems can, which is why the length of a list +## is represented as a [Nat] in Roc. +## +## If any operation would result in an [Int] that is either too big +## or too small to fit in that range (e.g. calling `Num.maxI32 + 1`), +## then the operation will *overflow*. When an overflow occurs, the program will crash. +## +## As such, it's very important to design your code not to exceed these bounds! +## If you need to do math outside these bounds, consider using a larger numeric size. + Int range : Num (Integer range) -Float range : Num (FloatingPoint range) -Signed128 : [ @Signed128 ] -Signed64 : [ @Signed64 ] -Signed32 : [ @Signed32 ] -Signed16 : [ @Signed16 ] -Signed8 : [ @Signed8 ] +## A fixed-size number with a fractional component. +## +## Roc fractions come in two flavors: fixed-point base-10 and floating-point base-2. +## +## * [Dec] is a 128-bit [fixed-point](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) base-10 number. It's a great default choice, especially when precision is important - for example when representing currency. With [Dec], 0.1 + 0.2 returns 0.3. +## * [F64] and [F32] are [floating-point](https://en.wikipedia.org/wiki/Floating-point_arithmetic) base-2 numbers. They sacrifice precision for lower memory usage and improved performance on some operations. This makes them a good fit for representing graphical coordinates. With [F64], 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125. +## +## If you don't specify a type, Roc will default to using [Dec] because it's +## the least error-prone overall. For example, suppose you write this: +## +## wasItPrecise = 0.1 + 0.2 == 0.3 +## +## The value of `wasItPrecise` here will be `True`, because Roc uses [Dec] +## by default when there are no types specified. +## +## In contrast, suppose we use `f32` or `f64` for one of these numbers: +## +## wasItPrecise = 0.1f64 + 0.2 == 0.3 +## +## Here, `wasItPrecise` will be `False` because the entire calculation will have +## been done in a base-2 floating point calculation, which causes noticeable +## precision loss in this case. +## +## The floating-point numbers ([F32] and [F64]) also have three values which +## are not ordinary [finite numbers](https://en.wikipedia.org/wiki/Finite_number). +## They are: +## * ∞ ([infinity](https://en.wikipedia.org/wiki/Infinity)) +## * -∞ (negative infinity) +## * *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)) +## +## These values are different from ordinary numbers in that they only occur +## when a floating-point calculation encounters an error. For example: +## * Dividing a positive [F64] by `0.0` returns ∞. +## * Dividing a negative [F64] by `0.0` returns -∞. +## * Dividing a [F64] of `0.0` by `0.0` returns [*NaN*](Num.isNaN). +## +## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) +## floating point standard. Because almost all modern processors are built to +## this standard, deviating from these rules has a significant performance +## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is +## access to hardware-accelerated performance, Roc follows these rules exactly. +## +## There's no literal syntax for these error values, but you can check to see if +## you ended up with one of them by using [isNaN], [isFinite], and [isInfinite]. +## Whenever a function in this module could return one of these values, that +## possibility is noted in the function's documentation. +## +## ## Performance Notes +## +## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32] +## for addition and subtraction. For example, [F32] and [F64] do addition using +## a single CPU floating-point addition instruction, which typically takes a +## few clock cycles to complete. In contrast, [Dec] does addition using a few +## CPU integer arithmetic instructions, each of which typically takes only one +## clock cycle to complete. Exact numbers will vary by CPU, but they should be +## similar overall. +## +## [Dec] is significantly slower for multiplication and division. It not only +## needs to do more arithmetic instructions than [F32] and [F64] do, but also +## those instructions typically take more clock cycles to complete. +## +## With [Num.sqrt] and trigonometry functions like [Num.cos], there is +## an even bigger performance difference. [F32] and [F64] can do these in a +## single instruction, whereas [Dec] needs entire custom procedures - which use +## loops and conditionals. If you need to do performance-critical trigonometry +## or square roots, either [F64] or [F32] is probably a better choice than the +## usual default choice of [Dec], despite the precision problems they bring. +Frac range : Num (FloatingPoint range) -Unsigned128 : [ @Unsigned128 ] -Unsigned64 : [ @Unsigned64 ] -Unsigned32 : [ @Unsigned32 ] -Unsigned16 : [ @Unsigned16 ] -Unsigned8 : [ @Unsigned8 ] +Signed128 := [] +Signed64 := [] +Signed32 := [] +Signed16 := [] +Signed8 := [] -Natural : [ @Natural ] +Unsigned128 := [] +Unsigned64 := [] +Unsigned32 := [] +Unsigned16 := [] +Unsigned8 := [] -Integer range : [ @Integer range ] +Natural := [] + +Integer range := range I128 : Num (Integer Signed128) I64 : Num (Integer Signed64) I32 : Num (Integer Signed32) I16 : Num (Integer Signed16) + +## A signed 8-bit integer, ranging from -128 to 127 I8 : Int Signed8 U128 : Num (Integer Unsigned128) @@ -187,20 +445,80 @@ U32 : Num (Integer Unsigned32) U16 : Num (Integer Unsigned16) U8 : Num (Integer Unsigned8) +## A [natural number](https://en.wikipedia.org/wiki/Natural_number) represented +## as a 64-bit unsigned integer on 64-bit systems, a 32-bit unsigned integer +## on 32-bit systems, and so on. +## +## This system-specific size makes it useful for certain data structure +## functions like [List.len], because the number of elements many data strucures +## can hold is also system-specific. For example, the maximum number of elements +## a [List] can hold on a 64-bit system fits in a 64-bit unsigned integer, and +## on a 32-bit system it fits in 32-bit unsigned integer. This makes [Nat] a +## good fit for [List.len] regardless of system. Nat : Num (Integer Natural) -Decimal : [ @Decimal ] -Binary64 : [ @Binary64 ] -Binary32 : [ @Binary32 ] +Decimal := [] +Binary64 := [] +Binary32 := [] -FloatingPoint range : [ @FloatingPoint range ] +FloatingPoint range := range F64 : Num (FloatingPoint Binary64) F32 : Num (FloatingPoint Binary32) + +## A decimal number. +## +## [Dec] is the best default choice for representing base-10 decimal numbers +## like currency, because it is base-10 under the hood. In contrast, +## [F64] and [F32] are base-2 under the hood, which can lead to decimal +## precision loss even when doing addition and subtraction. For example, when +## using [F64], running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125, +## whereas when using [Dec], 0.1 + 0.2 returns 0.3. +## +## Under the hood, a [Dec] is an [I128], and operations on it perform +## [base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) +## with 18 decimal places of precision. +## +## This means a [Dec] can represent whole numbers up to slightly over 170 +## quintillion, along with 18 decimal places. (To be precise, it can store +## numbers betwween `-170_141_183_460_469_231_731.687303715884105728` +## and `170_141_183_460_469_231_731.687303715884105727`.) Why 18 +## decimal places? It's the highest number of decimal places where you can still +## convert any [U64] to a [Dec] without losing information. +## +## There are some use cases where [F64] and [F32] can be better choices than [Dec] +## despite their precision issues. For example, in graphical applications they +## can be a better choice for representing coordinates because they take up +## less memory, certain relevant calculations run faster (see performance +## details, below), and decimal precision loss isn't as big a concern when +## dealing with screen coordinates as it is when dealing with currency. +## +## ## Performance +## +## [Dec] typically takes slightly less time than [F64] to perform addition and +## subtraction, but 10-20 times longer to perform multiplication and division. +## [sqrt] and trigonometry are massively slower with [Dec] than with [F64]. Dec : Num (FloatingPoint Decimal) # ------- Functions +## Convert a number to a [Str]. +## +## This is the same as calling `Num.format {}` - so for more details on +## exact formatting, see `Num.format`. +## +## >>> Num.toStr 42 +## +## Only [Frac] values will include a decimal point, and they will always include one. +## +## >>> Num.toStr 4.2 +## +## >>> Num.toStr 4.0 +## +## When this function is given a non-[finite](Num.isFinite) +## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`. +## +## To get strings in hexadecimal, octal, or binary format, use `Num.format`. toStr : Num * -> Str intCast : Int a -> Int b @@ -209,49 +527,277 @@ bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] compare : Num a, Num a -> [ LT, EQ, GT ] +## Returns `True` if the first number is less than the second. +## +## `a < b` is shorthand for `Num.isLt a b`. +## +## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* +## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) +## +## >>> 5 +## >>> |> Num.isLt 6 isLt : Num a, Num a -> Bool + +## Returns `True` if the first number is greater than the second. +## +## `a > b` is shorthand for `Num.isGt a b`. +## +## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* +## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) +## +## >>> 6 +## >>> |> Num.isGt 5 isGt : Num a, Num a -> Bool + +## Returns `True` if the first number is less than or equal to the second. +## +## `a <= b` is shorthand for `Num.isLte a b`. +## +## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* +## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) isLte : Num a, Num a -> Bool + +## Returns `True` if the first number is greater than or equal to the second. +## +## `a >= b` is shorthand for `Num.isGte a b`. +## +## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* +## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) isGte : Num a, Num a -> Bool +## Returns `True` if the number is `0`, and `False` otherwise. isZero : Num a -> Bool +## A number is even if dividing it by 2 gives a remainder of 0. +## +## Examples of even numbers: 0, 2, 4, 6, 8, -2, -4, -6, -8 isEven : Int a -> Bool + +## A number is odd if dividing it by 2 gives a remainder of 1. +## +## Examples of odd numbers: 1, 3, 5, 7, -1, -3, -5, -7 isOdd : Int a -> Bool +## Positive numbers are greater than `0`. isPositive : Num a -> Bool + +## Negative numbers are less than `0`. isNegative : Num a -> Bool -toFloat : Num * -> Float * +toFrac : Num * -> Frac * +## Return the absolute value of the number. +## +## * For a positive number, returns the same number. +## * For a negative number, returns the same number except positive. +## * For zero, returns zero. +## +## >>> Num.abs 4 +## +## >>> Num.abs -2.5 +## +## >>> Num.abs 0 +## +## >>> Num.abs 0.0 +## +## This is safe to use with any [Frac], but it can cause overflow when used with certain [Int] values. +## +## For example, calling #Num.abs on the lowest value of a signed integer (such as [Num.minI64] or [Num.minI32]) will cause overflow. +## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than +## the highest value it can represent. (For this reason, calling [Num.neg] on the lowest signed value will also cause overflow.) +## +## Calling this on an unsigned integer (like [U32] or [U64]) never does anything. abs : Num a -> Num a + +## Return a negative number when given a positive one, and vice versa. +## +## >>> Num.neg 5 +## +## >>> Num.neg -2.5 +## +## >>> Num.neg 0 +## +## >>> Num.neg 0.0 +## +## This is safe to use with any [Frac], but it can cause overflow when used with certain [Int] values. +## +## For example, calling #Num.neg on the lowest value of a signed integer (such as [Num.minI64] or [Num.minI32]) will cause overflow. +## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than +## the highest value it can represent. (For this reason, calling #Num.abs on the lowest signed value will also cause overflow.) +## +## Additionally, calling #Num.neg on any unsigned integer (such as any [U64] or [U32] value) other than zero will cause overflow. +## +## (It will never crash when given a [Frac], however, because of how floating point numbers represent positive and negative numbers.) neg : Num a -> Num a +## Add two numbers of the same type. +## +## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.) +## +## `a + b` is shorthand for `Num.add a b`. +## +## >>> 5 + 7 +## +## >>> Num.add 5 7 +## +## `Num.add` can be convenient in pipelines. +## +## >>> Frac.pi +## >>> |> Num.add 1.0 +## +## If the answer to this operation can't fit in the return value (e.g. an +## [I8] answer that's higher than 127 or lower than -128), the result is an +## *overflow*. For [F64] and [F32], overflow results in an answer of either +## ∞ or -∞. For all other number types, overflow results in a panic. add : Num a, Num a -> Num a + +## Subtract two numbers of the same type. +## +## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.) +## +## `a - b` is shorthand for `Num.sub a b`. +## +## >>> 7 - 5 +## +## >>> Num.sub 7 5 +## +## `Num.sub` can be convenient in pipelines. +## +## >>> Frac.pi +## >>> |> Num.sub 2.0 +## +## If the answer to this operation can't fit in the return value (e.g. an +## [I8] answer that's higher than 127 or lower than -128), the result is an +## *overflow*. For [F64] and [F32], overflow results in an answer of either +## ∞ or -∞. For all other number types, overflow results in a panic. sub : Num a, Num a -> Num a + +## Multiply two numbers of the same type. +## +## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.) +## +## `a * b` is shorthand for `Num.mul a b`. +## +## >>> 5 * 7 +## +## >>> Num.mul 5 7 +## +## `Num.mul` can be convenient in pipelines. +## +## >>> Frac.pi +## >>> |> Num.mul 2.0 +## +## If the answer to this operation can't fit in the return value (e.g. an +## [I8] answer that's higher than 127 or lower than -128), the result is an +## *overflow*. For [F64] and [F32], overflow results in an answer of either +## ∞ or -∞. For all other number types, overflow results in a panic. mul : Num a, Num a -> Num a -sin : Float a -> Float a -cos : Float a -> Float a -tan : Float a -> Float a +sin : Frac a -> Frac a +cos : Frac a -> Frac a +tan : Frac a -> Frac a -asin : Float a -> Float a -acos : Float a -> Float a -atan : Float a -> Float a +asin : Frac a -> Frac a +acos : Frac a -> Frac a +atan : Frac a -> Frac a -sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]* -log : Float a -> Result (Float a) [ LogNeedsPositive ]* -div : Float a, Float a -> Float a -divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]* +## Returns an approximation of the absolute value of a [Frac]'s square root. +## +## The square root of a negative number is an irrational number, and [Frac] only +## supports rational numbers. As such, you should make sure never to pass this +## function a negative number! Calling [sqrt] on a negative [Dec] will cause a panic. +## +## Calling [sqrt] on [F32] and [F64] values follows these rules: +## * Passing a negative [F64] or [F32] returns [*NaN*](Num.isNaN). +## * Passing [*NaN*](Num.isNaN) or -∞ also returns [*NaN*](Num.isNaN). +## * Passing ∞ returns ∞. +## +## > These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) +## > floating point standard. Because almost all modern processors are built to +## > this standard, deviating from these rules has a significant performance +## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is +## > access to hardware-accelerated performance, Roc follows these rules exactly. +## +## >>> Num.sqrt 4.0 +## +## >>> Num.sqrt 1.5 +## +## >>> Num.sqrt 0.0 +## +## >>> Num.sqrt -4.0f64 +sqrt : Frac a -> Frac a +sqrtChecked : Frac a -> Result (Frac a) [ SqrtOfNegative ]* +log : Frac a -> Frac a +logChecked : Frac a -> Result (Frac a) [ LogNeedsPositive ]* +## Divide one [Frac] by another. +## +## `a / b` is shorthand for `Num.div a b`. +## +## [Division by zero is undefined in mathematics](https://en.wikipedia.org/wiki/Division_by_zero). +## As such, you should make sure never to pass zero as the denomaintor to this function! +## Calling [div] on a [Dec] denominator of zero will cause a panic. +## +## Calling [div] on [F32] and [F64] values follows these rules: +## * Dividing a positive [F64] or [F32] by zero returns ∞. +## * Dividing a negative [F64] or [F32] by zero returns -∞. +## * Dividing a zero [F64] or [F32] by zero returns [*NaN*](Num.isNaN). +## +## > These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) +## > floating point standard. Because almost all modern processors are built to +## > this standard, deviating from these rules has a significant performance +## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is +## > access to hardware-accelerated performance, Roc follows these rules exactly. +## +## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using +## one of the functions in this module like #toDec. +## +## >>> 5.0 / 7.0 +## +## >>> Num.div 5 7 +## +## `Num.div` can be convenient in pipelines. +## +## >>> Num.pi +## >>> |> Num.div 2.0 +div : Frac a, Frac a -> Frac a +divChecked : Frac a, Frac a -> Result (Frac a) [ DivByZero ]* divCeil : Int a, Int a -> Int a divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* + +## Divide two integers, truncating the result towards zero. +## +## `a // b` is shorthand for `Num.divTrunc a b`. +## +## Division by zero is undefined in mathematics. As such, you should make +## sure never to pass zero as the denomaintor to this function! If you do, +## it will crash. +## +## >>> 5 // 7 +## +## >>> Num.divTrunc 5 7 +## +## >>> 8 // -3 +## +## >>> Num.divTrunc 8 -3 +## divTrunc : Int a, Int a -> Int a divTruncChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* -# mod : Float a, Float a -> Result (Float a) [ DivByZero ]* -rem : Int a, Int a -> Result (Int a) [ DivByZero ]* -# mod : Int a, Int a -> Result (Int a) [ DivByZero ]* +## Obtain the remainder (truncating modulo) from the division of two integers. +## +## `a % b` is shorthand for `Num.rem a b`. +## +## >>> 5 % 7 +## +## >>> Num.rem 5 7 +## +## >>> -8 % -3 +## +## >>> Num.rem -8 -3 +rem : Int a, Int a -> Int a +remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* + isMultipleOf : Int a, Int a -> Bool bitwiseAnd : Int a, Int a -> Int a @@ -261,84 +807,272 @@ shiftLeftBy : Int a, Int a -> Int a shiftRightBy : Int a, Int a -> Int a shiftRightZfBy : Int a, Int a -> Int a -round : Float * -> Int * -floor : Float * -> Int * -ceiling : Float * -> Int * +## Round off the given fraction to the nearest integer. +round : Frac * -> Int * +floor : Frac * -> Int * +ceiling : Frac * -> Int * -pow : Float a, Float a -> Float a +## Raises a [Frac] to the power of another [Frac]. +## +## For an [Int] alternative to this function, see [Num.powInt] +pow : Frac a, Frac a -> Frac a + +## Raises an integer to the power of another, by multiplying the integer by +## itself the given number of times. +## +## This process is known as [exponentiation by squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring). +## +## For a [Frac] alternative to this function, which supports negative exponents, +## see #Num.exp. +## +## >>> Num.exp 5 0 +## +## >>> Num.exp 5 1 +## +## >>> Num.exp 5 2 +## +## >>> Num.exp 5 6 +## +## ## Performance Notes +## +## Be careful! It is very easy for this function to produce an answer +## so large it causes an overflow. powInt : Int a, Int a -> Int a addWrap : Int range, Int range -> Int range + +## Add two numbers, clamping on the maximum representable number rather than +## overflowing. +## +## This is the same as [Num.add] except for the saturating behavior if the +## addition is to overflow. +## For example, if `x : U8` is 200 and `y : U8` is 100, `addSaturated x y` will +## yield 255, the maximum value of a `U8`. addSaturated : Num a, Num a -> Num a + +## Add two numbers and check for overflow. +## +## This is the same as [Num.add] except if the operation overflows, instead of +## panicking or returning ∞ or -∞, it will return `Err Overflow`. addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* subWrap : Int range, Int range -> Int range + +## Subtract two numbers, clamping on the minimum representable number rather +## than overflowing. +## +## This is the same as [Num.sub] except for the saturating behavior if the +## subtraction is to overflow. +## For example, if `x : U8` is 10 and `y : U8` is 20, `subSaturated x y` will +## yield 0, the minimum value of a `U8`. subSaturated : Num a, Num a -> Num a + +## Subtract two numbers and check for overflow. +## +## This is the same as [Num.sub] except if the operation overflows, instead of +## panicking or returning ∞ or -∞, it will return `Err Overflow`. subChecked : Num a, Num a -> Result (Num a) [ Overflow ]* mulWrap : Int range, Int range -> Int range # mulSaturated : Num a, Num a -> Num a + +## Multiply two numbers and check for overflow. +## +## This is the same as [Num.mul] except if the operation overflows, instead of +## panicking or returning ∞ or -∞, it will return `Err Overflow`. mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]* +## The lowest number that can be stored in an [I8] without underflowing its +## available memory and crashing. +## +## For reference, this number is `-128`. +## +## Note that the positive version of this number is larger than [Num.maxI8], +## which means if you call [Num.abs] on [Num.minI8], it will overflow and crash! minI8 : I8 minI8 = -128i8 +## The highest number that can be stored in an [I8] without overflowing its +## available memory and crashing. +## +## For reference, this number is `127`. +## +## Note that this is smaller than the positive version of [Num.minI8], +## which means if you call [Num.abs] on [Num.minI8], it will overflow and crash! maxI8 : I8 maxI8 = 127i8 +## The lowest number that can be stored in a [U8] without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because [U8] is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. minU8 : U8 minU8 = 0u8 +## The highest number that can be stored in a [U8] without overflowing its +## available memory and crashing. +## +## For reference, this number is `255`. maxU8 : U8 maxU8 = 255u8 +## The lowest number that can be stored in an [I16] without underflowing its +## available memory and crashing. +## +## For reference, this number is `-32_768`. +## +## Note that the positive version of this number is larger than [Num.maxI16], +## which means if you call [Num.abs] on [Num.minI16], it will overflow and crash! minI16 : I16 minI16 = -32768i16 +## The highest number that can be stored in an [I16] without overflowing its +## available memory and crashing. +## +## For reference, this number is `32_767`. +## +## Note that this is smaller than the positive version of [Num.minI16], +## which means if you call [Num.abs] on [Num.minI16], it will overflow and crash! maxI16 : I16 maxI16 = 32767i16 +## The lowest number that can be stored in a [U16] without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because [U16] is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. minU16 : U16 minU16 = 0u16 +## The highest number that can be stored in a [U16] without overflowing its +## available memory and crashing. +## +## For reference, this number is `65_535`. maxU16 : U16 maxU16 = 65535u16 +## The lowest number that can be stored in an [I32] without underflowing its +## available memory and crashing. +## +## For reference, this number is `-2_147_483_648`. +## +## Note that the positive version of this number is larger than [Num.maxI32], +## which means if you call [Num.abs] on [Num.minI32], it will overflow and crash! minI32 : I32 minI32 = -2147483648 +## The highest number that can be stored in an [I32] without overflowing its +## available memory and crashing. +## +## For reference, this number is `2_147_483_647`, +## which is over 2 million. +## +## Note that this is smaller than the positive version of [Num.minI32], +## which means if you call [Num.abs] on [Num.minI32], it will overflow and crash! maxI32 : I32 maxI32 = 2147483647 +## The lowest number that can be stored in a [U32] without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because [U32] is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. minU32 : U32 minU32 = 0 +## The highest number that can be stored in a [U32] without overflowing its +## available memory and crashing. +## +## For reference, this number is `4_294_967_295`. maxU32 : U32 maxU32 = 4294967295 +## The lowest number that can be stored in an [I64] without underflowing its +## available memory and crashing. +## +## For reference, this number is `-9_223_372_036_854_775_808`, +## which is under 9 quintillion. +## +## Note that the positive version of this number is larger than [Num.maxI64], +## which means if you call [Num.abs] on [Num.minI64], it will overflow and crash! minI64 : I64 minI64 = -9223372036854775808 +## The highest number that can be stored in an [I64] without overflowing its +## available memory and crashing. +## +## For reference, this number is `9_223_372_036_854_775_807`, +## which is over 9 quintillion. +## +## Note that this is smaller than the positive version of [Num.minI64], +## which means if you call [Num.abs] on [Num.minI64], it will overflow and crash! maxI64 : I64 maxI64 = 9223372036854775807 +## The lowest number that can be stored in a [U64] without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because [U64] is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. minU64 : U64 minU64 = 0 +## The highest number that can be stored in a [U64] without overflowing its +## available memory and crashing. +## +## For reference, this number is `18_446_744_073_709_551_615`, +## which is over 18 quintillion. maxU64 : U64 maxU64 = 18446744073709551615 +## The lowest number that can be stored in an [I128] without underflowing its +## available memory and crashing. +## +## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`. +## which is under 170 undecillion. +## +## Note that the positive version of this number is larger than [Num.maxI128], +## which means if you call [Num.abs] on [Num.minI128], it will overflow and crash! minI128 : I128 minI128 = -170141183460469231731687303715884105728 +## The highest number that can be stored in an [I128] without overflowing its +## available memory and crashing. +## +## For reference, this number is `170_141_183_460_469_231_731_687_303_715_884_105_727`, +## which is over 170 undecillion. +## +## Note that this is smaller than the positive version of [Num.minI128], +## which means if you call [Num.abs] on [Num.minI128], it will overflow and crash! maxI128 : I128 maxI128 = 170141183460469231731687303715884105727 +## The lowest number that can be stored in a [U128] without underflowing its +## available memory and crashing. +## +## For reference, this number is zero, because [U128] is +## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), +## and zero is the lowest unsigned number. +## Unsigned numbers cannot be negative. minU128 : U128 minU128 = 0 +## The highest number that can be stored in a [U128] without overflowing its +## available memory and crashing. +## +## For reference, this number is `340_282_366_920_938_463_463_374_607_431_768_211_455`, +## which is over 340 undecillion. maxU128 : U128 -maxU128 = 0340282366920938463463374607431768211455 +maxU128 = 340282366920938463463374607431768211455 minF32 : F32 minF32 = -3.40282347e38 @@ -352,6 +1086,9 @@ minF64 = -1.7976931348623157e308 maxF64 : F64 maxF64 = 1.7976931348623157e308 + +## Converts an [Int] to an [I8]. If the given number can't be precisely represented in an [I8], +## the returned number may be different from the given number. toI8 : Int * -> I8 toI16 : Int * -> I16 toI32 : Int * -> I32 @@ -362,11 +1099,35 @@ toU16 : Int * -> U16 toU32 : Int * -> U32 toU64 : Int * -> U64 toU128 : Int * -> U128 + +## Convert an [Int] to a [Nat]. If the given number doesn't fit in [Nat], it will be truncated. +## Since #Nat has a different maximum number depending on the system you're building +## for, this may give a different answer on different systems. +## +## For example, on a 32-bit system, #Num.maxNat will return the same answer as +## [Num.maxU32]. This means that calling `Num.toNat 9_000_000_000` on a 32-bit +## system will return [Num.maxU32] instead of 9 billion, because 9 billion is +## higher than [Num.maxU32] and will not fit in a [Nat] on a 32-bit system. +## +## However, calling `Num.toNat 9_000_000_000` on a 64-bit system will return +## the #Nat value of 9_000_000_000. This is because on a 64-bit system, [Nat] can +## hold up to [Num.maxU64], and 9_000_000_000 is lower than [Num.maxU64]. +## +## To convert a [Frac] to a [Nat], first call either #Num.round, #Num.ceil, or [Num.floor] +## on it, then call this on the resulting [Int]. toNat : Int * -> Nat +## Converts a [Num] to an [F32]. If the given number can't be precisely represented in an [F32], +## the returned number may be different from the given number. toF32 : Num * -> F32 + +## Converts a [Num] to an [F64]. If the given number can't be precisely represented in an [F64], +## the returned number may be different from the given number. toF64 : Num * -> F64 +## Converts a [Int] to an [I8]. +## If the given integer can't be precisely represented in an [I8], returns +## `Err OutOfBounds`. toI8Checked : Int * -> Result I8 [ OutOfBounds ]* toI16Checked : Int * -> Result I16 [ OutOfBounds ]* toI32Checked : Int * -> Result I32 [ OutOfBounds ]* @@ -380,3 +1141,183 @@ toU128Checked : Int * -> Result U128 [ OutOfBounds ]* toNatChecked : Int * -> Result Nat [ OutOfBounds ]* toF32Checked : Num * -> Result F32 [ OutOfBounds ]* toF64Checked : Num * -> Result F64 [ OutOfBounds ]* + + +# Special Floating-Point operations + +## When given a [F64] or [F32] value, returns `False` if that value is +## [*NaN*](Num.isNaN), ∞ or -∞, and `True` otherwise. +## +## Always returns `True` when given a [Dec]. +## +## This is the opposite of [isInfinite], except when given [*NaN*](Num.isNaN). Both +## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). +#isFinite : Frac * -> Bool + +## When given a [F64] or [F32] value, returns `True` if that value is either +## ∞ or -∞, and `False` otherwise. +## +## Always returns `False` when given a [Dec]. +## +## This is the opposite of [isFinite], except when given [*NaN*](Num.isNaN). Both +## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). +#isInfinite : Frac * -> Bool + +## When given a [F64] or [F32] value, returns `True` if that value is +## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `False` otherwise. +## +## Always returns `False` when given a [Dec]. +## +## >>> Num.isNaN 12.3 +## +## >>> Num.isNaN (Num.pow -1 0.5) +## +## *NaN* is unusual from other numberic values in that: +## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `False` if either argument is *NaN*. +## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `False` if either argument is *NaN*. +## +## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) +## floating point standard. Because almost all modern processors are built to +## this standard, deviating from these rules has a significant performance +## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is +## access to hardware-accelerated performance, Roc follows these rules exactly. +## +## Note that you should never put a *NaN* into a [Set], or use it as the key in +## a [Dict]. The result is entries that can never be removed from those +## collections! See the documentation for [Set.add] and [Dict.insert] for details. +#isNaN : Frac * -> Bool + + +## Returns the higher of two numbers. +## +## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* +## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) +#max : Num a, Num a -> Num a + +## Returns the lower of two numbers. +## +## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* +## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) +#min : Num a, Num a -> Num a + +# Branchless implementation that works for all numeric types: +# +# let is_lt = arg1 < arg2; +# let is_eq = arg1 == arg2; +# return (is_lt as i8 - is_eq as i8) + 1; +# +# 1, 1 -> (0 - 1) + 1 == 0 # Eq +# 5, 1 -> (0 - 0) + 1 == 1 # Gt +# 1, 5 -> (1 - 0) + 1 == 2 # Lt + +## Returns `Lt` if the first number is less than the second, `Gt` if +## the first is greater than the second, and `Eq` if they're equal. +## +## Although this can be passed to `List.sort`, you'll get better performance +## by using `List.sortAsc` or `List.sortDesc` instead. +#compare : Num a, Num a -> [ Lt, Eq, Gt ] + +## [Endianness](https://en.wikipedia.org/wiki/Endianness) +# Endi : [ Big, Little, Native ] + +## The `Endi` argument does not matter for [U8] and [I8], since they have +## only one byte. +# toBytes : Num *, Endi -> List U8 + +## when Num.parseBytes bytes Big is +## Ok { val: f64, rest } -> ... +## Err (ExpectedNum (Frac Binary64)) -> ... +# parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ ExpectedNum a ]* + +## when Num.fromBytes bytes Big is +## Ok f64 -> ... +## Err (ExpectedNum (Frac Binary64)) -> ... +# fromBytes : List U8, Endi -> Result (Num a) [ ExpectedNum a ]* + +# Bit shifts + +## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) left. +## +## `a << b` is shorthand for `Num.shl a b`. +#shl : Int a, Int a -> Int a + +## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) left. +## +## This is called `shlWrap` because any bits shifted +## off the beginning of the number will be wrapped around to +## the end. (In contrast, [shl] replaces discarded bits with zeroes.) +#shlWrap : Int a, Int a -> Int a + +## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) right. +## +## `a >> b` is shorthand for `Num.shr a b`. +#shr : Int a, Int a -> Int a + +## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right. +## +## This is called `shrWrap` because any bits shifted +## off the end of the number will be wrapped around to +## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.) +#shrWrap : Int a, Int a -> Int a + +# ## Convert a number into a [Str], formatted with the given options. +# ## +# ## Default options: +# ## * `base: Decimal` +# ## * `notation: Standard` +# ## * `decimalMark: HideForIntegers "."` +# ## * `decimalDigits: { min: 0, max: All }` +# ## * `minIntDigits: 1` +# ## * `wholeSep: { mark: ",", places: 3 }` +# ## +# ## ## Options +# ## +# ## +# ## ### decimalMark +# ## +# ## * `AlwaysShow` always shows the decimal mark, no matter what. +# ## * `HideForIntegers` hides the decimal mark if all the numbers after the decimal mark are 0. +# ## +# ## The [Str] included in either of these represents the mark itself. +# ## +# ## ### `decimalDigits +# ## +# ## With 0 decimal digits, the decimal mark will still be rendered if +# ## `decimalMark` is set to `AlwaysShow`. +# ## +# ## If `max` is less than `min`, then first the number will be truncated to `max` +# ## digits, and then zeroes will be added afterwards until it reaches `min` digits. +# ## +# ## >>> Num.format 1.23 { decPlaces: 0, decPointVis: AlwaysShow } +# ## +# ## ### minIntDigits +# ## +# ## If the integer portion of number is fewer than this many digits, zeroes will +# ## be added in front of it until there are at least `minWholeDigits` digits. +# ## +# ## If this is set to zero, then numbers less than 1 will begin with `"."` +# ## rather than `"0."`. +# ## +# ## ### wholeSep +# ## +# ## Examples: +# ## +# ## In some countries (e.g. USA and UK), a comma is used to separate thousands: +# ## >>> Num.format 1_000_000 { pf: Decimal, wholeSep: { mark: ",", places: 3 } } +# ## +# ## Sometimes when rendering bits, it's nice to group them into groups of 4: +# ## >>> Num.format 1_000_000 { pf: Binary, wholeSep: { mark: " ", places: 4 } } +# ## +# ## It's also common to render hexadecimal in groups of 2: +# ## >>> Num.format 1_000_000 { pf: Hexadecimal, wholeSep: { mark: " ", places: 2 } } +# format : +# Num *, +# { +# base ? [ Decimal, Hexadecimal, Octal, Binary ], +# notation ? [ Standard, Scientific ], +# decimalMark ? [ AlwaysShow Str, HideForIntegers ], +# decimalDigits ? { min : U16, max : [ All, Trunc U16, Round U16, Floor U16, Ceil U16 ] }, +# minWholeDigits ? U16, +# wholeSep ? { mark : Str, places : U64 } +# } +# -> Str diff --git a/compiler/builtins/roc/Result.roc b/compiler/builtins/roc/Result.roc index a857a6c456..06046299a2 100644 --- a/compiler/builtins/roc/Result.roc +++ b/compiler/builtins/roc/Result.roc @@ -2,38 +2,79 @@ interface Result exposes [ Result, isOk, isErr, map, mapErr, after, withDefault ] imports [ Bool.{ Bool } ] +## The result of an operation that could fail: either the operation went +## okay, or else there was an error of some sort. Result ok err : [ Ok ok, Err err ] +## Return True if the result indicates a success, else return False +## +## >>> Result.isOk (Ok 5) isOk : Result ok err -> Bool isOk = \result -> when result is Ok _ -> True Err _ -> False +## Return True if the result indicates a failure, else return False +## +## >>> Result.isErr (Err "uh oh") isErr : Result ok err -> Bool isErr = \result -> when result is Ok _ -> False Err _ -> True +## If the result is `Ok`, return the value it holds. Otherwise, return +## the given default value. +## +## >>> Result.withDefault (Ok 7) 42 +## +## >>> Result.withDefault (Err "uh oh") 42 withDefault : Result ok err, ok -> ok withDefault = \result, default -> when result is Ok value -> value Err _ -> default +## If the result is `Ok`, transform the value it holds by running a conversion +## function on it. Then return a new `Ok` holding the transformed value. +## +## (If the result is `Err`, this has no effect. Use [mapErr] to transform an `Err`.) +## +## >>> Result.map (Ok 12) Num.negate +## +## >>> Result.map (Err "yipes!") Num.negate +## +## `map` functions like this are common in Roc, and they all work similarly. +## See for example [List.map], `Set.map`, and `Dict.map`. map : Result a err, (a -> b) -> Result b err map = \result, transform -> when result is Ok v -> Ok (transform v) Err e -> Err e +## If the result is `Err`, transform the value it holds by running a conversion +## function on it. Then return a new `Err` holding the transformed value. +## +## (If the result is `Ok`, this has no effect. Use [map] to transform an `Ok`.) +## +## >>> Result.mapErr (Err "yipes!") Str.isEmpty +## +## >>> Result.mapErr (Ok 12) Str.isEmpty mapErr : Result ok a, (a -> b) -> Result ok b mapErr = \result, transform -> when result is Ok v -> Ok v Err e -> Err (transform e) +## If the result is `Ok`, transform the entire result by running a conversion +## function on the value the `Ok` holds. Then return that new result. +## +## (If the result is `Err`, this has no effect. Use `afterErr` to transform an `Err`.) +## +## >>> Result.after (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num +## +## >>> Result.after (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num after : Result a err, (a -> Result b err) -> Result b err after = \result, transform -> when result is diff --git a/compiler/builtins/roc/Set.roc b/compiler/builtins/roc/Set.roc index f19d80eb2d..6545518141 100644 --- a/compiler/builtins/roc/Set.roc +++ b/compiler/builtins/roc/Set.roc @@ -16,10 +16,17 @@ interface Set ] imports [ List, Bool.{ Bool }, Dict.{ values } ] +## An empty set. empty : Set k single : k -> Set k + +## Make sure never to insert a *NaN* to a [Set]! Because *NaN* is defined to be +## unequal to *NaN*, adding a *NaN* results in an entry that can never be +## retrieved or removed from the [Set]. insert : Set k, k -> Set k len : Set k -> Nat + +## Drops the given element from the set. remove : Set k, k -> Set k contains : Set k, k -> Bool diff --git a/compiler/builtins/roc/Str.roc b/compiler/builtins/roc/Str.roc index fae59869c1..06d59eac53 100644 --- a/compiler/builtins/roc/Str.roc +++ b/compiler/builtins/roc/Str.roc @@ -36,6 +36,81 @@ interface Str ] imports [ Bool.{ Bool }, Result.{ Result } ] +## # Types +## +## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks +## to the basics. +## + +## ### Unicode +## +## Unicode can represent text values which span multiple languages, symbols, and emoji. +## Here are some valid Roc strings: +## +## "Roc!" +## "鹏" +## "🕊" +## +## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster). +## An extended grapheme cluster represents what a person reading a string might +## call a "character" - like "A" or "ö" or "👩‍👩‍👦‍👦". +## Because the term "character" means different things in different areas of +## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the +## term "grapheme" as a shorthand for the more precise "extended grapheme cluster." +## +## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it: +## +## Str.countGraphemes "Roc!" +## Str.countGraphemes "折り紙" +## Str.countGraphemes "🕊" +## +## > The `countGraphemes` function walks through the entire string to get its answer, +## > so if you want to check whether a string is empty, you'll get much better performance +## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`. +## +## ### Escape sequences +## +## If you put a `\` in a Roc string literal, it begins an *escape sequence*. +## An escape sequence is a convenient way to insert certain strings into other strings. +## For example, suppose you write this Roc string: +## +## "I took the one less traveled by,\nAnd that has made all the difference." +## +## The `"\n"` in the middle will insert a line break into this string. There are +## other ways of getting a line break in there, but `"\n"` is the most common. +## +## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`. +## That would result in the same string, because the `\u` escape sequence inserts +## [Unicode code points](https://unicode.org/glossary/#code_point) directly into +## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal. +## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always +## followed by a hexadecimal literal inside `{` and `}` like this. +## +## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because +## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you +## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut), +## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\. +## +## Roc strings also support these escape sequences: +## +## * `\\` - an actual backslash (writing a single `\` always begins an escape sequence!) +## * `\"` - an actual quotation mark (writing a `"` without a `\` ends the string) +## * `\r` - [carriage return](https://en.wikipedia.org/wiki/Carriage_Return) +## * `\t` - [horizontal tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters) +## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters) +## +## You can also use escape sequences to insert named strings into other strings, like so: +## +## name = "Lee" +## city = "Roctown" +## +## greeting = "Hello there, \(name)! Welcome to \(city)." +## +## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`. +## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation), +## and you can use it as many times as you like inside a string. The name +## between the parentheses must refer to a `Str` value that is currently in +## scope, and it must be a name - it can't be an arbitrary expression like a function call. Utf8ByteProblem : @@ -50,15 +125,68 @@ Utf8ByteProblem : Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem } +## Returns `True` if the string is empty, and `False` otherwise. +## +## >>> Str.isEmpty "hi!" +## +## >>> Str.isEmpty "" isEmpty : Str -> Bool concat : Str, Str -> Str +## Combine a list of strings into a single string, with a separator +## string in between each. +## +## >>> Str.joinWith [ "one", "two", "three" ] ", " joinWith : List Str, Str -> Str + +## Split a string around a separator. +## +## >>> Str.split "1,2,3" "," +## +## Passing `""` for the separator is not useful; it returns the original string +## wrapped in a list. +## +## >>> Str.split "1,2,3" "" +## +## To split a string into its individual graphemes, use `Str.graphemes` split : Str, Str -> List Str repeat : Str, Nat -> Str + +## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster) +## in the string. +## +## Str.countGraphemes "Roc!" # 4 +## Str.countGraphemes "‰∏ÉÂ∑ßÊùø" # 3 +## Str.countGraphemes "üïä" # 1 countGraphemes : Str -> Nat + +## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point) +## equal to the given [U32], return `True`. Otherwise return `False`. +## +## If the given [Str] is empty, or if the given [U32] is not a valid +## code point, this will return `False`. +## +## **Performance Note:** This runs slightly faster than [Str.startsWith], so +## if you want to check whether a string begins with something that's representable +## in a single code point, you can use (for example) `Str.startsWithCodePt '鹏'` +## instead of `Str.startsWithCodePt "鹏"`. ('鹏' evaluates to the [U32] +## value `40527`.) This will not work for graphemes which take up multiple code +## points, however; `Str.startsWithCodePt '👩‍👩‍👦‍👦'` would be a compiler error +## because 👩‍👩‍👦‍👦 takes up multiple code points and cannot be represented as a +## single [U32]. You'd need to use `Str.startsWithCodePt "🕊"` instead. startsWithCodePt : Str, U32 -> Bool +## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit). +## (To split the string into a [List] of smaller [Str] values instead of [U8] values, +## see [Str.split].) +## +## >>> Str.toUtf8 "👩‍👩‍👦‍👦" +## +## >>> Str.toUtf8 "Roc" +## +## >>> Str.toUtf8 "鹏" +## +## >>> Str.toUtf8 "🐦" toUtf8 : Str -> List U8 # fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* @@ -70,6 +198,8 @@ fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [ BadUtf8 Ut startsWith : Str, Str -> Bool endsWith : Str, Str -> Bool +## Return the string with any blank spaces removed from both the beginning +## as well as the end. trim : Str -> Str trimLeft : Str -> Str trimRight : Str -> Str diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 70cb7e86d6..a78e562ec2 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -40,6 +40,9 @@ impl FloatWidth { pub const fn stack_size(&self) -> u32 { use FloatWidth::*; + // NOTE: this must never use mem::size_of, because that returns the size + // for the target of *the compiler itself* (e.g. this Rust code), not what + // the compiler is targeting (e.g. what the Roc code will be compiled to). match self { F32 => 4, F64 => 8, @@ -49,33 +52,27 @@ impl FloatWidth { pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { use roc_target::Architecture; - use std::mem::align_of; use FloatWidth::*; - // TODO actually alignment is architecture-specific + // NOTE: this must never use mem::align_of, because that returns the alignment + // for the target of *the compiler itself* (e.g. this Rust code), not what + // the compiler is targeting (e.g. what the Roc code will be compiled to). match self { - F32 => align_of::() as u32, - F64 => match target_info.architecture { + F32 => 4, + F64 | F128 => match target_info.architecture { Architecture::X86_64 | Architecture::Aarch64 | Architecture::Arm | Architecture::Wasm32 => 8, Architecture::X86_32 => 4, }, - F128 => align_of::() as u32, } } pub const fn try_from_symbol(symbol: Symbol) -> Option { match symbol { - Symbol::NUM_F64 | Symbol::NUM_BINARY64 | Symbol::NUM_AT_BINARY64 => { - Some(FloatWidth::F64) - } - - Symbol::NUM_F32 | Symbol::NUM_BINARY32 | Symbol::NUM_AT_BINARY32 => { - Some(FloatWidth::F32) - } - + Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(FloatWidth::F64), + Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(FloatWidth::F32), _ => None, } } @@ -105,6 +102,9 @@ impl IntWidth { pub const fn stack_size(&self) -> u32 { use IntWidth::*; + // NOTE: this must never use mem::size_of, because that returns the size + // for the target of *the compiler itself* (e.g. this Rust code), not what + // the compiler is targeting (e.g. what the Roc code will be compiled to). match self { U8 | I8 => 1, U16 | I16 => 2, @@ -116,13 +116,15 @@ impl IntWidth { pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 { use roc_target::Architecture; - use std::mem::align_of; use IntWidth::*; + // NOTE: this must never use mem::align_of, because that returns the alignment + // for the target of *the compiler itself* (e.g. this Rust code), not what + // the compiler is targeting (e.g. what the Roc code will be compiled to). match self { - U8 | I8 => align_of::() as u32, - U16 | I16 => align_of::() as u32, - U32 | I32 => align_of::() as u32, + U8 | I8 => 1, + U16 | I16 => 2, + U32 | I32 => 4, U64 | I64 => match target_info.architecture { Architecture::X86_64 | Architecture::Aarch64 @@ -130,32 +132,26 @@ impl IntWidth { | Architecture::Wasm32 => 8, Architecture::X86_32 => 4, }, - U128 | I128 => align_of::() as u32, + U128 | I128 => { + // the C ABI defines 128-bit integers to always be 16B aligned, + // according to https://reviews.llvm.org/D28990#655487 + 16 + } } } pub const fn try_from_symbol(symbol: Symbol) -> Option { match symbol { - Symbol::NUM_I128 | Symbol::NUM_SIGNED128 | Symbol::NUM_AT_SIGNED128 => { - Some(IntWidth::I128) - } - Symbol::NUM_I64 | Symbol::NUM_SIGNED64 | Symbol::NUM_AT_SIGNED64 => Some(IntWidth::I64), - Symbol::NUM_I32 | Symbol::NUM_SIGNED32 | Symbol::NUM_AT_SIGNED32 => Some(IntWidth::I32), - Symbol::NUM_I16 | Symbol::NUM_SIGNED16 | Symbol::NUM_AT_SIGNED16 => Some(IntWidth::I16), - Symbol::NUM_I8 | Symbol::NUM_SIGNED8 | Symbol::NUM_AT_SIGNED8 => Some(IntWidth::I8), - Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 | Symbol::NUM_AT_UNSIGNED128 => { - Some(IntWidth::U128) - } - Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 | Symbol::NUM_AT_UNSIGNED64 => { - Some(IntWidth::U64) - } - Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 | Symbol::NUM_AT_UNSIGNED32 => { - Some(IntWidth::U32) - } - Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 | Symbol::NUM_AT_UNSIGNED16 => { - Some(IntWidth::U16) - } - Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 | Symbol::NUM_AT_UNSIGNED8 => Some(IntWidth::U8), + Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(IntWidth::I128), + Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(IntWidth::I64), + Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(IntWidth::I32), + Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(IntWidth::I16), + Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(IntWidth::I8), + Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(IntWidth::U128), + Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(IntWidth::U64), + Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(IntWidth::U32), + Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(IntWidth::U16), + Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(IntWidth::U8), _ => None, } } @@ -283,7 +279,9 @@ pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan"); pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite"); pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int"); pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil"); -pub const NUM_ROUND: IntrinsicName = float_intrinsic!("roc_builtins.num.round"); + +pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32"); +pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64"); pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; @@ -359,6 +357,7 @@ 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"; +pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique"; pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str"; pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64"; @@ -379,6 +378,9 @@ pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed"; pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures"; pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures"; +pub const UTILS_LONGJMP: &str = "longjmp"; +pub const UTILS_SETJMP: &str = "setjmp"; + #[derive(Debug, Default)] pub struct IntToIntrinsicName { pub options: [IntrinsicName; 10], diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 68bf1283b2..33ec501c62 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -3,7 +3,7 @@ use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::builtin_aliases::{ - bool_type, box_type, dec_type, dict_type, f32_type, f64_type, float_type, i128_type, i16_type, + bool_type, box_type, dec_type, dict_type, f32_type, f64_type, frac_type, i128_type, i16_type, i32_type, i64_type, i8_type, int_type, list_type, nat_type, num_type, ordering_type, result_type, set_type, str_type, str_utf8_byte_problem_type, u128_type, u16_type, u32_type, u64_type, u8_type, @@ -131,7 +131,7 @@ pub fn types() -> MutMap { fn overflow() -> SolvedType { SolvedType::TagUnion( - vec![(TagName::Global("Overflow".into()), vec![])], + vec![(TagName::Tag("Overflow".into()), vec![])], Box::new(SolvedType::Wildcard), ) } @@ -269,11 +269,11 @@ pub fn types() -> MutMap { Box::new(ordering_type()), ); - // toFloat : Num * -> Float * + // toFrac : Num * -> Frac * add_top_level_function_type!( - Symbol::NUM_TO_FLOAT, + Symbol::NUM_TO_FRAC, vec![num_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR2))), + Box::new(frac_type(flex(TVAR2))), ); // isNegative : Num a -> Bool @@ -312,7 +312,7 @@ pub fn types() -> MutMap { ); let div_by_zero = SolvedType::TagUnion( - vec![(TagName::Global("DivByZero".into()), vec![])], + vec![(TagName::Tag("DivByZero".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -393,16 +393,16 @@ pub fn types() -> MutMap { Box::new(int_type(flex(TVAR2))) ); - // rem : Int a, Int a -> Result (Int a) [ DivByZero ]* + // rem : Int a, Int a -> Int a add_top_level_function_type!( Symbol::NUM_REM, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], - Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), + Box::new(int_type(flex(TVAR1))), ); - // mod : Int a, Int a -> Result (Int a) [ DivByZero ]* + // remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* add_top_level_function_type!( - Symbol::NUM_MOD_INT, + Symbol::NUM_REM_CHECKED, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), ); @@ -476,7 +476,7 @@ pub fn types() -> MutMap { ); let out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -551,7 +551,7 @@ pub fn types() -> MutMap { ); let out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -667,98 +667,99 @@ pub fn types() -> MutMap { Box::new(str_type()) ); - // Float module + // Frac module - // div : Float a, Float a -> Float a + // div : Frac a, Frac a -> Frac a add_top_level_function_type!( - Symbol::NUM_DIV_FLOAT, - vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR1))) + Symbol::NUM_DIV_FRAC, + vec![frac_type(flex(TVAR1)), frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))) ); - // divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]* + // divChecked : Frac a, Frac a -> Result (Frac a) [ DivByZero ]* add_top_level_function_type!( - Symbol::NUM_DIV_FLOAT_CHECKED, - vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], - Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())), - ); - - // mod : Float a, Float a -> Result (Float a) [ DivByZero ]* - add_top_level_function_type!( - Symbol::NUM_MOD_FLOAT, - vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], - Box::new(result_type(float_type(flex(TVAR1)), div_by_zero)), - ); - - // sqrt : Float a -> Float a - let sqrt_of_negative = SolvedType::TagUnion( - vec![(TagName::Global("SqrtOfNegative".into()), vec![])], - Box::new(SolvedType::Wildcard), + Symbol::NUM_DIV_FRAC_CHECKED, + vec![frac_type(flex(TVAR1)), frac_type(flex(TVAR1))], + Box::new(result_type(frac_type(flex(TVAR1)), div_by_zero)), ); + // sqrt : Frac a -> Frac a add_top_level_function_type!( Symbol::NUM_SQRT, - vec![float_type(flex(TVAR1))], - Box::new(result_type(float_type(flex(TVAR1)), sqrt_of_negative)), + vec![frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), ); - // log : Float a -> Float a - let log_needs_positive = SolvedType::TagUnion( - vec![(TagName::Global("LogNeedsPositive".into()), vec![])], + // sqrtChecked : Frac a -> Result (Frac a) [ SqrtOfNegative ]* + let sqrt_of_negative = SolvedType::TagUnion( + vec![(TagName::Tag("SqrtOfNegative".into()), vec![])], Box::new(SolvedType::Wildcard), ); add_top_level_function_type!( - Symbol::NUM_LOG, - vec![float_type(flex(TVAR1))], - Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)), + Symbol::NUM_SQRT_CHECKED, + vec![frac_type(flex(TVAR1))], + Box::new(result_type(frac_type(flex(TVAR1)), sqrt_of_negative)), ); - // round : Float a -> Int b + // log : Frac a -> Frac a + add_top_level_function_type!( + Symbol::NUM_LOG, + vec![frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), + ); + + // logChecked : Frac a -> Result (Frac a) [ LogNeedsPositive ]* + let log_needs_positive = SolvedType::TagUnion( + vec![(TagName::Tag("LogNeedsPositive".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + + add_top_level_function_type!( + Symbol::NUM_LOG_CHECKED, + vec![frac_type(flex(TVAR1))], + Box::new(result_type(frac_type(flex(TVAR1)), log_needs_positive)), + ); + + // round : Frac a -> Int b add_top_level_function_type!( Symbol::NUM_ROUND, - vec![float_type(flex(TVAR1))], + vec![frac_type(flex(TVAR1))], Box::new(int_type(flex(TVAR2))), ); - // sin : Float a -> Float a + // sin : Frac a -> Frac a add_top_level_function_type!( Symbol::NUM_SIN, - vec![float_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR1))), + vec![frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), ); - // cos : Float a -> Float a + // cos : Frac a -> Frac a add_top_level_function_type!( Symbol::NUM_COS, - vec![float_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR1))), + vec![frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), ); - // tan : Float a -> Float a + // tan : Frac a -> Frac a add_top_level_function_type!( Symbol::NUM_TAN, - vec![float_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR1))), + vec![frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), ); - // maxFloat : Float a - add_type!(Symbol::NUM_MAX_FLOAT, float_type(flex(TVAR1))); - - // minFloat : Float a - add_type!(Symbol::NUM_MIN_FLOAT, float_type(flex(TVAR1))); - - // pow : Float a, Float a -> Float a + // pow : Frac a, Frac a -> Frac a add_top_level_function_type!( Symbol::NUM_POW, - vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR1))), + vec![frac_type(flex(TVAR1)), frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), ); - // ceiling : Float a -> Int b + // ceiling : Frac a -> Int b add_top_level_function_type!( Symbol::NUM_CEILING, - vec![float_type(flex(TVAR1))], + vec![frac_type(flex(TVAR1))], Box::new(int_type(flex(TVAR2))), ); @@ -769,38 +770,38 @@ pub fn types() -> MutMap { Box::new(int_type(flex(TVAR1))), ); - // floor : Float a -> Int b + // floor : Frac a -> Int b add_top_level_function_type!( Symbol::NUM_FLOOR, - vec![float_type(flex(TVAR1))], + vec![frac_type(flex(TVAR1))], Box::new(int_type(flex(TVAR2))), ); - // atan : Float a -> Float a + // atan : Frac a -> Frac a add_top_level_function_type!( Symbol::NUM_ATAN, - vec![float_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR1))), + vec![frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), ); - // acos : Float a -> Float a + // acos : Frac a -> Frac a add_top_level_function_type!( Symbol::NUM_ACOS, - vec![float_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR1))), + vec![frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), ); - // asin : Float a -> Float a + // asin : Frac a -> Frac a add_top_level_function_type!( Symbol::NUM_ASIN, - vec![float_type(flex(TVAR1))], - Box::new(float_type(flex(TVAR1))), + vec![frac_type(flex(TVAR1))], + Box::new(frac_type(flex(TVAR1))), ); // bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ] { let position_out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); add_top_level_function_type!( @@ -813,7 +814,7 @@ pub fn types() -> MutMap { // bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] { let position_out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); add_top_level_function_type!( @@ -935,7 +936,7 @@ pub fn types() -> MutMap { { let bad_utf8 = SolvedType::TagUnion( vec![( - TagName::Global("BadUtf8".into()), + TagName::Tag("BadUtf8".into()), vec![str_utf8_byte_problem_type(), nat_type()], )], Box::new(SolvedType::Wildcard), @@ -953,10 +954,10 @@ pub fn types() -> MutMap { let bad_utf8 = SolvedType::TagUnion( vec![ ( - TagName::Global("BadUtf8".into()), + TagName::Tag("BadUtf8".into()), vec![str_utf8_byte_problem_type(), nat_type()], ), - (TagName::Global("OutOfBounds".into()), vec![]), + (TagName::Tag("OutOfBounds".into()), vec![]), ], Box::new(SolvedType::Wildcard), ); @@ -992,7 +993,7 @@ pub fn types() -> MutMap { // `str_to_num` in can `builtins.rs` let invalid_str = || { SolvedType::TagUnion( - vec![(TagName::Global("InvalidNumStr".into()), vec![])], + vec![(TagName::Tag("InvalidNumStr".into()), vec![])], Box::new(SolvedType::Wildcard), ) }; @@ -1099,7 +1100,7 @@ pub fn types() -> MutMap { // get : List elem, Nat -> Result elem [ OutOfBounds ]* let index_out_of_bounds = SolvedType::TagUnion( - vec![(TagName::Global("OutOfBounds".into()), vec![])], + vec![(TagName::Tag("OutOfBounds".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -1111,7 +1112,7 @@ pub fn types() -> MutMap { // first : List elem -> Result elem [ ListWasEmpty ]* let list_was_empty = SolvedType::TagUnion( - vec![(TagName::Global("ListWasEmpty".into()), vec![])], + vec![(TagName::Tag("ListWasEmpty".into()), vec![])], Box::new(SolvedType::Wildcard), ); @@ -1216,8 +1217,8 @@ pub fn types() -> MutMap { // [ LT, EQ, GT ] SolvedType::TagUnion( vec![ - (TagName::Global("Continue".into()), vec![content.clone()]), - (TagName::Global("Stop".into()), vec![content]), + (TagName::Tag("Continue".into()), vec![content.clone()]), + (TagName::Tag("Stop".into()), vec![content]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -1578,7 +1579,7 @@ pub fn types() -> MutMap { // find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* { let not_found = SolvedType::TagUnion( - vec![(TagName::Global("NotFound".into()), vec![])], + vec![(TagName::Tag("NotFound".into()), vec![])], Box::new(SolvedType::Wildcard), ); let (elem, cvar) = (TVAR1, TVAR2); @@ -1620,7 +1621,7 @@ pub fn types() -> MutMap { // get : Dict k v, k -> Result v [ KeyNotFound ]* let key_not_found = SolvedType::TagUnion( - vec![(TagName::Global("KeyNotFound".into()), vec![])], + vec![(TagName::Tag("KeyNotFound".into()), vec![])], Box::new(SolvedType::Wildcard), ); diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index b1d092053e..b57aba90ad 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -8,13 +8,12 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } roc_error_macros = { path = "../../error_macros" } +roc_exhaustive = { path = "../exhaustive" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_parse = { path = "../parse" } roc_problem = { path = "../problem" } 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" bitvec = "1" diff --git a/compiler/can/src/abilities.rs b/compiler/can/src/abilities.rs index 43f958eece..d2aef3fbd2 100644 --- a/compiler/can/src/abilities.rs +++ b/compiler/can/src/abilities.rs @@ -6,6 +6,8 @@ use roc_types::{subs::Variable, types::Type}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct MemberVariables { pub able_vars: Vec, + /// This includes - named rigid vars, lambda sets, wildcards. See + /// [`IntroducedVariables::collect_rigid`](crate::annotation::IntroducedVariables::collect_rigid). pub rigid_vars: Vec, pub flex_vars: Vec, } @@ -13,7 +15,7 @@ pub struct MemberVariables { /// Stores information about an ability member definition, including the parent ability, the /// defining type, and what type variables need to be instantiated with instances of the ability. // TODO: SoA and put me in an arena -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct AbilityMemberData { pub parent_ability: Symbol, pub signature_var: Variable, @@ -29,11 +31,22 @@ pub struct MemberSpecialization { pub region: Region, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SpecializationId(u64); + +#[allow(clippy::derivable_impls)] // let's be explicit about this +impl Default for SpecializationId { + fn default() -> Self { + Self(0) + } +} + /// Stores information about what abilities exist in a scope, what it means to implement an /// ability, and what types implement them. // TODO(abilities): this should probably go on the Scope, I don't put it there for now because we -// are only dealing with inter-module abilities for now. -#[derive(Default, Debug, Clone, PartialEq, Eq)] +// are only dealing with intra-module abilities for now. +// TODO(abilities): many of these should be `VecMap`s. Do some benchmarking. +#[derive(Default, Debug, Clone)] pub struct AbilitiesStore { /// Maps an ability to the members defining it. members_of_ability: MutMap>, @@ -54,6 +67,12 @@ pub struct AbilitiesStore { /// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability /// member `member`, to the exact symbol that implements the ability. declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>, + + next_specialization_id: u64, + + /// Resolved specializations for a symbol. These might be ephemeral (known due to type solving), + /// or resolved on-the-fly during mono. + resolved_specializations: MutMap, } impl AbilitiesStore { @@ -168,4 +187,37 @@ impl AbilitiesStore { pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> { self.members_of_ability.get(&ability).map(|v| v.as_ref()) } + + pub fn fresh_specialization_id(&mut self) -> SpecializationId { + debug_assert!(self.next_specialization_id != std::u64::MAX); + + let id = SpecializationId(self.next_specialization_id); + self.next_specialization_id += 1; + id + } + + pub fn insert_resolved(&mut self, id: SpecializationId, specialization: Symbol) { + debug_assert!(self.is_specialization_name(specialization)); + + let old_specialization = self.resolved_specializations.insert(id, specialization); + + debug_assert!( + old_specialization.is_none(), + "Existing resolution: {:?}", + old_specialization + ); + } + + pub fn remove_resolved(&mut self, id: SpecializationId) { + let old_specialization = self.resolved_specializations.remove(&id); + + debug_assert!( + old_specialization.is_some(), + "Trying to remove a resolved specialization that was never there!", + ); + } + + pub fn get_resolved(&self, id: SpecializationId) -> Option { + self.resolved_specializations.get(&id).copied() + } } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index b6bdff3e87..0a567e12e7 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,15 +1,16 @@ use crate::env::Env; +use crate::procedure::References; use crate::scope::Scope; use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet}; use roc_module::ident::{Ident, Lowercase, TagName}; -use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_module::symbol::Symbol; use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader}; use roc_problem::can::ShadowKind; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::{ - name_type_var, Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, - TypeExtension, + name_type_var, Alias, AliasCommon, AliasKind, AliasVar, LambdaSet, OptAbleType, OptAbleVar, + Problem, RecordField, Type, TypeExtension, }; #[derive(Clone, Debug)] @@ -20,6 +21,27 @@ pub struct Annotation { pub aliases: SendMap, } +impl Annotation { + pub fn add_to( + &self, + aliases: &mut VecMap, + references: &mut References, + introduced_variables: &mut IntroducedVariables, + ) { + for symbol in self.references.iter() { + references.insert_type_lookup(*symbol); + } + + introduced_variables.union(&self.introduced_variables); + + for (name, alias) in self.aliases.iter() { + if !aliases.contains_key(name) { + aliases.insert(*name, alias.clone()); + } + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum NamedOrAbleVariable<'a> { Named(&'a NamedVariable), @@ -49,6 +71,48 @@ impl<'a> NamedOrAbleVariable<'a> { } } +pub enum OwnedNamedOrAble { + Named(NamedVariable), + Able(AbleVariable), +} + +impl OwnedNamedOrAble { + pub fn first_seen(&self) -> Region { + match self { + OwnedNamedOrAble::Named(nv) => nv.first_seen, + OwnedNamedOrAble::Able(av) => av.first_seen, + } + } + + pub fn ref_name(&self) -> &Lowercase { + match self { + OwnedNamedOrAble::Named(nv) => &nv.name, + OwnedNamedOrAble::Able(av) => &av.name, + } + } + + pub fn name(self) -> Lowercase { + match self { + OwnedNamedOrAble::Named(nv) => nv.name, + OwnedNamedOrAble::Able(av) => av.name, + } + } + + pub fn variable(&self) -> Variable { + match self { + OwnedNamedOrAble::Named(nv) => nv.variable, + OwnedNamedOrAble::Able(av) => av.variable, + } + } + + pub fn opt_ability(&self) -> Option { + match self { + OwnedNamedOrAble::Named(_) => None, + OwnedNamedOrAble::Able(av) => Some(av.ability), + } + } +} + /// A named type variable, not bound to an ability. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct NamedVariable { @@ -277,7 +341,7 @@ fn make_apply_symbol( } } } else { - match env.qualified_lookup(module_name, ident, region) { + match env.qualified_lookup(scope, module_name, ident, region) { Ok(symbol) => Ok(symbol), Err(problem) => { // Either the module wasn't imported, or @@ -298,8 +362,7 @@ fn make_apply_symbol( /// For example, in `[ A Age U8, B Str {} ]`, there are three type definition references - `Age`, /// `U8`, and `Str`. pub fn find_type_def_symbols( - module_id: ModuleId, - ident_ids: &mut IdentIds, + scope: &mut Scope, initial_annotation: &roc_parse::ast::TypeAnnotation, ) -> Vec { use roc_parse::ast::TypeAnnotation::*; @@ -312,9 +375,8 @@ pub fn find_type_def_symbols( match annotation { Apply(_module_name, ident, arguments) => { let ident: Ident = (*ident).into(); - let ident_id = ident_ids.get_or_insert(&ident); + let symbol = scope.scopeless_symbol(&ident, Region::zero()); - let symbol = Symbol::new(module_id, ident_id); result.push(symbol); for t in arguments.iter() { @@ -365,7 +427,7 @@ pub fn find_type_def_symbols( while let Some(tag) = inner_stack.pop() { match tag { - Tag::Global { args, .. } | Tag::Private { args, .. } => { + Tag::Apply { args, .. } => { for t in args.iter() { stack.push(&t.value); } @@ -508,51 +570,28 @@ fn can_annotation_help( return error; } - let is_structural = alias.kind == AliasKind::Structural; - if is_structural { - let mut type_var_to_arg = Vec::new(); + let mut type_var_to_arg = Vec::new(); - for (loc_var, arg_ann) in alias.type_variables.iter().zip(args) { - let name = loc_var.value.0.clone(); - - type_var_to_arg.push((name, arg_ann)); - } - - let mut lambda_set_variables = - Vec::with_capacity(alias.lambda_set_variables.len()); - - for _ in 0..alias.lambda_set_variables.len() { - let lvar = var_store.fresh(); - - introduced_variables.insert_lambda_set(lvar); - - lambda_set_variables.push(LambdaSet(Type::Variable(lvar))); - } - - Type::DelayedAlias(AliasCommon { - symbol, - type_arguments: type_var_to_arg, - lambda_set_variables, - }) - } else { - let (type_arguments, lambda_set_variables, actual) = - instantiate_and_freshen_alias_type( - var_store, - introduced_variables, - &alias.type_variables, - args, - &alias.lambda_set_variables, - alias.typ.clone(), - ); - - Type::Alias { - symbol, - type_arguments, - lambda_set_variables, - actual: Box::new(actual), - kind: alias.kind, - } + for (_, arg_ann) in alias.type_variables.iter().zip(args) { + type_var_to_arg.push(arg_ann); } + + let mut lambda_set_variables = + Vec::with_capacity(alias.lambda_set_variables.len()); + + for _ in 0..alias.lambda_set_variables.len() { + let lvar = var_store.fresh(); + + introduced_variables.insert_lambda_set(lvar); + + lambda_set_variables.push(LambdaSet(Type::Variable(lvar))); + } + + Type::DelayedAlias(AliasCommon { + symbol, + type_arguments: type_var_to_arg, + lambda_set_variables, + }) } None => Type::Apply(symbol, args, region), } @@ -579,12 +618,7 @@ fn can_annotation_help( vars: loc_vars, }, ) => { - let symbol = match scope.introduce( - name.value.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + let symbol = match scope.introduce(name.value.into(), region) { Ok(symbol) => symbol, Err((original_region, shadow, _new_symbol)) => { @@ -611,7 +645,7 @@ fn can_annotation_help( references, ); let mut vars = Vec::with_capacity(loc_vars.len()); - let mut lowercase_vars = Vec::with_capacity(loc_vars.len()); + let mut lowercase_vars: Vec> = Vec::with_capacity(loc_vars.len()); references.insert(symbol); @@ -624,21 +658,36 @@ fn can_annotation_help( }; let var_name = Lowercase::from(var); + // TODO(abilities): check that there are no abilities bound here. if let Some(var) = introduced_variables.var_by_name(&var_name) { - vars.push((var_name.clone(), Type::Variable(var))); - lowercase_vars.push(Loc::at(loc_var.region, (var_name, var))); + vars.push(Type::Variable(var)); + lowercase_vars.push(Loc::at( + loc_var.region, + AliasVar { + name: var_name, + var, + opt_bound_ability: None, + }, + )); } else { let var = var_store.fresh(); introduced_variables .insert_named(var_name.clone(), Loc::at(loc_var.region, var)); - vars.push((var_name.clone(), Type::Variable(var))); + vars.push(Type::Variable(var)); - lowercase_vars.push(Loc::at(loc_var.region, (var_name, var))); + lowercase_vars.push(Loc::at( + loc_var.region, + AliasVar { + name: var_name, + var, + opt_bound_ability: None, + }, + )); } } - let alias_args = vars.iter().map(|(_, v)| v.clone()).collect::>(); + let alias_args = vars.clone(); let alias_actual = if let Type::TagUnion(tags, ext) = inner_type { let rec_var = var_store.fresh(); @@ -683,7 +732,7 @@ fn can_annotation_help( hidden_variables.extend(alias_actual.variables()); for loc_var in lowercase_vars.iter() { - hidden_variables.remove(&loc_var.value.1); + hidden_variables.remove(&loc_var.value.var); } scope.add_alias( @@ -697,8 +746,6 @@ fn can_annotation_help( let alias = scope.lookup_alias(symbol).unwrap(); local_aliases.insert(symbol, alias.clone()); - // Type::Alias(symbol, vars, Box::new(alias.typ.clone())) - if vars.is_empty() && env.home == symbol.module_id() { let actual_var = var_store.fresh(); introduced_variables.insert_host_exposed_alias(symbol, actual_var); @@ -712,7 +759,13 @@ fn can_annotation_help( } else { Type::Alias { symbol, - type_arguments: vars, + type_arguments: vars + .into_iter() + .map(|typ| OptAbleType { + typ, + opt_ability: None, + }) + .collect(), lambda_set_variables: alias.lambda_set_variables.clone(), actual: Box::new(alias.typ.clone()), kind: alias.kind, @@ -1005,7 +1058,7 @@ fn shallow_dealias_with_scope<'a>(scope: &'a mut Scope, typ: &'a Type) -> &'a Ty pub fn instantiate_and_freshen_alias_type( var_store: &mut VarStore, introduced_variables: &mut IntroducedVariables, - type_variables: &[Loc<(Lowercase, Variable)>], + type_variables: &[Loc], type_arguments: Vec, lambda_set_variables: &[LambdaSet], mut actual_type: Type, @@ -1014,8 +1067,8 @@ pub fn instantiate_and_freshen_alias_type( let mut type_var_to_arg = Vec::new(); for (loc_var, arg_ann) in type_variables.iter().zip(type_arguments.into_iter()) { - let name = loc_var.value.0.clone(); - let var = loc_var.value.1; + let name = loc_var.value.name.clone(); + let var = loc_var.value.var; substitutions.insert(var, arg_ann.clone()); type_var_to_arg.push((name.clone(), arg_ann)); @@ -1050,26 +1103,37 @@ pub fn instantiate_and_freshen_alias_type( pub fn freshen_opaque_def( var_store: &mut VarStore, opaque: &Alias, -) -> (Vec<(Lowercase, Type)>, Vec, Type) { +) -> (Vec, Vec, Type) { debug_assert!(opaque.kind == AliasKind::Opaque); - let fresh_arguments = opaque + let fresh_variables: Vec = opaque .type_variables .iter() - .map(|_| Type::Variable(var_store.fresh())) + .map(|alias_var| OptAbleVar { + var: var_store.fresh(), + opt_ability: alias_var.value.opt_bound_ability, + }) .collect(); - // TODO this gets ignored; is that a problem + let fresh_type_arguments = fresh_variables + .iter() + .map(|av| Type::Variable(av.var)) + .collect(); + + // NB: We don't introduce the fresh variables here, we introduce them during constraint gen. + // NB: If there are bugs, check whether this is a problem! let mut introduced_variables = IntroducedVariables::default(); - instantiate_and_freshen_alias_type( + let (_fresh_type_arguments, fresh_lambda_set, fresh_type) = instantiate_and_freshen_alias_type( var_store, &mut introduced_variables, &opaque.type_variables, - fresh_arguments, + fresh_type_arguments, &opaque.lambda_set_variables, opaque.typ.clone(), - ) + ); + + (fresh_variables, fresh_lambda_set, fresh_type) } fn insertion_sort_by(arr: &mut [T], mut compare: F) @@ -1229,7 +1293,7 @@ fn can_tags<'a>( // a duplicate let new_name = 'inner: loop { match tag { - Tag::Global { name, args } => { + Tag::Apply { name, args } => { let name = name.value.into(); let mut arg_types = Vec::with_capacity(args.len()); @@ -1248,32 +1312,7 @@ fn can_tags<'a>( arg_types.push(ann); } - let tag_name = TagName::Global(name); - tag_types.push((tag_name.clone(), arg_types)); - - break 'inner tag_name; - } - Tag::Private { name, args } => { - let ident_id = env.ident_ids.get_or_insert(&name.value.into()); - let symbol = Symbol::new(env.home, ident_id); - let mut arg_types = Vec::with_capacity(args.len()); - - for arg in args.iter() { - let ann = can_annotation_help( - env, - &arg.value, - arg.region, - scope, - var_store, - introduced_variables, - local_aliases, - references, - ); - - arg_types.push(ann); - } - - let tag_name = TagName::Private(symbol); + let tag_name = TagName::Tag(name); tag_types.push((tag_name.clone(), arg_types)); break 'inner tag_name; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index b4ec7e23f2..e8508e12a7 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,5 +1,5 @@ use crate::def::Def; -use crate::expr::{self, ClosureData, Expr::*, IntValue}; +use crate::expr::{self, AnnotatedMark, ClosureData, Expr::*, IntValue}; use crate::expr::{Expr, Field, Recursive}; use crate::num::{FloatBound, IntBound, IntWidth, NumericBound}; use crate::pattern::Pattern; @@ -9,7 +9,7 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; -use roc_types::subs::{VarStore, Variable}; +use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; macro_rules! macro_magic { (@single $($x:tt)*) => (()); @@ -150,6 +150,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_ANY => list_any, LIST_ALL => list_all, LIST_FIND => list_find, + LIST_IS_UNIQUE => list_is_unique, DICT_LEN => dict_len, DICT_EMPTY => dict_empty, DICT_SINGLE => dict_single, @@ -195,8 +196,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_SIN => num_sin, NUM_COS => num_cos, NUM_TAN => num_tan, - NUM_DIV_FLOAT => num_div_float, - NUM_DIV_FLOAT_CHECKED => num_div_float_checked, + NUM_DIV_FRAC => num_div_frac, + NUM_DIV_FRAC_CHECKED => num_div_frac_checked, NUM_DIV_TRUNC => num_div_trunc, NUM_DIV_TRUNC_CHECKED => num_div_trunc_checked, NUM_DIV_CEIL => num_div_ceil, @@ -204,16 +205,19 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_ABS => num_abs, NUM_NEG => num_neg, NUM_REM => num_rem, + NUM_REM_CHECKED => num_rem_checked, NUM_IS_MULTIPLE_OF => num_is_multiple_of, NUM_SQRT => num_sqrt, + NUM_SQRT_CHECKED => num_sqrt_checked, NUM_LOG => num_log, + NUM_LOG_CHECKED => num_log_checked, NUM_ROUND => num_round, NUM_IS_ODD => num_is_odd, NUM_IS_EVEN => num_is_even, NUM_IS_ZERO => num_is_zero, NUM_IS_POSITIVE => num_is_positive, NUM_IS_NEGATIVE => num_is_negative, - NUM_TO_FLOAT => num_to_float, + NUM_TO_FRAC => num_to_frac, NUM_POW => num_pow, NUM_CEILING => num_ceiling, NUM_POW_INT => num_pow_int, @@ -557,7 +561,7 @@ fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) annotation: None, }; - let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); + let body = LetNonRec(Box::new(def), Box::new(no_region(cont))); defn( symbol, @@ -713,6 +717,23 @@ fn bool_and(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +fn num_unaryop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def { + let num_var = var_store.fresh(); + let body = RunLowLevel { + op, + args: vec![(num_var, Var(Symbol::ARG_1))], + ret_var: num_var, + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1)], + var_store, + body, + num_var, + ) +} + /// Num a, Num a -> Num a fn num_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def { let num_var = var_store.fresh(); @@ -837,7 +858,7 @@ fn num_overflow_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowL annotation: None, }; - let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); + let body = LetNonRec(Box::new(def), Box::new(no_region(cont))); defn( symbol, @@ -918,74 +939,74 @@ fn num_compare(symbol: Symbol, var_store: &mut VarStore) -> Def { num_num_other_binop(symbol, var_store, LowLevel::NumCompare) } -/// Num.sin : Float -> Float +/// Num.sin : Frac -> Frac fn num_sin(symbol: Symbol, var_store: &mut VarStore) -> Def { - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumSin, - args: vec![(float_var, Var(Symbol::ARG_1))], - ret_var: float_var, + args: vec![(frac_var, Var(Symbol::ARG_1))], + ret_var: frac_var, }; defn( symbol, - vec![(float_var, Symbol::ARG_1)], + vec![(frac_var, Symbol::ARG_1)], var_store, body, - float_var, + frac_var, ) } -/// Num.cos : Float -> Float +/// Num.cos : Frac -> Frac fn num_cos(symbol: Symbol, var_store: &mut VarStore) -> Def { - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumCos, - args: vec![(float_var, Var(Symbol::ARG_1))], - ret_var: float_var, + args: vec![(frac_var, Var(Symbol::ARG_1))], + ret_var: frac_var, }; defn( symbol, - vec![(float_var, Symbol::ARG_1)], + vec![(frac_var, Symbol::ARG_1)], var_store, body, - float_var, + frac_var, ) } -/// Num.tan : Float -> Float +/// Num.tan : Frac -> Frac fn num_tan(symbol: Symbol, var_store: &mut VarStore) -> Def { - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumDivUnchecked, args: vec![ ( - float_var, + frac_var, RunLowLevel { op: LowLevel::NumSin, - args: vec![(float_var, Var(Symbol::ARG_1))], - ret_var: float_var, + args: vec![(frac_var, Var(Symbol::ARG_1))], + ret_var: frac_var, }, ), ( - float_var, + frac_var, RunLowLevel { op: LowLevel::NumCos, - args: vec![(float_var, Var(Symbol::ARG_1))], - ret_var: float_var, + args: vec![(frac_var, Var(Symbol::ARG_1))], + ret_var: frac_var, }, ), ], - ret_var: float_var, + ret_var: frac_var, }; defn( symbol, - vec![(float_var, Symbol::ARG_1)], + vec![(frac_var, Symbol::ARG_1)], var_store, body, - float_var, + frac_var, ) } @@ -1132,15 +1153,15 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.toFloat : Num * -> Float -fn num_to_float(symbol: Symbol, var_store: &mut VarStore) -> Def { +/// Num.toFrac : Num * -> Frac +fn num_to_frac(symbol: Symbol, var_store: &mut VarStore) -> Def { let arg_var = var_store.fresh(); - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let body = RunLowLevel { - op: LowLevel::NumToFloat, + op: LowLevel::NumToFrac, args: vec![(arg_var, Var(Symbol::ARG_1))], - ret_var: float_var, + ret_var: frac_var, }; defn( @@ -1148,14 +1169,19 @@ fn num_to_float(symbol: Symbol, var_store: &mut VarStore) -> Def { vec![(arg_var, Symbol::ARG_1)], var_store, body, - float_var, + frac_var, ) } -/// Num.sqrt : Float -> Result Float [ SqrtOfNegative ]* +/// Num.sqrt : Frac a -> Frac a fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_unaryop(symbol, var_store, LowLevel::NumSqrtUnchecked) +} + +/// Num.sqrtChecked : Frac a -> Result (Frac a) [ SqrtOfNegative ]* +fn num_sqrt_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); let precision_var = var_store.fresh(); let ret_var = var_store.fresh(); @@ -1167,10 +1193,10 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { no_region(RunLowLevel { op: LowLevel::NumGte, args: vec![ - (float_var, Var(Symbol::ARG_1)), + (frac_var, Var(Symbol::ARG_1)), ( - float_var, - float(unbound_zero_var, precision_var, 0.0, float_no_bound()), + frac_var, + frac(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -1179,8 +1205,8 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { "Ok", vec![RunLowLevel { op: LowLevel::NumSqrtUnchecked, - args: vec![(float_var, Var(Symbol::ARG_1))], - ret_var: float_var, + args: vec![(frac_var, Var(Symbol::ARG_1))], + ret_var: frac_var, }], var_store, )), @@ -1194,17 +1220,22 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { defn( symbol, - vec![(float_var, Symbol::ARG_1)], + vec![(frac_var, Symbol::ARG_1)], var_store, body, ret_var, ) } -/// Num.log : Float -> Result Float [ LogNeedsPositive ]* +/// Num.log : Frac a -> Frac a fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_unaryop(symbol, var_store, LowLevel::NumLogUnchecked) +} + +/// Num.logChecked : Frac a -> Result (Frac a) [ LogNeedsPositive ]* +fn num_log_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); let precision_var = var_store.fresh(); let ret_var = var_store.fresh(); @@ -1216,10 +1247,10 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { no_region(RunLowLevel { op: LowLevel::NumGt, args: vec![ - (float_var, Var(Symbol::ARG_1)), + (frac_var, Var(Symbol::ARG_1)), ( - float_var, - float(unbound_zero_var, precision_var, 0.0, float_no_bound()), + frac_var, + frac(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -1228,8 +1259,8 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { "Ok", vec![RunLowLevel { op: LowLevel::NumLogUnchecked, - args: vec![(float_var, Var(Symbol::ARG_1))], - ret_var: float_var, + args: vec![(frac_var, Var(Symbol::ARG_1))], + ret_var: frac_var, }], var_store, )), @@ -1243,69 +1274,69 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { defn( symbol, - vec![(float_var, Symbol::ARG_1)], + vec![(frac_var, Symbol::ARG_1)], var_store, body, ret_var, ) } -/// Num.round : Float -> Int +/// Num.round : Frac -> Int fn num_round(symbol: Symbol, var_store: &mut VarStore) -> Def { - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let int_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumRound, - args: vec![(float_var, Var(Symbol::ARG_1))], + args: vec![(frac_var, Var(Symbol::ARG_1))], ret_var: int_var, }; defn( symbol, - vec![(float_var, Symbol::ARG_1)], + vec![(frac_var, Symbol::ARG_1)], var_store, body, int_var, ) } -/// Num.pow : Float, Float -> Float +/// Num.pow : Frac, Frac -> Frac fn num_pow(symbol: Symbol, var_store: &mut VarStore) -> Def { - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumPow, args: vec![ - (float_var, Var(Symbol::ARG_1)), - (float_var, Var(Symbol::ARG_2)), + (frac_var, Var(Symbol::ARG_1)), + (frac_var, Var(Symbol::ARG_2)), ], - ret_var: float_var, + ret_var: frac_var, }; defn( symbol, - vec![(float_var, Symbol::ARG_1), (float_var, Symbol::ARG_2)], + vec![(frac_var, Symbol::ARG_1), (frac_var, Symbol::ARG_2)], var_store, body, - float_var, + frac_var, ) } -/// Num.ceiling : Float -> Int +/// Num.ceiling : Frac -> Int fn num_ceiling(symbol: Symbol, var_store: &mut VarStore) -> Def { - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let int_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumCeiling, - args: vec![(float_var, Var(Symbol::ARG_1))], + args: vec![(frac_var, Var(Symbol::ARG_1))], ret_var: int_var, }; defn( symbol, - vec![(float_var, Symbol::ARG_1)], + vec![(frac_var, Symbol::ARG_1)], var_store, body, int_var, @@ -1331,83 +1362,83 @@ fn num_pow_int(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.floor : Float -> Int +/// Num.floor : Frac -> Int fn num_floor(symbol: Symbol, var_store: &mut VarStore) -> Def { - let float_var = var_store.fresh(); + let frac_var = var_store.fresh(); let int_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumFloor, - args: vec![(float_var, Var(Symbol::ARG_1))], + args: vec![(frac_var, Var(Symbol::ARG_1))], ret_var: int_var, }; defn( symbol, - vec![(float_var, Symbol::ARG_1)], + vec![(frac_var, Symbol::ARG_1)], var_store, body, int_var, ) } -/// Num.atan : Float -> Float +/// Num.atan : Frac -> Frac fn num_atan(symbol: Symbol, var_store: &mut VarStore) -> Def { - let arg_float_var = var_store.fresh(); - let ret_float_var = var_store.fresh(); + let arg_frac_var = var_store.fresh(); + let ret_frac_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumAtan, - args: vec![(arg_float_var, Var(Symbol::ARG_1))], - ret_var: ret_float_var, + args: vec![(arg_frac_var, Var(Symbol::ARG_1))], + ret_var: ret_frac_var, }; defn( symbol, - vec![(arg_float_var, Symbol::ARG_1)], + vec![(arg_frac_var, Symbol::ARG_1)], var_store, body, - ret_float_var, + ret_frac_var, ) } -/// Num.acos : Float -> Float +/// Num.acos : Frac -> Frac fn num_acos(symbol: Symbol, var_store: &mut VarStore) -> Def { - let arg_float_var = var_store.fresh(); - let ret_float_var = var_store.fresh(); + let arg_frac_var = var_store.fresh(); + let ret_frac_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumAcos, - args: vec![(arg_float_var, Var(Symbol::ARG_1))], - ret_var: ret_float_var, + args: vec![(arg_frac_var, Var(Symbol::ARG_1))], + ret_var: ret_frac_var, }; defn( symbol, - vec![(arg_float_var, Symbol::ARG_1)], + vec![(arg_frac_var, Symbol::ARG_1)], var_store, body, - ret_float_var, + ret_frac_var, ) } -/// Num.asin : Float -> Float +/// Num.asin : Frac -> Frac fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def { - let arg_float_var = var_store.fresh(); - let ret_float_var = var_store.fresh(); + let arg_frac_var = var_store.fresh(); + let ret_frac_var = var_store.fresh(); let body = RunLowLevel { op: LowLevel::NumAsin, - args: vec![(arg_float_var, Var(Symbol::ARG_1))], - ret_var: ret_float_var, + args: vec![(arg_frac_var, Var(Symbol::ARG_1))], + ret_var: ret_frac_var, }; defn( symbol, - vec![(arg_float_var, Symbol::ARG_1)], + vec![(arg_frac_var, Symbol::ARG_1)], var_store, body, - ret_float_var, + ret_frac_var, ) } @@ -1631,7 +1662,7 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def { annotation: None, }; - let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); + let body = LetNonRec(Box::new(def), Box::new(no_region(cont))); defn( symbol, @@ -1867,7 +1898,7 @@ fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def { ), }; - let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); + let body = LetNonRec(Box::new(def), Box::new(no_region(cont))); defn( symbol, @@ -1972,7 +2003,7 @@ fn str_from_utf8_range(symbol: Symbol, var_store: &mut VarStore) -> Def { ), }; - let roc_result = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var); + let roc_result = LetNonRec(Box::new(def), Box::new(no_region(cont))); // Only do the business with the let if we're in bounds! @@ -2476,7 +2507,10 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def { let get_sub = RunLowLevel { op: LowLevel::NumSubWrap, - args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))], + args: vec![ + (len_var, get_list_len.clone()), + (len_var, Var(Symbol::ARG_2)), + ], ret_var: len_var, }; @@ -2486,7 +2520,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def { branches: vec![( no_region(RunLowLevel { op: LowLevel::NumGt, - args: vec![(len_var, get_sub.clone()), (len_var, zero.clone())], + args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))], ret_var: bool_var, }), no_region(get_sub), @@ -2592,8 +2626,16 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def { recursive: Recursive::NotRecursive, captured_symbols: vec![(sep_sym, sep_var)], arguments: vec![ - (clos_acc_var, no_region(Pattern::Identifier(clos_acc_sym))), - (sep_var, no_region(Pattern::Identifier(clos_elem_sym))), + ( + clos_acc_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(clos_acc_sym)), + ), + ( + sep_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(clos_elem_sym)), + ), ], loc_body: { let append_sep = RunLowLevel { @@ -2678,9 +2720,14 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def { arguments: vec![ ( clos_start_var, + AnnotatedMark::new(var_store), no_region(Pattern::Identifier(clos_start_sym)), ), - (clos_len_var, no_region(Pattern::Identifier(clos_len_sym))), + ( + clos_len_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(clos_len_sym)), + ), ], loc_body: { Box::new(no_region(RunLowLevel { @@ -2864,7 +2911,11 @@ fn list_drop_if(symbol: Symbol, var_store: &mut VarStore) -> Def { name: Symbol::LIST_DROP_IF_PREDICATE, recursive: Recursive::NotRecursive, captured_symbols: vec![(sym_predicate, t_predicate)], - arguments: vec![(t_elem, no_region(Pattern::Identifier(Symbol::ARG_3)))], + arguments: vec![( + t_elem, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_3)), + )], loc_body: { let should_drop = Call( Box::new(( @@ -3048,8 +3099,16 @@ fn list_join_map(symbol: Symbol, var_store: &mut VarStore) -> Def { recursive: Recursive::NotRecursive, captured_symbols: vec![(Symbol::ARG_2, before2list_after)], arguments: vec![ - (list_after, no_region(Pattern::Identifier(Symbol::ARG_3))), - (before, no_region(Pattern::Identifier(Symbol::ARG_4))), + ( + list_after, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_3)), + ), + ( + before, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_4)), + ), ], loc_body: { let mapper = Box::new(( @@ -3579,8 +3638,16 @@ fn list_sort_desc(symbol: Symbol, var_store: &mut VarStore) -> Def { recursive: Recursive::NotRecursive, captured_symbols: vec![], arguments: vec![ - (num_var, no_region(Pattern::Identifier(Symbol::ARG_2))), - (num_var, no_region(Pattern::Identifier(Symbol::ARG_3))), + ( + num_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_2)), + ), + ( + num_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_3)), + ), ], loc_body: { Box::new(no_region(RunLowLevel { @@ -3692,11 +3759,7 @@ fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def { final_else: Box::new(no_region(make_err)), }; - let body = LetNonRec( - Box::new(find_result_def), - Box::new(no_region(inspect)), - t_ret, - ); + let body = LetNonRec(Box::new(find_result_def), Box::new(no_region(inspect))); defn( symbol, @@ -3707,6 +3770,11 @@ fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.isUnique : List * -> Bool +fn list_is_unique(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_1(symbol, LowLevel::ListIsUnique, var_store) +} + /// Dict.len : Dict * * -> Nat fn dict_len(symbol: Symbol, var_store: &mut VarStore) -> Def { let arg1_var = var_store.fresh(); @@ -3861,7 +3929,7 @@ fn dict_get(symbol: Symbol, var_store: &mut VarStore) -> Def { final_else: Box::new(no_region(make_err)), }; - let body = LetNonRec(Box::new(def), Box::new(no_region(inspect)), ret_var); + let body = LetNonRec(Box::new(def), Box::new(no_region(inspect))); defn( symbol, @@ -4054,9 +4122,21 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def { recursive: Recursive::NotRecursive, captured_symbols: vec![(Symbol::ARG_3, func_var)], arguments: vec![ - (accum_var, no_region(Pattern::Identifier(Symbol::ARG_5))), - (key_var, no_region(Pattern::Identifier(Symbol::ARG_6))), - (Variable::EMPTY_RECORD, no_region(Pattern::Underscore)), + ( + accum_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_5)), + ), + ( + key_var, + AnnotatedMark::new(var_store), + no_region(Pattern::Identifier(Symbol::ARG_6)), + ), + ( + Variable::EMPTY_RECORD, + AnnotatedMark::new(var_store), + no_region(Pattern::Underscore), + ), ], loc_body: Box::new(no_region(call_func)), }); @@ -4084,8 +4164,13 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.rem : Int a, Int a -> Result (Int a) [ DivByZero ]* +/// Num.rem : Int a, Int a -> Int a fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumRemUnchecked) +} + +/// Num.remChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* +fn num_rem_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); let bool_var = var_store.fresh(); @@ -4186,13 +4271,13 @@ fn num_abs(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.div : Float, Float -> Float -fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { +/// Num.div : Frac, Frac -> Frac +fn num_div_frac(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumDivUnchecked) } -/// Num.divChecked : Float, Float -> Result Float [ DivByZero ]* -fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { +/// Num.divChecked : Frac, Frac -> Result Frac [ DivByZero ]* +fn num_div_frac_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); @@ -4212,7 +4297,7 @@ fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - float(unbound_zero_var, precision_var, 0.0, float_no_bound()), + frac(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -4220,7 +4305,7 @@ fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { ), // denominator was not zero no_region( - // Ok (Float.#divUnchecked numerator denominator) + // Ok (Frac.#divUnchecked numerator denominator) tag( "Ok", vec![ @@ -4627,7 +4712,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { CalledVia::Space, ); - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); // ok branch let ok = Tag { @@ -4651,6 +4736,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(ok), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4658,7 +4744,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let err = Tag { variant_var: var_store.fresh(), @@ -4681,6 +4767,7 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(err), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4692,6 +4779,8 @@ fn result_map(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -4724,7 +4813,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { CalledVia::Space, ); - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); // ok branch let ok = Tag { @@ -4748,6 +4837,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(ok), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4755,7 +4845,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); let err = Tag { variant_var: var_store.fresh(), @@ -4778,6 +4868,7 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(err), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4789,6 +4880,8 @@ fn result_map_err(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -4808,7 +4901,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { { // ok branch - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4821,6 +4914,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(Var(Symbol::ARG_3)), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4828,7 +4922,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4841,6 +4935,7 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(Var(Symbol::ARG_2)), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4852,6 +4947,8 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -4871,7 +4968,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { { // ok branch - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4883,7 +4980,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { let false_expr = Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global("False".into()), + name: TagName::Tag("False".into()), arguments: vec![], }; @@ -4891,6 +4988,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(false_expr), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4898,7 +4996,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4910,7 +5008,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { let true_expr = Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global("True".into()), + name: TagName::Tag("True".into()), arguments: vec![], }; @@ -4918,6 +5016,7 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(true_expr), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4929,6 +5028,8 @@ fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -4948,7 +5049,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { { // ok branch - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4960,7 +5061,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { let true_expr = Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global("True".into()), + name: TagName::Tag("True".into()), arguments: vec![], }; @@ -4968,6 +5069,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(true_expr), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -4975,7 +5077,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let pattern = Pattern::AppliedTag { whole_var: result_var, @@ -4987,7 +5089,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { let false_expr = Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global("False".into()), + name: TagName::Tag("False".into()), arguments: vec![], }; @@ -4995,6 +5097,7 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(false_expr), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5006,6 +5109,8 @@ fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -5038,7 +5143,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { CalledVia::Space, ); - let tag_name = TagName::Global("Ok".into()); + let tag_name = TagName::Tag("Ok".into()); // ok branch let ok = call_func; @@ -5057,6 +5162,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(ok), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5064,7 +5170,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { { // err branch - let tag_name = TagName::Global("Err".into()); + let tag_name = TagName::Tag("Err".into()); let err = Tag { variant_var: var_store.fresh(), @@ -5087,6 +5193,7 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { patterns: vec![no_region(pattern)], value: no_region(err), guard: None, + redundant: RedundantMark::new(var_store), }; branches.push(branch); @@ -5098,6 +5205,8 @@ fn result_after(symbol: Symbol, var_store: &mut VarStore) -> Def { region: Region::zero(), loc_cond: Box::new(no_region(Var(Symbol::ARG_1))), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; defn( @@ -5122,7 +5231,7 @@ fn tag(name: &'static str, args: Vec, var_store: &mut VarStore) -> Expr { Expr::Tag { variant_var: var_store.fresh(), ext_var: var_store.fresh(), - name: TagName::Global(name.into()), + name: TagName::Tag(name.into()), arguments: args .into_iter() .map(|expr| (var_store.fresh(), no_region(expr))) @@ -5283,7 +5392,13 @@ fn defn_help( let closure_args = args .into_iter() - .map(|(var, symbol)| (var, no_region(Identifier(symbol)))) + .map(|(var, symbol)| { + ( + var, + AnnotatedMark::new(var_store), + no_region(Identifier(symbol)), + ) + }) .collect(); Closure(ClosureData { @@ -5327,7 +5442,7 @@ where } #[inline(always)] -fn float(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -> Expr { +fn frac(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -> Expr { Float( num_var, precision_var, diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index c858834263..42e416b014 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -1,9 +1,10 @@ +use crate::exhaustive::{ExhaustiveContext, SketchedRows}; use crate::expected::{Expected, PExpected}; use roc_collections::soa::{EitherIndex, Index, Slice}; use roc_module::ident::TagName; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; -use roc_types::subs::Variable; +use roc_types::subs::{ExhaustiveMark, Variable}; use roc_types::types::{Category, PatternCategory, Type}; #[derive(Debug)] @@ -19,6 +20,9 @@ pub struct Constraints { pub pattern_expectations: Vec>, pub includes_tags: Vec, pub strings: Vec<&'static str>, + pub sketched_rows: Vec, + pub eq: Vec, + pub pattern_eq: Vec, } impl Default for Constraints { @@ -40,6 +44,9 @@ impl Constraints { let pattern_expectations = Vec::new(); let includes_tags = Vec::new(); let strings = Vec::new(); + let sketched_rows = Vec::new(); + let eq = Vec::new(); + let pattern_eq = Vec::new(); types.extend([ Type::EmptyRec, @@ -90,6 +97,9 @@ impl Constraints { pattern_expectations, includes_tags, strings, + sketched_rows, + eq, + pattern_eq, } } @@ -225,7 +235,7 @@ impl Constraints { 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) + Constraint::Eq(Eq(type_index, expected_index, category_index, region)) } #[inline(always)] @@ -240,7 +250,7 @@ impl Constraints { 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) + Constraint::Eq(Eq(type_index, expected_index, category_index, region)) } #[inline(always)] @@ -256,17 +266,17 @@ impl Constraints { let expected_index = Index::push_new(&mut self.expectations, expected); let category_index = Self::push_category(self, category); - let equal = Constraint::Eq(type_index, expected_index, category_index, region); + let equal = Constraint::Eq(Eq(type_index, expected_index, category_index, region)); let storage_type_index = Self::push_type_variable(storage_var); let storage_category = Category::Storage(std::file!(), std::line!()); let storage_category_index = Self::push_category(self, storage_category); - let storage = Constraint::Eq( + let storage = Constraint::Eq(Eq( storage_type_index, expected_index, storage_category_index, region, - ); + )); self.and_constraint([equal, storage]) } @@ -544,11 +554,6 @@ impl Constraints { 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()]; @@ -567,9 +572,15 @@ impl Constraints { .iter() .any(|c| self.contains_save_the_environment(c)) } - Constraint::IsOpenType(_) => false, - Constraint::IncludesTag(_) => false, - Constraint::PatternPresence(_, _, _, _) => false, + Constraint::Eq(..) + | Constraint::Store(..) + | Constraint::Lookup(..) + | Constraint::Pattern(..) + | Constraint::True + | Constraint::IsOpenType(_) + | Constraint::IncludesTag(_) + | Constraint::PatternPresence(_, _, _, _) + | Constraint::Exhaustive { .. } => false, } } @@ -597,18 +608,65 @@ impl Constraints { Constraint::Store(type_index, variable, string_index, line_number) } + + pub fn exhaustive( + &mut self, + real_var: Variable, + real_region: Region, + category_and_expectation: Result< + (Category, Expected), + (PatternCategory, PExpected), + >, + sketched_rows: SketchedRows, + context: ExhaustiveContext, + exhaustive: ExhaustiveMark, + ) -> Constraint { + let real_var = Self::push_type_variable(real_var); + let sketched_rows = Index::push_new(&mut self.sketched_rows, sketched_rows); + + let equality = match category_and_expectation { + Ok((category, expected)) => { + let category = Index::push_new(&mut self.categories, category); + let expected = Index::push_new(&mut self.expectations, expected); + let equality = Eq(real_var, expected, category, real_region); + let equality = Index::push_new(&mut self.eq, equality); + Ok(equality) + } + Err((category, expected)) => { + let category = Index::push_new(&mut self.pattern_categories, category); + let expected = Index::push_new(&mut self.pattern_expectations, expected); + let equality = PatternEq(real_var, expected, category, real_region); + let equality = Index::push_new(&mut self.pattern_eq, equality); + Err(equality) + } + }; + + Constraint::Exhaustive(equality, sketched_rows, context, exhaustive) + } } roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8); +roc_error_macros::assert_sizeof_aarch64!(Constraint, 3 * 8); -#[derive(Clone)] +#[derive(Clone, Copy, Debug)] +pub struct Eq( + pub EitherIndex, + pub Index>, + pub Index, + pub Region, +); + +#[derive(Clone, Copy, Debug)] +pub struct PatternEq( + pub EitherIndex, + pub Index>, + pub Index, + pub Region, +); + +#[derive(Clone, Copy)] pub enum Constraint { - Eq( - EitherIndex, - Index>, - Index, - Region, - ), + Eq(Eq), Store( EitherIndex, Variable, @@ -641,6 +699,12 @@ pub enum Constraint { Index, Region, ), + Exhaustive( + Result, Index>, + Index, + ExhaustiveContext, + ExhaustiveMark, + ), } #[derive(Debug, Clone, Copy, Default)] @@ -670,7 +734,7 @@ pub struct IncludesTag { impl std::fmt::Debug for Constraint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Eq(arg0, arg1, arg2, arg3) => { + Self::Eq(Eq(arg0, arg1, arg2, arg3)) => { write!(f, "Eq({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3) } Self::Store(arg0, arg1, arg2, arg3) => { @@ -695,6 +759,13 @@ impl std::fmt::Debug for Constraint { arg0, arg1, arg2, arg3 ) } + Self::Exhaustive(arg0, arg1, arg2, arg3) => { + write!( + f, + "Exhaustive({:?}, {:?}, {:?}, {:?})", + arg0, arg1, arg2, arg3 + ) + } } } } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 4d764d337e..f3b4b0569f 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1,17 +1,21 @@ use crate::abilities::MemberVariables; use crate::annotation::canonicalize_annotation; +use crate::annotation::find_type_def_symbols; use crate::annotation::IntroducedVariables; +use crate::annotation::OwnedNamedOrAble; use crate::env::Env; +use crate::expr::AnnotatedMark; use crate::expr::ClosureData; use crate::expr::Expr::{self, *}; use crate::expr::{canonicalize_expr, Output, Recursive}; -use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern}; +use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern}; use crate::procedure::References; -use crate::reference_matrix::ReferenceMatrix; -use crate::reference_matrix::TopologicalSort; use crate::scope::create_alias; use crate::scope::Scope; -use roc_collections::{default_hasher, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_collections::ReferenceMatrix; +use roc_collections::VecMap; +use roc_collections::{ImSet, MutMap, SendMap}; +use roc_module::ident::Ident; use roc_module::ident::Lowercase; use roc_module::symbol::IdentId; use roc_module::symbol::ModuleId; @@ -26,11 +30,10 @@ use roc_problem::can::{CycleEntry, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::AliasKind; +use roc_types::types::AliasVar; use roc_types::types::LambdaSet; use roc_types::types::{Alias, Type}; -use std::collections::HashMap; use std::fmt::Debug; -use ven_graph::topological_sort; #[derive(Clone, Debug)] pub struct Def { @@ -50,10 +53,11 @@ pub struct Annotation { } #[derive(Debug)] -pub struct CanDefs { - pub refs_by_symbol: MutMap, - pub can_defs_by_symbol: MutMap, - pub aliases: SendMap, +pub(crate) struct CanDefs { + defs: Vec>, + def_ordering: DefOrdering, + pub(crate) abilities_in_scope: Vec, + aliases: VecMap, } /// A Def that has had patterns and type annnotations canonicalized, @@ -82,6 +86,16 @@ enum PendingValueDef<'a> { ), } +impl PendingValueDef<'_> { + fn loc_pattern(&self) -> &Loc { + match self { + PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern, + PendingValueDef::Body(_, loc_pattern, _) => loc_pattern, + PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern, + } + } +} + #[derive(Debug, Clone)] enum PendingTypeDef<'a> { /// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively. @@ -98,14 +112,45 @@ enum PendingTypeDef<'a> { }, /// An invalid alias, that is ignored in the rest of the pipeline - /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` + /// e.g. a definition like `MyAlias 1 : Int` /// with an incorrect pattern - #[allow(dead_code)] - InvalidAlias { kind: AliasKind }, + InvalidAlias { + #[allow(dead_code)] + kind: AliasKind, + symbol: Symbol, + region: Region, + }, + + /// An alias with a name that shadows another symbol + ShadowedAlias, /// An invalid ability, that is ignored in the rest of the pipeline. /// E.g. a shadowed ability, or with a bad definition. - InvalidAbility, + InvalidAbility { + symbol: Symbol, + region: Region, + }, + + AbilityNotOnToplevel, + AbilityShadows, +} + +impl PendingTypeDef<'_> { + fn introduction(&self) -> Option<(Symbol, Region)> { + match self { + PendingTypeDef::Alias { name, ann, .. } => { + let region = Region::span_across(&name.region, &ann.region); + + Some((name.value, region)) + } + PendingTypeDef::Ability { name, .. } => Some((name.value, name.region)), + PendingTypeDef::InvalidAlias { symbol, region, .. } => Some((*symbol, *region)), + PendingTypeDef::ShadowedAlias { .. } => None, + PendingTypeDef::InvalidAbility { symbol, region } => Some((*symbol, *region)), + PendingTypeDef::AbilityNotOnToplevel => None, + PendingTypeDef::AbilityShadows => None, + } + } } // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. @@ -132,77 +177,40 @@ impl Declaration { /// Returns a topologically sorted sequence of alias/opaque names fn sort_type_defs_before_introduction( - mut referenced_symbols: MutMap>, + referenced_symbols: VecMap>, ) -> Vec { - let defined_symbols: Vec = referenced_symbols.keys().copied().collect(); + let capacity = referenced_symbols.len(); + let mut matrix = ReferenceMatrix::new(capacity); - // find the strongly connected components and their relations - let sccs = { - // only retain symbols from the current set of defined symbols; the rest come from other modules - for v in referenced_symbols.iter_mut() { - v.1.retain(|x| defined_symbols.iter().any(|s| s == x)); - } + let (symbols, referenced) = referenced_symbols.unzip(); - let all_successors_with_self = |symbol: &Symbol| referenced_symbols[symbol].iter().copied(); - - ven_graph::strongly_connected_components(&defined_symbols, all_successors_with_self) - }; - - // then sort the strongly connected components - let groups: Vec<_> = (0..sccs.len()).collect(); - let mut group_symbols: Vec> = vec![Vec::new(); groups.len()]; - - let mut symbol_to_group_index = MutMap::default(); - let mut group_to_groups = vec![Vec::new(); groups.len()]; - - for (index, group) in sccs.iter().enumerate() { - for s in group { - symbol_to_group_index.insert(*s, index); - } - } - - for (index, group) in sccs.iter().enumerate() { - for s in group { - let reachable = &referenced_symbols[s]; - for r in reachable { - let new_index = symbol_to_group_index[r]; - - if new_index != index { - group_to_groups[index].push(new_index); - } + for (index, references) in referenced.iter().enumerate() { + for referenced in references { + match symbols.iter().position(|k| k == referenced) { + None => { /* not defined in this scope */ } + Some(ref_index) => matrix.set_row_col(index, ref_index, true), } } } - for v in group_symbols.iter_mut() { - v.sort(); - v.dedup(); - } - - let all_successors_with_self = |group: &usize| group_to_groups[*group].iter().copied(); - - // split into self-recursive and mutually recursive - match topological_sort(&groups, all_successors_with_self) { - Ok(result) => result - .iter() - .rev() - .flat_map(|group_index| sccs[*group_index].iter()) - .copied() - .collect(), - - Err(_loop_detected) => unreachable!("the groups cannot recurse"), - } + // find the strongly connected components and their relations + matrix + .strongly_connected_components_all() + .groups() + .flat_map(|group| group.iter_ones()) + .map(|index| symbols[index]) + .collect() } #[inline(always)] -pub fn canonicalize_defs<'a>( +pub(crate) fn canonicalize_defs<'a>( env: &mut Env<'a>, mut output: Output, var_store: &mut VarStore, - original_scope: &Scope, + scope: &mut Scope, loc_defs: &'a [&'a Loc>], pattern_type: PatternType, -) -> (CanDefs, Scope, Output, MutMap) { +) -> (CanDefs, Output, MutMap) { // Canonicalizing defs while detecting shadowing involves a multi-step process: // // 1. Go through each of the patterns. @@ -220,41 +228,21 @@ pub fn canonicalize_defs<'a>( // This naturally handles recursion too, because a given expr which refers // to itself won't be processed until after its def has been added to scope. - // Record both the original and final idents from the scope, - // so we can diff them while detecting unused defs. - let mut scope = original_scope.clone(); let num_defs = loc_defs.len(); - let mut refs_by_symbol = MutMap::default(); - let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); - - let mut type_defs = Vec::with_capacity(num_defs); + let mut pending_type_defs = Vec::with_capacity(num_defs); let mut value_defs = Vec::with_capacity(num_defs); for loc_def in loc_defs { match loc_def.value.unroll_def() { - Ok(type_def) => type_defs.push(Loc::at(loc_def.region, type_def)), + Ok(type_def) => { + pending_type_defs.push(to_pending_type_def(env, type_def, scope, pattern_type)) + } Err(value_def) => value_defs.push(Loc::at(loc_def.region, value_def)), } } - // We need to canonicalize all the type defs first. - // Clippy is wrong - we do need the collect, otherwise "env" and "scope" are captured for - // longer than we'd like. - #[allow(clippy::needless_collect)] - let pending_type_defs = type_defs - .into_iter() - .filter_map(|loc_def| { - to_pending_type_def(env, loc_def.value, &mut scope, pattern_type).map( - |(new_output, pending_def)| { - output.union(new_output); - pending_def - }, - ) - }) - .collect::>(); - if cfg!(debug_assertions) { - env.home.register_debug_idents(&env.ident_ids); + scope.register_debug_idents(); } enum TypeDef<'a> { @@ -270,9 +258,16 @@ pub fn canonicalize_defs<'a>( let mut type_defs = MutMap::default(); let mut abilities_in_scope = Vec::new(); - let mut referenced_type_symbols = MutMap::default(); + let mut referenced_type_symbols = VecMap::default(); + + // Determine which idents we introduced in the course of this process. + let mut symbols_introduced = MutMap::default(); for pending_def in pending_type_defs.into_iter() { + if let Some((symbol, region)) = pending_def.introduction() { + symbols_introduced.insert(symbol, region); + } + match pending_def { PendingTypeDef::Alias { name, @@ -280,11 +275,7 @@ pub fn canonicalize_defs<'a>( ann, kind, } => { - let referenced_symbols = crate::annotation::find_type_def_symbols( - env.home, - &mut env.ident_ids, - &ann.value, - ); + let referenced_symbols = find_type_def_symbols(scope, &ann.value); referenced_type_symbols.insert(name.value, referenced_symbols); @@ -297,24 +288,23 @@ pub fn canonicalize_defs<'a>( // Add the referenced type symbols of each member function. We need to make // sure those are processed first before we resolve the whole ability // definition. - referenced_symbols.extend(crate::annotation::find_type_def_symbols( - env.home, - &mut env.ident_ids, - &member.typ.value, - )); + referenced_symbols.extend(find_type_def_symbols(scope, &member.typ.value)); } referenced_type_symbols.insert(name.value, referenced_symbols); type_defs.insert(name.value, TypeDef::Ability(name, members)); abilities_in_scope.push(name.value); } - PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } => { /* ignore */ - } + PendingTypeDef::InvalidAlias { .. } + | PendingTypeDef::InvalidAbility { .. } + | PendingTypeDef::AbilityShadows + | PendingTypeDef::ShadowedAlias { .. } + | PendingTypeDef::AbilityNotOnToplevel => { /* ignore */ } } } let sorted = sort_type_defs_before_introduction(referenced_type_symbols); - let mut aliases = SendMap::default(); + let mut aliases = VecMap::default(); let mut abilities = MutMap::default(); for type_name in sorted { @@ -323,45 +313,50 @@ pub fn canonicalize_defs<'a>( let symbol = name.value; let can_ann = canonicalize_annotation( env, - &mut scope, + scope, &ann.value, ann.region, var_store, &abilities_in_scope, ); - // Does this alias reference any abilities? For now, we don't permit that. - let ability_references = can_ann - .references - .iter() - .filter_map(|&ty_ref| abilities_in_scope.iter().find(|&&name| name == ty_ref)) - .collect::>(); - - if let Some(one_ability_ref) = ability_references.first() { - env.problem(Problem::AliasUsesAbility { - loc_name: name, - ability: **one_ability_ref, - }); - } - // Record all the annotation's references in output.references.lookups for symbol in can_ann.references { output.references.insert_type_lookup(symbol); } - let mut can_vars: Vec> = Vec::with_capacity(vars.len()); + let mut can_vars: Vec> = Vec::with_capacity(vars.len()); let mut is_phantom = false; - let mut named = can_ann.introduced_variables.named; + let IntroducedVariables { + named, + able, + wildcards, + inferred, + .. + } = can_ann.introduced_variables; + + let mut named: Vec<_> = (named.into_iter().map(OwnedNamedOrAble::Named)) + .chain(able.into_iter().map(OwnedNamedOrAble::Able)) + .collect(); for loc_lowercase in vars.iter() { - let opt_index = named.iter().position(|nv| nv.name == loc_lowercase.value); + let opt_index = named + .iter() + .position(|nv| nv.ref_name() == &loc_lowercase.value); match opt_index { Some(index) => { // This is a valid lowercase rigid var for the type def. let named_variable = named.swap_remove(index); + let var = named_variable.variable(); + let opt_bound_ability = named_variable.opt_ability(); + let name = named_variable.name(); can_vars.push(Loc { - value: (named_variable.name, named_variable.variable), + value: AliasVar { + name, + var, + opt_bound_ability, + }, region: loc_lowercase.region, }); } @@ -382,16 +377,11 @@ pub fn canonicalize_defs<'a>( continue; } - let IntroducedVariables { - wildcards, - inferred, - .. - } = can_ann.introduced_variables; let num_unbound = named.len() + wildcards.len() + inferred.len(); if num_unbound > 0 { let one_occurrence = named .iter() - .map(|nv| Loc::at(nv.first_seen, nv.variable)) + .map(|nv| Loc::at(nv.first_seen(), nv.variable())) .chain(wildcards) .chain(inferred) .next() @@ -416,7 +406,8 @@ pub fn canonicalize_defs<'a>( can_ann.typ.clone(), kind, ); - aliases.insert(symbol, alias.clone()); + + aliases.insert(symbol, alias); } TypeDef::Ability(name, members) => { @@ -430,6 +421,7 @@ pub fn canonicalize_defs<'a>( // Now that we know the alias dependency graph, we can try to insert recursion variables // where aliases are recursive tag unions, or detect illegal recursions. let mut aliases = correct_mutual_recursive_type_alias(env, aliases, var_store); + for (symbol, alias) in aliases.iter() { scope.add_alias( *symbol, @@ -440,18 +432,126 @@ pub fn canonicalize_defs<'a>( ); } - // Now we can go through and resolve all pending abilities, to add them to scope. + // Resolve all pending abilities, to add them to scope. + resolve_abilities( + env, + &mut output, + var_store, + scope, + abilities, + &abilities_in_scope, + pattern_type, + ); + + // Now that we have the scope completely assembled, and shadowing resolved, + // we're ready to canonicalize any body exprs. + + // Canonicalize all the patterns, record shadowing problems, and store + // the ast::Expr values in pending_exprs for further canonicalization + // once we've finished assembling the entire scope. + let mut pending_value_defs = Vec::with_capacity(value_defs.len()); + for loc_def in value_defs.into_iter() { + let mut new_output = Output::default(); + match to_pending_value_def( + env, + var_store, + loc_def.value, + scope, + &mut new_output, + pattern_type, + ) { + None => { /* skip */ } + Some(pending_def) => { + // Record the ast::Expr for later. We'll do another pass through these + // once we have the entire scope assembled. If we were to canonicalize + // the exprs right now, they wouldn't have symbols in scope from defs + // that get would have gotten added later in the defs list! + pending_value_defs.push(pending_def); + output.union(new_output); + } + } + } + + let mut symbol_to_index: Vec<(IdentId, u32)> = Vec::with_capacity(pending_value_defs.len()); + + for (def_index, pending_def) in pending_value_defs.iter().enumerate() { + for (s, r) in BindingsFromPattern::new(pending_def.loc_pattern()) { + // store the top-level defs, used to ensure that closures won't capture them + if let PatternType::TopLevelDef = pattern_type { + env.top_level_symbols.insert(s); + } + + symbols_introduced.insert(s, r); + + debug_assert_eq!(env.home, s.module_id()); + debug_assert!( + !symbol_to_index.iter().any(|(id, _)| *id == s.ident_id()), + "{:?}", + s + ); + + symbol_to_index.push((s.ident_id(), def_index as u32)); + } + } + + let capacity = pending_value_defs.len(); + let mut defs = Vec::with_capacity(capacity); + let mut def_ordering = DefOrdering::from_symbol_to_id(env.home, symbol_to_index, capacity); + + for (def_id, pending_def) in pending_value_defs.into_iter().enumerate() { + let temp_output = canonicalize_pending_value_def( + env, + pending_def, + output, + scope, + var_store, + pattern_type, + &mut aliases, + &abilities_in_scope, + ); + + output = temp_output.output; + + defs.push(Some(temp_output.def)); + + def_ordering.insert_symbol_references(def_id as u32, &temp_output.references) + } + + ( + CanDefs { + defs, + def_ordering, + abilities_in_scope, + // The result needs a thread-safe `SendMap` + aliases, + }, + output, + symbols_introduced, + ) +} + +/// Resolve all pending abilities, to add them to scope. +#[allow(clippy::too_many_arguments)] +fn resolve_abilities<'a>( + env: &mut Env<'a>, + output: &mut Output, + var_store: &mut VarStore, + scope: &mut Scope, + abilities: MutMap, &[AbilityMember])>, + abilities_in_scope: &[Symbol], + pattern_type: PatternType, +) { for (loc_ability_name, members) in abilities.into_values() { let mut can_members = Vec::with_capacity(members.len()); for member in members { let member_annot = canonicalize_annotation( env, - &mut scope, + scope, &member.typ.value, member.typ.region, var_store, - &abilities_in_scope, + abilities_in_scope, ); // Record all the annotation's references in output.references.lookups @@ -462,12 +562,7 @@ pub fn canonicalize_defs<'a>( let name_region = member.name.region; let member_name = member.name.extract_spaces().item; - let member_sym = match scope.introduce( - member_name.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - name_region, - ) { + let member_sym = match scope.introduce(member_name.into(), name_region) { Ok(sym) => sym, Err((original_region, shadow, _new_symbol)) => { env.problem(roc_problem::can::Problem::Shadowing { @@ -486,12 +581,14 @@ pub fn canonicalize_defs<'a>( // What variables in the annotation are bound to the parent ability, and what variables // are bound to some other ability? - let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) = - member_annot - .introduced_variables - .able - .iter() - .partition(|av| av.ability == loc_ability_name.value); + let (variables_bound_to_ability, _variables_bound_to_other_abilities): ( + Vec<_>, + Vec<_>, + ) = member_annot + .introduced_variables + .able + .iter() + .partition(|av| av.ability == loc_ability_name.value); let mut bad_has_clauses = false; @@ -526,18 +623,6 @@ pub fn canonicalize_defs<'a>( bad_has_clauses = true; } - if !variables_bound_to_other_abilities.is_empty() { - // Disallow variables bound to other abilities, for now. - for bad_variable in variables_bound_to_other_abilities.iter() { - env.problem(Problem::AbilityMemberBindsExternalAbility { - member: member_sym, - ability: loc_ability_name.value, - region: bad_variable.first_seen, - }); - } - bad_has_clauses = true; - } - if bad_has_clauses { // Pretend the member isn't a part of the ability continue; @@ -570,97 +655,6 @@ pub fn canonicalize_defs<'a>( .abilities_store .register_ability(loc_ability_name.value, can_members); } - - // Now that we have the scope completely assembled, and shadowing resolved, - // we're ready to canonicalize any body exprs. - - // Canonicalize all the patterns, record shadowing problems, and store - // the ast::Expr values in pending_exprs for further canonicalization - // once we've finished assembling the entire scope. - let mut pending_value_defs = Vec::with_capacity(value_defs.len()); - for loc_def in value_defs.into_iter() { - let mut new_output = Output::default(); - match to_pending_value_def( - env, - var_store, - loc_def.value, - &mut scope, - &mut new_output, - pattern_type, - ) { - None => { /* skip */ } - Some(pending_def) => { - // store the top-level defs, used to ensure that closures won't capture them - if let PatternType::TopLevelDef = pattern_type { - match &pending_def { - PendingValueDef::AnnotationOnly(_, loc_can_pattern, _) - | PendingValueDef::Body(_, loc_can_pattern, _) - | PendingValueDef::TypedBody(_, loc_can_pattern, _, _) => { - env.top_level_symbols.extend( - bindings_from_patterns(std::iter::once(loc_can_pattern)) - .iter() - .map(|t| t.0), - ) - } - } - } - // Record the ast::Expr for later. We'll do another pass through these - // once we have the entire scope assembled. If we were to canonicalize - // the exprs right now, they wouldn't have symbols in scope from defs - // that get would have gotten added later in the defs list! - pending_value_defs.push(pending_def); - output.union(new_output); - } - } - } - - for pending_def in pending_value_defs.into_iter() { - output = canonicalize_pending_value_def( - env, - pending_def, - output, - &mut scope, - &mut can_defs_by_symbol, - var_store, - &mut refs_by_symbol, - &mut aliases, - &abilities_in_scope, - ); - - // TODO we should do something with these references; they include - // things like type annotations. - } - - // Determine which idents we introduced in the course of this process. - let mut symbols_introduced = MutMap::default(); - - for (symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(*symbol) { - symbols_introduced.insert(*symbol, *region); - } - } - - // This returns both the defs info as well as the new scope. - // - // We have to return the new scope because we added defs to it - // (and those lookups shouldn't fail later, e.g. when canonicalizing - // the return expr), but we didn't want to mutate the original scope - // directly because we wanted to keep a clone of it around to diff - // when looking for unused idents. - // - // We have to return the scope separately from the defs, because the - // defs need to get moved later. - ( - CanDefs { - refs_by_symbol, - can_defs_by_symbol, - // The result needs a thread-safe `SendMap` - aliases: aliases.into_iter().collect(), - }, - scope, - output, - symbols_introduced, - ) } #[derive(Debug)] @@ -674,59 +668,54 @@ struct DefOrdering { // references without looking into closure bodies. // Used to spot definitely-wrong recursion direct_references: ReferenceMatrix, - - length: u32, } impl DefOrdering { - fn with_capacity(home: ModuleId, capacity: usize) -> Self { + fn from_symbol_to_id( + home: ModuleId, + symbol_to_id: Vec<(IdentId, u32)>, + capacity: usize, + ) -> Self { + // NOTE: because of `Pair a b = someDef` patterns, we can have more symbols than defs + // but because `_ = someDef` we can also have more defs than symbols + Self { home, - symbol_to_id: Vec::with_capacity(capacity), + symbol_to_id, references: ReferenceMatrix::new(capacity), direct_references: ReferenceMatrix::new(capacity), - length: capacity as u32, } } - fn from_defs_by_symbol( - env: &Env, - can_defs_by_symbol: &MutMap, - refs_by_symbol: &MutMap, - ) -> Self { - let mut this = Self::with_capacity(env.home, can_defs_by_symbol.len()); + fn insert_symbol_references(&mut self, def_id: u32, def_references: &DefReferences) { + match def_references { + DefReferences::Value(references) => { + let it = references.value_lookups().chain(references.calls()); - for (i, symbol) in can_defs_by_symbol.keys().enumerate() { - debug_assert_eq!(env.home, symbol.module_id()); + for referenced in it { + if let Some(ref_id) = self.get_id(*referenced) { + self.references + .set_row_col(def_id as usize, ref_id as usize, true); - this.symbol_to_id.push((symbol.ident_id(), i as u32)); - } - - for (symbol, (_, references)) in refs_by_symbol.iter() { - let def_id = this.get_id(*symbol).unwrap(); - - for referenced in references.value_lookups() { - this.register_reference(def_id, *referenced); - this.register_direct_reference(def_id, *referenced); - } - - for referenced in references.calls() { - this.register_reference(def_id, *referenced); - this.register_direct_reference(def_id, *referenced); - } - - if let Some(references) = env.closures.get(symbol) { - for referenced in references.value_lookups() { - this.register_reference(def_id, *referenced); - } - - for referenced in references.calls() { - this.register_reference(def_id, *referenced); + self.direct_references + .set_row_col(def_id as usize, ref_id as usize, true); + } } } - } + DefReferences::Function(references) => { + let it = references.value_lookups().chain(references.calls()); - this + for referenced in it { + if let Some(ref_id) = self.get_id(*referenced) { + self.references + .set_row_col(def_id as usize, ref_id as usize, true); + } + } + } + DefReferences::AnnotationWithoutBody => { + // annotatations without bodies don't reference any other definitions + } + } } fn get_id(&self, symbol: Symbol) -> Option { @@ -745,322 +734,146 @@ impl DefOrdering { None } - fn get_symbol(&self, id: u32) -> Option { + fn get_symbol(&self, id: usize) -> Option { for (ident_id, def_id) in self.symbol_to_id.iter() { - if id == *def_id { + if id as u32 == *def_id { return Some(Symbol::new(self.home, *ident_id)); } } None } - - fn register_direct_reference(&mut self, id: u32, referenced: Symbol) { - if let Some(ref_id) = self.get_id(referenced) { - self.direct_references - .set_row_col(id as usize, ref_id as usize, true); - } - } - - fn register_reference(&mut self, id: u32, referenced: Symbol) { - if let Some(ref_id) = self.get_id(referenced) { - self.references - .set_row_col(id as usize, ref_id as usize, true); - } - } - - fn is_self_recursive(&self, id: u32) -> bool { - debug_assert!(id < self.length); - - // id'th row, id'th column - let index = (id * self.length) + id; - - self.references.get(index as usize) - } - - #[inline(always)] - fn successors(&self, id: u32) -> impl Iterator + '_ { - self.references - .references_for(id as usize) - .map(|x| x as u32) - } - - #[inline(always)] - fn successors_without_self(&self, id: u32) -> impl Iterator + '_ { - self.successors(id).filter(move |x| *x != id) - } } #[inline(always)] -pub fn sort_can_defs( +pub(crate) fn sort_can_defs( env: &mut Env<'_>, defs: CanDefs, mut output: Output, -) -> (Result, RuntimeError>, Output) { - let def_ids = - DefOrdering::from_defs_by_symbol(env, &defs.can_defs_by_symbol, &defs.refs_by_symbol); - +) -> (Vec, Output) { let CanDefs { - refs_by_symbol, - mut can_defs_by_symbol, + mut defs, + def_ordering, aliases, + abilities_in_scope, } = defs; + output.abilities_in_scope = abilities_in_scope; + for (symbol, alias) in aliases.into_iter() { output.aliases.insert(symbol, alias); } - // TODO also do the same `addDirects` check elm/compiler does, so we can - // report an error if a recursive definition can't possibly terminate! - match def_ids.references.topological_sort_into_groups() { - TopologicalSort::Groups { groups } => { - let mut declarations = Vec::new(); + macro_rules! take_def { + ($index:expr) => { + match defs[$index].take() { + Some(def) => def, + None => { + // NOTE: a `_ = someDef` can mean we don't have a symbol here + let symbol = def_ordering.get_symbol($index); - // groups are in reversed order - for group in groups.into_iter().rev() { - group_to_declaration( - &def_ids, - &group, - &env.closures, - &mut can_defs_by_symbol, - &mut declarations, - ); - } - - (Ok(declarations), output) - } - TopologicalSort::HasCycles { - mut groups, - nodes_in_cycle, - } => { - let mut declarations = Vec::new(); - let mut problems = Vec::new(); - - // nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem, - // and in general it's impossible to decide whether it is. So we use a crude heuristic: - // - // Definitions where the cycle occurs behind a lambda are OK - // - // boom = \_ -> boom {} - // - // But otherwise we report an error, e.g. - // - // foo = if b then foo else bar - - let sccs = def_ids - .references - .strongly_connected_components(&nodes_in_cycle); - - for cycle in sccs { - // check whether the cycle is faulty, which is when it has - // a direct successor in the current cycle. This catches things like: - // - // x = x - // - // or - // - // p = q - // q = p - let is_invalid_cycle = match cycle.get(0) { - Some(def_id) => def_ids - .direct_references - .references_for(*def_id as usize) - .any(|key| cycle.contains(&(key as u32))), - None => false, - }; - - if is_invalid_cycle { - // We want to show the entire cycle in the error message, so expand it out. - let mut entries = Vec::new(); - - for def_id in &cycle { - let symbol = def_ids.get_symbol(*def_id).unwrap(); - match refs_by_symbol.get(&symbol) { - None => unreachable!( - r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, - symbol, refs_by_symbol - ), - Some((region, _)) => { - let expr_region = - can_defs_by_symbol.get(&symbol).unwrap().loc_expr.region; - - let entry = CycleEntry { - symbol, - symbol_region: *region, - expr_region, - }; - - entries.push(entry); - } - } - } - - // Sort them by line number to make the report more helpful. - entries.sort_by_key(|entry| entry.symbol_region); - - problems.push(Problem::RuntimeError(RuntimeError::CircularDef( - entries.clone(), - ))); - - declarations.push(Declaration::InvalidCycle(entries)); + roc_error_macros::internal_error!("def not available {:?}", symbol) } - - // if it's an invalid cycle, other groups may depend on the - // symbols defined here, so also push this cycle onto the groups - // - // if it's not an invalid cycle, this is slightly inefficient, - // because we know this becomes exactly one DeclareRec already - groups.push(cycle); } + }; + } - // now we have a collection of groups whose dependencies are not cyclic. - // They are however not yet topologically sorted. Here we have to get a bit - // creative to get all the definitions in the correct sorted order. + // We first perform SCC based on any reference, both variable usage and calls + // considering both value definitions and function bodies. This will spot any + // recursive relations between any 2 definitions. + let sccs = def_ordering.references.strongly_connected_components_all(); - let mut group_ids = Vec::with_capacity(groups.len()); - let mut symbol_to_group_index = MutMap::default(); - for (i, group) in groups.iter().enumerate() { - for symbol in group { - symbol_to_group_index.insert(*symbol, i); - } + let mut declarations = Vec::new(); - group_ids.push(i); - } + for group in sccs.groups() { + if group.count_ones() == 1 { + // a group with a single Def, nice and simple + let index = group.iter_ones().next().unwrap(); - let successors_of_group = |group_id: &usize| { - let mut result = MutSet::default(); + let def = take_def!(index); - // for each symbol in this group - for symbol in &groups[*group_id] { - // find its successors - for succ in def_ids.successors_without_self(*symbol) { - // and add its group to the result - match symbol_to_group_index.get(&succ) { - Some(index) => { - result.insert(*index); - } - None => unreachable!("no index for symbol {:?}", succ), - } - } - } + let declaration = if def_ordering.direct_references.get_row_col(index, index) { + // a definition like `x = x + 1`, which is invalid in roc + let symbol = def_ordering.get_symbol(index).unwrap(); - // don't introduce any cycles to self - result.remove(group_id); + let entries = vec![make_cycle_entry(symbol, &def)]; - result + let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone())); + env.problem(problem); + + Declaration::InvalidCycle(entries) + } else if def_ordering.references.get_row_col(index, index) { + // this function calls itself, and must be typechecked as a recursive def + Declaration::DeclareRec(vec![mark_def_recursive(def)]) + } else { + Declaration::Declare(def) }; - match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) { - Ok(sorted_group_ids) => { - for sorted_group in sorted_group_ids.iter().rev() { - for group_id in sorted_group.iter().rev() { - let group = &groups[*group_id]; + declarations.push(declaration); + } else { + // There is something recursive going on between the Defs of this group. + // Now we use the direct_references to see if it is clearly invalid recursion, e.g. + // + // x = y + // y = x + // + // We allow indirect recursion (behind a lambda), e.g. + // + // boom = \{} -> boom {} + // + // In general we cannot spot faulty recursion (halting problem) so this is our best attempt + let direct_sccs = def_ordering + .direct_references + .strongly_connected_components_subset(group); - group_to_declaration( - &def_ids, - group, - &env.closures, - &mut can_defs_by_symbol, - &mut declarations, - ); - } - } + let declaration = if direct_sccs.groups().count() == 1 { + // all defs are part of the same direct cycle, that is invalid! + let mut entries = Vec::with_capacity(group.count_ones()); + + for index in group.iter_ones() { + let def = take_def!(index); + let symbol = def_ordering.get_symbol(index).unwrap(); + + entries.push(make_cycle_entry(symbol, &def)) } - Err(_) => unreachable!("there should be no cycles now!"), - } - for problem in problems { + let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone())); env.problem(problem); - } - (Ok(declarations), output) + Declaration::InvalidCycle(entries) + } else { + let rec_defs = group + .iter_ones() + .map(|index| mark_def_recursive(take_def!(index))) + .collect(); + + Declaration::DeclareRec(rec_defs) + }; + + declarations.push(declaration); } } + + (declarations, output) } -fn group_to_declaration( - def_ids: &DefOrdering, - group: &[u32], - closures: &MutMap, - can_defs_by_symbol: &mut MutMap, - declarations: &mut Vec, -) { - use Declaration::*; +fn mark_def_recursive(mut def: Def) -> Def { + if let Closure(ClosureData { + recursive: recursive @ Recursive::NotRecursive, + .. + }) = &mut def.loc_expr.value + { + *recursive = Recursive::Recursive + } - // Patterns like - // - // { x, y } = someDef - // - // 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: Vec = Vec::with_capacity(2); + def +} - let sccs = def_ids.references.strongly_connected_components(group); - - for cycle in sccs { - if cycle.len() == 1 { - let def_id = cycle[0]; - let symbol = def_ids.get_symbol(def_id).unwrap(); - - match can_defs_by_symbol.remove(&symbol) { - Some(mut new_def) => { - // there is only one definition in this cycle, so we only have - // to check whether it recurses with itself; there is nobody else - // to recurse with, or they would also be in this cycle. - let is_self_recursive = def_ids.is_self_recursive(def_id); - - if let Closure(ClosureData { - recursive: recursive @ Recursive::NotRecursive, - .. - }) = &mut new_def.loc_expr.value - { - if is_self_recursive { - *recursive = Recursive::Recursive - } - } - - if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { - seen_pattern_regions.push(new_def.loc_pattern.region); - - if is_self_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 def_id in cycle.into_iter().rev() { - let symbol = def_ids.get_symbol(def_id).unwrap(); - match can_defs_by_symbol.remove(&symbol) { - Some(mut new_def) => { - // Determine recursivity of closures that are not tail-recursive - 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), - } - } - - declarations.push(DeclareRec(can_defs)); - } +fn make_cycle_entry(symbol: Symbol, def: &Def) -> CycleEntry { + CycleEntry { + symbol, + symbol_region: def.loc_pattern.region, + expr_region: def.loc_expr.region, } } @@ -1140,20 +953,24 @@ fn single_can_def( } } -fn add_annotation_aliases( - type_annotation: &crate::annotation::Annotation, - aliases: &mut ImMap, -) { - for (name, alias) in type_annotation.aliases.iter() { - match aliases.entry(*name) { - ImEntry::Occupied(_) => { - // do nothing - } - ImEntry::Vacant(vacant) => { - vacant.insert(alias.clone()); - } - } - } +// Functions' references don't count in defs. +// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its +// parent commit for the bug this fixed! +enum DefReferences { + /// A value may not reference itself + Value(References), + + /// If the def is a function, different rules apply (it can call itself) + Function(References), + + /// An annotation without a body references no other defs + AnnotationWithoutBody, +} + +struct DefOutput { + output: Output, + def: Def, + references: DefReferences, } // TODO trim down these arguments! @@ -1164,20 +981,19 @@ fn canonicalize_pending_value_def<'a>( pending_def: PendingValueDef<'a>, mut output: Output, scope: &mut Scope, - can_defs_by_symbol: &mut MutMap, var_store: &mut VarStore, - refs_by_symbol: &mut MutMap, - aliases: &mut ImMap, + pattern_type: PatternType, + aliases: &mut VecMap, abilities_in_scope: &[Symbol], -) -> Output { +) -> DefOutput { use PendingValueDef::*; - // Make types for the body expr, even if we won't end up having a body. - let expr_var = var_store.fresh(); - let mut vars_by_symbol = SendMap::default(); - - match pending_def { + let output = match pending_def { AnnotationOnly(_, loc_can_pattern, loc_ann) => { + // Make types for the body expr, even if we won't end up having a body. + let expr_var = var_store.fresh(); + let mut vars_by_symbol = SendMap::default(); + // annotation sans body cannot introduce new rigids that are visible in other annotations // but the rigids can show up in type error messages, so still register them let type_annotation = canonicalize_annotation( @@ -1190,16 +1006,11 @@ fn canonicalize_pending_value_def<'a>( ); // Record all the annotation's references in output.references.lookups - - for symbol in type_annotation.references.iter() { - output.references.insert_type_lookup(*symbol); - } - - add_annotation_aliases(&type_annotation, aliases); - - output - .introduced_variables - .union(&type_annotation.introduced_variables); + type_annotation.add_to( + aliases, + &mut output.references, + &mut output.introduced_variables, + ); pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); @@ -1226,7 +1037,7 @@ fn canonicalize_pending_value_def<'a>( region: loc_ann.region, } } else { - let symbol = env.gen_unique_symbol(); + let symbol = scope.gen_unique_symbol(); // generate a fake pattern for each argument. this makes signatures // that are functions only crash when they are applied. @@ -1238,7 +1049,11 @@ fn canonicalize_pending_value_def<'a>( region: Region::zero(), }; - underscores.push((var_store.fresh(), underscore)); + underscores.push(( + var_store.fresh(), + AnnotatedMark::known_exhaustive(), + underscore, + )); } let body_expr = Loc { @@ -1262,46 +1077,18 @@ fn canonicalize_pending_value_def<'a>( } }; - if let Pattern::Identifier(symbol) - | Pattern::AbilityMemberSpecialization { ident: 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; - } + 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(), + ); - // 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, - }), - }, - ); - } + DefOutput { + output, + references: DefReferences::AnnotationWithoutBody, + def, } } @@ -1316,304 +1103,157 @@ fn canonicalize_pending_value_def<'a>( ); // Record all the annotation's references in output.references.lookups - for symbol in type_annotation.references.iter() { - output.references.insert_type_lookup(*symbol); - } + type_annotation.add_to( + aliases, + &mut output.references, + &mut output.introduced_variables, + ); - add_annotation_aliases(&type_annotation, aliases); - - 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. - let outer_identifier = env.tailcallable_symbol; - - if let Pattern::Identifier(ref defined_symbol) = &loc_can_pattern.value { - env.tailcallable_symbol = Some(*defined_symbol); - }; - - // register the name of this closure, to make sure the closure won't capture it's own name - if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = - (&loc_can_pattern.value, &loc_expr.value) - { - env.closure_name_symbol = Some(*defined_symbol); - }; - - pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); - - let (mut loc_can_expr, can_output) = - canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); - - output.references.union_mut(&can_output.references); - - // reset the tailcallable_symbol - env.tailcallable_symbol = outer_identifier; - - // 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 Pattern::Identifier(symbol) - | Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value - { - if let Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: ref closure_name, - ref arguments, - loc_body: ref body, - ref captured_symbols, - .. - }) = 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 - ) - }); - - // 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.remove_value_lookup(&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(), - }); - - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); - - 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)); - } - - 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, - }), - }, - ); - } - } + canonicalize_pending_body( + env, + output, + scope, + var_store, + loc_can_pattern, + loc_expr, + Some(Loc::at(loc_ann.region, type_annotation)), + ) } - // If we have a pattern, then the def has a body (that is, it's not a - // standalone annotation), so we need to canonicalize the pattern and expr. - Body(loc_pattern, loc_can_pattern, loc_expr) => { - // bookkeeping for tail-call detection. If we're assigning to an - // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. - let outer_identifier = env.tailcallable_symbol; - - if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) = - (&loc_pattern.value, &loc_can_pattern.value) - { - env.tailcallable_symbol = Some(*defined_symbol); - - // TODO isn't types_by_symbol enough? Do we need vars_by_symbol too? - vars_by_symbol.insert(*defined_symbol, expr_var); - }; - - // register the name of this closure, to make sure the closure won't capture it's own name - if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = - (&loc_can_pattern.value, &loc_expr.value) - { - env.closure_name_symbol = Some(*defined_symbol); - }; - - let (mut loc_can_expr, can_output) = - canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); - - // reset the tailcallable_symbol - env.tailcallable_symbol = outer_identifier; - - // First, make sure we are actually assigning an identifier instead of (for example) a tag. + Body(_loc_pattern, loc_can_pattern, loc_expr) => { // - // 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 Pattern::Identifier(symbol) = loc_can_pattern.value { - if let Closure(ClosureData { - function_type, - closure_type, - closure_ext_var, - return_type, - name: ref closure_name, - ref arguments, - loc_body: ref body, - ref captured_symbols, - .. - }) = 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 - ) - }); - - // 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.remove_value_lookup(&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(), - }); - - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - 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)); - } - - 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); + canonicalize_pending_body( + env, + output, + scope, + var_store, + loc_can_pattern, + loc_expr, + None, + ) } }; + // Disallow ability specializations that aren't on the toplevel (note: we might loosen this + // restriction later on). + if pattern_type != PatternType::TopLevelDef { + if let Loc { + value: Pattern::AbilityMemberSpecialization { specializes, .. }, + region, + } = output.def.loc_pattern + { + env.problem(Problem::NestedSpecialization(specializes, region)); + } + } + output } +// TODO trim down these arguments! +#[allow(clippy::too_many_arguments)] +#[allow(clippy::cognitive_complexity)] +fn canonicalize_pending_body<'a>( + env: &mut Env<'a>, + mut output: Output, + scope: &mut Scope, + var_store: &mut VarStore, + + loc_can_pattern: Loc, + loc_expr: &'a Loc, + + opt_loc_annotation: Option>, +) -> DefOutput { + // We treat closure definitions `foo = \a, b -> ...` differently from other body expressions, + // because they need more bookkeeping (for tail calls, closure captures, etc.) + // + // Only defs of the form `foo = ...` can be closure declarations or self tail calls. + let (loc_can_expr, def_references) = { + match (&loc_can_pattern.value, &loc_expr.value) { + ( + Pattern::Identifier(defined_symbol) + | Pattern::AbilityMemberSpecialization { + ident: defined_symbol, + .. + }, + ast::Expr::Closure(arguments, body), + ) => { + // bookkeeping for tail-call detection. + let outer_tailcallable = env.tailcallable_symbol; + env.tailcallable_symbol = Some(*defined_symbol); + + let (mut closure_data, can_output) = crate::expr::canonicalize_closure( + env, + var_store, + scope, + arguments, + body, + Some(*defined_symbol), + ); + + // reset the tailcallable_symbol + env.tailcallable_symbol = outer_tailcallable; + + // 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 == *defined_symbol => Recursive::TailRecursive, + _ => Recursive::NotRecursive, + }; + + closure_data.recursive = is_recursive; + closure_data.name = *defined_symbol; + + let loc_can_expr = Loc::at(loc_expr.region, Expr::Closure(closure_data)); + + let def_references = DefReferences::Function(can_output.references.clone()); + output.union(can_output); + + (loc_can_expr, def_references) + } + + _ => { + let (loc_can_expr, can_output) = + canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); + + let def_references = DefReferences::Value(can_output.references.clone()); + output.union(can_output); + + (loc_can_expr, def_references) + } + } + }; + + let expr_var = var_store.fresh(); + let mut vars_by_symbol = SendMap::default(); + + pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + opt_loc_annotation, + vars_by_symbol, + ); + + DefOutput { + output, + references: def_references, + def, + } +} + #[inline(always)] pub fn can_defs_with_return<'a>( env: &mut Env<'a>, var_store: &mut VarStore, - scope: Scope, + scope: &mut Scope, loc_defs: &'a [&'a Loc>], loc_ret: &'a Loc>, ) -> (Expr, Output) { - let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( + let (unsorted, defs_output, symbols_introduced) = canonicalize_defs( env, Output::default(), var_store, - &scope, + scope, loc_defs, PatternType::DefExpr, ); @@ -1621,7 +1261,7 @@ pub fn can_defs_with_return<'a>( // The def as a whole is a tail call iff its return expression is a tail call. // Use its output as a starting point because its tail_call already has the right answer! let (ret_expr, mut output) = - canonicalize_expr(env, var_store, &mut scope, loc_ret.region, &loc_ret.value); + canonicalize_expr(env, var_store, scope, loc_ret.region, &loc_ret.value); output .introduced_variables @@ -1638,31 +1278,24 @@ pub fn can_defs_with_return<'a>( } } - let (can_defs, output) = sort_can_defs(env, unsorted, output); + let (declarations, output) = sort_can_defs(env, unsorted, output); - match can_defs { - Ok(decls) => { - let mut loc_expr: Loc = ret_expr; + let mut loc_expr: Loc = ret_expr; - for declaration in decls.into_iter().rev() { - loc_expr = Loc { - region: Region::zero(), - value: decl_to_let(var_store, declaration, loc_expr), - }; - } - - (loc_expr.value, output) - } - Err(err) => (RuntimeError(err), output), + for declaration in declarations.into_iter().rev() { + loc_expr = Loc { + region: Region::zero(), + value: decl_to_let(declaration, loc_expr), + }; } + + (loc_expr.value, output) } -fn decl_to_let(var_store: &mut VarStore, decl: Declaration, loc_ret: Loc) -> Expr { +fn decl_to_let(decl: Declaration, loc_ret: Loc) -> Expr { match decl { - Declaration::Declare(def) => { - Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh()) - } - Declaration::DeclareRec(defs) => Expr::LetRec(defs, Box::new(loc_ret), var_store.fresh()), + Declaration::Declare(def) => Expr::LetNonRec(Box::new(def), Box::new(loc_ret)), + Declaration::DeclareRec(defs) => Expr::LetRec(defs, Box::new(loc_ret)), Declaration::InvalidCycle(entries) => { Expr::RuntimeError(RuntimeError::CircularDef(entries)) } @@ -1673,46 +1306,12 @@ fn decl_to_let(var_store: &mut VarStore, decl: Declaration, loc_ret: Loc) } } -fn closure_recursivity(symbol: Symbol, closures: &MutMap) -> Recursive { - let mut visited = MutSet::default(); - - let mut stack = Vec::new(); - - if let Some(references) = closures.get(&symbol) { - for v in references.calls() { - stack.push(*v); - } - - // while there are symbols left to visit - while let Some(nested_symbol) = stack.pop() { - if nested_symbol == symbol { - return Recursive::Recursive; - } - - // if the called symbol not yet in the graph - if !visited.contains(&nested_symbol) { - // add it to the visited set - // if it calls any functions - if let Some(nested_references) = closures.get(&nested_symbol) { - // add its called to the stack - for v in nested_references.calls() { - stack.push(*v); - } - } - visited.insert(nested_symbol); - } - } - } - - Recursive::NotRecursive -} - fn to_pending_type_def<'a>( env: &mut Env<'a>, def: &'a ast::TypeDef<'a>, scope: &mut Scope, pattern_type: PatternType, -) -> Option<(Output, PendingTypeDef<'a>)> { +) -> PendingTypeDef<'a> { use ast::TypeDef::*; match def { @@ -1732,12 +1331,7 @@ fn to_pending_type_def<'a>( let region = Region::span_across(&name.region, &ann.region); - match scope.introduce( - name.value.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + match scope.introduce_without_shadow_symbol(&Ident::from(name.value), region) { Ok(symbol) => { let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); @@ -1760,10 +1354,11 @@ fn to_pending_type_def<'a>( }; env.problems.push(problem); - return Some(( - Output::default(), - PendingTypeDef::InvalidAlias { kind }, - )); + return PendingTypeDef::InvalidAlias { + kind, + symbol, + region, + }; } } } @@ -1773,24 +1368,22 @@ fn to_pending_type_def<'a>( value: symbol, }; - let pending_def = PendingTypeDef::Alias { + PendingTypeDef::Alias { name, vars: can_rigids, ann, kind, - }; - - Some((Output::default(), pending_def)) + } } - Err((original_region, loc_shadowed_symbol, _new_symbol)) => { + Err((original_region, loc_shadowed_symbol)) => { env.problem(Problem::Shadowing { original_region, shadow: loc_shadowed_symbol, kind: shadow_kind, }); - Some((Output::default(), PendingTypeDef::InvalidAlias { kind })) + PendingTypeDef::ShadowedAlias } } } @@ -1805,7 +1398,7 @@ fn to_pending_type_def<'a>( ); env.problem(Problem::AbilityNotOnToplevel { region }); - Some((Output::default(), PendingTypeDef::InvalidAbility)) + PendingTypeDef::AbilityNotOnToplevel } Ability { @@ -1813,12 +1406,9 @@ fn to_pending_type_def<'a>( members, loc_has: _, } => { - let name = match scope.introduce_without_shadow_symbol( - name.value.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - name.region, - ) { + let name = match scope + .introduce_without_shadow_symbol(&Ident::from(name.value), name.region) + { Ok(symbol) => Loc::at(name.region, symbol), Err((original_region, shadowed_symbol)) => { env.problem(Problem::Shadowing { @@ -1826,7 +1416,7 @@ fn to_pending_type_def<'a>( shadow: shadowed_symbol, kind: ShadowKind::Ability, }); - return Some((Output::default(), PendingTypeDef::InvalidAbility)); + return PendingTypeDef::AbilityShadows; } }; @@ -1838,16 +1428,17 @@ fn to_pending_type_def<'a>( name: name.value, variables_region, }); - return Some((Output::default(), PendingTypeDef::InvalidAbility)); + return PendingTypeDef::InvalidAbility { + symbol: name.value, + region: name.region, + }; } - let pending_ability = PendingTypeDef::Ability { + PendingTypeDef::Ability { name, // We'll handle adding the member symbols later on when we do all value defs. members, - }; - - Some((Output::default(), pending_ability)) + } } } } @@ -1952,75 +1543,146 @@ fn to_pending_value_def<'a>( /// Make aliases recursive fn correct_mutual_recursive_type_alias<'a>( env: &mut Env<'a>, - mut original_aliases: SendMap, + original_aliases: VecMap, var_store: &mut VarStore, -) -> ImMap { - let symbols_introduced: Vec = original_aliases.keys().copied().collect(); +) -> VecMap { + let capacity = original_aliases.len(); + let mut matrix = ReferenceMatrix::new(capacity); - let all_successors_with_self = |symbol: &Symbol| -> Vec { - match original_aliases.get(symbol) { - Some(alias) => { - let mut loc_succ = alias.typ.symbols(); - // remove anything that is not defined in the current block - loc_succ.retain(|key| symbols_introduced.contains(key)); + let (symbols_introduced, mut aliases) = original_aliases.unzip(); - loc_succ + for (index, alias) in aliases.iter().enumerate() { + for referenced in alias.typ.symbols() { + match symbols_introduced.iter().position(|k| referenced == *k) { + None => { /* ignore */ } + Some(ref_id) => matrix.set_row_col(index, ref_id, true), } - None => vec![], } - }; + } - // TODO investigate should this be in a loop? - let defined_symbols: Vec = original_aliases.keys().copied().collect(); + let mut solved_aliases = bitvec::vec::BitVec::::repeat(false, capacity); - let cycles = - ven_graph::strongly_connected_components(&defined_symbols, all_successors_with_self); - let mut solved_aliases = ImMap::default(); + let sccs = matrix.strongly_connected_components_all(); - for cycle in cycles { - debug_assert!(!cycle.is_empty()); + // scratchpad to store aliases that are modified in the current iteration. + // Only used when there is are more than one alias in a group. See below why + // this is needed. + let scratchpad_capacity = sccs + .groups() + .map(|r| r.count_ones()) + .max() + .unwrap_or_default(); + let mut scratchpad = Vec::with_capacity(scratchpad_capacity); - let mut pending_aliases: ImMap<_, _> = cycle - .iter() - .map(|&sym| (sym, original_aliases.remove(&sym).unwrap())) - .collect(); + for cycle in sccs.groups() { + debug_assert!(cycle.count_ones() > 0); + + // We need to instantiate the alias with any symbols in the currrent module it + // depends on. + // + // the `strongly_connected_components` returns SCCs in a topologically sorted order: + // SCC_0 has those aliases that don't rely on any other, SCC_1 has only those that rely on SCC_1, etc. + // + // Hence, we only need to worry about symbols in the current SCC or any prior one. + // It cannot be using any of the others, and we've already instantiated aliases coming from other modules. + let mut to_instantiate = solved_aliases | cycle; // Make sure we report only one error for the cycle, not an error for every // alias in the cycle. let mut can_still_report_error = true; - // We need to instantiate the alias with any symbols in the currrent module it - // depends on. - // We only need to worry about symbols in this SCC or any prior one, since the SCCs - // were sorted topologically, and we've already instantiated aliases coming from other - // modules. - // NB: ImMap::clone is O(1): https://docs.rs/im/latest/src/im/hash/map.rs.html#1527-1544 - let mut to_instantiate = solved_aliases.clone().union(pending_aliases.clone()); + for index in cycle.iter_ones() { + // Don't try to instantiate the alias itself in its own definition. + to_instantiate.set(index, false); - for &rec in cycle.iter() { - let alias = pending_aliases.get_mut(&rec).unwrap(); - // Don't try to instantiate the alias itself in its definition. - let original_alias_def = to_instantiate.remove(&rec).unwrap(); + // Within a recursive group, we must instantiate all aliases like how they came to the + // loop. e.g. given + // + // A : [ ConsA B, NilA ] + // B : [ ConsB A, NilB ] + // + // Our goal is + // + // A : [ ConsA [ ConsB A, NilB ], NilA ] + // B : [ ConsB [ ConsA B, NilA ], NilB ] + // + // But if we would first instantiate B into A, then use the updated A to instantiate B, + // we get + // + // A : [ ConsA [ ConsB A, NilB ], NilA ] + // B : [ ConsB [ ConsA [ ConsB A, NilB ], NilA ], NilB ] + // + // Which is incorrect. We do need the instantiated version however. + // e.g. if in a next group we have: + // + // C : A + // + // Then we must use the instantiated version + // + // C : [ ConsA [ ConsB A, NilB ], NilA ] + // + // So, we cannot replace the original version of A with its instantiated version + // while we process A's group. We have to store the instantiated version until the + // current group is done, then move it to the `aliases` array. That is what the scratchpad is for. + let alias = if cycle.count_ones() == 1 { + // an optimization: we can modify the alias in the `aliases` list directly + // because it is the only alias in the group. + &mut aliases[index] + } else { + scratchpad.push((index, aliases[index].clone())); + + &mut scratchpad.last_mut().unwrap().1 + }; + + // Now, `alias` is possibly a mutable borrow from the `aliases` vector. But we also want + // to immutably borrow other elements from that vector to instantiate them into `alias`. + // The borrow checker disallows that. + // + // So we get creative: we swap out the element we want to modify with a dummy. We can + // then freely modify the type we moved out, and the `to_instantiate` mask + // makes sure that our dummy is not used. + + let alias_region = alias.region; + let mut alias_type = Type::EmptyRec; + + std::mem::swap(&mut alias_type, &mut alias.typ); + + let can_instantiate_symbol = |s| match symbols_introduced.iter().position(|i| *i == s) { + Some(s_index) if to_instantiate[s_index] => aliases.get(s_index), + _ => None, + }; let mut new_lambda_sets = ImSet::default(); - alias.typ.instantiate_aliases( - alias.region, - &to_instantiate, + alias_type.instantiate_aliases( + alias_region, + &can_instantiate_symbol, var_store, &mut new_lambda_sets, ); - for lambda_set_var in new_lambda_sets { - alias - .lambda_set_variables - .push(LambdaSet(Type::Variable(lambda_set_var))); - } + let alias = if cycle.count_ones() > 1 { + &mut scratchpad.last_mut().unwrap().1 + } else { + &mut aliases[index] + }; - to_instantiate.insert(rec, original_alias_def); + // swap the type back + std::mem::swap(&mut alias_type, &mut alias.typ); + + // We can instantiate this alias in future iterations + to_instantiate.set(index, true); + + // add any lambda sets that the instantiation created to the current alias + alias.lambda_set_variables.extend( + new_lambda_sets + .iter() + .map(|var| LambdaSet(Type::Variable(*var))), + ); // Now mark the alias recursive, if it needs to be. - let is_self_recursive = alias.typ.contains_symbol(rec); - let is_mutually_recursive = cycle.len() > 1; + let rec = symbols_introduced[index]; + let is_self_recursive = cycle.count_ones() == 1 && matrix.get_row_col(index, index); + let is_mutually_recursive = cycle.count_ones() > 1; if is_self_recursive || is_mutually_recursive { let _made_recursive = make_tag_union_of_alias_recursive( @@ -2034,20 +1696,28 @@ fn correct_mutual_recursive_type_alias<'a>( } } + // the current group has instantiated. Now we can move the updated aliases to the `aliases` vector + for (index, alias) in scratchpad.drain(..) { + aliases[index] = alias; + } + // The cycle we just instantiated and marked recursive may still be an illegal cycle, if // all the types in the cycle are narrow newtypes. We can't figure this out until now, // because we need all the types to be deeply instantiated. - let all_are_narrow = cycle.iter().all(|sym| { - let typ = &pending_aliases.get(sym).unwrap().typ; + let all_are_narrow = cycle.iter_ones().all(|index| { + let typ = &aliases[index].typ; matches!(typ, Type::RecursiveTagUnion(..)) && typ.is_narrow() }); if all_are_narrow { // This cycle is illegal! - let mut rest = cycle; - let alias_name = rest.pop().unwrap(); + let mut indices = cycle.iter_ones(); + let first_index = indices.next().unwrap(); - let alias = pending_aliases.get_mut(&alias_name).unwrap(); + let rest: Vec = indices.map(|i| symbols_introduced[i]).collect(); + + let alias_name = symbols_introduced[first_index]; + let alias = aliases.get_mut(first_index).unwrap(); mark_cyclic_alias( env, @@ -2059,11 +1729,12 @@ fn correct_mutual_recursive_type_alias<'a>( ) } - // Now, promote all resolved aliases in this cycle as solved. - solved_aliases.extend(pending_aliases); + // We've instantiated all we could, so all instantiatable aliases are solved now + solved_aliases = to_instantiate; } - solved_aliases + // Safety: both vectors are equal length and there are no duplicates + unsafe { VecMap::zip(symbols_introduced, aliases) } } fn make_tag_union_of_alias_recursive<'a>( @@ -2072,22 +1743,25 @@ fn make_tag_union_of_alias_recursive<'a>( alias: &mut Alias, others: Vec, var_store: &mut VarStore, - can_report_error: &mut bool, + can_report_cyclic_error: &mut bool, ) -> Result<(), ()> { let alias_args = alias .type_variables .iter() - .map(|l| (l.value.0.clone(), Type::Variable(l.value.1))) + .map(|l| (l.value.name.clone(), Type::Variable(l.value.var))) .collect::>(); let made_recursive = make_tag_union_recursive_help( env, - Loc::at(alias.header_region(), (alias_name, &alias_args)), + Loc::at( + alias.header_region(), + (alias_name, alias_args.iter().map(|ta| &ta.1)), + ), alias.region, others, &mut alias.typ, var_store, - can_report_error, + can_report_cyclic_error, ); match made_recursive { @@ -2129,29 +1803,32 @@ enum MakeTagUnionRecursive { /// ``` /// /// When `Err` is returned, a problem will be added to `env`. -fn make_tag_union_recursive_help<'a>( +fn make_tag_union_recursive_help<'a, 'b>( env: &mut Env<'a>, - recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>, + recursive_alias: Loc<(Symbol, impl Iterator)>, region: Region, others: Vec, - typ: &mut Type, + typ: &'b mut Type, var_store: &mut VarStore, - can_report_error: &mut bool, + can_report_cyclic_error: &mut bool, ) -> MakeTagUnionRecursive { use MakeTagUnionRecursive::*; - let Loc { - value: (symbol, args), - region: alias_region, - } = recursive_alias; - let vars = args.iter().map(|(_, t)| t.clone()).collect::>(); + let (symbol, args) = recursive_alias.value; + let alias_region = recursive_alias.region; + match typ { Type::TagUnion(tags, ext) => { let recursion_variable = var_store.fresh(); + let type_arguments: Vec<_> = args.into_iter().cloned().collect(); + let mut pending_typ = Type::RecursiveTagUnion(recursion_variable, tags.to_vec(), ext.clone()); - let substitution_result = - pending_typ.substitute_alias(symbol, &vars, &Type::Variable(recursion_variable)); + let substitution_result = pending_typ.substitute_alias( + symbol, + &type_arguments, + &Type::Variable(recursion_variable), + ); match substitution_result { Ok(()) => { // We can substitute the alias presence for the variable exactly. @@ -2177,18 +1854,29 @@ fn make_tag_union_recursive_help<'a>( actual, type_arguments, .. - } => make_tag_union_recursive_help( - env, - Loc::at_zero((symbol, type_arguments)), - region, - others, - actual, - var_store, - can_report_error, - ), + } => { + // NB: We need to collect the type arguments to shut off rustc's closure type + // instantiator. Otherwise we get unfortunate errors like + // reached the recursion limit while instantiating `make_tag_union_recursive_help::<...n/src/def.rs:1879:65: 1879:77]>>` + #[allow(clippy::needless_collect)] + let type_arguments: Vec<&Type> = type_arguments.iter().map(|ta| &ta.typ).collect(); + let recursive_alias = Loc::at_zero((symbol, type_arguments.into_iter())); + + // try to make `actual` recursive + make_tag_union_recursive_help( + env, + recursive_alias, + region, + others, + actual, + var_store, + can_report_cyclic_error, + ) + } _ => { - mark_cyclic_alias(env, typ, symbol, region, others, *can_report_error); - *can_report_error = false; + // take care to report a cyclic alias only once (not once for each alias in the cycle) + mark_cyclic_alias(env, typ, symbol, region, others, *can_report_cyclic_error); + *can_report_cyclic_error = false; Cyclic } diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 2e58dc1bd1..7e055f3126 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -1,7 +1,6 @@ use crate::annotation::IntroducedVariables; use crate::def::{Declaration, Def}; -use crate::env::Env; -use crate::expr::{ClosureData, Expr, Recursive}; +use crate::expr::{AnnotatedMark, ClosureData, Expr, Recursive}; use crate::pattern::Pattern; use crate::scope::Scope; use roc_collections::{SendMap, VecSet}; @@ -9,8 +8,8 @@ use roc_module::called_via::CalledVia; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; -use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{AliasKind, Type, TypeExtension}; +use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; +use roc_types::types::{AliasKind, LambdaSet, OptAbleType, OptAbleVar, Type, TypeExtension}; #[derive(Debug, Default, Clone, Copy)] pub(crate) struct HostedGeneratedFunctions { @@ -30,12 +29,11 @@ pub(crate) struct HostedGeneratedFunctions { /// /// The effect alias is implemented as /// -/// Effect a : [ @Effect ({} -> a) ] +/// Effect a := {} -> a /// /// For this alias we implement the functions specified in HostedGeneratedFunctions with the /// standard implementation. pub(crate) fn build_effect_builtins( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, var_store: &mut VarStore, @@ -45,13 +43,7 @@ pub(crate) fn build_effect_builtins( ) { macro_rules! helper { ($f:expr) => {{ - let (symbol, def) = $f( - env, - scope, - effect_symbol, - TagName::Private(effect_symbol), - var_store, - ); + let (symbol, def) = $f(scope, effect_symbol, var_store); // make the outside world know this symbol exists exposed_symbols.insert(symbol); @@ -93,69 +85,42 @@ pub(crate) fn build_effect_builtins( // show up with their name. We have to register them like below to make the names show up in // debug prints if false { - env.home.register_debug_idents(&env.ident_ids); + scope.register_debug_idents(); } } macro_rules! new_symbol { - ($scope:expr, $env:expr, $name:expr) => {{ - $scope - .introduce( - $name.into(), - &$env.exposed_ident_ids, - &mut $env.ident_ids, - Region::zero(), - ) - .unwrap() + ($scope:expr, $name:expr) => {{ + $scope.introduce($name.into(), Region::zero()).unwrap() }}; } fn build_effect_always( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { // Effect.always = \value -> @Effect \{} -> value let value_symbol = { scope - .introduce( - "effect_always_value".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_always_value".into(), Region::zero()) .unwrap() }; let inner_closure_symbol = { scope - .introduce( - "effect_always_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_always_inner".into(), Region::zero()) .unwrap() }; - let always_symbol = { - scope - .introduce( - "always".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let always_symbol = { scope.introduce("always".into(), Region::zero()).unwrap() }; // \{} -> value let const_closure = { let arguments = vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(empty_record_pattern(var_store)), )]; @@ -177,15 +142,20 @@ fn build_effect_always( // \value -> @Effect \{} -> value let (function_var, always_closure) = { // `@Effect \{} -> value` - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name.clone(), - arguments: vec![(var_store.fresh(), Loc::at_zero(const_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let body = Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(const_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let arguments = vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(value_symbol)), )]; @@ -212,10 +182,8 @@ fn build_effect_always( let var_a = var_store.fresh(); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); - let effect_a = build_effect_alias( + let effect_a = build_effect_opaque( effect_symbol, - effect_tag_name, - "a", var_a, Type::Variable(var_a), var_store, @@ -254,46 +222,25 @@ fn build_effect_always( } fn build_effect_map( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { // Effect.map = \@Effect thunk, mapper -> @Effect \{} -> mapper (thunk {}) let thunk_symbol = { scope - .introduce( - "effect_map_thunk".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_map_thunk".into(), Region::zero()) .unwrap() }; let mapper_symbol = { scope - .introduce( - "effect_map_mapper".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_map_mapper".into(), Region::zero()) .unwrap() }; - let map_symbol = { - scope - .introduce( - "map".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let map_symbol = { scope.introduce("map".into(), Region::zero()).unwrap() }; // `thunk {}` let force_thunk_call = { @@ -323,12 +270,7 @@ fn build_effect_map( let inner_closure_symbol = { scope - .introduce( - "effect_map_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_map_inner".into(), Region::zero()) .unwrap() }; @@ -336,6 +278,7 @@ fn build_effect_map( let inner_closure = { let arguments = vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(empty_record_pattern(var_store)), )]; @@ -355,31 +298,42 @@ fn build_effect_map( }) }; + // \@Effect thunk, mapper + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); let arguments = vec![ ( var_store.fresh(), - Loc::at_zero(Pattern::AppliedTag { + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::UnwrappedOpaque { + opaque: effect_symbol, whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name: effect_tag_name.clone(), - arguments: vec![( + argument: Box::new(( var_store.fresh(), Loc::at_zero(Pattern::Identifier(thunk_symbol)), - )], + )), + specialized_def_type, + type_arguments, + lambda_set_variables, }), ), ( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(mapper_symbol)), ), ]; // `@Effect \{} -> (mapper (thunk {}))` - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name.clone(), - arguments: vec![(var_store.fresh(), Loc::at_zero(inner_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let body = Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(inner_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let function_var = var_store.fresh(); @@ -405,20 +359,16 @@ fn build_effect_map( introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); - let effect_a = build_effect_alias( + let effect_a = build_effect_opaque( effect_symbol, - effect_tag_name.clone(), - "a", var_a, Type::Variable(var_a), var_store, &mut introduced_variables, ); - let effect_b = build_effect_alias( + let effect_b = build_effect_opaque( effect_symbol, - effect_tag_name, - "b", var_b, Type::Variable(var_b), var_store, @@ -466,46 +416,25 @@ fn build_effect_map( } fn build_effect_after( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { // Effect.after = \@Effect effect, toEffect -> toEffect (effect {}) let thunk_symbol = { scope - .introduce( - "effect_after_thunk".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_after_thunk".into(), Region::zero()) .unwrap() }; let to_effect_symbol = { scope - .introduce( - "effect_after_toEffect".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("effect_after_toEffect".into(), Region::zero()) .unwrap() }; - let after_symbol = { - scope - .introduce( - "after".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let after_symbol = { scope.introduce("after".into(), Region::zero()).unwrap() }; // `thunk {}` let force_thunk_call = { @@ -533,21 +462,28 @@ fn build_effect_after( Expr::Call(Box::new(boxed), arguments, CalledVia::Space) }; + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let arguments = vec![ ( var_store.fresh(), - Loc::at_zero(Pattern::AppliedTag { + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::UnwrappedOpaque { + opaque: effect_symbol, whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name: effect_tag_name.clone(), - arguments: vec![( + argument: Box::new(( var_store.fresh(), Loc::at_zero(Pattern::Identifier(thunk_symbol)), - )], + )), + specialized_def_type, + type_arguments, + lambda_set_variables, }), ), ( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(to_effect_symbol)), ), ]; @@ -574,20 +510,16 @@ fn build_effect_after( introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); - let effect_a = build_effect_alias( + let effect_a = build_effect_opaque( effect_symbol, - effect_tag_name.clone(), - "a", var_a, Type::Variable(var_a), var_store, &mut introduced_variables, ); - let effect_b = build_effect_alias( + let effect_b = build_effect_opaque( effect_symbol, - effect_tag_name, - "b", var_b, Type::Variable(var_b), var_store, @@ -635,7 +567,7 @@ fn build_effect_after( /// turn `value` into `@Effect \{} -> value` fn wrap_in_effect_thunk( body: Expr, - effect_tag_name: TagName, + effect_symbol: Symbol, closure_name: Symbol, captured_symbols: Vec, var_store: &mut VarStore, @@ -649,6 +581,7 @@ fn wrap_in_effect_thunk( let const_closure = { let arguments = vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(empty_record_pattern(var_store)), )]; @@ -667,31 +600,38 @@ fn wrap_in_effect_thunk( }; // `@Effect \{} -> value` - Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name, - arguments: vec![(var_store.fresh(), Loc::at_zero(const_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(const_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, } } /// given `effect : Effect a`, unwrap the thunk and force it, giving a value of type `a` fn force_effect( effect: Expr, - effect_tag_name: TagName, + effect_symbol: Symbol, thunk_symbol: Symbol, var_store: &mut VarStore, ) -> Expr { let whole_var = var_store.fresh(); - let ext_var = var_store.fresh(); let thunk_var = var_store.fresh(); - let pattern = Pattern::AppliedTag { - ext_var, + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let pattern = Pattern::UnwrappedOpaque { whole_var, - tag_name: effect_tag_name, - arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk_symbol)))], + opaque: effect_symbol, + argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk_symbol)))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let pattern_vars = SendMap::default(); @@ -721,14 +661,12 @@ fn force_effect( Loc::at_zero(call) }; - Expr::LetNonRec(Box::new(def), Box::new(force_thunk_call), var_store.fresh()) + Expr::LetNonRec(Box::new(def), Box::new(force_thunk_call)) } fn build_effect_forever( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { // morally @@ -779,38 +717,17 @@ fn build_effect_forever( // // Making `foreverInner` perfectly tail-call optimizable - let forever_symbol = { - scope - .introduce( - "forever".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let forever_symbol = { scope.introduce("forever".into(), Region::zero()).unwrap() }; - let effect = { - scope - .introduce( - "effect".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let effect = { scope.introduce("effect".into(), Region::zero()).unwrap() }; - let body = build_effect_forever_body( - env, - scope, - effect_tag_name.clone(), - forever_symbol, - effect, - var_store, - ); + let body = build_effect_forever_body(scope, effect_symbol, forever_symbol, effect, var_store); - let arguments = vec![(var_store.fresh(), Loc::at_zero(Pattern::Identifier(effect)))]; + let arguments = vec![( + var_store.fresh(), + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::Identifier(effect)), + )]; let function_var = var_store.fresh(); let after_closure = Expr::Closure(ClosureData { @@ -834,20 +751,16 @@ fn build_effect_forever( introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); - let effect_a = build_effect_alias( + let effect_a = build_effect_opaque( effect_symbol, - effect_tag_name.clone(), - "a", var_a, Type::Variable(var_a), var_store, &mut introduced_variables, ); - let effect_b = build_effect_alias( + let effect_b = build_effect_opaque( effect_symbol, - effect_tag_name, - "b", var_b, Type::Variable(var_b), var_store, @@ -886,37 +799,25 @@ fn build_effect_forever( } fn build_effect_forever_body( - env: &mut Env, scope: &mut Scope, - effect_tag_name: TagName, + effect_symbol: Symbol, forever_symbol: Symbol, effect: Symbol, var_store: &mut VarStore, ) -> Expr { let closure_name = { scope - .introduce( - "forever_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("forever_inner".into(), Region::zero()) .unwrap() }; - let inner_body = build_effect_forever_inner_body( - env, - scope, - effect_tag_name.clone(), - forever_symbol, - effect, - var_store, - ); + let inner_body = + build_effect_forever_inner_body(scope, effect_symbol, forever_symbol, effect, var_store); let captured_symbols = vec![effect]; wrap_in_effect_thunk( inner_body, - effect_tag_name, + effect_symbol, closure_name, captured_symbols, var_store, @@ -924,47 +825,31 @@ fn build_effect_forever_body( } fn build_effect_forever_inner_body( - env: &mut Env, scope: &mut Scope, - effect_tag_name: TagName, + effect_symbol: Symbol, forever_symbol: Symbol, effect: Symbol, var_store: &mut VarStore, ) -> Expr { - let thunk1_symbol = { - scope - .introduce( - "thunk1".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let thunk1_symbol = { scope.introduce("thunk1".into(), Region::zero()).unwrap() }; - let thunk2_symbol = { - scope - .introduce( - "thunk2".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() - }; + let thunk2_symbol = { scope.introduce("thunk2".into(), Region::zero()).unwrap() }; - // Effect thunk1 = effect + // @Effect thunk1 = effect let thunk_from_effect = { let whole_var = var_store.fresh(); - let ext_var = var_store.fresh(); let thunk_var = var_store.fresh(); - let pattern = Pattern::AppliedTag { - ext_var, + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let pattern = Pattern::UnwrappedOpaque { whole_var, - tag_name: effect_tag_name.clone(), - arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))], + opaque: effect_symbol, + argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let pattern_vars = SendMap::default(); @@ -1017,12 +902,12 @@ fn build_effect_forever_inner_body( }; // ``` - // Effect thunk2 = forever effect + // @Effect thunk2 = forever effect // thunk2 {} // ``` let force_thunk2 = Loc::at_zero(force_effect( forever_effect, - effect_tag_name, + effect_symbol, thunk2_symbol, var_store, )); @@ -1032,27 +917,22 @@ fn build_effect_forever_inner_body( Box::new(Loc::at_zero(Expr::LetNonRec( Box::new(force_thunk1), Box::new(force_thunk2), - var_store.fresh(), ))), - var_store.fresh(), ) } fn build_effect_loop( - env: &mut Env, scope: &mut Scope, effect_symbol: Symbol, - effect_tag_name: TagName, var_store: &mut VarStore, ) -> (Symbol, Def) { - let loop_symbol = new_symbol!(scope, env, "loop"); - let state_symbol = new_symbol!(scope, env, "state"); - let step_symbol = new_symbol!(scope, env, "step"); + let loop_symbol = new_symbol!(scope, "loop"); + let state_symbol = new_symbol!(scope, "state"); + let step_symbol = new_symbol!(scope, "step"); let body = build_effect_loop_body( - env, scope, - effect_tag_name.clone(), + effect_symbol, loop_symbol, state_symbol, step_symbol, @@ -1062,10 +942,12 @@ fn build_effect_loop( let arguments = vec![ ( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(state_symbol)), ), ( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(Pattern::Identifier(step_symbol)), ), ]; @@ -1092,10 +974,8 @@ fn build_effect_loop( introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); - let effect_b = build_effect_alias( + let effect_b = build_effect_opaque( effect_symbol, - effect_tag_name.clone(), - "b", var_b, Type::Variable(var_b), var_store, @@ -1103,8 +983,8 @@ fn build_effect_loop( ); let state_type = { - let step_tag_name = TagName::Global("Step".into()); - let done_tag_name = TagName::Global("Done".into()); + let step_tag_name = TagName::Tag("Step".into()); + let done_tag_name = TagName::Tag("Done".into()); Type::TagUnion( vec![ @@ -1119,28 +999,20 @@ fn build_effect_loop( let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - let actual = { - Type::TagUnion( - vec![( - effect_tag_name, - vec![Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(state_type.clone()), - )], - )], - TypeExtension::Closed, - ) - }; + let actual = Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(state_type.clone()), + ); Type::Alias { symbol: effect_symbol, - type_arguments: vec![("a".into(), state_type)], + type_arguments: vec![OptAbleType::unbound(state_type)], lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable( closure_var, ))], actual: Box::new(actual), - kind: AliasKind::Structural, + kind: AliasKind::Opaque, } }; @@ -1185,9 +1057,8 @@ fn build_effect_loop( } fn build_effect_loop_body( - env: &mut Env, scope: &mut Scope, - effect_tag_name: TagName, + effect_symbol: Symbol, loop_symbol: Symbol, state_symbol: Symbol, step_symbol: Symbol, @@ -1195,19 +1066,13 @@ fn build_effect_loop_body( ) -> Expr { let closure_name = { scope - .introduce( - "loop_inner".into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) + .introduce("loop_inner".into(), Region::zero()) .unwrap() }; let inner_body = build_effect_loop_inner_body( - env, scope, - effect_tag_name.clone(), + effect_symbol, loop_symbol, state_symbol, step_symbol, @@ -1217,7 +1082,7 @@ fn build_effect_loop_body( let captured_symbols = vec![state_symbol, step_symbol]; wrap_in_effect_thunk( inner_body, - effect_tag_name, + effect_symbol, closure_name, captured_symbols, var_store, @@ -1247,32 +1112,34 @@ fn applied_tag_pattern( } fn build_effect_loop_inner_body( - env: &mut Env, scope: &mut Scope, - effect_tag_name: TagName, + effect_symbol: Symbol, loop_symbol: Symbol, state_symbol: Symbol, step_symbol: Symbol, var_store: &mut VarStore, ) -> Expr { - let thunk1_symbol = new_symbol!(scope, env, "thunk3"); - let thunk2_symbol = new_symbol!(scope, env, "thunk4"); + let thunk1_symbol = new_symbol!(scope, "thunk3"); + let thunk2_symbol = new_symbol!(scope, "thunk4"); - let new_state_symbol = new_symbol!(scope, env, "newState"); - let done_symbol = new_symbol!(scope, env, "done"); + let new_state_symbol = new_symbol!(scope, "newState"); + let done_symbol = new_symbol!(scope, "done"); // Effect thunk1 = step state let thunk_from_effect = { let whole_var = var_store.fresh(); - let ext_var = var_store.fresh(); let thunk_var = var_store.fresh(); - let pattern = Pattern::AppliedTag { - ext_var, + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let pattern = Pattern::UnwrappedOpaque { whole_var, - tag_name: effect_tag_name.clone(), - arguments: vec![(thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))], + opaque: effect_symbol, + argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; let pattern_vars = SendMap::default(); @@ -1332,18 +1199,13 @@ fn build_effect_loop_inner_body( }; // ``` - // Effect thunk2 = loop effect + // @Effect thunk2 = loop effect // thunk2 {} // ``` - let force_thunk2 = force_effect( - loop_new_state_step, - effect_tag_name, - thunk2_symbol, - var_store, - ); + let force_thunk2 = force_effect(loop_new_state_step, effect_symbol, thunk2_symbol, var_store); let step_branch = { - let step_tag_name = TagName::Global("Step".into()); + let step_tag_name = TagName::Tag("Step".into()); let step_pattern = applied_tag_pattern(step_tag_name, &[new_state_symbol], var_store); @@ -1351,17 +1213,19 @@ fn build_effect_loop_inner_body( patterns: vec![Loc::at_zero(step_pattern)], value: Loc::at_zero(force_thunk2), guard: None, + redundant: RedundantMark::new(var_store), } }; let done_branch = { - let done_tag_name = TagName::Global("Done".into()); + let done_tag_name = TagName::Tag("Done".into()); let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store); crate::expr::WhenBranch { patterns: vec![Loc::at_zero(done_pattern)], value: Loc::at_zero(Expr::Var(done_symbol)), guard: None, + redundant: RedundantMark::new(var_store), } }; @@ -1373,21 +1237,21 @@ fn build_effect_loop_inner_body( region: Region::zero(), loc_cond: Box::new(force_thunk_call), branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; Expr::LetNonRec( Box::new(thunk_from_effect), Box::new(Loc::at_zero(match_on_force_thunk1)), - var_store.fresh(), ) } pub fn build_host_exposed_def( - env: &mut Env, scope: &mut Scope, symbol: Symbol, ident: &str, - effect_tag_name: TagName, + effect_symbol: Symbol, var_store: &mut VarStore, annotation: crate::annotation::Annotation, ) -> Def { @@ -1396,31 +1260,35 @@ pub fn build_host_exposed_def( let mut pattern_vars = SendMap::default(); pattern_vars.insert(symbol, expr_var); - let mut arguments: Vec<(Variable, Loc)> = Vec::new(); + let mut arguments: Vec<(Variable, AnnotatedMark, Loc)> = Vec::new(); let mut linked_symbol_arguments: Vec<(Variable, Expr)> = Vec::new(); let mut captured_symbols: Vec<(Symbol, Variable)> = Vec::new(); + let crate::annotation::Annotation { + introduced_variables, + typ, + aliases, + .. + } = annotation; + let def_body = { - match annotation.typ.shallow_dealias() { + match typ.shallow_structural_dealias() { Type::Function(args, _, _) => { for i in 0..args.len() { let name = format!("closure_arg_{}_{}", ident, i); let arg_symbol = { let ident = name.clone().into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() + scope.introduce(ident, Region::zero()).unwrap() }; let arg_var = var_store.fresh(); - arguments.push((arg_var, Loc::at_zero(Pattern::Identifier(arg_symbol)))); + arguments.push(( + arg_var, + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::Identifier(arg_symbol)), + )); captured_symbols.push((arg_symbol, arg_var)); linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol))); @@ -1437,14 +1305,7 @@ pub fn build_host_exposed_def( let name = format!("effect_closure_{}", ident); let ident = name.into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() + scope.introduce(ident, Region::zero()).unwrap() }; let effect_closure = Expr::Closure(ClosureData { @@ -1457,16 +1318,21 @@ pub fn build_host_exposed_def( recursive: Recursive::NotRecursive, arguments: vec![( var_store.fresh(), + AnnotatedMark::new(var_store), Loc::at_zero(empty_record_pattern(var_store)), )], loc_body: Box::new(Loc::at_zero(low_level_call)), }); - let body = Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name, - arguments: vec![(var_store.fresh(), Loc::at_zero(effect_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let body = Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(effect_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, }; Expr::Closure(ClosureData { @@ -1495,14 +1361,7 @@ pub fn build_host_exposed_def( let name = format!("effect_closure_{}", ident); let ident = name.into(); - scope - .introduce( - ident, - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap() + scope.introduce(ident, Region::zero()).unwrap() }; let empty_record_pattern = Pattern::RecordDestructure { @@ -1519,24 +1378,32 @@ pub fn build_host_exposed_def( name: effect_closure_symbol, captured_symbols, recursive: Recursive::NotRecursive, - arguments: vec![(var_store.fresh(), Loc::at_zero(empty_record_pattern))], + arguments: vec![( + var_store.fresh(), + AnnotatedMark::new(var_store), + Loc::at_zero(empty_record_pattern), + )], loc_body: Box::new(Loc::at_zero(low_level_call)), }); - Expr::Tag { - variant_var: var_store.fresh(), - ext_var: var_store.fresh(), - name: effect_tag_name, - arguments: vec![(var_store.fresh(), Loc::at_zero(effect_closure))], + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: effect_symbol, + argument: Box::new((var_store.fresh(), Loc::at_zero(effect_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, } } } }; let def_annotation = crate::def::Annotation { - signature: annotation.typ, - introduced_variables: annotation.introduced_variables, - aliases: annotation.aliases, + signature: typ, + introduced_variables, + aliases, region: Region::zero(), }; @@ -1549,10 +1416,19 @@ pub fn build_host_exposed_def( } } -fn build_effect_alias( +pub fn build_effect_actual(a_type: Type, var_store: &mut VarStore) -> Type { + let closure_var = var_store.fresh(); + + Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(a_type), + ) +} + +/// Effect a := {} -> a +fn build_effect_opaque( effect_symbol: Symbol, - effect_tag_name: TagName, - a_name: &str, a_var: Variable, a_type: Type, var_store: &mut VarStore, @@ -1561,47 +1437,42 @@ fn build_effect_alias( let closure_var = var_store.fresh(); introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - let actual = { - Type::TagUnion( - vec![( - effect_tag_name, - vec![Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(a_type), - )], - )], - TypeExtension::Closed, - ) - }; + let actual = Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(a_type), + ); Type::Alias { symbol: effect_symbol, - type_arguments: vec![(a_name.into(), Type::Variable(a_var))], + type_arguments: vec![OptAbleType::unbound(Type::Variable(a_var))], lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(closure_var))], actual: Box::new(actual), - kind: AliasKind::Structural, + kind: AliasKind::Opaque, } } -pub fn build_effect_actual( - effect_tag_name: TagName, - a_type: Type, +fn build_fresh_opaque_variables( var_store: &mut VarStore, -) -> Type { +) -> (Box, Vec, Vec) { let closure_var = var_store.fresh(); - Type::TagUnion( - vec![( - effect_tag_name, - vec![Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(a_type), - )], - )], - TypeExtension::Closed, - ) + // NB: if there are bugs, check whether not introducing variables is a problem! + // introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); + + let a_var = var_store.fresh(); + let actual = Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(Type::Variable(a_var)), + ); + let type_arguments = vec![OptAbleVar { + var: a_var, + opt_ability: None, + }]; + let lambda_set_variables = vec![roc_types::types::LambdaSet(Type::Variable(closure_var))]; + + (Box::new(actual), type_arguments, lambda_set_variables) } #[inline(always)] diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index 8c3f3fb8b1..12cb7ce00c 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -1,17 +1,18 @@ use crate::procedure::References; +use crate::scope::Scope; use roc_collections::{MutMap, VecSet}; use roc_module::ident::{Ident, Lowercase, ModuleName}; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_module::symbol::{IdentIdsByModule, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; /// The canonicalization environment for a particular module. pub struct Env<'a> { - /// The module's path. Private tags and unqualified references to identifiers + /// The module's path. Opaques and unqualified references to identifiers /// are assumed to be relative to this path. pub home: ModuleId, - pub dep_idents: &'a MutMap, + pub dep_idents: &'a IdentIdsByModule, pub module_ids: &'a ModuleIds, @@ -24,9 +25,6 @@ pub struct Env<'a> { /// current tail-callable symbol pub tailcallable_symbol: Option, - /// current closure name (if any) - pub closure_name_symbol: Option, - /// Symbols of values/functions which were referenced by qualified lookups. pub qualified_value_lookups: VecSet, @@ -34,30 +32,23 @@ pub struct Env<'a> { pub qualified_type_lookups: VecSet, pub top_level_symbols: VecSet, - - pub ident_ids: IdentIds, - pub exposed_ident_ids: IdentIds, } impl<'a> Env<'a> { pub fn new( home: ModuleId, - dep_idents: &'a MutMap, + dep_idents: &'a IdentIdsByModule, module_ids: &'a ModuleIds, - exposed_ident_ids: IdentIds, ) -> Env<'a> { Env { home, dep_idents, module_ids, - ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later - exposed_ident_ids, problems: Vec::new(), closures: MutMap::default(), qualified_value_lookups: VecSet::default(), qualified_type_lookups: VecSet::default(), tailcallable_symbol: None, - closure_name_symbol: None, top_level_symbols: VecSet::default(), } } @@ -65,6 +56,7 @@ impl<'a> Env<'a> { /// Returns Err if the symbol resolved, but it was not exposed by the given module pub fn qualified_lookup( &mut self, + scope: &Scope, module_name_str: &str, ident: &str, region: Region, @@ -85,7 +77,7 @@ impl<'a> Env<'a> { // You can do qualified lookups on your own module, e.g. // if I'm in the Foo module, I can do a `Foo.bar` lookup. if module_id == self.home { - match self.ident_ids.get_id(&ident) { + match scope.locals.ident_ids.get_id(&ident) { Some(ident_id) => { let symbol = Symbol::new(module_id, ident_id); @@ -103,9 +95,11 @@ impl<'a> Env<'a> { value: ident, region, }, - self.ident_ids - .idents() - .map(|(_, string)| string.as_ref().into()) + scope + .locals + .ident_ids + .ident_strs() + .map(|(_, string)| string.into()) .collect(), ); Err(error) @@ -127,11 +121,11 @@ impl<'a> Env<'a> { } None => { let exposed_values = exposed_ids - .idents() + .ident_strs() .filter(|(_, ident)| { - ident.as_ref().starts_with(|c: char| c.is_lowercase()) + ident.starts_with(|c: char| c.is_lowercase()) }) - .map(|(_, ident)| Lowercase::from(ident.as_ref())) + .map(|(_, ident)| Lowercase::from(ident)) .collect(); Err(RuntimeError::ValueNotExposed { module_name, @@ -168,22 +162,7 @@ impl<'a> Env<'a> { } } - /// Generates a unique, new symbol like "$1" or "$5", - /// using the home module as the module_id. - /// - /// This is used, for example, during canonicalization of an Expr::Closure - /// to generate a unique symbol to refer to that closure. - pub fn gen_unique_symbol(&mut self) -> Symbol { - let ident_id = self.ident_ids.gen_unique(); - - Symbol::new(self.home, ident_id) - } - pub fn problem(&mut self, problem: Problem) { self.problems.push(problem) } - - pub fn register_closure(&mut self, symbol: Symbol, references: References) { - self.closures.insert(symbol, references); - } } diff --git a/compiler/can/src/exhaustive.rs b/compiler/can/src/exhaustive.rs new file mode 100644 index 0000000000..c97f9d0013 --- /dev/null +++ b/compiler/can/src/exhaustive.rs @@ -0,0 +1,423 @@ +use crate::expr::{IntValue, WhenBranch}; +use crate::pattern::DestructType; +use roc_collections::all::HumanIndex; +use roc_error_macros::internal_error; +use roc_exhaustive::{ + is_useful, Ctor, CtorName, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, +}; +use roc_module::ident::{TagIdIntType, TagName}; +use roc_region::all::{Loc, Region}; +use roc_types::subs::{Content, FlatType, RedundantMark, Subs, SubsFmtContent, Variable}; +use roc_types::types::AliasKind; + +pub use roc_exhaustive::Context as ExhaustiveContext; + +pub const GUARD_CTOR: &str = "#Guard"; +pub const NONEXHAUSIVE_CTOR: &str = "#Open"; + +pub struct ExhaustiveSummary { + pub errors: Vec, + pub exhaustive: bool, + pub redundancies: Vec, +} + +pub fn check( + subs: &Subs, + sketched_rows: SketchedRows, + context: ExhaustiveContext, +) -> ExhaustiveSummary { + let overall_region = sketched_rows.overall_region; + let mut all_errors = Vec::with_capacity(1); + + let NonRedundantSummary { + non_redundant_rows, + errors, + redundancies, + } = sketched_rows.reify_to_non_redundant(subs); + all_errors.extend(errors); + + let exhaustive = match roc_exhaustive::check(overall_region, context, non_redundant_rows) { + Ok(()) => true, + Err(errors) => { + all_errors.extend(errors); + false + } + }; + + ExhaustiveSummary { + errors: all_errors, + exhaustive, + redundancies, + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum SketchedPattern { + Anything, + Literal(Literal), + Ctor(Variable, TagName, Vec), + KnownCtor(Union, TagId, Vec), +} + +impl SketchedPattern { + fn reify(self, subs: &Subs) -> Pattern { + match self { + Self::Anything => Pattern::Anything, + Self::Literal(lit) => Pattern::Literal(lit), + Self::KnownCtor(union, tag_id, patterns) => Pattern::Ctor( + union, + tag_id, + patterns.into_iter().map(|pat| pat.reify(subs)).collect(), + ), + Self::Ctor(var, tag_name, patterns) => { + let (union, tag_id) = convert_tag(subs, var, &tag_name); + Pattern::Ctor( + union, + tag_id, + patterns.into_iter().map(|pat| pat.reify(subs)).collect(), + ) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct SketchedRow { + patterns: Vec, + region: Region, + guard: Guard, + redundant_mark: RedundantMark, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SketchedRows { + rows: Vec, + overall_region: Region, +} + +impl SketchedRows { + fn reify_to_non_redundant(self, subs: &Subs) -> NonRedundantSummary { + to_nonredundant_rows(subs, self) + } +} + +fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedPattern { + use crate::pattern::Pattern::*; + use SketchedPattern as SP; + + match pattern { + &NumLiteral(_, _, IntValue::I128(n), _) | &IntLiteral(_, _, _, IntValue::I128(n), _) => { + SP::Literal(Literal::Int(n)) + } + &NumLiteral(_, _, IntValue::U128(n), _) | &IntLiteral(_, _, _, IntValue::U128(n), _) => { + SP::Literal(Literal::U128(n)) + } + &FloatLiteral(_, _, _, f, _) => SP::Literal(Literal::Float(f64::to_bits(f))), + StrLiteral(v) => SP::Literal(Literal::Str(v.clone())), + &SingleQuote(c) => SP::Literal(Literal::Byte(c as u8)), + RecordDestructure { destructs, .. } => { + let tag_id = TagId(0); + let mut patterns = std::vec::Vec::with_capacity(destructs.len()); + let mut field_names = std::vec::Vec::with_capacity(destructs.len()); + + for Loc { + value: destruct, + region: _, + } in destructs + { + field_names.push(destruct.label.clone()); + + match &destruct.typ { + DestructType::Required | DestructType::Optional(..) => { + patterns.push(SP::Anything) + } + DestructType::Guard(_, guard) => { + patterns.push(sketch_pattern(destruct.var, &guard.value)) + } + } + } + + let union = Union { + render_as: RenderAs::Record(field_names), + alternatives: vec![Ctor { + name: CtorName::Tag(TagName::Tag("#Record".into())), + tag_id, + arity: destructs.len(), + }], + }; + + SP::KnownCtor(union, tag_id, patterns) + } + + AppliedTag { + tag_name, + arguments, + .. + } => { + let simplified_args: std::vec::Vec<_> = arguments + .iter() + .map(|(var, arg)| sketch_pattern(*var, &arg.value)) + .collect(); + + SP::Ctor(var, tag_name.clone(), simplified_args) + } + + UnwrappedOpaque { + opaque, argument, .. + } => { + let (arg_var, argument) = &(**argument); + + let tag_id = TagId(0); + + let union = Union { + render_as: RenderAs::Opaque, + alternatives: vec![Ctor { + name: CtorName::Opaque(*opaque), + tag_id, + arity: 1, + }], + }; + + SP::KnownCtor( + union, + tag_id, + vec![sketch_pattern(*arg_var, &argument.value)], + ) + } + + // Treat this like a literal so we mark it as non-exhaustive + MalformedPattern(..) => SP::Literal(Literal::Byte(1)), + + Underscore + | Identifier(_) + | AbilityMemberSpecialization { .. } + | Shadowed(..) + | OpaqueNotInScope(..) + | UnsupportedPattern(..) => SP::Anything, + } +} + +pub fn sketch_when_branches( + target_var: Variable, + region: Region, + patterns: &[WhenBranch], +) -> SketchedRows { + let mut rows: Vec = Vec::with_capacity(patterns.len()); + + // If any of the branches has a guard, e.g. + // + // when x is + // y if y < 10 -> "foo" + // _ -> "bar" + // + // then we treat it as a pattern match on the pattern and a boolean, wrapped in the #Guard + // constructor. We can use this special constructor name to generate better error messages. + // This transformation of the pattern match only works because we only report exhaustiveness + // errors: the Pattern created in this file is not used for code gen. + // + // when x is + // #Guard y True -> "foo" + // #Guard _ _ -> "bar" + let any_has_guard = patterns.iter().any(|branch| branch.guard.is_some()); + + use SketchedPattern as SP; + for WhenBranch { + patterns, + guard, + value: _, + redundant, + } in patterns + { + let guard = if guard.is_some() { + Guard::HasGuard + } else { + Guard::NoGuard + }; + + for loc_pat in patterns { + // Decompose each pattern in the branch into its own row. + + let patterns = if any_has_guard { + let guard_pattern = match guard { + Guard::HasGuard => SP::Literal(Literal::Bit(true)), + Guard::NoGuard => SP::Anything, + }; + + let tag_id = TagId(0); + + let union = Union { + render_as: RenderAs::Guard, + alternatives: vec![Ctor { + tag_id, + name: CtorName::Tag(TagName::Tag(GUARD_CTOR.into())), + arity: 2, + }], + }; + + vec![SP::KnownCtor( + union, + tag_id, + // NB: ordering the guard pattern first seems to be better at catching + // non-exhaustive constructors in the second argument; see the paper to see if + // there is a way to improve this in general. + vec![guard_pattern, sketch_pattern(target_var, &loc_pat.value)], + )] + } else { + // Simple case + vec![sketch_pattern(target_var, &loc_pat.value)] + }; + + let row = SketchedRow { + patterns, + region: loc_pat.region, + guard, + redundant_mark: *redundant, + }; + rows.push(row); + } + } + + SketchedRows { + rows, + overall_region: region, + } +} + +pub fn sketch_pattern_to_rows( + target_var: Variable, + region: Region, + pattern: &crate::pattern::Pattern, +) -> SketchedRows { + let row = SketchedRow { + patterns: vec![sketch_pattern(target_var, pattern)], + region, + // A single row cannot be redundant! + redundant_mark: RedundantMark::known_non_redundant(), + guard: Guard::NoGuard, + }; + SketchedRows { + rows: vec![row], + overall_region: region, + } +} + +/// REDUNDANT PATTERNS + +struct NonRedundantSummary { + non_redundant_rows: Vec>, + redundancies: Vec, + errors: Vec, +} + +/// INVARIANT: Produces a list of rows where (forall row. length row == 1) +fn to_nonredundant_rows(subs: &Subs, rows: SketchedRows) -> NonRedundantSummary { + let SketchedRows { + rows, + overall_region, + } = rows; + let mut checked_rows = Vec::with_capacity(rows.len()); + + let mut redundancies = vec![]; + let mut errors = vec![]; + + for SketchedRow { + patterns, + guard, + region, + redundant_mark, + } in rows.into_iter() + { + let next_row: Vec = patterns + .into_iter() + .map(|pattern| pattern.reify(subs)) + .collect(); + + if matches!(guard, Guard::HasGuard) || is_useful(checked_rows.clone(), next_row.clone()) { + checked_rows.push(next_row); + } else { + redundancies.push(redundant_mark); + errors.push(Error::Redundant { + overall_region, + branch_region: region, + index: HumanIndex::zero_based(checked_rows.len()), + }); + } + } + + NonRedundantSummary { + non_redundant_rows: checked_rows, + redundancies, + errors, + } +} + +fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union, TagId) { + let content = subs.get_content_without_compacting(whole_var); + + use {Content::*, FlatType::*}; + + match dealias_tag(subs, content) { + Structure(TagUnion(tags, ext) | RecursiveTagUnion(_, tags, ext)) => { + let (sorted_tags, ext) = tags.sorted_iterator_and_ext(subs, *ext); + + let mut num_tags = sorted_tags.len(); + + // DEVIATION: model openness by attaching a #Open constructor, that can never + // be matched unless there's an `Anything` pattern. + let opt_openness_tag = match subs.get_content_without_compacting(ext) { + FlexVar(_) | RigidVar(_) => { + let openness_tag = TagName::Tag(NONEXHAUSIVE_CTOR.into()); + num_tags += 1; + Some((openness_tag, &[] as _)) + } + Structure(EmptyTagUnion) => None, + // Anything else is erroneous and we ignore + _ => None, + }; + + // High tag ID if we're out-of-bounds. + let mut my_tag_id = TagId(num_tags as TagIdIntType); + + let mut alternatives = Vec::with_capacity(num_tags); + let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter()); + + for (index, (tag, args)) in alternatives_iter.enumerate() { + let tag_id = TagId(index as TagIdIntType); + if this_tag == &tag { + my_tag_id = tag_id; + } + alternatives.push(Ctor { + name: CtorName::Tag(tag), + tag_id, + arity: args.len(), + }); + } + + let union = Union { + alternatives, + render_as: RenderAs::Tag, + }; + + (union, my_tag_id) + } + _ => internal_error!( + "Content is not a tag union: {:?}", + SubsFmtContent(content, subs) + ), + } +} + +pub fn dealias_tag<'a>(subs: &'a Subs, content: &'a Content) -> &'a Content { + use Content::*; + let mut result = content; + loop { + match result { + Alias(_, _, real_var, AliasKind::Structural) + | RecursionVar { + structure: real_var, + .. + } => result = subs.get_content_without_compacting(*real_var), + _ => return result, + } + } +} diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 2dade79cd6..3b6c99898a 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -1,3 +1,4 @@ +use crate::abilities::SpecializationId; use crate::annotation::{freshen_opaque_def, IntroducedVariables}; use crate::builtins::builtin_defs_map; use crate::def::{can_defs_with_return, Def}; @@ -6,10 +7,10 @@ use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result, int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound, }; -use crate::pattern::{canonicalize_pattern, Pattern}; +use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern}; use crate::procedure::References; use crate::scope::Scope; -use roc_collections::{MutSet, SendMap, VecMap, VecSet}; +use roc_collections::{SendMap, VecMap, VecSet}; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -18,8 +19,8 @@ use roc_parse::ast::{self, EscapedChar, StrLiteral}; use roc_parse::pattern::PatternType::*; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; -use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, LambdaSet, Type}; +use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; +use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type}; use std::fmt::{Debug, Display}; use std::{char, u32}; @@ -30,6 +31,7 @@ pub struct Output { pub introduced_variables: IntroducedVariables, pub aliases: VecMap, pub non_closures: VecSet, + pub abilities_in_scope: Vec, } impl Output { @@ -82,13 +84,27 @@ pub enum Expr { // Lookups Var(Symbol), + AbilityMember( + /// Actual member name + Symbol, + /// Specialization to use + SpecializationId, + ), + // Branching When { + /// The actual condition of the when expression. + loc_cond: Box>, cond_var: Variable, + /// Result type produced by the branches. expr_var: Variable, region: Region, - loc_cond: Box>, + /// The branches of the when, and the type of the condition that they expect to be matched + /// against. branches: Vec, + branches_cond_var: Variable, + /// Whether the branches are exhaustive. + exhaustive: ExhaustiveMark, }, If { cond_var: Variable, @@ -98,8 +114,8 @@ pub enum Expr { }, // Let - LetRec(Vec, Box>, Variable), - LetNonRec(Box, Box>, Variable), + LetRec(Vec, Box>), + LetNonRec(Box, Box>), /// This is *only* for calling functions, not for tag application. /// The Tag variant contains any applied values inside it. @@ -163,8 +179,7 @@ pub enum Expr { name: TagName, }, - /// A wrapping of an opaque type, like `$Age 21` - // TODO(opaques): $->@ above when opaques land + /// A wrapping of an opaque type, like `@Age 21` OpaqueRef { opaque_var: Variable, name: Symbol, @@ -184,7 +199,7 @@ pub enum Expr { // for the expression from the opaque definition. `type_arguments` is something like // [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]". specialized_def_type: Box, - type_arguments: Vec<(Lowercase, Type)>, + type_arguments: Vec, lambda_set_variables: Vec, }, @@ -194,6 +209,74 @@ pub enum Expr { // Compiles, but will crash if reached RuntimeError(RuntimeError), } + +impl Expr { + pub fn category(&self) -> Category { + match self { + Self::Num(..) => Category::Num, + Self::Int(..) => Category::Int, + Self::Float(..) => Category::Float, + Self::Str(..) => Category::Str, + Self::SingleQuote(..) => Category::Character, + Self::List { .. } => Category::List, + &Self::Var(sym) => Category::Lookup(sym), + &Self::AbilityMember(sym, _) => Category::Lookup(sym), + Self::When { .. } => Category::When, + Self::If { .. } => Category::If, + Self::LetRec(_, expr) => expr.value.category(), + Self::LetNonRec(_, expr) => expr.value.category(), + &Self::Call(_, _, called_via) => Category::CallResult(None, called_via), + &Self::RunLowLevel { op, .. } => Category::LowLevelOpResult(op), + Self::ForeignCall { .. } => Category::ForeignCall, + Self::Closure(..) => Category::Lambda, + Self::Record { .. } => Category::Record, + Self::EmptyRecord => Category::Record, + Self::Access { field, .. } => Category::Access(field.clone()), + Self::Accessor(data) => Category::Accessor(data.field.clone()), + Self::Update { .. } => Category::Record, + Self::Tag { + name, arguments, .. + } => Category::TagApply { + tag_name: name.clone(), + args_count: arguments.len(), + }, + Self::ZeroArgumentTag { name, .. } => Category::TagApply { + tag_name: name.clone(), + args_count: 0, + }, + &Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name), + Self::Expect(..) => Category::Expect, + Self::RuntimeError(..) => Category::Unknown, + } + } +} + +/// Stores exhaustiveness-checking metadata for a closure argument that may +/// have an annotated type. +#[derive(Clone, Copy, Debug)] +pub struct AnnotatedMark { + pub annotation_var: Variable, + pub exhaustive: ExhaustiveMark, +} + +impl AnnotatedMark { + pub fn new(var_store: &mut VarStore) -> Self { + Self { + annotation_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), + } + } + + // NOTE: only ever use this if you *know* a pattern match is surely exhaustive! + // Otherwise you will get unpleasant unification errors. + pub fn known_exhaustive() -> Self { + Self { + annotation_var: Variable::EMPTY_TAG_UNION, + exhaustive: ExhaustiveMark::known_exhaustive(), + } + } +} + #[derive(Clone, Debug)] pub struct ClosureData { pub function_type: Variable, @@ -203,7 +286,7 @@ pub struct ClosureData { pub name: Symbol, pub captured_symbols: Vec<(Symbol, Variable)>, pub recursive: Recursive, - pub arguments: Vec<(Variable, Loc)>, + pub arguments: Vec<(Variable, AnnotatedMark, Loc)>, pub loc_body: Box>, } @@ -255,7 +338,11 @@ impl AccessorData { let loc_body = Loc::at_zero(body); - let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))]; + let arguments = vec![( + record_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(record_symbol)), + )]; ClosureData { function_type: function_var, @@ -279,7 +366,7 @@ pub struct Field { pub loc_expr: Box>, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Recursive { NotRecursive = 0, Recursive = 1, @@ -291,6 +378,36 @@ pub struct WhenBranch { pub patterns: Vec>, pub value: Loc, pub guard: Option>, + /// Whether this branch is redundant in the `when` it appears in + pub redundant: RedundantMark, +} + +impl WhenBranch { + pub fn pattern_region(&self) -> Region { + Region::span_across( + &self + .patterns + .first() + .expect("when branch has no pattern?") + .region, + &self + .patterns + .last() + .expect("when branch has no pattern?") + .region, + ) + } +} + +impl WhenBranch { + pub fn region(&self) -> Region { + Region::across_all( + self.patterns + .iter() + .map(|p| &p.region) + .chain([self.value.region].iter()), + ) + } } pub fn canonicalize_expr<'a>( @@ -596,139 +713,19 @@ pub fn canonicalize_expr<'a>( (RuntimeError(problem), Output::default()) } ast::Expr::Defs(loc_defs, loc_ret) => { - can_defs_with_return( - env, - var_store, - // The body expression gets a new scope for canonicalization, - // so clone it. - scope.clone(), - loc_defs, - loc_ret, - ) + // The body expression gets a new scope for canonicalization, + scope.inner_scope(|inner_scope| { + can_defs_with_return(env, var_store, inner_scope, loc_defs, loc_ret) + }) } ast::Expr::Backpassing(_, _, _) => { unreachable!("Backpassing should have been desugared by now") } ast::Expr::Closure(loc_arg_patterns, loc_body_expr) => { - // The globally unique symbol that will refer to this closure once it gets converted - // into a top-level procedure for code gen. - // - // In the Foo module, this will look something like Foo.$1 or Foo.$2. - let symbol = env - .closure_name_symbol - .unwrap_or_else(|| env.gen_unique_symbol()); - env.closure_name_symbol = None; + let (closure_data, output) = + canonicalize_closure(env, var_store, scope, loc_arg_patterns, loc_body_expr, None); - // The body expression gets a new scope for canonicalization. - // Shadow `scope` to make sure we don't accidentally use the original one for the - // rest of this block, but keep the original around for later diffing. - let original_scope = scope; - let mut scope = original_scope.clone(); - let mut can_args = Vec::with_capacity(loc_arg_patterns.len()); - let mut output = Output::default(); - - for loc_pattern in loc_arg_patterns.iter() { - let can_argument_pattern = canonicalize_pattern( - env, - var_store, - &mut scope, - &mut output, - FunctionArg, - &loc_pattern.value, - loc_pattern.region, - ); - - can_args.push((var_store.fresh(), can_argument_pattern)); - } - - let bound_by_argument_patterns: Vec<_> = - output.references.bound_symbols().copied().collect(); - - let (loc_body_expr, new_output) = canonicalize_expr( - env, - var_store, - &mut scope, - loc_body_expr.region, - &loc_body_expr.value, - ); - - let mut captured_symbols: MutSet = - new_output.references.value_lookups().copied().collect(); - - // filter out the closure's name itself - captured_symbols.remove(&symbol); - - // symbols bound either in this pattern or deeper down are not captured! - captured_symbols.retain(|s| !new_output.references.bound_symbols().any(|x| x == s)); - captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); - - // filter out top-level symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| !env.top_level_symbols.contains(s)); - - // filter out imported symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| s.module_id() == env.home); - - // TODO any Closure that has an empty `captured_symbols` list could be excluded! - - output.union(new_output); - - // filter out aliases - debug_assert!(captured_symbols - .iter() - .all(|s| !output.references.references_type_def(*s))); - // captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s)); - - // filter out functions that don't close over anything - captured_symbols.retain(|s| !output.non_closures.contains(s)); - - // Now that we've collected all the references, check to see if any of the args we defined - // went unreferenced. If any did, report them as unused arguments. - for (sub_symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(*sub_symbol) { - if !output.references.has_value_lookup(*sub_symbol) { - // The body never referenced this argument we declared. It's an unused argument! - env.problem(Problem::UnusedArgument(symbol, *sub_symbol, *region)); - } - - // We shouldn't ultimately count arguments as referenced locals. Otherwise, - // we end up with weird conclusions like the expression (\x -> x + 1) - // references the (nonexistent) local variable x! - output.references.remove_value_lookup(sub_symbol); - } - } - - env.register_closure(symbol, output.references.clone()); - - let mut captured_symbols: Vec<_> = captured_symbols - .into_iter() - .map(|s| (s, var_store.fresh())) - .collect(); - - // sort symbols, so we know the order in which they're stored in the closure record - captured_symbols.sort(); - - // store that this function doesn't capture anything. It will be promoted to a - // top-level function, and does not need to be captured by other surrounding functions. - if captured_symbols.is_empty() { - output.non_closures.insert(symbol); - } - - ( - Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - closure_ext_var: var_store.fresh(), - return_type: var_store.fresh(), - name: symbol, - captured_symbols, - recursive: Recursive::NotRecursive, - arguments: can_args, - loc_body: Box::new(loc_body_expr), - }), - output, - ) + (Closure(closure_data), output) } ast::Expr::When(loc_cond, branches) => { // Infer the condition expression's type. @@ -742,8 +739,16 @@ pub fn canonicalize_expr<'a>( let mut can_branches = Vec::with_capacity(branches.len()); for branch in branches.iter() { - let (can_when_branch, branch_references) = - canonicalize_when_branch(env, var_store, scope, region, *branch, &mut output); + let (can_when_branch, branch_references) = scope.inner_scope(|inner_scope| { + canonicalize_when_branch( + env, + var_store, + inner_scope, + region, + *branch, + &mut output, + ) + }); output.references.union_mut(&branch_references); @@ -764,6 +769,8 @@ pub fn canonicalize_expr<'a>( region, loc_cond: Box::new(can_cond), branches: can_branches, + branches_cond_var: var_store.fresh(), + exhaustive: ExhaustiveMark::new(var_store), }; (expr, output) @@ -784,7 +791,7 @@ pub fn canonicalize_expr<'a>( } ast::Expr::AccessorFunction(field) => ( Accessor(AccessorData { - name: env.gen_unique_symbol(), + name: scope.gen_unique_symbol(), function_var: var_store.fresh(), record_var: var_store.fresh(), ext_var: var_store.fresh(), @@ -795,15 +802,15 @@ pub fn canonicalize_expr<'a>( }), Output::default(), ), - ast::Expr::GlobalTag(tag) => { + ast::Expr::Tag(tag) => { let variant_var = var_store.fresh(); let ext_var = var_store.fresh(); - let symbol = env.gen_unique_symbol(); + let symbol = scope.gen_unique_symbol(); ( ZeroArgumentTag { - name: TagName::Global((*tag).into()), + name: TagName::Tag((*tag).into()), variant_var, closure_name: symbol, ext_var, @@ -811,23 +818,6 @@ pub fn canonicalize_expr<'a>( Output::default(), ) } - ast::Expr::PrivateTag(tag) => { - let variant_var = var_store.fresh(); - let ext_var = var_store.fresh(); - let tag_ident = env.ident_ids.get_or_insert(&(*tag).into()); - let symbol = Symbol::new(env.home, tag_ident); - let lambda_set_symbol = env.gen_unique_symbol(); - - ( - ZeroArgumentTag { - name: TagName::Private(symbol), - variant_var, - ext_var, - closure_name: lambda_set_symbol, - }, - Output::default(), - ) - } ast::Expr::OpaqueRef(opaque_ref) => { // If we're here, the opaque reference is definitely not wrapping an argument - wrapped // arguments are handled in the Apply branch. @@ -1016,6 +1006,133 @@ pub fn canonicalize_expr<'a>( ) } +pub fn canonicalize_closure<'a>( + env: &mut Env<'a>, + var_store: &mut VarStore, + scope: &mut Scope, + loc_arg_patterns: &'a [Loc>], + loc_body_expr: &'a Loc>, + opt_def_name: Option, +) -> (ClosureData, Output) { + scope.inner_scope(|inner_scope| { + canonicalize_closure_body( + env, + var_store, + inner_scope, + loc_arg_patterns, + loc_body_expr, + opt_def_name, + ) + }) +} + +fn canonicalize_closure_body<'a>( + env: &mut Env<'a>, + var_store: &mut VarStore, + scope: &mut Scope, + loc_arg_patterns: &'a [Loc>], + loc_body_expr: &'a Loc>, + opt_def_name: Option, +) -> (ClosureData, Output) { + // The globally unique symbol that will refer to this closure once it gets converted + // into a top-level procedure for code gen. + let symbol = opt_def_name.unwrap_or_else(|| scope.gen_unique_symbol()); + + let mut can_args = Vec::with_capacity(loc_arg_patterns.len()); + let mut output = Output::default(); + + for loc_pattern in loc_arg_patterns.iter() { + let can_argument_pattern = canonicalize_pattern( + env, + var_store, + scope, + &mut output, + FunctionArg, + &loc_pattern.value, + loc_pattern.region, + ); + + can_args.push(( + var_store.fresh(), + AnnotatedMark::new(var_store), + can_argument_pattern, + )); + } + + let bound_by_argument_patterns: Vec<_> = + BindingsFromPattern::new_many(can_args.iter().map(|x| &x.2)).collect(); + + let (loc_body_expr, new_output) = canonicalize_expr( + env, + var_store, + scope, + loc_body_expr.region, + &loc_body_expr.value, + ); + + let mut captured_symbols: Vec<_> = new_output + .references + .value_lookups() + .copied() + // filter out the closure's name itself + .filter(|s| *s != symbol) + // symbols bound either in this pattern or deeper down are not captured! + .filter(|s| !new_output.references.bound_symbols().any(|x| x == s)) + .filter(|s| bound_by_argument_patterns.iter().all(|(k, _)| s != k)) + // filter out top-level symbols those will be globally available, and don't need to be captured + .filter(|s| !env.top_level_symbols.contains(s)) + // filter out imported symbols those will be globally available, and don't need to be captured + .filter(|s| s.module_id() == env.home) + // filter out functions that don't close over anything + .filter(|s| !new_output.non_closures.contains(s)) + .filter(|s| !output.non_closures.contains(s)) + .map(|s| (s, var_store.fresh())) + .collect(); + + output.union(new_output); + + // Now that we've collected all the references, check to see if any of the args we defined + // went unreferenced. If any did, report them as unused arguments. + for (sub_symbol, region) in bound_by_argument_patterns { + if !output.references.has_value_lookup(sub_symbol) { + // The body never referenced this argument we declared. It's an unused argument! + env.problem(Problem::UnusedArgument(symbol, sub_symbol, region)); + } else { + // We shouldn't ultimately count arguments as referenced locals. Otherwise, + // we end up with weird conclusions like the expression (\x -> x + 1) + // references the (nonexistent) local variable x! + output.references.remove_value_lookup(&sub_symbol); + } + } + + // store the references of this function in the Env. This information is used + // when we canonicalize a surrounding def (if it exists) + env.closures.insert(symbol, output.references.clone()); + + // sort symbols, so we know the order in which they're stored in the closure record + captured_symbols.sort(); + + // store that this function doesn't capture anything. It will be promoted to a + // top-level function, and does not need to be captured by other surrounding functions. + if captured_symbols.is_empty() { + output.non_closures.insert(symbol); + } + + let closure_data = ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: symbol, + captured_symbols, + recursive: Recursive::NotRecursive, + arguments: can_args, + loc_body: Box::new(loc_body_expr), + }; + + (closure_data, output) +} + #[inline(always)] fn canonicalize_when_branch<'a>( env: &mut Env<'a>, @@ -1027,15 +1144,12 @@ fn canonicalize_when_branch<'a>( ) -> (WhenBranch, References) { let mut patterns = Vec::with_capacity(branch.patterns.len()); - let original_scope = scope; - let mut scope = original_scope.clone(); - // TODO report symbols not bound in all patterns for loc_pattern in branch.patterns.iter() { let can_pattern = canonicalize_pattern( env, var_store, - &mut scope, + scope, output, WhenBranch, &loc_pattern.value, @@ -1048,7 +1162,7 @@ fn canonicalize_when_branch<'a>( let (value, mut branch_output) = canonicalize_expr( env, var_store, - &mut scope, + scope, branch.value.region, &branch.value.value, ); @@ -1057,35 +1171,30 @@ fn canonicalize_when_branch<'a>( None => None, Some(loc_expr) => { let (can_guard, guard_branch_output) = - canonicalize_expr(env, var_store, &mut scope, loc_expr.region, &loc_expr.value); + canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); branch_output.union(guard_branch_output); Some(can_guard) } }; - // Now that we've collected all the references for this branch, check to see if - // any of the new idents it defined were unused. If any were, report it. - for (symbol, region) in scope.symbols() { - let symbol = *symbol; - - if !output.references.has_type_or_value_lookup(symbol) - && !branch_output.references.has_type_or_value_lookup(symbol) - && !original_scope.contains_symbol(symbol) - && !scope.abilities_store.is_specialization_name(symbol) - { - env.problem(Problem::UnusedDef(symbol, *region)); - } - } - let references = branch_output.references.clone(); output.union(branch_output); + // Now that we've collected all the references for this branch, check to see if + // any of the new idents it defined were unused. If any were, report it. + for (symbol, region) in BindingsFromPattern::new_many(patterns.iter()) { + if !output.references.has_value_lookup(symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + ( WhenBranch { patterns, value, guard, + redundant: RedundantMark::new(var_store), }, references, ) @@ -1218,7 +1327,11 @@ fn canonicalize_var_lookup( Ok(symbol) => { output.references.insert_value_lookup(symbol); - Var(symbol) + if scope.abilities_store.is_ability_member_name(symbol) { + AbilityMember(symbol, scope.abilities_store.fresh_specialization_id()) + } else { + Var(symbol) + } } Err(problem) => { env.problem(Problem::RuntimeError(problem.clone())); @@ -1229,11 +1342,15 @@ fn canonicalize_var_lookup( } else { // Since module_name was nonempty, this is a qualified var. // Look it up in the env! - match env.qualified_lookup(module_name, ident, region) { + match env.qualified_lookup(scope, module_name, ident, region) { Ok(symbol) => { output.references.insert_value_lookup(symbol); - Var(symbol) + if scope.abilities_store.is_ability_member_name(symbol) { + AbilityMember(symbol, scope.abilities_store.fresh_specialization_id()) + } else { + Var(symbol) + } } Err(problem) => { // Either the module wasn't imported, or @@ -1267,6 +1384,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> | other @ Accessor { .. } | other @ Update { .. } | other @ Var(_) + | other @ AbilityMember(..) | other @ RunLowLevel { .. } | other @ ForeignCall { .. } => other, @@ -1297,6 +1415,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> region, loc_cond, branches, + branches_cond_var, + exhaustive, } => { let loc_cond = Box::new(Loc { region: loc_cond.region, @@ -1321,6 +1441,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> patterns: branch.patterns, value, guard, + redundant: RedundantMark::new(var_store), }; new_branches.push(new_branch); @@ -1332,6 +1453,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> region, loc_cond, branches: new_branches, + branches_cond_var, + exhaustive, } } If { @@ -1383,7 +1506,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> Expect(Box::new(loc_condition), Box::new(loc_expr)) } - LetRec(defs, loc_expr, var) => { + LetRec(defs, loc_expr) => { let mut new_defs = Vec::with_capacity(defs.len()); for def in defs { @@ -1404,10 +1527,10 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> value: inline_calls(var_store, scope, loc_expr.value), }; - LetRec(new_defs, Box::new(loc_expr), var) + LetRec(new_defs, Box::new(loc_expr)) } - LetNonRec(def, loc_expr, var) => { + LetNonRec(def, loc_expr) => { let def = Def { loc_pattern: def.loc_pattern, loc_expr: Loc { @@ -1424,7 +1547,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> value: inline_calls(var_store, scope, loc_expr.value), }; - LetNonRec(Box::new(def), Box::new(loc_expr), var) + LetNonRec(Box::new(def), Box::new(loc_expr)) } Closure(ClosureData { @@ -1561,7 +1684,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> // Wrap the body in one LetNonRec for each argument, // such that at the end we have all the arguments in // scope with the values the caller provided. - for ((_param_var, loc_pattern), (expr_var, loc_expr)) in + for ((_param_var, _exhaustive_mark, loc_pattern), (expr_var, loc_expr)) in params.iter().cloned().zip(args.into_iter()).rev() { // TODO get the correct vars into here. @@ -1578,11 +1701,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> loc_answer = Loc { region: Region::zero(), - value: LetNonRec( - Box::new(def), - Box::new(loc_answer), - var_store.fresh(), - ), + value: LetNonRec(Box::new(def), Box::new(loc_answer)), }; } diff --git a/compiler/can/src/lib.rs b/compiler/can/src/lib.rs index 4e90bf4d4d..b140f93267 100644 --- a/compiler/can/src/lib.rs +++ b/compiler/can/src/lib.rs @@ -8,6 +8,7 @@ pub mod constraint; pub mod def; pub mod effect_module; pub mod env; +pub mod exhaustive; pub mod expected; pub mod expr; pub mod module; @@ -15,6 +16,6 @@ pub mod num; pub mod operator; pub mod pattern; pub mod procedure; -mod reference_matrix; pub mod scope; pub mod string; +pub mod traverse; diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index b34aba4fc5..2d0fda6c9b 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -1,4 +1,5 @@ use crate::abilities::AbilitiesStore; +use crate::annotation::canonicalize_annotation; use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; use crate::effect_module::HostedGeneratedFunctions; use crate::env::Env; @@ -8,16 +9,16 @@ use crate::pattern::Pattern; use crate::scope::Scope; use bumpalo::Bump; use roc_collections::{MutMap, SendMap, VecSet}; +use roc_module::ident::Ident; use roc_module::ident::Lowercase; -use roc_module::ident::{Ident, TagName}; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; -use roc_parse::ast; +use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol}; +use roc_parse::ast::{self, TypeAnnotation}; use roc_parse::header::HeaderFor; use roc_parse::pattern::PatternType; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, AliasKind, Type}; +use roc_types::types::{Alias, AliasKind, AliasVar, Type}; #[derive(Debug)] pub struct Module { @@ -47,9 +48,9 @@ pub struct ModuleOutput { pub exposed_imports: MutMap, pub lookups: Vec<(Symbol, Variable, Region)>, pub problems: Vec, - pub ident_ids: IdentIds, pub referenced_values: VecSet, pub referenced_types: VecSet, + pub symbols_from_requires: Vec<(Loc, Loc)>, pub scope: Scope, } @@ -107,32 +108,20 @@ impl GeneratedInfo { env.problem(Problem::UnknownGeneratesWith(unknown)); } - let effect_symbol = scope - .introduce( - name.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - Region::zero(), - ) - .unwrap(); - - let effect_tag_name = TagName::Private(effect_symbol); + let effect_symbol = scope.introduce(name.into(), Region::zero()).unwrap(); { let a_var = var_store.fresh(); - let actual = crate::effect_module::build_effect_actual( - effect_tag_name, - Type::Variable(a_var), - var_store, - ); + let actual = + crate::effect_module::build_effect_actual(Type::Variable(a_var), var_store); scope.add_alias( effect_symbol, Region::zero(), - vec![Loc::at_zero(("a".into(), a_var))], + vec![Loc::at_zero(AliasVar::unbound("a".into(), a_var))], actual, - AliasKind::Structural, + AliasKind::Opaque, ); } @@ -169,21 +158,22 @@ fn has_no_implementation(expr: &Expr) -> bool { // TODO trim these down #[allow(clippy::too_many_arguments)] pub fn canonicalize_module_defs<'a>( - arena: &Bump, + arena: &'a Bump, loc_defs: &'a [Loc>], header_for: &roc_parse::header::HeaderFor, home: ModuleId, - module_ids: &ModuleIds, + module_ids: &'a ModuleIds, exposed_ident_ids: IdentIds, - dep_idents: &'a MutMap, + dep_idents: &'a IdentIdsByModule, aliases: MutMap, exposed_imports: MutMap, exposed_symbols: &VecSet, + symbols_from_requires: &[(Loc, Loc>)], var_store: &mut VarStore, -) -> Result { +) -> ModuleOutput { let mut can_exposed_imports = MutMap::default(); - let mut scope = Scope::new(home, var_store); - let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids); + let mut scope = Scope::new(home, exposed_ident_ids); + let mut env = Env::new(home, dep_idents, module_ids); let num_deps = dep_idents.len(); for (name, alias) in aliases.into_iter() { @@ -290,11 +280,11 @@ pub fn canonicalize_module_defs<'a>( } } - let (defs, mut scope, output, symbols_introduced) = canonicalize_defs( + let (defs, output, symbols_introduced) = canonicalize_defs( &mut env, Output::default(), var_store, - &scope, + &mut scope, &desugared, PatternType::TopLevelDef, ); @@ -361,208 +351,230 @@ pub fn canonicalize_module_defs<'a>( ..Default::default() }; - match sort_can_defs(&mut env, defs, new_output) { - (Ok(mut declarations), output) => { - use crate::def::Declaration::*; + let (mut declarations, mut output) = sort_can_defs(&mut env, defs, new_output); - if let GeneratedInfo::Hosted { - effect_symbol, - generated_functions, - } = generated_info - { - let mut exposed_symbols = VecSet::default(); + let symbols_from_requires = symbols_from_requires + .iter() + .map(|(symbol, loc_ann)| { + let ann = canonicalize_annotation( + &mut env, + &mut scope, + &loc_ann.value, + loc_ann.region, + var_store, + &output.abilities_in_scope, + ); - // NOTE this currently builds all functions, not just the ones that the user requested - crate::effect_module::build_effect_builtins( - &mut env, - &mut scope, - effect_symbol, - var_store, - &mut exposed_symbols, - &mut declarations, - generated_functions, - ); + ann.add_to( + &mut output.aliases, + &mut output.references, + &mut output.introduced_variables, + ); + + ( + *symbol, + Loc { + value: ann.typ, + region: loc_ann.region, + }, + ) + }) + .collect(); + + if let GeneratedInfo::Hosted { + effect_symbol, + generated_functions, + } = generated_info + { + let mut exposed_symbols = VecSet::default(); + + // NOTE this currently builds all functions, not just the ones that the user requested + crate::effect_module::build_effect_builtins( + &mut scope, + effect_symbol, + var_store, + &mut exposed_symbols, + &mut declarations, + generated_functions, + ); + } + + use crate::def::Declaration::*; + for decl in declarations.iter_mut() { + match decl { + Declare(def) => { + for (symbol, _) in def.pattern_vars.iter() { + if exposed_but_not_defined.contains(symbol) { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_but_not_defined.remove(symbol); + } + } + + // Temporary hack: we don't know exactly what symbols are hosted symbols, + // and which are meant to be normal definitions without a body. So for now + // we just assume they are hosted functions (meant to be provided by the platform) + if has_no_implementation(&def.loc_expr.value) { + match generated_info { + GeneratedInfo::Builtin => { + let symbol = def.pattern_vars.iter().next().unwrap().0; + match crate::builtins::builtin_defs_map(*symbol, var_store) { + None => { + panic!("A builtin module contains a signature without implementation for {:?}", symbol) + } + Some(mut replacement_def) => { + replacement_def.annotation = def.annotation.take(); + *def = replacement_def; + } + } + } + GeneratedInfo::Hosted { effect_symbol, .. } => { + let symbol = def.pattern_vars.iter().next().unwrap().0; + let ident_id = symbol.ident_id(); + let ident = scope + .locals + .ident_ids + .get_name(ident_id) + .unwrap() + .to_string(); + let def_annotation = def.annotation.clone().unwrap(); + let annotation = crate::annotation::Annotation { + typ: def_annotation.signature, + introduced_variables: def_annotation.introduced_variables, + references: Default::default(), + aliases: Default::default(), + }; + + let hosted_def = crate::effect_module::build_host_exposed_def( + &mut scope, + *symbol, + &ident, + effect_symbol, + var_store, + annotation, + ); + + *def = hosted_def; + } + _ => (), + } + } } - - for decl in declarations.iter_mut() { - match decl { - Declare(def) => { - for (symbol, _) in def.pattern_vars.iter() { - if exposed_but_not_defined.contains(symbol) { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_but_not_defined.remove(symbol); - } + DeclareRec(defs) => { + for def in defs { + for (symbol, _) in def.pattern_vars.iter() { + if exposed_but_not_defined.contains(symbol) { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_but_not_defined.remove(symbol); } - - // Temporary hack: we don't know exactly what symbols are hosted symbols, - // and which are meant to be normal definitions without a body. So for now - // we just assume they are hosted functions (meant to be provided by the platform) - if has_no_implementation(&def.loc_expr.value) { - match generated_info { - GeneratedInfo::Builtin => { - let symbol = def.pattern_vars.iter().next().unwrap().0; - match crate::builtins::builtin_defs_map(*symbol, var_store) { - None => { - panic!("A builtin module contains a signature without implementation for {:?}", symbol) - } - Some(mut replacement_def) => { - replacement_def.annotation = def.annotation.take(); - *def = replacement_def; - } - } - } - GeneratedInfo::Hosted { effect_symbol, .. } => { - let symbol = def.pattern_vars.iter().next().unwrap().0; - let ident_id = symbol.ident_id(); - let ident = - env.ident_ids.get_name(ident_id).unwrap().to_string(); - let def_annotation = def.annotation.clone().unwrap(); - let annotation = crate::annotation::Annotation { - typ: def_annotation.signature, - introduced_variables: def_annotation.introduced_variables, - references: Default::default(), - aliases: Default::default(), - }; - - let hosted_def = crate::effect_module::build_host_exposed_def( - &mut env, - &mut scope, - *symbol, - &ident, - TagName::Private(effect_symbol), - var_store, - annotation, - ); - - *def = hosted_def; - } - _ => (), - } - } - } - DeclareRec(defs) => { - for def in defs { - for (symbol, _) in def.pattern_vars.iter() { - if exposed_but_not_defined.contains(symbol) { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_but_not_defined.remove(symbol); - } - } - } - } - - InvalidCycle(entries) => { - env.problems.push(Problem::BadRecursion(entries.to_vec())); - } - Builtin(def) => { - // Builtins cannot be exposed in module declarations. - // This should never happen! - debug_assert!(def - .pattern_vars - .iter() - .all(|(symbol, _)| !exposed_but_not_defined.contains(symbol))); } } } - let mut aliases = MutMap::default(); - - if let GeneratedInfo::Hosted { effect_symbol, .. } = generated_info { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_but_not_defined.remove(&effect_symbol); - - let hosted_alias = scope.lookup_alias(effect_symbol).unwrap().clone(); - aliases.insert(effect_symbol, hosted_alias); + InvalidCycle(entries) => { + env.problems.push(Problem::BadRecursion(entries.to_vec())); } - - for (symbol, alias) in output.aliases { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_but_not_defined.remove(&symbol); - - aliases.insert(symbol, alias); + Builtin(def) => { + // Builtins cannot be exposed in module declarations. + // This should never happen! + debug_assert!(def + .pattern_vars + .iter() + .all(|(symbol, _)| !exposed_but_not_defined.contains(symbol))); } - - for member in scope.abilities_store.root_ability_members().keys() { - exposed_but_not_defined.remove(member); - } - - // By this point, all exposed symbols should have been removed from - // exposed_symbols and added to exposed_vars_by_symbol. If any were - // not, that means they were declared as exposed but there was - // no actual declaration with that name! - for symbol in exposed_but_not_defined { - env.problem(Problem::ExposedButNotDefined(symbol)); - - // In case this exposed value is referenced by other modules, - // create a decl for it whose implementation is a runtime error. - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(symbol, var_store.fresh()); - - let runtime_error = RuntimeError::ExposedButNotDefined(symbol); - let def = Def { - loc_pattern: Loc::at(Region::zero(), Pattern::Identifier(symbol)), - loc_expr: Loc::at(Region::zero(), Expr::RuntimeError(runtime_error)), - expr_var: var_store.fresh(), - pattern_vars, - annotation: None, - }; - - declarations.push(Declaration::Declare(def)); - } - - // Incorporate any remaining output.lookups entries into references. - referenced_values.extend(output.references.value_lookups().copied()); - referenced_types.extend(output.references.type_lookups().copied()); - - // Incorporate any remaining output.calls entries into references. - referenced_values.extend(output.references.calls().copied()); - - // Gather up all the symbols that were referenced from other modules. - referenced_values.extend(env.qualified_value_lookups.iter().copied()); - referenced_types.extend(env.qualified_type_lookups.iter().copied()); - - for declaration in declarations.iter_mut() { - match declaration { - Declare(def) => fix_values_captured_in_closure_def(def, &mut VecSet::default()), - DeclareRec(defs) => { - fix_values_captured_in_closure_defs(defs, &mut VecSet::default()) - } - InvalidCycle(_) | Builtin(_) => {} - } - } - - let output = ModuleOutput { - scope, - aliases, - rigid_variables, - declarations, - referenced_values, - referenced_types, - exposed_imports: can_exposed_imports, - problems: env.problems, - lookups, - ident_ids: env.ident_ids, - }; - - Ok(output) } - (Err(runtime_error), _) => Err(runtime_error), + } + + let mut aliases = MutMap::default(); + + if let GeneratedInfo::Hosted { effect_symbol, .. } = generated_info { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_but_not_defined.remove(&effect_symbol); + + let hosted_alias = scope.lookup_alias(effect_symbol).unwrap().clone(); + aliases.insert(effect_symbol, hosted_alias); + } + + for (symbol, alias) in output.aliases { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_but_not_defined.remove(&symbol); + + aliases.insert(symbol, alias); + } + + for member in scope.abilities_store.root_ability_members().keys() { + exposed_but_not_defined.remove(member); + } + + // By this point, all exposed symbols should have been removed from + // exposed_symbols and added to exposed_vars_by_symbol. If any were + // not, that means they were declared as exposed but there was + // no actual declaration with that name! + for symbol in exposed_but_not_defined { + env.problem(Problem::ExposedButNotDefined(symbol)); + + // In case this exposed value is referenced by other modules, + // create a decl for it whose implementation is a runtime error. + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(symbol, var_store.fresh()); + + let runtime_error = RuntimeError::ExposedButNotDefined(symbol); + let def = Def { + loc_pattern: Loc::at(Region::zero(), Pattern::Identifier(symbol)), + loc_expr: Loc::at(Region::zero(), Expr::RuntimeError(runtime_error)), + expr_var: var_store.fresh(), + pattern_vars, + annotation: None, + }; + + declarations.push(Declaration::Declare(def)); + } + + // Incorporate any remaining output.lookups entries into references. + referenced_values.extend(output.references.value_lookups().copied()); + referenced_types.extend(output.references.type_lookups().copied()); + + // Incorporate any remaining output.calls entries into references. + referenced_values.extend(output.references.calls().copied()); + + // Gather up all the symbols that were referenced from other modules. + referenced_values.extend(env.qualified_value_lookups.iter().copied()); + referenced_types.extend(env.qualified_type_lookups.iter().copied()); + + for declaration in declarations.iter_mut() { + match declaration { + Declare(def) => fix_values_captured_in_closure_def(def, &mut VecSet::default()), + DeclareRec(defs) => fix_values_captured_in_closure_defs(defs, &mut VecSet::default()), + InvalidCycle(_) | Builtin(_) => {} + } + } + + ModuleOutput { + scope, + aliases, + rigid_variables, + declarations, + referenced_values, + referenced_types, + exposed_imports: can_exposed_imports, + problems: env.problems, + symbols_from_requires, + lookups, } } @@ -577,7 +589,7 @@ fn fix_values_captured_in_closure_def( } fn fix_values_captured_in_closure_defs( - defs: &mut Vec, + defs: &mut [crate::def::Def], no_capture_symbols: &mut VecSet, ) { // recursive defs cannot capture each other @@ -648,12 +660,12 @@ fn fix_values_captured_in_closure_expr( use crate::expr::Expr::*; match expr { - LetNonRec(def, loc_expr, _) => { + LetNonRec(def, loc_expr) => { // LetNonRec(Box, Box>, Variable, Aliases), fix_values_captured_in_closure_def(def, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols); } - LetRec(defs, loc_expr, _) => { + LetRec(defs, loc_expr) => { // LetRec(Vec, Box>, Variable, Aliases), fix_values_captured_in_closure_defs(defs, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols); @@ -679,7 +691,7 @@ fn fix_values_captured_in_closure_expr( } // patterns can contain default expressions, so much go over them too! - for (_, loc_pat) in arguments.iter_mut() { + for (_, _, loc_pat) in arguments.iter_mut() { fix_values_captured_in_closure_pattern(&mut loc_pat.value, no_capture_symbols); } @@ -692,6 +704,7 @@ fn fix_values_captured_in_closure_expr( | Str(_) | SingleQuote(_) | Var(_) + | AbilityMember(..) | EmptyRecord | RuntimeError(_) | ZeroArgumentTag { .. } diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 0386408b47..271e33ff0a 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -150,8 +150,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc | MalformedIdent(_, _) | MalformedClosure | PrecedenceConflict { .. } - | GlobalTag(_) - | PrivateTag(_) + | Tag(_) | OpaqueRef(_) => loc_expr, Access(sub_expr, paths) => { @@ -425,7 +424,6 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { Slash => (ModuleName::NUM, "div"), DoubleSlash => (ModuleName::NUM, "divTrunc"), Percent => (ModuleName::NUM, "rem"), - DoublePercent => (ModuleName::NUM, "mod"), Plus => (ModuleName::NUM, "add"), Minus => (ModuleName::NUM, "sub"), Equals => (ModuleName::BOOL, "isEq"), diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 9804707b2c..e06f695b29 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -13,7 +13,7 @@ use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{LambdaSet, Type}; +use roc_types::types::{LambdaSet, OptAbleVar, PatternCategory, Type}; /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. @@ -47,7 +47,7 @@ pub enum Pattern { // for the expression from the opaque definition. `type_arguments` is something like // [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]". specialized_def_type: Box, - type_arguments: Vec<(Lowercase, Type)>, + type_arguments: Vec, lambda_set_variables: Vec, }, RecordDestructure { @@ -82,6 +82,84 @@ pub enum Pattern { MalformedPattern(MalformedPatternProblem, Region), } +impl Pattern { + pub fn opt_var(&self) -> Option { + use Pattern::*; + match self { + Identifier(_) => None, + + AppliedTag { whole_var, .. } => Some(*whole_var), + UnwrappedOpaque { whole_var, .. } => Some(*whole_var), + RecordDestructure { whole_var, .. } => Some(*whole_var), + NumLiteral(var, ..) => Some(*var), + IntLiteral(var, ..) => Some(*var), + FloatLiteral(var, ..) => Some(*var), + StrLiteral(_) => None, + SingleQuote(_) => None, + Underscore => None, + + AbilityMemberSpecialization { .. } => None, + + Shadowed(..) | OpaqueNotInScope(..) | UnsupportedPattern(..) | MalformedPattern(..) => { + None + } + } + } + + /// Is this pattern sure to cover all instances of a type T, assuming it typechecks against T? + pub fn surely_exhaustive(&self) -> bool { + use Pattern::*; + match self { + Identifier(..) + | Underscore + | Shadowed(..) + | OpaqueNotInScope(..) + | UnsupportedPattern(..) + | MalformedPattern(..) + | AbilityMemberSpecialization { .. } => true, + RecordDestructure { destructs, .. } => destructs.is_empty(), + AppliedTag { .. } + | NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) + | StrLiteral(..) + | SingleQuote(..) => false, + UnwrappedOpaque { argument, .. } => { + // Opaques can only match against one constructor (the opaque symbol), so this is + // surely exhaustive against T if the inner pattern is surely exhaustive against + // its type U. + argument.1.value.surely_exhaustive() + } + } + } + + pub fn category(&self) -> PatternCategory { + use Pattern::*; + use PatternCategory as C; + + match self { + Identifier(_) => C::PatternDefault, + + AppliedTag { tag_name, .. } => C::Ctor(tag_name.clone()), + UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque), + RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord, + RecordDestructure { .. } => C::Record, + NumLiteral(..) => C::Num, + IntLiteral(..) => C::Int, + FloatLiteral(..) => C::Float, + StrLiteral(_) => C::Str, + SingleQuote(_) => C::Character, + Underscore => C::PatternDefault, + + AbilityMemberSpecialization { .. } => C::PatternDefault, + + Shadowed(..) | OpaqueNotInScope(..) | UnsupportedPattern(..) | MalformedPattern(..) => { + C::PatternDefault + } + } + } +} + #[derive(Clone, Debug)] pub struct RecordDestruct { pub var: Variable, @@ -165,37 +243,34 @@ pub fn canonicalize_def_header_pattern<'a>( match pattern { // Identifiers that shadow ability members may appear (and may only appear) at the header of a def. - Identifier(name) => match scope.introduce_or_shadow_ability_member( - (*name).into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok((symbol, shadowing_ability_member)) => { - output.references.insert_bound(symbol); - let can_pattern = match shadowing_ability_member { - // A fresh identifier. - None => Pattern::Identifier(symbol), - // Likely a specialization of an ability. - Some(ability_member_name) => Pattern::AbilityMemberSpecialization { - ident: symbol, - specializes: ability_member_name, - }, - }; - Loc::at(region, can_pattern) - } - Err((original_region, shadow, new_symbol)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - kind: ShadowKind::Variable, - })); - output.references.insert_bound(new_symbol); + Identifier(name) => { + match scope.introduce_or_shadow_ability_member((*name).into(), region) { + Ok((symbol, shadowing_ability_member)) => { + output.references.insert_bound(symbol); + let can_pattern = match shadowing_ability_member { + // A fresh identifier. + None => Pattern::Identifier(symbol), + // Likely a specialization of an ability. + Some(ability_member_name) => Pattern::AbilityMemberSpecialization { + ident: symbol, + specializes: ability_member_name, + }, + }; + Loc::at(region, can_pattern) + } + Err((original_region, shadow, new_symbol)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + kind: ShadowKind::Variable, + })); + output.references.insert_bound(new_symbol); - let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol); - Loc::at(region, can_pattern) + let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol); + Loc::at(region, can_pattern) + } } - }, + } _ => canonicalize_pattern(env, var_store, scope, output, pattern_type, pattern, region), } } @@ -213,12 +288,7 @@ pub fn canonicalize_pattern<'a>( use PatternType::*; let can_pattern = match pattern { - Identifier(name) => match scope.introduce( - (*name).into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + Identifier(name) => match scope.introduce_str(name, region) { Ok(symbol) => { output.references.insert_bound(symbol); @@ -235,23 +305,12 @@ pub fn canonicalize_pattern<'a>( Pattern::Shadowed(original_region, shadow, new_symbol) } }, - GlobalTag(name) => { + Tag(name) => { // Canonicalize the tag's name. Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), - tag_name: TagName::Global((*name).into()), - arguments: vec![], - } - } - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - - // Canonicalize the tag's name. - Pattern::AppliedTag { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name: TagName::Private(Symbol::new(env.home, ident_id)), + tag_name: TagName::Tag((*name).into()), arguments: vec![], } } @@ -280,19 +339,8 @@ pub fn canonicalize_pattern<'a>( } match tag.value { - GlobalTag(name) => { - let tag_name = TagName::Global(name.into()); - Pattern::AppliedTag { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - tag_name, - arguments: can_patterns, - } - } - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&name.into()); - let tag_name = TagName::Private(Symbol::new(env.home, ident_id)); - + Tag(name) => { + let tag_name = TagName::Tag(name.into()); Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), @@ -460,12 +508,7 @@ pub fn canonicalize_pattern<'a>( for loc_pattern in patterns.iter() { match loc_pattern.value { Identifier(label) => { - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + match scope.introduce(label.into(), region) { Ok(symbol) => { output.references.insert_bound(symbol); @@ -498,7 +541,8 @@ pub fn canonicalize_pattern<'a>( RequiredField(label, loc_guard) => { // a guard does not introduce the label into scope! - let symbol = scope.ignore(label.into(), &mut env.ident_ids); + let symbol = + scope.scopeless_symbol(&Ident::from(label), loc_pattern.region); let can_guard = canonicalize_pattern( env, var_store, @@ -521,12 +565,7 @@ pub fn canonicalize_pattern<'a>( } OptionalField(label, loc_default) => { // an optional DOES introduce the label into scope! - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { + match scope.introduce(label.into(), region) { Ok(symbol) => { let (can_default, expr_output) = canonicalize_expr( env, @@ -641,69 +680,122 @@ fn malformed_pattern(env: &mut Env, problem: MalformedPatternProblem, region: Re Pattern::MalformedPattern(problem, region) } -pub fn bindings_from_patterns<'a, I>(loc_patterns: I) -> Vec<(Symbol, Region)> -where - I: Iterator>, -{ - let mut answer = Vec::new(); - - for loc_pattern in loc_patterns { - add_bindings_from_patterns(&loc_pattern.region, &loc_pattern.value, &mut answer); - } - - answer +/// An iterator over the bindings made by a pattern. +/// +/// We attempt to make no allocations when we can. +pub enum BindingsFromPattern<'a> { + Empty, + One(&'a Loc), + Many(Vec>), } -/// helper function for idents_from_patterns -fn add_bindings_from_patterns( - region: &Region, - pattern: &Pattern, - answer: &mut Vec<(Symbol, Region)>, -) { - use Pattern::*; +pub enum BindingsFromPatternWork<'a> { + Pattern(&'a Loc), + Destruct(&'a Loc), +} - match pattern { - Identifier(symbol) - | Shadowed(_, _, symbol) - | AbilityMemberSpecialization { - ident: symbol, - specializes: _, - } => { - answer.push((*symbol, *region)); +impl<'a> BindingsFromPattern<'a> { + pub fn new(initial: &'a Loc) -> Self { + Self::One(initial) + } + + pub fn new_many(mut it: I) -> Self + where + I: Iterator>, + { + if let (1, Some(1)) = it.size_hint() { + Self::new(it.next().unwrap()) + } else { + Self::Many(it.map(BindingsFromPatternWork::Pattern).collect()) } - AppliedTag { - arguments: loc_args, - .. - } => { - for (_, loc_arg) in loc_args { - add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer); + } + + fn next_many(stack: &mut Vec>) -> Option<(Symbol, Region)> { + use Pattern::*; + + while let Some(work) = stack.pop() { + match work { + BindingsFromPatternWork::Pattern(loc_pattern) => { + use BindingsFromPatternWork::*; + + match &loc_pattern.value { + Identifier(symbol) + | AbilityMemberSpecialization { + ident: symbol, + specializes: _, + } => { + return Some((*symbol, loc_pattern.region)); + } + AppliedTag { + arguments: loc_args, + .. + } => { + let it = loc_args.iter().rev().map(|(_, p)| Pattern(p)); + stack.extend(it); + } + UnwrappedOpaque { argument, .. } => { + let (_, loc_arg) = &**argument; + stack.push(Pattern(loc_arg)); + } + RecordDestructure { destructs, .. } => { + let it = destructs.iter().rev().map(Destruct); + stack.extend(it); + } + NumLiteral(..) + | IntLiteral(..) + | FloatLiteral(..) + | StrLiteral(_) + | SingleQuote(_) + | Underscore + | Shadowed(_, _, _) + | MalformedPattern(_, _) + | UnsupportedPattern(_) + | OpaqueNotInScope(..) => (), + } + } + BindingsFromPatternWork::Destruct(loc_destruct) => { + match &loc_destruct.value.typ { + DestructType::Required | DestructType::Optional(_, _) => { + return Some((loc_destruct.value.symbol, loc_destruct.region)); + } + DestructType::Guard(_, inner) => { + // a guard does not introduce the symbol + stack.push(BindingsFromPatternWork::Pattern(inner)) + } + } + } } } - UnwrappedOpaque { - argument, opaque, .. - } => { - let (_, loc_arg) = &**argument; - add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer); - answer.push((*opaque, *region)); + + None + } +} + +impl<'a> Iterator for BindingsFromPattern<'a> { + type Item = (Symbol, Region); + + fn next(&mut self) -> Option { + use Pattern::*; + + match self { + BindingsFromPattern::Empty => None, + BindingsFromPattern::One(loc_pattern) => match &loc_pattern.value { + Identifier(symbol) + | AbilityMemberSpecialization { + ident: symbol, + specializes: _, + } => { + let region = loc_pattern.region; + *self = Self::Empty; + Some((*symbol, region)) + } + _ => { + *self = Self::Many(vec![BindingsFromPatternWork::Pattern(loc_pattern)]); + self.next() + } + }, + BindingsFromPattern::Many(stack) => Self::next_many(stack), } - RecordDestructure { destructs, .. } => { - for Loc { - region, - value: RecordDestruct { symbol, .. }, - } in destructs - { - answer.push((*symbol, *region)); - } - } - NumLiteral(..) - | IntLiteral(..) - | FloatLiteral(..) - | StrLiteral(_) - | SingleQuote(_) - | Underscore - | MalformedPattern(_, _) - | UnsupportedPattern(_) - | OpaqueNotInScope(..) => (), } } diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index 76a08d276a..52867942b8 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -1,25 +1,18 @@ -use roc_collections::all::{MutSet, SendMap}; -use roc_module::ident::{Ident, Lowercase}; -use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_collections::VecMap; +use roc_module::ident::Ident; +use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol}; use roc_problem::can::RuntimeError; use roc_region::all::{Loc, Region}; -use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, AliasKind, Type}; +use roc_types::types::{Alias, AliasKind, AliasVar, Type}; use crate::abilities::AbilitiesStore; -#[derive(Clone, Debug, PartialEq)] +use bitvec::vec::BitVec; + +#[derive(Clone, Debug)] pub struct Scope { - /// All the identifiers in scope, mapped to were they were defined and - /// the Symbol they resolve to. - idents: SendMap, - - /// A cache of all the symbols in scope. This makes lookups much - /// faster when checking for unused defs and unused arguments. - symbols: SendMap, - /// The type aliases currently in scope - pub aliases: SendMap, + pub aliases: VecMap, /// The abilities currently in scope, and their implementors. pub abilities_store: AbilitiesStore, @@ -27,97 +20,47 @@ pub struct Scope { /// The current module being processed. This will be used to turn /// unqualified idents into Symbols. home: ModuleId, -} -fn add_aliases(var_store: &mut VarStore) -> SendMap { - use roc_types::solved_types::{BuiltinAlias, FreeVars}; + /// The first `exposed_ident_count` identifiers are exposed + exposed_ident_count: usize, - let solved_aliases = roc_types::builtin_aliases::aliases(); - let mut aliases = SendMap::default(); + /// Identifiers that are imported (and introduced in the header) + imports: Vec<(Ident, Symbol, Region)>, - for (symbol, builtin_alias) in solved_aliases { - let BuiltinAlias { region, vars, typ } = builtin_alias; - - let mut free_vars = FreeVars::default(); - let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); - - let mut variables = Vec::new(); - // make sure to sort these variables to make them line up with the type arguments - let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); - type_variables.sort(); - for (loc_name, (_, var)) in vars.iter().zip(type_variables) { - variables.push(Loc::at(loc_name.region, (loc_name.value.clone(), var))); - } - - let alias = Alias { - region, - typ, - lambda_set_variables: Vec::new(), - recursion_variables: MutSet::default(), - type_variables: variables, - // TODO(opaques): replace when opaques are included in the stdlib - kind: AliasKind::Structural, - }; - - aliases.insert(symbol, alias); - } - - aliases + /// Identifiers that are in scope, and defined in the current module + pub locals: ScopedIdentIds, } impl Scope { - pub fn new(home: ModuleId, _var_store: &mut VarStore) -> Scope { + pub fn new(home: ModuleId, initial_ident_ids: IdentIds) -> Scope { + let imports = Symbol::default_in_scope() + .into_iter() + .map(|(a, (b, c))| (a, b, c)) + .collect(); + Scope { home, - idents: Symbol::default_in_scope(), - symbols: SendMap::default(), - aliases: SendMap::default(), + exposed_ident_count: initial_ident_ids.len(), + locals: ScopedIdentIds::from_ident_ids(home, initial_ident_ids), + aliases: VecMap::default(), // TODO(abilities): default abilities in scope abilities_store: AbilitiesStore::default(), + imports, } } - pub fn new_with_aliases(home: ModuleId, var_store: &mut VarStore) -> Scope { - Scope { - home, - idents: Symbol::default_in_scope(), - symbols: SendMap::default(), - aliases: add_aliases(var_store), - // TODO(abilities): default abilities in scope - abilities_store: AbilitiesStore::default(), - } - } - - pub fn idents(&self) -> impl Iterator { - self.idents.iter() - } - - pub fn symbols(&self) -> impl Iterator { - self.symbols.iter() - } - - pub fn contains_ident(&self, ident: &Ident) -> bool { - self.idents.contains_key(ident) - } - - pub fn contains_symbol(&self, symbol: Symbol) -> bool { - self.symbols.contains_key(&symbol) - } - - pub fn num_idents(&self) -> usize { - self.idents.len() - } - pub fn lookup(&self, ident: &Ident, region: Region) -> Result { - match self.idents.get(ident) { - Some((symbol, _)) => Ok(*symbol), - None => { + use ContainsIdent::*; + + match self.scope_contains_ident(ident.as_str()) { + InScope(symbol, _) => Ok(symbol), + NotInScope(_) | NotPresent => { let error = RuntimeError::LookupNotInScope( Loc { region, value: ident.clone(), }, - self.idents.keys().map(|v| v.as_ref().into()).collect(), + self.idents_in_scope().map(|v| v.as_ref().into()).collect(), ); Err(error) @@ -125,52 +68,72 @@ impl Scope { } } - pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> { - self.aliases.get(&symbol) + fn idents_in_scope(&self) -> impl Iterator + '_ { + let it1 = self.locals.idents_in_scope(); + let it2 = self.imports.iter().map(|t| t.0.clone()); + + it2.chain(it1) } /// Check if there is an opaque type alias referenced by `opaque_ref` referenced in the - /// current scope. E.g. `$Age` must reference an opaque `Age` declared in this module, not any + /// current scope. E.g. `@Age` must reference an opaque `Age` declared in this module, not any /// other! - // TODO(opaques): $->@ in the above comment pub fn lookup_opaque_ref( &self, opaque_ref: &str, lookup_region: Region, ) -> Result<(Symbol, &Alias), RuntimeError> { - debug_assert!(opaque_ref.starts_with('$')); - let opaque = opaque_ref[1..].into(); + debug_assert!(opaque_ref.starts_with('@')); + let opaque_str = &opaque_ref[1..]; + let opaque = opaque_str.into(); - match self.idents.get(&opaque) { - // TODO: is it worth caching any of these results? - Some((symbol, decl_region)) => { - if symbol.module_id() != self.home { - // The reference is to an opaque type declared in another module - this is - // illegal, as opaque types can only be wrapped/unwrapped in the scope they're - // declared. - return Err(RuntimeError::OpaqueOutsideScope { + match self.locals.has_in_scope(&opaque) { + Some((symbol, _)) => match self.lookup_opaque_alias(symbol) { + Ok(alias) => Ok((symbol, alias)), + Err(opt_alias_def_region) => { + Err(self.opaque_not_defined_error(opaque, lookup_region, opt_alias_def_region)) + } + }, + None => { + // opaque types can only be wrapped/unwrapped in the scope they are defined in (and below) + let error = if let Some((_, decl_region)) = self.has_imported(opaque_str) { + // specific error for when the opaque is imported, which definitely does not work + RuntimeError::OpaqueOutsideScope { opaque, referenced_region: lookup_region, - imported_region: *decl_region, - }); - } + imported_region: decl_region, + } + } else { + self.opaque_not_defined_error(opaque, lookup_region, None) + }; - match self.aliases.get(symbol) { - None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)), - - Some(alias) => match alias.kind { - // The reference is to a proper alias like `Age : U32`, not an opaque type! - AliasKind::Structural => Err(self.opaque_not_defined_error( - opaque, - lookup_region, - Some(alias.header_region()), - )), - // All is good - AliasKind::Opaque => Ok((*symbol, alias)), - }, - } + Err(error) } - None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)), + } + } + + fn lookup_opaque_alias(&self, symbol: Symbol) -> Result<&Alias, Option> { + match self.aliases.get(&symbol) { + None => Err(None), + + Some(alias) => match alias.kind { + AliasKind::Opaque => Ok(alias), + AliasKind::Structural => Err(Some(alias.header_region())), + }, + } + } + + fn is_opaque(&self, ident_id: IdentId, string: &str) -> Option> { + if string.is_empty() { + return None; + } + + let symbol = Symbol::new(self.home, ident_id); + + if let Some(AliasKind::Opaque) = self.aliases.get(&symbol).map(|alias| alias.kind) { + Some(string.into()) + } else { + None } } @@ -180,16 +143,13 @@ impl Scope { lookup_region: Region, opt_defined_alias: Option, ) -> RuntimeError { + // for opaques, we only look at the locals because opaques can only be matched + // on in the module that defines them. let opaques_in_scope = self - .idents() - .filter(|(_, (sym, _))| { - self.aliases - .get(sym) - .map(|alias| alias.kind) - .unwrap_or(AliasKind::Structural) - == AliasKind::Opaque - }) - .map(|(v, _)| v.as_ref().into()) + .locals + .ident_ids + .ident_strs() + .filter_map(|(ident_id, string)| self.is_opaque(ident_id, string)) .collect(); RuntimeError::OpaqueNotDefined { @@ -199,6 +159,58 @@ impl Scope { } } + fn has_imported(&self, ident: &str) -> Option<(Symbol, Region)> { + for (import, shadow, original_region) in self.imports.iter() { + if ident == import.as_str() { + return Some((*shadow, *original_region)); + } + } + + None + } + + /// Is an identifier in scope, either in the locals or imports + fn scope_contains_ident(&self, ident: &str) -> ContainsIdent { + // exposed imports are likely to be small + match self.has_imported(ident) { + Some((symbol, region)) => ContainsIdent::InScope(symbol, region), + None => self.locals.contains_ident(ident), + } + } + + fn introduce_help(&mut self, ident: &str, region: Region) -> Result { + match self.scope_contains_ident(ident) { + ContainsIdent::InScope(original_symbol, original_region) => { + // the ident is already in scope; up to the caller how to handle that + // (usually it's shadowing, but it is valid to shadow ability members) + Err((original_symbol, original_region)) + } + ContainsIdent::NotPresent => { + // We know nothing about this ident yet; introduce it to the scope + let ident_id = self.locals.introduce_into_scope(ident, region); + Ok(Symbol::new(self.home, ident_id)) + } + ContainsIdent::NotInScope(existing) => { + // The ident is not in scope, but its name is already in the string interner + if existing.index() < self.exposed_ident_count { + // if the identifier is exposed, use the IdentId we already have for it + // other modules depend on the symbol having that IdentId + let symbol = Symbol::new(self.home, existing); + + self.locals.in_scope.set(existing.index(), true); + self.locals.regions[existing.index()] = region; + + Ok(symbol) + } else { + // create a new IdentId that under the hood uses the same string bytes as an existing one + let ident_id = self.locals.introduce_into_scope_duplicate(existing, region); + + Ok(Symbol::new(self.home, ident_id)) + } + } + } + } + /// Introduce a new ident to scope. /// /// Returns Err if this would shadow an existing ident, including the @@ -211,46 +223,45 @@ impl Scope { pub fn introduce( &mut self, ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, region: Region, ) -> Result, Symbol)> { - match self.idents.get(&ident) { - Some(&(_, original_region)) => { + self.introduce_str(ident.as_str(), region) + } + + pub fn introduce_str( + &mut self, + ident: &str, + region: Region, + ) -> Result, Symbol)> { + match self.introduce_help(ident, region) { + Ok(symbol) => Ok(symbol), + Err((_, original_region)) => { let shadow = Loc { - value: ident.clone(), + value: Ident::from(ident), region, }; - - let ident_id = all_ident_ids.add(ident.clone()); - let symbol = Symbol::new(self.home, ident_id); - - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); + let symbol = self.locals.scopeless_symbol(ident, region); Err((original_region, shadow, symbol)) } - None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)), } } /// Like [Self::introduce], but does not introduce a new symbol for the shadowing symbol. pub fn introduce_without_shadow_symbol( &mut self, - ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, + ident: &Ident, region: Region, ) -> Result)> { - match self.idents.get(&ident) { - Some(&(_, original_region)) => { + match self.introduce_help(ident.as_str(), region) { + Err((_, original_region)) => { let shadow = Loc { value: ident.clone(), region, }; Err((original_region, shadow)) } - None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)), + Ok(symbol) => Ok(symbol), } } @@ -264,21 +275,18 @@ impl Scope { pub fn introduce_or_shadow_ability_member( &mut self, ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, region: Region, ) -> Result<(Symbol, Option), (Region, Loc, Symbol)> { - match self.idents.get(&ident) { - Some(&(original_symbol, original_region)) => { - let shadow_ident_id = all_ident_ids.add(ident.clone()); - let shadow_symbol = Symbol::new(self.home, shadow_ident_id); + let ident = &ident; - self.symbols.insert(shadow_symbol, region); + match self.introduce_help(ident.as_str(), region) { + Err((original_symbol, original_region)) => { + let shadow_symbol = self.scopeless_symbol(ident, region); if self.abilities_store.is_ability_member_name(original_symbol) { self.abilities_store .register_specializing_symbol(shadow_symbol, original_symbol); - // Add a symbol for the shadow, but don't re-associate the member name. + Ok((shadow_symbol, Some(original_symbol))) } else { // This is an illegal shadow. @@ -287,48 +295,20 @@ impl Scope { region, }; - self.idents.insert(ident, (shadow_symbol, region)); - Err((original_region, shadow, shadow_symbol)) } } - None => { - let new_symbol = - self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region); - Ok((new_symbol, None)) - } + Ok(symbol) => Ok((symbol, None)), } } - fn commit_introduction( - &mut self, - ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, - region: Region, - ) -> Symbol { - // If this IdentId was already added previously - // when the value was exposed in the module header, - // use that existing IdentId. Otherwise, create a fresh one. - let ident_id = match exposed_ident_ids.get_id(&ident) { - Some(ident_id) => ident_id, - None => all_ident_ids.add(ident.clone()), - }; - - let symbol = Symbol::new(self.home, ident_id); - - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); - - symbol - } - - /// Ignore an identifier. + /// Create a new symbol, but don't add it to the scope (yet) /// - /// Used for record guards like { x: Just _ } - pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { - let ident_id = all_ident_ids.add(ident); - Symbol::new(self.home, ident_id) + /// Used for record guards like { x: Just _ } where the `x` is not added to the scope, + /// but also in other places where we need to create a symbol and we don't have the right + /// scope information yet. An identifier can be introduced later, and will use the same IdentId + pub fn scopeless_symbol(&mut self, ident: &Ident, region: Region) -> Symbol { + self.locals.scopeless_symbol(ident.as_str(), region) } /// Import a Symbol from another module into this module's top-level scope. @@ -341,22 +321,20 @@ impl Scope { symbol: Symbol, region: Region, ) -> Result<(), (Symbol, Region)> { - match self.idents.get(&ident) { - Some(shadowed) => Err(*shadowed), - None => { - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); - - Ok(()) - } + if let Some((s, r)) = self.has_imported(ident.as_str()) { + return Err((s, r)); } + + self.imports.push((ident, symbol, region)); + + Ok(()) } pub fn add_alias( &mut self, name: Symbol, region: Region, - vars: Vec>, + vars: Vec>, typ: Type, kind: AliasKind, ) { @@ -364,15 +342,58 @@ impl Scope { self.aliases.insert(name, alias); } + pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> { + self.aliases.get(&symbol) + } + pub fn contains_alias(&mut self, name: Symbol) -> bool { self.aliases.contains_key(&name) } + + pub fn inner_scope(&mut self, f: F) -> T + where + F: FnOnce(&mut Scope) -> T, + { + // store enough information to roll back to the original outer scope + // + // - abilities_store: ability definitions not allowed in inner scopes + // - locals: everything introduced in the inner scope is marked as not in scope in the rollback + // - aliases: stored in a VecMap, we just discard anything added in an inner scope + // - exposed_ident_count: unchanged + // - home: unchanged + let aliases_count = self.aliases.len(); + let locals_snapshot = self.locals.in_scope.len(); + + let result = f(self); + + self.aliases.truncate(aliases_count); + + // anything added in the inner scope is no longer in scope now + for i in locals_snapshot..self.locals.in_scope.len() { + self.locals.in_scope.set(i, false); + } + + result + } + + pub fn register_debug_idents(&self) { + self.home.register_debug_idents(&self.locals.ident_ids) + } + + /// Generates a unique, new symbol like "$1" or "$5", + /// using the home module as the module_id. + /// + /// This is used, for example, during canonicalization of an Expr::Closure + /// to generate a unique symbol to refer to that closure. + pub fn gen_unique_symbol(&mut self) -> Symbol { + Symbol::new(self.home, self.locals.gen_unique()) + } } pub fn create_alias( name: Symbol, region: Region, - vars: Vec>, + vars: Vec>, typ: Type, kind: AliasKind, ) -> Alias { @@ -386,7 +407,7 @@ pub fn create_alias( let mut hidden = type_variables; for loc_var in vars.iter() { - hidden.remove(&loc_var.value.1); + hidden.remove(&loc_var.value.var); } if !hidden.is_empty() { @@ -413,3 +434,317 @@ pub fn create_alias( kind, } } + +#[derive(Debug)] +enum ContainsIdent { + InScope(Symbol, Region), + NotInScope(IdentId), + NotPresent, +} + +#[derive(Clone, Debug)] +pub struct ScopedIdentIds { + pub ident_ids: IdentIds, + in_scope: BitVec, + regions: Vec, + home: ModuleId, +} + +impl ScopedIdentIds { + fn from_ident_ids(home: ModuleId, ident_ids: IdentIds) -> Self { + let capacity = ident_ids.len(); + + Self { + in_scope: BitVec::repeat(false, capacity), + ident_ids, + regions: vec![Region::zero(); capacity], + home, + } + } + + fn has_in_scope(&self, ident: &Ident) -> Option<(Symbol, Region)> { + match self.contains_ident(ident.as_str()) { + ContainsIdent::InScope(symbol, region) => Some((symbol, region)), + ContainsIdent::NotInScope(_) | ContainsIdent::NotPresent => None, + } + } + + fn contains_ident(&self, ident: &str) -> ContainsIdent { + use ContainsIdent::*; + + let mut result = NotPresent; + + for ident_id in self.ident_ids.get_id_many(ident) { + let index = ident_id.index(); + if self.in_scope[index] { + return InScope(Symbol::new(self.home, ident_id), self.regions[index]); + } else { + result = NotInScope(ident_id) + } + } + + result + } + + fn idents_in_scope(&self) -> impl Iterator + '_ { + self.ident_ids + .ident_strs() + .zip(self.in_scope.iter()) + .filter_map(|((_, string), keep)| { + if *keep { + Some(Ident::from(string)) + } else { + None + } + }) + } + + fn introduce_into_scope(&mut self, ident_name: &str, region: Region) -> IdentId { + let id = self.ident_ids.add_str(ident_name); + + debug_assert_eq!(id.index(), self.in_scope.len()); + debug_assert_eq!(id.index(), self.regions.len()); + + self.in_scope.push(true); + self.regions.push(region); + + id + } + + fn introduce_into_scope_duplicate(&mut self, existing: IdentId, region: Region) -> IdentId { + let id = self.ident_ids.duplicate_ident(existing); + + debug_assert_eq!(id.index(), self.in_scope.len()); + debug_assert_eq!(id.index(), self.regions.len()); + + self.in_scope.push(true); + self.regions.push(region); + + id + } + + /// Adds an IdentId, but does not introduce it to the scope + fn scopeless_symbol(&mut self, ident_name: &str, region: Region) -> Symbol { + let id = self.ident_ids.add_str(ident_name); + + debug_assert_eq!(id.index(), self.in_scope.len()); + debug_assert_eq!(id.index(), self.regions.len()); + + self.in_scope.push(false); + self.regions.push(region); + + Symbol::new(self.home, id) + } + + fn gen_unique(&mut self) -> IdentId { + let id = self.ident_ids.gen_unique(); + + debug_assert_eq!(id.index(), self.in_scope.len()); + debug_assert_eq!(id.index(), self.regions.len()); + + self.in_scope.push(false); + self.regions.push(Region::zero()); + + id + } +} + +#[cfg(test)] +mod test { + use super::*; + use roc_module::symbol::ModuleIds; + use roc_region::all::Position; + + use pretty_assertions::{assert_eq, assert_ne}; + + #[test] + fn scope_contains_introduced() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let region = Region::zero(); + let ident = Ident::from("mezolit"); + + assert!(scope.lookup(&ident, region).is_err()); + + assert!(scope.introduce(ident.clone(), region).is_ok()); + + assert!(scope.lookup(&ident, region).is_ok()); + } + + #[test] + fn second_introduce_shadows() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let region1 = Region::from_pos(Position { offset: 10 }); + let region2 = Region::from_pos(Position { offset: 20 }); + let ident = Ident::from("mezolit"); + + assert!(scope.lookup(&ident, Region::zero()).is_err()); + + let first = scope.introduce(ident.clone(), region1).unwrap(); + let (original_region, _ident, shadow_symbol) = + scope.introduce(ident.clone(), region2).unwrap_err(); + + scope.register_debug_idents(); + + assert_ne!(first, shadow_symbol); + assert_eq!(original_region, region1); + + let lookup = scope.lookup(&ident, Region::zero()).unwrap(); + + assert_eq!(first, lookup); + } + + #[test] + fn inner_scope_does_not_influence_outer() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let region = Region::zero(); + let ident = Ident::from("uránia"); + + assert!(scope.lookup(&ident, region).is_err()); + + scope.inner_scope(|inner| { + assert!(inner.introduce(ident.clone(), region).is_ok()); + }); + + assert!(scope.lookup(&ident, region).is_err()); + } + + #[test] + fn default_idents_in_scope() { + let _register_module_debug_names = ModuleIds::default(); + let scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let idents: Vec<_> = scope.idents_in_scope().collect(); + + assert_eq!( + &idents, + &[ + Ident::from("Box"), + Ident::from("Set"), + Ident::from("Dict"), + Ident::from("Str"), + Ident::from("Ok"), + Ident::from("False"), + Ident::from("List"), + Ident::from("True"), + Ident::from("Err"), + ] + ); + } + + #[test] + fn idents_with_inner_scope() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let idents: Vec<_> = scope.idents_in_scope().collect(); + + assert_eq!( + &idents, + &[ + Ident::from("Box"), + Ident::from("Set"), + Ident::from("Dict"), + Ident::from("Str"), + Ident::from("Ok"), + Ident::from("False"), + Ident::from("List"), + Ident::from("True"), + Ident::from("Err"), + ] + ); + + let builtin_count = idents.len(); + + let region = Region::zero(); + + let ident1 = Ident::from("uránia"); + let ident2 = Ident::from("malmok"); + let ident3 = Ident::from("Járnak"); + + scope.introduce(ident1.clone(), region).unwrap(); + scope.introduce(ident2.clone(), region).unwrap(); + scope.introduce(ident3.clone(), region).unwrap(); + + let idents: Vec<_> = scope.idents_in_scope().collect(); + + assert_eq!( + &idents[builtin_count..], + &[ident1.clone(), ident2.clone(), ident3.clone(),] + ); + + scope.inner_scope(|inner| { + let ident4 = Ident::from("Ångström"); + let ident5 = Ident::from("Sirály"); + + inner.introduce(ident4.clone(), region).unwrap(); + inner.introduce(ident5.clone(), region).unwrap(); + + let idents: Vec<_> = inner.idents_in_scope().collect(); + + assert_eq!( + &idents[builtin_count..], + &[ + ident1.clone(), + ident2.clone(), + ident3.clone(), + ident4, + ident5 + ] + ); + }); + + let idents: Vec<_> = scope.idents_in_scope().collect(); + + assert_eq!(&idents[builtin_count..], &[ident1, ident2, ident3,]); + } + + #[test] + fn import_is_in_scope() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let ident = Ident::from("product"); + let symbol = Symbol::LIST_PRODUCT; + let region = Region::zero(); + + assert!(scope.lookup(&ident, region).is_err()); + + assert!(scope.import(ident.clone(), symbol, region).is_ok()); + + assert!(scope.lookup(&ident, region).is_ok()); + + assert!(scope.idents_in_scope().any(|x| x == ident)); + } + + #[test] + fn shadow_of_import() { + let _register_module_debug_names = ModuleIds::default(); + let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + + let ident = Ident::from("product"); + let symbol = Symbol::LIST_PRODUCT; + + let region1 = Region::from_pos(Position { offset: 10 }); + let region2 = Region::from_pos(Position { offset: 20 }); + + scope.import(ident.clone(), symbol, region1).unwrap(); + + let (original_region, _ident, shadow_symbol) = + scope.introduce(ident.clone(), region2).unwrap_err(); + + scope.register_debug_idents(); + + assert_ne!(symbol, shadow_symbol); + assert_eq!(original_region, region1); + + let lookup = scope.lookup(&ident, Region::zero()).unwrap(); + + assert_eq!(symbol, lookup); + } +} diff --git a/compiler/can/src/traverse.rs b/compiler/can/src/traverse.rs new file mode 100644 index 0000000000..7a23cdf20f --- /dev/null +++ b/compiler/can/src/traverse.rs @@ -0,0 +1,341 @@ +//! Traversals over the can ast. + +use roc_module::ident::Lowercase; +use roc_region::all::{Loc, Region}; +use roc_types::subs::Variable; + +use crate::{ + def::{Annotation, Declaration, Def}, + expr::{AccessorData, ClosureData, Expr, Field, WhenBranch}, + pattern::Pattern, +}; + +macro_rules! visit_list { + ($visitor:ident, $walk:ident, $list:expr) => { + for elem in $list { + $visitor.$walk(elem) + } + }; +} + +pub fn walk_decls(visitor: &mut V, decls: &[Declaration]) { + visit_list!(visitor, visit_decl, decls) +} + +pub fn walk_decl(visitor: &mut V, decl: &Declaration) { + match decl { + Declaration::Declare(def) => { + visitor.visit_def(def); + } + Declaration::DeclareRec(defs) => { + visit_list!(visitor, visit_def, defs) + } + Declaration::Builtin(def) => visitor.visit_def(def), + Declaration::InvalidCycle(_cycles) => { + // ignore + } + } +} + +pub fn walk_def(visitor: &mut V, def: &Def) { + let Def { + loc_pattern, + loc_expr, + annotation, + expr_var, + .. + } = def; + + let opt_var = match loc_pattern.value { + Pattern::Identifier(..) | Pattern::AbilityMemberSpecialization { .. } => Some(*expr_var), + _ => loc_pattern.value.opt_var(), + }; + + visitor.visit_pattern(&loc_pattern.value, loc_pattern.region, opt_var); + visitor.visit_expr(&loc_expr.value, loc_expr.region, *expr_var); + if let Some(annot) = &annotation { + visitor.visit_annotation(annot); + } +} + +pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) { + match expr { + Expr::Closure(closure_data) => walk_closure(visitor, closure_data), + Expr::When { + cond_var, + expr_var, + loc_cond, + branches, + region: _, + branches_cond_var: _, + exhaustive: _, + } => { + walk_when(visitor, *cond_var, *expr_var, loc_cond, branches); + } + Expr::Num(..) => { /* terminal */ } + Expr::Int(..) => { /* terminal */ } + Expr::Float(..) => { /* terminal */ } + Expr::Str(..) => { /* terminal */ } + Expr::SingleQuote(..) => { /* terminal */ } + Expr::List { + elem_var, + loc_elems, + } => { + walk_list(visitor, *elem_var, loc_elems); + } + Expr::Var(..) => { /* terminal */ } + Expr::AbilityMember(..) => { /* terminal */ } + Expr::If { + cond_var, + branches, + branch_var, + final_else, + } => walk_if(visitor, *cond_var, branches, *branch_var, final_else), + Expr::LetRec(defs, body) => { + defs.iter().for_each(|def| visitor.visit_def(def)); + visitor.visit_expr(&body.value, body.region, var); + } + Expr::LetNonRec(def, body) => { + visitor.visit_def(def); + visitor.visit_expr(&body.value, body.region, var); + } + Expr::Call(f, args, _called_via) => { + let (fn_var, loc_fn, _closure_var, _ret_var) = &**f; + walk_call(visitor, *fn_var, loc_fn, args); + } + Expr::RunLowLevel { + op: _, + args, + ret_var: _, + } => { + args.iter() + .for_each(|(v, e)| visitor.visit_expr(e, Region::zero(), *v)); + } + Expr::ForeignCall { + foreign_symbol: _, + args, + ret_var: _, + } => { + args.iter() + .for_each(|(v, e)| visitor.visit_expr(e, Region::zero(), *v)); + } + Expr::Record { + record_var: _, + fields, + } => { + walk_record_fields(visitor, fields.iter()); + } + Expr::EmptyRecord => { /* terminal */ } + Expr::Access { + field_var, + loc_expr, + field: _, + record_var: _, + ext_var: _, + } => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var), + Expr::Accessor(AccessorData { .. }) => { /* terminal */ } + Expr::Update { + record_var: _, + ext_var: _, + symbol: _, + updates, + } => { + walk_record_fields(visitor, updates.iter()); + } + Expr::Tag { + variant_var: _, + ext_var: _, + name: _, + arguments, + } => arguments + .iter() + .for_each(|(v, le)| visitor.visit_expr(&le.value, le.region, *v)), + Expr::ZeroArgumentTag { .. } => { /* terminal */ } + Expr::OpaqueRef { + opaque_var: _, + name: _, + argument, + specialized_def_type: _, + type_arguments: _, + lambda_set_variables: _, + } => { + let (var, le) = &**argument; + visitor.visit_expr(&le.value, le.region, *var); + } + Expr::Expect(e1, e2) => { + // TODO: what type does an expect have? + visitor.visit_expr(&e1.value, e1.region, Variable::NULL); + visitor.visit_expr(&e2.value, e2.region, Variable::NULL); + } + Expr::RuntimeError(..) => { /* terminal */ } + } +} + +#[inline(always)] +pub fn walk_closure(visitor: &mut V, clos: &ClosureData) { + let ClosureData { + arguments, + loc_body, + return_type, + .. + } = clos; + + arguments.iter().for_each(|(var, _exhaustive_mark, arg)| { + visitor.visit_pattern(&arg.value, arg.region, Some(*var)) + }); + + visitor.visit_expr(&loc_body.value, loc_body.region, *return_type); +} + +#[inline(always)] +pub fn walk_when( + visitor: &mut V, + cond_var: Variable, + expr_var: Variable, + loc_cond: &Loc, + branches: &[WhenBranch], +) { + visitor.visit_expr(&loc_cond.value, loc_cond.region, cond_var); + + branches + .iter() + .for_each(|branch| walk_when_branch(visitor, branch, expr_var)); +} + +#[inline(always)] +pub fn walk_when_branch(visitor: &mut V, branch: &WhenBranch, expr_var: Variable) { + let WhenBranch { + patterns, + value, + guard, + redundant: _, + } = branch; + + patterns + .iter() + .for_each(|pat| visitor.visit_pattern(&pat.value, pat.region, pat.value.opt_var())); + visitor.visit_expr(&value.value, value.region, expr_var); + if let Some(guard) = guard { + visitor.visit_expr(&guard.value, guard.region, Variable::BOOL); + } +} + +#[inline(always)] +pub fn walk_list(visitor: &mut V, elem_var: Variable, loc_elems: &[Loc]) { + loc_elems + .iter() + .for_each(|le| visitor.visit_expr(&le.value, le.region, elem_var)); +} + +#[inline(always)] +pub fn walk_if( + visitor: &mut V, + cond_var: Variable, + branches: &[(Loc, Loc)], + branch_var: Variable, + final_else: &Loc, +) { + branches.iter().for_each(|(cond, body)| { + visitor.visit_expr(&cond.value, cond.region, cond_var); + visitor.visit_expr(&body.value, body.region, branch_var); + }); + visitor.visit_expr(&final_else.value, final_else.region, branch_var); +} + +#[inline(always)] +pub fn walk_call( + visitor: &mut V, + fn_var: Variable, + fn_expr: &Loc, + args: &[(Variable, Loc)], +) { + visitor.visit_expr(&fn_expr.value, fn_expr.region, fn_var); + args.iter() + .for_each(|(v, le)| visitor.visit_expr(&le.value, le.region, *v)); +} + +#[inline(always)] +pub fn walk_record_fields<'a, V: Visitor>( + visitor: &mut V, + fields: impl Iterator, +) { + fields.for_each( + |( + _name, + Field { + var, + loc_expr, + region: _, + }, + )| { visitor.visit_expr(&loc_expr.value, loc_expr.region, *var) }, + ) +} + +pub trait Visitor: Sized + PatternVisitor { + fn visit_decls(&mut self, decls: &[Declaration]) { + walk_decls(self, decls); + } + + fn visit_decl(&mut self, decl: &Declaration) { + walk_decl(self, decl); + } + + fn visit_def(&mut self, def: &Def) { + walk_def(self, def); + } + + fn visit_annotation(&mut self, _pat: &Annotation) { + // ignore by default + } + + fn visit_expr(&mut self, expr: &Expr, _region: Region, var: Variable) { + walk_expr(self, expr, var); + } +} + +pub fn walk_pattern(_visitor: &mut V, _pattern: &Pattern) { + // ignore for now +} + +pub trait PatternVisitor: Sized { + fn visit_pattern(&mut self, pattern: &Pattern, _region: Region, _opt_var: Option) { + walk_pattern(self, pattern); + } +} + +struct TypeAtVisitor { + region: Region, + typ: Option, +} + +impl PatternVisitor for TypeAtVisitor { + fn visit_pattern(&mut self, pat: &Pattern, region: Region, opt_var: Option) { + if region == self.region { + debug_assert!(self.typ.is_none()); + self.typ = opt_var; + return; + } + if region.contains(&self.region) { + walk_pattern(self, pat) + } + } +} +impl Visitor for TypeAtVisitor { + fn visit_expr(&mut self, expr: &Expr, region: Region, var: Variable) { + if region == self.region { + debug_assert!(self.typ.is_none()); + self.typ = Some(var); + return; + } + if region.contains(&self.region) { + walk_expr(self, expr, var); + } + } +} + +/// Attempts to find the type of an expression at `region`, if it exists. +pub fn find_type_at(region: Region, decls: &[Declaration]) -> Option { + let mut visitor = TypeAtVisitor { region, typ: None }; + visitor.visit_decls(decls); + visitor.typ +} diff --git a/compiler/can/tests/helpers/mod.rs b/compiler/can/tests/helpers/mod.rs index f37d6a9190..038ae2cef1 100644 --- a/compiler/can/tests/helpers/mod.rs +++ b/compiler/can/tests/helpers/mod.rs @@ -11,7 +11,7 @@ use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_problem::can::Problem; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::Type; +use roc_types::types::{AliasVar, Type}; use std::hash::Hash; pub fn test_home() -> ModuleId { @@ -55,17 +55,20 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut // rules multiple times unnecessarily. let loc_expr = operator::desugar_expr(arena, &loc_expr); - let mut scope = Scope::new(home, &mut var_store); + let mut scope = Scope::new(home, IdentIds::default()); scope.add_alias( Symbol::NUM_INT, Region::zero(), - vec![Loc::at_zero(("a".into(), Variable::EMPTY_RECORD))], + vec![Loc::at_zero(AliasVar::unbound( + "a".into(), + Variable::EMPTY_RECORD, + ))], Type::EmptyRec, roc_types::types::AliasKind::Structural, ); let dep_idents = IdentIds::exposed_builtins(0); - let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default()); + let mut env = Env::new(home, &dep_idents, &module_ids); let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, @@ -74,15 +77,8 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut &loc_expr.value, ); - let mut all_ident_ids = MutMap::default(); - - // When pretty printing types, we may need the exposed builtins, - // so include them in the Interns we'll ultimately return. - for (module_id, ident_ids) in IdentIds::exposed_builtins(0) { - all_ident_ids.insert(module_id, ident_ids); - } - - all_ident_ids.insert(home, env.ident_ids); + let mut all_ident_ids = IdentIds::exposed_builtins(1); + all_ident_ids.insert(home, scope.locals.ident_ids); let interns = Interns { module_ids: env.module_ids.clone(), diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 838280f73d..0b52352c5c 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -154,7 +154,7 @@ mod test_can { let region = Region::zero(); assert_can_runtime_error( - string.clone(), + string, RuntimeError::InvalidFloat(FloatErrorKind::Error, region, string.into()), ); } @@ -373,11 +373,9 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 2); + assert_eq!(problems.len(), 1); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, - // Due to one of the shadows - Problem::UnusedDef(..) => true, _ => false, })); } @@ -396,11 +394,9 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 2); + assert_eq!(problems.len(), 1); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, - // Due to one of the shadows - Problem::UnusedDef(..) => true, _ => false, })); } @@ -419,12 +415,10 @@ mod test_can { let arena = Bump::new(); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); - assert_eq!(problems.len(), 2); + assert_eq!(problems.len(), 1); println!("{:#?}", problems); assert!(problems.iter().all(|problem| match problem { Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, - // Due to one of the shadows - Problem::UnusedDef(..) => true, _ => false, })); } @@ -654,34 +648,32 @@ mod test_can { _ => false, })); - assert!(match loc_expr.value { - Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue { .. }) => true, - _ => false, - }); + assert!(matches!( + loc_expr.value, + Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue { .. }) + )); } // TAIL CALLS fn get_closure(expr: &Expr, i: usize) -> roc_can::expr::Recursive { match expr { - LetRec(assignments, body, _) => { - match &assignments.get(i).map(|def| &def.loc_expr.value) { - Some(Closure(ClosureData { - recursive: recursion, - .. - })) => recursion.clone(), - Some(other) => { - panic!("assignment at {} is not a closure, but a {:?}", i, other) - } - None => { - if i > 0 { - get_closure(&body.value, i - 1) - } else { - panic!("Looking for assignment at {} but the list is too short", i) - } + LetRec(assignments, body) => match &assignments.get(i).map(|def| &def.loc_expr.value) { + Some(Closure(ClosureData { + recursive: recursion, + .. + })) => *recursion, + Some(other) => { + panic!("assignment at {} is not a closure, but a {:?}", i, other) + } + None => { + if i > 0 { + get_closure(&body.value, i - 1) + } else { + panic!("Looking for assignment at {} but the list is too short", i) } } - } - LetNonRec(def, body, _) => { + }, + LetNonRec(def, body) => { if i > 0 { // recurse in the body (not the def!) get_closure(&body.value, i - 1) @@ -690,7 +682,7 @@ mod test_can { Closure(ClosureData { recursive: recursion, .. - }) => recursion.clone(), + }) => *recursion, other => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } @@ -741,10 +733,9 @@ mod test_can { } = can_expr_with(&arena, test_home(), src); assert_eq!(problems, Vec::new()); - assert!(problems.iter().all(|problem| match problem { - Problem::UnusedDef(_, _) => true, - _ => false, - })); + assert!(problems + .iter() + .all(|problem| matches!(problem, Problem::UnusedDef(_, _)))); let actual = loc_expr.value; @@ -918,11 +909,7 @@ mod test_can { assert_eq!(problems, Vec::new()); - let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_)) = loc_expr.value { - true - } else { - false - }; + let is_circular_def = matches!(loc_expr.value, RuntimeError(RuntimeError::CircularDef(_))); assert_eq!(is_circular_def, false); } @@ -946,11 +933,7 @@ mod test_can { .. } = can_expr_with(&arena, home, src); - let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_)) = loc_expr.value { - true - } else { - false - }; + let is_circular_def = matches!(loc_expr.value, RuntimeError(RuntimeError::CircularDef(_))); let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry { symbol: interns.symbol(home, "x".into()), diff --git a/compiler/collections/Cargo.toml b/compiler/collections/Cargo.toml index b56f06701b..6cd46e1101 100644 --- a/compiler/collections/Cargo.toml +++ b/compiler/collections/Cargo.toml @@ -11,3 +11,4 @@ im-rc = "15.0.0" wyhash = "0.5.0" bumpalo = { version = "3.8.0", features = ["collections"] } hashbrown = { version = "0.11.2", features = [ "bumpalo" ] } +bitvec = "1" diff --git a/compiler/collections/src/lib.rs b/compiler/collections/src/lib.rs index f0f98b5e64..b41a0ad0a0 100644 --- a/compiler/collections/src/lib.rs +++ b/compiler/collections/src/lib.rs @@ -3,10 +3,14 @@ #![allow(clippy::large_enum_variant)] pub mod all; +mod reference_matrix; +mod small_string_interner; pub mod soa; mod vec_map; mod vec_set; pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; +pub use reference_matrix::{ReferenceMatrix, Sccs}; +pub use small_string_interner::SmallStringInterner; pub use vec_map::VecMap; pub use vec_set::VecSet; diff --git a/compiler/can/src/reference_matrix.rs b/compiler/collections/src/reference_matrix.rs similarity index 72% rename from compiler/can/src/reference_matrix.rs rename to compiler/collections/src/reference_matrix.rs index eaadee556c..8ef1981df5 100644 --- a/compiler/can/src/reference_matrix.rs +++ b/compiler/collections/src/reference_matrix.rs @@ -1,14 +1,14 @@ -// see if we get better performance with different integer types -pub(crate) type Element = usize; -pub(crate) type BitVec = bitvec::vec::BitVec; -pub(crate) type BitSlice = bitvec::prelude::BitSlice; +type Order = bitvec::order::Lsb0; +type Element = usize; +type BitVec = bitvec::vec::BitVec; +type BitSlice = bitvec::prelude::BitSlice; /// A square boolean matrix used to store relations /// /// We use this for sorting definitions so every definition is defined before it is used. /// This functionality is also used to spot and report invalid recursion. #[derive(Debug)] -pub(crate) struct ReferenceMatrix { +pub struct ReferenceMatrix { bitvec: BitVec, length: usize, } @@ -36,8 +36,8 @@ impl ReferenceMatrix { } #[inline(always)] - pub fn get(&self, index: usize) -> bool { - self.bitvec[index] + pub fn get_row_col(&self, row: usize, col: usize) -> bool { + self.bitvec[row * self.length + col] } } @@ -50,6 +50,7 @@ impl ReferenceMatrix { // // Thank you, Samuel! impl ReferenceMatrix { + #[allow(dead_code)] pub fn topological_sort_into_groups(&self) -> TopologicalSort { if self.length == 0 { return TopologicalSort::Groups { groups: Vec::new() }; @@ -58,7 +59,7 @@ impl ReferenceMatrix { let mut preds_map: Vec = vec![0; self.length]; // this is basically summing the columns, I don't see a better way to do it - for row in self.bitvec.chunks(self.length) { + for row in self.bitvec.chunks_exact(self.length) { for succ in row.iter_ones() { preds_map[succ] += 1; } @@ -127,8 +128,14 @@ impl ReferenceMatrix { TopologicalSort::Groups { groups } } - /// Get the strongly-connected components of the set of input nodes. - pub fn strongly_connected_components(&self, nodes: &[u32]) -> Vec> { + /// Get the strongly-connected components all nodes in the matrix + pub fn strongly_connected_components_all(&self) -> Sccs { + let bitvec = BitVec::repeat(true, self.length); + self.strongly_connected_components_subset(&bitvec) + } + + /// Get the strongly-connected components of a set of input nodes. + pub fn strongly_connected_components_subset(&self, nodes: &BitSlice) -> Sccs { let mut params = Params::new(self.length, nodes); 'outer: loop { @@ -147,7 +154,8 @@ impl ReferenceMatrix { } } -pub(crate) enum TopologicalSort { +#[allow(dead_code)] +pub enum TopologicalSort { /// There were no cycles, all nodes have been partitioned into groups Groups { groups: Vec> }, /// Cycles were found. All nodes that are not part of a cycle have been partitioned @@ -172,16 +180,16 @@ struct Params { c: usize, p: Vec, s: Vec, - scc: Vec>, - scca: Vec, + scc: Sccs, + scca: BitVec, } impl Params { - fn new(length: usize, group: &[u32]) -> Self { + fn new(length: usize, group: &BitSlice) -> Self { let mut preorders = vec![Preorder::Removed; length]; - for value in group { - preorders[*value as usize] = Preorder::Empty; + for index in group.iter_ones() { + preorders[index] = Preorder::Empty; } Self { @@ -189,8 +197,11 @@ impl Params { c: 0, s: Vec::new(), p: Vec::new(), - scc: Vec::new(), - scca: Vec::new(), + scc: Sccs { + matrix: ReferenceMatrix::new(length), + components: 0, + }, + scca: BitVec::repeat(false, length), } } } @@ -204,7 +215,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) { params.p.push(v as u32); for w in bitvec[v * length..][..length].iter_ones() { - if !params.scca.contains(&(w as u32)) { + if !params.scca[w] { match params.preorders[w] { Preorder::Filled(pw) => loop { let index = *params.p.last().unwrap(); @@ -230,15 +241,50 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) { if params.p.last() == Some(&(v as u32)) { params.p.pop(); - let mut component = Vec::new(); while let Some(node) = params.s.pop() { - component.push(node); - params.scca.push(node); + params + .scc + .matrix + .set_row_col(params.scc.components, node as usize, true); + params.scca.set(node as usize, true); params.preorders[node as usize] = Preorder::Removed; if node as usize == v { break; } } - params.scc.push(component); + + params.scc.components += 1; + } +} + +#[derive(Debug)] +pub struct Sccs { + components: usize, + matrix: ReferenceMatrix, +} + +impl Sccs { + /// Iterate over the individual components. Each component is represented as a bit vector where + /// a one indicates that the node is part of the group and a zero that it is not. + /// + /// A good way to get the actual nodes is the `.iter_ones()` method. + /// + /// It is guaranteed that a group is non-empty, and that flattening the groups gives a valid + /// topological ordering. + pub fn groups(&self) -> std::iter::Take> { + // work around a panic when requesting a chunk size of 0 + let length = if self.matrix.length == 0 { + // the `.take(self.components)` ensures the resulting iterator will be empty + assert!(self.components == 0); + + 1 + } else { + self.matrix.length + }; + + self.matrix + .bitvec + .chunks_exact(length) + .take(self.components) } } diff --git a/compiler/collections/src/small_string_interner.rs b/compiler/collections/src/small_string_interner.rs new file mode 100644 index 0000000000..89cc93067e --- /dev/null +++ b/compiler/collections/src/small_string_interner.rs @@ -0,0 +1,266 @@ +use std::{fmt::Debug, mem::ManuallyDrop}; + +/// Collection of small (length < u16::MAX) strings, stored compactly. +#[derive(Clone, Default, PartialEq, Eq)] +pub struct SmallStringInterner { + buffer: Vec, + + // lengths could be Vec, but the mono refcount generation + // stringifies Layout's and that creates > 256 character strings + lengths: Vec, + offsets: Vec, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +struct Length(i16); + +impl std::fmt::Debug for Length { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.kind().fmt(f) + } +} + +impl Length { + #[inline(always)] + const fn kind(self) -> Kind { + if self.0 == 0 { + Kind::Empty + } else if self.0 > 0 { + Kind::Interned(self.0 as usize) + } else { + Kind::Generated(self.0.abs() as usize) + } + } + + #[inline(always)] + const fn from_usize(len: usize) -> Self { + Self(len as i16) + } +} + +enum Kind { + Generated(usize), + Empty, + Interned(usize), +} + +impl Debug for Kind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Generated(arg0) => write!(f, "Generated({})", arg0), + Self::Empty => write!(f, "Empty"), + Self::Interned(arg0) => write!(f, "Interned({})", arg0), + } + } +} + +impl std::fmt::Debug for SmallStringInterner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let strings: Vec<_> = self.iter().collect(); + + f.debug_struct("SmallStringInterner") + .field("buffer", &self.buffer) + .field("lengths", &self.lengths) + .field("offsets", &self.offsets) + .field("strings", &strings) + .finish() + } +} + +impl SmallStringInterner { + pub fn with_capacity(capacity: usize) -> Self { + Self { + // guess: the average symbol length is 5 + buffer: Vec::with_capacity(5 * capacity), + + lengths: Vec::with_capacity(capacity), + offsets: Vec::with_capacity(capacity), + } + } + + /// # Safety + /// + /// lengths must be non-negative integers less than 2^15 + pub unsafe fn from_parts(buffer: Vec, lengths: Vec, offsets: Vec) -> Self { + // the recommended way of transmuting a vector + let mut lengths = ManuallyDrop::new(lengths); + + let lengths = Vec::from_raw_parts( + lengths.as_mut_ptr().cast(), + lengths.len(), + lengths.capacity(), + ); + + Self { + buffer, + lengths, + offsets, + } + } + + pub fn iter(&self) -> impl Iterator { + (0..self.offsets.len()).map(move |index| self.get(index)) + } + + /// Insert without deduplicating + pub fn insert(&mut self, string: &str) -> usize { + let bytes = string.as_bytes(); + + assert!(bytes.len() < (1 << 15)); + + let offset = self.buffer.len() as u32; + let length = Length::from_usize(bytes.len()); + + let index = self.lengths.len(); + + self.lengths.push(length); + self.offsets.push(offset); + + self.buffer.extend(bytes); + + index + } + + /// Create a new entry that uses the same string bytes as an existing entry + pub fn duplicate(&mut self, existing: usize) -> usize { + let offset = self.offsets[existing]; + let length = self.lengths[existing]; + + let index = self.lengths.len(); + + self.lengths.push(length); + self.offsets.push(offset); + + index + } + + /// Insert a string equal to the current length into the interner. + /// + /// Assuming that normally you don't insert strings consisting of just digits, + /// this is an easy way to create a unique string name. We use this to create + /// unique variable names: variable names cannot start with a digit in the source, + /// so if we insert the current length of `length` as its digits, that is always unique + pub fn insert_index_str(&mut self) -> usize { + use std::io::Write; + + let index = self.lengths.len(); + + let offset = self.buffer.len(); + write!(self.buffer, "{}", index).unwrap(); + + // this is a generated name, so store it as a negative length + let length = Length(-((self.buffer.len() - offset) as i16)); + + self.lengths.push(length); + self.offsets.push(offset as u32); + + index + } + + #[inline(always)] + pub fn find_index(&self, string: &str) -> Option { + self.find_indices(string).next() + } + + #[inline(always)] + pub fn find_indices<'a>(&'a self, string: &'a str) -> impl Iterator + 'a { + let target_length = string.len(); + + // there can be gaps in the parts of the string that we use (because of updates) + // hence we can't just sum the lengths we've seen so far to get the next offset + self.lengths + .iter() + .enumerate() + .filter_map(move |(index, length)| match length.kind() { + Kind::Generated(_) => None, + Kind::Empty => { + if target_length == 0 { + Some(index) + } else { + None + } + } + Kind::Interned(length) => { + if target_length == length { + let offset = self.offsets[index]; + let slice = &self.buffer[offset as usize..][..length]; + + if string.as_bytes() == slice { + return Some(index); + } + } + + None + } + }) + } + + fn get(&self, index: usize) -> &str { + match self.lengths[index].kind() { + Kind::Empty => "", + Kind::Generated(length) | Kind::Interned(length) => { + let offset = self.offsets[index] as usize; + + let bytes = &self.buffer[offset..][..length]; + + unsafe { std::str::from_utf8_unchecked(bytes) } + } + } + } + + pub fn try_get(&self, index: usize) -> Option<&str> { + if index < self.lengths.len() { + Some(self.get(index)) + } else { + None + } + } + + pub fn update(&mut self, index: usize, new_string: &str) { + let length = new_string.len(); + let offset = self.buffer.len(); + + // future optimization idea: if the current name bytes are at the end of + // `buffer`, we can update them in-place + self.buffer.extend(new_string.bytes()); + + self.lengths[index] = Length::from_usize(length); + self.offsets[index] = offset as u32; + } + + pub fn find_and_update(&mut self, old_string: &str, new_string: &str) -> Option { + match self.find_index(old_string) { + Some(index) => { + self.update(index, new_string); + + Some(index) + } + None => None, + } + } + + pub fn len(&self) -> usize { + self.lengths.len() + } + + pub fn is_empty(&self) -> bool { + self.lengths.is_empty() + } +} + +#[cfg(test)] +mod test { + use super::SmallStringInterner; + + #[test] + fn update_key() { + let mut interner = SmallStringInterner::default(); + + interner.insert("main"); + interner.insert("a"); + assert!(interner.find_and_update("a", "ab").is_some()); + interner.insert("c"); + assert!(interner.find_and_update("c", "cd").is_some()); + } +} diff --git a/compiler/collections/src/vec_map.rs b/compiler/collections/src/vec_map.rs index 57ab5c31b8..8a38fcdb7b 100644 --- a/compiler/collections/src/vec_map.rs +++ b/compiler/collections/src/vec_map.rs @@ -13,6 +13,13 @@ impl Default for VecMap { } } +impl VecMap { + pub fn len(&self) -> usize { + debug_assert_eq!(self.keys.len(), self.values.len()); + self.keys.len() + } +} + impl VecMap { pub fn with_capacity(capacity: usize) -> Self { Self { @@ -21,11 +28,6 @@ impl VecMap { } } - pub fn len(&self) -> usize { - debug_assert_eq!(self.keys.len(), self.values.len()); - self.keys.len() - } - pub fn is_empty(&self) -> bool { debug_assert_eq!(self.keys.len(), self.values.len()); self.keys.is_empty() @@ -54,28 +56,71 @@ impl VecMap { } } - pub fn contains(&self, key: &K) -> bool { + pub fn contains_key(&self, key: &K) -> bool { self.keys.contains(key) } - pub fn remove(&mut self, key: &K) { + pub fn remove(&mut self, key: &K) -> Option<(K, V)> { + let index = self.keys.iter().position(|x| x == key)?; + Some(self.swap_remove(index)) + } + + pub fn get(&self, key: &K) -> Option<&V> { match self.keys.iter().position(|x| x == key) { + None => None, + Some(index) => Some(&self.values[index]), + } + } + + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + match self.keys.iter().position(|x| x == key) { + None => None, + Some(index) => Some(&mut self.values[index]), + } + } + + pub fn get_or_insert(&mut self, key: K, default_value: impl FnOnce() -> V) -> &mut V { + match self.keys.iter().position(|x| x == &key) { + Some(index) => &mut self.values[index], None => { - // just do nothing - } - Some(index) => { - self.swap_remove(index); + let value = default_value(); + + self.keys.push(key); + self.values.push(value); + + self.values.last_mut().unwrap() } } } - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl ExactSizeIterator { self.keys.iter().zip(self.values.iter()) } - pub fn values(&self) -> impl Iterator { + pub fn keys(&self) -> impl ExactSizeIterator { + self.keys.iter() + } + + pub fn values(&self) -> impl ExactSizeIterator { self.values.iter() } + + pub fn truncate(&mut self, len: usize) { + self.keys.truncate(len); + self.values.truncate(len); + } + + pub fn unzip(self) -> (Vec, Vec) { + (self.keys, self.values) + } + + /// # Safety + /// + /// keys and values must have the same length, and there must not + /// be any duplicates in the keys vector + pub unsafe fn zip(keys: Vec, values: Vec) -> Self { + Self { keys, values } + } } impl Extend<(K, V)> for VecMap { @@ -110,6 +155,7 @@ impl IntoIterator for VecMap { fn into_iter(self) -> Self::IntoIter { IntoIter { + len: self.len(), keys: self.keys.into_iter(), values: self.values.into_iter(), } @@ -117,6 +163,7 @@ impl IntoIterator for VecMap { } pub struct IntoIter { + len: usize, keys: std::vec::IntoIter, values: std::vec::IntoIter, } @@ -131,3 +178,9 @@ impl Iterator for IntoIter { } } } + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + self.len + } +} diff --git a/compiler/collections/src/vec_set.rs b/compiler/collections/src/vec_set.rs index 4869084677..d139272e89 100644 --- a/compiler/collections/src/vec_set.rs +++ b/compiler/collections/src/vec_set.rs @@ -40,6 +40,17 @@ impl VecSet { } } + /// Returns true iff any of the given elements previoously existed in the set. + pub fn insert_all>(&mut self, values: I) -> bool { + let mut any_existed = false; + + for value in values { + any_existed = any_existed || self.insert(value); + } + + any_existed + } + pub fn contains(&self, value: &T) -> bool { self.elements.contains(value) } @@ -58,6 +69,10 @@ impl VecSet { pub fn iter(&self) -> impl Iterator { self.elements.iter() } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.elements.iter_mut() + } } impl Extend for VecSet { diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 86a812aa64..9f1b4c918d 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -2,13 +2,12 @@ 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_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, Category}; -use roc_types::types::{Reason, TypeExtension}; +use roc_types::types::{OptAbleType, Reason}; #[must_use] #[inline(always)] @@ -161,117 +160,106 @@ pub fn str_type() -> Type { #[inline(always)] fn builtin_alias( symbol: Symbol, - type_arguments: Vec<(Lowercase, Type)>, + type_arguments: Vec, actual: Box, + kind: AliasKind, ) -> Type { Type::Alias { symbol, type_arguments, actual, lambda_set_variables: vec![], - // TODO(opaques): revisit later - kind: AliasKind::Structural, + kind, } } #[inline(always)] pub fn num_float(range: Type) -> Type { builtin_alias( - Symbol::NUM_FLOAT, - vec![("range".into(), range.clone())], + Symbol::NUM_FRAC, + vec![OptAbleType::unbound(range.clone())], Box::new(num_num(num_floatingpoint(range))), + AliasKind::Structural, ) } #[inline(always)] pub fn num_floatingpoint(range: Type) -> Type { - let alias_content = Type::TagUnion( - vec![( - TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), - vec![range.clone()], - )], - TypeExtension::Closed, - ); - builtin_alias( Symbol::NUM_FLOATINGPOINT, - vec![("range".into(), range)], - Box::new(alias_content), + vec![OptAbleType::unbound(range.clone())], + Box::new(range), + AliasKind::Opaque, ) } #[inline(always)] pub fn num_u32() -> Type { - builtin_alias(Symbol::NUM_U32, vec![], Box::new(num_int(num_unsigned32()))) + builtin_alias( + Symbol::NUM_U32, + vec![], + Box::new(num_int(num_unsigned32())), + AliasKind::Structural, + ) } #[inline(always)] fn num_unsigned32() -> Type { - let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_UNSIGNED32), vec![])], - TypeExtension::Closed, - ); - - builtin_alias(Symbol::NUM_UNSIGNED32, vec![], Box::new(alias_content)) + builtin_alias( + Symbol::NUM_UNSIGNED32, + vec![], + Box::new(Type::EmptyTagUnion), + AliasKind::Opaque, + ) } #[inline(always)] pub fn num_binary64() -> Type { - let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_BINARY64), vec![])], - TypeExtension::Closed, - ); - - builtin_alias(Symbol::NUM_BINARY64, vec![], Box::new(alias_content)) + builtin_alias( + Symbol::NUM_BINARY64, + vec![], + Box::new(Type::EmptyTagUnion), + AliasKind::Opaque, + ) } #[inline(always)] pub fn num_int(range: Type) -> Type { builtin_alias( Symbol::NUM_INT, - vec![("range".into(), range.clone())], + vec![OptAbleType::unbound(range.clone())], Box::new(num_num(num_integer(range))), + AliasKind::Structural, ) } #[inline(always)] pub fn num_signed64() -> Type { - let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_SIGNED64), vec![])], - TypeExtension::Closed, - ); - - builtin_alias(Symbol::NUM_SIGNED64, vec![], Box::new(alias_content)) + builtin_alias( + Symbol::NUM_SIGNED64, + vec![], + Box::new(Type::EmptyTagUnion), + AliasKind::Opaque, + ) } #[inline(always)] pub fn num_integer(range: Type) -> Type { - let alias_content = Type::TagUnion( - vec![( - TagName::Private(Symbol::NUM_AT_INTEGER), - vec![range.clone()], - )], - TypeExtension::Closed, - ); - builtin_alias( Symbol::NUM_INTEGER, - vec![("range".into(), range)], - Box::new(alias_content), + vec![OptAbleType::unbound(range.clone())], + Box::new(range), + AliasKind::Opaque, ) } #[inline(always)] pub fn num_num(typ: Type) -> Type { - let alias_content = Type::TagUnion( - vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])], - TypeExtension::Closed, - ); - builtin_alias( Symbol::NUM_NUM, - vec![("range".into(), typ)], - Box::new(alias_content), + vec![OptAbleType::unbound(typ.clone())], + Box::new(typ), + AliasKind::Opaque, ) } diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 1b820bf762..d6dd14f57e 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -5,10 +5,11 @@ use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::{Declaration, Def}; +use roc_can::exhaustive::{sketch_pattern_to_rows, sketch_when_branches, ExhaustiveContext}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; -use roc_can::expr::{AccessorData, ClosureData, Field, WhenBranch}; +use roc_can::expr::{AccessorData, AnnotatedMark, ClosureData, Field, WhenBranch}; use roc_can::pattern::Pattern; use roc_collections::all::{HumanIndex, MutMap, SendMap}; use roc_module::ident::{Lowercase, TagName}; @@ -17,7 +18,7 @@ use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; use roc_types::types::{ - AliasKind, AnnotationSource, Category, PReason, Reason, RecordField, TypeExtension, + AliasKind, AnnotationSource, Category, OptAbleType, PReason, Reason, RecordField, TypeExtension, }; /// This is for constraining Defs @@ -49,7 +50,7 @@ pub struct Env { fn constrain_untyped_args( constraints: &mut Constraints, env: &Env, - arguments: &[(Variable, Loc)], + arguments: &[(Variable, AnnotatedMark, Loc)], closure_type: Type, return_type: Type, ) -> (Vec, PatternState, Type) { @@ -58,7 +59,10 @@ fn constrain_untyped_args( let mut pattern_state = PatternState::default(); - for (pattern_var, loc_pattern) in arguments { + for (pattern_var, annotated_mark, loc_pattern) in arguments { + // Untyped args don't need exhaustiveness checking because they are the source of truth! + let _ = annotated_mark; + let pattern_type = Type::Variable(*pattern_var); let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); @@ -254,7 +258,7 @@ pub fn constrain_expr( let (fn_var, loc_fn, closure_var, ret_var) = &**boxed; // The expression that evaluates to the function being called, e.g. `foo` in // (foo) bar baz - let opt_symbol = if let Var(symbol) = loc_fn.value { + let opt_symbol = if let Var(symbol) | AbilityMember(symbol, _) = loc_fn.value { Some(symbol) } else { None @@ -332,6 +336,11 @@ pub fn constrain_expr( // make lookup constraint to lookup this symbol's type in the environment constraints.lookup(*symbol, expected, region) } + AbilityMember(symbol, _specialization) => { + // make lookup constraint to lookup this symbol's type in the environment + constraints.lookup(*symbol, expected, region) + // TODO: consider trying to solve `_specialization` here. + } Closure(ClosureData { function_type: fn_var, closure_type: closure_var, @@ -579,25 +588,28 @@ pub fn constrain_expr( } } When { - cond_var, + cond_var: real_cond_var, expr_var, loc_cond, branches, + branches_cond_var, + exhaustive, .. } => { - // Infer the condition expression's type. - 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 branches_cond_var = *branches_cond_var; + let branches_cond_type = Variable(branches_cond_var); - let branch_var = *expr_var; - let branch_type = Variable(branch_var); + let body_var = *expr_var; + let body_type = Variable(body_var); + + let branches_region = { + debug_assert!(!branches.is_empty()); + Region::span_across( + &loc_cond.region, + // &branches.first().unwrap().region(), + &branches.last().unwrap().pattern_region(), + ) + }; let branch_expr_reason = |expected: &Expected, index, branch_region| match expected { @@ -613,13 +625,13 @@ pub fn constrain_expr( index, region: ann_source.region(), }, - branch_type.clone(), + body_type.clone(), ) } _ => ForReason( Reason::WhenBranch { index }, - branch_type.clone(), + body_type.clone(), branch_region, ), }; @@ -647,12 +659,20 @@ pub fn constrain_expr( // constraints. let mut pattern_vars = Vec::with_capacity(branches.len()); let mut pattern_headers = SendMap::default(); - let mut pattern_cons = Vec::with_capacity(branches.len()); + let mut pattern_cons = Vec::with_capacity(branches.len() + 2); let mut branch_cons = Vec::with_capacity(branches.len()); for (index, when_branch) in branches.iter().enumerate() { - let pattern_region = - Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + let expected_pattern = |sub_pattern, sub_region| { + PExpected::ForReason( + PReason::WhenMatch { + index: HumanIndex::zero_based(index), + sub_pattern, + }, + branches_cond_type.clone(), + sub_region, + ) + }; let (new_pattern_vars, new_pattern_headers, pattern_con, branch_con) = constrain_when_branch_help( @@ -660,13 +680,7 @@ pub fn constrain_expr( env, region, when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: HumanIndex::zero_based(index), - }, - cond_type.clone(), - pattern_region, - ), + expected_pattern, branch_expr_reason( &expected, HumanIndex::zero_based(index), @@ -680,7 +694,8 @@ pub fn constrain_expr( .clone() .intersection(new_pattern_headers.clone()) .is_empty(), - "Two patterns introduce the same symbols - that's a bug!" + "Two patterns introduce the same symbols - that's a bug!\n{:?}", + pattern_headers.clone().intersection(new_pattern_headers) ); pattern_headers.extend(new_pattern_headers); pattern_cons.push(pattern_con); @@ -696,10 +711,36 @@ pub fn constrain_expr( // // The return type of each branch must equal the return type of // the entire when-expression. - // branch_cons.extend(pattern_cons); - // branch_constraints.push(constraints.and_constraint(pattern_cons)); - let mut total_cons = Vec::with_capacity(1 + 2 * branches.len() + 1); - total_cons.push(expr_con); + + // After solving the condition variable with what's expected from the branch patterns, + // check it against the condition expression. + // + // First, solve the condition type. + let real_cond_var = *real_cond_var; + let real_cond_type = Type::Variable(real_cond_var); + let cond_constraint = constrain_expr( + constraints, + env, + loc_cond.region, + &loc_cond.value, + Expected::NoExpectation(real_cond_type), + ); + pattern_cons.push(cond_constraint); + + // Now check the condition against the type expected by the branches. + let sketched_rows = sketch_when_branches(real_cond_var, branches_region, branches); + let cond_matches_branches_constraint = constraints.exhaustive( + real_cond_var, + loc_cond.region, + Ok(( + loc_cond.value.category(), + Expected::ForReason(Reason::WhenBranches, branches_cond_type, branches_region), + )), + sketched_rows, + ExhaustiveContext::BadCase, + *exhaustive, + ); + pattern_cons.push(cond_matches_branches_constraint); // Solve all the pattern constraints together, introducing variables in the pattern as // need be before solving the bodies. @@ -712,20 +753,22 @@ pub fn constrain_expr( pattern_constraints, body_constraints, ); - total_cons.push(when_body_con); - total_cons.push(constraints.equal_types_var( - branch_var, - expected, - Category::When, - region, - )); + let result_con = + constraints.equal_types_var(body_var, expected, Category::When, region); + let total_cons = [when_body_con, result_con]; let branch_constraints = constraints.and_constraint(total_cons); - // exhautiveness checking happens when converting to mono::Expr - // ...for now - constraints.exists([cond_var, *expr_var], branch_constraints) + constraints.exists( + [ + exhaustive.variable_for_introduction(), + branches_cond_var, + real_cond_var, + *expr_var, + ], + branch_constraints, + ) } Access { record_var, @@ -841,7 +884,7 @@ pub fn constrain_expr( cons, ) } - LetRec(defs, loc_ret, var) => { + LetRec(defs, loc_ret) => { let body_con = constrain_expr( constraints, env, @@ -850,29 +893,17 @@ pub fn constrain_expr( expected.clone(), ); - 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_var( - *var, - expected, - Category::Storage(std::file!(), std::line!()), - loc_ret.region, - ), - ]; - - constraints.exists_many([*var], cons) + constrain_recursive_defs(constraints, env, defs, body_con) } - LetNonRec(def, loc_ret, var) => { + LetNonRec(def, loc_ret) => { let mut stack = Vec::with_capacity(1); let mut loc_ret = loc_ret; - stack.push((def, var, loc_ret.region)); + stack.push(def); - while let LetNonRec(def, new_loc_ret, var) = &loc_ret.value { - stack.push((def, var, new_loc_ret.region)); + while let LetNonRec(def, new_loc_ret) = &loc_ret.value { + stack.push(def); loc_ret = new_loc_ret; } @@ -884,20 +915,8 @@ pub fn constrain_expr( expected.clone(), ); - while let Some((def, var, ret_region)) = stack.pop() { - 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_var( - *var, - expected.clone(), - Category::Storage(std::file!(), std::line!()), - ret_region, - ), - ]; - - body_con = constraints.exists_many([*var], cons) + while let Some(def) = stack.pop() { + body_con = constrain_def(constraints, env, def, body_con) } body_con @@ -984,7 +1003,13 @@ pub fn constrain_expr( let opaque_type = Type::Alias { symbol: *name, - type_arguments: type_arguments.clone(), + type_arguments: type_arguments + .iter() + .map(|v| OptAbleType { + typ: Type::Variable(v.var), + opt_ability: v.opt_ability, + }) + .collect(), lambda_set_variables: lambda_set_variables.clone(), actual: Box::new(arg_type.clone()), kind: AliasKind::Opaque, @@ -1021,9 +1046,7 @@ pub fn constrain_expr( let mut vars = vec![*arg_var, *opaque_var]; // Also add the fresh variables we created for the type argument and lambda sets - vars.extend(type_arguments.iter().map(|(_, t)| { - t.expect_variable("all type arguments should be fresh variables here") - })); + vars.extend(type_arguments.iter().map(|v| v.var)); vars.extend(lambda_set_variables.iter().map(|v| { v.0.expect_variable("all lambda sets should be fresh variables here") })); @@ -1122,7 +1145,7 @@ fn constrain_when_branch_help( env: &Env, region: Region, when_branch: &WhenBranch, - pattern_expected: PExpected, + pattern_expected: impl Fn(HumanIndex, Region) -> PExpected, expr_expected: Expected, ) -> ( Vec, @@ -1140,19 +1163,22 @@ fn constrain_when_branch_help( let mut state = PatternState { headers: SendMap::default(), - vars: Vec::with_capacity(1), - constraints: Vec::with_capacity(1), + vars: Vec::with_capacity(2), + constraints: Vec::with_capacity(2), + delayed_is_open_constraints: Vec::new(), }; // TODO investigate for error messages, is it better to unify all branches with a variable, // then unify that variable with the expectation? - for loc_pattern in &when_branch.patterns { + for (i, loc_pattern) in when_branch.patterns.iter().enumerate() { + let pattern_expected = pattern_expected(HumanIndex::zero_based(i), loc_pattern.region); + constrain_pattern( constraints, env, &loc_pattern.value, loc_pattern.region, - pattern_expected.clone(), + pattern_expected, &mut state, ); } @@ -1171,11 +1197,17 @@ fn constrain_when_branch_help( ); // must introduce the headers from the pattern before constraining the guard + state + .constraints + .append(&mut state.delayed_is_open_constraints); let state_constraints = constraints.and_constraint(state.constraints); let inner = constraints.let_constraint([], [], [], guard_constraint, ret_constraint); (state_constraints, inner) } else { + state + .constraints + .append(&mut state.delayed_is_open_constraints); let state_constraints = constraints.and_constraint(state.constraints); (state_constraints, ret_constraint) }; @@ -1255,7 +1287,7 @@ pub fn constrain_decls( constraint } -fn constrain_def_pattern( +pub fn constrain_def_pattern( constraints: &mut Constraints, env: &Env, loc_pattern: &Loc, @@ -1267,6 +1299,7 @@ fn constrain_def_pattern( headers: SendMap::default(), vars: Vec::with_capacity(1), constraints: Vec::with_capacity(1), + delayed_is_open_constraints: vec![], }; constrain_pattern( @@ -1364,6 +1397,7 @@ fn constrain_typed_def( headers: SendMap::default(), vars: Vec::with_capacity(arguments.len()), constraints: Vec::with_capacity(1), + delayed_is_open_constraints: vec![], }; let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1); let ret_var = *ret_var; @@ -1448,8 +1482,8 @@ fn constrain_typed_def( constrain_def_make_constraint( constraints, - new_rigid_variables, - new_infer_variables, + new_rigid_variables.into_iter(), + new_infer_variables.into_iter(), expr_con, body_con, def_pattern_state, @@ -1483,8 +1517,8 @@ fn constrain_typed_def( constrain_def_make_constraint( constraints, - new_rigid_variables, - new_infer_variables, + new_rigid_variables.into_iter(), + new_infer_variables.into_iter(), expr_con, body_con, def_pattern_state, @@ -1499,7 +1533,7 @@ fn constrain_typed_function_arguments( def: &Def, def_pattern_state: &mut PatternState, argument_pattern_state: &mut PatternState, - arguments: &[(Variable, Loc)], + arguments: &[(Variable, AnnotatedMark, Loc)], arg_types: &[Type], ) { // ensure type matches the one in the annotation @@ -1510,38 +1544,113 @@ fn constrain_typed_function_arguments( }; let it = arguments.iter().zip(arg_types.iter()).enumerate(); - for (index, ((pattern_var, loc_pattern), loc_ann)) in it { - let pattern_expected = PExpected::ForReason( - PReason::TypedArg { - index: HumanIndex::zero_based(index), - opt_name: opt_label, - }, - loc_ann.clone(), - loc_pattern.region, - ); - - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - argument_pattern_state, - ); - - { - // NOTE: because we perform an equality with part of the signature - // this constraint must be to the def_pattern_state's constraints - def_pattern_state.vars.push(*pattern_var); - - let pattern_con = constraints.equal_types_var( - *pattern_var, - Expected::NoExpectation(loc_ann.clone()), - Category::Storage(std::file!(), std::line!()), + for (index, ((pattern_var, annotated_mark, loc_pattern), ann)) in it { + if loc_pattern.value.surely_exhaustive() { + // OPT: we don't need to perform any type-level exhaustiveness checking. + // Check instead only that the pattern unifies with the annotation type. + let pattern_expected = PExpected::ForReason( + PReason::TypedArg { + index: HumanIndex::zero_based(index), + opt_name: opt_label, + }, + ann.clone(), loc_pattern.region, ); - def_pattern_state.constraints.push(pattern_con); + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + argument_pattern_state, + ); + + { + // NOTE: because we perform an equality with part of the signature + // this constraint must be to the def_pattern_state's constraints + def_pattern_state.vars.push(*pattern_var); + + let pattern_con = constraints.equal_types_var( + *pattern_var, + Expected::NoExpectation(ann.clone()), + Category::Storage(std::file!(), std::line!()), + loc_pattern.region, + ); + + def_pattern_state.constraints.push(pattern_con); + } + } else { + // We need to check the types, and run exhaustiveness checking. + let &AnnotatedMark { + annotation_var, + exhaustive, + } = annotated_mark; + + def_pattern_state.vars.push(*pattern_var); + def_pattern_state.vars.push(annotation_var); + + { + // First, solve the type that the pattern is expecting to match in this + // position. + let pattern_expected = PExpected::NoExpectation(Type::Variable(*pattern_var)); + constrain_pattern( + constraints, + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + argument_pattern_state, + ); + } + + { + // Store the actual type in a variable. + argument_pattern_state + .constraints + .push(constraints.equal_types_var( + annotation_var, + Expected::NoExpectation(ann.clone()), + Category::Storage(file!(), line!()), + Region::zero(), + )); + } + + { + // let pattern_expected = PExpected::ForReason( + // PReason::TypedArg { + // index: HumanIndex::zero_based(index), + // opt_name: opt_label, + // }, + // ann.clone(), + // loc_pattern.region, + // ); + + // Exhaustiveness-check the type in the pattern against what the + // annotation wants. + let sketched_rows = + sketch_pattern_to_rows(annotation_var, loc_pattern.region, &loc_pattern.value); + let category = loc_pattern.value.category(); + let expected = PExpected::ForReason( + PReason::TypedArg { + index: HumanIndex::zero_based(index), + opt_name: opt_label, + }, + Type::Variable(*pattern_var), + loc_pattern.region, + ); + let exhaustive_constraint = constraints.exhaustive( + annotation_var, + loc_pattern.region, + Err((category, expected)), + sketched_rows, + ExhaustiveContext::BadArg, + exhaustive, + ); + argument_pattern_state + .constraints + .push(exhaustive_constraint) + } } } } @@ -1574,8 +1683,8 @@ fn constrain_def( constrain_def_make_constraint( constraints, - vec![], - vec![], + std::iter::empty(), + std::iter::empty(), expr_con, body_con, def_pattern_state, @@ -1584,10 +1693,10 @@ fn constrain_def( } } -fn constrain_def_make_constraint( +pub fn constrain_def_make_constraint( constraints: &mut Constraints, - new_rigid_variables: Vec, - new_infer_variables: Vec, + new_rigid_variables: impl Iterator, + new_infer_variables: impl Iterator, expr_con: Constraint, body_con: Constraint, def_pattern_state: PatternState, @@ -1843,9 +1952,9 @@ pub fn rec_defs_help( headers: SendMap::default(), vars: Vec::with_capacity(arguments.len()), constraints: Vec::with_capacity(1), + delayed_is_open_constraints: vec![], }; let mut vars = Vec::with_capacity(state.vars.capacity() + 1); - let mut pattern_types = Vec::with_capacity(state.vars.capacity()); let ret_var = *ret_var; let closure_var = *closure_var; let closure_ext_var = *closure_ext_var; @@ -1855,53 +1964,16 @@ pub fn rec_defs_help( vars.push(closure_var); vars.push(closure_ext_var); - let it = arguments.iter().zip(arg_types.iter()).enumerate(); - for (index, ((pattern_var, loc_pattern), loc_ann)) in it { - { - // ensure type matches the one in the annotation - let opt_label = - if let Pattern::Identifier(label) = def.loc_pattern.value { - Some(label) - } else { - None - }; - let pattern_type: &Type = loc_ann; - - let pattern_expected = PExpected::ForReason( - PReason::TypedArg { - index: HumanIndex::zero_based(index), - opt_name: opt_label, - }, - pattern_type.clone(), - loc_pattern.region, - ); - - constrain_pattern( - constraints, - env, - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - &mut state, - ); - } - - { - // NOTE: because we perform an equality with part of the signature - // this constraint must be to the def_pattern_state's constraints - def_pattern_state.vars.push(*pattern_var); - pattern_types.push(Type::Variable(*pattern_var)); - - let pattern_con = constraints.equal_types_var( - *pattern_var, - Expected::NoExpectation(loc_ann.clone()), - Category::Storage(std::file!(), std::line!()), - loc_pattern.region, - ); - - def_pattern_state.constraints.push(pattern_con); - } - } + constrain_typed_function_arguments( + constraints, + env, + def, + &mut def_pattern_state, + &mut state, + arguments, + arg_types, + ); + let pattern_types = arguments.iter().map(|a| Type::Variable(a.0)).collect(); let closure_constraint = constrain_closure_size( constraints, diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index f0761ea965..ec913f44cd 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -1,15 +1,17 @@ +use crate::expr::{constrain_def_make_constraint, constrain_def_pattern, Env}; use roc_builtins::std::StdLib; use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::Declaration; use roc_can::expected::Expected; +use roc_can::pattern::Pattern; use roc_collections::all::MutMap; use roc_error_macros::internal_error; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_types::solved_types::{FreeVars, SolvedType}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Category, Type}; +use roc_types::types::{AnnotationSource, Category, Type}; /// The types of all exposed values/functions of a collection of modules #[derive(Clone, Debug, Default)] @@ -94,13 +96,15 @@ pub enum ExposedModuleTypes { pub fn constrain_module( constraints: &mut Constraints, + symbols_from_requires: Vec<(Loc, Loc)>, abilities_store: &AbilitiesStore, declarations: &[Declaration], home: ModuleId, ) -> Constraint { let constraint = crate::expr::constrain_decls(constraints, home, declarations); - - let constraint = frontload_ability_constraints(constraints, abilities_store, constraint); + let constraint = + constrain_symbols_from_requires(constraints, symbols_from_requires, home, constraint); + let constraint = frontload_ability_constraints(constraints, abilities_store, home, constraint); // The module constraint should always save the environment at the end. debug_assert!(constraints.contains_save_the_environment(&constraint)); @@ -108,42 +112,101 @@ pub fn constrain_module( constraint } +fn constrain_symbols_from_requires( + constraints: &mut Constraints, + symbols_from_requires: Vec<(Loc, Loc)>, + home: ModuleId, + constraint: Constraint, +) -> Constraint { + symbols_from_requires + .into_iter() + .fold(constraint, |constraint, (loc_symbol, loc_type)| { + if loc_symbol.value.module_id() == home { + // 1. Required symbols can only be specified in package modules + // 2. Required symbols come from app modules + // But, if we are running e.g. `roc check` on a package module, there is no app + // module, and we will have instead put the required symbols in the package module + // namespace. If this is the case, we want to introduce the symbols as if they had + // the types they are annotated with. + let rigids = Default::default(); + let env = Env { home, rigids }; + let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(loc_symbol.value)); + + let def_pattern_state = + constrain_def_pattern(constraints, &env, &pattern, loc_type.value); + + constrain_def_make_constraint( + constraints, + // No new rigids or flex vars because they are represented in the type + // annotation. + std::iter::empty(), + std::iter::empty(), + Constraint::True, + constraint, + def_pattern_state, + ) + } else { + // Otherwise, this symbol comes from an app module - we want to check that the type + // provided by the app is in fact what the package module requires. + let arity = loc_type.value.arity(); + let provided_eq_requires_constr = constraints.lookup( + loc_symbol.value, + Expected::FromAnnotation( + loc_symbol.map(|&s| Pattern::Identifier(s)), + arity, + AnnotationSource::RequiredSymbol { + region: loc_type.region, + }, + loc_type.value, + ), + loc_type.region, + ); + constraints.and_constraint([provided_eq_requires_constr, constraint]) + } + }) +} + pub fn frontload_ability_constraints( constraints: &mut Constraints, abilities_store: &AbilitiesStore, + home: ModuleId, mut constraint: Constraint, ) -> Constraint { for (member_name, member_data) in abilities_store.root_ability_members().iter() { - // 1. Attach the type of member signature to the reserved signature_var. This is - // infallible. - let unify_with_signature_var = constraints.equal_types_var( - member_data.signature_var, - Expected::NoExpectation(member_data.signature.clone()), - Category::Storage(std::file!(), std::column!()), - Region::zero(), + let rigids = Default::default(); + let env = Env { home, rigids }; + let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(*member_name)); + + let mut def_pattern_state = constrain_def_pattern( + constraints, + &env, + &pattern, + Type::Variable(member_data.signature_var), ); - // 2. Store the member signature on the member symbol. This makes sure we generalize it on - // the toplevel, as appropriate. - let vars = &member_data.variables; - let rigids = (vars.rigid_vars.iter()) - // For our purposes, in the let constraint, able vars are treated like rigids. - .chain(vars.able_vars.iter()) - .copied(); - let flex = vars.flex_vars.iter().copied(); + def_pattern_state.vars.push(member_data.signature_var); - let let_constr = constraints.let_constraint( - rigids, - flex, - [( - *member_name, - Loc::at_zero(Type::Variable(member_data.signature_var)), - )], + let vars = &member_data.variables; + let rigid_variables = vars.rigid_vars.iter().chain(vars.able_vars.iter()).copied(); + let infer_variables = vars.flex_vars.iter().copied(); + + def_pattern_state + .constraints + .push(constraints.equal_types_var( + member_data.signature_var, + Expected::NoExpectation(member_data.signature.clone()), + Category::Storage(file!(), line!()), + Region::zero(), + )); + + constraint = constrain_def_make_constraint( + constraints, + rigid_variables, + infer_variables, Constraint::True, constraint, + def_pattern_state, ); - - constraint = constraints.and_constraint([unify_with_signature_var, let_constr]); } constraint } diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 2672602b7b..19d552ddf4 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -10,7 +10,8 @@ use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; use roc_types::types::{ - AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type, TypeExtension, + AliasKind, Category, OptAbleType, PReason, PatternCategory, Reason, RecordField, Type, + TypeExtension, }; #[derive(Default)] @@ -18,6 +19,7 @@ pub struct PatternState { pub headers: SendMap>, pub vars: Vec, pub constraints: Vec, + pub delayed_is_open_constraints: Vec, } /// If there is a type annotation, the pattern state headers can be optimized by putting the @@ -180,7 +182,7 @@ pub fn constrain_pattern( // so, we know that "x" (in this case, a tag union) must be open. if could_be_a_tag_union(expected.get_type_ref()) { state - .constraints + .delayed_is_open_constraints .push(constraints.is_open_type(expected.get_type())); } } @@ -191,7 +193,7 @@ pub fn constrain_pattern( Identifier(symbol) | Shadowed(_, _, symbol) => { if could_be_a_tag_union(expected.get_type_ref()) { state - .constraints + .delayed_is_open_constraints .push(constraints.is_open_type(expected.get_type_ref().clone())); } @@ -494,6 +496,9 @@ pub fn constrain_pattern( state.vars.push(*ext_var); state.constraints.push(whole_con); state.constraints.push(tag_con); + state + .constraints + .append(&mut state.delayed_is_open_constraints); } UnwrappedOpaque { @@ -510,7 +515,13 @@ pub fn constrain_pattern( let opaque_type = Type::Alias { symbol: *opaque, - type_arguments: type_arguments.clone(), + type_arguments: type_arguments + .iter() + .map(|v| OptAbleType { + typ: Type::Variable(v.var), + opt_ability: v.opt_ability, + }) + .collect(), lambda_set_variables: lambda_set_variables.clone(), actual: Box::new(arg_pattern_type.clone()), kind: AliasKind::Opaque, @@ -567,9 +578,7 @@ pub fn constrain_pattern( .vars .extend_from_slice(&[*arg_pattern_var, *whole_var]); // Also add the fresh variables we created for the type argument and lambda sets - state.vars.extend(type_arguments.iter().map(|(_, t)| { - t.expect_variable("all type arguments should be fresh variables here") - })); + state.vars.extend(type_arguments.iter().map(|v| v.var)); state.vars.extend(lambda_set_variables.iter().map(|v| { v.0.expect_variable("all lambda sets should be fresh variables here") })); diff --git a/compiler/debug_flags/Cargo.toml b/compiler/debug_flags/Cargo.toml new file mode 100644 index 0000000000..8aecfb22ec --- /dev/null +++ b/compiler/debug_flags/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "roc_debug_flags" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/compiler/debug_flags/src/lib.rs b/compiler/debug_flags/src/lib.rs new file mode 100644 index 0000000000..5af06aff4c --- /dev/null +++ b/compiler/debug_flags/src/lib.rs @@ -0,0 +1,111 @@ +//! Flags for debugging the Roc compiler. +//! +//! Lists environment variable flags that can be enabled for verbose debugging features in debug +//! builds of the compiler. +//! +//! For example, I might define the following alias to run cargo with all unifications and +//! expanded type aliases printed: +//! +//! ```bash +//! alias cargo="\ +//! ROC_PRINT_UNIFICATIONS=1 \ +//! ROC_PRETTY_PRINT_ALIAS_CONTENTS=1 \ +//! cargo" +//! ``` +//! +//! More generally, I have the following: +//! +//! ```bash +//! alias cargo="\ +//! ROC_PRETTY_PRINT_ALIAS_CONTENTS=0 \ +//! ROC_PRINT_UNIFICATIONS=0 \ +//! ROC_PRINT_MISMATCHES=0 \ +//! ROC_PRINT_IR_AFTER_SPECIALIZATION=0 \ +//! ROC_PRINT_IR_AFTER_RESET_REUSE=0 \ +//! ROC_PRINT_IR_AFTER_REFCOUNT=0 \ +//! ROC_PRETTY_PRINT_IR_SYMBOLS=0 \ +//! # ...other flags +//! cargo" +//! ``` +//! +//! Now you can turn debug flags on and off as you like. +//! +//! These flags are also set in .cargo/config found at the repository root. You can modify them +//! there to avoid maintaining a separate script. + +#[macro_export] +macro_rules! dbg_do { + ($flag:path, $expr:expr) => { + #[cfg(debug_assertions)] + { + let flag = std::env::var($flag); + if !flag.is_err() && flag.as_deref() != Ok("0") { + $expr + } + } + }; +} + +macro_rules! flags { + ($($(#[doc = $doc:expr])+ $flag:ident)*) => {$( + $(#[doc = $doc])+ + pub static $flag: &str = stringify!($flag); + )*}; +} + +flags! { + // ===Types=== + + /// Expands the contents of aliases during pretty-printing of types. + ROC_PRETTY_PRINT_ALIAS_CONTENTS + + // ===Solve=== + + /// Prints type unifications, before and after they happen. + ROC_PRINT_UNIFICATIONS + + /// Prints all type mismatches hit during type unification. + ROC_PRINT_MISMATCHES + + /// Verifies that after let-generalization of a def, any rigid variables in the type annotation + /// of the def are indeed generalized. + /// + /// Note that rigids need not always be generalized in a def. For example, they may be + /// constrained by a type from a lower rank, as `b` is in the following def: + /// + /// F a : { foo : a } + /// foo = \arg -> + /// x : F b + /// x = arg + /// x.foo + /// + /// Instead, this flag is useful for checking that in general, introduction is correct, when + /// chainging how defs are constrained. + ROC_VERIFY_RIGID_LET_GENERALIZED + + // ===Mono=== + + /// Writes a pretty-printed mono IR to stderr after function specialization. + ROC_PRINT_IR_AFTER_SPECIALIZATION + + /// Writes a pretty-printed mono IR to stderr after insertion of reset/reuse + /// instructions. + ROC_PRINT_IR_AFTER_RESET_REUSE + + /// Writes a pretty-printed mono IR to stderr after insertion of refcount + /// instructions. + ROC_PRINT_IR_AFTER_REFCOUNT + + /// Prints debug information during the alias analysis pass. + ROC_DEBUG_ALIAS_ANALYSIS + + // ===LLVM Gen=== + + /// Prints LLVM function verification output. + ROC_PRINT_LLVM_FN_VERIFICATION + + // ===Load=== + + /// Print load phases as they complete. + ROC_PRINT_LOAD_LOG +} diff --git a/compiler/exhaustive/src/lib.rs b/compiler/exhaustive/src/lib.rs index 644af3add0..a01e0a7033 100644 --- a/compiler/exhaustive/src/lib.rs +++ b/compiler/exhaustive/src/lib.rs @@ -2,7 +2,10 @@ //! http://moscova.inria.fr/~maranget/papers/warn/warn.pdf use roc_collections::all::{HumanIndex, MutMap}; -use roc_module::ident::{Lowercase, TagIdIntType, TagName}; +use roc_module::{ + ident::{Lowercase, TagIdIntType, TagName}, + symbol::Symbol, +}; use roc_region::all::Region; use roc_std::RocDec; @@ -15,9 +18,9 @@ pub struct Union { } impl Union { - pub fn newtype_wrapper(tag_name: TagName, arity: usize) -> Self { + pub fn newtype_wrapper(name: CtorName, arity: usize) -> Self { let alternatives = vec![Ctor { - name: tag_name, + name, tag_id: TagId(0), arity, }]; @@ -40,9 +43,24 @@ pub enum RenderAs { #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] pub struct TagId(pub TagIdIntType); +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum CtorName { + Tag(TagName), + Opaque(Symbol), +} + +impl CtorName { + pub fn is_tag(&self, tag_name: &TagName) -> bool { + match self { + Self::Tag(test) => test == tag_name, + _ => false, + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Ctor { - pub name: TagName, + pub name: CtorName, pub tag_id: TagId, pub arity: usize, } @@ -54,12 +72,13 @@ pub enum Pattern { Ctor(Union, TagId, std::vec::Vec), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Literal { Int(i128), U128(u128), Bit(bool), Byte(u8), + /// Stores the float bits Float(u64), Decimal(RocDec), Str(Box), @@ -77,14 +96,14 @@ pub enum Error { }, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Context { BadArg, BadDestruct, BadCase, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Guard { HasGuard, NoGuard, diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 8a6184f944..99220a4043 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -468,9 +468,7 @@ impl<'a> Formattable for Tag<'a> { use self::Tag::*; match self { - Global { args, .. } | Private { args, .. } => { - args.iter().any(|arg| (&arg.value).is_multiline()) - } + Apply { args, .. } => args.iter().any(|arg| (&arg.value).is_multiline()), Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => true, Malformed(text) => text.chars().any(|c| c == '\n'), } @@ -486,25 +484,7 @@ impl<'a> Formattable for Tag<'a> { let is_multiline = self.is_multiline(); match self { - Tag::Global { name, args } => { - buf.indent(indent); - buf.push_str(name.value); - if is_multiline { - let arg_indent = indent + INDENT; - - for arg in *args { - buf.newline(); - arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent); - } - } else { - for arg in *args { - buf.spaces(1); - arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); - } - } - } - Tag::Private { name, args } => { - debug_assert!(name.value.starts_with('@')); + Tag::Apply { name, args } => { buf.indent(indent); buf.push_str(name.value); if is_multiline { diff --git a/compiler/fmt/src/collection.rs b/compiler/fmt/src/collection.rs index 9e54b327cf..836ad04210 100644 --- a/compiler/fmt/src/collection.rs +++ b/compiler/fmt/src/collection.rs @@ -2,7 +2,7 @@ use roc_parse::ast::{Collection, ExtractSpaces}; use crate::{ annotation::{Formattable, Newlines}, - spaces::{fmt_comments_only, NewlineAt, INDENT}, + spaces::{count_leading_newlines, fmt_comments_only, NewlineAt, INDENT}, Buf, }; @@ -25,12 +25,27 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( buf.indent(braces_indent); buf.push(start); - for item in items.iter() { + for (index, item) in items.iter().enumerate() { let item = item.extract_spaces(); + let is_first_item = index == 0; buf.newline(); + if !item.before.is_empty() { + let is_only_newlines = item.before.iter().all(|s| s.is_newline()); + + if !is_first_item + && !is_only_newlines + && count_leading_newlines(item.before.iter()) > 1 + { + buf.newline(); + } + fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, item_indent); + + if !is_only_newlines && count_leading_newlines(item.before.iter().rev()) > 0 { + buf.newline(); + } } item.item.format(buf, item_indent); @@ -41,6 +56,11 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( fmt_comments_only(buf, item.after.iter(), NewlineAt::Top, item_indent); } } + + if count_leading_newlines(items.final_comments().iter()) > 1 { + buf.newline(); + } + fmt_comments_only( buf, items.final_comments().iter(), diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index ca99d19110..72f95b0dab 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -191,12 +191,26 @@ pub fn fmt_body<'a, 'buf>( buf.push_str(" ="); if body.is_multiline() { match body { - Expr::SpaceBefore(_, _) => { - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); - } - Expr::Record { .. } | Expr::List { .. } => { - buf.newline(); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + Expr::SpaceBefore(sub_def, spaces) => { + let should_outdent = match sub_def { + Expr::Record { .. } | Expr::List { .. } => { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + is_only_newlines && sub_def.is_multiline() + } + _ => false, + }; + + if should_outdent { + buf.spaces(1); + sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } else { + body.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + indent + INDENT, + ); + } } _ => { buf.spaces(1); diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index b54ff826b2..7e038bc148 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -2,7 +2,7 @@ use crate::annotation::{Formattable, Newlines, Parens}; use crate::collection::fmt_collection; use crate::def::fmt_def; use crate::pattern::fmt_pattern; -use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}; +use crate::spaces::{count_leading_newlines, fmt_comments_only, fmt_spaces, NewlineAt, INDENT}; use crate::Buf; use roc_module::called_via::{self, BinOp}; use roc_parse::ast::{ @@ -37,8 +37,7 @@ impl<'a> Formattable for Expr<'a> { | Underscore { .. } | MalformedIdent(_, _) | MalformedClosure - | GlobalTag(_) - | PrivateTag(_) + | Tag(_) | OpaqueRef(_) => false, // These expressions always have newlines @@ -118,25 +117,16 @@ impl<'a> Formattable for Expr<'a> { use self::Expr::*; //dbg!(self); - let format_newlines = newlines == Newlines::Yes; let apply_needs_parens = parens == Parens::InApply; match self { SpaceBefore(sub_expr, spaces) => { - if format_newlines { - fmt_spaces(buf, spaces.iter(), indent); - } else { - fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - } + format_spaces(buf, spaces, newlines, indent); sub_expr.format_with_options(buf, parens, newlines, indent); } SpaceAfter(sub_expr, spaces) => { sub_expr.format_with_options(buf, parens, newlines, indent); - if format_newlines { - fmt_spaces(buf, spaces.iter(), indent); - } else { - fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - } + format_spaces(buf, spaces, newlines, indent); } ParensAround(sub_expr) => { if parens == Parens::NotNeeded && !sub_expr_requests_parens(sub_expr) { @@ -187,13 +177,73 @@ impl<'a> Formattable for Expr<'a> { let multiline_args = loc_args.iter().any(|loc_arg| loc_arg.is_multiline()); - if multiline_args { + let mut found_multiline_expr = false; + let mut iter = loc_args.iter().peekable(); + + while let Some(loc_arg) = iter.next() { + if iter.peek().is_none() { + found_multiline_expr = match loc_arg.value { + SpaceBefore(sub_expr, spaces) => match sub_expr { + Record { .. } | List { .. } => { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + is_only_newlines + && !found_multiline_expr + && sub_expr.is_multiline() + } + _ => false, + }, + Record { .. } | List { .. } | Closure { .. } => { + !found_multiline_expr && loc_arg.is_multiline() + } + _ => false, + } + } else { + found_multiline_expr = loc_arg.is_multiline(); + } + } + + let should_outdent_last_arg = found_multiline_expr; + + if multiline_args && !should_outdent_last_arg { let arg_indent = indent + INDENT; for loc_arg in loc_args.iter() { buf.newline(); loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent); } + } else if multiline_args && should_outdent_last_arg { + let mut iter = loc_args.iter().peekable(); + while let Some(loc_arg) = iter.next() { + buf.spaces(1); + + if iter.peek().is_none() { + match loc_arg.value { + SpaceBefore(sub_expr, _) => { + sub_expr.format_with_options( + buf, + Parens::InApply, + Newlines::Yes, + indent, + ); + } + _ => { + loc_arg.format_with_options( + buf, + Parens::InApply, + Newlines::Yes, + indent, + ); + } + } + } else { + loc_arg.format_with_options( + buf, + Parens::InApply, + Newlines::Yes, + indent, + ); + } + } } else { for loc_arg in loc_args.iter() { buf.spaces(1); @@ -213,7 +263,7 @@ impl<'a> Formattable for Expr<'a> { buf.indent(indent); buf.push_str(string); } - GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => { + Tag(string) | OpaqueRef(string) => { buf.indent(indent); buf.push_str(string) } @@ -262,15 +312,39 @@ impl<'a> Formattable for Expr<'a> { fmt_def(buf, &loc_def.value, indent); } - let empty_line_before_return = empty_line_before_expr(&ret.value); + match &ret.value { + SpaceBefore(sub_expr, spaces) => { + let empty_line_before_return = empty_line_before_expr(&ret.value); + let has_inline_comment = with_inline_comment(&ret.value); - if !empty_line_before_return { - buf.newline(); + if has_inline_comment { + buf.spaces(1); + format_spaces(buf, spaces, newlines, indent); + + if !empty_line_before_return { + buf.newline(); + } + + sub_expr.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + indent, + ); + } else { + if !empty_line_before_return { + buf.newline(); + } + + ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } + } + _ => { + // Even if there were no defs, which theoretically should never happen, + // still print the return value. + ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } } - - // Even if there were no defs, which theoretically should never happen, - // still print the return value. - ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); } Expect(condition, continuation) => { fmt_expect(buf, condition, continuation, self.is_multiline(), indent); @@ -372,7 +446,6 @@ fn push_op(buf: &mut Buf, op: BinOp) { called_via::BinOp::Slash => buf.push('/'), called_via::BinOp::DoubleSlash => buf.push_str("//"), called_via::BinOp::Percent => buf.push('%'), - called_via::BinOp::DoublePercent => buf.push_str("%%"), called_via::BinOp::Plus => buf.push('+'), called_via::BinOp::Minus => buf.push('-'), called_via::BinOp::Equals => buf.push_str("=="), @@ -483,6 +556,34 @@ fn fmt_bin_ops<'a, 'buf>( loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, next_indent); } +fn format_spaces<'a, 'buf>( + buf: &mut Buf<'buf>, + spaces: &[CommentOrNewline<'a>], + newlines: Newlines, + indent: u16, +) { + let format_newlines = newlines == Newlines::Yes; + + if format_newlines { + fmt_spaces(buf, spaces.iter(), indent); + } else { + fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); + } +} + +fn with_inline_comment<'a>(expr: &'a Expr<'a>) -> bool { + use roc_parse::ast::Expr::*; + + match expr { + SpaceBefore(_, spaces) => match spaces.iter().next() { + Some(CommentOrNewline::LineComment(_)) => true, + Some(_) => false, + None => false, + }, + _ => false, + } +} + fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { use roc_parse::ast::Expr::*; @@ -847,7 +948,34 @@ fn fmt_closure<'a, 'buf>( } }; - loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + if is_multiline { + match &loc_ret.value { + SpaceBefore(sub_expr, spaces) => { + let should_outdent = match sub_expr { + Record { .. } | List { .. } => { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + is_only_newlines && sub_expr.is_multiline() + } + _ => false, + }; + + if should_outdent { + buf.spaces(1); + sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } else { + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + } + } + Record { .. } | List { .. } => { + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } + _ => { + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + } + } + } else { + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + } } fn fmt_backpassing<'a, 'buf>( @@ -977,16 +1105,38 @@ fn fmt_record<'a, 'buf>( if is_multiline { let field_indent = indent + INDENT; - for field in loc_fields.iter() { + for (index, field) in loc_fields.iter().enumerate() { // comma addition is handled by the `format_field_multiline` function // since we can have stuff like: // { x # comment // , y // } // In this case, we have to move the comma before the comment. + + let is_first_item = index == 0; + if let AssignedField::SpaceBefore(_sub_field, spaces) = &field.value { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + if !is_first_item + && !is_only_newlines + && count_leading_newlines(spaces.iter()) > 1 + { + buf.newline(); + } + + fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, field_indent); + + if !is_only_newlines && count_leading_newlines(spaces.iter().rev()) > 0 { + buf.newline(); + } + } + format_field_multiline(buf, &field.value, field_indent, ""); } + if count_leading_newlines(final_comments.iter()) > 1 { + buf.newline(); + } + fmt_comments_only(buf, final_comments.iter(), NewlineAt::Top, field_indent); buf.newline(); @@ -1061,7 +1211,7 @@ fn format_field_multiline<'a, 'buf, T>( buf.push_str(name.value); buf.push(','); } - AssignedField::SpaceBefore(sub_field, spaces) => { + AssignedField::SpaceBefore(sub_field, _spaces) => { // We have something like that: // ``` // # comment @@ -1069,7 +1219,6 @@ fn format_field_multiline<'a, 'buf, T>( // ``` // we'd like to preserve this - fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent); format_field_multiline(buf, sub_field, indent, separator_prefix); } AssignedField::SpaceAfter(sub_field, spaces) => { @@ -1104,7 +1253,6 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool { | BinOp::Slash | BinOp::DoubleSlash | BinOp::Percent - | BinOp::DoublePercent | BinOp::Plus | BinOp::Minus | BinOp::Equals diff --git a/compiler/fmt/src/lib.rs b/compiler/fmt/src/lib.rs index c7e8b6ca7a..7ba58ddca8 100644 --- a/compiler/fmt/src/lib.rs +++ b/compiler/fmt/src/lib.rs @@ -10,6 +10,14 @@ pub mod pattern; pub mod spaces; use bumpalo::{collections::String, Bump}; +use roc_parse::ast::{Def, Module}; +use roc_region::all::Loc; + +#[derive(Debug, PartialEq)] +pub struct Ast<'a> { + pub module: Module<'a>, + pub defs: bumpalo::collections::vec::Vec<'a, Loc>>, +} #[derive(Debug)] pub struct Buf<'a> { diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index ba698dd2f8..9c6dc8bd56 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -11,7 +11,7 @@ use roc_parse::header::{ use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; -pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) { +pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) { match module { Module::Interface { header } => { fmt_interface_header(buf, header); diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index c903ad9596..e4f53fc47c 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -28,8 +28,7 @@ impl<'a> Formattable for Pattern<'a> { Pattern::OptionalField(_, expr) => expr.is_multiline(), Pattern::Identifier(_) - | Pattern::GlobalTag(_) - | Pattern::PrivateTag(_) + | Pattern::Tag(_) | Pattern::OpaqueRef(_) | Pattern::Apply(_, _) | Pattern::NumLiteral(..) @@ -58,7 +57,7 @@ impl<'a> Formattable for Pattern<'a> { buf.indent(indent); buf.push_str(string) } - GlobalTag(name) | PrivateTag(name) | OpaqueRef(name) => { + Tag(name) | OpaqueRef(name) => { buf.indent(indent); buf.push_str(name); } diff --git a/compiler/fmt/src/spaces.rs b/compiler/fmt/src/spaces.rs index 4cb4f6fb8a..116ed718d4 100644 --- a/compiler/fmt/src/spaces.rs +++ b/compiler/fmt/src/spaces.rs @@ -1,6 +1,21 @@ -use roc_parse::ast::CommentOrNewline; +use bumpalo::collections::vec::Vec; +use bumpalo::Bump; +use roc_module::called_via::{BinOp, UnaryOp}; +use roc_parse::{ + ast::{ + AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Expr, Has, HasClause, + Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, + ValueDef, WhenBranch, + }, + header::{ + AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, + PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, + }, + ident::UppercaseIdent, +}; +use roc_region::all::{Loc, Region}; -use crate::Buf; +use crate::{Ast, Buf}; /// The number of spaces to indent. pub const INDENT: u16 = 4; @@ -117,6 +132,31 @@ fn fmt_comment<'buf>(buf: &mut Buf<'buf>, comment: &str) { buf.push_str(comment.trim_end()); } +pub fn count_leading_newlines<'a, I>(data: I) -> u16 +where + I: Iterator>, +{ + let mut count = 0; + let mut allow_counting = false; + + for (index, val) in data.enumerate() { + let is_first = index == 0; + let is_newline = matches!(val, CommentOrNewline::Newline); + + if is_first && is_newline { + allow_counting = true + } + + if is_newline && allow_counting { + count += 1; + } else { + break; + } + } + + count +} + fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) { buf.push_str("##"); if !docs.starts_with(' ') { @@ -124,3 +164,575 @@ fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) { } buf.push_str(docs); } + +/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting. +/// +/// Currently this consists of: +/// * Removing newlines +/// * Removing comments +/// * Removing parens in Exprs +/// +/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting) +/// - but there are currently several bugs where they're _not_ preserved. +/// TODO: ensure formatting retains comments +pub trait RemoveSpaces<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self; +} + +impl<'a> RemoveSpaces<'a> for Ast<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + Ast { + module: self.module.remove_spaces(arena), + defs: { + let mut defs = Vec::with_capacity_in(self.defs.len(), arena); + for d in &self.defs { + defs.push(d.remove_spaces(arena)) + } + defs + }, + } + } +} + +impl<'a> RemoveSpaces<'a> for Module<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match self { + Module::Interface { header } => Module::Interface { + header: InterfaceHeader { + name: header.name.remove_spaces(arena), + exposes: header.exposes.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + before_header: &[], + after_interface_keyword: &[], + before_exposes: &[], + after_exposes: &[], + before_imports: &[], + after_imports: &[], + }, + }, + Module::App { header } => Module::App { + header: AppHeader { + name: header.name.remove_spaces(arena), + packages: header.packages.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + provides: header.provides.remove_spaces(arena), + provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)), + to: header.to.remove_spaces(arena), + before_header: &[], + after_app_keyword: &[], + before_packages: &[], + after_packages: &[], + before_imports: &[], + after_imports: &[], + before_provides: &[], + after_provides: &[], + before_to: &[], + after_to: &[], + }, + }, + Module::Platform { header } => Module::Platform { + header: PlatformHeader { + name: header.name.remove_spaces(arena), + requires: header.requires.remove_spaces(arena), + exposes: header.exposes.remove_spaces(arena), + packages: header.packages.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + provides: header.provides.remove_spaces(arena), + before_header: &[], + after_platform_keyword: &[], + before_requires: &[], + after_requires: &[], + before_exposes: &[], + after_exposes: &[], + before_packages: &[], + after_packages: &[], + before_imports: &[], + after_imports: &[], + before_provides: &[], + after_provides: &[], + }, + }, + Module::Hosted { header } => Module::Hosted { + header: HostedHeader { + name: header.name.remove_spaces(arena), + exposes: header.exposes.remove_spaces(arena), + imports: header.imports.remove_spaces(arena), + generates: header.generates.remove_spaces(arena), + generates_with: header.generates_with.remove_spaces(arena), + before_header: &[], + after_hosted_keyword: &[], + before_exposes: &[], + after_exposes: &[], + before_imports: &[], + after_imports: &[], + before_generates: &[], + after_generates: &[], + before_with: &[], + after_with: &[], + }, + }, + } + } +} + +impl<'a> RemoveSpaces<'a> for &'a str { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + self + } +} + +impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)), + Spaced::SpaceBefore(a, _) => a.remove_spaces(arena), + Spaced::SpaceAfter(a, _) => a.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for ExposedName<'a> { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + *self + } +} + +impl<'a> RemoveSpaces<'a> for ModuleName<'a> { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + *self + } +} + +impl<'a> RemoveSpaces<'a> for PackageName<'a> { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + *self + } +} + +impl<'a> RemoveSpaces<'a> for To<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + To::ExistingPackage(a) => To::ExistingPackage(a), + To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)), + } + } +} + +impl<'a> RemoveSpaces<'a> for TypedIdent<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + TypedIdent { + ident: self.ident.remove_spaces(arena), + spaces_before_colon: &[], + ann: self.ann.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + PlatformRequires { + rigids: self.rigids.remove_spaces(arena), + signature: self.signature.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + *self + } +} + +impl<'a> RemoveSpaces<'a> for PackageEntry<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + PackageEntry { + shorthand: self.shorthand, + spaces_after_shorthand: &[], + package_name: self.package_name.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)), + ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)), + } + } +} + +impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + self.as_ref().map(|a| a.remove_spaces(arena)) + } +} + +impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + let res = self.value.remove_spaces(arena); + Loc::at(Region::zero(), res) + } +} + +impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + (self.0.remove_spaces(arena), self.1.remove_spaces(arena)) + } +} + +impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + let mut items = Vec::with_capacity_in(self.items.len(), arena); + for item in self.items { + items.push(item.remove_spaces(arena)); + } + Collection::with_items(items.into_bump_slice()) + } +} + +impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + let mut items = Vec::with_capacity_in(self.len(), arena); + for item in *self { + let res = item.remove_spaces(arena); + items.push(res); + } + items.into_bump_slice() + } +} + +impl<'a> RemoveSpaces<'a> for UnaryOp { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + *self + } +} + +impl<'a> RemoveSpaces<'a> for BinOp { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + *self + } +} + +impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + arena.alloc((*self).remove_spaces(arena)) + } +} + +impl<'a> RemoveSpaces<'a> for TypeDef<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + use TypeDef::*; + + match *self { + Alias { + header: TypeHeader { name, vars }, + ann, + } => Alias { + header: TypeHeader { + name: name.remove_spaces(arena), + vars: vars.remove_spaces(arena), + }, + ann: ann.remove_spaces(arena), + }, + Opaque { + header: TypeHeader { name, vars }, + typ, + } => Opaque { + header: TypeHeader { + name: name.remove_spaces(arena), + vars: vars.remove_spaces(arena), + }, + typ: typ.remove_spaces(arena), + }, + Ability { + header: TypeHeader { name, vars }, + loc_has, + members, + } => Ability { + header: TypeHeader { + name: name.remove_spaces(arena), + vars: vars.remove_spaces(arena), + }, + loc_has: loc_has.remove_spaces(arena), + members: members.remove_spaces(arena), + }, + } + } +} + +impl<'a> RemoveSpaces<'a> for ValueDef<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + use ValueDef::*; + + match *self { + Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)), + Body(a, b) => Body( + arena.alloc(a.remove_spaces(arena)), + arena.alloc(b.remove_spaces(arena)), + ), + AnnotatedBody { + ann_pattern, + ann_type, + comment: _, + body_pattern, + body_expr, + } => AnnotatedBody { + ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)), + ann_type: arena.alloc(ann_type.remove_spaces(arena)), + comment: None, + body_pattern: arena.alloc(body_pattern.remove_spaces(arena)), + body_expr: arena.alloc(body_expr.remove_spaces(arena)), + }, + Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))), + } + } +} + +impl<'a> RemoveSpaces<'a> for Def<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + Def::Type(def) => Def::Type(def.remove_spaces(arena)), + Def::Value(def) => Def::Value(def.remove_spaces(arena)), + Def::NotYetImplemented(a) => Def::NotYetImplemented(a), + Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for Has<'a> { + fn remove_spaces(&self, _arena: &'a Bump) -> Self { + Has::Has + } +} + +impl<'a> RemoveSpaces<'a> for AbilityMember<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + AbilityMember { + name: self.name.remove_spaces(arena), + typ: self.typ.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for WhenBranch<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + WhenBranch { + patterns: self.patterns.remove_spaces(arena), + value: self.value.remove_spaces(arena), + guard: self.guard.remove_spaces(arena), + } + } +} + +impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue( + a.remove_spaces(arena), + arena.alloc([]), + arena.alloc(c.remove_spaces(arena)), + ), + AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue( + a.remove_spaces(arena), + arena.alloc([]), + arena.alloc(c.remove_spaces(arena)), + ), + AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)), + AssignedField::Malformed(a) => AssignedField::Malformed(a), + AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena), + AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for StrLiteral<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t), + StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)), + StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)), + } + } +} + +impl<'a> RemoveSpaces<'a> for StrSegment<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + StrSegment::Plaintext(t) => StrSegment::Plaintext(t), + StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)), + StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c), + StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)), + } + } +} + +impl<'a> RemoveSpaces<'a> for Expr<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + Expr::Float(a) => Expr::Float(a), + Expr::Num(a) => Expr::Num(a), + Expr::NonBase10Int { + string, + base, + is_negative, + } => Expr::NonBase10Int { + string, + base, + is_negative, + }, + Expr::Str(a) => Expr::Str(a.remove_spaces(arena)), + Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b), + Expr::AccessorFunction(a) => Expr::AccessorFunction(a), + Expr::List(a) => Expr::List(a.remove_spaces(arena)), + Expr::RecordUpdate { update, fields } => Expr::RecordUpdate { + update: arena.alloc(update.remove_spaces(arena)), + fields: fields.remove_spaces(arena), + }, + Expr::Record(a) => Expr::Record(a.remove_spaces(arena)), + Expr::Var { module_name, ident } => Expr::Var { module_name, ident }, + Expr::Underscore(a) => Expr::Underscore(a), + Expr::Tag(a) => Expr::Tag(a), + Expr::OpaqueRef(a) => Expr::OpaqueRef(a), + Expr::Closure(a, b) => Expr::Closure( + arena.alloc(a.remove_spaces(arena)), + arena.alloc(b.remove_spaces(arena)), + ), + Expr::Defs(a, b) => { + Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))) + } + Expr::Backpassing(a, b, c) => Expr::Backpassing( + arena.alloc(a.remove_spaces(arena)), + arena.alloc(b.remove_spaces(arena)), + arena.alloc(c.remove_spaces(arena)), + ), + Expr::Expect(a, b) => Expr::Expect( + arena.alloc(a.remove_spaces(arena)), + arena.alloc(b.remove_spaces(arena)), + ), + Expr::Apply(a, b, c) => Expr::Apply( + arena.alloc(a.remove_spaces(arena)), + b.remove_spaces(arena), + c, + ), + Expr::BinOps(a, b) => { + Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))) + } + Expr::UnaryOp(a, b) => { + Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena)) + } + Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))), + Expr::When(a, b) => { + Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena)) + } + Expr::ParensAround(a) => { + // The formatter can remove redundant parentheses, so also remove these when normalizing for comparison. + a.remove_spaces(arena) + } + Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b), + Expr::MalformedClosure => Expr::MalformedClosure, + Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a), + Expr::SpaceBefore(a, _) => a.remove_spaces(arena), + Expr::SpaceAfter(a, _) => a.remove_spaces(arena), + Expr::SingleQuote(a) => Expr::Num(a), + } + } +} + +impl<'a> RemoveSpaces<'a> for Pattern<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + Pattern::Identifier(a) => Pattern::Identifier(a), + Pattern::Tag(a) => Pattern::Tag(a), + Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a), + Pattern::Apply(a, b) => Pattern::Apply( + arena.alloc(a.remove_spaces(arena)), + arena.alloc(b.remove_spaces(arena)), + ), + Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)), + Pattern::RequiredField(a, b) => { + Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena))) + } + Pattern::OptionalField(a, b) => { + Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena))) + } + Pattern::NumLiteral(a) => Pattern::NumLiteral(a), + Pattern::NonBase10Literal { + string, + base, + is_negative, + } => Pattern::NonBase10Literal { + string, + base, + is_negative, + }, + Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a), + Pattern::StrLiteral(a) => Pattern::StrLiteral(a), + Pattern::Underscore(a) => Pattern::Underscore(a), + Pattern::Malformed(a) => Pattern::Malformed(a), + Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b), + Pattern::QualifiedIdentifier { module_name, ident } => { + Pattern::QualifiedIdentifier { module_name, ident } + } + Pattern::SpaceBefore(a, _) => a.remove_spaces(arena), + Pattern::SpaceAfter(a, _) => a.remove_spaces(arena), + Pattern::SingleQuote(a) => Pattern::NumLiteral(a), + } + } +} + +impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + TypeAnnotation::Function(a, b) => TypeAnnotation::Function( + arena.alloc(a.remove_spaces(arena)), + arena.alloc(b.remove_spaces(arena)), + ), + TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)), + TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a), + TypeAnnotation::As(a, _, c) => { + TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c) + } + TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record { + fields: fields.remove_spaces(arena), + ext: ext.remove_spaces(arena), + }, + TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion { + ext: ext.remove_spaces(arena), + tags: tags.remove_spaces(arena), + }, + TypeAnnotation::Inferred => TypeAnnotation::Inferred, + TypeAnnotation::Wildcard => TypeAnnotation::Wildcard, + TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where( + arena.alloc(annot.remove_spaces(arena)), + arena.alloc(has_clauses.remove_spaces(arena)), + ), + TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena), + TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena), + TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a), + } + } +} + +impl<'a> RemoveSpaces<'a> for HasClause<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + HasClause { + var: self.var.remove_spaces(arena), + ability: self.ability.remove_spaces(arena), + } + } +} + +impl<'a> RemoveSpaces<'a> for Tag<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + Tag::Apply { name, args } => Tag::Apply { + name: name.remove_spaces(arena), + args: args.remove_spaces(arena), + }, + Tag::Malformed(a) => Tag::Malformed(a), + Tag::SpaceBefore(a, _) => a.remove_spaces(arena), + Tag::SpaceAfter(a, _) => a.remove_spaces(arena), + } + } +} diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index c5bde106a7..25e9a485f0 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -10,60 +10,153 @@ mod test_fmt { use roc_fmt::def::fmt_def; use roc_fmt::module::fmt_module; use roc_fmt::Buf; + use roc_parse::ast::Module; use roc_parse::module::{self, module_defs}; use roc_parse::parser::Parser; use roc_parse::state::State; use roc_test_utils::assert_multiline_str_eq; // Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same - fn expect_format_expr_helper(input: &str, expected: &str) { + fn expr_formats_to(input: &str, expected: &str) { let arena = Bump::new(); - match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) { + let input = input.trim(); + let expected = expected.trim(); + + match roc_parse::test_helpers::parse_expr_with(&arena, input) { Ok(actual) => { + use roc_fmt::spaces::RemoveSpaces; + let mut buf = Buf::new_in(&arena); actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0); - assert_multiline_str_eq!(expected, buf.as_str()); + let output = buf.as_str(); + + assert_multiline_str_eq!(expected, output); + + let reparsed_ast = roc_parse::test_helpers::parse_expr_with(&arena, output).unwrap_or_else(|err| { + panic!( + "After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n", + err, output + ); + }); + + let ast_normalized = actual.remove_spaces(&arena); + let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena); + + // HACK! + // We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast, + // the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same. + // I don't have the patience to debug this right now, so let's leave it for another day... + // TODO: fix PartialEq impl on ast types + if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) { + panic!( + "Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\ + * * * Source code before formatting:\n{}\n\n\ + * * * Source code after formatting:\n{}\n\n", + input, + output + ); + } + + // Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted + let mut reformatted_buf = Buf::new_in(&arena); + reparsed_ast.format_with_options(&mut reformatted_buf, Parens::NotNeeded, Newlines::Yes, 0); + + if output != reformatted_buf.as_str() { + eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n"); + + assert_multiline_str_eq!(output, reformatted_buf.as_str()); + } } Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error) }; } - fn expr_formats_to(input: &str, expected: &str) { - let input = input.trim_end(); - let expected = expected.trim_end(); - - // First check that input formats to the expected version - expect_format_expr_helper(input, expected); - - // Parse the expected result format it, asserting that it doesn't change - // It's important that formatting be stable / idempotent - expect_format_expr_helper(expected, expected); - } - fn expr_formats_same(input: &str) { expr_formats_to(input, input); } + fn fmt_module_and_defs<'a>( + arena: &Bump, + src: &str, + module: &Module<'a>, + state: State<'a>, + buf: &mut Buf<'_>, + ) { + fmt_module(buf, module); + + match module_defs().parse(&arena, state) { + Ok((_, loc_defs, _)) => { + for loc_def in loc_defs { + fmt_def(buf, arena.alloc(loc_def.value), 0); + } + } + Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error) + } + } + // Not intended to be used directly in tests; please use module_formats_to or module_formats_same fn expect_format_module_helper(src: &str, expected: &str) { let arena = Bump::new(); + let src = src.trim(); + let expected = expected.trim(); + match module::parse_header(&arena, State::new(src.as_bytes())) { Ok((actual, state)) => { + use roc_fmt::spaces::RemoveSpaces; + let mut buf = Buf::new_in(&arena); - fmt_module(&mut buf, &actual); + fmt_module_and_defs(&arena, src, &actual, state, &mut buf); - match module_defs().parse(&arena, state) { - Ok((_, loc_defs, _)) => { - for loc_def in loc_defs { - fmt_def(&mut buf, arena.alloc(loc_def.value), 0); - } - } - Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error) + let output = buf.as_str().trim(); + + let (reparsed_ast, state) = module::parse_header(&arena, State::new(output.as_bytes())).unwrap_or_else(|err| { + panic!( + "After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n", + err, output + ); + }); + + let ast_normalized = actual.remove_spaces(&arena); + let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena); + + // HACK! + // We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast, + // the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same. + // I don't have the patience to debug this right now, so let's leave it for another day... + // TODO: fix PartialEq impl on ast types + if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) { + panic!( + "Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\ + * * * Source code before formatting:\n{}\n\n\ + * * * Source code after formatting:\n{}\n\n", + src, + output + ); } - assert_multiline_str_eq!(expected, buf.as_str()) + + // Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted + let mut reformatted_buf = Buf::new_in(&arena); + + fmt_module_and_defs(&arena, output, &reparsed_ast, state, &mut reformatted_buf); + + let reformatted = reformatted_buf.as_str().trim(); + + if output != reformatted { + eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n"); + + assert_multiline_str_eq!(output, reformatted); + } + + // If everything was idempotent re-parsing worked, finally assert + // that the formatted code was what we expected it to be. + // + // Do this last because if there were any serious problems with the + // formatter (e.g. it wasn't idempotent), we want to know about + // those more than we want to know that the expectation failed! + assert_multiline_str_eq!(expected, output); } Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error) }; @@ -106,26 +199,61 @@ mod test_fmt { } #[test] - #[ignore] - fn def_with_comment_on_same_line() { - // TODO(joshuawarner32): make trailing comments format stabily - // This test currently fails because the comment ends up as SpaceBefore for the following `a` - // This works fine when formatted _once_ - but if you format again, the formatter wants to - // insert a newline between `a = "Hello"` and the comment, further muddying the waters. - // Clearly the formatter shouldn't be allowed to migrate a comment around like that. + fn def_with_inline_comment() { + expr_formats_same(indoc!( + r#" + x = 0 # comment + + x + "# + )); + expr_formats_to( indoc!( r#" - a = "Hello" # This variable is for greeting + x = 0# comment - a + x "# ), indoc!( r#" - a = "Hello" - # This variable is for greeting - a + x = 0 # comment + + x + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + x = 0# comment + x + "# + ), + indoc!( + r#" + x = 0 # comment + + x + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + x = 0 # comment + + x + "# + ), + indoc!( + r#" + x = 0 # comment + + x "# ), ); @@ -157,6 +285,714 @@ mod test_fmt { ); } + #[test] + fn type_annotation_allow_blank_line_before_and_after_comment() { + expr_formats_same(indoc!( + r#" + person : + { + firstName : Str, + # comment + lastName : Str, + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person : + { + firstName : Str, + + # comment + lastName : Str, + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person : + { + firstName : Str, + # comment + + lastName : Str, + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person : + { + firstName : Str, + + # comment + + lastName : Str, + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person : + { + firstName : Str, + + # comment 1 + + lastName : Str, + + # comment 2 + # comment 3 + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person : + { + firstName : Str, + + # comment 1 + + lastName : Str, + # comment 2 + # comment 3 + } + + person + "# + )); + + expr_formats_to( + indoc!( + r#" + person : + { + + # comment + + firstName : Str, + lastName : Str, + } + + person + "# + ), + indoc!( + r#" + person : + { + # comment + + firstName : Str, + lastName : Str, + } + + person + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + person : + { + firstName : Str, + lastName : Str, + + # comment + + } + + person + "# + ), + indoc!( + r#" + person : + { + firstName : Str, + lastName : Str, + + # comment + } + + person + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + person : + { + firstName : Str, + + + # comment + lastName : Str, + } + + person + "# + ), + indoc!( + r#" + person : + { + firstName : Str, + + # comment + lastName : Str, + } + + person + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + person : + { + firstName : Str, + # comment + + + lastName : Str, + } + + person + "# + ), + indoc!( + r#" + person : + { + firstName : Str, + # comment + + lastName : Str, + } + + person + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + person : + { + firstName : Str, + + + # comment + + + lastName : Str, + } + + person + "# + ), + indoc!( + r#" + person : + { + firstName : Str, + + # comment + + lastName : Str, + } + + person + "# + ), + ); + } + + #[test] + fn record_allow_blank_line_before_and_after_comment() { + expr_formats_same(indoc!( + r#" + person = { + firstName: "first", + # comment 1 + lastName: "last", + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person = { + firstName: "first", + # comment 1 + + lastName: "last", + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person = { + firstName: "first", + + # comment 1 + lastName: "last", + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person = { + firstName: "first", + + # comment 1 + + lastName: "last", + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person = { + firstName: "first", + + # comment 1 + + lastName: "last", + + # comment 2 + # comment 3 + } + + person + "# + )); + + expr_formats_same(indoc!( + r#" + person = { + firstName: "first", + + # comment 1 + + lastName: "last", + # comment 2 + # comment 3 + } + + person + "# + )); + + expr_formats_to( + indoc!( + r#" + person = { + + # comment + + firstName: "first", + lastName: "last", + } + + person + "# + ), + indoc!( + r#" + person = { + # comment + + firstName: "first", + lastName: "last", + } + + person + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + person = { + firstName: "first", + lastName: "last", + + # comment + + } + + person + "# + ), + indoc!( + r#" + person = { + firstName: "first", + lastName: "last", + + # comment + } + + person + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + person = { + firstName: "first", + + + # comment 1 + lastName: "last", + } + + person + "# + ), + indoc!( + r#" + person = { + firstName: "first", + + # comment 1 + lastName: "last", + } + + person + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + person = { + firstName: "first", + # comment 1 + + + lastName: "last", + } + + person + "# + ), + indoc!( + r#" + person = { + firstName: "first", + # comment 1 + + lastName: "last", + } + + person + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + person = { + firstName: "first", + + + # comment 1 + + + lastName: "last", + } + + person + "# + ), + indoc!( + r#" + person = { + firstName: "first", + + # comment 1 + + lastName: "last", + } + + person + "# + ), + ); + } + + #[test] + fn list_allow_blank_line_before_and_after_comment() { + expr_formats_same(indoc!( + r#" + list = [ + 0, + # comment + 1, + ] + + list + "# + )); + + expr_formats_same(indoc!( + r#" + list = [ + 0, + + # comment + 1, + ] + + list + "# + )); + + expr_formats_same(indoc!( + r#" + list = [ + 0, + # comment + + 1, + ] + + list + "# + )); + + expr_formats_same(indoc!( + r#" + list = [ + 0, + + # comment + + 1, + ] + + list + "# + )); + + expr_formats_same(indoc!( + r#" + list = [ + 0, + + # comment 1 + + 1, + + # comment 2 + # comment 3 + ] + + list + "# + )); + + expr_formats_same(indoc!( + r#" + list = [ + 0, + + # comment 1 + + 1, + # comment 2 + # comment 3 + ] + + list + "# + )); + expr_formats_to( + indoc!( + r#" + list = [ + + # comment + + 0, + 1, + ] + + list + "# + ), + indoc!( + r#" + list = [ + # comment + + 0, + 1, + ] + + list + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + list = [ + 0, + 1, + + # comment + + ] + + list + "# + ), + indoc!( + r#" + list = [ + 0, + 1, + + # comment + ] + + list + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + list = [ + 0, + + + # comment + 1, + ] + + list + "# + ), + indoc!( + r#" + list = [ + 0, + + # comment + 1, + ] + + list + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + list = [ + 0, + # comment + + + 1, + ] + + list + "# + ), + indoc!( + r#" + list = [ + 0, + # comment + + 1, + ] + + list + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + list = [ + 0, + + + # comment + + + 1, + ] + + list + "# + ), + indoc!( + r#" + list = [ + 0, + + # comment + + 1, + ] + + list + "# + ), + ); + } + #[test] fn force_space_at_beginning_of_comment() { expr_formats_to( @@ -626,6 +1462,392 @@ mod test_fmt { )); } + #[test] + fn lambda_returns_record() { + expr_formats_same(indoc!( + r#" + toRecord = \_ -> { + x: 1, + y: 2, + z: 3, + } + + toRecord + "# + )); + + expr_formats_same(indoc!( + r#" + func = \_ -> + { x: 1, y: 2, z: 3 } + + func + "# + )); + + expr_formats_same(indoc!( + r#" + toRecord = \_ -> + val = 0 + + { + x: 1, + y: 2, + z: 3, + } + + toRecord + "# + )); + + expr_formats_to( + indoc!( + r#" + toRecord = \_ -> + { + x: 1, + y: 2, + z: 3, + } + + toRecord + "# + ), + indoc!( + r#" + toRecord = \_ -> { + x: 1, + y: 2, + z: 3, + } + + toRecord + "# + ), + ); + } + + #[test] + fn lambda_returns_list() { + expr_formats_same(indoc!( + r#" + toList = \_ -> [ + 1, + 2, + 3, + ] + + toList + "# + )); + + expr_formats_same(indoc!( + r#" + func = \_ -> + [ 1, 2, 3 ] + + func + "# + )); + + expr_formats_same(indoc!( + r#" + toList = \_ -> + val = 0 + + [ + 1, + 2, + 3, + ] + + toList + "# + )); + + expr_formats_to( + indoc!( + r#" + toList = \_ -> + [ + 1, + 2, + 3, + ] + + toList + "# + ), + indoc!( + r#" + toList = \_ -> [ + 1, + 2, + 3, + ] + + toList + "# + ), + ); + } + + #[test] + fn multiline_list_func_arg() { + expr_formats_same(indoc!( + r#" + result = func arg [ + 1, + 2, + 3, + ] + + result + "# + )); + + expr_formats_to( + indoc!( + r#" + result = func arg + [ 1, 2, 3 ] + + result + "# + ), + indoc!( + r#" + result = func + arg + [ 1, 2, 3 ] + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func arg [ + 1, + 2, + 3, + ] + + result + "# + ), + indoc!( + r#" + result = func arg [ + 1, + 2, + 3, + ] + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func [ + 1, + 2, + 3, + ] + arg + + result + "# + ), + indoc!( + r#" + result = func + [ + 1, + 2, + 3, + ] + arg + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func arg + [ + 1, + 2, + 3, + ] + + result + "# + ), + indoc!( + r#" + result = func arg [ + 1, + 2, + 3, + ] + + result + "# + ), + ); + + expr_formats_same(indoc!( + r#" + result = func + arg + [ + 1, + 2, + 3, + ] + + result + "# + )); + } + + #[test] + fn multiline_record_func_arg() { + expr_formats_same(indoc!( + r#" + result = func arg { + x: 1, + y: 2, + z: 3, + } + + result + "# + )); + + expr_formats_to( + indoc!( + r#" + result = func arg + { x: 1, y: 2, z: 3 } + + result + "# + ), + indoc!( + r#" + result = func + arg + { x: 1, y: 2, z: 3 } + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func arg { + x: 1, + y: 2, + z: 3, + } + + result + "# + ), + indoc!( + r#" + result = func arg { + x: 1, + y: 2, + z: 3, + } + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func { + x: 1, + y: 2, + z: 3, + } + arg + + result + "# + ), + indoc!( + r#" + result = func + { + x: 1, + y: 2, + z: 3, + } + arg + + result + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + result = func arg + { + x: 1, + y: 2, + z: 3, + } + + result + "# + ), + indoc!( + r#" + result = func arg { + x: 1, + y: 2, + z: 3, + } + + result + "# + ), + ); + + expr_formats_same(indoc!( + r#" + result = func + arg + { + x: 1, + y: 2, + z: 3, + } + + result + "# + )); + } + #[test] fn record_updating() { expr_formats_same(indoc!( @@ -1227,6 +2449,7 @@ mod test_fmt { r#" [ # Thirty Seven + 37, # Thirty Eight 38, @@ -1301,6 +2524,27 @@ mod test_fmt { fn multi_line_list_def() { expr_formats_same(indoc!( r#" + l = [ + 1, + 2, + ] + + l + "# + )); + + expr_formats_same(indoc!( + r#" + l = + [ 1, 2 ] + + l + "# + )); + + expr_formats_to( + indoc!( + r#" l = [ 1, @@ -1308,8 +2552,19 @@ mod test_fmt { ] l - "# - )); + "# + ), + indoc!( + r#" + l = [ + 1, + 2, + ] + + l + "# + ), + ); expr_formats_to( indoc!( @@ -1324,11 +2579,10 @@ mod test_fmt { ), indoc!( r#" - results = - [ - Ok 4, - Ok 5, - ] + results = [ + Ok 4, + Ok 5, + ] allOks results "# @@ -1417,18 +2671,69 @@ mod test_fmt { #[test] fn multi_line_record_def() { + expr_formats_same(indoc!( + r#" + pos = { + x: 4, + y: 11, + z: 16, + } + + pos + "# + )); + expr_formats_same(indoc!( r#" pos = + { x: 4, y: 11, z: 16 } + + pos + "# + )); + + expr_formats_same(indoc!( + r#" + myDef = + list = [ + a, + b, + ] + { + c, + d, + } + + myDef + "# + )); + + expr_formats_to( + indoc!( + r#" + pos = + { + x: 4, + y: 11, + z: 16, + } + + pos + "# + ), + indoc!( + r#" + pos = { x: 4, y: 11, z: 16, } - pos - "# - )); + pos + "# + ), + ); expr_formats_to( indoc!( @@ -1443,11 +2748,10 @@ mod test_fmt { ), indoc!( r#" - pos = - { - x: 5, - y: 10, - } + pos = { + x: 5, + y: 10, + } pos "# @@ -2537,7 +3841,7 @@ mod test_fmt { indoc!( r#" 2 % 3 - %% 5 + // 5 + 7 "# ), @@ -2545,7 +3849,7 @@ mod test_fmt { r#" 2 % 3 - %% 5 + // 5 + 7 "# ), @@ -2619,6 +3923,18 @@ mod test_fmt { )); } + #[test] + fn func_call_trailing_multiline_lambda() { + expr_formats_same(indoc!( + r#" + list = List.map [ 1, 2, 3 ] \x -> + x + 1 + + list + "# + )); + } + // MODULES #[test] @@ -2716,6 +4032,39 @@ mod test_fmt { ); } + #[test] + fn format_tui_package_config() { + // At one point this failed to reformat. + module_formats_to( + indoc!( + r#" + platform "tui" + requires { Model } { main : { init : ({} -> Model), update : (Model, Str -> Model), view : (Model -> Str) } } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + + mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } + mainForHost = main + "# + ), + indoc!( + r#" + platform "tui" + requires { Model } { main : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + + mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } + mainForHost = main + "# + ), + ); + } + #[test] fn single_line_hosted() { module_formats_same(indoc!( diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index 0b5a08fe0d..00b08bd467 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -19,7 +19,7 @@ roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_error_macros = { path = "../../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" # TODO: Deal with the update of object to 0.27. # It looks like it breaks linking the generated objects. # Probably just need to specify an extra field that used to be implicit or something. diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 8275f7f4d4..ed1aca5277 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -803,7 +803,7 @@ impl< } } - fn build_num_to_float( + fn build_num_to_frac( &mut self, dst: &Symbol, src: &Symbol, @@ -854,7 +854,7 @@ impl< let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, src_reg); } - (a, r) => todo!("NumToFloat: layout, arg {:?}, ret {:?}", a, r), + (a, r) => todo!("NumToFrac: layout, arg {:?}, ret {:?}", a, r), } } diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 1d6fe2ea2c..cd655880ac 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -502,11 +502,11 @@ trait Backend<'a> { ); self.build_num_lt(sym, &args[0], &args[1], &arg_layouts[0]) } - LowLevel::NumToFloat => { + LowLevel::NumToFrac => { debug_assert_eq!( 1, args.len(), - "NumToFloat: expected to have exactly one argument" + "NumToFrac: expected to have exactly one argument" ); debug_assert!( @@ -514,9 +514,9 @@ trait Backend<'a> { *ret_layout, Layout::Builtin(Builtin::Float(FloatWidth::F32 | FloatWidth::F64)), ), - "NumToFloat: expected to have return layout of type Float" + "NumToFrac: expected to have return layout of type Float" ); - self.build_num_to_float(sym, &args[0], &arg_layouts[0], ret_layout) + self.build_num_to_frac(sym, &args[0], &arg_layouts[0], ret_layout) } LowLevel::NumLte => { debug_assert_eq!( @@ -554,7 +554,7 @@ trait Backend<'a> { } LowLevel::NumRound => self.build_fn_call( sym, - bitcode::NUM_ROUND[FloatWidth::F64].to_string(), + bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(), args, arg_layouts, ret_layout, @@ -691,8 +691,8 @@ trait Backend<'a> { /// build_num_lt stores the result of `src1 < src2` into dst. fn build_num_lt(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>); - /// build_num_to_float convert Number to Float - fn build_num_to_float( + /// build_num_to_frac convert Number to Frac + fn build_num_to_frac( &mut self, dst: &Symbol, src: &Symbol, @@ -913,10 +913,7 @@ trait Backend<'a> { TagName::Closure(sym) => { self.set_last_seen(*sym, stmt); } - TagName::Private(sym) => { - self.set_last_seen(*sym, stmt); - } - TagName::Global(_) => {} + TagName::Tag(_) => {} } for sym in *arguments { self.set_last_seen(*sym, stmt); diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 26335be9ff..5d020a5507 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -15,7 +15,8 @@ roc_error_macros = { path = "../../error_macros" } roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std", default-features = false } +roc_debug_flags = { path = "../debug_flags" } morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } inkwell = { path = "../../vendor/inkwell" } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 6c81a0faeb..18a39ab97e 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -30,6 +30,7 @@ use crate::llvm::refcounting::{ }; use bumpalo::collections::Vec; use bumpalo::Bump; +use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::basic_block::BasicBlock; use inkwell::builder::Builder; use inkwell::context::Context; @@ -40,7 +41,7 @@ use inkwell::memory_buffer::MemoryBuffer; use inkwell::module::{Linkage, Module}; use inkwell::passes::{PassManager, PassManagerBuilder}; use inkwell::types::{ - BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType, + AnyType, BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, StructType, }; use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{ @@ -55,6 +56,7 @@ use morphic_lib::{ use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName}; use roc_builtins::{float_intrinsic, llvm_int_intrinsic}; use roc_collections::all::{ImMap, MutMap, MutSet}; +use roc_debug_flags::{dbg_do, ROC_PRINT_LLVM_FN_VERIFICATION}; use roc_error_macros::internal_error; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -63,16 +65,18 @@ use roc_mono::ir::{ ModifyRc, OptLevel, ProcLayout, }; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; -use roc_target::TargetInfo; +use roc_target::{PtrWidth, TargetInfo}; use target_lexicon::{Architecture, OperatingSystem, Triple}; -/// This is for Inkwell's FunctionValue::verify - we want to know the verification -/// output in debug builds, but we don't want it to print to stdout in release builds! -#[cfg(debug_assertions)] -const PRINT_FN_VERIFICATION_OUTPUT: bool = true; +use super::convert::zig_with_overflow_roc_dec; -#[cfg(not(debug_assertions))] -const PRINT_FN_VERIFICATION_OUTPUT: bool = false; +#[inline(always)] +fn print_fn_verification_output() -> bool { + dbg_do!(ROC_PRINT_LLVM_FN_VERIFICATION, { + return true; + }); + false +} #[macro_export] macro_rules! debug_info_init { @@ -467,7 +471,7 @@ fn add_float_intrinsic<'ctx, F>( if let Some(_) = module.get_function(full_name) { // zig defined this function already } else { - add_intrinsic(module, full_name, construct_type($typ)); + add_intrinsic(ctx, module, full_name, construct_type($typ)); } }; } @@ -492,7 +496,7 @@ fn add_int_intrinsic<'ctx, F>( if let Some(_) = module.get_function(full_name) { // zig defined this function already } else { - add_intrinsic(module, full_name, construct_type($typ)); + add_intrinsic(ctx, module, full_name, construct_type($typ)); } }; } @@ -514,12 +518,10 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { // List of all supported LLVM intrinsics: // // https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics - let f64_type = ctx.f64_type(); let i1_type = ctx.bool_type(); let i8_type = ctx.i8_type(); let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic); let i32_type = ctx.i32_type(); - let i64_type = ctx.i64_type(); let void_type = ctx.void_type(); if let Some(func) = module.get_function("__muloti4") { @@ -527,37 +529,31 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { } add_intrinsic( + ctx, module, LLVM_SETJMP, i32_type.fn_type(&[i8_ptr_type.into()], false), ); - if true { - add_intrinsic( - module, - LLVM_LONGJMP, - void_type.fn_type(&[i8_ptr_type.into()], false), - ); - } else { - add_intrinsic( - module, - LLVM_LONGJMP, - void_type.fn_type(&[i8_ptr_type.into(), i32_type.into()], false), - ); - } + add_intrinsic( + ctx, + module, + LLVM_LONGJMP, + void_type.fn_type(&[i8_ptr_type.into()], false), + ); add_intrinsic( + ctx, module, LLVM_FRAME_ADDRESS, i8_ptr_type.fn_type(&[i32_type.into()], false), ); - add_intrinsic(module, LLVM_STACK_SAVE, i8_ptr_type.fn_type(&[], false)); - add_intrinsic( + ctx, module, - LLVM_LROUND_I64_F64, - i64_type.fn_type(&[f64_type.into()], false), + LLVM_STACK_SAVE, + i8_ptr_type.fn_type(&[], false), ); add_float_intrinsic(ctx, module, &LLVM_LOG, |t| t.fn_type(&[t.into()], false)); @@ -608,12 +604,11 @@ static LLVM_SIN: IntrinsicName = float_intrinsic!("llvm.sin"); static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos"); static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil"); static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor"); +static LLVM_ROUND: IntrinsicName = float_intrinsic!("llvm.round"); static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32"; -static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; -// static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress"; static LLVM_FRAME_ADDRESS: &str = "llvm.frameaddress.p0i8"; static LLVM_STACK_SAVE: &str = "llvm.stacksave"; @@ -631,18 +626,17 @@ const LLVM_ADD_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.sat", " const LLVM_SUB_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat"); fn add_intrinsic<'ctx>( + context: &Context, module: &Module<'ctx>, intrinsic_name: &str, fn_type: FunctionType<'ctx>, ) -> FunctionValue<'ctx> { add_func( + context, module, intrinsic_name, - fn_type, + FunctionSpec::intrinsic(fn_type), Linkage::External, - // LLVM intrinsics always use the C calling convention, because - // they are implemented in C libraries - C_CALL_CONV, ) } @@ -815,20 +809,24 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Str(str_literal) => { - let global = if str_literal.len() < env.small_str_bytes() as usize { + if str_literal.len() < env.small_str_bytes() as usize { match env.small_str_bytes() { - 24 => small_str_ptr_width_8(env, parent, str_literal), - 12 => small_str_ptr_width_4(env, parent, str_literal), + 24 => small_str_ptr_width_8(env, parent, str_literal).into(), + 12 => small_str_ptr_width_4(env, parent, str_literal).into(), _ => unreachable!("incorrect small_str_bytes"), } } else { let ptr = define_global_str_literal_ptr(env, *str_literal); let number_of_elements = env.ptr_int().const_int(str_literal.len() as u64, false); - const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements) - }; + let alloca = + const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements); - global.into() + match env.target_info.ptr_width() { + PtrWidth::Bytes4 => env.builder.build_load(alloca, "load_const_str"), + PtrWidth::Bytes8 => alloca.into(), + } + } } } } @@ -3254,32 +3252,29 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( // let mut argument_types = roc_function.get_type().get_param_types(); let mut argument_types = cc_argument_types; - let c_function_type = match roc_function.get_type().get_return_type() { + match roc_function.get_type().get_return_type() { None => { // this function already returns by-pointer let output_type = roc_function.get_type().get_param_types().pop().unwrap(); argument_types.insert(0, output_type); - - env.context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false) } Some(return_type) => { let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.insert(0, output_type.into()); - - env.context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false) } - }; + } + // This is not actually a function that returns a value but then became + // return-by-pointer do to the calling convention. Instead, here we + // explicitly are forcing the passing of values via the first parameter + // pointer, since they are generic and hence opaque to anything outside roc. + let c_function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types); let c_function = add_func( + env.context, env.module, c_function_name, - c_function_type, + c_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(c_function_name); @@ -3392,20 +3387,18 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( let mut argument_types = cc_argument_types; let return_type = wrapper_return_type; - let c_function_type = { + let c_function_spec = { let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.push(output_type.into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &argument_types), false) + FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types) }; let c_function = add_func( + env.context, env.module, c_function_name, - c_function_type, + c_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(c_function_name); @@ -3470,15 +3463,20 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( builder.build_return(None); // STEP 3: build a {} -> u64 function that gives the size of the return type - let size_function_type = env.context.i64_type().fn_type(&[], false); + let size_function_spec = FunctionSpec::cconv( + env, + CCReturn::Return, + Some(env.context.i64_type().as_basic_type_enum()), + &[], + ); let size_function_name: String = format!("roc__{}_size", ident_string); let size_function = add_func( + env.context, env.module, size_function_name.as_str(), - size_function_type, + size_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(&size_function_name); @@ -3510,14 +3508,14 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>( let cc_return = to_cc_return(env, &return_layout); let roc_return = RocReturn::from_layout(env, &return_layout); - let c_function_type = cc_return.to_signature(env, return_type, argument_types.as_slice()); + let c_function_spec = FunctionSpec::cconv(env, cc_return, Some(return_type), &argument_types); let c_function = add_func( + env.context, env.module, c_function_name, - c_function_type, + c_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(c_function_name); @@ -3628,15 +3626,20 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( ); // STEP 3: build a {} -> u64 function that gives the size of the return type - let size_function_type = env.context.i64_type().fn_type(&[], false); + let size_function_spec = FunctionSpec::cconv( + env, + CCReturn::Return, + Some(env.context.i64_type().as_basic_type_enum()), + &[], + ); let size_function_name: String = format!("roc__{}_size", ident_string); let size_function = add_func( + env.context, env.module, size_function_name.as_str(), - size_function_type, + size_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(&size_function_name); @@ -3662,10 +3665,22 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( } pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { - let type_ = env - .context - .i8_type() - .array_type(5 * env.target_info.ptr_width() as u32); + // The size of jump_buf is platform-dependent. + // - AArch64 needs 3 machine-sized words + // - LLVM says the following about the SJLJ intrinsic: + // + // [It is] a five word buffer in which the calling context is saved. + // The front end places the frame pointer in the first word, and the + // target implementation of this intrinsic should place the destination + // address for a llvm.eh.sjlj.longjmp in the second word. + // The following three words are available for use in a target-specific manner. + // + // So, let's create a 5-word buffer. + let word_type = match env.target_info.ptr_width() { + PtrWidth::Bytes4 => env.context.i32_type(), + PtrWidth::Bytes8 => env.context.i64_type(), + }; + let type_ = word_type.array_type(5); let global = match env.module.get_global("roc_sjlj_buffer") { Some(global) => global, @@ -3677,12 +3692,85 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu env.builder .build_bitcast( global.as_pointer_value(), - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i32_type().ptr_type(AddressSpace::Generic), "cast_sjlj_buffer", ) .into_pointer_value() } +pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { + let jmp_buf = get_sjlj_buffer(env); + if cfg!(target_arch = "aarch64") { + // Due to https://github.com/rtfeldman/roc/issues/2965, we use a setjmp we linked in from Zig + call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_SETJMP) + } else { + // Anywhere else, use the LLVM intrinsic. + // https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp + + let jmp_buf_i8p_arr = env + .builder + .build_bitcast( + jmp_buf, + env.context + .i8_type() + .ptr_type(AddressSpace::Generic) + .array_type(5) + .ptr_type(AddressSpace::Generic), + "jmp_buf [5 x i8*]", + ) + .into_pointer_value(); + + // LLVM asks us to please store the frame pointer in the first word. + let frame_address = env.call_intrinsic( + LLVM_FRAME_ADDRESS, + &[env.context.i32_type().const_zero().into()], + ); + + let zero = env.context.i32_type().const_zero(); + let fa_index = env.context.i32_type().const_zero(); + let fa = unsafe { + env.builder.build_in_bounds_gep( + jmp_buf_i8p_arr, + &[zero, fa_index], + "frame address index", + ) + }; + env.builder.build_store(fa, frame_address); + + // LLVM says that the target implementation of the setjmp intrinsic will put the + // destination address at index 1, and that the remaining three words are for ad-hoc target + // usage. But for whatever reason, on x86, it appears we need a stacksave in those words. + let ss_index = env.context.i32_type().const_int(2, false); + let ss = unsafe { + env.builder + .build_in_bounds_gep(jmp_buf_i8p_arr, &[zero, ss_index], "name") + }; + let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]); + env.builder.build_store(ss, stack_save); + + let jmp_buf_i8p = env.builder.build_bitcast( + jmp_buf, + env.context.i8_type().ptr_type(AddressSpace::Generic), + "jmp_buf i8*", + ); + env.call_intrinsic(LLVM_SETJMP, &[jmp_buf_i8p]) + } +} + +/// Pointer to pointer of the panic message. +pub fn get_panic_msg_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> { + let ptr_to_u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let global_name = "roc_panic_msg_ptr"; + let global = env.module.get_global(global_name).unwrap_or_else(|| { + let global = env.module.add_global(ptr_to_u8_ptr, None, global_name); + global.set_initializer(&ptr_to_u8_ptr.const_zero()); + global + }); + + global.as_pointer_value() +} + fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -3701,53 +3789,7 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( let catch_block = context.append_basic_block(parent, "catch_block"); let cont_block = context.append_basic_block(parent, "cont_block"); - let buffer = get_sjlj_buffer(env); - - let cast = env - .builder - .build_bitcast( - buffer, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .array_type(5) - .ptr_type(AddressSpace::Generic), - "to [5 x i8*]", - ) - .into_pointer_value(); - - let zero = env.context.i32_type().const_zero(); - - let index = env.context.i32_type().const_zero(); - let fa = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let index = env.context.i32_type().const_int(2, false); - let ss = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let index = env.context.i32_type().const_int(3, false); - let error_msg = unsafe { - env.builder - .build_in_bounds_gep(cast, &[zero, index], "name") - }; - - let frame_address = env.call_intrinsic( - LLVM_FRAME_ADDRESS, - &[env.context.i32_type().const_zero().into()], - ); - - env.builder.build_store(fa, frame_address); - - let stack_save = env.call_intrinsic(LLVM_STACK_SAVE, &[]); - - env.builder.build_store(ss, stack_save); - - let panicked_u32 = env.call_intrinsic(LLVM_SETJMP, &[buffer.into()]); + let panicked_u32 = build_setjmp_call(env); let panicked_bool = env.builder.build_int_compare( IntPredicate::NE, panicked_u32.into_int_value(), @@ -3777,19 +3819,10 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>( let error_msg = { // u8** - let ptr_int_ptr = builder.build_bitcast( - error_msg, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .ptr_type(AddressSpace::Generic), - "cast", - ); + let ptr_int_ptr = get_panic_msg_ptr(env); // u8* again - let ptr_int = builder.build_load(ptr_int_ptr.into_pointer_value(), "ptr_int"); - - ptr_int + builder.build_load(ptr_int_ptr, "ptr_int") }; let return_value = { @@ -3917,16 +3950,20 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into()); // let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false); - let wrapper_function_type = - wrapper_return_type.fn_type(&function_arguments(env, &argument_types), false); + let wrapper_function_spec = FunctionSpec::cconv( + env, + CCReturn::Return, + Some(wrapper_return_type.as_basic_type_enum()), + &argument_types, + ); // Add main to the module. let wrapper_function = add_func( + env.context, env.module, wrapper_function_name, - wrapper_function_type, + wrapper_function_spec, Linkage::External, - C_CALL_CONV, ); let subprogram = env.new_subprogram(wrapper_function_name); @@ -4155,23 +4192,15 @@ fn build_proc_header<'a, 'ctx, 'env>( arg_basic_types.push(arg_type); } - let fn_type = match RocReturn::from_layout(env, &proc.ret_layout) { - RocReturn::Return => ret_type.fn_type(&function_arguments(env, &arg_basic_types), false), - RocReturn::ByPointer => { - // println!( "{:?} will return void instead of {:?}", symbol, proc.ret_layout); - arg_basic_types.push(ret_type.ptr_type(AddressSpace::Generic).into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &arg_basic_types), false) - } - }; + let roc_return = RocReturn::from_layout(env, &proc.ret_layout); + let fn_spec = FunctionSpec::fastcc(env, roc_return, ret_type, arg_basic_types); let fn_val = add_func( + env.context, env.module, fn_name.as_str(), - fn_type, + fn_spec, Linkage::Internal, - FAST_CALL_CONV, ); let subprogram = env.new_subprogram(&fn_name); @@ -4189,8 +4218,6 @@ fn build_proc_header<'a, 'ctx, 'env>( } if false { - use inkwell::attributes::{Attribute, AttributeLoc}; - let kind_id = Attribute::get_named_enum_kind_id("alwaysinline"); debug_assert!(kind_id > 0); let enum_attr = env.context.create_enum_attribute(kind_id, 1); @@ -4198,8 +4225,6 @@ fn build_proc_header<'a, 'ctx, 'env>( } if false { - use inkwell::attributes::{Attribute, AttributeLoc}; - let kind_id = Attribute::get_named_enum_kind_id("noinline"); debug_assert!(kind_id > 0); let enum_attr = env.context.create_enum_attribute(kind_id, 1); @@ -4253,14 +4278,14 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( alias_symbol.as_str(&env.interns) ); - let function_type = context.void_type().fn_type(&argument_types, false); + let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types); let function_value = add_func( + env.context, env.module, function_name.as_str(), - function_type, + function_spec, Linkage::External, - C_CALL_CONV, ); // STEP 2: build function body @@ -4359,7 +4384,8 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( let builder = env.builder; let context = env.context; - let size_function_type = env.context.i64_type().fn_type(&[], false); + let i64 = env.context.i64_type().as_basic_type_enum(); + let size_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(i64), &[]); let size_function_name: String = if let Some(label) = opt_label { format!( "roc__{}_{}_{}_size", @@ -4376,11 +4402,11 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( }; let size_function = add_func( + env.context, env.module, size_function_name.as_str(), - size_function_type, + size_function_spec, Linkage::External, - C_CALL_CONV, ); let entry = context.append_basic_block(size_function, "entry"); @@ -4495,7 +4521,7 @@ pub fn build_proc<'a, 'ctx, 'env>( } pub fn verify_fn(fn_val: FunctionValue<'_>) { - if !fn_val.verify(PRINT_FN_VERIFICATION_OUTPUT) { + if !fn_val.verify(print_fn_verification_output()) { unsafe { fn_val.delete(); } @@ -5222,6 +5248,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>( argument_layouts, Layout::Builtin(Builtin::Bool), ); + list_find_unsafe(env, layout_ids, roc_function_call, list, element_layout) } _ => unreachable!("invalid list layout"), @@ -5344,7 +5371,17 @@ fn run_low_level<'a, 'ctx, 'env>( let string = load_symbol(scope, &args[0]); - call_bitcode_fn(env, &[string], intrinsic) + let result = call_bitcode_fn(env, &[string], intrinsic); + + // zig passes the result as a packed integer sometimes, instead of a struct. So we cast + let expected_type = basic_type_from_layout(env, layout); + let actual_type = result.get_type(); + + if expected_type != actual_type { + complex_bitcast_check_size(env, result, expected_type, "str_to_num_cast") + } else { + result + } } StrFromInt => { // Str.fromInt : Int -> Str @@ -5644,6 +5681,15 @@ fn run_low_level<'a, 'ctx, 'env>( update_mode, ) } + ListIsUnique => { + // List.isUnique : List a -> Bool + debug_assert_eq!(args.len(), 1); + + let list = load_symbol(scope, &args[0]); + let list = list_to_c_abi(env, list).into(); + + call_bitcode_fn(env, &[list], bitcode::LIST_IS_UNIQUE) + } NumToStr => { // Num.toStr : Num a -> Str debug_assert_eq!(args.len(), 1); @@ -5663,7 +5709,7 @@ fn run_low_level<'a, 'ctx, 'env>( } } NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos - | NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin + | NumCeiling | NumFloor | NumToFrac | NumIsFinite | NumAtan | NumAcos | NumAsin | NumToIntChecked => { debug_assert_eq!(args.len(), 1); @@ -6199,6 +6245,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>( } } +#[derive(Clone, Copy)] enum RocReturn { /// Return as normal Return, @@ -6239,7 +6286,7 @@ impl RocReturn { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum CCReturn { /// Return as normal Return, @@ -6251,32 +6298,113 @@ pub enum CCReturn { Void, } -impl CCReturn { - fn to_signature<'a, 'ctx, 'env>( - &self, +#[derive(Debug, Clone, Copy)] +pub struct FunctionSpec<'ctx> { + /// The function type + pub typ: FunctionType<'ctx>, + call_conv: u32, + + /// Index (0-based) of return-by-pointer parameter, if it exists. + /// We only care about this for C-call-conv functions, because this may take + /// ownership of a register due to the convention. For example, on AArch64, + /// values returned-by-pointer use the x8 register. + /// But for internal functions we don't need to worry about that and we don't + /// want the convention, since it might eat a register and cause a spill! + cconv_sret_parameter: Option, +} + +impl<'ctx> FunctionSpec<'ctx> { + fn attach_attributes(&self, ctx: &Context, fn_val: FunctionValue<'ctx>) { + fn_val.set_call_conventions(self.call_conv); + + if let Some(param_index) = self.cconv_sret_parameter { + // Indicate to LLVM that this argument holds the return value of the function. + let sret_attribute_id = Attribute::get_named_enum_kind_id("sret"); + debug_assert!(sret_attribute_id > 0); + let ret_typ = self.typ.get_param_types()[param_index as usize]; + let sret_attribute = + ctx.create_type_attribute(sret_attribute_id, ret_typ.as_any_type_enum()); + fn_val.add_attribute(AttributeLoc::Param(0), sret_attribute); + } + } + + /// C-calling convention + pub fn cconv<'a, 'env>( env: &Env<'a, 'ctx, 'env>, - return_type: BasicTypeEnum<'ctx>, + cc_return: CCReturn, + return_type: Option>, argument_types: &[BasicTypeEnum<'ctx>], - ) -> FunctionType<'ctx> { - match self { + ) -> FunctionSpec<'ctx> { + let (typ, opt_sret_parameter) = match cc_return { CCReturn::ByPointer => { // turn the output type into a pointer type. Make it the first argument to the function - let output_type = return_type.ptr_type(AddressSpace::Generic); + let output_type = return_type.unwrap().ptr_type(AddressSpace::Generic); + let mut arguments: Vec<'_, BasicTypeEnum> = bumpalo::vec![in env.arena; output_type.into()]; arguments.extend(argument_types); let arguments = function_arguments(env, &arguments); - env.context.void_type().fn_type(&arguments, false) + (env.context.void_type().fn_type(&arguments, false), Some(0)) } CCReturn::Return => { let arguments = function_arguments(env, argument_types); - return_type.fn_type(&arguments, false) + (return_type.unwrap().fn_type(&arguments, false), None) } CCReturn::Void => { let arguments = function_arguments(env, argument_types); - env.context.void_type().fn_type(&arguments, false) + (env.context.void_type().fn_type(&arguments, false), None) } + }; + + Self { + typ, + call_conv: C_CALL_CONV, + cconv_sret_parameter: opt_sret_parameter, + } + } + + /// Fastcc calling convention + fn fastcc<'a, 'env>( + env: &Env<'a, 'ctx, 'env>, + roc_return: RocReturn, + return_type: BasicTypeEnum<'ctx>, + mut argument_types: Vec>, + ) -> FunctionSpec<'ctx> { + let typ = match roc_return { + RocReturn::Return => { + return_type.fn_type(&function_arguments(env, &argument_types), false) + } + RocReturn::ByPointer => { + argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + env.context + .void_type() + .fn_type(&function_arguments(env, &argument_types), false) + } + }; + + Self { + typ, + call_conv: FAST_CALL_CONV, + cconv_sret_parameter: None, + } + } + + pub fn known_fastcc(fn_type: FunctionType<'ctx>) -> FunctionSpec<'ctx> { + Self { + typ: fn_type, + call_conv: FAST_CALL_CONV, + cconv_sret_parameter: None, + } + } + + pub fn intrinsic(fn_type: FunctionType<'ctx>) -> Self { + // LLVM intrinsics always use the C calling convention, because + // they are implemented in C libraries + Self { + typ: fn_type, + call_conv: C_CALL_CONV, + cconv_sret_parameter: None, } } } @@ -6357,27 +6485,19 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( arguments.push(value); } - let cc_type = cc_return.to_signature(env, return_type, cc_argument_types.as_slice()); + let cc_type = + FunctionSpec::cconv(env, cc_return, Some(return_type), &cc_argument_types); let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); - let fastcc_type = match roc_return { - RocReturn::Return => { - return_type.fn_type(&function_arguments(env, &fastcc_argument_types), false) - } - RocReturn::ByPointer => { - fastcc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); - env.context - .void_type() - .fn_type(&function_arguments(env, &fastcc_argument_types), false) - } - }; + let fastcc_type = + FunctionSpec::fastcc(env, roc_return, return_type, fastcc_argument_types); let fastcc_function = add_func( + env.context, env.module, &fastcc_function_name, fastcc_type, Linkage::Internal, - FAST_CALL_CONV, ); let old = builder.get_insert_block().unwrap(); @@ -6574,10 +6694,34 @@ fn build_int_binop<'a, 'ctx, 'env>( &LLVM_MUL_WITH_OVERFLOW[int_width], &[lhs.into(), rhs.into()], ), - NumGt => bd.build_int_compare(SGT, lhs, rhs, "int_gt").into(), - NumGte => bd.build_int_compare(SGE, lhs, rhs, "int_gte").into(), - NumLt => bd.build_int_compare(SLT, lhs, rhs, "int_lt").into(), - NumLte => bd.build_int_compare(SLE, lhs, rhs, "int_lte").into(), + NumGt => { + if int_width.is_signed() { + bd.build_int_compare(SGT, lhs, rhs, "gt_int").into() + } else { + bd.build_int_compare(UGT, lhs, rhs, "gt_uint").into() + } + } + NumGte => { + if int_width.is_signed() { + bd.build_int_compare(SGE, lhs, rhs, "gte_int").into() + } else { + bd.build_int_compare(UGE, lhs, rhs, "gte_uint").into() + } + } + NumLt => { + if int_width.is_signed() { + bd.build_int_compare(SLT, lhs, rhs, "lt_int").into() + } else { + bd.build_int_compare(ULT, lhs, rhs, "lt_uint").into() + } + } + NumLte => { + if int_width.is_signed() { + bd.build_int_compare(SLE, lhs, rhs, "lte_int").into() + } else { + bd.build_int_compare(ULE, lhs, rhs, "lte_uint").into() + } + } NumRemUnchecked => { if int_width.is_signed() { bd.build_int_signed_rem(lhs, rhs, "rem_int").into() @@ -6915,7 +7059,6 @@ fn build_float_binop<'a, 'ctx, 'env>( NumGte => bd.build_float_compare(OGE, lhs, rhs, "float_gte").into(), NumLt => bd.build_float_compare(OLT, lhs, rhs, "float_lt").into(), NumLte => bd.build_float_compare(OLE, lhs, rhs, "float_lte").into(), - NumRemUnchecked => bd.build_float_rem(lhs, rhs, "rem_float").into(), NumDivUnchecked => bd.build_float_div(lhs, rhs, "div_float").into(), NumPow => env.call_intrinsic(&LLVM_POW[float_width], &[lhs.into(), rhs.into()]), _ => { @@ -6924,6 +7067,76 @@ fn build_float_binop<'a, 'ctx, 'env>( } } +fn dec_binop_with_overflow<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + fn_name: &str, + lhs: BasicValueEnum<'ctx>, + rhs: BasicValueEnum<'ctx>, +) -> StructValue<'ctx> { + let lhs = lhs.into_int_value(); + let rhs = rhs.into_int_value(); + + let return_type = zig_with_overflow_roc_dec(env); + let return_alloca = env.builder.build_alloca(return_type, "return_alloca"); + + let int_64 = env.context.i128_type().const_int(64, false); + let int_64_type = env.context.i64_type(); + + let lhs1 = env + .builder + .build_right_shift(lhs, int_64, false, "lhs_left_bits"); + let rhs1 = env + .builder + .build_right_shift(rhs, int_64, false, "rhs_left_bits"); + + call_void_bitcode_fn( + env, + &[ + return_alloca.into(), + env.builder.build_int_cast(lhs, int_64_type, "").into(), + env.builder.build_int_cast(lhs1, int_64_type, "").into(), + env.builder.build_int_cast(rhs, int_64_type, "").into(), + env.builder.build_int_cast(rhs1, int_64_type, "").into(), + ], + fn_name, + ); + + env.builder + .build_load(return_alloca, "load_dec") + .into_struct_value() +} + +pub fn dec_binop_with_unchecked<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + fn_name: &str, + lhs: BasicValueEnum<'ctx>, + rhs: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + let lhs = lhs.into_int_value(); + let rhs = rhs.into_int_value(); + + let int_64 = env.context.i128_type().const_int(64, false); + let int_64_type = env.context.i64_type(); + + let lhs1 = env + .builder + .build_right_shift(lhs, int_64, false, "lhs_left_bits"); + let rhs1 = env + .builder + .build_right_shift(rhs, int_64, false, "rhs_left_bits"); + + call_bitcode_fn( + env, + &[ + env.builder.build_int_cast(lhs, int_64_type, "").into(), + env.builder.build_int_cast(lhs1, int_64_type, "").into(), + env.builder.build_int_cast(rhs, int_64_type, "").into(), + env.builder.build_int_cast(rhs1, int_64_type, "").into(), + ], + fn_name, + ) +} + fn build_dec_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -6963,7 +7176,7 @@ fn build_dec_binop<'a, 'ctx, 'env>( rhs, "decimal multiplication overflowed", ), - NumDivUnchecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_DIV), + NumDivUnchecked => dec_binop_with_unchecked(env, bitcode::DEC_DIV, lhs, rhs), _ => { unreachable!("Unrecognized int binary operation: {:?}", op); } @@ -6978,15 +7191,7 @@ fn build_dec_binop_throw_on_overflow<'a, 'ctx, 'env>( rhs: BasicValueEnum<'ctx>, message: &str, ) -> BasicValueEnum<'ctx> { - let overflow_type = crate::llvm::convert::zig_with_overflow_roc_dec(env); - - let result_ptr = env.builder.build_alloca(overflow_type, "result_ptr"); - call_void_bitcode_fn(env, &[result_ptr.into(), lhs, rhs], operation); - - let result = env - .builder - .build_load(result_ptr, "load_overflow") - .into_struct_value(); + let result = dec_binop_with_overflow(env, operation, lhs, rhs); let value = throw_on_overflow(env, parent, result, message).into_struct_value(); @@ -7031,7 +7236,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>( // integer abs overflows when applied to the minimum value of a signed type int_abs_raise_on_overflow(env, arg, arg_int_type) } - NumToFloat => { + NumToFrac => { // This is an Int, so we need to convert it. let target_float_type = match return_layout { @@ -7081,6 +7286,9 @@ fn build_int_unary_op<'a, 'ctx, 'env>( || // Or if the two types are the same, they trivially fit. arg_width == target_int_width; + let return_type = + convert::basic_type_from_layout(env, return_layout).into_struct_type(); + if arg_always_fits_in_target { // This is guaranteed to succeed so we can just make it an int cast and let LLVM // optimize it away. @@ -7095,8 +7303,6 @@ fn build_int_unary_op<'a, 'ctx, 'env>( ) .into(); - let return_type = - convert::basic_type_from_layout(env, return_layout).into_struct_type(); let r = return_type.const_zero(); let r = bd .build_insert_value(r, target_int_val, 0, "converted_int") @@ -7119,7 +7325,14 @@ fn build_int_unary_op<'a, 'ctx, 'env>( &bitcode::NUM_INT_TO_INT_CHECKING_MAX_AND_MIN[target_int_width][arg_width] }; - call_bitcode_fn_fixing_for_convention(env, &[arg.into()], return_layout, bitcode_fn) + let result = call_bitcode_fn_fixing_for_convention( + env, + &[arg.into()], + return_layout, + bitcode_fn, + ); + + complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked") } } _ => { @@ -7245,7 +7458,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>( NumAbs => env.call_intrinsic(&LLVM_FABS[float_width], &[arg.into()]), NumSqrtUnchecked => env.call_intrinsic(&LLVM_SQRT[float_width], &[arg.into()]), NumLogUnchecked => env.call_intrinsic(&LLVM_LOG[float_width], &[arg.into()]), - NumToFloat => { + NumToFrac => { let return_width = match layout { Layout::Builtin(Builtin::Float(return_width)) => *return_width, _ => internal_error!("Layout for returning is not Float : {:?}", layout), @@ -7267,27 +7480,74 @@ fn build_float_unary_op<'a, 'ctx, 'env>( (FloatWidth::F64, FloatWidth::F64) => arg.into(), (FloatWidth::F128, FloatWidth::F128) => arg.into(), (FloatWidth::F128, _) => { - unimplemented!("I cannot handle F128 with Num.toFloat yet") + unimplemented!("I cannot handle F128 with Num.toFrac yet") } (_, FloatWidth::F128) => { - unimplemented!("I cannot handle F128 with Num.toFloat yet") + unimplemented!("I cannot handle F128 with Num.toFrac yet") } } } - NumCeiling => env.builder.build_cast( - InstructionOpcode::FPToSI, - env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]), - env.context.i64_type(), - "num_ceiling", - ), - NumFloor => env.builder.build_cast( - InstructionOpcode::FPToSI, - env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]), - env.context.i64_type(), - "num_floor", - ), + NumCeiling => { + let (return_signed, return_type) = match layout { + Layout::Builtin(Builtin::Int(int_width)) => ( + int_width.is_signed(), + convert::int_type_from_int_width(env, *int_width), + ), + _ => internal_error!("Ceiling return layout is not int: {:?}", layout), + }; + let opcode = if return_signed { + InstructionOpcode::FPToSI + } else { + InstructionOpcode::FPToUI + }; + env.builder.build_cast( + opcode, + env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]), + return_type, + "num_ceiling", + ) + } + NumFloor => { + let (return_signed, return_type) = match layout { + Layout::Builtin(Builtin::Int(int_width)) => ( + int_width.is_signed(), + convert::int_type_from_int_width(env, *int_width), + ), + _ => internal_error!("Ceiling return layout is not int: {:?}", layout), + }; + let opcode = if return_signed { + InstructionOpcode::FPToSI + } else { + InstructionOpcode::FPToUI + }; + env.builder.build_cast( + opcode, + env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]), + return_type, + "num_floor", + ) + } + NumRound => { + let (return_signed, return_type) = match layout { + Layout::Builtin(Builtin::Int(int_width)) => ( + int_width.is_signed(), + convert::int_type_from_int_width(env, *int_width), + ), + _ => internal_error!("Ceiling return layout is not int: {:?}", layout), + }; + let opcode = if return_signed { + InstructionOpcode::FPToSI + } else { + InstructionOpcode::FPToUI + }; + env.builder.build_cast( + opcode, + env.call_intrinsic(&LLVM_ROUND[float_width], &[arg.into()]), + return_type, + "num_round", + ) + } NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]), - NumRound => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND[float_width]), // trigonometry NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]), @@ -7434,7 +7694,7 @@ fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) { fn get_foreign_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, foreign_symbol: roc_module::ident::ForeignSymbol, - function_type: FunctionType<'ctx>, + function_spec: FunctionSpec<'ctx>, ) -> FunctionValue<'ctx> { let module = env.module; @@ -7442,11 +7702,11 @@ fn get_foreign_symbol<'a, 'ctx, 'env>( Some(gvalue) => gvalue, None => { let foreign_function = add_func( + env.context, module, foreign_symbol.as_str(), - function_type, + function_spec, Linkage::External, - C_CALL_CONV, ); foreign_function @@ -7458,11 +7718,11 @@ fn get_foreign_symbol<'a, 'ctx, 'env>( /// We never want to define the same function twice in the same module! /// The result can be bugs that are difficult to track down. pub fn add_func<'ctx>( + ctx: &Context, module: &Module<'ctx>, name: &str, - typ: FunctionType<'ctx>, + spec: FunctionSpec<'ctx>, linkage: Linkage, - call_conv: u32, ) -> FunctionValue<'ctx> { if cfg!(debug_assertions) { if let Some(func) = module.get_function(name) { @@ -7470,9 +7730,9 @@ pub fn add_func<'ctx>( } } - let fn_val = module.add_function(name, typ, Some(linkage)); + let fn_val = module.add_function(name, spec.typ, Some(linkage)); - fn_val.set_call_conventions(call_conv); + spec.attach_attributes(ctx, fn_val); fn_val } diff --git a/compiler/gen_llvm/src/llvm/build_dict.rs b/compiler/gen_llvm/src/llvm/build_dict.rs index a9be95352b..a7f4f123df 100644 --- a/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/compiler/gen_llvm/src/llvm/build_dict.rs @@ -283,6 +283,8 @@ pub fn dict_get<'a, 'ctx, 'env>( .ptr_int() .const_int(value_layout.stack_size(env.target_info) as u64, false); + let value_bt = basic_type_from_layout(env, value_layout); + let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info); let alignment_iv = alignment.as_int_value(env.context); @@ -308,17 +310,26 @@ pub fn dict_get<'a, 'ctx, 'env>( ) .into_struct_value(); - let flag = env + let flag_u8 = env .builder .build_extract_value(result, 1, "get_flag") .unwrap() .into_int_value(); + let flag = env + .builder + .build_int_cast(flag_u8, env.context.bool_type(), "to_bool"); + + let value_u8_ptr_int = env + .builder + .build_extract_value(result, 0, "get_value_ptr_int") + .unwrap() + .into_int_value(); + + let ptr_type = value_bt.ptr_type(AddressSpace::Generic); let value_u8_ptr = env .builder - .build_extract_value(result, 0, "get_value_ptr") - .unwrap() - .into_pointer_value(); + .build_int_to_ptr(value_u8_ptr_int, ptr_type, "opaque_value_ptr"); let start_block = env.builder.get_insert_block().unwrap(); let parent = start_block.get_parent().unwrap(); @@ -326,7 +337,6 @@ pub fn dict_get<'a, 'ctx, 'env>( let if_not_null = env.context.append_basic_block(parent, "if_not_null"); let done_block = env.context.append_basic_block(parent, "done"); - let value_bt = basic_type_from_layout(env, value_layout); let default = value_bt.const_zero(); env.builder diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 2b85785241..57edf0696b 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -494,7 +494,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>( layout_width(env, element_layout), layout_width(env, function_call_return_layout), layout_width(env, default_layout), - has_tag_id.as_global_value().as_pointer_value().into(), + has_tag_id_helper(env, has_tag_id).into(), dec_element_fn.as_global_value().as_pointer_value().into(), pass_as_opaque(env, result_ptr), ], @@ -603,6 +603,30 @@ fn empty_list<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> BasicValueEnum::StructValue(struct_type.const_zero()) } +fn has_tag_id_helper<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + has_tag_id: FunctionValue<'ctx>, +) -> PointerValue<'ctx> { + let u8_t = env.context.i8_type(); + let u16_t = env.context.i16_type(); + + let u8_ptr_t = u8_t.ptr_type(AddressSpace::Generic); + + let struct_t = env + .context + .struct_type(&[u8_t.into(), env.ptr_int().into()], false); + + let has_tag_id_type = struct_t + .fn_type(&[u16_t.into(), u8_ptr_t.into()], false) + .ptr_type(AddressSpace::Generic); + + env.builder.build_pointer_cast( + has_tag_id.as_global_value().as_pointer_value(), + has_tag_id_type, + "has_tag_id_cast", + ) +} + /// List.keepOks : List before, (before -> Result after *) -> List after pub fn list_keep_oks<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -626,7 +650,7 @@ pub fn list_keep_oks<'a, 'ctx, 'env>( let has_tag_id = match result_layout { Layout::Union(union_layout) => build_has_tag_id(env, function, *union_layout), Layout::Builtin(Builtin::Bool) => { - // a `Result [] whatever`, so there is nothing to keep + // a `Result whatever []`, so there is nothing to keep return empty_list(env); } _ => unreachable!(), @@ -644,7 +668,7 @@ pub fn list_keep_oks<'a, 'ctx, 'env>( layout_width(env, before_layout), layout_width(env, result_layout), layout_width(env, after_layout), - has_tag_id.as_global_value().as_pointer_value().into(), + has_tag_id_helper(env, has_tag_id).into(), dec_result_fn.as_global_value().as_pointer_value().into(), ], bitcode::LIST_KEEP_OKS, @@ -692,7 +716,7 @@ pub fn list_keep_errs<'a, 'ctx, 'env>( layout_width(env, before_layout), layout_width(env, result_layout), layout_width(env, after_layout), - has_tag_id.as_global_value().as_pointer_value().into(), + has_tag_id_helper(env, has_tag_id).into(), dec_result_fn.as_global_value().as_pointer_value().into(), ], bitcode::LIST_KEEP_ERRS, @@ -968,7 +992,6 @@ pub fn list_find_unsafe<'a, 'ctx, 'env>( pass_as_opaque(env, roc_function_call.data), roc_function_call.inc_n_data.into(), roc_function_call.data_is_owned.into(), - env.alignment_intvalue(element_layout), layout_width(env, element_layout), inc_element_fn.as_global_value().as_pointer_value().into(), dec_element_fn.as_global_value().as_pointer_value().into(), @@ -982,18 +1005,22 @@ pub fn list_find_unsafe<'a, 'ctx, 'env>( // in the Zig definition called above, because we don't know the size of the // element until user compile time, which is later than the compile time of bitcode defs. - let value_u8_ptr = env + let value_u8_ptr_int = env .builder - .build_extract_value(result, 0, "get_value_ptr") + .build_extract_value(result, 0, "get_value_ptr_int") .unwrap() - .into_pointer_value(); + .into_int_value(); - let found = env + let found_u8 = env .builder .build_extract_value(result, 1, "get_found") .unwrap() .into_int_value(); + let found = env + .builder + .build_int_cast(found_u8, env.context.bool_type(), "found_as_bool"); + let start_block = env.builder.get_insert_block().unwrap(); let parent = start_block.get_parent().unwrap(); @@ -1007,14 +1034,13 @@ pub fn list_find_unsafe<'a, 'ctx, 'env>( .build_conditional_branch(found, if_not_null, done_block); env.builder.position_at_end(if_not_null); - let value_ptr = env - .builder - .build_bitcast( - value_u8_ptr, - value_bt.ptr_type(AddressSpace::Generic), - "from_opaque", - ) - .into_pointer_value(); + + let value_ptr = env.builder.build_int_to_ptr( + value_u8_ptr_int, + value_bt.ptr_type(AddressSpace::Generic), + "get_value_ptr", + ); + let loaded = env.builder.build_load(value_ptr, "load_value"); env.builder.build_unconditional_branch(done_block); diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 5df2d25611..12b24b2dea 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -1,5 +1,5 @@ use crate::llvm::bitcode::{call_bitcode_fn, call_str_bitcode_fn, call_void_bitcode_fn}; -use crate::llvm::build::{complex_bitcast, Env, Scope}; +use crate::llvm::build::{Env, Scope}; use crate::llvm::build_list::{allocate_list, pass_update_mode, store_list}; use inkwell::builder::Builder; use inkwell::values::{BasicValueEnum, IntValue, PointerValue, StructValue}; @@ -155,16 +155,22 @@ pub fn str_from_utf8_range<'a, 'ctx, 'env>( let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap(); let result_ptr = builder.build_alloca(result_type, "alloca_utf8_validate_bytes_result"); + let count = env + .builder + .build_extract_value(count_and_start, 0, "get_count") + .unwrap(); + + let start = env + .builder + .build_extract_value(count_and_start, 1, "get_start") + .unwrap(); + call_void_bitcode_fn( env, &[ list_symbol_to_c_abi(env, scope, list).into(), - complex_bitcast( - env.builder, - count_and_start.into(), - env.twice_ptr_int().into(), - "to_i128", - ), + count, + start, result_ptr.into(), ], bitcode::STR_FROM_UTF8_RANGE, diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index 7095b5dd1b..8af0ea575c 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -1,4 +1,3 @@ -use crate::llvm::bitcode::call_bitcode_fn; use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV}; use crate::llvm::build_list::{list_len, load_list_ptr}; use crate::llvm::build_str::str_equal; @@ -14,7 +13,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; -use super::build::{load_roc_value, use_roc_value}; +use super::build::{dec_binop_with_unchecked, load_roc_value, use_roc_value}; use super::convert::argument_type_from_union_layout; #[derive(Clone, Debug)] @@ -124,7 +123,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>( } Builtin::Bool => int_cmp(IntPredicate::EQ, "eq_i1"), - Builtin::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], bitcode::DEC_EQ), + Builtin::Decimal => dec_binop_with_unchecked(env, bitcode::DEC_EQ, lhs_val, rhs_val), Builtin::Str => str_equal(env, lhs_val, rhs_val), Builtin::List(elem) => build_list_eq( @@ -289,7 +288,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>( } Builtin::Bool => int_cmp(IntPredicate::NE, "neq_i1"), - Builtin::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], bitcode::DEC_NEQ), + Builtin::Decimal => dec_binop_with_unchecked(env, bitcode::DEC_NEQ, lhs_val, rhs_val), Builtin::Str => { let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value(); diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 2ca3060d7a..224885005a 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -273,7 +273,10 @@ pub fn zig_str_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ct } pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> { - env.module.get_struct_type("list.HasTagId").unwrap() + let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::Generic); + + env.context + .struct_type(&[env.context.bool_type().into(), u8_ptr_t.into()], false) } pub fn zig_with_overflow_roc_dec<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> { diff --git a/compiler/gen_llvm/src/llvm/externs.rs b/compiler/gen_llvm/src/llvm/externs.rs index 889a0edd3b..b3c428958b 100644 --- a/compiler/gen_llvm/src/llvm/externs.rs +++ b/compiler/gen_llvm/src/llvm/externs.rs @@ -1,8 +1,13 @@ -use crate::llvm::build::Env; -use crate::llvm::build::{add_func, C_CALL_CONV}; +use crate::llvm::bitcode::call_void_bitcode_fn; +use crate::llvm::build::{add_func, get_panic_msg_ptr, C_CALL_CONV}; +use crate::llvm::build::{CCReturn, Env, FunctionSpec}; use inkwell::module::Linkage; +use inkwell::types::BasicType; use inkwell::values::BasicValue; use inkwell::AddressSpace; +use roc_builtins::bitcode; + +use super::build::{get_sjlj_buffer, LLVM_LONGJMP}; /// Define functions for roc_alloc, roc_realloc, and roc_dealloc /// which use libc implementations (malloc, realloc, and free) @@ -82,21 +87,18 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) { // roc_realloc { let libc_realloc_val = { - let fn_val = add_func( - module, - "realloc", - i8_ptr_type.fn_type( - &[ - // ptr: *void - i8_ptr_type.into(), - // size: usize - usize_type.into(), - ], - false, - ), - Linkage::External, - C_CALL_CONV, + let fn_spec = FunctionSpec::cconv( + env, + CCReturn::Return, + Some(i8_ptr_type.as_basic_type_enum()), + &[ + // ptr: *void + i8_ptr_type.into(), + // size: usize + usize_type.into(), + ], ); + let fn_val = add_func(env.context, module, "realloc", fn_spec, Linkage::External); let mut params = fn_val.get_param_iter(); let ptr_arg = params.next().unwrap(); @@ -186,8 +188,6 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { // roc_panic { - use crate::llvm::build::LLVM_LONGJMP; - // The type of this function (but not the implementation) should have // already been defined by the builtins, which rely on it. let fn_val = module.get_function("roc_panic").unwrap(); @@ -209,32 +209,10 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { builder.position_at_end(entry); - let buffer = crate::llvm::build::get_sjlj_buffer(env); - // write our error message pointer - let index = env - .ptr_int() - .const_int(3 * env.target_info.ptr_width() as u64, false); - let message_buffer_raw = - unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; - let message_buffer = builder.build_bitcast( - message_buffer_raw, - env.context - .i8_type() - .ptr_type(AddressSpace::Generic) - .ptr_type(AddressSpace::Generic), - "to **u8", - ); + env.builder.build_store(get_panic_msg_ptr(env), ptr_arg); - env.builder - .build_store(message_buffer.into_pointer_value(), ptr_arg); - - let tag = env.context.i32_type().const_int(1, false); - if true { - let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into()]); - } else { - let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[buffer.into(), tag.into()]); - } + build_longjmp_call(env); builder.build_unreachable(); @@ -243,3 +221,21 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { } } } + +pub fn build_longjmp_call(env: &Env) { + let jmp_buf = get_sjlj_buffer(env); + if cfg!(target_arch = "aarch64") { + // Call the Zig-linked longjmp: `void longjmp(i32*, i32)` + let tag = env.context.i32_type().const_int(1, false); + let _call = + call_void_bitcode_fn(env, &[jmp_buf.into(), tag.into()], bitcode::UTILS_LONGJMP); + } else { + // Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)` + let jmp_buf_i8p = env.builder.build_bitcast( + jmp_buf, + env.context.i8_type().ptr_type(AddressSpace::Generic), + "jmp_buf i8*", + ); + let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p]); + } +} diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index ce79180f75..efc9d9bfa5 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -18,7 +18,7 @@ use roc_module::symbol::Interns; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; -use super::build::load_roc_value; +use super::build::{load_roc_value, FunctionSpec}; use super::convert::{argument_type_from_layout, argument_type_from_union_layout}; pub struct PointerToRefcount<'ctx> { @@ -143,11 +143,11 @@ impl<'ctx> PointerToRefcount<'ctx> { ); let function_value = add_func( + env.context, env.module, fn_name, - fn_type, + FunctionSpec::known_fastcc(fn_type), Linkage::Internal, - FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention. ); let subprogram = env.new_subprogram(fn_name); @@ -1138,11 +1138,11 @@ pub fn build_header_help<'a, 'ctx, 'env>( }; let fn_val = add_func( + env.context, env.module, fn_name, - fn_type, + FunctionSpec::known_fastcc(fn_type), Linkage::Private, - FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention. ); let subprogram = env.new_subprogram(fn_name); @@ -1809,7 +1809,39 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>( for (i, field_layout) in field_layouts.iter().enumerate() { if let Layout::RecursivePointer = field_layout { - panic!("non-recursive tag unions cannot contain naked recursion pointers!"); + let recursive_union_layout = match when_recursive { + WhenRecursive::Unreachable => { + panic!("non-recursive tag unions cannot contain naked recursion pointers!"); + } + WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout, + }; + + // This field is a pointer to the recursive pointer. + let field_ptr = env + .builder + .build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field") + .unwrap(); + + // This is the actual pointer to the recursive data. + let field_value = env.builder.build_load(field_ptr, "load_recursive_pointer"); + + debug_assert!(field_value.is_pointer_value()); + + // therefore we must cast it to our desired type + let union_type = + basic_type_from_layout(env, &Layout::Union(*recursive_union_layout)); + let recursive_ptr_field_value = + cast_basic_basic(env.builder, field_value, union_type); + + modify_refcount_layout_help( + env, + parent, + layout_ids, + mode.to_call_mode(fn_val), + when_recursive, + recursive_ptr_field_value, + &Layout::RecursivePointer, + ) } else if field_layout.contains_refcounted() { let field_ptr = env .builder diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 0ad99742cb..d52ac14008 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -4,7 +4,6 @@ use std::fmt::Write; use code_builder::Align; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; -use roc_module::ident::Ident; use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX}; @@ -22,12 +21,11 @@ use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol}; use crate::wasm_module::sections::{DataMode, DataSegment, Limits}; use crate::wasm_module::{ - code_builder, CodeBuilder, Export, ExportType, LocalId, Signature, SymInfo, ValueType, - WasmModule, + code_builder, CodeBuilder, ExportType, LocalId, Signature, SymInfo, ValueType, WasmModule, }; use crate::{ - copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, MEMORY_NAME, - PTR_SIZE, PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME, TARGET_INFO, + copy_memory, round_up_to_alignment, CopyMemoryConfig, Env, DEBUG_LOG_SETTINGS, PTR_SIZE, + PTR_TYPE, TARGET_INFO, }; #[derive(Clone, Copy, Debug)] @@ -78,22 +76,32 @@ impl<'a> WasmBackend<'a> { fn_index_offset: u32, helper_proc_gen: CodeGenHelp<'a>, ) -> Self { - module.export.append(Export { - name: MEMORY_NAME.as_bytes(), - ty: ExportType::Mem, - index: 0, - }); - module.export.append(Export { - name: STACK_POINTER_NAME.as_bytes(), - ty: ExportType::Global, - index: STACK_POINTER_GLOBAL_ID, - }); + // The preloaded builtins object file exports all functions, but the final app binary doesn't. + // Remove the function exports and use them to populate the Name section (debug info) + let platform_and_builtins_exports = + std::mem::replace(&mut module.export.exports, bumpalo::vec![in env.arena]); + let mut app_exports = Vec::with_capacity_in(32, env.arena); + for ex in platform_and_builtins_exports.into_iter() { + match ex.ty { + ExportType::Func => module.names.append_function(ex.index, ex.name), + _ => app_exports.push(ex), + } + } // The preloaded binary has a global to tell us where its data section ends // Note: We need this to account for zero data (.bss), which doesn't have an explicit DataSegment! - let data_end_idx = module.export.globals_lookup["__data_end".as_bytes()]; + let data_end_name = "__data_end".as_bytes(); + let data_end_idx = app_exports + .iter() + .find(|ex| ex.name == data_end_name) + .map(|ex| ex.index) + .unwrap_or_else(|| { + internal_error!("Preloaded Wasm binary must export global constant `__data_end`") + }); let next_constant_addr = module.global.parse_u32_at_index(data_end_idx); + module.export.exports = app_exports; + WasmBackend { env, interns, @@ -183,7 +191,7 @@ impl<'a> WasmBackend<'a> { .get_mut(&self.env.module_id) .unwrap(); - let ident_id = ident_ids.add(Ident::from(debug_name)); + let ident_id = ident_ids.add_str(debug_name); Symbol::new(self.env.module_id, ident_id) } @@ -225,15 +233,19 @@ impl<'a> WasmBackend<'a> { } fn start_proc(&mut self, proc: &Proc<'a>) { + use ReturnMethod::*; let ret_layout = WasmLayout::new(&proc.ret_layout); - let ret_type = match ret_layout.return_method() { - ReturnMethod::Primitive(ty, _) => Some(ty), - ReturnMethod::NoReturnValue => None, - ReturnMethod::WriteToPointerArg => { + let ret_type = match ret_layout.return_method(CallConv::C) { + Primitive(ty, _) => Some(ty), + NoReturnValue => None, + WriteToPointerArg => { self.storage.arg_types.push(PTR_TYPE); None } + ZigPackedStruct => { + internal_error!("C calling convention does not return Zig packed structs") + } }; // Create a block so we can exit the function without skipping stack frame "pop" code. @@ -324,7 +336,7 @@ impl<'a> WasmBackend<'a> { }; let mut n_inner_wasm_args = 0; - let ret_type_and_size = match inner_ret_layout.return_method() { + let ret_type_and_size = match inner_ret_layout.return_method(CallConv::C) { ReturnMethod::NoReturnValue => None, ReturnMethod::Primitive(ty, size) => { // If the inner function returns a primitive, load the address to store it at @@ -338,6 +350,7 @@ impl<'a> WasmBackend<'a> { n_inner_wasm_args += 1; None } + x => internal_error!("A Roc function should never use ReturnMethod {:?}", x), }; // Load all the arguments for the inner function @@ -1021,14 +1034,16 @@ impl<'a> WasmBackend<'a> { return self.expr_call_low_level(lowlevel, arguments, ret_sym, ret_layout, ret_storage); } - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - ret_sym, - &wasm_layout, - CallConv::C, - ); + let (num_wasm_args, has_return_val, ret_zig_packed_struct) = + self.storage.load_symbols_for_call( + self.env.arena, + &mut self.code_builder, + arguments, + ret_sym, + &wasm_layout, + CallConv::C, + ); + debug_assert!(!ret_zig_packed_struct); for (roc_proc_index, lookup) in self.proc_lookup.iter().enumerate() { let ProcLookupData { @@ -1039,8 +1054,6 @@ impl<'a> WasmBackend<'a> { } = lookup; if *ir_sym == func_sym && pl == proc_layout { let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32; - let num_wasm_args = param_types.len(); - let has_return_val = ret_type.is_some(); self.code_builder.call( wasm_fn_index, *linker_sym_index, diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 33450c6680..36335bb78e 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -4,9 +4,6 @@ use roc_mono::layout::{Layout, UnionLayout}; use crate::wasm_module::ValueType; use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO}; -/// Manually keep up to date with the Zig version we are using for builtins -pub const BUILTINS_ZIG_VERSION: ZigVersion = ZigVersion::Zig8; - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReturnMethod { /// This layout is returned from a Wasm function "normally" as a Primitive @@ -15,6 +12,8 @@ pub enum ReturnMethod { WriteToPointerArg, /// This layout is empty and requires no return value or argument (e.g. refcount helpers) NoReturnValue, + /// This layout is returned as a packed struct in an integer. Only used by Zig, not C. + ZigPackedStruct, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -124,33 +123,23 @@ impl WasmLayout { } } - pub fn return_method(&self) -> ReturnMethod { + pub fn return_method(&self, conv: CallConv) -> ReturnMethod { match self { Self::Primitive(ty, size) => ReturnMethod::Primitive(*ty, *size), - Self::StackMemory { size, .. } => { - if *size == 0 { - ReturnMethod::NoReturnValue - } else { - ReturnMethod::WriteToPointerArg - } + Self::StackMemory { size, format, .. } => { + conv.stack_memory_return_method(*size, *format) } } } } -#[derive(PartialEq, Eq)] -pub enum ZigVersion { - Zig8, - Zig9, -} - #[derive(Debug, Clone, Copy)] pub enum CallConv { /// The C calling convention, as defined here: /// https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md C, - /// The calling convention that Zig 0.8 or 0.9 generates for Wasm when we *ask* it - /// for the .C calling convention, due to bugs in both versions of the Zig compiler. + /// The calling convention that Zig 0.9 generates for Wasm when we *ask* it + /// for the .C calling convention, due to bugs in the Zig compiler. Zig, } @@ -182,7 +171,7 @@ impl CallConv { &[I32] // Small struct: pass by value } else if size <= 8 { &[I64] // Small struct: pass by value - } else if size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 { + } else if size <= 12 { &[I64, I32] // Medium struct: pass by value, as two Wasm arguments } else if size <= 16 { &[I64, I64] // Medium struct: pass by value, as two Wasm arguments @@ -194,4 +183,30 @@ impl CallConv { } } } + + pub fn stack_memory_return_method(&self, size: u32, format: StackMemoryFormat) -> ReturnMethod { + use ReturnMethod::*; + use StackMemoryFormat::*; + + match format { + Int128 | Float128 | Decimal => WriteToPointerArg, + + DataStructure => { + if size == 0 { + return NoReturnValue; + } + match self { + CallConv::C => WriteToPointerArg, + + CallConv::Zig => { + if size <= 8 { + ZigPackedStruct + } else { + WriteToPointerArg + } + } + } + } + } + } } diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 367154caa5..44deefdc98 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -22,7 +22,7 @@ use crate::TARGET_INFO; /// the smallest integer supported in the Wasm instruction set. /// We may choose different instructions for signed and unsigned integers, /// but they share the same Wasm value type. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] enum CodeGenNumType { I32, // Supported in Wasm instruction set I64, // Supported in Wasm instruction set @@ -116,6 +116,13 @@ impl From<&StoredValue> for CodeGenNumType { } } +fn integer_symbol_is_signed(backend: &WasmBackend<'_>, symbol: Symbol) -> bool { + return match backend.storage.symbol_layouts[&symbol] { + Layout::Builtin(Builtin::Int(int_width)) => int_width.is_signed(), + x => internal_error!("Expected integer, found {:?}", x), + }; +} + pub struct LowLevelCall<'a> { pub lowlevel: LowLevel, pub arguments: &'a [Symbol], @@ -129,7 +136,7 @@ impl<'a> LowLevelCall<'a> { /// For numerical ops, this just pushes the arguments to the Wasm VM's value stack /// It implements the calling convention used by Zig for both numbers and structs /// Result is the type signature of the call - fn load_args(&self, backend: &mut WasmBackend<'a>) -> (Vec<'a, ValueType>, Option) { + fn load_args(&self, backend: &mut WasmBackend<'a>) -> (usize, bool, bool) { backend.storage.load_symbols_for_call( backend.env.arena, &mut backend.code_builder, @@ -141,8 +148,29 @@ impl<'a> LowLevelCall<'a> { } fn load_args_and_call_zig(&self, backend: &mut WasmBackend<'a>, name: &'a str) { - let (param_types, ret_type) = self.load_args(backend); - backend.call_zig_builtin_after_loading_args(name, param_types.len(), ret_type.is_some()); + let (num_wasm_args, has_return_val, ret_zig_packed_struct) = self.load_args(backend); + backend.call_zig_builtin_after_loading_args(name, num_wasm_args, has_return_val); + + if ret_zig_packed_struct { + match self.ret_storage { + StoredValue::StackMemory { + size, + alignment_bytes, + .. + } => { + // The address of the return value was already loaded before the call + let align = Align::from(alignment_bytes); + if size > 4 { + backend.code_builder.i64_store(align, 0); + } else { + backend.code_builder.i32_store(align, 0); + } + } + _ => { + internal_error!("Zig packed struct should always be stored to StackMemory") + } + } + } } /// Wrap an integer whose Wasm representation is i32 @@ -270,6 +298,8 @@ impl<'a> LowLevelCall<'a> { _ => internal_error!("invalid storage for List"), }, + ListIsUnique => self.load_args_and_call_zig(backend, bitcode::LIST_IS_UNIQUE), + ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListAny | ListAll | ListFindUnsafe | DictWalk => { @@ -377,8 +407,20 @@ impl<'a> LowLevelCall<'a> { NumGt => { self.load_args(backend); match CodeGenNumType::for_symbol(backend, self.arguments[0]) { - I32 => backend.code_builder.i32_gt_s(), - I64 => backend.code_builder.i64_gt_s(), + I32 => { + if integer_symbol_is_signed(backend, self.arguments[0]) { + backend.code_builder.i32_gt_s() + } else { + backend.code_builder.i32_gt_u() + } + } + I64 => { + if integer_symbol_is_signed(backend, self.arguments[0]) { + backend.code_builder.i64_gt_s() + } else { + backend.code_builder.i64_gt_u() + } + } F32 => backend.code_builder.f32_gt(), F64 => backend.code_builder.f64_gt(), x => todo!("{:?} for {:?}", self.lowlevel, x), @@ -387,8 +429,20 @@ impl<'a> LowLevelCall<'a> { NumGte => { self.load_args(backend); match CodeGenNumType::for_symbol(backend, self.arguments[0]) { - I32 => backend.code_builder.i32_ge_s(), - I64 => backend.code_builder.i64_ge_s(), + I32 => { + if integer_symbol_is_signed(backend, self.arguments[0]) { + backend.code_builder.i32_ge_s() + } else { + backend.code_builder.i32_ge_u() + } + } + I64 => { + if integer_symbol_is_signed(backend, self.arguments[0]) { + backend.code_builder.i64_ge_s() + } else { + backend.code_builder.i64_ge_u() + } + } F32 => backend.code_builder.f32_ge(), F64 => backend.code_builder.f64_ge(), x => todo!("{:?} for {:?}", self.lowlevel, x), @@ -397,8 +451,20 @@ impl<'a> LowLevelCall<'a> { NumLt => { self.load_args(backend); match CodeGenNumType::for_symbol(backend, self.arguments[0]) { - I32 => backend.code_builder.i32_lt_s(), - I64 => backend.code_builder.i64_lt_s(), + I32 => { + if integer_symbol_is_signed(backend, self.arguments[0]) { + backend.code_builder.i32_lt_s() + } else { + backend.code_builder.i32_lt_u() + } + } + I64 => { + if integer_symbol_is_signed(backend, self.arguments[0]) { + backend.code_builder.i64_lt_s() + } else { + backend.code_builder.i64_lt_u() + } + } F32 => backend.code_builder.f32_lt(), F64 => backend.code_builder.f64_lt(), x => todo!("{:?} for {:?}", self.lowlevel, x), @@ -407,8 +473,20 @@ impl<'a> LowLevelCall<'a> { NumLte => { self.load_args(backend); match CodeGenNumType::for_symbol(backend, self.arguments[0]) { - I32 => backend.code_builder.i32_le_s(), - I64 => backend.code_builder.i64_le_s(), + I32 => { + if integer_symbol_is_signed(backend, self.arguments[0]) { + backend.code_builder.i32_le_s() + } else { + backend.code_builder.i32_le_u() + } + } + I64 => { + if integer_symbol_is_signed(backend, self.arguments[0]) { + backend.code_builder.i64_le_s() + } else { + backend.code_builder.i64_le_u() + } + } F32 => backend.code_builder.f32_le(), F64 => backend.code_builder.f64_le(), x => todo!("{:?} for {:?}", self.lowlevel, x), @@ -504,19 +582,7 @@ impl<'a> LowLevelCall<'a> { NumCos => todo!("{:?}", self.lowlevel), NumSqrtUnchecked => todo!("{:?}", self.lowlevel), NumLogUnchecked => todo!("{:?}", self.lowlevel), - NumRound => { - self.load_args(backend); - match CodeGenNumType::for_symbol(backend, self.arguments[0]) { - F32 => { - self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F32]) - } - F64 => { - self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F64]) - } - _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), - } - } - NumToFloat => { + NumToFrac => { self.load_args(backend); let ret_type = CodeGenNumType::from(self.ret_layout); let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); @@ -535,35 +601,72 @@ impl<'a> LowLevelCall<'a> { } } NumPow => todo!("{:?}", self.lowlevel), - NumCeiling => { + NumRound => { self.load_args(backend); - match CodeGenNumType::from(self.ret_layout) { - I32 => { + let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + let ret_type = CodeGenNumType::from(self.ret_layout); + + let width = match ret_type { + CodeGenNumType::I32 => IntWidth::I32, + CodeGenNumType::I64 => IntWidth::I64, + CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel), + _ => internal_error!("Invalid return type for round: {:?}", ret_type), + }; + + match arg_type { + F32 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F32[width]), + F64 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F64[width]), + _ => internal_error!("Invalid argument type for round: {:?}", arg_type), + } + } + NumCeiling | NumFloor => { + self.load_args(backend); + let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + let ret_type = CodeGenNumType::from(self.ret_layout); + match (arg_type, self.lowlevel) { + (F32, NumCeiling) => { backend.code_builder.f32_ceil(); - backend.code_builder.i32_trunc_s_f32() } - I64 => { + (F64, NumCeiling) => { backend.code_builder.f64_ceil(); - backend.code_builder.i64_trunc_s_f64() } - _ => panic_ret_type(), - } - } - NumPowInt => todo!("{:?}", self.lowlevel), - NumFloor => { - self.load_args(backend); - match CodeGenNumType::from(self.ret_layout) { - I32 => { + (F32, NumFloor) => { backend.code_builder.f32_floor(); - backend.code_builder.i32_trunc_s_f32() } - I64 => { + (F64, NumFloor) => { backend.code_builder.f64_floor(); - backend.code_builder.i64_trunc_s_f64() } + _ => internal_error!("Invalid argument type for ceiling: {:?}", arg_type), + } + match (ret_type, arg_type) { + // TODO: unsigned truncation + (I32, F32) => backend.code_builder.i32_trunc_s_f32(), + (I32, F64) => backend.code_builder.i32_trunc_s_f64(), + (I64, F32) => backend.code_builder.i64_trunc_s_f32(), + (I64, F64) => backend.code_builder.i64_trunc_s_f64(), + (I128, _) => todo!("{:?} for I128", self.lowlevel), _ => panic_ret_type(), } } + NumPowInt => { + self.load_args(backend); + let base_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + let exponent_type = CodeGenNumType::for_symbol(backend, self.arguments[1]); + let ret_type = CodeGenNumType::from(self.ret_layout); + + debug_assert!(base_type == exponent_type); + debug_assert!(exponent_type == ret_type); + + let width = match ret_type { + CodeGenNumType::I32 => IntWidth::I32, + CodeGenNumType::I64 => IntWidth::I64, + CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel), + _ => internal_error!("Invalid return type for pow: {:?}", ret_type), + }; + + self.load_args_and_call_zig(backend, &bitcode::NUM_POW_INT[width]) + } + NumIsFinite => num_is_finite(backend, self.arguments[0]), NumAtan => match self.ret_layout { diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index e2d2429f00..c41b4d44b8 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -6,9 +6,7 @@ use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::Layout; -use crate::layout::{ - CallConv, ReturnMethod, StackMemoryFormat, WasmLayout, ZigVersion, BUILTINS_ZIG_VERSION, -}; +use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout}; use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_TYPE}; @@ -327,7 +325,7 @@ impl<'a> Storage<'a> { code_builder.i32_load(align, offset); } else if *size <= 8 { code_builder.i64_load(align, offset); - } else if *size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 { + } else if *size <= 12 { code_builder.i64_load(align, offset); code_builder.get_local(local_id); code_builder.i32_load(align, offset + 8); @@ -397,37 +395,47 @@ impl<'a> Storage<'a> { return_symbol: Symbol, return_layout: &WasmLayout, call_conv: CallConv, - ) -> (Vec<'a, ValueType>, Option) { - let mut wasm_arg_types = Vec::with_capacity_in(arguments.len() * 2 + 1, arena); - let mut wasm_args = Vec::with_capacity_in(arguments.len() * 2 + 1, arena); + ) -> (usize, bool, bool) { + use ReturnMethod::*; - let return_method = return_layout.return_method(); - let return_type = match return_method { - ReturnMethod::Primitive(ty, _) => Some(ty), - ReturnMethod::NoReturnValue => None, - ReturnMethod::WriteToPointerArg => { - wasm_arg_types.push(PTR_TYPE); - wasm_args.push(return_symbol); - None + let mut num_wasm_args = 0; + let mut symbols_to_load = Vec::with_capacity_in(arguments.len() * 2 + 1, arena); + + let return_method = return_layout.return_method(call_conv); + let has_return_val = match return_method { + Primitive(..) => true, + NoReturnValue => false, + WriteToPointerArg => { + num_wasm_args += 1; + symbols_to_load.push(return_symbol); + false + } + ZigPackedStruct => { + // Workaround for Zig's incorrect implementation of the C calling convention. + // We need to copy the packed struct into the stack frame + // Load the address before the call so that afterward, it will be 2nd on the value stack, + // ready for the store instruction. + symbols_to_load.push(return_symbol); + true } }; for arg in arguments { let stored = self.symbol_storage_map.get(arg).unwrap(); let arg_types = stored.arg_types(call_conv); - wasm_arg_types.extend_from_slice(arg_types); + num_wasm_args += arg_types.len(); match arg_types.len() { 0 => {} - 1 => wasm_args.push(*arg), - 2 => wasm_args.extend_from_slice(&[*arg, *arg]), + 1 => symbols_to_load.push(*arg), + 2 => symbols_to_load.extend_from_slice(&[*arg, *arg]), n => internal_error!("Cannot have {} Wasm arguments for 1 Roc argument", n), } } // If the symbols were already at the top of the stack, do nothing! // Should be common for simple cases, due to the structure of the Mono IR - if !code_builder.verify_stack_match(&wasm_args) { - if return_method == ReturnMethod::WriteToPointerArg { + if !code_builder.verify_stack_match(&symbols_to_load) { + if matches!(return_method, WriteToPointerArg | ZigPackedStruct) { self.load_return_address_ccc(code_builder, return_symbol); }; @@ -439,7 +447,11 @@ impl<'a> Storage<'a> { } } - (wasm_arg_types, return_type) + ( + num_wasm_args, + has_return_val, + return_method == ZigPackedStruct, + ) } /// Generate code to copy a StoredValue to an arbitrary memory location diff --git a/compiler/gen_wasm/src/wasm_module/mod.rs b/compiler/gen_wasm/src/wasm_module/mod.rs index 24d75701f4..63a2cf5468 100644 --- a/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/compiler/gen_wasm/src/wasm_module/mod.rs @@ -5,7 +5,7 @@ pub mod opcodes; pub mod sections; pub mod serialize; -use bumpalo::Bump; +use bumpalo::{collections::Vec, Bump}; pub use code_builder::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; pub use linking::SymInfo; use roc_error_macros::internal_error; @@ -145,7 +145,7 @@ impl<'a> WasmModule<'a> { let global = GlobalSection::preload(arena, bytes, &mut cursor); - let export = ExportSection::preload_globals(arena, bytes, &mut cursor); + let export = ExportSection::preload(arena, bytes, &mut cursor); let start = OpaqueSection::preload(SectionId::Start, arena, bytes, &mut cursor); @@ -191,10 +191,18 @@ impl<'a> WasmModule<'a> { arena: &'a Bump, called_preload_fns: T, ) { + let exported_fn_iter = self + .export + .exports + .iter() + .filter(|ex| ex.ty == ExportType::Func) + .map(|ex| ex.index); + let function_indices = Vec::from_iter_in(exported_fn_iter, arena); + self.code.remove_dead_preloads( arena, self.import.function_count, - &self.export.function_indices, + &function_indices, called_preload_fns, ) } diff --git a/compiler/gen_wasm/src/wasm_module/sections.rs b/compiler/gen_wasm/src/wasm_module/sections.rs index 270fe303f4..8a4dc106a1 100644 --- a/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/compiler/gen_wasm/src/wasm_module/sections.rs @@ -115,7 +115,7 @@ fn section_size(bytes: &[u8]) -> usize { } fn parse_section<'a>(id: SectionId, module_bytes: &'a [u8], cursor: &mut usize) -> (u32, &'a [u8]) { - if module_bytes[*cursor] != id as u8 { + if (*cursor >= module_bytes.len()) || (module_bytes[*cursor] != id as u8) { return (0, &[]); } *cursor += 1; @@ -809,56 +809,35 @@ impl Serialize for Export<'_> { #[derive(Debug)] pub struct ExportSection<'a> { - pub count: u32, - pub bytes: Vec<'a, u8>, - /// List of exported functions to keep during dead-code-elimination - pub function_indices: Vec<'a, u32>, - /// name -> index - pub globals_lookup: MutMap<&'a [u8], u32>, + pub exports: Vec<'a, Export<'a>>, } impl<'a> ExportSection<'a> { const ID: SectionId = SectionId::Export; - pub fn append(&mut self, export: Export) { - export.serialize(&mut self.bytes); - self.count += 1; - if matches!(export.ty, ExportType::Func) { - self.function_indices.push(export.index); - } + pub fn append(&mut self, export: Export<'a>) { + self.exports.push(export); } pub fn size(&self) -> usize { - section_size(&self.bytes) + self.exports + .iter() + .map(|ex| ex.name.len() + 1 + MAX_SIZE_ENCODED_U32) + .sum() } - fn empty(arena: &'a Bump) -> Self { - ExportSection { - count: 0, - bytes: Vec::with_capacity_in(256, arena), - function_indices: Vec::with_capacity_in(4, arena), - globals_lookup: MutMap::default(), - } - } - - /// Preload from object file. Keep only the Global exports, ignore the rest. - pub fn preload_globals(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + /// Preload from object file. + pub fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { let (num_exports, body_bytes) = parse_section(Self::ID, module_bytes, cursor); - let mut export_section = ExportSection::empty(arena); + let mut export_section = ExportSection { + exports: Vec::with_capacity_in(num_exports as usize, arena), + }; let mut body_cursor = 0; - for _ in 0..num_exports { - let export_start = body_cursor; + while body_cursor < body_bytes.len() { let export = Export::parse(arena, body_bytes, &mut body_cursor); - if matches!(export.ty, ExportType::Global) { - let global_bytes = &body_bytes[export_start..body_cursor]; - export_section.bytes.extend_from_slice(global_bytes); - export_section.count += 1; - export_section - .globals_lookup - .insert(export.name, export.index); - } + export_section.exports.push(export); } export_section @@ -867,10 +846,9 @@ impl<'a> ExportSection<'a> { impl<'a> Serialize for ExportSection<'a> { fn serialize(&self, buffer: &mut T) { - if !self.bytes.is_empty() { + if !self.exports.is_empty() { let header_indices = write_section_header(buffer, Self::ID); - buffer.encode_u32(self.count); - buffer.append_slice(&self.bytes); + self.exports.serialize(buffer); update_section_size(buffer, header_indices); } } @@ -1291,6 +1269,14 @@ impl<'a> NameSection<'a> { } pub fn parse(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { + // If we're already past the end of the preloaded file then there is no Name section + if *cursor >= module_bytes.len() { + return NameSection { + bytes: bumpalo::vec![in arena], + functions: MutMap::default(), + }; + } + // Custom section ID let section_id_byte = module_bytes[*cursor]; if section_id_byte != Self::ID as u8 { diff --git a/compiler/ident/Cargo.toml b/compiler/ident/Cargo.toml index 14edceaab5..d272f4818f 100644 --- a/compiler/ident/Cargo.toml +++ b/compiler/ident/Cargo.toml @@ -4,6 +4,3 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" - -[dependencies] -arrayvec = "0.7.2" diff --git a/compiler/ident/src/lib.rs b/compiler/ident/src/lib.rs index 5b5dbefcdf..f5c37713d1 100644 --- a/compiler/ident/src/lib.rs +++ b/compiler/ident/src/lib.rs @@ -145,6 +145,7 @@ impl IdentStr { } } + #[inline(always)] pub fn as_slice(&self) -> &[u8] { use core::slice::from_raw_parts; @@ -157,6 +158,7 @@ impl IdentStr { } } + #[inline(always)] pub fn as_str(&self) -> &str { let slice = self.as_slice(); diff --git a/compiler/load/build.rs b/compiler/load/build.rs index ed424eb6eb..5f2754723e 100644 --- a/compiler/load/build.rs +++ b/compiler/load/build.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use bumpalo::Bump; +use roc_load_internal::file::Threading; use roc_module::symbol::ModuleId; const MODULES: &[(ModuleId, &str)] = &[ @@ -37,6 +38,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) { Default::default(), target_info, roc_reporting::report::RenderTarget::ColorTerminal, + Threading::AllAvailable, ); let module = res_module.unwrap(); diff --git a/compiler/load/src/lib.rs b/compiler/load/src/lib.rs index 5d5c09daa4..9548d95889 100644 --- a/compiler/load/src/lib.rs +++ b/compiler/load/src/lib.rs @@ -1,3 +1,5 @@ +pub use roc_load_internal::file::Threading; + use bumpalo::Bump; use roc_collections::all::MutMap; use roc_constrain::module::ExposedByModule; @@ -12,6 +14,7 @@ pub use roc_load_internal::file::{ LoadResult, LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase, }; +#[allow(clippy::too_many_arguments)] fn load<'a>( arena: &'a Bump, load_start: LoadStart<'a>, @@ -20,6 +23,7 @@ fn load<'a>( goal_phase: Phase, target_info: TargetInfo, render: RenderTarget, + threading: Threading, ) -> Result, LoadingProblem<'a>> { let cached_subs = read_cached_subs(); @@ -32,6 +36,7 @@ fn load<'a>( target_info, cached_subs, render, + threading, ) } @@ -59,6 +64,7 @@ pub fn load_single_threaded<'a>( ) } +#[allow(clippy::too_many_arguments)] pub fn load_and_monomorphize_from_str<'a>( arena: &'a Bump, filename: PathBuf, @@ -67,6 +73,7 @@ pub fn load_and_monomorphize_from_str<'a>( exposed_types: ExposedByModule, target_info: TargetInfo, render: RenderTarget, + threading: Threading, ) -> Result, LoadingProblem<'a>> { use LoadResult::*; @@ -80,6 +87,7 @@ pub fn load_and_monomorphize_from_str<'a>( Phase::MakeSpecializations, target_info, render, + threading, )? { Monomorphized(module) => Ok(module), TypeChecked(_) => unreachable!(""), @@ -93,6 +101,7 @@ pub fn load_and_monomorphize<'a>( exposed_types: ExposedByModule, target_info: TargetInfo, render: RenderTarget, + threading: Threading, ) -> Result, LoadingProblem<'a>> { use LoadResult::*; @@ -106,6 +115,7 @@ pub fn load_and_monomorphize<'a>( Phase::MakeSpecializations, target_info, render, + threading, )? { Monomorphized(module) => Ok(module), TypeChecked(_) => unreachable!(""), @@ -119,6 +129,7 @@ pub fn load_and_typecheck<'a>( exposed_types: ExposedByModule, target_info: TargetInfo, render: RenderTarget, + threading: Threading, ) -> Result> { use LoadResult::*; @@ -132,6 +143,7 @@ pub fn load_and_typecheck<'a>( Phase::SolveTypes, target_info, render, + threading, )? { Monomorphized(_) => unreachable!(""), TypeChecked(module) => Ok(module), diff --git a/compiler/load_internal/Cargo.toml b/compiler/load_internal/Cargo.toml index 28641d1c1b..798c462551 100644 --- a/compiler/load_internal/Cargo.toml +++ b/compiler/load_internal/Cargo.toml @@ -21,16 +21,14 @@ roc_solve = { path = "../solve" } roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_reporting = { path = "../../reporting" } -morphic_lib = { path = "../../vendor/morphic_lib" } +roc_debug_flags = { path = "../debug_flags" } ven_pretty = { path = "../../vendor/pretty" } bumpalo = { version = "3.8.0", features = ["collections"] } parking_lot = "0.12" crossbeam = "0.8.1" -num_cpus = "1.13.0" [dev-dependencies] -tempfile = "3.2.0" pretty_assertions = "1.0.0" maplit = "1.0.2" indoc = "1.0.3" -roc_test_utils = { path = "../../test_utils" } \ No newline at end of file +roc_test_utils = { path = "../../test_utils" } diff --git a/compiler/load_internal/src/docs.rs b/compiler/load_internal/src/docs.rs index 0400a53533..55cdca9328 100644 --- a/compiler/load_internal/src/docs.rs +++ b/compiler/load_internal/src/docs.rs @@ -1,7 +1,5 @@ use crate::docs::DocEntry::DetachedDoc; -use crate::docs::TypeAnnotation::{ - Apply, BoundVariable, Function, NoTypeAnn, ObscuredRecord, ObscuredTagUnion, Record, TagUnion, -}; +use crate::docs::TypeAnnotation::{Apply, BoundVariable, Function, NoTypeAnn, Record, TagUnion}; use crate::file::LoadedModule; use roc_can::scope::Scope; use roc_error_macros::todo_abilities; @@ -91,14 +89,18 @@ pub struct Tag { pub fn generate_module_docs<'a>( scope: Scope, module_name: ModuleName, - ident_ids: &'a IdentIds, parsed_defs: &'a [Loc>], ) -> ModuleDocumentation { let (entries, _) = parsed_defs .iter() .fold((vec![], None), |(acc, maybe_comments_after), def| { - generate_entry_doc(ident_ids, acc, maybe_comments_after, &def.value) + generate_entry_doc( + &scope.locals.ident_ids, + acc, + maybe_comments_after, + &def.value, + ) }); ModuleDocumentation { @@ -274,36 +276,20 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> ast::TypeAnnotation::TagUnion { tags, ext } => { let mut tags_to_render: Vec = Vec::new(); - let mut any_tags_are_private = false; - for tag in tags.iter() { - match tag_to_doc(in_func_type_ann, tag.value) { - None => { - any_tags_are_private = true; - break; - } - Some(tag_ann) => { - tags_to_render.push(tag_ann); - } + if let Some(tag_ann) = tag_to_doc(in_func_type_ann, tag.value) { + tags_to_render.push(tag_ann); } } - if any_tags_are_private { - if in_func_type_ann { - ObscuredTagUnion - } else { - NoTypeAnn - } - } else { - let extension = match ext { - None => NoTypeAnn, - Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), - }; + let extension = match ext { + None => NoTypeAnn, + Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), + }; - TagUnion { - tags: tags_to_render, - extension: Box::new(extension), - } + TagUnion { + tags: tags_to_render, + extension: Box::new(extension), } } ast::TypeAnnotation::BoundVariable(var_name) => BoundVariable(var_name.to_string()), @@ -328,35 +314,19 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> ast::TypeAnnotation::Record { fields, ext } => { let mut doc_fields = Vec::new(); - let mut any_fields_include_private_tags = false; - for field in fields.items { - match record_field_to_doc(in_func_type_ann, field.value) { - None => { - any_fields_include_private_tags = true; - break; - } - Some(doc_field) => { - doc_fields.push(doc_field); - } + if let Some(doc_field) = record_field_to_doc(in_func_type_ann, field.value) { + doc_fields.push(doc_field); } } - if any_fields_include_private_tags { - if in_func_type_ann { - ObscuredRecord - } else { - NoTypeAnn - } - } else { - let extension = match ext { - None => NoTypeAnn, - Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), - }; + let extension = match ext { + None => NoTypeAnn, + Some(ext_type_ann) => type_to_docs(in_func_type_ann, ext_type_ann.value), + }; - Record { - fields: doc_fields, - extension: Box::new(extension), - } + Record { + fields: doc_fields, + extension: Box::new(extension), } } ast::TypeAnnotation::SpaceBefore(&sub_type_ann, _) => { @@ -404,11 +374,10 @@ fn record_field_to_doc( } } -// The Option here represents if it is private. Private tags -// evaluate to `None`. +// The Option here represents if it is malformed. fn tag_to_doc(in_func_ann: bool, tag: ast::Tag) -> Option { match tag { - ast::Tag::Global { name, args } => Some(Tag { + ast::Tag::Apply { name, args } => Some(Tag { name: name.value.to_string(), values: { let mut type_vars = Vec::new(); @@ -420,7 +389,6 @@ fn tag_to_doc(in_func_ann: bool, tag: ast::Tag) -> Option { type_vars }, }), - ast::Tag::Private { .. } => None, ast::Tag::SpaceBefore(&sub_tag, _) => tag_to_doc(in_func_ann, sub_tag), ast::Tag::SpaceAfter(&sub_tag, _) => tag_to_doc(in_func_ann, sub_tag), ast::Tag::Malformed(_) => None, diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 4af83a07c8..230582b30e 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -15,18 +15,22 @@ use roc_constrain::module::{ constrain_builtin_imports, constrain_module, ExposedByModule, ExposedForModule, ExposedModuleTypes, }; +use roc_debug_flags::{ + dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, + ROC_PRINT_IR_AFTER_SPECIALIZATION, ROC_PRINT_LOAD_LOG, +}; use roc_error_macros::internal_error; -use roc_module::ident::{Ident, ModuleName, QualifiedModuleName, TagName}; +use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::symbol::{ - IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified, - Symbol, + IdentIds, IdentIdsByModule, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, + PackageQualified, Symbol, }; use roc_mono::ir::{ CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, UpdateModeIds, }; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; -use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral}; +use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation}; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName}; use roc_parse::ident::UppercaseIdent; @@ -39,7 +43,7 @@ use roc_solve::solve; use roc_target::TargetInfo; use roc_types::solved_types::Solved; use roc_types::subs::{Subs, VarStore, Variable}; -use roc_types::types::{Alias, AliasCommon, TypeExtension}; +use roc_types::types::{Alias, AliasKind}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::HashMap; use std::io; @@ -70,13 +74,46 @@ const PKG_CONFIG_FILE_NAME: &str = "Package-Config"; /// The . in between module names like Foo.Bar.Baz const MODULE_SEPARATOR: char = '.'; -const SHOW_MESSAGE_LOG: bool = false; - const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024; +const PRELUDE_TYPES: [(&str, Symbol); 33] = [ + ("Num", Symbol::NUM_NUM), + ("Int", Symbol::NUM_INT), + ("Frac", Symbol::NUM_FRAC), + ("Integer", Symbol::NUM_INTEGER), + ("FloatingPoint", Symbol::NUM_FLOATINGPOINT), + ("Binary32", Symbol::NUM_BINARY32), + ("Binary64", Symbol::NUM_BINARY64), + ("Signed128", Symbol::NUM_SIGNED128), + ("Signed64", Symbol::NUM_SIGNED64), + ("Signed32", Symbol::NUM_SIGNED32), + ("Signed16", Symbol::NUM_SIGNED16), + ("Signed8", Symbol::NUM_SIGNED8), + ("Unsigned128", Symbol::NUM_UNSIGNED128), + ("Unsigned64", Symbol::NUM_UNSIGNED64), + ("Unsigned32", Symbol::NUM_UNSIGNED32), + ("Unsigned16", Symbol::NUM_UNSIGNED16), + ("Unsigned8", Symbol::NUM_UNSIGNED8), + ("Natural", Symbol::NUM_NATURAL), + ("Decimal", Symbol::NUM_DECIMAL), + ("Nat", Symbol::NUM_NAT), + ("I8", Symbol::NUM_I8), + ("I16", Symbol::NUM_I16), + ("I32", Symbol::NUM_I32), + ("I64", Symbol::NUM_I64), + ("I128", Symbol::NUM_I128), + ("U8", Symbol::NUM_U8), + ("U16", Symbol::NUM_U16), + ("U32", Symbol::NUM_U32), + ("U64", Symbol::NUM_U64), + ("U128", Symbol::NUM_U128), + ("F32", Symbol::NUM_F32), + ("F64", Symbol::NUM_F64), + ("Dec", Symbol::NUM_DEC), +]; + macro_rules! log { - () => (if SHOW_MESSAGE_LOG { println!()} else {}); - ($($arg:tt)*) => (if SHOW_MESSAGE_LOG { println!($($arg)*); } else {}) + ($($arg:tt)*) => (dbg_do!(ROC_PRINT_LOAD_LOG, println!($($arg)*))) } /// Struct storing various intermediate stages by their ModuleId @@ -168,6 +205,8 @@ impl Default for ModuleCache<'_> { } } +type SharedIdentIdsByModule = Arc>; + fn start_phase<'a>( module_id: ModuleId, phase: Phase, @@ -228,8 +267,7 @@ fn start_phase<'a>( let deps_by_name = &parsed.deps_by_name; let num_deps = deps_by_name.len(); - let mut dep_idents: MutMap = - IdentIds::exposed_builtins(num_deps); + let mut dep_idents: IdentIdsByModule = IdentIds::exposed_builtins(num_deps); let State { ident_ids_by_module, @@ -291,7 +329,11 @@ fn start_phase<'a>( } } - let skip_constraint_gen = state.cached_subs.lock().contains_key(&module_id); + let skip_constraint_gen = { + // Give this its own scope to make sure that the Guard from the lock() is dropped + // immediately after contains_key returns + state.cached_subs.lock().contains_key(&module_id) + }; BuildTask::CanonicalizeAndConstrain { parsed, @@ -419,7 +461,7 @@ pub struct LoadedModule { pub type_problems: MutMap>, pub declarations_by_id: MutMap>, pub exposed_to_host: MutMap, - pub dep_idents: MutMap, + pub dep_idents: IdentIdsByModule, pub exposed_aliases: MutMap, pub exposed_values: Vec, pub sources: MutMap)>, @@ -446,7 +488,7 @@ impl LoadedModule { pub fn exposed_values_str(&self) -> Vec<&str> { self.exposed_values .iter() - .map(|symbol| symbol.ident_str(&self.interns).as_str()) + .map(|symbol| symbol.as_str(&self.interns)) .collect() } } @@ -470,8 +512,9 @@ struct ModuleHeader<'a> { exposes: Vec, exposed_imports: MutMap, parse_state: roc_parse::state::State<'a>, - module_timing: ModuleTiming, header_for: HeaderFor<'a>, + symbols_from_requires: Vec<(Loc, Loc>)>, + module_timing: ModuleTiming, } #[derive(Debug)] @@ -483,7 +526,7 @@ struct ConstrainedModule { constraint: ConstraintSoa, ident_ids: IdentIds, var_store: VarStore, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, module_timing: ModuleTiming, } @@ -518,7 +561,6 @@ pub struct MonomorphizedModule<'a> { pub platform_path: Box, pub can_problems: MutMap>, pub type_problems: MutMap>, - pub mono_problems: MutMap>, pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, pub entry_point: EntryPoint<'a>, pub exposed_to_host: ExposedToHost, @@ -546,10 +588,6 @@ impl<'a> MonomorphizedModule<'a> { total += problems.len(); } - for problems in self.mono_problems.values() { - total += problems.len(); - } - total } } @@ -566,6 +604,7 @@ struct ParsedModule<'a> { exposed_imports: MutMap, parsed_defs: &'a [Loc>], module_name: ModuleNameEnum<'a>, + symbols_from_requires: Vec<(Loc, Loc>)>, header_for: HeaderFor<'a>, } @@ -576,18 +615,14 @@ enum Msg<'a> { Many(Vec>), Header(ModuleHeader<'a>), Parsed(ParsedModule<'a>), - CanonicalizedAndConstrained { - constrained_module: ConstrainedModule, - canonicalization_problems: Vec, - module_docs: Option, - }, + CanonicalizedAndConstrained(CanAndCon), SolvedTypes { module_id: ModuleId, ident_ids: IdentIds, solved_module: SolvedModule, solved_subs: Solved, decls: Vec, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, module_timing: ModuleTiming, abilities_store: AbilitiesStore, }, @@ -595,7 +630,7 @@ enum Msg<'a> { solved_subs: Solved, exposed_vars_by_symbol: Vec<(Symbol, Variable)>, exposed_aliases_by_symbol: MutMap, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, documentation: MutMap, abilities_store: AbilitiesStore, }, @@ -635,6 +670,13 @@ enum Msg<'a> { }, } +#[derive(Debug)] +struct CanAndCon { + constrained_module: ConstrainedModule, + canonicalization_problems: Vec, + module_docs: Option, +} + #[derive(Debug)] enum PlatformPath<'a> { NotSpecified, @@ -668,13 +710,13 @@ struct State<'a> { /// This is the "final" list of IdentIds, after canonicalization and constraint gen /// have completed for a given module. - pub constrained_ident_ids: MutMap, + pub constrained_ident_ids: IdentIdsByModule, /// From now on, these will be used by multiple threads; time to make an Arc>! pub arc_modules: Arc>>, pub arc_shorthands: Arc>>>, - pub ident_ids_by_module: Arc>>, + pub ident_ids_by_module: SharedIdentIdsByModule, pub declarations_by_id: MutMap>, @@ -705,9 +747,10 @@ impl<'a> State<'a> { goal_phase: Phase, exposed_types: ExposedByModule, arc_modules: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, cached_subs: MutMap)>, render: RenderTarget, + number_of_workers: usize, ) -> Self { let arc_shorthands = Arc::new(Mutex::new(MutMap::default())); @@ -731,7 +774,7 @@ impl<'a> State<'a> { declarations_by_id: MutMap::default(), exposed_symbols_by_module: MutMap::default(), timings: MutMap::default(), - layout_caches: std::vec::Vec::with_capacity(num_cpus::get()), + layout_caches: std::vec::Vec::with_capacity(number_of_workers), cached_subs: Arc::new(Mutex::new(cached_subs)), render, } @@ -814,7 +857,7 @@ enum BuildTask<'a> { module_name: PQModuleName<'a>, module_ids: Arc>>, shorthands: Arc>>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, }, Parse { header: ModuleHeader<'a>, @@ -822,7 +865,7 @@ enum BuildTask<'a> { CanonicalizeAndConstrain { parsed: ParsedModule<'a>, module_ids: ModuleIds, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, exposed_symbols: VecSet, aliases: MutMap, skip_constraint_gen: bool, @@ -837,7 +880,7 @@ enum BuildTask<'a> { constraint: ConstraintSoa, var_store: VarStore, declarations: Vec, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, cached_subs: CachedSubs, }, BuildPendingSpecializations { @@ -911,6 +954,7 @@ fn enqueue_task<'a>( Ok(()) } +#[allow(clippy::too_many_arguments)] pub fn load_and_typecheck_str<'a>( arena: &'a Bump, filename: PathBuf, @@ -919,6 +963,7 @@ pub fn load_and_typecheck_str<'a>( exposed_types: ExposedByModule, target_info: TargetInfo, render: RenderTarget, + threading: Threading, ) -> Result> { use LoadResult::*; @@ -937,6 +982,7 @@ pub fn load_and_typecheck_str<'a>( target_info, cached_subs, render, + threading, )? { Monomorphized(_) => unreachable!(""), TypeChecked(module) => Ok(module), @@ -951,7 +997,7 @@ pub enum PrintTarget { pub struct LoadStart<'a> { arc_modules: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, root_id: ModuleId, root_msg: Msg<'a>, } @@ -1054,6 +1100,13 @@ pub enum LoadResult<'a> { Monomorphized(MonomorphizedModule<'a>), } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Threading { + Single, + AllAvailable, + AtMost(usize), +} + /// The loading process works like this, starting from the given filename (e.g. "main.roc"): /// /// 1. Open the file. @@ -1107,11 +1160,34 @@ pub fn load<'a>( target_info: TargetInfo, cached_subs: MutMap)>, render: RenderTarget, + threading: Threading, ) -> Result, LoadingProblem<'a>> { - // When compiling to wasm, we cannot spawn extra threads - // so we have a single-threaded implementation - if cfg!(target_family = "wasm") { - load_single_threaded( + enum Threads { + Single, + Many(usize), + } + + let threads = { + if cfg!(target_family = "wasm") { + // When compiling to wasm, we cannot spawn extra threads + // so we have a single-threaded implementation + Threads::Single + } else { + match std::thread::available_parallelism().map(|v| v.get()) { + Err(_) => Threads::Single, + Ok(0) => unreachable!("NonZeroUsize"), + Ok(1) => Threads::Single, + Ok(reported) => match threading { + Threading::Single => Threads::Single, + Threading::AllAvailable => Threads::Many(reported), + Threading::AtMost(at_most) => Threads::Many(Ord::min(reported, at_most)), + }, + } + } + }; + + match threads { + Threads::Single => load_single_threaded( arena, load_start, src_dir, @@ -1120,9 +1196,8 @@ pub fn load<'a>( target_info, cached_subs, render, - ) - } else { - load_multi_threaded( + ), + Threads::Many(threads) => load_multi_threaded( arena, load_start, src_dir, @@ -1131,7 +1206,8 @@ pub fn load<'a>( target_info, cached_subs, render, - ) + threads, + ), } } @@ -1161,6 +1237,7 @@ pub fn load_single_threaded<'a>( .send(root_msg) .map_err(|_| LoadingProblem::MsgChannelDied)?; + let number_of_workers = 1; let mut state = State::new( root_id, target_info, @@ -1170,6 +1247,7 @@ pub fn load_single_threaded<'a>( ident_ids_by_module, cached_subs, render, + number_of_workers, ); // We'll add tasks to this, and then worker threads will take tasks from it. @@ -1341,6 +1419,7 @@ fn load_multi_threaded<'a>( target_info: TargetInfo, cached_subs: MutMap)>, render: RenderTarget, + available_threads: usize, ) -> Result, LoadingProblem<'a>> { let LoadStart { arc_modules, @@ -1350,6 +1429,28 @@ fn load_multi_threaded<'a>( .. } = load_start; + let (msg_tx, msg_rx) = bounded(1024); + msg_tx + .send(root_msg) + .map_err(|_| LoadingProblem::MsgChannelDied)?; + + // Reserve one CPU for the main thread, and let all the others be eligible + // to spawn workers. + let available_workers = available_threads - 1; + + let num_workers = match env::var("ROC_NUM_WORKERS") { + Ok(env_str) => env_str + .parse::() + .unwrap_or(available_workers) + .min(available_workers), + Err(_) => available_workers, + }; + + assert!( + num_workers >= 1, + "`load_multi_threaded` needs at least one worker" + ); + let mut state = State::new( root_id, target_info, @@ -1359,28 +1460,9 @@ fn load_multi_threaded<'a>( ident_ids_by_module, cached_subs, render, + num_workers, ); - let (msg_tx, msg_rx) = bounded(1024); - msg_tx - .send(root_msg) - .map_err(|_| LoadingProblem::MsgChannelDied)?; - - // Reserve one CPU for the main thread, and let all the others be eligible - // to spawn workers. We use .max(2) to enforce that we always - // end up with at least 1 worker - since (.max(2) - 1) will - // always return a number that's at least 1. Using - // .max(2) on the initial number of CPUs instead of - // doing .max(1) on the entire expression guards against - // num_cpus returning 0, while also avoiding wrapping - // unsigned subtraction overflow. - let default_num_workers = num_cpus::get().max(2) - 1; - - let num_workers = match env::var("ROC_NUM_WORKERS") { - Ok(env_str) => env_str.parse::().unwrap_or(default_num_workers), - Err(_) => default_num_workers, - }; - // an arena for every worker, stored in an arena-allocated bumpalo vec to make the lifetimes work let arenas = std::iter::repeat_with(Bump::new).take(num_workers); let worker_arenas = arena.alloc(bumpalo::collections::Vec::from_iter_in(arenas, arena)); @@ -1631,21 +1713,20 @@ fn start_tasks<'a>( Ok(()) } -#[cfg(debug_assertions)] -fn debug_print_ir(state: &State, flag: &str) { - if env::var(flag) != Ok("1".into()) { - return; - } +macro_rules! debug_print_ir { + ($state:expr, $flag:path) => { + dbg_do!($flag, { + let procs_string = $state + .procedures + .values() + .map(|proc| proc.to_pretty(200)) + .collect::>(); - let procs_string = state - .procedures - .values() - .map(|proc| proc.to_pretty(200)) - .collect::>(); + let result = procs_string.join("\n"); - let result = procs_string.join("\n"); - - println!("{}", result); + eprintln!("{}", result); + }) + }; } /// Report modules that are imported, but from which nothing is used @@ -1800,46 +1881,10 @@ fn update<'a>( .imported_modules .insert(ModuleId::NUM, Region::zero()); - let prelude_types = [ - (Ident::from("Num"), Symbol::NUM_NUM), - (Ident::from("Int"), Symbol::NUM_INT), - (Ident::from("Float"), Symbol::NUM_FLOAT), - (Ident::from("Integer"), Symbol::NUM_INTEGER), - (Ident::from("FloatingPoint"), Symbol::NUM_FLOATINGPOINT), - (Ident::from("Binary32"), Symbol::NUM_BINARY32), - (Ident::from("Binary64"), Symbol::NUM_BINARY64), - (Ident::from("Signed128"), Symbol::NUM_SIGNED128), - (Ident::from("Signed64"), Symbol::NUM_SIGNED64), - (Ident::from("Signed32"), Symbol::NUM_SIGNED32), - (Ident::from("Signed16"), Symbol::NUM_SIGNED16), - (Ident::from("Signed8"), Symbol::NUM_SIGNED8), - (Ident::from("Unsigned128"), Symbol::NUM_UNSIGNED128), - (Ident::from("Unsigned64"), Symbol::NUM_UNSIGNED64), - (Ident::from("Unsigned32"), Symbol::NUM_UNSIGNED32), - (Ident::from("Unsigned16"), Symbol::NUM_UNSIGNED16), - (Ident::from("Unsigned8"), Symbol::NUM_UNSIGNED8), - (Ident::from("Natural"), Symbol::NUM_NATURAL), - (Ident::from("Decimal"), Symbol::NUM_DECIMAL), - (Ident::from("Nat"), Symbol::NUM_NAT), - (Ident::from("I8"), Symbol::NUM_I8), - (Ident::from("I16"), Symbol::NUM_I16), - (Ident::from("I32"), Symbol::NUM_I32), - (Ident::from("I64"), Symbol::NUM_I64), - (Ident::from("I128"), Symbol::NUM_I128), - (Ident::from("U8"), Symbol::NUM_U8), - (Ident::from("U16"), Symbol::NUM_U16), - (Ident::from("U32"), Symbol::NUM_U32), - (Ident::from("U64"), Symbol::NUM_U64), - (Ident::from("U128"), Symbol::NUM_U128), - (Ident::from("F32"), Symbol::NUM_F32), - (Ident::from("F64"), Symbol::NUM_F64), - (Ident::from("Dec"), Symbol::NUM_DEC), - ]; - - for (ident, symbol) in prelude_types { + for (type_name, symbol) in PRELUDE_TYPES { header .exposed_imports - .insert(ident, (symbol, Region::zero())); + .insert(Ident::from(type_name), (symbol, Region::zero())); } } @@ -1962,11 +2007,11 @@ fn update<'a>( Ok(state) } - CanonicalizedAndConstrained { + CanonicalizedAndConstrained(CanAndCon { constrained_module, canonicalization_problems, module_docs, - } => { + }) => { let module_id = constrained_module.module.module_id; log!("generated constraints for {:?}", module_id); state @@ -2185,8 +2230,7 @@ fn update<'a>( && state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations { - #[cfg(debug_assertions)] - debug_print_ir(&state, "PRINT_IR_AFTER_SPECIALIZATION"); + debug_print_ir!(state, ROC_PRINT_IR_AFTER_SPECIALIZATION); Proc::insert_reset_reuse_operations( arena, @@ -2196,13 +2240,17 @@ fn update<'a>( &mut state.procedures, ); - #[cfg(debug_assertions)] - debug_print_ir(&state, "PRINT_IR_AFTER_RESET_REUSE"); + debug_print_ir!(state, ROC_PRINT_IR_AFTER_RESET_REUSE); - Proc::insert_refcount_operations(arena, &mut state.procedures); + Proc::insert_refcount_operations( + arena, + module_id, + &mut ident_ids, + &mut update_mode_ids, + &mut state.procedures, + ); - #[cfg(debug_assertions)] - debug_print_ir(&state, "PRINT_IR_AFTER_REFCOUNT"); + debug_print_ir!(state, ROC_PRINT_IR_AFTER_REFCOUNT); // This is not safe with the new non-recursive RC updates that we do for tag unions // @@ -2320,7 +2368,6 @@ fn finish_specialization( } = state; let ModuleCache { - mono_problems, type_problems, can_problems, sources, @@ -2383,7 +2430,6 @@ fn finish_specialization( Ok(MonomorphizedModule { can_problems, - mono_problems, type_problems, output_path: output_path.unwrap_or(DEFAULT_APP_OUTPUT_PATH).into(), platform_path, @@ -2403,7 +2449,7 @@ fn finish( solved: Solved, exposed_aliases_by_symbol: MutMap, exposed_vars_by_symbol: Vec<(Symbol, Variable)>, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, documentation: MutMap, abilities_store: AbilitiesStore, ) -> LoadedModule { @@ -2451,7 +2497,7 @@ fn load_pkg_config<'a>( shorthand: &'a str, app_module_id: ModuleId, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, ) -> Result, LoadingProblem<'a>> { let module_start_time = SystemTime::now(); @@ -2499,7 +2545,7 @@ fn load_pkg_config<'a>( let pkg_config_module_msg = fabricate_pkg_config_module( arena, shorthand, - app_module_id, + Some(app_module_id), filename, parser_state, module_ids.clone(), @@ -2569,7 +2615,7 @@ fn load_builtin_module_help<'a>( fn load_builtin_module<'a>( arena: &'a Bump, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_timing: ModuleTiming, module_id: ModuleId, module_name: &str, @@ -2594,7 +2640,7 @@ fn load_module<'a>( module_name: PQModuleName<'a>, module_ids: Arc>>, arc_shorthands: Arc>>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let module_start_time = SystemTime::now(); @@ -2781,7 +2827,7 @@ fn parse_header<'a>( is_root_module: bool, opt_shorthand: Option<&'a str>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, src_bytes: &'a [u8], start_time: SystemTime, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { @@ -2947,11 +2993,18 @@ fn parse_header<'a>( To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)), } } - Ok((ast::Module::Platform { header }, _parse_state)) => { - Err(LoadingProblem::UnexpectedHeader(format!( - "got an unexpected platform header\n{:?}", - header - ))) + Ok((ast::Module::Platform { header }, parse_state)) => { + Ok(fabricate_pkg_config_module( + arena, + "", // Use a shorthand of "" - it will be fine for `roc check` and bindgen + None, + filename, + parse_state, + module_ids.clone(), + ident_ids_by_module, + &header, + module_timing, + )) } Err(fail) => Err(LoadingProblem::ParsingFailed( @@ -2969,7 +3022,7 @@ fn load_filename<'a>( is_root_module: bool, opt_shorthand: Option<&'a str>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_start_time: SystemTime, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let file_io_start = SystemTime::now(); @@ -3003,7 +3056,7 @@ fn load_from_str<'a>( filename: PathBuf, src: &'a str, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_start_time: SystemTime, ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let file_io_start = SystemTime::now(); @@ -3039,7 +3092,7 @@ fn send_header<'a>( info: HeaderInfo<'a>, parse_state: roc_parse::state::State<'a>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { use ModuleNameEnum::*; @@ -3102,9 +3155,7 @@ fn send_header<'a>( home = module_ids.get_or_insert(&name); // Ensure this module has an entry in the exposed_ident_ids map. - ident_ids_by_module - .entry(home) - .or_insert_with(IdentIds::default); + ident_ids_by_module.get_or_insert(home); // For each of our imports, add an entry to deps_by_name // @@ -3131,9 +3182,7 @@ fn send_header<'a>( // Add the new exposed idents to the dep module's IdentIds, so // once that module later gets loaded, its lookups will resolve // to the same symbols as the ones we're using here. - let ident_ids = ident_ids_by_module - .entry(module_id) - .or_insert_with(IdentIds::default); + let ident_ids = ident_ids_by_module.get_or_insert(module_id); for ident in exposed_idents { let ident_id = ident_ids.get_or_insert(&ident); @@ -3230,8 +3279,9 @@ fn send_header<'a>( exposes: exposed, parse_state, exposed_imports: scope, - module_timing, + symbols_from_requires: Vec::new(), header_for: extra, + module_timing, }), ) } @@ -3241,7 +3291,7 @@ struct PlatformHeaderInfo<'a> { filename: PathBuf, is_root_module: bool, shorthand: &'a str, - app_module_id: ModuleId, + opt_app_module_id: Option, packages: &'a [Loc>], provides: &'a [Loc>], requires: &'a [Loc>], @@ -3255,14 +3305,14 @@ fn send_header_two<'a>( info: PlatformHeaderInfo<'a>, parse_state: roc_parse::state::State<'a>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { let PlatformHeaderInfo { filename, shorthand, is_root_module, - app_module_id, + opt_app_module_id, packages, provides, requires, @@ -3271,6 +3321,7 @@ fn send_header_two<'a>( } = info; let declared_name: ModuleName = "".into(); + let mut symbols_from_requires = Vec::with_capacity(requires.len()); let mut imported: Vec<(QualifiedModuleName, Vec, Region)> = Vec::with_capacity(imports.len()); @@ -3280,12 +3331,16 @@ fn send_header_two<'a>( let mut deps_by_name: MutMap = HashMap::with_capacity_and_hasher(num_exposes, default_hasher()); - // add standard imports - imported_modules.insert(app_module_id, Region::zero()); - deps_by_name.insert( - PQModuleName::Unqualified(ModuleName::APP.into()), - app_module_id, - ); + // Add standard imports, if there is an app module. + // (There might not be, e.g. when running `roc check Package-Config.roc` or + // when generating bindings.) + if let Some(app_module_id) = opt_app_module_id { + imported_modules.insert(app_module_id, Region::zero()); + deps_by_name.insert( + PQModuleName::Unqualified(ModuleName::APP.into()), + app_module_id, + ); + } let mut scope_size = 0; @@ -3314,9 +3369,7 @@ fn send_header_two<'a>( home = module_ids.get_or_insert(&name); // Ensure this module has an entry in the exposed_ident_ids map. - ident_ids_by_module - .entry(home) - .or_insert_with(IdentIds::default); + ident_ids_by_module.get_or_insert(home); // For each of our imports, add an entry to deps_by_name // @@ -3338,9 +3391,7 @@ fn send_header_two<'a>( // Add the new exposed idents to the dep module's IdentIds, so // once that module later gets loaded, its lookups will resolve // to the same symbols as the ones we're using here. - let ident_ids = ident_ids_by_module - .entry(module_id) - .or_insert_with(IdentIds::default); + let ident_ids = ident_ids_by_module.get_or_insert(module_id); for ident in exposed_idents { let ident_id = ident_ids.get_or_insert(&ident); @@ -3354,32 +3405,37 @@ fn send_header_two<'a>( } { - let ident_ids = ident_ids_by_module - .entry(app_module_id) - .or_insert_with(IdentIds::default); + // If we don't have an app module id (e.g. because we're doing + // `roc check Package-Config.roc` or because we're doing bindgen), + // insert the `requires` symbols into the platform module's IdentIds. + // + // Otherwise, get them from the app module's IdentIds, because it + // should already have a symbol for each `requires` entry, and we + // want to make sure we're referencing the same symbols! + let module_id = opt_app_module_id.unwrap_or(home); + let ident_ids = ident_ids_by_module.get_or_insert(module_id); for entry in requires { let entry = entry.value; - let ident: Ident = entry.ident.value.into(); let ident_id = ident_ids.get_or_insert(&ident); - let symbol = Symbol::new(app_module_id, ident_id); + let symbol = Symbol::new(module_id, ident_id); // Since this value is exposed, add it to our module's default scope. debug_assert!(!scope.contains_key(&ident.clone())); scope.insert(ident, (symbol, entry.ident.region)); + symbols_from_requires.push((Loc::at(entry.ident.region, symbol), entry.ann)); } for entry in requires_types { let string: &str = entry.value.into(); let ident: Ident = string.into(); let ident_id = ident_ids.get_or_insert(&ident); - let symbol = Symbol::new(app_module_id, ident_id); + let symbol = Symbol::new(module_id, ident_id); // Since this value is exposed, add it to our module's default scope. debug_assert!(!scope.contains_key(&ident.clone())); - scope.insert(ident, (symbol, entry.region)); } } @@ -3469,6 +3525,7 @@ fn send_header_two<'a>( parse_state, exposed_imports: scope, module_timing, + symbols_from_requires, header_for: extra, }), ) @@ -3486,7 +3543,7 @@ impl<'a> BuildTask<'a> { var_store: VarStore, imported_modules: MutMap, exposed_types: &mut ExposedByModule, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, declarations: Vec, cached_subs: CachedSubs, ) -> Self { @@ -3668,7 +3725,7 @@ fn run_solve<'a>( constraint: ConstraintSoa, var_store: VarStore, decls: Vec, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, cached_subs: CachedSubs, ) -> Msg<'a> { let solve_start = SystemTime::now(); @@ -3754,19 +3811,23 @@ fn unspace<'a, T: Copy>(arena: &'a Bump, items: &[Loc>]) -> &'a [L fn fabricate_pkg_config_module<'a>( arena: &'a Bump, shorthand: &'a str, - app_module_id: ModuleId, + opt_app_module_id: Option, filename: PathBuf, parse_state: roc_parse::state::State<'a>, module_ids: Arc>>, - ident_ids_by_module: Arc>>, + ident_ids_by_module: SharedIdentIdsByModule, header: &PlatformHeader<'a>, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { + // If we have an app module, then it's the root module; + // otherwise, we must be the root. + let is_root_module = opt_app_module_id.is_none(); + let info = PlatformHeaderInfo { filename, - is_root_module: false, + is_root_module, shorthand, - app_module_id, + opt_app_module_id, packages: &[], provides: unspace(arena, header.provides.items), requires: &*arena.alloc([Loc::at( @@ -3791,12 +3852,12 @@ fn fabricate_pkg_config_module<'a>( fn canonicalize_and_constrain<'a>( arena: &'a Bump, module_ids: &ModuleIds, - dep_idents: MutMap, + dep_idents: IdentIdsByModule, exposed_symbols: VecSet, aliases: MutMap, parsed: ParsedModule<'a>, skip_constraint_gen: bool, -) -> Result, LoadingProblem<'a>> { +) -> CanAndCon { let canonicalize_start = SystemTime::now(); let ParsedModule { @@ -3808,13 +3869,14 @@ fn canonicalize_and_constrain<'a>( exposed_imports, imported_modules, mut module_timing, + symbols_from_requires, .. } = parsed; let before = roc_types::types::get_type_clone_count(); let mut var_store = VarStore::default(); - let canonicalized = canonicalize_module_defs( + let module_output = canonicalize_module_defs( arena, parsed_defs, &header_for, @@ -3825,6 +3887,7 @@ fn canonicalize_and_constrain<'a>( aliases, exposed_imports, &exposed_symbols, + &symbols_from_requires, &mut var_store, ); @@ -3842,106 +3905,96 @@ fn canonicalize_and_constrain<'a>( module_timing.canonicalize = canonicalize_end.duration_since(canonicalize_start).unwrap(); - match canonicalized { - Ok(module_output) => { - // Generate documentation information - // TODO: store timing information? - let module_docs = match module_name { - ModuleNameEnum::PkgConfig => None, - ModuleNameEnum::App(_) => None, - ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => { - let docs = crate::docs::generate_module_docs( - module_output.scope.clone(), - name.as_str().into(), - &module_output.ident_ids, - parsed_defs, - ); - - Some(docs) - } - }; - - let before = roc_types::types::get_type_clone_count(); - - let mut constraints = Constraints::new(); - - let constraint = if skip_constraint_gen { - roc_can::constraint::Constraint::True - } else { - constrain_module( - &mut constraints, - &module_output.scope.abilities_store, - &module_output.declarations, - module_id, - ) - }; - - let after = roc_types::types::get_type_clone_count(); - - log!( - "constraint gen of {:?} cloned Type {} times ({} -> {})", - module_id, - after - before, - before, - after + // Generate documentation information + // TODO: store timing information? + let module_docs = match module_name { + ModuleNameEnum::PkgConfig => None, + ModuleNameEnum::App(_) => None, + ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => { + let docs = crate::docs::generate_module_docs( + module_output.scope.clone(), + name.as_str().into(), + parsed_defs, ); - // scope has imported aliases, but misses aliases from inner scopes - // module_output.aliases does have those aliases, so we combine them - let mut aliases: MutMap = module_output - .aliases - .into_iter() - .map(|(k, v)| (k, (true, v))) - .collect(); - for (name, alias) in module_output.scope.aliases { - match aliases.entry(name) { - Occupied(_) => { - // do nothing - } - Vacant(vacant) => { - if !name.is_builtin() { - vacant.insert((false, alias)); - } - } + Some(docs) + } + }; + + let before = roc_types::types::get_type_clone_count(); + + let mut constraints = Constraints::new(); + + let constraint = if skip_constraint_gen { + roc_can::constraint::Constraint::True + } else { + constrain_module( + &mut constraints, + module_output.symbols_from_requires, + &module_output.scope.abilities_store, + &module_output.declarations, + module_id, + ) + }; + + let after = roc_types::types::get_type_clone_count(); + + log!( + "constraint gen of {:?} cloned Type {} times ({} -> {})", + module_id, + after - before, + before, + after + ); + + // scope has imported aliases, but misses aliases from inner scopes + // module_output.aliases does have those aliases, so we combine them + let mut aliases: MutMap = module_output + .aliases + .into_iter() + .map(|(k, v)| (k, (true, v))) + .collect(); + for (name, alias) in module_output.scope.aliases { + match aliases.entry(name) { + Occupied(_) => { + // do nothing + } + Vacant(vacant) => { + if !name.is_builtin() { + vacant.insert((false, alias)); } } - - let module = Module { - module_id, - exposed_imports: module_output.exposed_imports, - exposed_symbols, - referenced_values: module_output.referenced_values, - referenced_types: module_output.referenced_types, - aliases, - rigid_variables: module_output.rigid_variables, - abilities_store: module_output.scope.abilities_store, - }; - - let constrained_module = ConstrainedModule { - module, - declarations: module_output.declarations, - imported_modules, - var_store, - constraints, - constraint, - ident_ids: module_output.ident_ids, - dep_idents, - module_timing, - }; - - Ok(Msg::CanonicalizedAndConstrained { - constrained_module, - canonicalization_problems: module_output.problems, - module_docs, - }) - } - Err(runtime_error) => { - panic!( - "TODO gracefully handle module canonicalization error {:?}", - runtime_error - ); } } + + let module = Module { + module_id, + exposed_imports: module_output.exposed_imports, + exposed_symbols, + referenced_values: module_output.referenced_values, + referenced_types: module_output.referenced_types, + aliases, + rigid_variables: module_output.rigid_variables, + abilities_store: module_output.scope.abilities_store, + }; + + let constrained_module = ConstrainedModule { + module, + declarations: module_output.declarations, + imported_modules, + var_store, + constraints, + constraint, + ident_ids: module_output.scope.locals.ident_ids, + dep_idents, + module_timing, + }; + + CanAndCon { + constrained_module, + canonicalization_problems: module_output.problems, + module_docs, + } } fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, LoadingProblem<'a>> { @@ -3981,6 +4034,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi exposed_imports, module_path, header_for, + symbols_from_requires, .. } = header; @@ -3995,6 +4049,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi exposed_ident_ids, exposed_imports, parsed_defs, + symbols_from_requires, header_for, }; @@ -4052,7 +4107,7 @@ fn make_specializations<'a>( specializations_we_must_make: Vec, mut module_timing: ModuleTiming, target_info: TargetInfo, - abilities_store: AbilitiesStore, + mut abilities_store: AbilitiesStore, ) -> Msg<'a> { let make_specializations_start = SystemTime::now(); let mut mono_problems = Vec::new(); @@ -4068,7 +4123,7 @@ fn make_specializations<'a>( update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, - abilities_store: &abilities_store, + abilities_store: &mut abilities_store, }; let mut procs = Procs::new_in(arena); @@ -4139,7 +4194,7 @@ fn build_pending_specializations<'a>( target_info: TargetInfo, // TODO remove exposed_to_host: ExposedToHost, - abilities_store: AbilitiesStore, + mut abilities_store: AbilitiesStore, ) -> Msg<'a> { let find_specializations_start = SystemTime::now(); @@ -4166,7 +4221,7 @@ fn build_pending_specializations<'a>( update_mode_ids: &mut update_mode_ids, // call_specialization_counter=0 is reserved call_specialization_counter: 1, - abilities_store: &abilities_store, + abilities_store: &mut abilities_store, }; // Add modules' decls to Procs @@ -4301,7 +4356,6 @@ fn add_def_to_module<'a>( let partial_proc = PartialProc::from_named_function( mono_env, - layout_cache, annotation, loc_args, *loc_body, @@ -4409,15 +4463,19 @@ fn run_task<'a>( exposed_symbols, aliases, skip_constraint_gen, - } => canonicalize_and_constrain( - arena, - &module_ids, - dep_idents, - exposed_symbols, - aliases, - parsed, - skip_constraint_gen, - ), + } => { + let can_and_con = canonicalize_and_constrain( + arena, + &module_ids, + dep_idents, + exposed_symbols, + aliases, + parsed, + skip_constraint_gen, + ); + + Ok(Msg::CanonicalizedAndConstrained(can_and_con)) + } Solve { module, module_timing, @@ -4576,7 +4634,7 @@ fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { fn to_parse_problem_report<'a>( problem: FileError<'a, SyntaxError<'a>>, mut module_ids: ModuleIds, - all_ident_ids: MutMap, + all_ident_ids: IdentIdsByModule, render: RenderTarget, ) -> String { use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE}; @@ -4707,206 +4765,41 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin /// Builtin aliases that are not covered by type checker optimizations /// /// Types like `F64` and `I32` are hardcoded into Subs and therefore we don't define them here. -/// All that remains are the generic number types (Num, Int, Float) and Result +/// Generic number types (Num, Int, Float, etc.) are treated as `DelayedAlias`es resolved during +/// type solving. +/// All that remains are Signed8, Signed16, etc. fn default_aliases() -> roc_solve::solve::Aliases { use roc_types::types::Type; let mut solve_aliases = roc_solve::solve::Aliases::default(); - let mut var_store = VarStore::default(); - - { - let symbol = Symbol::NUM_NUM; - let tvar = var_store.fresh(); - - let typ = Type::TagUnion( - vec![( - TagName::Private(Symbol::NUM_AT_NUM), - vec![Type::Variable(tvar)], - )], - TypeExtension::Closed, - ); - - let alias = Alias { - region: Region::zero(), - type_variables: vec![Loc::at_zero(("range".into(), tvar))], - lambda_set_variables: Default::default(), - recursion_variables: Default::default(), - typ, - kind: roc_types::types::AliasKind::Structural, - }; - - solve_aliases.insert(symbol, alias); - } - - // FloatingPoint range : [ @FloatingPoint range ] - { - let symbol = Symbol::NUM_FLOATINGPOINT; - let tvar = var_store.fresh(); - - let typ = Type::TagUnion( - vec![( - TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), - vec![Type::Variable(tvar)], - )], - TypeExtension::Closed, - ); - - let alias = Alias { - region: Region::zero(), - type_variables: vec![Loc::at_zero(("range".into(), tvar))], - lambda_set_variables: Default::default(), - recursion_variables: Default::default(), - typ, - kind: roc_types::types::AliasKind::Structural, - }; - - solve_aliases.insert(symbol, alias); - } - - // Int range : Num (Integer range) - { - let symbol = Symbol::NUM_INT; - let tvar = var_store.fresh(); - - let typ = Type::DelayedAlias(AliasCommon { - symbol: Symbol::NUM_NUM, - type_arguments: vec![( - "range".into(), - Type::DelayedAlias(AliasCommon { - symbol: Symbol::NUM_INTEGER, - type_arguments: vec![("range".into(), Type::Variable(tvar))], - lambda_set_variables: vec![], - }), - )], - lambda_set_variables: vec![], - }); - - let alias = Alias { - region: Region::zero(), - type_variables: vec![Loc::at_zero(("range".into(), tvar))], - lambda_set_variables: Default::default(), - recursion_variables: Default::default(), - typ, - kind: roc_types::types::AliasKind::Structural, - }; - - solve_aliases.insert(symbol, alias); - } - - { - let symbol = Symbol::NUM_FLOAT; - let tvar = var_store.fresh(); - - let typ = Type::DelayedAlias(AliasCommon { - symbol: Symbol::NUM_NUM, - type_arguments: vec![( - "range".into(), - Type::DelayedAlias(AliasCommon { - symbol: Symbol::NUM_FLOATINGPOINT, - type_arguments: vec![("range".into(), Type::Variable(tvar))], - lambda_set_variables: vec![], - }), - )], - lambda_set_variables: vec![], - }); - - let alias = Alias { - region: Region::zero(), - type_variables: vec![Loc::at_zero(("range".into(), tvar))], - lambda_set_variables: Default::default(), - recursion_variables: Default::default(), - typ, - kind: roc_types::types::AliasKind::Structural, - }; - - solve_aliases.insert(symbol, alias); - } - - { - let symbol = Symbol::NUM_INTEGER; - let tvar = var_store.fresh(); - - let typ = Type::TagUnion( - vec![( - TagName::Private(Symbol::NUM_AT_INTEGER), - vec![Type::Variable(tvar)], - )], - TypeExtension::Closed, - ); - - let alias = Alias { - region: Region::zero(), - type_variables: vec![Loc::at_zero(("range".into(), tvar))], - lambda_set_variables: Default::default(), - recursion_variables: Default::default(), - typ, - kind: roc_types::types::AliasKind::Structural, - }; - - solve_aliases.insert(symbol, alias); - } - - { - let symbol = Symbol::RESULT_RESULT; - let tvar1 = var_store.fresh(); - let tvar2 = var_store.fresh(); - - let typ = Type::TagUnion( - vec![ - (TagName::Global("Ok".into()), vec![Type::Variable(tvar1)]), - (TagName::Global("Err".into()), vec![Type::Variable(tvar2)]), - ], - TypeExtension::Closed, - ); - - let alias = Alias { - region: Region::zero(), - type_variables: vec![ - Loc::at_zero(("ok".into(), tvar1)), - Loc::at_zero(("err".into(), tvar2)), - ], - lambda_set_variables: Default::default(), - recursion_variables: Default::default(), - typ, - kind: roc_types::types::AliasKind::Structural, - }; - - solve_aliases.insert(symbol, alias); - } - - let mut unit_function = |alias_name: Symbol, at_tag_name: Symbol| { - let typ = Type::TagUnion( - vec![(TagName::Private(at_tag_name), vec![])], - TypeExtension::Closed, - ); - + let mut zero_opaque = |alias_name: Symbol| { let alias = Alias { region: Region::zero(), type_variables: vec![], lambda_set_variables: Default::default(), recursion_variables: Default::default(), - typ, - kind: roc_types::types::AliasKind::Structural, + typ: Type::EmptyTagUnion, + kind: AliasKind::Opaque, }; solve_aliases.insert(alias_name, alias); }; - unit_function(Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8); - unit_function(Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16); - unit_function(Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32); - unit_function(Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64); - unit_function(Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128); + zero_opaque(Symbol::NUM_SIGNED8); + zero_opaque(Symbol::NUM_SIGNED16); + zero_opaque(Symbol::NUM_SIGNED32); + zero_opaque(Symbol::NUM_SIGNED64); + zero_opaque(Symbol::NUM_SIGNED128); - unit_function(Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8); - unit_function(Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16); - unit_function(Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32); - unit_function(Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64); - unit_function(Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128); + zero_opaque(Symbol::NUM_UNSIGNED8); + zero_opaque(Symbol::NUM_UNSIGNED16); + zero_opaque(Symbol::NUM_UNSIGNED32); + zero_opaque(Symbol::NUM_UNSIGNED64); + zero_opaque(Symbol::NUM_UNSIGNED128); - unit_function(Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32); - unit_function(Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64); + zero_opaque(Symbol::NUM_BINARY32); + zero_opaque(Symbol::NUM_BINARY64); solve_aliases } diff --git a/compiler/load_internal/tests/fixtures/build/app_with_deps/AStar.roc b/compiler/load_internal/tests/fixtures/build/app_with_deps/AStar.roc index 3c719d7642..217f6fbc63 100644 --- a/compiler/load_internal/tests/fixtures/build/app_with_deps/AStar.roc +++ b/compiler/load_internal/tests/fixtures/build/app_with_deps/AStar.roc @@ -64,7 +64,7 @@ updateCost = \current, neighbour, model -> distanceTo = reconstructPath newCameFrom neighbour |> List.len - |> Num.toFloat + |> Num.toFrac newModel = { model & costs : newCosts , cameFrom : newCameFrom } diff --git a/compiler/load_internal/tests/fixtures/build/app_with_deps/WithBuiltins.roc b/compiler/load_internal/tests/fixtures/build/app_with_deps/WithBuiltins.roc index 4082739f17..1b2ce3699d 100644 --- a/compiler/load_internal/tests/fixtures/build/app_with_deps/WithBuiltins.roc +++ b/compiler/load_internal/tests/fixtures/build/app_with_deps/WithBuiltins.roc @@ -2,13 +2,13 @@ interface WithBuiltins exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ] imports [ Dep1, Dep2.{ two } ] -floatTest = Num.maxFloat +floatTest = Num.maxF64 divisionFn = Num.div x = 5.0 -divisionTest = Num.maxFloat / x +divisionTest = Num.maxF64 / x intTest = Num.maxI64 diff --git a/compiler/load_internal/tests/fixtures/build/interface_with_deps/AStar.roc b/compiler/load_internal/tests/fixtures/build/interface_with_deps/AStar.roc index 2734330984..1c46526781 100644 --- a/compiler/load_internal/tests/fixtures/build/interface_with_deps/AStar.roc +++ b/compiler/load_internal/tests/fixtures/build/interface_with_deps/AStar.roc @@ -64,7 +64,7 @@ updateCost = \current, neighbour, model -> distanceTo = reconstructPath newCameFrom neighbour |> List.len - |> Num.toFloat + |> Num.toFrac newModel = { model & costs : newCosts , cameFrom : newCameFrom } diff --git a/compiler/load_internal/tests/fixtures/build/interface_with_deps/WithBuiltins.roc b/compiler/load_internal/tests/fixtures/build/interface_with_deps/WithBuiltins.roc index 4082739f17..1b2ce3699d 100644 --- a/compiler/load_internal/tests/fixtures/build/interface_with_deps/WithBuiltins.roc +++ b/compiler/load_internal/tests/fixtures/build/interface_with_deps/WithBuiltins.roc @@ -2,13 +2,13 @@ interface WithBuiltins exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ] imports [ Dep1, Dep2.{ two } ] -floatTest = Num.maxFloat +floatTest = Num.maxF64 divisionFn = Num.div x = 5.0 -divisionTest = Num.maxFloat / x +divisionTest = Num.maxF64 / x intTest = Num.maxI64 diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index 6f1bfffee3..82a65a0c8b 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -19,6 +19,7 @@ mod test_load { use roc_can::def::Declaration::*; use roc_can::def::Def; use roc_constrain::module::ExposedByModule; + use roc_load_internal::file::Threading; use roc_load_internal::file::{LoadResult, LoadStart, LoadedModule, LoadingProblem, Phase}; use roc_module::ident::ModuleName; use roc_module::symbol::{Interns, ModuleId}; @@ -28,7 +29,7 @@ mod test_load { use roc_reporting::report::RenderTarget; use roc_reporting::report::RocDocAllocator; use roc_target::TargetInfo; - use roc_types::pretty_print::{content_to_string, name_all_type_vars}; + use roc_types::pretty_print::name_and_print_var; use roc_types::subs::Subs; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -53,6 +54,7 @@ mod test_load { target_info, Default::default(), // these tests will re-compile the builtins RenderTarget::Generic, + Threading::Single, )? { Monomorphized(_) => unreachable!(""), TypeChecked(module) => Ok(module), @@ -235,10 +237,7 @@ mod test_load { expected_types: &mut HashMap<&str, &str>, ) { for (symbol, expr_var) in &def.pattern_vars { - name_all_type_vars(*expr_var, subs); - - let content = subs.get_content_without_compacting(*expr_var); - let actual_str = content_to_string(content, subs, home, interns); + let actual_str = name_and_print_var(*expr_var, subs, home, interns); let fully_qualified = symbol.fully_qualified(interns, home).to_string(); let expected_type = expected_types .remove(fully_qualified.as_str()) @@ -418,12 +417,13 @@ mod test_load { expect_types( loaded_module, hashmap! { - "floatTest" => "Float *", + "floatTest" => "F64", "divisionFn" => "Float a, Float a -> Float a", - "divisionTest" => "Float *", - "intTest" => "I64", "x" => "Float *", + "divisionTest" => "F64", + "intTest" => "I64", "constantNum" => "Num *", + "divisionTest" => "F64", "divDep1ByDep2" => "Float *", "fromDep2" => "Float *", }, @@ -757,9 +757,9 @@ mod test_load { r#" interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ] - twenty = $Age 20 + twenty = @Age 20 - readAge = \$Age n -> n + readAge = \@Age n -> n "# ), ), @@ -775,7 +775,7 @@ mod test_load { The unwrapped opaque type Age referenced here: - 3│ twenty = $Age 20 + 3│ twenty = @Age 20 ^^^^ is imported from another module: @@ -789,7 +789,7 @@ mod test_load { The unwrapped opaque type Age referenced here: - 5│ readAge = \$Age n -> n + 5│ readAge = \@Age n -> n ^^^^ is imported from another module: @@ -866,7 +866,7 @@ mod test_load { Dict Result List - Nat + Box " ) ) diff --git a/compiler/module/Cargo.toml b/compiler/module/Cargo.toml index 50dc2e3eb2..9b8c272d54 100644 --- a/compiler/module/Cargo.toml +++ b/compiler/module/Cargo.toml @@ -14,4 +14,3 @@ bumpalo = { version = "3.8.0", features = ["collections"] } lazy_static = "1.4.0" static_assertions = "1.1.0" snafu = { version = "0.6.10", features = ["backtraces"] } -arrayvec = "0.7.2" diff --git a/compiler/module/src/called_via.rs b/compiler/module/src/called_via.rs index c83245d3e9..bc314e0eec 100644 --- a/compiler/module/src/called_via.rs +++ b/compiler/module/src/called_via.rs @@ -34,7 +34,6 @@ pub enum BinOp { Slash, DoubleSlash, Percent, - DoublePercent, Plus, Minus, Equals, @@ -58,8 +57,8 @@ impl BinOp { pub fn width(self) -> u16 { match self { Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1, - DoubleSlash | DoublePercent | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq - | And | Or | Pizza => 2, + DoubleSlash | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq | And | Or + | Pizza => 2, Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(), } } @@ -97,9 +96,7 @@ impl BinOp { use self::Associativity::*; match self { - Pizza | Star | Slash | DoubleSlash | DoublePercent | Percent | Plus | Minus => { - LeftAssociative - } + Pizza | Star | Slash | DoubleSlash | Percent | Plus | Minus => LeftAssociative, And | Or | Caret => RightAssociative, Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => { NonAssociative @@ -111,7 +108,7 @@ impl BinOp { fn precedence(self) -> u8 { match self { Caret => 7, - Star | Slash | DoubleSlash | DoublePercent | Percent => 6, + Star | Slash | DoubleSlash | Percent => 6, Plus | Minus => 5, Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => 4, And => 3, @@ -142,7 +139,6 @@ impl std::fmt::Display for BinOp { Slash => "/", DoubleSlash => "//", Percent => "%", - DoublePercent => "%%", Plus => "+", Minus => "-", Equals => "==", diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 2b4347d3d9..56adb96189 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -3,13 +3,18 @@ pub use roc_ident::IdentStr; use std::fmt; /// This could be uppercase or lowercase, qualified or unqualified. -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] pub struct Ident(pub IdentStr); impl Ident { pub fn as_inline_str(&self) -> &IdentStr { &self.0 } + + #[inline(always)] + pub fn as_str(&self) -> &str { + self.0.as_str() + } } pub struct QualifiedModuleName<'a> { @@ -44,34 +49,27 @@ pub type TagIdIntType = u16; #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TagName { - /// Global tags have no module, but tend to be short strings (since they're + /// Tags have no module, but tend to be short strings (since they're /// never qualified), so we store them as ident strings. /// /// This is allows canonicalization to happen in parallel without locks. - /// If global tags had a Symbol representation, then each module would have to - /// deal with contention on a global mutex around translating global tag strings + /// If tags had a Symbol representation, then each module would have to + /// deal with contention on a global mutex around translating tag strings /// into integers. (Record field labels work the same way, for the same reason.) - Global(Uppercase), - - /// Private tags are associated with a specific module, and as such use a - /// Symbol just like all other module-specific identifiers. - Private(Symbol), + Tag(Uppercase), /// Used to connect the closure size to the function it corresponds to Closure(Symbol), } roc_error_macros::assert_sizeof_aarch64!(TagName, 24); -roc_error_macros::assert_sizeof_wasm!(TagName, 16); +roc_error_macros::assert_sizeof_wasm!(TagName, 12); roc_error_macros::assert_sizeof_default!(TagName, 24); impl TagName { pub fn as_ident_str(&self, interns: &Interns, home: ModuleId) -> IdentStr { match self { - TagName::Global(uppercase) => uppercase.as_ident_str().clone(), - TagName::Private(symbol) => { - symbol.fully_qualified(interns, home).as_ident_str().clone() - } + TagName::Tag(uppercase) => uppercase.as_ident_str().clone(), TagName::Closure(symbol) => { symbol.fully_qualified(interns, home).as_ident_str().clone() } diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 317eab34e2..49a70ffcb9 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -53,6 +53,7 @@ pub enum LowLevel { ListAny, ListAll, ListFindUnsafe, + ListIsUnique, DictSize, DictEmpty, DictInsert, @@ -94,7 +95,7 @@ pub enum LowLevel { NumSqrtUnchecked, NumLogUnchecked, NumRound, - NumToFloat, + NumToFrac, NumPow, NumCeiling, NumPowInt, @@ -290,20 +291,23 @@ impl LowLevelWrapperType { Symbol::NUM_LT => CanBeReplacedBy(NumLt), Symbol::NUM_LTE => CanBeReplacedBy(NumLte), Symbol::NUM_COMPARE => CanBeReplacedBy(NumCompare), - Symbol::NUM_DIV_FLOAT => CanBeReplacedBy(NumDivUnchecked), - Symbol::NUM_DIV_FLOAT_CHECKED => WrapperIsRequired, + Symbol::NUM_DIV_FRAC => CanBeReplacedBy(NumDivUnchecked), + Symbol::NUM_DIV_FRAC_CHECKED => WrapperIsRequired, Symbol::NUM_DIV_CEIL => CanBeReplacedBy(NumDivCeilUnchecked), Symbol::NUM_DIV_CEIL_CHECKED => WrapperIsRequired, - Symbol::NUM_REM => WrapperIsRequired, + Symbol::NUM_REM => CanBeReplacedBy(NumRemUnchecked), + Symbol::NUM_REM_CHECKED => WrapperIsRequired, Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf), Symbol::NUM_ABS => CanBeReplacedBy(NumAbs), Symbol::NUM_NEG => CanBeReplacedBy(NumNeg), Symbol::NUM_SIN => CanBeReplacedBy(NumSin), Symbol::NUM_COS => CanBeReplacedBy(NumCos), - Symbol::NUM_SQRT => WrapperIsRequired, - Symbol::NUM_LOG => WrapperIsRequired, + Symbol::NUM_SQRT => CanBeReplacedBy(NumSqrtUnchecked), + Symbol::NUM_SQRT_CHECKED => WrapperIsRequired, + Symbol::NUM_LOG => CanBeReplacedBy(NumLogUnchecked), + Symbol::NUM_LOG_CHECKED => WrapperIsRequired, Symbol::NUM_ROUND => CanBeReplacedBy(NumRound), - Symbol::NUM_TO_FLOAT => CanBeReplacedBy(NumToFloat), + Symbol::NUM_TO_FRAC => CanBeReplacedBy(NumToFrac), Symbol::NUM_POW => CanBeReplacedBy(NumPow), Symbol::NUM_CEILING => CanBeReplacedBy(NumCeiling), Symbol::NUM_POW_INT => CanBeReplacedBy(NumPowInt), diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 02e2e842f1..8a3516f632 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1,15 +1,44 @@ use crate::ident::{Ident, ModuleName}; use crate::module_err::{IdentIdNotFound, ModuleIdNotFound, ModuleResult}; -use roc_collections::all::{default_hasher, MutMap, SendMap}; +use roc_collections::{default_hasher, MutMap, SendMap, SmallStringInterner, VecMap}; use roc_ident::IdentStr; use roc_region::all::Region; use snafu::OptionExt; use std::collections::HashMap; +use std::num::NonZeroU32; use std::{fmt, u32}; -// TODO: benchmark this as { ident_id: u32, module_id: u32 } and see if perf stays the same +// the packed(4) is needed for faster equality comparisons. With it, the structure is +// treated as a single u64, and comparison is one instruction +// +// example::eq_sym64: +// cmp rdi, rsi +// sete al +// ret +// +// while without it we get 2 extra instructions +// +// example::eq_sym64: +// xor edi, edx +// xor esi, ecx +// or esi, edi +// sete al +// ret +// +// #[repr(packed)] gives you #[repr(packed(1))], and then all your reads are unaligned +// so we set the alignment to (the natural) 4 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Symbol(u64); +#[repr(packed(4))] +pub struct Symbol { + ident_id: u32, + module_id: NonZeroU32, +} + +/// An Option will use the 0 that is not used by the NonZeroU32 module_id field to encode +/// the Nothing case. An Option hence takes no more space than a Symbol. +#[allow(dead_code)] +const SYMBOL_HAS_NICHE: () = + assert!(std::mem::size_of::() == std::mem::size_of::>()); // When this is `true` (which it normally should be), Symbol's Debug::fmt implementation // attempts to pretty print debug symbols using interns recorded using @@ -28,7 +57,7 @@ impl Symbol { // e.g. pub const NUM_NUM: Symbol = … pub const fn new(module_id: ModuleId, ident_id: IdentId) -> Symbol { - // The bit layout of the u64 inside a Symbol is: + // The bit layout of the inside of a Symbol is: // // |------ 32 bits -----|------ 32 bits -----| // | ident_id | module_id | @@ -37,20 +66,22 @@ impl Symbol { // module_id comes second because we need to query it more often, // and this way we can get it by truncating the u64 to u32, // whereas accessing the first slot requires a bit shift first. - let bits = ((ident_id.0 as u64) << 32) | (module_id.0 as u64); - Symbol(bits) + Self { + module_id: module_id.0, + ident_id: ident_id.0, + } } - pub fn module_id(self) -> ModuleId { - ModuleId(self.0 as u32) + pub const fn module_id(self) -> ModuleId { + ModuleId(self.module_id) } - pub fn ident_id(self) -> IdentId { - IdentId((self.0 >> 32) as u32) + pub const fn ident_id(self) -> IdentId { + IdentId(self.ident_id) } - pub fn is_builtin(self) -> bool { + pub const fn is_builtin(self) -> bool { self.module_id().is_builtin() } @@ -68,10 +99,6 @@ impl Symbol { } pub fn as_str(self, interns: &Interns) -> &str { - self.ident_str(interns).as_str() - } - - pub fn ident_str(self, interns: &Interns) -> &IdentStr { let ident_ids = interns .all_ident_ids .get(&self.module_id()) @@ -83,40 +110,42 @@ impl Symbol { ) }); - ident_ids - .get_name(self.ident_id()) - .unwrap_or_else(|| { - panic!( - "ident_string's IdentIds did not contain an entry for {} in module {:?}", - self.ident_id().0, - self.module_id() - ) - }) - .into() + ident_ids.get_name(self.ident_id()).unwrap_or_else(|| { + panic!( + "ident_string's IdentIds did not contain an entry for {} in module {:?}", + self.ident_id().0, + self.module_id() + ) + }) } - pub fn as_u64(self) -> u64 { - self.0 + pub const fn as_u64(self) -> u64 { + u64::from_ne_bytes(self.to_ne_bytes()) } pub fn fully_qualified(self, interns: &Interns, home: ModuleId) -> ModuleName { let module_id = self.module_id(); if module_id == home { - self.ident_str(interns).clone().into() + ModuleName::from(self.as_str(interns)) } else { // TODO do this without format! to avoid allocation for short strings format!( "{}.{}", self.module_string(interns).as_str(), - self.ident_str(interns) + self.as_str(interns) ) .into() } } pub const fn to_ne_bytes(self) -> [u8; 8] { - self.0.to_ne_bytes() + unsafe { std::mem::transmute(self) } + } + + #[cfg(debug_assertions)] + pub fn contains(self, needle: &str) -> bool { + format!("{:?}", self).contains(needle) } } @@ -135,7 +164,7 @@ impl fmt::Debug for Symbol { let ident_id = self.ident_id(); match DEBUG_IDENT_IDS_BY_MODULE_ID.lock() { - Ok(names) => match &names.get(&module_id.0) { + Ok(names) => match &names.get(&(module_id.to_zero_indexed() as u32)) { Some(ident_ids) => match ident_ids.get_name(ident_id) { Some(ident_str) => write!(f, "`{:?}.{}`", module_id, ident_str), None => fallback_debug_fmt(*self, f), @@ -176,7 +205,7 @@ impl fmt::Display for Symbol { impl From for u64 { fn from(symbol: Symbol) -> Self { - symbol.0 + symbol.as_u64() } } @@ -214,7 +243,7 @@ lazy_static! { #[derive(Debug, Default)] pub struct Interns { pub module_ids: ModuleIds, - pub all_ident_ids: MutMap, + pub all_ident_ids: IdentIdsByModule, } impl Interns { @@ -256,7 +285,7 @@ impl Interns { } pub fn get_module_ident_ids<'a>( - all_ident_ids: &'a MutMap, + all_ident_ids: &'a IdentIdsByModule, module_id: &ModuleId, ) -> ModuleResult<&'a IdentIds> { all_ident_ids @@ -268,7 +297,7 @@ pub fn get_module_ident_ids<'a>( } pub fn get_module_ident_ids_mut<'a>( - all_ident_ids: &'a mut MutMap, + all_ident_ids: &'a mut IdentIdsByModule, module_id: &ModuleId, ) -> ModuleResult<&'a mut IdentIds> { all_ident_ids @@ -294,18 +323,31 @@ lazy_static! { /// A globally unique ID that gets assigned to each module as it is loaded. #[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct ModuleId(u32); +pub struct ModuleId(NonZeroU32); impl ModuleId { // NOTE: the define_builtins! macro adds a bunch of constants to this impl, // // e.g. pub const NUM: ModuleId = … + const fn from_zero_indexed(mut id: usize) -> Self { + id += 1; + + // only happens on overflow + debug_assert!(id != 0); + + ModuleId(unsafe { NonZeroU32::new_unchecked(id as u32) }) + } + + const fn to_zero_indexed(self) -> usize { + (self.0.get() - 1) as usize + } + #[cfg(debug_assertions)] pub fn register_debug_idents(self, ident_ids: &IdentIds) { let mut all = DEBUG_IDENT_IDS_BY_MODULE_ID.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked."); - all.insert(self.0, ident_ids.clone()); + all.insert(self.to_zero_indexed() as u32, ident_ids.clone()); } #[cfg(not(debug_assertions))] @@ -338,7 +380,7 @@ impl fmt::Debug for ModuleId { .expect("Failed to acquire lock for Debug reading from DEBUG_MODULE_ID_NAMES, presumably because a thread panicked."); if PRETTY_PRINT_DEBUG_SYMBOLS { - match names.get(&self.0) { + match names.get(&(self.to_zero_indexed() as u32)) { Some(str_ref) => write!(f, "{}", str_ref.clone()), None => { panic!( @@ -396,7 +438,7 @@ impl<'a> PackageModuleIds<'a> { Some(id) => *id, None => { let by_id = &mut self.by_id; - let module_id = ModuleId(by_id.len() as u32); + let module_id = ModuleId::from_zero_indexed(by_id.len()); by_id.push(module_name.clone()); @@ -432,7 +474,7 @@ impl<'a> PackageModuleIds<'a> { let mut names = DEBUG_MODULE_ID_NAMES.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked."); names - .entry(module_id.0) + .entry(module_id.to_zero_indexed() as u32) .or_insert_with(|| match module_name { PQModuleName::Unqualified(module) => module.as_str().into(), PQModuleName::Qualified(package, module) => { @@ -452,7 +494,7 @@ impl<'a> PackageModuleIds<'a> { } pub fn get_name(&self, id: ModuleId) -> Option<&PQModuleName> { - self.by_id.get(id.0 as usize) + self.by_id.get(id.to_zero_indexed()) } pub fn available_modules(&self) -> impl Iterator { @@ -477,7 +519,7 @@ impl ModuleIds { Some(id) => *id, None => { let by_id = &mut self.by_id; - let module_id = ModuleId(by_id.len() as u32); + let module_id = ModuleId::from_zero_indexed(by_id.len()); by_id.push(module_name.clone()); @@ -498,7 +540,7 @@ impl ModuleIds { // TODO make sure modules are never added more than once! names - .entry(module_id.0) + .entry(module_id.to_zero_indexed() as u32) .or_insert_with(|| module_name.as_str().to_string().into()); } @@ -512,7 +554,7 @@ impl ModuleIds { } pub fn get_name(&self, id: ModuleId) -> Option<&ModuleName> { - self.by_id.get(id.0 as usize) + self.by_id.get(id.to_zero_indexed()) } pub fn available_modules(&self) -> impl Iterator { @@ -529,89 +571,51 @@ impl ModuleIds { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct IdentId(u32); -/// Stores a mapping between IdentId and InlinableString. -/// -/// Each module name is stored twice, for faster lookups. -/// Since these are interned strings, this shouldn't result in many total allocations in practice. +impl IdentId { + pub const fn index(self) -> usize { + self.0 as usize + } +} + +/// Stores a mapping between Ident and IdentId. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct IdentIds { - /// Each IdentId is an index into this Vec - by_id: Vec, - - next_generated_name: u32, + pub interner: SmallStringInterner, } impl IdentIds { - pub fn idents(&self) -> impl Iterator { - self.by_id + pub fn ident_strs(&self) -> impl Iterator { + self.interner .iter() .enumerate() .map(|(index, ident)| (IdentId(index as u32), ident)) } - pub fn add(&mut self, ident_name: Ident) -> IdentId { - let by_id = &mut self.by_id; - let ident_id = IdentId(by_id.len() as u32); + pub fn add_ident(&mut self, ident_name: &Ident) -> IdentId { + self.add_str(ident_name.as_str()) + } - by_id.push(ident_name); + pub fn add_str(&mut self, ident_name: &str) -> IdentId { + IdentId(self.interner.insert(ident_name) as u32) + } - ident_id + pub fn duplicate_ident(&mut self, ident_id: IdentId) -> IdentId { + IdentId(self.interner.duplicate(ident_id.0 as usize) as u32) } pub fn get_or_insert(&mut self, name: &Ident) -> IdentId { match self.get_id(name) { Some(id) => id, - None => { - let ident_id = IdentId(self.by_id.len() as u32); - - self.by_id.push(name.clone()); - - ident_id - } + None => self.add_str(name.as_str()), } } // necessary when the name of a value is changed in the editor // TODO fix when same ident_name is present multiple times, see issue #2548 - pub fn update_key( - &mut self, - old_ident_name: &str, - new_ident_name: &str, - ) -> Result { - let old_ident: Ident = old_ident_name.into(); - - let ident_id_ref_opt = self.get_id(&old_ident); - - match ident_id_ref_opt { - Some(ident_id_ref) => { - let ident_id = ident_id_ref; - - let by_id = &mut self.by_id; - let key_index_opt = by_id.iter().position(|x| *x == old_ident); - - if let Some(key_index) = key_index_opt { - if let Some(vec_elt) = by_id.get_mut(key_index) { - *vec_elt = new_ident_name.into(); - } else { - // we get the index from by_id - unreachable!() - } - - Ok(ident_id) - } else { - Err( - format!( - "Tried to find position of key {:?} in IdentIds.by_id but I could not find the key. IdentIds.by_id: {:?}", - old_ident_name, - self.by_id - ) - ) - } - } - None => Err(format!( - "Tried to update key in IdentIds ({:?}) but I could not find the key ({}).", - self.by_id, old_ident_name - )), + pub fn update_key(&mut self, old_name: &str, new_name: &str) -> Result { + match self.interner.find_and_update(old_name, new_name) { + Some(index) => Ok(IdentId(index as u32)), + None => Err(format!("The identifier {:?} is not in IdentIds", old_name)), } } @@ -623,49 +627,143 @@ impl IdentIds { /// This is used, for example, during canonicalization of an Expr::Closure /// to generate a unique symbol to refer to that closure. pub fn gen_unique(&mut self) -> IdentId { - use std::fmt::Write; - - let index: u32 = self.next_generated_name; - self.next_generated_name += 1; - - // "4294967296" is 10 characters - let mut buffer: arrayvec::ArrayString<10> = arrayvec::ArrayString::new(); - - write!(buffer, "{}", index).unwrap(); - let ident = Ident(IdentStr::from_str(buffer.as_str())); - - self.add(ident) + IdentId(self.interner.insert_index_str() as u32) } #[inline(always)] pub fn get_id(&self, ident_name: &Ident) -> Option { - for (id, ident) in self.idents() { - if ident_name == ident { - return Some(id); - } - } - - None + self.interner + .find_index(ident_name.as_str()) + .map(|i| IdentId(i as u32)) } - pub fn get_name(&self, id: IdentId) -> Option<&Ident> { - self.by_id.get(id.0 as usize) + #[inline(always)] + pub fn get_id_many<'a>(&'a self, ident_name: &'a str) -> impl Iterator + 'a { + self.interner + .find_indices(ident_name) + .map(|i| IdentId(i as u32)) + } + + pub fn get_name(&self, id: IdentId) -> Option<&str> { + self.interner.try_get(id.0 as usize) } pub fn get_name_str_res(&self, ident_id: IdentId) -> ModuleResult<&str> { - Ok(self - .get_name(ident_id) - .with_context(|| IdentIdNotFound { - ident_id, - ident_ids_str: format!("{:?}", self), - })? - .as_inline_str() - .as_str()) + self.get_name(ident_id).with_context(|| IdentIdNotFound { + ident_id, + ident_ids_str: format!("{:?}", self), + }) + } + + pub fn len(&self) -> usize { + self.interner.len() + } + + pub fn is_empty(&self) -> bool { + self.interner.is_empty() + } +} + +#[derive(Debug, Default)] +pub struct IdentIdsByModule(VecMap); + +impl IdentIdsByModule { + pub fn get_or_insert(&mut self, module_id: ModuleId) -> &mut IdentIds { + self.0.get_or_insert(module_id, IdentIds::default) + } + + pub fn get_mut(&mut self, key: &ModuleId) -> Option<&mut IdentIds> { + self.0.get_mut(key) + } + + pub fn get(&self, key: &ModuleId) -> Option<&IdentIds> { + self.0.get(key) + } + + pub fn insert(&mut self, key: ModuleId, value: IdentIds) -> Option { + self.0.insert(key, value) + } + + pub fn keys(&self) -> impl Iterator { + self.0.keys() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() } } // BUILTINS +const fn offset_helper(mut array: [u32; N]) -> [u32; N] { + let mut sum = 0u32; + + let mut i = 0; + while i < N { + // In rust 1.60 change to: (array[i], sum) = (sum, sum + array[i]); + let temp = array[i]; + array[i] = sum; + sum += temp; + + i += 1; + } + + array +} + +const fn byte_slice_equality(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; + } + + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + + i += 1; + } + + true +} + +const fn find_duplicates(array: [&str; N]) -> Option<(usize, usize)> { + let mut i = 0; + while i < N { + let needle = array[i]; + let mut j = i + 1; + while j < N { + if byte_slice_equality(needle.as_bytes(), array[j].as_bytes()) { + return Some((i, j)); + } + + j += 1; + } + + i += 1; + } + + None +} + +const fn check_indices(array: [u32; N]) -> Option<(u32, usize)> { + let mut i = 0; + while i < N { + if array[i] as usize != i { + return Some((array[i], i)); + } + + i += 1; + } + + None +} + macro_rules! define_builtins { { $( @@ -678,82 +776,83 @@ macro_rules! define_builtins { num_modules: $total:literal } => { impl IdentIds { - pub fn exposed_builtins(extra_capacity: usize) -> MutMap { - let mut exposed_idents_by_module = HashMap::with_capacity_and_hasher(extra_capacity + $total, default_hasher()); + pub fn exposed_builtins(extra_capacity: usize) -> IdentIdsByModule { + let mut exposed_idents_by_module = VecMap::with_capacity(extra_capacity + $total); $( - debug_assert!(!exposed_idents_by_module.contains_key(&ModuleId($module_id)), "Error setting up Builtins: when setting up module {} {:?} - the module ID {} is already present in the map. Check the map for duplicate module IDs!", $module_id, $module_name, $module_id); + let module_id = ModuleId::$module_const; + debug_assert!(!exposed_idents_by_module.contains_key(&module_id), r"Error setting up Builtins: when setting up module {} {:?} - the module ID {} is already present in the map. Check the map for duplicate module IDs!", $module_id, $module_name, $module_id); - let mut by_id : Vec = Vec::new(); let ident_ids = { - $( - debug_assert!(by_id.len() == $ident_id, "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - this entry was assigned an ID of {}, but based on insertion order, it should have had an ID of {} instead! To fix this, change it from {} …: {:?} to {} …: {:?} instead.", $ident_id, $ident_name, $module_id, $module_name, $ident_id, by_id.len(), $ident_id, $ident_name, by_id.len(), $ident_name); + const TOTAL : usize = [ $($ident_name),+ ].len(); + const NAMES : [ &str; TOTAL] = [ $($ident_name),+ ]; + const LENGTHS: [ u16; TOTAL] = [ $($ident_name.len() as u16),+ ]; + const OFFSETS: [ u32; TOTAL] = offset_helper([ $($ident_name.len() as u32),+ ]); + const BUFFER: &str = concat!($($ident_name),+); - by_id.push($ident_name.into()); - )+ + const LENGTH_CHECK: Option<(u32, usize)> = check_indices([ $($ident_id),+ ]); + const DUPLICATE_CHECK: Option<(usize, usize)> = find_duplicates(NAMES); - #[cfg(debug_assertions)] - { - let mut cloned = by_id.clone(); - let before = cloned.len(); - cloned.sort(); - cloned.dedup(); - let after = cloned.len(); - - - if before != after { - let mut duplicates : Vec<&Ident> = Vec::new(); - let mut temp : Vec<&Ident> = Vec::new(); - - for symbol in cloned.iter() { - if temp.contains(&&symbol) { - duplicates.push(symbol); - } - - temp.push(&symbol); - } - - - panic!("duplicate symbols in IdentIds for module {:?}: {:?}", $module_name, duplicates); - } - } - - IdentIds { - by_id, - next_generated_name: 0, + if cfg!(debug_assertions) { + match LENGTH_CHECK { + None => (), + Some((given, expected)) => panic!( + "Symbol {} : {} should have index {} based on the insertion order, try {} : {} instead", + given, NAMES[expected], expected, expected, NAMES[expected], + ), } }; - if cfg!(debug_assertions) { - let module_id = ModuleId($module_id); + if cfg!(debug_assertions) { + match DUPLICATE_CHECK { + None => (), + Some((first, second)) => panic!( + "Symbol {} : {} is duplicated at position {}, try removing the duplicate", + first, NAMES[first], second + ), + } + }; + // Safety: all lengths are non-negative and smaller than 2^15 + let interner = unsafe { + SmallStringInterner::from_parts ( + BUFFER.as_bytes().to_vec(), + LENGTHS.to_vec(), + OFFSETS.to_vec(), + )}; + + IdentIds{ interner } + }; + + if cfg!(debug_assertions) { let name = PQModuleName::Unqualified($module_name.into()); PackageModuleIds::insert_debug_name(module_id, &name); module_id.register_debug_idents(&ident_ids); } + exposed_idents_by_module.insert( - ModuleId($module_id), + module_id, ident_ids ); )+ debug_assert!(exposed_idents_by_module.len() == $total, "Error setting up Builtins: `total:` is set to the wrong amount. It was set to {} but {} modules were set up.", $total, exposed_idents_by_module.len()); - exposed_idents_by_module + IdentIdsByModule(exposed_idents_by_module) } } impl ModuleId { - pub fn is_builtin(&self) -> bool { + pub const fn is_builtin(self) -> bool { // This is a builtin ModuleId iff it's below the // total number of builtin modules, since they // take up the first $total ModuleId numbers. - self.0 < $total + self.to_zero_indexed() < $total } $( - pub const $module_const: ModuleId = ModuleId($module_id); + pub const $module_const: ModuleId = ModuleId::from_zero_indexed($module_id); )+ } @@ -777,7 +876,7 @@ macro_rules! define_builtins { }; $( - insert_both(ModuleId($module_id), $module_name); + insert_both(ModuleId::$module_const, $module_name); )+ ModuleIds { by_name, by_id } @@ -805,7 +904,7 @@ macro_rules! define_builtins { }; $( - insert_both(ModuleId($module_id), $module_name); + insert_both(ModuleId::$module_const, $module_name); )+ PackageModuleIds { by_name, by_id } @@ -815,7 +914,7 @@ macro_rules! define_builtins { impl Symbol { $( $( - pub const $ident_const: Symbol = Symbol::new(ModuleId($module_id), IdentId($ident_id)); + pub const $ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId($ident_id)); )+ )+ @@ -836,7 +935,7 @@ macro_rules! define_builtins { let $imported = true; if $imported { - scope.insert($ident_name.into(), (Symbol::new(ModuleId($module_id), IdentId($ident_id)), Region::zero())); + scope.insert($ident_name.into(), (Symbol::new(ModuleId::$module_const, IdentId($ident_id)), Region::zero())); } )? )+ @@ -907,171 +1006,152 @@ define_builtins! { } 1 NUM: "Num" => { 0 NUM_NUM: "Num" // the Num.Num type alias - 1 NUM_AT_NUM: "@Num" // the Num.@Num private tag - 2 NUM_I128: "I128" // the Num.I128 type alias - 3 NUM_U128: "U128" // the Num.U128 type alias - 4 NUM_I64: "I64" // the Num.I64 type alias - 5 NUM_U64: "U64" // the Num.U64 type alias - 6 NUM_I32: "I32" // the Num.I32 type alias - 7 NUM_U32: "U32" // the Num.U32 type alias - 8 NUM_I16: "I16" // the Num.I16 type alias - 9 NUM_U16: "U16" // the Num.U16 type alias - 10 NUM_I8: "I8" // the Num.I8 type alias - 11 NUM_U8: "U8" // the Num.U8 type alias - 12 NUM_INTEGER: "Integer" // Int : Num Integer - 13 NUM_AT_INTEGER: "@Integer" // the Int.@Integer private tag - 14 NUM_F64: "F64" // the Num.F64 type alias - 15 NUM_F32: "F32" // the Num.F32 type alias - 16 NUM_FLOATINGPOINT: "FloatingPoint" // Float : Num FloatingPoint - 17 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag - 18 NUM_MAX_FLOAT: "maxFloat" - 19 NUM_MIN_FLOAT: "minFloat" - 20 NUM_ABS: "abs" - 21 NUM_NEG: "neg" - 22 NUM_ADD: "add" - 23 NUM_SUB: "sub" - 24 NUM_MUL: "mul" - 25 NUM_LT: "isLt" - 26 NUM_LTE: "isLte" - 27 NUM_GT: "isGt" - 28 NUM_GTE: "isGte" - 29 NUM_TO_FLOAT: "toFloat" - 30 NUM_SIN: "sin" - 31 NUM_COS: "cos" - 32 NUM_TAN: "tan" - 33 NUM_IS_ZERO: "isZero" - 34 NUM_IS_EVEN: "isEven" - 35 NUM_IS_ODD: "isOdd" - 36 NUM_IS_POSITIVE: "isPositive" - 37 NUM_IS_NEGATIVE: "isNegative" - 38 NUM_REM: "rem" - 39 NUM_REM_CHECKED: "remChecked" - 40 NUM_DIV_FLOAT: "div" - 41 NUM_DIV_FLOAT_CHECKED: "divChecked" - 42 NUM_DIV_TRUNC: "divTrunc" - 43 NUM_DIV_TRUNC_CHECKED: "divTruncChecked" - 44 NUM_MOD_INT: "modInt" - 45 NUM_MOD_INT_CHECKED: "modIntChecked" - 46 NUM_MOD_FLOAT: "modFloat" - 47 NUM_MOD_FLOAT_CHECKED: "modFloatChecked" - 48 NUM_SQRT: "sqrt" - 49 NUM_SQRT_CHECKED: "sqrtChecked" - 50 NUM_LOG: "log" - 51 NUM_LOG_CHECKED: "logChecked" - 52 NUM_ROUND: "round" - 53 NUM_COMPARE: "compare" - 54 NUM_POW: "pow" - 55 NUM_CEILING: "ceiling" - 56 NUM_POW_INT: "powInt" - 57 NUM_FLOOR: "floor" - 58 NUM_ADD_WRAP: "addWrap" - 59 NUM_ADD_CHECKED: "addChecked" - 60 NUM_ADD_SATURATED: "addSaturated" - 61 NUM_ATAN: "atan" - 62 NUM_ACOS: "acos" - 63 NUM_ASIN: "asin" - 64 NUM_AT_SIGNED128: "@Signed128" - 65 NUM_SIGNED128: "Signed128" - 66 NUM_AT_SIGNED64: "@Signed64" - 67 NUM_SIGNED64: "Signed64" - 68 NUM_AT_SIGNED32: "@Signed32" - 69 NUM_SIGNED32: "Signed32" - 70 NUM_AT_SIGNED16: "@Signed16" - 71 NUM_SIGNED16: "Signed16" - 72 NUM_AT_SIGNED8: "@Signed8" - 73 NUM_SIGNED8: "Signed8" - 74 NUM_AT_UNSIGNED128: "@Unsigned128" - 75 NUM_UNSIGNED128: "Unsigned128" - 76 NUM_AT_UNSIGNED64: "@Unsigned64" - 77 NUM_UNSIGNED64: "Unsigned64" - 78 NUM_AT_UNSIGNED32: "@Unsigned32" - 79 NUM_UNSIGNED32: "Unsigned32" - 80 NUM_AT_UNSIGNED16: "@Unsigned16" - 81 NUM_UNSIGNED16: "Unsigned16" - 82 NUM_AT_UNSIGNED8: "@Unsigned8" - 83 NUM_UNSIGNED8: "Unsigned8" - 84 NUM_AT_BINARY64: "@Binary64" - 85 NUM_BINARY64: "Binary64" - 86 NUM_AT_BINARY32: "@Binary32" - 87 NUM_BINARY32: "Binary32" - 88 NUM_BITWISE_AND: "bitwiseAnd" - 89 NUM_BITWISE_XOR: "bitwiseXor" - 90 NUM_BITWISE_OR: "bitwiseOr" - 91 NUM_SHIFT_LEFT: "shiftLeftBy" - 92 NUM_SHIFT_RIGHT: "shiftRightBy" - 93 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" - 94 NUM_SUB_WRAP: "subWrap" - 95 NUM_SUB_CHECKED: "subChecked" - 96 NUM_SUB_SATURATED: "subSaturated" - 97 NUM_MUL_WRAP: "mulWrap" - 98 NUM_MUL_CHECKED: "mulChecked" - 99 NUM_INT: "Int" - 100 NUM_FLOAT: "Float" - 101 NUM_AT_NATURAL: "@Natural" - 102 NUM_NATURAL: "Natural" - 103 NUM_NAT: "Nat" - 104 NUM_INT_CAST: "intCast" - 105 NUM_IS_MULTIPLE_OF: "isMultipleOf" - 106 NUM_AT_DECIMAL: "@Decimal" - 107 NUM_DECIMAL: "Decimal" - 108 NUM_DEC: "Dec" // the Num.Dectype alias - 109 NUM_BYTES_TO_U16: "bytesToU16" - 110 NUM_BYTES_TO_U32: "bytesToU32" - 111 NUM_CAST_TO_NAT: "#castToNat" - 112 NUM_DIV_CEIL: "divCeil" - 113 NUM_DIV_CEIL_CHECKED: "divCeilChecked" - 114 NUM_TO_STR: "toStr" - 115 NUM_MIN_I8: "minI8" - 116 NUM_MAX_I8: "maxI8" - 117 NUM_MIN_U8: "minU8" - 118 NUM_MAX_U8: "maxU8" - 119 NUM_MIN_I16: "minI16" - 120 NUM_MAX_I16: "maxI16" - 121 NUM_MIN_U16: "minU16" - 122 NUM_MAX_U16: "maxU16" - 123 NUM_MIN_I32: "minI32" - 124 NUM_MAX_I32: "maxI32" - 125 NUM_MIN_U32: "minU32" - 126 NUM_MAX_U32: "maxU32" - 127 NUM_MIN_I64: "minI64" - 128 NUM_MAX_I64: "maxI64" - 129 NUM_MIN_U64: "minU64" - 130 NUM_MAX_U64: "maxU64" - 131 NUM_MIN_I128: "minI128" - 132 NUM_MAX_I128: "maxI128" - 133 NUM_TO_I8: "toI8" - 134 NUM_TO_I8_CHECKED: "toI8Checked" - 135 NUM_TO_I16: "toI16" - 136 NUM_TO_I16_CHECKED: "toI16Checked" - 137 NUM_TO_I32: "toI32" - 138 NUM_TO_I32_CHECKED: "toI32Checked" - 139 NUM_TO_I64: "toI64" - 140 NUM_TO_I64_CHECKED: "toI64Checked" - 141 NUM_TO_I128: "toI128" - 142 NUM_TO_I128_CHECKED: "toI128Checked" - 143 NUM_TO_U8: "toU8" - 144 NUM_TO_U8_CHECKED: "toU8Checked" - 145 NUM_TO_U16: "toU16" - 146 NUM_TO_U16_CHECKED: "toU16Checked" - 147 NUM_TO_U32: "toU32" - 148 NUM_TO_U32_CHECKED: "toU32Checked" - 149 NUM_TO_U64: "toU64" - 150 NUM_TO_U64_CHECKED: "toU64Checked" - 151 NUM_TO_U128: "toU128" - 152 NUM_TO_U128_CHECKED: "toU128Checked" - 153 NUM_TO_NAT: "toNat" - 154 NUM_TO_NAT_CHECKED: "toNatChecked" - 155 NUM_TO_F32: "toF32" - 156 NUM_TO_F32_CHECKED: "toF32Checked" - 157 NUM_TO_F64: "toF64" - 158 NUM_TO_F64_CHECKED: "toF64Checked" + 1 NUM_I128: "I128" // the Num.I128 type alias + 2 NUM_U128: "U128" // the Num.U128 type alias + 3 NUM_I64: "I64" // the Num.I64 type alias + 4 NUM_U64: "U64" // the Num.U64 type alias + 5 NUM_I32: "I32" // the Num.I32 type alias + 6 NUM_U32: "U32" // the Num.U32 type alias + 7 NUM_I16: "I16" // the Num.I16 type alias + 8 NUM_U16: "U16" // the Num.U16 type alias + 9 NUM_I8: "I8" // the Num.I8 type alias + 10 NUM_U8: "U8" // the Num.U8 type alias + 11 NUM_INTEGER: "Integer" // Int : Num Integer + 12 NUM_F64: "F64" // the Num.F64 type alias + 13 NUM_F32: "F32" // the Num.F32 type alias + 14 NUM_FLOATINGPOINT: "FloatingPoint" // Float : Num FloatingPoint + 15 NUM_MAX_F32: "maxF32" + 16 NUM_MIN_F32: "minF32" + 17 NUM_ABS: "abs" + 18 NUM_NEG: "neg" + 19 NUM_ADD: "add" + 20 NUM_SUB: "sub" + 21 NUM_MUL: "mul" + 22 NUM_LT: "isLt" + 23 NUM_LTE: "isLte" + 24 NUM_GT: "isGt" + 25 NUM_GTE: "isGte" + 26 NUM_TO_FRAC: "toFrac" + 27 NUM_SIN: "sin" + 28 NUM_COS: "cos" + 29 NUM_TAN: "tan" + 30 NUM_IS_ZERO: "isZero" + 31 NUM_IS_EVEN: "isEven" + 32 NUM_IS_ODD: "isOdd" + 33 NUM_IS_POSITIVE: "isPositive" + 34 NUM_IS_NEGATIVE: "isNegative" + 35 NUM_REM: "rem" + 36 NUM_REM_CHECKED: "remChecked" + 37 NUM_DIV_FRAC: "div" + 38 NUM_DIV_FRAC_CHECKED: "divChecked" + 39 NUM_DIV_TRUNC: "divTrunc" + 40 NUM_DIV_TRUNC_CHECKED: "divTruncChecked" + 41 NUM_SQRT: "sqrt" + 42 NUM_SQRT_CHECKED: "sqrtChecked" + 43 NUM_LOG: "log" + 44 NUM_LOG_CHECKED: "logChecked" + 45 NUM_ROUND: "round" + 46 NUM_COMPARE: "compare" + 47 NUM_POW: "pow" + 48 NUM_CEILING: "ceiling" + 49 NUM_POW_INT: "powInt" + 50 NUM_FLOOR: "floor" + 51 NUM_ADD_WRAP: "addWrap" + 52 NUM_ADD_CHECKED: "addChecked" + 53 NUM_ADD_SATURATED: "addSaturated" + 54 NUM_ATAN: "atan" + 55 NUM_ACOS: "acos" + 56 NUM_ASIN: "asin" + 57 NUM_SIGNED128: "Signed128" + 58 NUM_SIGNED64: "Signed64" + 59 NUM_SIGNED32: "Signed32" + 60 NUM_SIGNED16: "Signed16" + 61 NUM_SIGNED8: "Signed8" + 62 NUM_UNSIGNED128: "Unsigned128" + 63 NUM_UNSIGNED64: "Unsigned64" + 64 NUM_UNSIGNED32: "Unsigned32" + 65 NUM_UNSIGNED16: "Unsigned16" + 66 NUM_UNSIGNED8: "Unsigned8" + 67 NUM_BINARY64: "Binary64" + 68 NUM_BINARY32: "Binary32" + 69 NUM_BITWISE_AND: "bitwiseAnd" + 70 NUM_BITWISE_XOR: "bitwiseXor" + 71 NUM_BITWISE_OR: "bitwiseOr" + 72 NUM_SHIFT_LEFT: "shiftLeftBy" + 73 NUM_SHIFT_RIGHT: "shiftRightBy" + 74 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" + 75 NUM_SUB_WRAP: "subWrap" + 76 NUM_SUB_CHECKED: "subChecked" + 77 NUM_SUB_SATURATED: "subSaturated" + 78 NUM_MUL_WRAP: "mulWrap" + 79 NUM_MUL_CHECKED: "mulChecked" + 80 NUM_INT: "Int" + 81 NUM_FRAC: "Frac" + 82 NUM_NATURAL: "Natural" + 83 NUM_NAT: "Nat" + 84 NUM_INT_CAST: "intCast" + 85 NUM_IS_MULTIPLE_OF: "isMultipleOf" + 86 NUM_DECIMAL: "Decimal" + 87 NUM_DEC: "Dec" // the Num.Dectype alias + 88 NUM_BYTES_TO_U16: "bytesToU16" + 89 NUM_BYTES_TO_U32: "bytesToU32" + 90 NUM_CAST_TO_NAT: "#castToNat" + 91 NUM_DIV_CEIL: "divCeil" + 92 NUM_DIV_CEIL_CHECKED: "divCeilChecked" + 93 NUM_TO_STR: "toStr" + 94 NUM_MIN_I8: "minI8" + 95 NUM_MAX_I8: "maxI8" + 96 NUM_MIN_U8: "minU8" + 97 NUM_MAX_U8: "maxU8" + 98 NUM_MIN_I16: "minI16" + 99 NUM_MAX_I16: "maxI16" + 100 NUM_MIN_U16: "minU16" + 101 NUM_MAX_U16: "maxU16" + 102 NUM_MIN_I32: "minI32" + 103 NUM_MAX_I32: "maxI32" + 104 NUM_MIN_U32: "minU32" + 105 NUM_MAX_U32: "maxU32" + 106 NUM_MIN_I64: "minI64" + 107 NUM_MAX_I64: "maxI64" + 108 NUM_MIN_U64: "minU64" + 109 NUM_MAX_U64: "maxU64" + 110 NUM_MIN_I128: "minI128" + 111 NUM_MAX_I128: "maxI128" + 112 NUM_TO_I8: "toI8" + 113 NUM_TO_I8_CHECKED: "toI8Checked" + 114 NUM_TO_I16: "toI16" + 115 NUM_TO_I16_CHECKED: "toI16Checked" + 116 NUM_TO_I32: "toI32" + 117 NUM_TO_I32_CHECKED: "toI32Checked" + 118 NUM_TO_I64: "toI64" + 119 NUM_TO_I64_CHECKED: "toI64Checked" + 120 NUM_TO_I128: "toI128" + 121 NUM_TO_I128_CHECKED: "toI128Checked" + 122 NUM_TO_U8: "toU8" + 123 NUM_TO_U8_CHECKED: "toU8Checked" + 124 NUM_TO_U16: "toU16" + 125 NUM_TO_U16_CHECKED: "toU16Checked" + 126 NUM_TO_U32: "toU32" + 127 NUM_TO_U32_CHECKED: "toU32Checked" + 128 NUM_TO_U64: "toU64" + 129 NUM_TO_U64_CHECKED: "toU64Checked" + 130 NUM_TO_U128: "toU128" + 131 NUM_TO_U128_CHECKED: "toU128Checked" + 132 NUM_TO_NAT: "toNat" + 133 NUM_TO_NAT_CHECKED: "toNatChecked" + 134 NUM_TO_F32: "toF32" + 135 NUM_TO_F32_CHECKED: "toF32Checked" + 136 NUM_TO_F64: "toF64" + 137 NUM_TO_F64_CHECKED: "toF64Checked" + 138 NUM_MAX_F64: "maxF64" + 139 NUM_MIN_F64: "minF64" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" // the Bool.Bool type alias 1 BOOL_FALSE: "False" imported // Bool.Bool = [ False, True ] - // NB: not strictly needed; used for finding global tag names in error suggestions + // NB: not strictly needed; used for finding tag names in error suggestions 2 BOOL_TRUE: "True" imported // Bool.Bool = [ False, True ] - // NB: not strictly needed; used for finding global tag names in error suggestions + // NB: not strictly needed; used for finding tag names in error suggestions 3 BOOL_AND: "and" 4 BOOL_OR: "or" 5 BOOL_NOT: "not" @@ -1081,108 +1161,107 @@ define_builtins! { } 3 STR: "Str" => { 0 STR_STR: "Str" imported // the Str.Str type alias - 1 STR_AT_STR: "@Str" // the Str.@Str private tag - 2 STR_IS_EMPTY: "isEmpty" - 3 STR_APPEND: "#append" // unused - 4 STR_CONCAT: "concat" - 5 STR_JOIN_WITH: "joinWith" - 6 STR_SPLIT: "split" - 7 STR_COUNT_GRAPHEMES: "countGraphemes" - 8 STR_STARTS_WITH: "startsWith" - 9 STR_ENDS_WITH: "endsWith" - 10 STR_FROM_UTF8: "fromUtf8" - 11 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias - 12 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias - 13 STR_TO_UTF8: "toUtf8" - 14 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" - 15 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime - 16 STR_FROM_UTF8_RANGE: "fromUtf8Range" - 17 STR_REPEAT: "repeat" - 18 STR_TRIM: "trim" - 19 STR_TRIM_LEFT: "trimLeft" - 20 STR_TRIM_RIGHT: "trimRight" - 21 STR_TO_DEC: "toDec" - 22 STR_TO_F64: "toF64" - 23 STR_TO_F32: "toF32" - 24 STR_TO_NAT: "toNat" - 25 STR_TO_U128: "toU128" - 26 STR_TO_I128: "toI128" - 27 STR_TO_U64: "toU64" - 28 STR_TO_I64: "toI64" - 29 STR_TO_U32: "toU32" - 30 STR_TO_I32: "toI32" - 31 STR_TO_U16: "toU16" - 32 STR_TO_I16: "toI16" - 33 STR_TO_U8: "toU8" - 34 STR_TO_I8: "toI8" + 1 STR_IS_EMPTY: "isEmpty" + 2 STR_APPEND: "#append" // unused + 3 STR_CONCAT: "concat" + 4 STR_JOIN_WITH: "joinWith" + 5 STR_SPLIT: "split" + 6 STR_COUNT_GRAPHEMES: "countGraphemes" + 7 STR_STARTS_WITH: "startsWith" + 8 STR_ENDS_WITH: "endsWith" + 9 STR_FROM_UTF8: "fromUtf8" + 10 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias + 11 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias + 12 STR_TO_UTF8: "toUtf8" + 13 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" + 14 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime + 15 STR_FROM_UTF8_RANGE: "fromUtf8Range" + 16 STR_REPEAT: "repeat" + 17 STR_TRIM: "trim" + 18 STR_TRIM_LEFT: "trimLeft" + 19 STR_TRIM_RIGHT: "trimRight" + 20 STR_TO_DEC: "toDec" + 21 STR_TO_F64: "toF64" + 22 STR_TO_F32: "toF32" + 23 STR_TO_NAT: "toNat" + 24 STR_TO_U128: "toU128" + 25 STR_TO_I128: "toI128" + 26 STR_TO_U64: "toU64" + 27 STR_TO_I64: "toI64" + 28 STR_TO_U32: "toU32" + 29 STR_TO_I32: "toI32" + 30 STR_TO_U16: "toU16" + 31 STR_TO_I16: "toI16" + 32 STR_TO_U8: "toU8" + 33 STR_TO_I8: "toI8" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias - 1 LIST_AT_LIST: "@List" // the List.@List private tag - 2 LIST_IS_EMPTY: "isEmpty" - 3 LIST_GET: "get" - 4 LIST_SET: "set" - 5 LIST_APPEND: "append" - 6 LIST_MAP: "map" - 7 LIST_LEN: "len" - 8 LIST_WALK_BACKWARDS: "walkBackwards" - 9 LIST_CONCAT: "concat" - 10 LIST_FIRST: "first" - 11 LIST_SINGLE: "single" - 12 LIST_REPEAT: "repeat" - 13 LIST_REVERSE: "reverse" - 14 LIST_PREPEND: "prepend" - 15 LIST_JOIN: "join" - 16 LIST_KEEP_IF: "keepIf" - 17 LIST_CONTAINS: "contains" - 18 LIST_SUM: "sum" - 19 LIST_WALK: "walk" - 20 LIST_LAST: "last" - 21 LIST_KEEP_OKS: "keepOks" - 22 LIST_KEEP_ERRS: "keepErrs" - 23 LIST_MAP_WITH_INDEX: "mapWithIndex" - 24 LIST_MAP2: "map2" - 25 LIST_MAP3: "map3" - 26 LIST_PRODUCT: "product" - 27 LIST_WALK_UNTIL: "walkUntil" - 28 LIST_RANGE: "range" - 29 LIST_SORT_WITH: "sortWith" - 30 LIST_DROP: "drop" - 31 LIST_SWAP: "swap" - 32 LIST_DROP_AT: "dropAt" - 33 LIST_DROP_LAST: "dropLast" - 34 LIST_MIN: "min" - 35 LIST_MIN_LT: "#minlt" - 36 LIST_MAX: "max" - 37 LIST_MAX_GT: "#maxGt" - 38 LIST_MAP4: "map4" - 39 LIST_DROP_FIRST: "dropFirst" - 40 LIST_JOIN_MAP: "joinMap" - 41 LIST_JOIN_MAP_CONCAT: "#joinMapConcat" - 42 LIST_ANY: "any" - 43 LIST_TAKE_FIRST: "takeFirst" - 44 LIST_TAKE_LAST: "takeLast" - 45 LIST_FIND: "find" - 46 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find - 47 LIST_SUBLIST: "sublist" - 48 LIST_INTERSPERSE: "intersperse" - 49 LIST_INTERSPERSE_CLOS: "#intersperseClos" - 50 LIST_SPLIT: "split" - 51 LIST_SPLIT_CLOS: "#splitClos" - 52 LIST_ALL: "all" - 53 LIST_DROP_IF: "dropIf" - 54 LIST_DROP_IF_PREDICATE: "#dropIfPred" - 55 LIST_SORT_ASC: "sortAsc" - 56 LIST_SORT_DESC: "sortDesc" - 57 LIST_SORT_DESC_COMPARE: "#sortDescCompare" - 58 LIST_REPLACE: "replace" + 1 LIST_IS_EMPTY: "isEmpty" + 2 LIST_GET: "get" + 3 LIST_SET: "set" + 4 LIST_APPEND: "append" + 5 LIST_MAP: "map" + 6 LIST_LEN: "len" + 7 LIST_WALK_BACKWARDS: "walkBackwards" + 8 LIST_CONCAT: "concat" + 9 LIST_FIRST: "first" + 10 LIST_SINGLE: "single" + 11 LIST_REPEAT: "repeat" + 12 LIST_REVERSE: "reverse" + 13 LIST_PREPEND: "prepend" + 14 LIST_JOIN: "join" + 15 LIST_KEEP_IF: "keepIf" + 16 LIST_CONTAINS: "contains" + 17 LIST_SUM: "sum" + 18 LIST_WALK: "walk" + 19 LIST_LAST: "last" + 20 LIST_KEEP_OKS: "keepOks" + 21 LIST_KEEP_ERRS: "keepErrs" + 22 LIST_MAP_WITH_INDEX: "mapWithIndex" + 23 LIST_MAP2: "map2" + 24 LIST_MAP3: "map3" + 25 LIST_PRODUCT: "product" + 26 LIST_WALK_UNTIL: "walkUntil" + 27 LIST_RANGE: "range" + 28 LIST_SORT_WITH: "sortWith" + 29 LIST_DROP: "drop" + 30 LIST_SWAP: "swap" + 31 LIST_DROP_AT: "dropAt" + 32 LIST_DROP_LAST: "dropLast" + 33 LIST_MIN: "min" + 34 LIST_MIN_LT: "#minlt" + 35 LIST_MAX: "max" + 36 LIST_MAX_GT: "#maxGt" + 37 LIST_MAP4: "map4" + 38 LIST_DROP_FIRST: "dropFirst" + 39 LIST_JOIN_MAP: "joinMap" + 40 LIST_JOIN_MAP_CONCAT: "#joinMapConcat" + 41 LIST_ANY: "any" + 42 LIST_TAKE_FIRST: "takeFirst" + 43 LIST_TAKE_LAST: "takeLast" + 44 LIST_FIND: "find" + 45 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find + 46 LIST_SUBLIST: "sublist" + 47 LIST_INTERSPERSE: "intersperse" + 48 LIST_INTERSPERSE_CLOS: "#intersperseClos" + 49 LIST_SPLIT: "split" + 50 LIST_SPLIT_CLOS: "#splitClos" + 51 LIST_ALL: "all" + 52 LIST_DROP_IF: "dropIf" + 53 LIST_DROP_IF_PREDICATE: "#dropIfPred" + 54 LIST_SORT_ASC: "sortAsc" + 55 LIST_SORT_DESC: "sortDesc" + 56 LIST_SORT_DESC_COMPARE: "#sortDescCompare" + 57 LIST_REPLACE: "replace" + 58 LIST_IS_UNIQUE: "#isUnique" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" // the Result.Result type alias 1 RESULT_OK: "Ok" imported // Result.Result a e = [ Ok a, Err e ] - // NB: not strictly needed; used for finding global tag names in error suggestions + // NB: not strictly needed; used for finding tag names in error suggestions 2 RESULT_ERR: "Err" imported // Result.Result a e = [ Ok a, Err e ] - // NB: not strictly needed; used for finding global tag names in error suggestions + // NB: not strictly needed; used for finding tag names in error suggestions 3 RESULT_MAP: "map" 4 RESULT_MAP_ERR: "mapErr" 5 RESULT_WITH_DEFAULT: "withDefault" @@ -1192,41 +1271,39 @@ define_builtins! { } 6 DICT: "Dict" => { 0 DICT_DICT: "Dict" imported // the Dict.Dict type alias - 1 DICT_AT_DICT: "@Dict" // the Dict.@Dict private tag - 2 DICT_EMPTY: "empty" - 3 DICT_SINGLE: "single" - 4 DICT_GET: "get" - 5 DICT_GET_RESULT: "#get_result" // symbol used in the definition of Dict.get - 6 DICT_WALK: "walk" - 7 DICT_INSERT: "insert" - 8 DICT_LEN: "len" + 1 DICT_EMPTY: "empty" + 2 DICT_SINGLE: "single" + 3 DICT_GET: "get" + 4 DICT_GET_RESULT: "#get_result" // symbol used in the definition of Dict.get + 5 DICT_WALK: "walk" + 6 DICT_INSERT: "insert" + 7 DICT_LEN: "len" - 9 DICT_REMOVE: "remove" - 10 DICT_CONTAINS: "contains" - 11 DICT_KEYS: "keys" - 12 DICT_VALUES: "values" + 8 DICT_REMOVE: "remove" + 9 DICT_CONTAINS: "contains" + 10 DICT_KEYS: "keys" + 11 DICT_VALUES: "values" - 13 DICT_UNION: "union" - 14 DICT_INTERSECTION: "intersection" - 15 DICT_DIFFERENCE: "difference" + 12 DICT_UNION: "union" + 13 DICT_INTERSECTION: "intersection" + 14 DICT_DIFFERENCE: "difference" } 7 SET: "Set" => { 0 SET_SET: "Set" imported // the Set.Set type alias - 1 SET_AT_SET: "@Set" // the Set.@Set private tag - 2 SET_EMPTY: "empty" - 3 SET_SINGLE: "single" - 4 SET_LEN: "len" - 5 SET_INSERT: "insert" - 6 SET_REMOVE: "remove" - 7 SET_UNION: "union" - 8 SET_DIFFERENCE: "difference" - 9 SET_INTERSECTION: "intersection" - 10 SET_TO_LIST: "toList" - 11 SET_FROM_LIST: "fromList" - 12 SET_WALK: "walk" - 13 SET_WALK_USER_FUNCTION: "#walk_user_function" - 14 SET_CONTAINS: "contains" - 15 SET_TO_DICT: "toDict" + 1 SET_EMPTY: "empty" + 2 SET_SINGLE: "single" + 3 SET_LEN: "len" + 4 SET_INSERT: "insert" + 5 SET_REMOVE: "remove" + 6 SET_UNION: "union" + 7 SET_DIFFERENCE: "difference" + 8 SET_INTERSECTION: "intersection" + 9 SET_TO_LIST: "toList" + 10 SET_FROM_LIST: "fromList" + 11 SET_WALK: "walk" + 12 SET_WALK_USER_FUNCTION: "#walk_user_function" + 13 SET_CONTAINS: "contains" + 14 SET_TO_DICT: "toDict" } 8 BOX: "Box" => { 0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index d47714ebdd..ff32e8e1e9 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -19,9 +19,8 @@ roc_problem = { path = "../problem" } roc_builtins = { path = "../builtins" } roc_target = { path = "../roc_target" } roc_error_macros = {path="../../error_macros"} +roc_debug_flags = {path="../debug_flags"} ven_pretty = { path = "../../vendor/pretty" } -morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.8.0", features = ["collections"] } hashbrown = { version = "0.11.2", features = [ "bumpalo" ] } -ven_graph = { path = "../../vendor/pathfinding" } static_assertions = "1.1.0" diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index a883797efb..ca69f99c1a 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -2,13 +2,16 @@ use crate::ir::{Expr, HigherOrderLowLevel, JoinPointId, Param, Proc, ProcLayout, use crate::layout::Layout; use bumpalo::collections::Vec; use bumpalo::Bump; -use roc_collections::all::{default_hasher, MutMap, MutSet}; +use roc_collections::all::{MutMap, MutSet}; +use roc_collections::ReferenceMatrix; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; -pub const OWNED: bool = false; -pub const BORROWED: bool = true; +pub(crate) const OWNED: bool = false; +pub(crate) const BORROWED: bool = true; +/// For reference-counted types (lists, (big) strings, recursive tags), owning a value +/// means incrementing its reference count. Hence, we prefer borrowing for these types fn should_borrow_layout(layout: &Layout) -> bool { layout.is_refcounted() } @@ -45,94 +48,27 @@ pub fn infer_borrow<'a>( // topological sort on these components, finally run the fix-point borrow analysis on each // component (in top-sorted order, from primitives (std-lib) to main) - let successor_map = &make_successor_mapping(arena, procs); - let successors = move |key: &Symbol| { - let slice = match successor_map.get(key) { - None => &[] as &[_], - Some(s) => s.as_slice(), + let mut matrix = ReferenceMatrix::new(procs.len()); + + for (row, proc) in procs.values().enumerate() { + let mut call_info = CallInfo { + keys: Vec::new_in(arena), }; + call_info_stmt(arena, &proc.body, &mut call_info); - slice.iter().copied() - }; - - let mut symbols = Vec::with_capacity_in(procs.len(), arena); - symbols.extend(procs.keys().map(|x| x.0)); - - let sccs = ven_graph::strongly_connected_components(&symbols, successors); - - let mut symbol_to_component = MutMap::default(); - for (i, symbols) in sccs.iter().enumerate() { - for symbol in symbols { - symbol_to_component.insert(*symbol, i); - } - } - - let mut component_to_successors = Vec::with_capacity_in(sccs.len(), arena); - for (i, symbols) in sccs.iter().enumerate() { - // guess: every function has ~1 successor - let mut succs = Vec::with_capacity_in(symbols.len(), arena); - - for symbol in symbols { - for s in successors(symbol) { - let c = symbol_to_component[&s]; - - // don't insert self to prevent cycles - if c != i { - succs.push(c); + for key in call_info.keys.iter() { + // the same symbol can be in `keys` multiple times (with different layouts) + for (col, (k, _)) in procs.keys().enumerate() { + if k == key { + matrix.set_row_col(row, col, true); } } } - - succs.sort_unstable(); - succs.dedup(); - - component_to_successors.push(succs); } - let mut components = Vec::with_capacity_in(component_to_successors.len(), arena); - components.extend(0..component_to_successors.len()); + let sccs = matrix.strongly_connected_components_all(); - let mut groups = Vec::new_in(arena); - - let component_to_successors = &component_to_successors; - match ven_graph::topological_sort_into_groups(&components, |c: &usize| { - component_to_successors[*c].iter().copied() - }) { - Ok(component_groups) => { - let mut component_to_group = bumpalo::vec![in arena; usize::MAX; components.len()]; - - // for each component, store which group it is in - for (group_index, component_group) in component_groups.iter().enumerate() { - for component in component_group { - component_to_group[*component] = group_index; - } - } - - // prepare groups - groups.reserve(component_groups.len()); - for _ in 0..component_groups.len() { - groups.push(Vec::new_in(arena)); - } - - for (key, proc) in procs { - let symbol = key.0; - let offset = param_map.get_param_offset(key.0, key.1); - - // the component this symbol is a part of - let component = symbol_to_component[&symbol]; - - // now find the group that this component belongs to - let group = component_to_group[component]; - - groups[group].push((proc, offset)); - } - } - Err((_groups, _remainder)) => { - unreachable!("because we find strongly-connected components first"); - } - } - - for group in groups.into_iter().rev() { + for group in sccs.groups() { // This is a fixed-point analysis // // all functions initiall own all their parameters @@ -141,8 +77,10 @@ pub fn infer_borrow<'a>( // // when the signatures no longer change, the analysis stops and returns the signatures loop { - for (proc, param_offset) in group.iter() { - env.collect_proc(&mut param_map, proc, *param_offset); + for index in group.iter_ones() { + let (key, proc) = &procs.iter().nth(index).unwrap(); + let param_offset = param_map.get_param_offset(key.0, key.1); + env.collect_proc(&mut param_map, proc, param_offset); } if !env.modified { @@ -1000,7 +938,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { } NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked - | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos + | NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => { arena.alloc_slice_copy(&[irrelevant]) } @@ -1031,6 +969,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ExpectTrue => arena.alloc_slice_copy(&[irrelevant]), + ListIsUnique => arena.alloc_slice_copy(&[borrowed]), + BoxExpr | UnboxExpr => { unreachable!("These lowlevel operations are turned into mono Expr's") } @@ -1041,28 +981,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { } } -fn make_successor_mapping<'a>( - arena: &'a Bump, - procs: &MutMap<(Symbol, ProcLayout<'_>), Proc<'a>>, -) -> MutMap> { - let mut result = MutMap::with_capacity_and_hasher(procs.len(), default_hasher()); - - for (key, proc) in procs { - let mut call_info = CallInfo { - keys: Vec::new_in(arena), - }; - call_info_stmt(arena, &proc.body, &mut call_info); - - let mut keys = call_info.keys; - keys.sort_unstable(); - keys.dedup(); - - result.insert(key.0, keys); - } - - result -} - struct CallInfo<'a> { keys: Vec<'a, Symbol>, } diff --git a/compiler/mono/src/code_gen_help/mod.rs b/compiler/mono/src/code_gen_help/mod.rs index 3244e04fbb..e5799a571b 100644 --- a/compiler/mono/src/code_gen_help/mod.rs +++ b/compiler/mono/src/code_gen_help/mod.rs @@ -1,6 +1,5 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; -use roc_module::ident::Ident; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_target::TargetInfo; @@ -84,7 +83,7 @@ pub struct CodeGenHelp<'a> { impl<'a> CodeGenHelp<'a> { pub fn new(arena: &'a Bump, target_info: TargetInfo, home: ModuleId) -> Self { - let layout_isize = Layout::usize(target_info); + let layout_isize = Layout::isize(target_info); // Refcount is a boxed isize. TODO: use the new Box layout when dev backends support it let union_refcount = UnionLayout::NonNullableUnwrapped(arena.alloc([layout_isize])); @@ -396,7 +395,7 @@ impl<'a> CodeGenHelp<'a> { } fn create_symbol(&self, ident_ids: &mut IdentIds, debug_name: &str) -> Symbol { - let ident_id = ident_ids.add(Ident::from(debug_name)); + let ident_id = ident_ids.add_str(debug_name); Symbol::new(self.home, ident_id) } diff --git a/compiler/mono/src/copy.rs b/compiler/mono/src/copy.rs new file mode 100644 index 0000000000..0621d34061 --- /dev/null +++ b/compiler/mono/src/copy.rs @@ -0,0 +1,738 @@ +use bumpalo::collections::Vec; +use bumpalo::Bump; +use roc_can::{ + def::Def, + expr::{AccessorData, ClosureData, Expr, Field, WhenBranch}, +}; +use roc_types::subs::{ + AliasVariables, Descriptor, OptVariable, RecordFields, Subs, SubsSlice, UnionTags, Variable, + VariableSubsSlice, +}; + +/// Deep copies the type variables in the type hosted by [`var`] into [`expr`]. +/// Returns [`None`] if the expression does not need to be copied. +pub fn deep_copy_type_vars_into_expr<'a>( + arena: &'a Bump, + subs: &mut Subs, + var: Variable, + expr: &Expr, +) -> Option<(Variable, Expr)> { + // Always deal with the root, so that aliases propagate correctly. + let var = subs.get_root_key_without_compacting(var); + + let substitutions = deep_copy_type_vars(arena, subs, var); + + if substitutions.is_empty() { + return None; + } + + let new_var = substitutions + .iter() + .find_map(|&(original, new)| if original == var { Some(new) } else { None }) + .expect("Variable marked as cloned, but it isn't"); + + return Some((new_var, help(subs, expr, &substitutions))); + + fn help(subs: &Subs, expr: &Expr, substitutions: &[(Variable, Variable)]) -> Expr { + use Expr::*; + + macro_rules! sub { + ($var:expr) => {{ + // Always deal with the root, so that aliases propagate correctly. + let root = subs.get_root_key_without_compacting($var); + substitutions + .iter() + .find_map(|&(original, new)| if original == root { Some(new) } else { None }) + .unwrap_or($var) + }}; + } + + let go_help = |e: &Expr| help(subs, e, substitutions); + + match expr { + Num(var, str, val, bound) => Num(sub!(*var), str.clone(), val.clone(), *bound), + Int(v1, v2, str, val, bound) => { + Int(sub!(*v1), sub!(*v2), str.clone(), val.clone(), *bound) + } + Float(v1, v2, str, val, bound) => { + Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound) + } + Str(str) => Str(str.clone()), + SingleQuote(char) => SingleQuote(*char), + List { + elem_var, + loc_elems, + } => List { + elem_var: sub!(*elem_var), + loc_elems: loc_elems.iter().map(|le| le.map(go_help)).collect(), + }, + Var(sym) => Var(*sym), + AbilityMember(sym, specialization) => AbilityMember(*sym, *specialization), + When { + loc_cond, + cond_var, + expr_var, + region, + branches, + branches_cond_var, + exhaustive, + } => When { + loc_cond: Box::new(loc_cond.map(go_help)), + cond_var: sub!(*cond_var), + expr_var: sub!(*expr_var), + region: *region, + branches: branches + .iter() + .map( + |WhenBranch { + patterns, + value, + guard, + redundant, + }| WhenBranch { + patterns: patterns.clone(), + value: value.map(go_help), + guard: guard.as_ref().map(|le| le.map(go_help)), + redundant: *redundant, + }, + ) + .collect(), + branches_cond_var: sub!(*branches_cond_var), + exhaustive: *exhaustive, + }, + If { + cond_var, + branch_var, + branches, + final_else, + } => If { + cond_var: sub!(*cond_var), + branch_var: sub!(*branch_var), + branches: branches + .iter() + .map(|(c, e)| (c.map(go_help), e.map(go_help))) + .collect(), + final_else: Box::new(final_else.map(go_help)), + }, + + LetRec(defs, body) => LetRec( + defs.iter() + .map( + |Def { + loc_pattern, + loc_expr, + expr_var, + pattern_vars, + annotation, + }| Def { + loc_pattern: loc_pattern.clone(), + loc_expr: loc_expr.map(go_help), + expr_var: sub!(*expr_var), + pattern_vars: pattern_vars + .iter() + .map(|(s, v)| (*s, sub!(*v))) + .collect(), + annotation: annotation.clone(), + }, + ) + .collect(), + Box::new(body.map(go_help)), + ), + LetNonRec(def, body) => { + let Def { + loc_pattern, + loc_expr, + expr_var, + pattern_vars, + annotation, + } = &**def; + let def = Def { + loc_pattern: loc_pattern.clone(), + loc_expr: loc_expr.map(go_help), + expr_var: sub!(*expr_var), + pattern_vars: pattern_vars.iter().map(|(s, v)| (*s, sub!(*v))).collect(), + annotation: annotation.clone(), + }; + LetNonRec(Box::new(def), Box::new(body.map(go_help))) + } + + Call(f, args, called_via) => { + let (fn_var, fn_expr, clos_var, ret_var) = &**f; + Call( + Box::new(( + sub!(*fn_var), + fn_expr.map(go_help), + sub!(*clos_var), + sub!(*ret_var), + )), + args.iter() + .map(|(var, expr)| (sub!(*var), expr.map(go_help))) + .collect(), + *called_via, + ) + } + RunLowLevel { op, args, ret_var } => RunLowLevel { + op: *op, + args: args + .iter() + .map(|(var, expr)| (sub!(*var), go_help(expr))) + .collect(), + ret_var: sub!(*ret_var), + }, + ForeignCall { + foreign_symbol, + args, + ret_var, + } => ForeignCall { + foreign_symbol: foreign_symbol.clone(), + args: args + .iter() + .map(|(var, expr)| (sub!(*var), go_help(expr))) + .collect(), + ret_var: sub!(*ret_var), + }, + + Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name, + captured_symbols, + recursive, + arguments, + loc_body, + }) => Closure(ClosureData { + function_type: sub!(*function_type), + closure_type: sub!(*closure_type), + closure_ext_var: sub!(*closure_ext_var), + return_type: sub!(*return_type), + name: *name, + captured_symbols: captured_symbols + .iter() + .map(|(s, v)| (*s, sub!(*v))) + .collect(), + recursive: *recursive, + arguments: arguments + .iter() + .map(|(v, mark, pat)| (sub!(*v), *mark, pat.clone())) + .collect(), + loc_body: Box::new(loc_body.map(go_help)), + }), + + Record { record_var, fields } => Record { + record_var: sub!(*record_var), + fields: fields + .iter() + .map( + |( + k, + Field { + var, + region, + loc_expr, + }, + )| { + ( + k.clone(), + Field { + var: sub!(*var), + region: *region, + loc_expr: Box::new(loc_expr.map(go_help)), + }, + ) + }, + ) + .collect(), + }, + + EmptyRecord => EmptyRecord, + + Access { + record_var, + ext_var, + field_var, + loc_expr, + field, + } => Access { + record_var: sub!(*record_var), + ext_var: sub!(*ext_var), + field_var: sub!(*field_var), + loc_expr: Box::new(loc_expr.map(go_help)), + field: field.clone(), + }, + + Accessor(AccessorData { + name, + function_var, + record_var, + closure_var, + closure_ext_var, + ext_var, + field_var, + field, + }) => Accessor(AccessorData { + name: *name, + function_var: sub!(*function_var), + record_var: sub!(*record_var), + closure_var: sub!(*closure_var), + closure_ext_var: sub!(*closure_ext_var), + ext_var: sub!(*ext_var), + field_var: sub!(*field_var), + field: field.clone(), + }), + + Update { + record_var, + ext_var, + symbol, + updates, + } => Update { + record_var: sub!(*record_var), + ext_var: sub!(*ext_var), + symbol: *symbol, + updates: updates + .iter() + .map( + |( + k, + Field { + var, + region, + loc_expr, + }, + )| { + ( + k.clone(), + Field { + var: sub!(*var), + region: *region, + loc_expr: Box::new(loc_expr.map(go_help)), + }, + ) + }, + ) + .collect(), + }, + + Tag { + variant_var, + ext_var, + name, + arguments, + } => Tag { + variant_var: sub!(*variant_var), + ext_var: sub!(*ext_var), + name: name.clone(), + arguments: arguments + .iter() + .map(|(v, e)| (sub!(*v), e.map(go_help))) + .collect(), + }, + + ZeroArgumentTag { + closure_name, + variant_var, + ext_var, + name, + } => ZeroArgumentTag { + closure_name: *closure_name, + variant_var: sub!(*variant_var), + ext_var: sub!(*ext_var), + name: name.clone(), + }, + + OpaqueRef { + opaque_var, + name, + argument, + specialized_def_type, + type_arguments, + lambda_set_variables, + } => OpaqueRef { + opaque_var: sub!(*opaque_var), + name: *name, + argument: Box::new((sub!(argument.0), argument.1.map(go_help))), + // These shouldn't matter for opaques during mono, because they are only used for reporting + // and pretty-printing to the user. During mono we decay immediately into the argument. + // NB: if there are bugs, check if not substituting here is the problem! + specialized_def_type: specialized_def_type.clone(), + type_arguments: type_arguments.clone(), + lambda_set_variables: lambda_set_variables.clone(), + }, + + Expect(e1, e2) => Expect(Box::new(e1.map(go_help)), Box::new(e2.map(go_help))), + + RuntimeError(err) => RuntimeError(err.clone()), + } + } +} + +/// Deep copies the type variables in [`var`], returning a map of original -> new type variable for +/// all type variables copied. +fn deep_copy_type_vars<'a>( + arena: &'a Bump, + subs: &mut Subs, + var: Variable, +) -> Vec<'a, (Variable, Variable)> { + // Always deal with the root, so that unified variables are treated the same. + let var = subs.get_root_key_without_compacting(var); + + let mut copied = Vec::with_capacity_in(16, arena); + + let cloned_var = help(arena, subs, &mut copied, var); + + // we have tracked all visited variables, and can now traverse them + // in one go (without looking at the UnificationTable) and clear the copy field + let mut result = Vec::with_capacity_in(copied.len(), arena); + for var in copied { + let descriptor = subs.get_ref_mut(var); + + if let Some(copy) = descriptor.copy.into_variable() { + result.push((var, copy)); + descriptor.copy = OptVariable::NONE; + } else { + debug_assert!(false, "{:?} marked as copied but it wasn't", var); + } + } + + debug_assert!(result.contains(&(var, cloned_var))); + + return result; + + #[must_use] + fn help(arena: &Bump, subs: &mut Subs, visited: &mut Vec, var: Variable) -> Variable { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + // Always deal with the root, so that unified variables are treated the same. + let var = subs.get_root_key_without_compacting(var); + + let desc = subs.get_ref_mut(var); + + // Unlike `deep_copy_var` in solve, here we are cloning *all* flex and rigid vars. + // So we only want to short-circuit if we've already done the cloning work for a particular + // var. + if let Some(copy) = desc.copy.into_variable() { + return copy; + } + + let content = desc.content; + + let copy_descriptor = Descriptor { + content: Error, // we'll update this below + rank: desc.rank, + mark: desc.mark, + copy: OptVariable::NONE, + }; + + let copy = subs.fresh(copy_descriptor); + subs.get_ref_mut(var).copy = copy.into(); + + visited.push(var); + + macro_rules! descend_slice { + ($slice:expr) => { + for var_index in $slice { + let var = subs[var_index]; + let _ = help(arena, subs, visited, var); + } + }; + } + + macro_rules! descend_var { + ($var:expr) => {{ + help(arena, subs, visited, $var) + }}; + } + + macro_rules! clone_var_slice { + ($slice:expr) => {{ + let new_arguments = VariableSubsSlice::reserve_into_subs(subs, $slice.len()); + for (target_index, var_index) in (new_arguments.indices()).zip($slice) { + let var = subs[var_index]; + let copy_var = subs.get_ref(var).copy.into_variable().unwrap_or(var); + subs.variables[target_index] = copy_var; + } + new_arguments + }}; + } + + macro_rules! perform_clone { + ($do_clone:expr) => {{ + // It may the case that while deep-copying nested variables of this type, we + // ended up copying the type itself (notably if it was self-referencing, in a + // recursive type). In that case, short-circuit with the known copy. + // if let Some(copy) = subs.get_ref(var).copy.into_variable() { + // return copy; + // } + // Perform the clone. + $do_clone + }}; + } + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + let new_content = match content { + // The vars for which we want to do something interesting. + FlexVar(opt_name) => FlexVar(opt_name), + FlexAbleVar(opt_name, ability) => FlexAbleVar(opt_name, ability), + RigidVar(name) => RigidVar(name), + RigidAbleVar(name, ability) => RigidAbleVar(name, ability), + + // Everything else is a mechanical descent. + Structure(flat_type) => match flat_type { + EmptyRecord | EmptyTagUnion | Erroneous(_) => Structure(flat_type), + Apply(symbol, arguments) => { + descend_slice!(arguments); + + perform_clone!({ + let new_arguments = clone_var_slice!(arguments); + Structure(Apply(symbol, new_arguments)) + }) + } + Func(arguments, closure_var, ret_var) => { + descend_slice!(arguments); + + let new_closure_var = descend_var!(closure_var); + let new_ret_var = descend_var!(ret_var); + + perform_clone!({ + let new_arguments = clone_var_slice!(arguments); + Structure(Func(new_arguments, new_closure_var, new_ret_var)) + }) + } + Record(fields, ext_var) => { + let new_ext_var = descend_var!(ext_var); + + descend_slice!(fields.variables()); + + perform_clone!({ + let new_variables = clone_var_slice!(fields.variables()); + let new_fields = { + RecordFields { + length: fields.length, + field_names_start: fields.field_names_start, + variables_start: new_variables.start, + field_types_start: fields.field_types_start, + } + }; + + Structure(Record(new_fields, new_ext_var)) + }) + } + TagUnion(tags, ext_var) => { + let new_ext_var = descend_var!(ext_var); + + for variables_slice_index in tags.variables() { + let variables_slice = subs[variables_slice_index]; + descend_slice!(variables_slice); + } + + perform_clone!({ + let new_variable_slices = + SubsSlice::reserve_variable_slices(subs, tags.len()); + let it = (new_variable_slices.indices()).zip(tags.variables()); + for (target_index, index) in it { + let slice = subs[index]; + let new_variables = clone_var_slice!(slice); + subs.variable_slices[target_index] = new_variables; + } + + let new_union_tags = + UnionTags::from_slices(tags.tag_names(), new_variable_slices); + + Structure(TagUnion(new_union_tags, new_ext_var)) + }) + } + RecursiveTagUnion(rec_var, tags, ext_var) => { + let new_ext_var = descend_var!(ext_var); + let new_rec_var = descend_var!(rec_var); + + for variables_slice_index in tags.variables() { + let variables_slice = subs[variables_slice_index]; + descend_slice!(variables_slice); + } + + perform_clone!({ + let new_variable_slices = + SubsSlice::reserve_variable_slices(subs, tags.len()); + let it = (new_variable_slices.indices()).zip(tags.variables()); + for (target_index, index) in it { + let slice = subs[index]; + let new_variables = clone_var_slice!(slice); + subs.variable_slices[target_index] = new_variables; + } + + let new_union_tags = + UnionTags::from_slices(tags.tag_names(), new_variable_slices); + + Structure(RecursiveTagUnion(new_rec_var, new_union_tags, new_ext_var)) + }) + } + FunctionOrTagUnion(tag_name, symbol, ext_var) => { + let new_ext_var = descend_var!(ext_var); + perform_clone!(Structure(FunctionOrTagUnion(tag_name, symbol, new_ext_var))) + } + }, + + RecursionVar { + opt_name, + structure, + } => { + let new_structure = descend_var!(structure); + + perform_clone!({ + RecursionVar { + opt_name, + structure: new_structure, + } + }) + } + + Alias(symbol, arguments, real_type_var, kind) => { + let new_real_type_var = descend_var!(real_type_var); + descend_slice!(arguments.all_variables()); + + perform_clone!({ + let new_variables = clone_var_slice!(arguments.all_variables()); + let new_arguments = AliasVariables { + variables_start: new_variables.start, + ..arguments + }; + + Alias(symbol, new_arguments, new_real_type_var, kind) + }) + } + + RangedNumber(typ, range_vars) => { + let new_typ = descend_var!(typ); + descend_slice!(range_vars); + + perform_clone!({ + let new_range_vars = clone_var_slice!(range_vars); + + RangedNumber(new_typ, new_range_vars) + }) + } + Error => Error, + }; + + subs.set_content(copy, new_content); + + copy + } +} + +#[cfg(test)] +mod test { + use super::deep_copy_type_vars; + use bumpalo::Bump; + use roc_module::symbol::Symbol; + use roc_types::subs::{ + Content, Content::*, Descriptor, Mark, OptVariable, Rank, Subs, SubsIndex, Variable, + }; + + #[cfg(test)] + fn new_var(subs: &mut Subs, content: Content) -> Variable { + subs.fresh(Descriptor { + content, + rank: Rank::toplevel(), + mark: Mark::NONE, + copy: OptVariable::NONE, + }) + } + + #[test] + fn copy_flex_var() { + let mut subs = Subs::new(); + let arena = Bump::new(); + + let field_name = SubsIndex::push_new(&mut subs.field_names, "a".into()); + let var = new_var(&mut subs, FlexVar(Some(field_name))); + + let mut copies = deep_copy_type_vars(&arena, &mut subs, var); + + assert_eq!(copies.len(), 1); + let (original, new) = copies.pop().unwrap(); + assert_ne!(original, new); + + assert_eq!(original, var); + match subs.get_content_without_compacting(new) { + FlexVar(Some(name)) => { + assert_eq!(subs[*name].as_str(), "a"); + } + it => assert!(false, "{:?}", it), + } + } + + #[test] + fn copy_rigid_var() { + let mut subs = Subs::new(); + let arena = Bump::new(); + + let field_name = SubsIndex::push_new(&mut subs.field_names, "a".into()); + let var = new_var(&mut subs, RigidVar(field_name)); + + let mut copies = deep_copy_type_vars(&arena, &mut subs, var); + + assert_eq!(copies.len(), 1); + let (original, new) = copies.pop().unwrap(); + assert_ne!(original, new); + + assert_eq!(original, var); + match subs.get_content_without_compacting(new) { + RigidVar(name) => { + assert_eq!(subs[*name].as_str(), "a"); + } + it => assert!(false, "{:?}", it), + } + } + + #[test] + fn copy_flex_able_var() { + let mut subs = Subs::new(); + let arena = Bump::new(); + + let field_name = SubsIndex::push_new(&mut subs.field_names, "a".into()); + let var = new_var(&mut subs, FlexAbleVar(Some(field_name), Symbol::UNDERSCORE)); + + let mut copies = deep_copy_type_vars(&arena, &mut subs, var); + + assert_eq!(copies.len(), 1); + let (original, new) = copies.pop().unwrap(); + assert_ne!(original, new); + + assert_eq!(original, var); + match subs.get_content_without_compacting(new) { + FlexAbleVar(Some(name), Symbol::UNDERSCORE) => { + assert_eq!(subs[*name].as_str(), "a"); + } + it => assert!(false, "{:?}", it), + } + } + + #[test] + fn copy_rigid_able_var() { + let mut subs = Subs::new(); + let arena = Bump::new(); + + let field_name = SubsIndex::push_new(&mut subs.field_names, "a".into()); + let var = new_var(&mut subs, RigidAbleVar(field_name, Symbol::UNDERSCORE)); + + let mut copies = deep_copy_type_vars(&arena, &mut subs, var); + + assert_eq!(copies.len(), 1); + let (original, new) = copies.pop().unwrap(); + assert_ne!(original, new); + + assert_eq!(original, var); + match subs.get_content_without_compacting(new) { + RigidAbleVar(name, Symbol::UNDERSCORE) => { + assert_eq!(subs[*name].as_str(), "a"); + } + it => assert!(false, "{:?}", it), + } + } +} diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 2d9718c417..8e3f3816b8 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -4,7 +4,7 @@ use crate::ir::{ use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout}; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; -use roc_exhaustive::{Ctor, RenderAs, TagId, Union}; +use roc_exhaustive::{Ctor, CtorName, RenderAs, TagId, Union}; use roc_module::ident::TagName; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; @@ -82,7 +82,7 @@ enum GuardedTest<'a> { enum Test<'a> { IsCtor { tag_id: TagIdIntType, - tag_name: TagName, + ctor_name: CtorName, union: roc_exhaustive::Union, arguments: Vec<(Pattern<'a>, Layout<'a>)>, }, @@ -512,7 +512,7 @@ fn test_at_path<'a>( render_as: RenderAs::Tag, alternatives: vec![Ctor { tag_id: TagId(0), - name: TagName::Global(RECORD_TAG_NAME.into()), + name: CtorName::Tag(TagName::Tag(RECORD_TAG_NAME.into())), arity: destructs.len(), }], }; @@ -532,7 +532,7 @@ fn test_at_path<'a>( IsCtor { tag_id: 0, - tag_name: TagName::Global(RECORD_TAG_NAME.into()), + ctor_name: CtorName::Tag(TagName::Tag(RECORD_TAG_NAME.into())), union, arguments, } @@ -543,11 +543,12 @@ fn test_at_path<'a>( arguments, } => { let tag_id = 0; - let union = Union::newtype_wrapper(tag_name.clone(), arguments.len()); + let union = + Union::newtype_wrapper(CtorName::Tag(tag_name.clone()), arguments.len()); IsCtor { tag_id, - tag_name: tag_name.clone(), + ctor_name: CtorName::Tag(tag_name.clone()), union, arguments: arguments.to_vec(), } @@ -561,7 +562,7 @@ fn test_at_path<'a>( .. } => IsCtor { tag_id: *tag_id, - tag_name: tag_name.clone(), + ctor_name: CtorName::Tag(tag_name.clone()), union: union.clone(), arguments: arguments.to_vec(), }, @@ -571,14 +572,14 @@ fn test_at_path<'a>( render_as: RenderAs::Tag, alternatives: vec![Ctor { tag_id: TagId(0), - name: TagName::Private(*opaque), + name: CtorName::Opaque(*opaque), arity: 1, }], }; IsCtor { tag_id: 0, - tag_name: TagName::Private(*opaque), + ctor_name: CtorName::Opaque(*opaque), union, arguments: vec![(**argument).clone()], } @@ -680,11 +681,11 @@ fn to_relevant_branch_help<'a>( RecordDestructure(destructs, _) => match test { IsCtor { - tag_name: test_name, + ctor_name: test_name, tag_id, .. } => { - debug_assert!(test_name == &TagName::Global(RECORD_TAG_NAME.into())); + debug_assert!(test_name == &CtorName::Tag(TagName::Tag(RECORD_TAG_NAME.into()))); let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| { let pattern = match destruct.typ { DestructType::Guard(guard) => guard.clone(), @@ -713,11 +714,11 @@ fn to_relevant_branch_help<'a>( OpaqueUnwrap { opaque, argument } => match test { IsCtor { - tag_name: test_opaque_tag_name, + ctor_name: test_opaque_tag_name, tag_id, .. } => { - debug_assert_eq!(test_opaque_tag_name, &TagName::Private(opaque)); + debug_assert_eq!(test_opaque_tag_name, &CtorName::Opaque(opaque)); let (argument, _) = *argument; @@ -744,10 +745,10 @@ fn to_relevant_branch_help<'a>( .. } => match test { IsCtor { - tag_name: test_name, + ctor_name: test_name, tag_id: test_id, .. - } if &tag_name == test_name => { + } if test_name.is_tag(&tag_name) => { let tag_id = 0; debug_assert_eq!(tag_id, *test_id); @@ -785,10 +786,10 @@ fn to_relevant_branch_help<'a>( } => { match test { IsCtor { - tag_name: test_name, + ctor_name: test_name, tag_id: test_id, .. - } if &tag_name == test_name => { + } if test_name.is_tag(&tag_name) => { debug_assert_eq!(tag_id, *test_id); // the test matches the constructor of this pattern diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index bfcb3b3e90..79b54d446e 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -1,7 +1,7 @@ use crate::ir::DestructType; use roc_collections::all::HumanIndex; use roc_exhaustive::{ - is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, + is_useful, Context, Ctor, CtorName, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, }; use roc_module::ident::{TagIdIntType, TagName}; use roc_region::all::{Loc, Region}; @@ -45,7 +45,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { let union = Union { render_as: RenderAs::Record(field_names), alternatives: vec![Ctor { - name: TagName::Global("#Record".into()), + name: CtorName::Tag(TagName::Tag("#Record".into())), tag_id, arity: destructures.len(), }], @@ -62,7 +62,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { let simplified_args: std::vec::Vec<_> = arguments.iter().map(|v| simplify(&v.0)).collect(); Ctor( - Union::newtype_wrapper(tag_name.clone(), arguments.len()), + Union::newtype_wrapper(CtorName::Tag(tag_name.clone()), arguments.len()), TagId(tag_id), simplified_args, ) @@ -87,7 +87,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { let union = Union { render_as: RenderAs::Opaque, alternatives: vec![Ctor { - name: TagName::Private(*opaque), + name: CtorName::Opaque(*opaque), tag_id, arity: 1, }], @@ -113,7 +113,7 @@ pub fn check( } } -pub fn check_patterns<'a>( +fn check_patterns<'a>( region: Region, context: Context, patterns: &[(Loc>, Guard)], @@ -169,7 +169,7 @@ fn to_nonredundant_rows( render_as: RenderAs::Guard, alternatives: vec![Ctor { tag_id, - name: TagName::Global("#Guard".into()), + name: CtorName::Tag(TagName::Tag("#Guard".into())), arity: 2, }], }; diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 6836cc45fc..817a6b02a9 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -1,12 +1,76 @@ use crate::borrow::{ParamMap, BORROWED, OWNED}; use crate::ir::{ CallType, Expr, HigherOrderLowLevel, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt, + UpdateModeIds, }; use crate::layout::Layout; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_collections::all::{MutMap, MutSet}; -use roc_module::symbol::Symbol; +use roc_module::symbol::{IdentIds, ModuleId, Symbol}; + +/// Data and Function ownership relation for higher-order lowlevels +/// +/// Normally, the borrowing algorithm figures out how to own/borrow data so that +/// the borrows match up. But that fails for our higher-order lowlevels, because +/// they are rigid (cannot add extra inc/dec in there dynamically) and opaque to +/// the borrow inference. +/// +/// So, we must fix this up ourselves. This code is deliberately verbose to make +/// it easier to understand without full context. +/// +/// If we take `List.map list f` as an example, then there are two orders of freedom: +/// +/// - `list` can be either owned or borrowed by `List.map` +/// - `f` can require either owned or borrowed elements from `list` +/// +/// Hence we get the four options below: the data (`list` in the example) is owned or borrowed by +/// the higher-order function, and the function argument (`f`) either owns or borrows the elements. +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +enum DataFunction { + /// `list` is owned, and `f` expects owned values. That means that when we run the map, all + /// list elements will be consumed (because they are passed to `f`, which takes owned values) + /// Because we own the whole list, and must consume it, we need to `decref` the list. + /// `decref` just decrements the container, and will never recursively decrement elements + DataOwnedFunctionOwns, + /// `list` is owned, and `f` expects borrowed values. After running `f` for each element, the + /// elements are not consumed, and neither is the list. We must consume it though, because we + /// own the `list`. Therefore we need to perform a `dec` + DataOwnedFunctionBorrows, + /// `list` is borrowed, `f` borrows, so the trivial implementation is correct: just map `f` + /// over elements of `list`, and don't do anything else. + DataBorrowedFunctionBorrows, + /// The trickiest case: we only borrow the `list`, but the mapped function `f` needs owned + /// values. There are two options + /// + /// - define some `fBorrow` that takes a borrowed value, `inc`'s it (similar to `.clone()` on + /// an `Rc` in rust) and then passes the (now owned) value to `f`, then rewrite the call + /// to `List.map list fBorrow` + /// - `inc` the list (which recursively increments the elements), map `f` over the list, + /// consuming one RC token on the elements, finally `decref` the list. + /// + /// For simplicity, we use the second option right now, but the first option is probably + /// preferable long-term. + DataBorrowedFunctionOwns, +} + +impl DataFunction { + fn new(vars: &VarMap, lowlevel_argument: Symbol, passed_function_argument: Param) -> Self { + use DataFunction::*; + + let data_borrowed = !vars[&lowlevel_argument].consume; + let function_borrows = passed_function_argument.borrow; + + match (data_borrowed, function_borrows) { + (BORROWED, BORROWED) => DataBorrowedFunctionBorrows, + (BORROWED, OWNED) => DataBorrowedFunctionOwns, + (OWNED, BORROWED) => DataOwnedFunctionBorrows, + (OWNED, OWNED) => DataOwnedFunctionOwns, + } + } +} pub fn free_variables(stmt: &Stmt<'_>) -> MutSet { let (mut occurring, bound) = occurring_variables(stmt); @@ -443,8 +507,10 @@ impl<'a> Context<'a> { b } - fn visit_call( + #[allow(clippy::too_many_arguments)] + fn visit_call<'i>( &self, + codegen: &mut CodegenTools<'i>, z: Symbol, call_type: crate::ir::CallType<'a>, arguments: &'a [Symbol], @@ -467,186 +533,8 @@ impl<'a> Context<'a> { &*self.arena.alloc(Stmt::Let(z, v, l, b)) } - HigherOrder(HigherOrderLowLevel { - op, - closure_env_layout, - update_mode, - passed_function, - .. - }) => { - // setup - use crate::low_level::HigherOrder::*; - - macro_rules! create_call { - ($borrows:expr) => { - Expr::Call(crate::ir::Call { - call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) { - let mut passed_function = *passed_function; - passed_function.owns_captured_environment = true; - - let higher_order = HigherOrderLowLevel { - op: *op, - closure_env_layout: *closure_env_layout, - update_mode: *update_mode, - passed_function, - }; - - CallType::HigherOrder(self.arena.alloc(higher_order)) - } else { - call_type - }, - arguments, - }) - }; - } - - macro_rules! decref_if_owned { - ($borrows:expr, $argument:expr, $stmt:expr) => { - if !$borrows { - self.arena.alloc(Stmt::Refcounting( - ModifyRc::DecRef($argument), - self.arena.alloc($stmt), - )) - } else { - $stmt - } - }; - } - - const FUNCTION: bool = BORROWED; - const CLOSURE_DATA: bool = BORROWED; - - let function_layout = ProcLayout { - arguments: passed_function.argument_layouts, - result: passed_function.return_layout, - }; - - let function_ps = match self - .param_map - .get_symbol(passed_function.name, function_layout) - { - Some(function_ps) => function_ps, - None => unreachable!(), - }; - - match op { - ListMap { xs } - | ListKeepIf { xs } - | ListKeepOks { xs } - | ListKeepErrs { xs } - | ListAny { xs } - | ListAll { xs } - | ListFindUnsafe { xs } => { - let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA]; - - let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); - - // if the list is owned, then all elements have been consumed, but not the list itself - let b = decref_if_owned!(function_ps[0].borrow, *xs, b); - - let v = create_call!(function_ps.get(1)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - ListMap2 { xs, ys } => { - let borrows = [ - function_ps[0].borrow, - function_ps[1].borrow, - FUNCTION, - CLOSURE_DATA, - ]; - - let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); - - let b = decref_if_owned!(function_ps[0].borrow, *xs, b); - let b = decref_if_owned!(function_ps[1].borrow, *ys, b); - - let v = create_call!(function_ps.get(2)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - ListMap3 { xs, ys, zs } => { - let borrows = [ - function_ps[0].borrow, - function_ps[1].borrow, - function_ps[2].borrow, - FUNCTION, - CLOSURE_DATA, - ]; - - let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); - - let b = decref_if_owned!(function_ps[0].borrow, *xs, b); - let b = decref_if_owned!(function_ps[1].borrow, *ys, b); - let b = decref_if_owned!(function_ps[2].borrow, *zs, b); - - let v = create_call!(function_ps.get(3)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - ListMap4 { xs, ys, zs, ws } => { - let borrows = [ - function_ps[0].borrow, - function_ps[1].borrow, - function_ps[2].borrow, - function_ps[3].borrow, - FUNCTION, - CLOSURE_DATA, - ]; - - let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); - - let b = decref_if_owned!(function_ps[0].borrow, *xs, b); - let b = decref_if_owned!(function_ps[1].borrow, *ys, b); - let b = decref_if_owned!(function_ps[2].borrow, *zs, b); - let b = decref_if_owned!(function_ps[3].borrow, *ws, b); - - let v = create_call!(function_ps.get(3)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - ListMapWithIndex { xs } => { - let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA]; - - let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); - - let b = decref_if_owned!(function_ps[1].borrow, *xs, b); - - let v = create_call!(function_ps.get(2)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - ListSortWith { xs: _ } => { - let borrows = [OWNED, FUNCTION, CLOSURE_DATA]; - - let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); - - let v = create_call!(function_ps.get(2)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - ListWalk { xs, state: _ } - | ListWalkUntil { xs, state: _ } - | ListWalkBackwards { xs, state: _ } - | DictWalk { xs, state: _ } => { - // borrow data structure based on first argument of the folded function - // borrow the default based on second argument of the folded function - let borrows = [ - function_ps[1].borrow, - function_ps[0].borrow, - FUNCTION, - CLOSURE_DATA, - ]; - - let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); - - let b = decref_if_owned!(function_ps[1].borrow, *xs, b); - - let v = create_call!(function_ps.get(2)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - } + HigherOrder(lowlevel) => { + self.visit_higher_order_lowlevel(codegen, z, lowlevel, arguments, l, b, b_live_vars) } Foreign { .. } => { @@ -688,9 +576,273 @@ impl<'a> Context<'a> { } } - #[allow(clippy::many_single_char_names)] - fn visit_variable_declaration( + #[allow(clippy::too_many_arguments)] + fn visit_higher_order_lowlevel<'i>( &self, + codegen: &mut CodegenTools<'i>, + z: Symbol, + lowlevel: &'a crate::ir::HigherOrderLowLevel, + arguments: &'a [Symbol], + l: Layout<'a>, + b: &'a Stmt<'a>, + b_live_vars: &LiveVarSet, + ) -> &'a Stmt<'a> { + use crate::low_level::HigherOrder::*; + use DataFunction::*; + + let HigherOrderLowLevel { + op, + passed_function, + .. + } = lowlevel; + + macro_rules! create_call { + ($borrows:expr) => { + create_holl_call(self.arena, lowlevel, $borrows, arguments) + }; + } + + let function_layout = ProcLayout { + arguments: passed_function.argument_layouts, + result: passed_function.return_layout, + }; + + let function_ps = match self + .param_map + .get_symbol(passed_function.name, function_layout) + { + Some(function_ps) => function_ps, + None => unreachable!(), + }; + + macro_rules! handle_ownerships_post { + ($stmt:expr, $args:expr) => {{ + let mut stmt = $stmt; + + for (argument, function_ps) in $args.iter().copied() { + let ownership = DataFunction::new(&self.vars, argument, function_ps); + + match ownership { + DataOwnedFunctionOwns | DataBorrowedFunctionOwns => { + // elements have been consumed, must still consume the list itself + let rest = self.arena.alloc($stmt); + let rc = Stmt::Refcounting(ModifyRc::DecRef(argument), rest); + + stmt = self.arena.alloc(rc); + } + DataOwnedFunctionBorrows => { + // must consume list and elements + let rest = self.arena.alloc($stmt); + let rc = Stmt::Refcounting(ModifyRc::Dec(argument), rest); + + stmt = self.arena.alloc(rc); + } + DataBorrowedFunctionBorrows => { + // list borrows, function borrows, so there is nothing to do + } + } + } + + stmt + }}; + } + + macro_rules! handle_ownerships_pre { + ($stmt:expr, $args:expr) => {{ + let mut stmt = self.arena.alloc($stmt); + + for (argument, function_ps) in $args.iter().copied() { + let ownership = DataFunction::new(&self.vars, argument, function_ps); + + match ownership { + DataBorrowedFunctionOwns => { + // the data is borrowed; + // increment it to own the values so the function can use them + let rc = Stmt::Refcounting(ModifyRc::Inc(argument, 1), stmt); + + stmt = self.arena.alloc(rc); + } + DataOwnedFunctionOwns | DataOwnedFunctionBorrows => { + // we actually own the data; nothing to do + } + DataBorrowedFunctionBorrows => { + // list borrows, function borrows, so there is nothing to do + } + } + } + + stmt + }}; + } + + // incrementing/consuming the closure (if needed) is done by the zig implementation. + // We don't want to touch the RC on the roc side, so treat these as borrowed. + const FUNCTION: bool = BORROWED; + const CLOSURE_DATA: bool = BORROWED; + + let borrows = [FUNCTION, CLOSURE_DATA]; + let after_arguments = &arguments[op.function_index()..]; + + match *op { + ListMap { xs } + | ListKeepIf { xs } + | ListKeepOks { xs } + | ListKeepErrs { xs } + | ListAny { xs } + | ListAll { xs } + | ListFindUnsafe { xs } => { + let ownerships = [(xs, function_ps[0])]; + + let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); + + let b = handle_ownerships_post!(b, ownerships); + + let v = create_call!(function_ps.get(1)); + + handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) + } + ListMap2 { xs, ys } => { + let ownerships = [(xs, function_ps[0]), (ys, function_ps[1])]; + + let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); + + let b = handle_ownerships_post!(b, ownerships); + + let v = create_call!(function_ps.get(2)); + + handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) + } + ListMap3 { xs, ys, zs } => { + let ownerships = [ + (xs, function_ps[0]), + (ys, function_ps[1]), + (zs, function_ps[2]), + ]; + + let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); + + let b = handle_ownerships_post!(b, ownerships); + + let v = create_call!(function_ps.get(3)); + + handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) + } + ListMap4 { xs, ys, zs, ws } => { + let ownerships = [ + (xs, function_ps[0]), + (ys, function_ps[1]), + (zs, function_ps[2]), + (ws, function_ps[3]), + ]; + + let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); + + let b = handle_ownerships_post!(b, ownerships); + + let v = create_call!(function_ps.get(3)); + + handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) + } + ListMapWithIndex { xs } => { + let ownerships = [(xs, function_ps[0])]; + + let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); + + let b = handle_ownerships_post!(b, ownerships); + + let v = create_call!(function_ps.get(2)); + + handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) + } + ListSortWith { xs } => { + // NOTE: we may apply the function to the same argument multiple times. + // for that to be valid, the function must borrow its argument. This is not + // enforced at the moment + // + // we also don't check that both arguments have the same ownership characteristics + let ownerships = [(xs, function_ps[0])]; + + let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); + + // list-sort will sort in-place; that really changes how RC should work + let b = { + let ownership = DataFunction::new(&self.vars, xs, function_ps[0]); + + match ownership { + DataOwnedFunctionOwns => { + // if non-unique, elements have been consumed, must still consume the list itself + let rc = Stmt::Refcounting(ModifyRc::DecRef(xs), b); + + let condition_stmt = branch_on_list_uniqueness( + self.arena, + codegen, + xs, + l, + b.clone(), + self.arena.alloc(rc), + ); + + &*self.arena.alloc(condition_stmt) + } + DataOwnedFunctionBorrows => { + // must consume list and elements + let rc = Stmt::Refcounting(ModifyRc::Dec(xs), b); + + let condition_stmt = branch_on_list_uniqueness( + self.arena, + codegen, + xs, + l, + b.clone(), + self.arena.alloc(rc), + ); + + &*self.arena.alloc(condition_stmt) + } + DataBorrowedFunctionOwns => { + // elements have been consumed, must still consume the list itself + let rest = self.arena.alloc(b); + let rc = Stmt::Refcounting(ModifyRc::DecRef(xs), rest); + + &*self.arena.alloc(rc) + } + DataBorrowedFunctionBorrows => { + // list borrows, function borrows, so there is nothing to do + b + } + } + }; + + let v = create_call!(function_ps.get(2)); + + handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) + } + ListWalk { xs, state: _ } + | ListWalkUntil { xs, state: _ } + | ListWalkBackwards { xs, state: _ } + | DictWalk { xs, state: _ } => { + let ownerships = [ + // borrow data structure based on second argument of the folded function + (xs, function_ps[1]), + ]; + // borrow the default based on first argument of the folded function + // (state, function_ps[0]) + + let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars); + + let b = handle_ownerships_post!(b, ownerships); + + let v = create_call!(function_ps.get(2)); + + handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships) + } + } + } + + #[allow(clippy::many_single_char_names)] + fn visit_variable_declaration<'i>( + &self, + codegen: &mut CodegenTools<'i>, z: Symbol, v: Expr<'a>, l: Layout<'a>, @@ -722,7 +874,7 @@ impl<'a> Context<'a> { Call(crate::ir::Call { call_type, arguments, - }) => self.visit_call(z, call_type, arguments, l, b, b_live_vars), + }) => self.visit_call(codegen, z, call_type, arguments, l, b, b_live_vars), StructAtIndex { structure: x, .. } => { let b = self.add_dec_if_needed(x, b, b_live_vars); @@ -883,7 +1035,11 @@ impl<'a> Context<'a> { b } - fn visit_stmt(&self, stmt: &'a Stmt<'a>) -> (&'a Stmt<'a>, LiveVarSet) { + fn visit_stmt<'i>( + &self, + codegen: &mut CodegenTools<'i>, + stmt: &'a Stmt<'a>, + ) -> (&'a Stmt<'a>, LiveVarSet) { use Stmt::*; // let-chains can be very long, especially for large (list) literals @@ -902,9 +1058,10 @@ impl<'a> Context<'a> { for (symbol, expr, layout) in triples.iter() { ctx = ctx.update_var_info(**symbol, layout, expr); } - let (mut b, mut b_live_vars) = ctx.visit_stmt(cont); + let (mut b, mut b_live_vars) = ctx.visit_stmt(codegen, cont); for (symbol, expr, layout) in triples.into_iter().rev() { let pair = ctx.visit_variable_declaration( + codegen, *symbol, (*expr).clone(), *layout, @@ -923,8 +1080,15 @@ impl<'a> Context<'a> { match stmt { Let(symbol, expr, layout, cont) => { let ctx = self.update_var_info(*symbol, layout, expr); - let (b, b_live_vars) = ctx.visit_stmt(cont); - ctx.visit_variable_declaration(*symbol, expr.clone(), *layout, b, &b_live_vars) + let (b, b_live_vars) = ctx.visit_stmt(codegen, cont); + ctx.visit_variable_declaration( + codegen, + *symbol, + expr.clone(), + *layout, + b, + &b_live_vars, + ) } Join { @@ -938,7 +1102,7 @@ impl<'a> Context<'a> { let (v, v_live_vars) = { let ctx = self.update_var_info_with_params(xs); - ctx.visit_stmt(v) + ctx.visit_stmt(codegen, v) }; let mut ctx = self.clone(); @@ -946,7 +1110,7 @@ impl<'a> Context<'a> { update_jp_live_vars(*j, xs, v, &mut ctx.jp_live_vars); - let (b, b_live_vars) = ctx.visit_stmt(b); + let (b, b_live_vars) = ctx.visit_stmt(codegen, b); ( ctx.arena.alloc(Join { @@ -1001,7 +1165,7 @@ impl<'a> Context<'a> { branches.iter().map(|(label, info, branch)| { // TODO should we use ctor info like Lean? let ctx = self.clone(); - let (b, alt_live_vars) = ctx.visit_stmt(branch); + let (b, alt_live_vars) = ctx.visit_stmt(codegen, branch); let b = ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b); (*label, info.clone(), b.clone()) @@ -1013,7 +1177,7 @@ impl<'a> Context<'a> { let default_branch = { // TODO should we use ctor info like Lean? let ctx = self.clone(); - let (b, alt_live_vars) = ctx.visit_stmt(default_branch.1); + let (b, alt_live_vars) = ctx.visit_stmt(codegen, default_branch.1); ( default_branch.0.clone(), @@ -1037,6 +1201,75 @@ impl<'a> Context<'a> { } } +fn branch_on_list_uniqueness<'a, 'i>( + arena: &'a Bump, + codegen: &mut CodegenTools<'i>, + list_symbol: Symbol, + return_layout: Layout<'a>, + then_branch_stmt: Stmt<'a>, + else_branch_stmt: &'a Stmt<'a>, +) -> Stmt<'a> { + let condition_symbol = Symbol::new(codegen.home, codegen.ident_ids.add_str("listIsUnique")); + + let when_stmt = Stmt::if_then_else( + arena, + condition_symbol, + return_layout, + then_branch_stmt, + else_branch_stmt, + ); + + let stmt = arena.alloc(when_stmt); + + // define the condition + + let condition_call_type = CallType::LowLevel { + op: roc_module::low_level::LowLevel::ListIsUnique, + update_mode: codegen.update_mode_ids.next_id(), + }; + + let condition_call = crate::ir::Call { + call_type: condition_call_type, + arguments: arena.alloc([list_symbol]), + }; + + Stmt::Let( + condition_symbol, + Expr::Call(condition_call), + Layout::bool(), + stmt, + ) +} + +fn create_holl_call<'a>( + arena: &'a Bump, + holl: &'a crate::ir::HigherOrderLowLevel, + param: Option<&Param>, + arguments: &'a [Symbol], +) -> Expr<'a> { + let call = crate::ir::Call { + call_type: if let Some(OWNED) = param.map(|p| p.borrow) { + let mut passed_function = holl.passed_function; + passed_function.owns_captured_environment = true; + + let higher_order = HigherOrderLowLevel { + op: holl.op, + closure_env_layout: holl.closure_env_layout, + update_mode: holl.update_mode, + passed_function, + }; + + CallType::HigherOrder(arena.alloc(higher_order)) + } else { + debug_assert!(!holl.passed_function.owns_captured_environment); + CallType::HigherOrder(holl) + }, + arguments, + }; + + Expr::Call(call) +} + pub fn collect_stmt( stmt: &Stmt<'_>, jp_live_vars: &JPLiveVarMap, @@ -1127,20 +1360,36 @@ fn update_jp_live_vars(j: JoinPointId, ys: &[Param], v: &Stmt<'_>, m: &mut JPLiv m.insert(j, j_live_vars); } -pub fn visit_procs<'a>( +struct CodegenTools<'i> { + home: ModuleId, + ident_ids: &'i mut IdentIds, + update_mode_ids: &'i mut UpdateModeIds, +} + +pub fn visit_procs<'a, 'i>( arena: &'a Bump, + home: ModuleId, + ident_ids: &'i mut IdentIds, + update_mode_ids: &'i mut UpdateModeIds, param_map: &'a ParamMap<'a>, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) { let ctx = Context::new(arena, param_map); + let mut codegen = CodegenTools { + home, + ident_ids, + update_mode_ids, + }; + for (key, proc) in procs.iter_mut() { - visit_proc(arena, param_map, &ctx, proc, key.1); + visit_proc(arena, &mut codegen, param_map, &ctx, proc, key.1); } } -fn visit_proc<'a>( +fn visit_proc<'a, 'i>( arena: &'a Bump, + codegen: &mut CodegenTools<'i>, param_map: &'a ParamMap<'a>, ctx: &Context<'a>, proc: &mut Proc<'a>, @@ -1161,7 +1410,7 @@ fn visit_proc<'a>( let stmt = arena.alloc(proc.body.clone()); let ctx = ctx.update_var_info_with_params(params); - let (b, b_live_vars) = ctx.visit_stmt(stmt); + let (b, b_live_vars) = ctx.visit_stmt(codegen, stmt); let b = ctx.add_dec_for_dead_params(params, b, &b_live_vars); proc.body = b.clone(); diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 69d1a080f8..67e8880ce5 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4,29 +4,45 @@ use crate::layout::{ Builtin, ClosureRepresentation, LambdaSet, Layout, LayoutCache, LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant, }; -use bumpalo::collections::Vec; +use bumpalo::collections::{CollectIn, Vec}; use bumpalo::Bump; use roc_builtins::bitcode::{FloatWidth, IntWidth}; -use roc_can::abilities::AbilitiesStore; -use roc_can::expr::{ClosureData, IntValue}; +use roc_can::abilities::{AbilitiesStore, SpecializationId}; +use roc_can::expr::{AnnotatedMark, ClosureData, IntValue}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; -use roc_exhaustive::{Ctor, Guard, RenderAs, TagId}; +use roc_collections::{MutSet, VecMap}; +use roc_debug_flags::{ + dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, + ROC_PRINT_IR_AFTER_SPECIALIZATION, +}; +use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::{RuntimeError, ShadowKind}; use roc_region::all::{Loc, Region}; +use roc_solve::ability::resolve_ability_specialization; use roc_std::RocDec; use roc_target::TargetInfo; -use roc_types::subs::{Content, FlatType, StorageSubs, Subs, Variable, VariableSubsSlice}; +use roc_types::subs::{ + Content, ExhaustiveMark, FlatType, RedundantMark, StorageSubs, Subs, Variable, + VariableSubsSlice, +}; +use roc_unify::unify::Mode; use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; +#[inline(always)] pub fn pretty_print_ir_symbols() -> bool { - #[cfg(debug_assertions)] - if std::env::var("PRETTY_PRINT_IR_SYMBOLS") == Ok("1".into()) { + dbg_do!(ROC_PRINT_IR_AFTER_SPECIALIZATION, { return true; - } + }); + dbg_do!(ROC_PRINT_IR_AFTER_RESET_REUSE, { + return true; + }); + dbg_do!(ROC_PRINT_IR_AFTER_REFCOUNT, { + return true; + }); false } @@ -43,11 +59,11 @@ roc_error_macros::assert_sizeof_aarch64!(Call, 7 * 8); roc_error_macros::assert_sizeof_aarch64!(CallType, 5 * 8); roc_error_macros::assert_sizeof_wasm!(Literal, 24); -roc_error_macros::assert_sizeof_wasm!(Expr, 56); +roc_error_macros::assert_sizeof_wasm!(Expr, 48); roc_error_macros::assert_sizeof_wasm!(Stmt, 120); roc_error_macros::assert_sizeof_wasm!(ProcLayout, 32); -roc_error_macros::assert_sizeof_wasm!(Call, 40); -roc_error_macros::assert_sizeof_wasm!(CallType, 32); +roc_error_macros::assert_sizeof_wasm!(Call, 36); +roc_error_macros::assert_sizeof_wasm!(CallType, 28); roc_error_macros::assert_sizeof_default!(Literal, 3 * 8); roc_error_macros::assert_sizeof_default!(Expr, 10 * 8); @@ -204,9 +220,8 @@ impl<'a> PartialProc<'a> { #[allow(clippy::too_many_arguments)] pub fn from_named_function( env: &mut Env<'a, '_>, - layout_cache: &mut LayoutCache<'a>, annotation: Variable, - loc_args: std::vec::Vec<(Variable, Loc)>, + loc_args: std::vec::Vec<(Variable, AnnotatedMark, Loc)>, loc_body: Loc, captured_symbols: CapturedSymbols<'a>, is_self_recursive: bool, @@ -214,7 +229,7 @@ impl<'a> PartialProc<'a> { ) -> PartialProc<'a> { let number_of_arguments = loc_args.len(); - match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { + match patterns_to_when(env, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { // a named closure. Since these aren't specialized by the surrounding // context, we can't add pending specializations for them yet. @@ -251,80 +266,24 @@ impl<'a> PartialProc<'a> { } } -#[derive(Clone, Debug)] -enum PolymorphicExpr { - /// A root ability member, which must be specialized at a call site, for example - /// "hash" which must be specialized to an exact symbol implementing "hash" for a type. - AbilityMember(Symbol), - /// A polymorphic expression we inline at the usage site. - Expr(roc_can::expr::Expr, Variable), -} +#[derive(Clone, Copy, Debug)] +struct AbilityMember(Symbol); +/// A table of aliases of ability member symbols. #[derive(Clone, Debug)] -enum PartialExprLink { - /// The root polymorphic expression - Sink(PolymorphicExpr), - /// A hop in a partial expression alias chain - Aliases(Symbol), -} +struct AbilityAliases(BumpMap); -/// A table of symbols to polymorphic expressions. For example, in the program -/// -/// n = 1 -/// -/// asU8 : U8 -> U8 -/// asU8 = \_ -> 1 -/// -/// asU32 : U32 -> U8 -/// asU32 = \_ -> 1 -/// -/// asU8 n + asU32 n -/// -/// The expression bound by `n` doesn't have a definite layout until it is used -/// at the call sites `asU8 n`, `asU32 n`. -/// -/// Polymorphic *functions* are stored in `PartialProc`s, since functions are -/// non longer first-class once we finish lowering to the IR. -#[derive(Clone, Debug)] -struct PartialExprs(BumpMap); - -impl PartialExprs { +impl AbilityAliases { fn new_in(arena: &Bump) -> Self { Self(BumpMap::new_in(arena)) } - fn insert(&mut self, symbol: Symbol, expr: PolymorphicExpr) { - self.0.insert(symbol, PartialExprLink::Sink(expr)); + fn insert(&mut self, symbol: Symbol, member: AbilityMember) { + self.0.insert(symbol, member); } - fn insert_alias(&mut self, symbol: Symbol, aliases: Symbol) { - self.0.insert(symbol, PartialExprLink::Aliases(aliases)); - } - - fn contains(&self, symbol: Symbol) -> bool { - self.0.contains_key(&symbol) - } - - fn get(&mut self, mut symbol: Symbol) -> Option<&PolymorphicExpr> { - // In practice the alias chain is very short - loop { - match self.0.get(&symbol) { - None => { - return None; - } - Some(&PartialExprLink::Aliases(real_symbol)) => { - symbol = real_symbol; - } - Some(PartialExprLink::Sink(expr)) => { - return Some(expr); - } - } - } - } - - fn remove(&mut self, symbol: Symbol) { - debug_assert!(self.contains(symbol)); - self.0.remove(&symbol); + fn get(&self, symbol: Symbol) -> Option<&AbilityMember> { + self.0.get(&symbol) } } @@ -432,13 +391,23 @@ impl<'a> Proc<'a> { String::from_utf8(w).unwrap() } - pub fn insert_refcount_operations( + pub fn insert_refcount_operations<'i>( arena: &'a Bump, + home: ModuleId, + ident_ids: &'i mut IdentIds, + update_mode_ids: &'i mut UpdateModeIds, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) { let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs)); - crate::inc_dec::visit_procs(arena, borrow_params, procs); + crate::inc_dec::visit_procs( + arena, + home, + ident_ids, + update_mode_ids, + borrow_params, + procs, + ); } pub fn insert_reset_reuse_operations<'i>( @@ -712,7 +681,7 @@ impl<'a> Specialized<'a> { }) } - fn is_specialized(&mut self, symbol: Symbol, layout: &ProcLayout<'a>) -> bool { + fn is_specialized(&self, symbol: Symbol, layout: &ProcLayout<'a>) -> bool { for (i, s) in self.symbols.iter().enumerate() { if *s == symbol && &self.proc_layouts[i] == layout { return true; @@ -786,29 +755,182 @@ impl<'a> Specialized<'a> { } } +/// Uniquely determines the specialization of a polymorphic (non-proc) value symbol. +/// Two specializations are equivalent if their [`SpecializationMark`]s are equal. +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +struct SpecializationMark<'a> { + /// The layout of the symbol itself. + layout: Layout<'a>, + + /// If this symbol is a closure def, we must also keep track of what function it specializes, + /// because the [`layout`] field will only keep track of its closure and lambda set - which can + /// be the same for two different function specializations. For example, + /// + /// id = if True then \x -> x else \y -> y + /// { a: id "", b: id 1u8 } + /// + /// The lambda set and captures of `id` is the same in both usages inside the record, but the + /// reified specializations of `\x -> x` and `\y -> y` must be for Str and U8. + /// + /// Note that this field is not relevant for anything that is not a function. + function_mark: Option>, +} + +/// When walking a function body, we may encounter specialized usages of polymorphic symbols. For +/// example +/// +/// myTag = A +/// use1 : [A, B] +/// use1 = myTag +/// use2 : [A, B, C] +/// use2 = myTag +/// +/// We keep track of the specializations of `myTag` and create fresh symbols when there is more +/// than one, so that a unique def can be created for each. +#[derive(Default, Debug, Clone)] +struct SymbolSpecializations<'a>( + // THEORY: + // 1. the number of symbols in a def is very small + // 2. the number of specializations of a symbol in a def is even smaller (almost always only one) + // So, a linear VecMap is preferrable. Use a two-layered one to make (1) extraction of defs easy + // and (2) reads of a certain symbol be determined by its first occurrence, not its last. + VecMap, (Variable, Symbol)>>, +); + +impl<'a> SymbolSpecializations<'a> { + /// Gets a specialization for a symbol, or creates a new one. + #[inline(always)] + fn get_or_insert( + &mut self, + env: &mut Env<'a, '_>, + layout_cache: &mut LayoutCache<'a>, + symbol: Symbol, + specialization_var: Variable, + ) -> Symbol { + let arena = env.arena; + let subs: &Subs = env.subs; + + let layout = match layout_cache.from_var(arena, specialization_var, subs) { + Ok(layout) => layout, + // This can happen when the def symbol has a type error. In such cases just use the + // def symbol, which is erroring. + Err(_) => return symbol, + }; + + let is_closure = matches!( + subs.get_content_without_compacting(specialization_var), + Content::Structure(FlatType::Func(..)) + ); + let function_mark = if is_closure { + let fn_layout = match layout_cache.raw_from_var(arena, specialization_var, subs) { + Ok(layout) => layout, + // This can happen when the def symbol has a type error. In such cases just use the + // def symbol, which is erroring. + Err(_) => return symbol, + }; + Some(fn_layout) + } else { + None + }; + + let specialization_mark = SpecializationMark { + layout, + function_mark, + }; + + let symbol_specializations = self.0.get_or_insert(symbol, Default::default); + + // For the first specialization, always reuse the current symbol. The vast majority of defs + // only have one instance type, so this preserves readability of the IR. + // TODO: turn me off and see what breaks. + let needs_fresh_symbol = !symbol_specializations.is_empty(); + + let mut make_specialized_symbol = || { + if needs_fresh_symbol { + env.unique_symbol() + } else { + symbol + } + }; + + let (_var, specialized_symbol) = symbol_specializations + .get_or_insert(specialization_mark, || { + (specialization_var, make_specialized_symbol()) + }); + + *specialized_symbol + } + + /// Inserts a known specialization for a symbol. Returns the overwritten specialization, if any. + pub fn get_or_insert_known( + &mut self, + symbol: Symbol, + mark: SpecializationMark<'a>, + specialization_var: Variable, + specialization_symbol: Symbol, + ) -> Option<(Variable, Symbol)> { + self.0 + .get_or_insert(symbol, Default::default) + .insert(mark, (specialization_var, specialization_symbol)) + } + + /// Removes all specializations for a symbol, returning the type and symbol of each specialization. + pub fn remove( + &mut self, + symbol: Symbol, + ) -> impl ExactSizeIterator, (Variable, Symbol))> { + self.0 + .remove(&symbol) + .map(|(_, specializations)| specializations) + .unwrap_or_default() + .into_iter() + } + + /// Expects and removes at most a single specialization symbol for the given requested symbol. + /// A symbol may have no specializations if it is never referenced in a body, so it is possible + /// for this to return None. + pub fn remove_single(&mut self, symbol: Symbol) -> Option { + let mut specializations = self.remove(symbol); + + debug_assert!( + specializations.len() < 2, + "Symbol {:?} has multiple specializations", + symbol + ); + + specializations.next().map(|(_, (_, symbol))| symbol) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + #[derive(Clone, Debug)] pub struct Procs<'a> { pub partial_procs: PartialProcs<'a>, - partial_exprs: PartialExprs, + ability_member_aliases: AbilityAliases, pub imported_module_thunks: &'a [Symbol], pub module_thunks: &'a [Symbol], pending_specializations: PendingSpecializations<'a>, specialized: Specialized<'a>, pub runtime_errors: BumpMap, pub externals_we_need: BumpMap, + symbol_specializations: SymbolSpecializations<'a>, } impl<'a> Procs<'a> { pub fn new_in(arena: &'a Bump) -> Self { Self { partial_procs: PartialProcs::new_in(arena), - partial_exprs: PartialExprs::new_in(arena), + ability_member_aliases: AbilityAliases::new_in(arena), imported_module_thunks: &[], module_thunks: &[], pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)), specialized: Specialized::default(), runtime_errors: BumpMap::new_in(arena), externals_we_need: BumpMap::new_in(arena), + symbol_specializations: Default::default(), } } } @@ -855,7 +977,7 @@ impl<'a> Procs<'a> { env: &mut Env<'a, '_>, symbol: Symbol, annotation: Variable, - loc_args: std::vec::Vec<(Variable, Loc)>, + loc_args: std::vec::Vec<(Variable, AnnotatedMark, Loc)>, loc_body: Loc, captured_symbols: CapturedSymbols<'a>, ret_var: Variable, @@ -874,7 +996,7 @@ impl<'a> Procs<'a> { _ => false, }; - match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { + match patterns_to_when(env, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { // an anonymous closure. These will always be specialized already // by the surrounding context, so we can add pending specializations @@ -1131,7 +1253,7 @@ pub struct Env<'a, 'i> { pub target_info: TargetInfo, pub update_mode_ids: &'i mut UpdateModeIds, pub call_specialization_counter: u32, - pub abilities_store: &'i AbilitiesStore, + pub abilities_store: &'i mut AbilitiesStore, } impl<'a, 'i> Env<'a, 'i> { @@ -1655,8 +1777,7 @@ impl<'a> Expr<'a> { .. } => { let doc_tag = match tag_name { - TagName::Global(s) => alloc.text(s.as_str()), - TagName::Private(s) => symbol_to_doc(alloc, *s), + TagName::Tag(s) => alloc.text(s.as_str()), TagName::Closure(s) => alloc .text("ClosureTag(") .append(symbol_to_doc(alloc, *s)) @@ -1677,8 +1798,7 @@ impl<'a> Expr<'a> { .. } => { let doc_tag = match tag_name { - TagName::Global(s) => alloc.text(s.as_str()), - TagName::Private(s) => alloc.text(format!("{}", s)), + TagName::Tag(s) => alloc.text(s.as_str()), TagName::Closure(s) => alloc .text("ClosureTag(") .append(symbol_to_doc(alloc, *s)) @@ -1923,6 +2043,335 @@ impl<'a> Stmt<'a> { _ => false, } } + + pub fn if_then_else( + arena: &'a Bump, + condition_symbol: Symbol, + return_layout: Layout<'a>, + then_branch_stmt: Stmt<'a>, + else_branch_stmt: &'a Stmt<'a>, + ) -> Self { + let then_branch_info = BranchInfo::Constructor { + scrutinee: condition_symbol, + layout: Layout::bool(), + tag_id: 1, + }; + let then_branch = (1u64, then_branch_info, then_branch_stmt); + + let else_branch_info = BranchInfo::Constructor { + scrutinee: condition_symbol, + layout: Layout::bool(), + tag_id: 0, + }; + let else_branch = (else_branch_info, else_branch_stmt); + + Stmt::Switch { + cond_symbol: condition_symbol, + cond_layout: Layout::bool(), + branches: &*arena.alloc([then_branch]), + default_branch: else_branch, + ret_layout: return_layout, + } + } +} + +fn from_can_let<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + def: Box, + cont: Box>, + variable: Variable, + opt_assigned_and_hole: Option<(Symbol, &'a Stmt<'a>)>, +) -> Stmt<'a> { + use roc_can::expr::Expr::*; + + macro_rules! lower_rest { + ($variable:expr, $expr:expr) => { + lower_rest!(env, procs, layout_cache, $variable, $expr) + }; + ($env:expr, $procs:expr, $layout_cache:expr, $variable:expr, $expr:expr) => { + match opt_assigned_and_hole { + None => from_can($env, $variable, $expr, $procs, $layout_cache), + Some((assigned, hole)) => with_hole( + $env, + $expr, + $variable, + $procs, + $layout_cache, + assigned, + hole, + ), + } + }; + } + + if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { + return match def.loc_expr.value { + Closure(closure_data) => { + register_capturing_closure(env, procs, layout_cache, *symbol, closure_data); + + lower_rest!(variable, cont.value) + } + Accessor(accessor_data) => { + let fresh_record_symbol = env.unique_symbol(); + register_noncapturing_closure( + env, + procs, + *symbol, + accessor_data.to_closure_data(fresh_record_symbol), + ); + + lower_rest!(variable, cont.value) + } + Var(original) | AbilityMember(original, _) => { + // a variable is aliased, e.g. + // + // foo = bar + // + // or + // + // foo = RBTRee.empty + + // TODO: right now we need help out rustc with the closure types; + // it isn't able to infer the right lifetime bounds. See if we + // can remove the annotations in the future. + let build_rest = + |env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>| { + lower_rest!(env, procs, layout_cache, variable, cont.value) + }; + + return handle_variable_aliasing( + env, + procs, + layout_cache, + def.expr_var, + *symbol, + original, + build_rest, + ); + } + LetNonRec(nested_def, nested_cont) => { + use roc_can::expr::Expr::*; + // We must transform + // + // let answer = 1337 + // in + // let unused = + // let nested = 17 + // in + // nested + // in + // answer + // + // into + // + // let answer = 1337 + // in + // let nested = 17 + // in + // let unused = nested + // in + // answer + + let new_def = roc_can::def::Def { + loc_pattern: def.loc_pattern, + loc_expr: *nested_cont, + pattern_vars: def.pattern_vars, + annotation: def.annotation, + expr_var: def.expr_var, + }; + + let new_inner = LetNonRec(Box::new(new_def), cont); + + let new_outer = LetNonRec(nested_def, Box::new(Loc::at_zero(new_inner))); + + lower_rest!(variable, new_outer) + } + LetRec(nested_defs, nested_cont) => { + use roc_can::expr::Expr::*; + // We must transform + // + // let answer = 1337 + // in + // let unused = + // let nested = \{} -> nested {} + // in + // nested + // in + // answer + // + // into + // + // let answer = 1337 + // in + // let nested = \{} -> nested {} + // in + // let unused = nested + // in + // answer + + let new_def = roc_can::def::Def { + loc_pattern: def.loc_pattern, + loc_expr: *nested_cont, + pattern_vars: def.pattern_vars, + annotation: def.annotation, + expr_var: def.expr_var, + }; + + let new_inner = LetNonRec(Box::new(new_def), cont); + + let new_outer = LetRec(nested_defs, Box::new(Loc::at_zero(new_inner))); + + lower_rest!(variable, new_outer) + } + _ => { + let rest = lower_rest!(variable, cont.value); + + // Remove all the requested symbol specializations now, since this is the + // def site and hence we won't need them any higher up. + let mut needed_specializations = procs.symbol_specializations.remove(*symbol); + + match needed_specializations.len() { + 0 => { + // We don't need any specializations, that means this symbol is never + // referenced. + with_hole( + env, + def.loc_expr.value, + def.expr_var, + procs, + layout_cache, + *symbol, + env.arena.alloc(rest), + ) + } + + // We do need specializations + 1 => { + let (_specialization_mark, (var, specialized_symbol)) = + needed_specializations.next().unwrap(); + + // Unify the expr_var with the requested specialization once. + let _res = roc_unify::unify::unify(env.subs, var, def.expr_var, Mode::EQ); + + resolve_abilities_in_specialized_body( + env, + procs, + &def.loc_expr.value, + def.expr_var, + ); + + with_hole( + env, + def.loc_expr.value, + def.expr_var, + procs, + layout_cache, + specialized_symbol, + env.arena.alloc(rest), + ) + } + _n => { + let mut stmt = rest; + + // Need to eat the cost and create a specialized version of the body for + // each specialization. + for (_specialization_mark, (var, specialized_symbol)) in + needed_specializations + { + use crate::copy::deep_copy_type_vars_into_expr; + + let (new_def_expr_var, specialized_expr) = deep_copy_type_vars_into_expr( + env.arena, + env.subs, + def.expr_var, + &def.loc_expr.value, + ) + .expect( + "expr marked as having specializations, but it has no type variables!", + ); + + let _res = + roc_unify::unify::unify(env.subs, var, new_def_expr_var, Mode::EQ); + + resolve_abilities_in_specialized_body( + env, + procs, + &def.loc_expr.value, + def.expr_var, + ); + + stmt = with_hole( + env, + specialized_expr, + new_def_expr_var, + procs, + layout_cache, + specialized_symbol, + env.arena.alloc(stmt), + ); + } + + stmt + } + } + } + }; + } + + // this may be a destructure pattern + let (mono_pattern, assignments) = + match from_can_pattern(env, procs, layout_cache, &def.loc_pattern.value) { + Ok(v) => v, + Err(_) => todo!(), + }; + + // convert the continuation + let mut stmt = lower_rest!(variable, cont.value); + + // layer on any default record fields + for (symbol, variable, expr) in assignments { + let specialization_symbol = procs + .symbol_specializations + .remove_single(symbol) + // Can happen when the symbol was never used under this body, and hence has no + // requested specialization. + .unwrap_or(symbol); + + let hole = env.arena.alloc(stmt); + stmt = with_hole( + env, + expr, + variable, + procs, + layout_cache, + specialization_symbol, + hole, + ); + } + + if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value { + store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) + } else { + let outer_symbol = env.unique_symbol(); + stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt); + + resolve_abilities_in_specialized_body(env, procs, &def.loc_expr.value, def.expr_var); + + // convert the def body, store in outer_symbol + with_hole( + env, + def.loc_expr.value, + def.expr_var, + procs, + layout_cache, + outer_symbol, + env.arena.alloc(stmt), + ) + } } /// turn record/tag patterns into a when expression, e.g. @@ -1937,8 +2386,7 @@ impl<'a> Stmt<'a> { #[allow(clippy::type_complexity)] fn patterns_to_when<'a>( env: &mut Env<'a, '_>, - layout_cache: &mut LayoutCache<'a>, - patterns: std::vec::Vec<(Variable, Loc)>, + patterns: std::vec::Vec<(Variable, AnnotatedMark, Loc)>, body_var: Variable, body: Loc, ) -> Result<(Vec<'a, Variable>, Vec<'a, Symbol>, Loc), Loc> { @@ -1953,66 +2401,26 @@ fn patterns_to_when<'a>( // NOTE this fails if the pattern contains rigid variables, // see https://github.com/rtfeldman/roc/issues/786 // this must be fixed when moving exhaustiveness checking to the new canonical AST - for (pattern_var, pattern) in patterns.into_iter() { - let context = roc_exhaustive::Context::BadArg; - let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) { - Ok((pat, _assignments)) => { - // Don't apply any assignments (e.g. to initialize optional variables) yet. - // We'll take care of that later when expanding the new "when" branch. - pat - } - Err(runtime_error) => { - // Even if the body was Ok, replace it with this Err. - // If it was already an Err, leave it at that Err, so the first - // RuntimeError we encountered remains the first. - body = body.and({ - Err(Loc { - region: pattern.region, - value: runtime_error, - }) - }); + for (pattern_var, annotated_mark, pattern) in patterns.into_iter() { + if annotated_mark.exhaustive.is_non_exhaustive(env.subs) { + // Even if the body was Ok, replace it with this Err. + // If it was already an Err, leave it at that Err, so the first + // RuntimeError we encountered remains the first. + let value = RuntimeError::UnsupportedPattern(pattern.region); + body = body.and({ + Err(Loc { + region: pattern.region, + value, + }) + }); + } else if let Ok(unwrapped_body) = body { + let (new_symbol, new_body) = + pattern_to_when(env, pattern_var, pattern, body_var, unwrapped_body); - continue; - } - }; + symbols.push(new_symbol); + arg_vars.push(pattern_var); - match crate::exhaustive::check( - pattern.region, - &[( - Loc::at(pattern.region, mono_pattern), - roc_exhaustive::Guard::NoGuard, - )], - context, - ) { - Ok(_) => { - // Replace the body with a new one, but only if it was Ok. - if let Ok(unwrapped_body) = body { - let (new_symbol, new_body) = - pattern_to_when(env, pattern_var, pattern, body_var, unwrapped_body); - - symbols.push(new_symbol); - arg_vars.push(pattern_var); - - body = Ok(new_body) - } - } - Err(errors) => { - for error in errors { - env.problems.push(MonoProblem::PatternProblem(error)) - } - - let value = RuntimeError::UnsupportedPattern(pattern.region); - - // Even if the body was Ok, replace it with this Err. - // If it was already an Err, leave it at that Err, so the first - // RuntimeError we encountered remains the first. - body = body.and({ - Err(Loc { - region: pattern.region, - value, - }) - }); - } + body = Ok(new_body) } } @@ -2092,7 +2500,12 @@ fn pattern_to_when<'a>( patterns: vec![pattern], value: body, guard: None, + // If this type-checked, it's non-redundant + redundant: RedundantMark::known_non_redundant(), }], + branches_cond_var: pattern_var, + // If this type-checked, it's exhaustive + exhaustive: ExhaustiveMark::known_exhaustive(), }; (symbol, Loc::at_zero(wrapped_body)) @@ -2215,6 +2628,12 @@ pub fn specialize_all<'a>( specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host); + debug_assert!( + procs.symbol_specializations.is_empty(), + "{:?}", + &procs.symbol_specializations + ); + procs } @@ -2359,6 +2778,106 @@ fn generate_runtime_error_function<'a>( } } +fn resolve_abilities_in_specialized_body<'a>( + env: &mut Env<'a, '_>, + procs: &Procs<'a>, + specialized_body: &roc_can::expr::Expr, + body_var: Variable, +) -> std::vec::Vec { + use roc_can::expr::Expr; + use roc_can::traverse::{walk_expr, PatternVisitor, Visitor}; + use roc_unify::unify::unify; + + struct Resolver<'a> { + subs: &'a mut Subs, + procs: &'a Procs<'a>, + abilities_store: &'a mut AbilitiesStore, + seen_defs: MutSet, + specialized: std::vec::Vec, + } + impl PatternVisitor for Resolver<'_> {} + impl Visitor for Resolver<'_> { + fn visit_expr(&mut self, expr: &Expr, _region: Region, var: Variable) { + match expr { + Expr::Closure(..) => { + // Don't walk down closure bodies. They will have their types refined when they + // are themselves specialized, so we'll handle ability resolution in them at + // that time too. + } + Expr::LetRec(..) | Expr::LetNonRec(..) => { + // Also don't walk down let-bindings. These may be generalized and we won't + // know their specializations until we collect them while building up the def. + // So, we'll resolve any nested abilities when we know their specialized type + // during def construction. + } + Expr::AbilityMember(member_sym, specialization_id) => { + if self + .abilities_store + .get_resolved(*specialization_id) + .is_some() + { + // We already know the specialization from type solving; we are good to go. + return; + } + + let specialization = resolve_ability_specialization( + self.subs, + self.abilities_store, + *member_sym, + var, + ) + .expect("Ability specialization is unknown - code generation cannot proceed!"); + + // We must now refine the current type state to account for this specialization, + // since `var` may only have partial specialization information - enough to + // figure out what specialization we need, but not the types of all arguments + // and return types. So, unify with the variable with the specialization's type. + let specialization_def = self + .procs + .partial_procs + .get_symbol(specialization) + .expect("Specialization found, but it's not in procs"); + let specialization_var = specialization_def.annotation; + + let unified = unify(self.subs, var, specialization_var, Mode::EQ); + unified.expect_success( + "Specialization does not unify - this is a typechecker bug!", + ); + + // Now walk the specialization def to pick up any more needed types. Of course, + // we only want to pass through it once to avoid unbounded recursion. + if !self.seen_defs.contains(&specialization) { + self.visit_expr( + &specialization_def.body, + Region::zero(), + specialization_def.body_var, + ); + self.seen_defs.insert(specialization); + } + + self.abilities_store + .insert_resolved(*specialization_id, specialization); + + debug_assert!(!self.specialized.contains(specialization_id)); + self.specialized.push(*specialization_id); + } + _ => walk_expr(self, expr, var), + } + } + } + + let mut specializer = Resolver { + subs: env.subs, + procs, + abilities_store: env.abilities_store, + seen_defs: MutSet::default(), + specialized: vec![], + }; + specializer.visit_expr(specialized_body, Region::zero(), body_var); + + specializer.specialized +} + fn specialize_external<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, @@ -2494,8 +3013,17 @@ fn specialize_external<'a>( }; let body = partial_proc.body.clone(); + let resolved_ability_specializations = + resolve_abilities_in_specialized_body(env, procs, &body, partial_proc.body_var); + let mut specialized_body = from_can(env, partial_proc.body_var, body, procs, layout_cache); + // reset the resolved ability specializations so as not to interfere with other specializations + // of this proc. + resolved_ability_specializations + .into_iter() + .for_each(|sid| env.abilities_store.remove_resolved(sid)); + match specialized { SpecializedLayout::FunctionPointerBody { ret_layout, @@ -2544,6 +3072,16 @@ fn specialize_external<'a>( (Some(closure_layout), CapturedSymbols::Captured(captured)) => { // debug_assert!(!captured.is_empty()); + // An argument from the closure list may have taken on a specialized symbol + // name during the evaluation of the def body. If this is the case, load the + // specialized name rather than the original captured name! + let mut get_specialized_name = |symbol| { + procs + .symbol_specializations + .remove_single(symbol) + .unwrap_or(symbol) + }; + match closure_layout.layout_for_member(proc_name) { ClosureRepresentation::Union { alphabetic_order_fields: field_layouts, @@ -2578,8 +3116,10 @@ fn specialize_external<'a>( union_layout, }; + let symbol = get_specialized_name(**symbol); + specialized_body = Stmt::Let( - **symbol, + symbol, expr, **layout, env.arena.alloc(specialized_body), @@ -2619,8 +3159,10 @@ fn specialize_external<'a>( structure: Symbol::ARG_CLOSURE, }; + let symbol = get_specialized_name(**symbol); + specialized_body = Stmt::Let( - **symbol, + symbol, expr, **layout, env.arena.alloc(specialized_body), @@ -2659,6 +3201,19 @@ fn specialize_external<'a>( _ => unreachable!("to closure or not to closure?"), } + let proc_args: Vec<_> = proc_args + .iter() + .map(|&(layout, symbol)| { + // Grab the specialization symbol, if it exists. + let symbol = procs + .symbol_specializations + .remove_single(symbol) + .unwrap_or(symbol); + + (layout, symbol) + }) + .collect_in(env.arena); + // reset subs, so we don't get type errors when specializing for a different signature layout_cache.rollback_to(cache_snapshot); env.subs.rollback_to(snapshot); @@ -2670,7 +3225,7 @@ fn specialize_external<'a>( let proc = Proc { name: proc_name, - args: proc_args, + args: proc_args.into_bump_slice(), body: specialized_body, closure_data_layout, ret_layout, @@ -3257,159 +3812,21 @@ pub fn with_hole<'a>( } } } - LetNonRec(def, cont, _) => { - if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value { - if let Closure(closure_data) = def.loc_expr.value { - register_noncapturing_closure(env, procs, layout_cache, symbol, closure_data); - - return with_hole( - env, - cont.value, - variable, - procs, - layout_cache, - assigned, - hole, - ); - } - // special-case the form `let x = E in x` - // not doing so will drop the `hole` - match &cont.value { - roc_can::expr::Expr::Var(original) if *original == symbol => { - return with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - assigned, - hole, - ); - } - _ => {} - } - - let build_rest = - |env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>| { - with_hole( - env, - cont.value, - variable, - procs, - layout_cache, - assigned, - hole, - ) - }; - - // a variable is aliased - if let roc_can::expr::Expr::Var(original) = def.loc_expr.value { - // a variable is aliased, e.g. - // - // foo = bar - // - // or - // - // foo = RBTRee.empty - - handle_variable_aliasing( - env, - procs, - layout_cache, - def.expr_var, - symbol, - original, - build_rest, - ) - } else { - let rest = build_rest(env, procs, layout_cache); - with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - symbol, - env.arena.alloc(rest), - ) - } - } else { - // this may be a destructure pattern - let (mono_pattern, assignments) = - match from_can_pattern(env, layout_cache, &def.loc_pattern.value) { - Ok(v) => v, - Err(_runtime_error) => { - // todo - panic!(); - } - }; - - let context = roc_exhaustive::Context::BadDestruct; - match crate::exhaustive::check( - def.loc_pattern.region, - &[( - Loc::at(def.loc_pattern.region, mono_pattern.clone()), - roc_exhaustive::Guard::NoGuard, - )], - context, - ) { - Ok(_) => {} - Err(errors) => { - for error in errors { - env.problems.push(MonoProblem::PatternProblem(error)) - } - } // TODO make all variables bound in the pattern evaluate to a runtime error - // return Stmt::RuntimeError("TODO non-exhaustive pattern"); - } - - let mut hole = hole; - - for (symbol, variable, expr) in assignments { - let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole); - - hole = env.arena.alloc(stmt); - } - - // convert the continuation - let mut stmt = with_hole( - env, - cont.value, - variable, - procs, - layout_cache, - assigned, - hole, - ); - - let outer_symbol = env.unique_symbol(); - stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt); - - // convert the def body, store in outer_symbol - with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - outer_symbol, - env.arena.alloc(stmt), - ) - } - } - LetRec(defs, cont, _) => { + LetNonRec(def, cont) => from_can_let( + env, + procs, + layout_cache, + def, + cont, + variable, + Some((assigned, hole)), + ), + LetRec(defs, cont) => { // because Roc is strict, only functions can be recursive! for def in defs.into_iter() { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let Closure(closure_data) = def.loc_expr.value { - register_noncapturing_closure( - env, - procs, - layout_cache, - *symbol, - closure_data, - ); + register_noncapturing_closure(env, procs, *symbol, closure_data); continue; } @@ -3427,9 +3844,36 @@ pub fn with_hole<'a>( hole, ) } - Var(symbol) => { + Var(mut symbol) => { + // If this symbol is a raw value, find the real name we gave to its specialized usage. + if let ReuseSymbol::Value(_symbol) = + can_reuse_symbol(env, procs, &roc_can::expr::Expr::Var(symbol)) + { + let real_symbol = + procs + .symbol_specializations + .get_or_insert(env, layout_cache, symbol, variable); + symbol = real_symbol; + } + specialize_naked_symbol(env, variable, procs, layout_cache, assigned, hole, symbol) } + AbilityMember(_member, specialization_id) => { + let specialization_symbol = env + .abilities_store + .get_resolved(specialization_id) + .expect("Specialization was never made!"); + + specialize_naked_symbol( + env, + variable, + procs, + layout_cache, + assigned, + hole, + specialization_symbol, + ) + } Tag { variant_var, name: tag_name, @@ -3499,15 +3943,30 @@ pub fn with_hole<'a>( OpaqueRef { argument, .. } => { let (arg_var, loc_arg_expr) = *argument; - with_hole( - env, - loc_arg_expr.value, - arg_var, - procs, - layout_cache, - assigned, - hole, - ) + + match can_reuse_symbol(env, procs, &loc_arg_expr.value) { + // Opaques decay to their argument. + ReuseSymbol::Value(symbol) => { + let real_name = procs.symbol_specializations.get_or_insert( + env, + layout_cache, + symbol, + arg_var, + ); + let mut result = hole.clone(); + substitute_in_exprs(arena, &mut result, assigned, real_name); + result + } + _ => with_hole( + env, + loc_arg_expr.value, + arg_var, + procs, + layout_cache, + assigned, + hole, + ), + } } Record { @@ -3545,7 +4004,13 @@ pub fn with_hole<'a>( field_symbols.push(symbol); can_fields.push(Field::Function(symbol, variable)); } - Value(reusable) => { + Value(symbol) => { + let reusable = procs.symbol_specializations.get_or_insert( + env, + layout_cache, + symbol, + field.var, + ); field_symbols.push(reusable); can_fields.push(Field::ValueSymbol); } @@ -3688,8 +4153,13 @@ pub fn with_hole<'a>( ); for (loc_cond, loc_then) in branches.into_iter().rev() { - let branching_symbol = - possible_reuse_symbol(env, procs, &loc_cond.value); + let branching_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &loc_cond.value, + cond_var, + ); let then = with_hole( env, @@ -3743,11 +4213,19 @@ pub fn with_hole<'a>( When { cond_var, expr_var, - region, + region: _, loc_cond, branches, + branches_cond_var: _, + exhaustive, } => { - let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); + let cond_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &loc_cond.value, + cond_var, + ); let id = JoinPointId(env.unique_symbol()); @@ -3755,9 +4233,9 @@ pub fn with_hole<'a>( env, cond_var, expr_var, - region, cond_symbol, branches, + exhaustive, layout_cache, procs, Some(id), @@ -3836,7 +4314,13 @@ pub fn with_hole<'a>( if let Some(literal) = try_make_literal(env, &arg_expr.value) { elements.push(ListLiteralElement::Literal(literal)); } else { - let symbol = possible_reuse_symbol(env, procs, &arg_expr.value); + let symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &arg_expr.value, + elem_var, + ); elements.push(ListLiteralElement::Symbol(symbol)); arg_symbols.push(symbol); @@ -3909,7 +4393,13 @@ pub fn with_hole<'a>( } } - let record_symbol = possible_reuse_symbol(env, procs, &loc_expr.value); + let record_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &loc_expr.value, + record_var, + ); let mut stmt = match field_layouts.as_slice() { [_] => { @@ -3977,16 +4467,9 @@ pub fn with_hole<'a>( ); match raw_layout { - RawFunctionLayout::Function(_, lambda_set, _) => construct_closure_data( - env, - procs, - layout_cache, - lambda_set, - name, - &[], - assigned, - hole, - ), + RawFunctionLayout::Function(_, lambda_set, _) => { + construct_closure_data(env, lambda_set, name, &[], assigned, hole) + } RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(), } } @@ -4041,9 +4524,13 @@ pub fn with_hole<'a>( field_layouts.push(field_layout); if let Some(field) = updates.get(&label) { - // TODO - let field_symbol = - possible_reuse_symbol(env, procs, &field.loc_expr.value); + let field_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &field.loc_expr.value, + field.var, + ); fields.push(UpdateExisting(field)); symbols.push(field_symbol); @@ -4120,7 +4607,7 @@ pub fn with_hole<'a>( } CopyExisting(index) => { let record_needs_specialization = - procs.partial_exprs.contains(structure); + procs.ability_member_aliases.get(structure).is_some(); let specialized_structure_sym = if record_needs_specialization { // We need to specialize the record now; create a new one for it. // TODO: reuse this symbol for all updates @@ -4207,11 +4694,9 @@ pub fn with_hole<'a>( construct_closure_data( env, - procs, - layout_cache, lambda_set, name, - symbols, + symbols.iter().copied(), assigned, hole, ) @@ -4231,14 +4716,10 @@ pub fn with_hole<'a>( // a proc in this module, or an imported symbol procs.partial_procs.contains_key(key) || (env.is_imported_symbol(key) && !procs.is_imported_module_thunk(key)) - || env.abilities_store.is_ability_member_name(key) }; match loc_expr.value { roc_can::expr::Expr::Var(proc_name) if is_known(proc_name) => { - // This might be an ability member - if so, use the appropriate specialization. - let proc_name = get_specialization(env, fn_var, proc_name).unwrap_or(proc_name); - // a call by a known name call_by_name( env, @@ -4251,6 +4732,22 @@ pub fn with_hole<'a>( hole, ) } + roc_can::expr::Expr::AbilityMember(_, specialization_id) => { + let proc_name = env.abilities_store.get_resolved(specialization_id).expect( + "Ability specialization is unknown - code generation cannot proceed!", + ); + + call_by_name( + env, + procs, + fn_var, + proc_name, + loc_args, + layout_cache, + assigned, + hole, + ) + } _ => { // Call by pointer - the closure was anonymous, e.g. // @@ -4269,8 +4766,14 @@ pub fn with_hole<'a>( // (\f, x -> f x) let arg_symbols = Vec::from_iter_in( - loc_args.iter().map(|(_, arg_expr)| { - possible_reuse_symbol(env, procs, &arg_expr.value) + loc_args.iter().map(|(var, arg_expr)| { + possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &arg_expr.value, + *var, + ) }), arena, ) @@ -4328,89 +4831,57 @@ pub fn with_hole<'a>( } } } - Value(function_symbol) => match full_layout { - RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => { - let closure_data_symbol = function_symbol; + Value(function_symbol) => { + let function_symbol = procs.symbol_specializations.get_or_insert( + env, + layout_cache, + function_symbol, + fn_var, + ); - result = match_on_lambda_set( - env, - lambda_set, - closure_data_symbol, - arg_symbols, + match full_layout { + RawFunctionLayout::Function( arg_layouts, + lambda_set, ret_layout, - assigned, - hole, - ); - } - RawFunctionLayout::ZeroArgumentThunk(_) => { - unreachable!("calling a non-closure layout") - } - }, - UnspecializedExpr(symbol) => match procs.partial_exprs.get(symbol).unwrap() - { - &PolymorphicExpr::AbilityMember(member) => { - let proc_name = get_specialization(env, fn_var, member).expect("Recorded as an ability member, but it doesn't have a specialization"); + ) => { + let closure_data_symbol = function_symbol; - // a call by a known name - return call_by_name( - env, - procs, - fn_var, - proc_name, - loc_args, - layout_cache, - assigned, - hole, - ); - } - PolymorphicExpr::Expr(lambda_expr, lambda_expr_var) => { - match full_layout { - RawFunctionLayout::Function( - arg_layouts, + result = match_on_lambda_set( + env, lambda_set, + closure_data_symbol, + arg_symbols, + arg_layouts, ret_layout, - ) => { - let closure_data_symbol = env.unique_symbol(); - - result = match_on_lambda_set( - env, - lambda_set, - closure_data_symbol, - arg_symbols, - arg_layouts, - ret_layout, - assigned, - hole, - ); - - let snapshot = env.subs.snapshot(); - let cache_snapshot = layout_cache.snapshot(); - let _unified = roc_unify::unify::unify( - env.subs, - fn_var, - *lambda_expr_var, - roc_unify::unify::Mode::EQ, - ); - - result = with_hole( - env, - lambda_expr.clone(), - fn_var, - procs, - layout_cache, - closure_data_symbol, - env.arena.alloc(result), - ); - env.subs.rollback_to(snapshot); - layout_cache.rollback_to(cache_snapshot); - } - RawFunctionLayout::ZeroArgumentThunk(_) => { - unreachable!("calling a non-closure layout") - } + assigned, + hole, + ); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("calling a non-closure layout") } } - }, + } + UnspecializedExpr(symbol) => { + match procs.ability_member_aliases.get(symbol).unwrap() { + &self::AbilityMember(member) => { + let proc_name = resolve_ability_specialization(env.subs, env.abilities_store, member, fn_var).expect("Recorded as an ability member, but it doesn't have a specialization"); + + // a call by a known name + return call_by_name( + env, + procs, + fn_var, + proc_name, + loc_args, + layout_cache, + assigned, + hole, + ); + } + } + } NotASymbol => { // the expression is not a symbol. That means it's an expression // evaluating to a function value. @@ -4466,8 +4937,14 @@ pub fn with_hole<'a>( } => { let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena); - for (_, arg_expr) in args.iter() { - arg_symbols.push(possible_reuse_symbol(env, procs, arg_expr)); + for (var, arg_expr) in args.iter() { + arg_symbols.push(possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + arg_expr, + *var, + )); } let arg_symbols = arg_symbols.into_bump_slice(); @@ -4496,8 +4973,14 @@ pub fn with_hole<'a>( RunLowLevel { op, args, ret_var } => { let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena); - for (_, arg_expr) in args.iter() { - arg_symbols.push(possible_reuse_symbol(env, procs, arg_expr)); + for (var, arg_expr) in args.iter() { + arg_symbols.push(possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + arg_expr, + *var, + )); } let arg_symbols = arg_symbols.into_bump_slice(); @@ -4743,59 +5226,24 @@ pub fn with_hole<'a>( } } -#[inline(always)] -fn get_specialization<'a>( - env: &mut Env<'a, '_>, - symbol_var: Variable, - symbol: Symbol, -) -> Option { - use roc_solve::ability::type_implementing_member; - use roc_solve::solve::instantiate_rigids; - use roc_unify::unify::unify; - - match env.abilities_store.member_def(symbol) { - None => { - // This is not an ability member, it doesn't need specialization. - None - } - Some(member) => { - let snapshot = env.subs.snapshot(); - instantiate_rigids(env.subs, member.signature_var); - let (_, must_implement_ability) = unify( - env.subs, - symbol_var, - member.signature_var, - roc_unify::unify::Mode::EQ, - ) - .expect_success("This typechecked previously"); - env.subs.rollback_to(snapshot); - let specializing_type = - type_implementing_member(&must_implement_ability, member.parent_ability); - - let specialization = env - .abilities_store - .get_specialization(symbol, specializing_type) - .expect("No specialization is recorded - I thought there would only be a type error here."); - - Some(specialization.symbol) - } - } -} - #[allow(clippy::too_many_arguments)] -fn construct_closure_data<'a>( +fn construct_closure_data<'a, I>( env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, + // procs: &mut Procs<'a>, lambda_set: LambdaSet<'a>, name: Symbol, - symbols: &'a [&(Symbol, Variable)], + symbols: I, assigned: Symbol, hole: &'a Stmt<'a>, -) -> Stmt<'a> { +) -> Stmt<'a> +where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, +{ let lambda_set_layout = Layout::LambdaSet(lambda_set); + let symbols = symbols.into_iter(); - let mut result = match lambda_set.layout_for_member(name) { + let result = match lambda_set.layout_for_member(name) { ClosureRepresentation::Union { tag_id, alphabetic_order_fields: field_layouts, @@ -4804,10 +5252,10 @@ fn construct_closure_data<'a>( } => { // captured variables are in symbol-alphabetic order, but now we want // them ordered by their alignment requirements - let mut combined = Vec::from_iter_in( - symbols.iter().map(|&&(s, _)| s).zip(field_layouts.iter()), - env.arena, - ); + let mut combined = Vec::with_capacity_in(symbols.len(), env.arena); + for ((symbol, _variable), layout) in symbols.zip(field_layouts.iter()) { + combined.push((*symbol, layout)) + } let ptr_bytes = env.target_info; @@ -4835,10 +5283,10 @@ fn construct_closure_data<'a>( // captured variables are in symbol-alphabetic order, but now we want // them ordered by their alignment requirements - let mut combined = Vec::from_iter_in( - symbols.iter().map(|&(s, _)| s).zip(field_layouts.iter()), - env.arena, - ); + let mut combined = Vec::with_capacity_in(symbols.len(), env.arena); + for ((symbol, _variable), layout) in symbols.zip(field_layouts.iter()) { + combined.push((*symbol, layout)) + } let ptr_bytes = env.target_info; @@ -4850,7 +5298,7 @@ fn construct_closure_data<'a>( }); let symbols = - Vec::from_iter_in(combined.iter().map(|(a, _)| **a), env.arena).into_bump_slice(); + Vec::from_iter_in(combined.iter().map(|(a, _)| *a), env.arena).into_bump_slice(); let field_layouts = Vec::from_iter_in(combined.iter().map(|(_, b)| **b), env.arena).into_bump_slice(); @@ -4884,19 +5332,6 @@ fn construct_closure_data<'a>( _ => unreachable!(), }; - // Some of the captured symbols may be references to polymorphic expressions, - // which have not been specialized yet. We need to perform those - // specializations now so that there are real symbols to capture. - // - // TODO: this is not quite right. What we should actually be doing is removing references to - // polymorphic expressions from the captured symbols, and allowing the specializations of those - // symbols to be inlined when specializing the closure body elsewhere. - for &&(symbol, var) in symbols { - if procs.partial_exprs.contains(symbol) { - result = specialize_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol); - } - } - result } @@ -5167,7 +5602,7 @@ fn tag_union_to_function<'a>( let loc_expr = Loc::at_zero(roc_can::expr::Expr::Var(arg_symbol)); - loc_pattern_args.push((arg_var, loc_pattern)); + loc_pattern_args.push((arg_var, AnnotatedMark::known_exhaustive(), loc_pattern)); loc_expr_args.push((arg_var, loc_expr)); } @@ -5198,16 +5633,9 @@ fn tag_union_to_function<'a>( ); match raw_layout { - RawFunctionLayout::Function(_, lambda_set, _) => construct_closure_data( - env, - procs, - layout_cache, - lambda_set, - proc_symbol, - &[], - assigned, - hole, - ), + RawFunctionLayout::Function(_, lambda_set, _) => { + construct_closure_data(env, lambda_set, proc_symbol, &[], assigned, hole) + } RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!(), } } @@ -5257,7 +5685,7 @@ fn sorted_field_symbols<'a>( let alignment = layout.alignment_bytes(env.target_info); - let symbol = possible_reuse_symbol(env, procs, &arg.value); + let symbol = possible_reuse_symbol_or_specialize(env, procs, layout_cache, &arg.value, var); field_symbols_temp.push((alignment, symbol, ((var, arg), &*env.arena.alloc(symbol)))); } field_symbols_temp.sort_by(|a, b| b.0.cmp(&a.0)); @@ -5269,7 +5697,6 @@ fn sorted_field_symbols<'a>( fn register_noncapturing_closure<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, closure_name: Symbol, closure_data: ClosureData, ) { @@ -5297,7 +5724,6 @@ fn register_noncapturing_closure<'a>( let partial_proc = PartialProc::from_named_function( env, - layout_cache, function_type, arguments, loc_body, @@ -5381,7 +5807,6 @@ fn register_capturing_closure<'a>( let partial_proc = PartialProc::from_named_function( env, - layout_cache, function_type, arguments, loc_body, @@ -5394,32 +5819,6 @@ fn register_capturing_closure<'a>( } } -fn is_literal_like(expr: &roc_can::expr::Expr) -> bool { - use roc_can::expr::Expr::*; - matches!( - expr, - Num(..) - | Int(..) - | Float(..) - | List { .. } - | Str(_) - | ZeroArgumentTag { .. } - | Tag { .. } - | Record { .. } - | Call(..) - ) -} - -fn expr_is_polymorphic<'a>( - env: &mut Env<'a, '_>, - expr: &roc_can::expr::Expr, - expr_var: Variable, -) -> bool { - // TODO: I don't think we need the `is_literal_like` check, but taking it slow for now... - let is_flex_or_rigid = |c: &Content| matches!(c, Content::FlexVar(_) | Content::RigidVar(_)); - is_literal_like(expr) && env.subs.var_contains_content(expr_var, is_flex_or_rigid) -} - pub fn from_can<'a>( env: &mut Env<'a, '_>, variable: Variable, @@ -5433,19 +5832,27 @@ pub fn from_can<'a>( When { cond_var, expr_var, - region, + region: _, loc_cond, branches, + branches_cond_var: _, + exhaustive, } => { - let cond_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); + let cond_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &loc_cond.value, + cond_var, + ); let stmt = from_can_when( env, cond_var, expr_var, - region, cond_symbol, branches, + exhaustive, layout_cache, procs, None, @@ -5478,7 +5885,13 @@ pub fn from_can<'a>( let mut stmt = from_can(env, branch_var, final_else.value, procs, layout_cache); for (loc_cond, loc_then) in branches.into_iter().rev() { - let branching_symbol = possible_reuse_symbol(env, procs, &loc_cond.value); + let branching_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &loc_cond.value, + cond_var, + ); let then = from_can(env, branch_var, loc_then.value, procs, layout_cache); stmt = cond(env, branching_symbol, cond_layout, then, stmt, ret_layout); @@ -5532,7 +5945,7 @@ pub fn from_can<'a>( ) } - LetRec(defs, cont, _) => { + LetRec(defs, cont) => { // because Roc is strict, only functions can be recursive! for def in defs.into_iter() { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { @@ -5558,250 +5971,7 @@ pub fn from_can<'a>( from_can(env, variable, cont.value, procs, layout_cache) } - LetNonRec(def, cont, outer_annotation) => { - if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { - match def.loc_expr.value { - roc_can::expr::Expr::Closure(closure_data) => { - register_capturing_closure(env, procs, layout_cache, *symbol, closure_data); - - return from_can(env, variable, cont.value, procs, layout_cache); - } - roc_can::expr::Expr::Accessor(accessor_data) => { - let fresh_record_symbol = env.unique_symbol(); - register_noncapturing_closure( - env, - procs, - layout_cache, - *symbol, - accessor_data.to_closure_data(fresh_record_symbol), - ); - - return from_can(env, variable, cont.value, procs, layout_cache); - } - roc_can::expr::Expr::Var(original) => { - // a variable is aliased, e.g. - // - // foo = bar - // - // or - // - // foo = RBTRee.empty - - // TODO: right now we need help out rustc with the closure types; - // it isn't able to infer the right lifetime bounds. See if we - // can remove the annotations in the future. - let build_rest = - |env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>| { - from_can(env, def.expr_var, cont.value, procs, layout_cache) - }; - - return handle_variable_aliasing( - env, - procs, - layout_cache, - def.expr_var, - *symbol, - original, - build_rest, - ); - } - roc_can::expr::Expr::LetNonRec(nested_def, nested_cont, nested_annotation) => { - use roc_can::expr::Expr::*; - // We must transform - // - // let answer = 1337 - // in - // let unused = - // let nested = 17 - // in - // nested - // in - // answer - // - // into - // - // let answer = 1337 - // in - // let nested = 17 - // in - // let unused = nested - // in - // answer - - let new_def = roc_can::def::Def { - loc_pattern: def.loc_pattern, - loc_expr: *nested_cont, - pattern_vars: def.pattern_vars, - annotation: def.annotation, - expr_var: def.expr_var, - }; - - let new_inner = LetNonRec(Box::new(new_def), cont, outer_annotation); - - let new_outer = LetNonRec( - nested_def, - Box::new(Loc::at_zero(new_inner)), - nested_annotation, - ); - - return from_can(env, variable, new_outer, procs, layout_cache); - } - roc_can::expr::Expr::LetRec(nested_defs, nested_cont, nested_annotation) => { - use roc_can::expr::Expr::*; - // We must transform - // - // let answer = 1337 - // in - // let unused = - // let nested = \{} -> nested {} - // in - // nested - // in - // answer - // - // into - // - // let answer = 1337 - // in - // let nested = \{} -> nested {} - // in - // let unused = nested - // in - // answer - - let new_def = roc_can::def::Def { - loc_pattern: def.loc_pattern, - loc_expr: *nested_cont, - pattern_vars: def.pattern_vars, - annotation: def.annotation, - expr_var: def.expr_var, - }; - - let new_inner = LetNonRec(Box::new(new_def), cont, outer_annotation); - - let new_outer = LetRec( - nested_defs, - Box::new(Loc::at_zero(new_inner)), - nested_annotation, - ); - - return from_can(env, variable, new_outer, procs, layout_cache); - } - ref body if expr_is_polymorphic(env, body, def.expr_var) => { - // This is a pattern like - // - // n = 1 - // asU8 n - // - // At the definition site `n = 1` we only know `1` to have the type `[Int *]`, - // which won't be refined until the call `asU8 n`. Add it as a partial expression - // that will be specialized at each concrete usage site. - procs.partial_exprs.insert( - *symbol, - PolymorphicExpr::Expr(def.loc_expr.value, def.expr_var), - ); - - let result = from_can(env, variable, cont.value, procs, layout_cache); - - // We won't see this symbol again. - procs.partial_exprs.remove(*symbol); - - return result; - } - _ => { - let rest = from_can(env, variable, cont.value, procs, layout_cache); - return with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - *symbol, - env.arena.alloc(rest), - ); - } - } - } - - // this may be a destructure pattern - let (mono_pattern, assignments) = - match from_can_pattern(env, layout_cache, &def.loc_pattern.value) { - Ok(v) => v, - Err(_) => todo!(), - }; - - if let Pattern::Identifier(symbol) = mono_pattern { - let mut hole = - env.arena - .alloc(from_can(env, variable, cont.value, procs, layout_cache)); - - for (symbol, variable, expr) in assignments { - let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole); - - hole = env.arena.alloc(stmt); - } - - with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - symbol, - hole, - ) - } else { - let context = roc_exhaustive::Context::BadDestruct; - match crate::exhaustive::check( - def.loc_pattern.region, - &[( - Loc::at(def.loc_pattern.region, mono_pattern.clone()), - roc_exhaustive::Guard::NoGuard, - )], - context, - ) { - Ok(_) => {} - Err(errors) => { - for error in errors { - env.problems.push(MonoProblem::PatternProblem(error)) - } - - return Stmt::RuntimeError("TODO non-exhaustive pattern"); - } - } - - // convert the continuation - let mut stmt = from_can(env, variable, cont.value, procs, layout_cache); - - // layer on any default record fields - for (symbol, variable, expr) in assignments { - let hole = env.arena.alloc(stmt); - stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole); - } - - if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value { - store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) - } else { - let outer_symbol = env.unique_symbol(); - stmt = - store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt); - - // convert the def body, store in outer_symbol - with_hole( - env, - def.loc_expr.value, - def.expr_var, - procs, - layout_cache, - outer_symbol, - env.arena.alloc(stmt), - ) - } - } - } - + LetNonRec(def, cont) => from_can_let(env, procs, layout_cache, def, cont, variable, None), _ => { let symbol = env.unique_symbol(); let hole = env.arena.alloc(Stmt::Ret(symbol)); @@ -5812,8 +5982,9 @@ pub fn from_can<'a>( fn to_opt_branches<'a>( env: &mut Env<'a, '_>, - region: Region, + procs: &mut Procs<'a>, branches: std::vec::Vec, + exhaustive_mark: ExhaustiveMark, layout_cache: &mut LayoutCache<'a>, ) -> std::vec::Vec<( Pattern<'a>, @@ -5832,12 +6003,17 @@ fn to_opt_branches<'a>( Guard::NoGuard }; + if when_branch.redundant.is_redundant(env.subs) { + // Don't codegen this branch since it's redundant. + continue; + } + for loc_pattern in when_branch.patterns { - match from_can_pattern(env, layout_cache, &loc_pattern.value) { + match from_can_pattern(env, procs, layout_cache, &loc_pattern.value) { Ok((mono_pattern, assignments)) => { loc_branches.push(( Loc::at(loc_pattern.region, mono_pattern.clone()), - exhaustive_guard.clone(), + exhaustive_guard, )); let mut loc_expr = when_branch.value.clone(); @@ -5853,11 +6029,8 @@ fn to_opt_branches<'a>( ), pattern_vars: std::iter::once((symbol, variable)).collect(), }; - let new_expr = roc_can::expr::Expr::LetNonRec( - Box::new(def), - Box::new(loc_expr), - variable, - ); + let new_expr = + roc_can::expr::Expr::LetNonRec(Box::new(def), Box::new(loc_expr)); loc_expr = Loc::at(region, new_expr); } @@ -5867,7 +6040,7 @@ fn to_opt_branches<'a>( Err(runtime_error) => { loc_branches.push(( Loc::at(loc_pattern.region, Pattern::Underscore), - exhaustive_guard.clone(), + exhaustive_guard, )); // TODO remove clone? @@ -5881,45 +6054,14 @@ fn to_opt_branches<'a>( } } - // NOTE exhaustiveness is checked after the construction of all the branches - // In contrast to elm (currently), we still do codegen even if a pattern is non-exhaustive. - // So we not only report exhaustiveness errors, but also correct them - let context = roc_exhaustive::Context::BadCase; - match crate::exhaustive::check(region, &loc_branches, context) { - Ok(_) => {} - Err(errors) => { - use roc_exhaustive::Error::*; - let mut is_not_exhaustive = false; - let mut overlapping_branches = std::vec::Vec::new(); - - for error in errors { - match &error { - Incomplete(_, _, _) => { - is_not_exhaustive = true; - } - Redundant { index, .. } => { - overlapping_branches.push(index.to_zero_based()); - } - } - env.problems.push(MonoProblem::PatternProblem(error)) - } - - overlapping_branches.sort_unstable(); - - for i in overlapping_branches.into_iter().rev() { - opt_branches.remove(i); - } - - if is_not_exhaustive { - opt_branches.push(( - Pattern::Underscore, - None, - roc_can::expr::Expr::RuntimeError( - roc_problem::can::RuntimeError::NonExhaustivePattern, - ), - )); - } - } + if exhaustive_mark.is_non_exhaustive(env.subs) { + // In contrast to elm (currently), we still do codegen even if a pattern is non-exhaustive. + // So we not only report exhaustiveness errors, but also correct them + opt_branches.push(( + Pattern::Underscore, + None, + roc_can::expr::Expr::RuntimeError(roc_problem::can::RuntimeError::NonExhaustivePattern), + )); } opt_branches @@ -5930,9 +6072,9 @@ fn from_can_when<'a>( env: &mut Env<'a, '_>, cond_var: Variable, expr_var: Variable, - region: Region, cond_symbol: Symbol, branches: std::vec::Vec, + exhaustive_mark: ExhaustiveMark, layout_cache: &mut LayoutCache<'a>, procs: &mut Procs<'a>, join_point: Option, @@ -5942,7 +6084,7 @@ fn from_can_when<'a>( // We can't know what to return! return Stmt::RuntimeError("Hit a 0-branch when expression"); } - let opt_branches = to_opt_branches(env, region, branches, layout_cache); + let opt_branches = to_opt_branches(env, procs, branches, exhaustive_mark, layout_cache); let cond_layout = return_on_layout_error!(env, layout_cache.from_var(env.arena, cond_var, env.subs)); @@ -6407,21 +6549,16 @@ fn store_pattern_help<'a>( match can_pat { Identifier(symbol) => { - if let Some(&PolymorphicExpr::Expr(_, var)) = procs.partial_exprs.get(outer_symbol) { - // It might be the case that symbol we're storing hasn't been reified to a value - // yet, if it's polymorphic. Do that now. - stmt = specialize_symbol( - env, - procs, - layout_cache, - Some(var), - *symbol, - stmt, - outer_symbol, - ); - } + // An identifier in a pattern can define at most one specialization! + // Remove any requested specializations for this name now, since this is the definition site. + let specialization_symbol = procs + .symbol_specializations + .remove_single(*symbol) + // Can happen when the symbol was never used under this body, and hence has no + // requested specialization. + .unwrap_or(*symbol); - substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol); + substitute_in_exprs(env.arena, &mut stmt, specialization_symbol, outer_symbol); } Underscore => { // do nothing @@ -6437,8 +6574,8 @@ fn store_pattern_help<'a>( return StorePattern::NotProductive(stmt); } NewtypeDestructure { arguments, .. } => match arguments.as_slice() { - [single] => { - return store_pattern_help(env, procs, layout_cache, &single.0, outer_symbol, stmt); + [(pattern, _layout)] => { + return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt); } _ => { let mut fields = Vec::with_capacity_in(arguments.len(), env.arena); @@ -6475,14 +6612,27 @@ fn store_pattern_help<'a>( ); } OpaqueUnwrap { argument, .. } => { - return store_pattern_help(env, procs, layout_cache, &argument.0, outer_symbol, stmt); + let (pattern, _layout) = &**argument; + return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt); } RecordDestructure(destructs, [_single_field]) => { for destruct in destructs { match &destruct.typ { DestructType::Required(symbol) => { - substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol); + let specialization_symbol = procs + .symbol_specializations + .remove_single(*symbol) + // Can happen when the symbol was never used under this body, and hence has no + // requested specialization. + .unwrap_or(*symbol); + + substitute_in_exprs( + env.arena, + &mut stmt, + specialization_symbol, + outer_symbol, + ); } DestructType::Guard(guard_pattern) => { return store_pattern_help( @@ -6560,8 +6710,14 @@ fn store_tag_pattern<'a>( match argument { Identifier(symbol) => { + // Pattern can define only one specialization + let symbol = procs + .symbol_specializations + .remove_single(*symbol) + .unwrap_or(*symbol); + // store immediately in the given symbol - stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt)); + stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt)); is_productive = true; } Underscore => { @@ -6636,8 +6792,20 @@ fn store_newtype_pattern<'a>( match argument { Identifier(symbol) => { - // store immediately in the given symbol - stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt)); + // store immediately in the given symbol, removing it specialization if it had any + let specialization_symbol = procs + .symbol_specializations + .remove_single(*symbol) + // Can happen when the symbol was never used under this body, and hence has no + // requested specialization. + .unwrap_or(*symbol); + + stmt = Stmt::Let( + specialization_symbol, + load, + arg_layout, + env.arena.alloc(stmt), + ); is_productive = true; } Underscore => { @@ -6699,11 +6867,37 @@ fn store_record_destruct<'a>( match &destruct.typ { DestructType::Required(symbol) => { - stmt = Stmt::Let(*symbol, load, destruct.layout, env.arena.alloc(stmt)); + // A destructure can define at most one specialization! + // Remove any requested specializations for this name now, since this is the definition site. + let specialization_symbol = procs + .symbol_specializations + .remove_single(*symbol) + // Can happen when the symbol was never used under this body, and hence has no + // requested specialization. + .unwrap_or(*symbol); + + stmt = Stmt::Let( + specialization_symbol, + load, + destruct.layout, + env.arena.alloc(stmt), + ); } DestructType::Guard(guard_pattern) => match &guard_pattern { Identifier(symbol) => { - stmt = Stmt::Let(*symbol, load, destruct.layout, env.arena.alloc(stmt)); + let specialization_symbol = procs + .symbol_specializations + .remove_single(*symbol) + // Can happen when the symbol was never used under this body, and hence has no + // requested specialization. + .unwrap_or(*symbol); + + stmt = Stmt::Let( + specialization_symbol, + load, + destruct.layout, + env.arena.alloc(stmt), + ); } Underscore => { // important that this is special-cased to do nothing: mono record patterns will extract all the @@ -6763,44 +6957,54 @@ fn can_reuse_symbol<'a>( procs: &Procs<'a>, expr: &roc_can::expr::Expr, ) -> ReuseSymbol { + use roc_can::expr::Expr::*; use ReuseSymbol::*; - if let roc_can::expr::Expr::Var(symbol) = expr { - let symbol = *symbol; + let symbol = match expr { + AbilityMember(_, specialization_id) => env + .abilities_store + .get_resolved(*specialization_id) + .expect("Specialization must be known!"), + Var(symbol) => *symbol, + _ => return NotASymbol, + }; - let arguments = [ - Symbol::ARG_1, - Symbol::ARG_2, - Symbol::ARG_3, - Symbol::ARG_4, - Symbol::ARG_5, - Symbol::ARG_6, - Symbol::ARG_7, - ]; + let arguments = [ + Symbol::ARG_1, + Symbol::ARG_2, + Symbol::ARG_3, + Symbol::ARG_4, + Symbol::ARG_5, + Symbol::ARG_6, + Symbol::ARG_7, + ]; - if arguments.contains(&symbol) { - Value(symbol) - } else if env.is_imported_symbol(symbol) { - Imported(symbol) - } else if procs.partial_procs.contains_key(symbol) { - LocalFunction(symbol) - } else if procs.partial_exprs.contains(symbol) { - UnspecializedExpr(symbol) - } else { - Value(symbol) - } + if arguments.contains(&symbol) { + Value(symbol) + } else if env.is_imported_symbol(symbol) { + Imported(symbol) + } else if procs.partial_procs.contains_key(symbol) { + LocalFunction(symbol) + } else if procs.ability_member_aliases.get(symbol).is_some() { + UnspecializedExpr(symbol) } else { - NotASymbol + Value(symbol) } } -fn possible_reuse_symbol<'a>( +fn possible_reuse_symbol_or_specialize<'a>( env: &mut Env<'a, '_>, - procs: &Procs<'a>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, expr: &roc_can::expr::Expr, + var: Variable, ) -> Symbol { match can_reuse_symbol(env, procs, expr) { - ReuseSymbol::Value(s) => s, + ReuseSymbol::Value(symbol) => { + procs + .symbol_specializations + .get_or_insert(env, layout_cache, symbol, var) + } _ => env.unique_symbol(), } } @@ -6817,52 +7021,93 @@ fn handle_variable_aliasing<'a, BuildRest>( where BuildRest: FnOnce(&mut Env<'a, '_>, &mut Procs<'a>, &mut LayoutCache<'a>) -> Stmt<'a>, { - if env.abilities_store.is_ability_member_name(right) { - procs - .partial_exprs - .insert(left, PolymorphicExpr::AbilityMember(right)); + // 1. Handle references to ability members - we could be aliasing an ability member, or another + // alias to an ability member. + { + if env.abilities_store.is_ability_member_name(right) { + procs + .ability_member_aliases + .insert(left, AbilityMember(right)); + return build_rest(env, procs, layout_cache); + } + if let Some(&ability_member) = procs.ability_member_aliases.get(right) { + procs.ability_member_aliases.insert(left, ability_member); + return build_rest(env, procs, layout_cache); + } + } + + // 2. Handle references to a known proc - again, we may be either aliasing the proc, or another + // alias to a proc. + if procs.partial_procs.contains_key(right) { + // This is an alias to a function defined in this module. + // Attach the alias, then build the rest of the module, so that we reference and specialize + // the correct proc. + procs.partial_procs.insert_alias(left, right); return build_rest(env, procs, layout_cache); } - if procs.partial_exprs.contains(right) { - // If `right` links to a partial expression, make sure we link `left` to it as well, so - // that usages of it will be specialized when building the rest of the program. - procs.partial_exprs.insert_alias(left, right); - return build_rest(env, procs, layout_cache); - } + // Otherwise we're dealing with an alias whose usages will tell us what specializations we + // need. So let's figure those out first. + let result = build_rest(env, procs, layout_cache); + + // The specializations we wanted of the symbol on the LHS of this alias. + let needed_specializations_of_left = procs.symbol_specializations.remove(left); - // Otherwise we're dealing with an alias to something that doesn't need to be specialized, or - // whose usages will already be specialized in the rest of the program. if procs.is_imported_module_thunk(right) { - let result = build_rest(env, procs, layout_cache); - // if this is an imported symbol, then we must make sure it is // specialized, and wrap the original in a function pointer. - add_needed_external(procs, env, variable, right); + let mut result = result; + for (_, (variable, left)) in needed_specializations_of_left { + add_needed_external(procs, env, variable, right); - let res_layout = layout_cache.from_var(env.arena, variable, env.subs); - let layout = return_on_layout_error!(env, res_layout); + let res_layout = layout_cache.from_var(env.arena, variable, env.subs); + let layout = return_on_layout_error!(env, res_layout); - force_thunk(env, right, layout, left, env.arena.alloc(result)) + result = force_thunk(env, right, layout, left, env.arena.alloc(result)); + } + result } else if env.is_imported_symbol(right) { - let result = build_rest(env, procs, layout_cache); - // if this is an imported symbol, then we must make sure it is // specialized, and wrap the original in a function pointer. add_needed_external(procs, env, variable, right); // then we must construct its closure; since imported symbols have no closure, we use the empty struct let_empty_struct(left, env.arena.alloc(result)) - } else if procs.partial_procs.contains_key(right) { - // This is an alias to a function defined in this module. - // Attach the alias, then build the rest of the module, so that we reference and specialize - // the correct proc. - procs.partial_procs.insert_alias(left, right); - build_rest(env, procs, layout_cache) } else { - // This should be a fully specialized value. Replace the alias with the original symbol. - let mut result = build_rest(env, procs, layout_cache); - substitute_in_exprs(env.arena, &mut result, left, right); + // Otherwise, we are referencing a non-proc value. + + // We need to lift all specializations of "left" to be specializations of "right". + let mut scratchpad_update_specializations = std::vec::Vec::new(); + + let left_had_specialization_symbols = needed_specializations_of_left.len() > 0; + + for (specialization_mark, (specialized_var, specialized_sym)) in + needed_specializations_of_left + { + let old_specialized_sym = procs.symbol_specializations.get_or_insert_known( + right, + specialization_mark, + specialized_var, + specialized_sym, + ); + + if let Some((_, old_specialized_sym)) = old_specialized_sym { + scratchpad_update_specializations.push((old_specialized_sym, specialized_sym)); + } + } + + let mut result = result; + if left_had_specialization_symbols { + // If the symbol is specialized, only the specializations need to be updated. + for (old_specialized_sym, specialized_sym) in + scratchpad_update_specializations.into_iter() + { + substitute_in_exprs(env.arena, &mut result, old_specialized_sym, specialized_sym); + } + } else { + substitute_in_exprs(env.arena, &mut result, left, right); + } + result } } @@ -6901,35 +7146,6 @@ fn specialize_symbol<'a>( result: Stmt<'a>, original: Symbol, ) -> Stmt<'a> { - if let Some(PolymorphicExpr::Expr(expr, expr_var)) = procs.partial_exprs.get(original) { - // Specialize the expression type now, based off the `arg_var` we've been given. - // TODO: cache the specialized result - let snapshot = env.subs.snapshot(); - let cache_snapshot = layout_cache.snapshot(); - let _unified = roc_unify::unify::unify( - env.subs, - arg_var.unwrap(), - *expr_var, - roc_unify::unify::Mode::EQ, - ); - - let result = with_hole( - env, - expr.clone(), - *expr_var, - procs, - layout_cache, - symbol, - env.arena.alloc(result), - ); - - // Restore the prior state so as not to interfere with future specializations. - env.subs.rollback_to(snapshot); - layout_cache.rollback_to(cache_snapshot); - - return result; - } - match procs.get_partial_proc(original) { None => { match arg_var { @@ -7029,11 +7245,9 @@ fn specialize_symbol<'a>( construct_closure_data( env, - procs, - layout_cache, lambda_set, original, - symbols, + symbols.iter().copied(), closure_data, env.arena.alloc(result), ) @@ -7067,8 +7281,6 @@ fn specialize_symbol<'a>( // unification may still cause it to have an extra argument construct_closure_data( env, - procs, - layout_cache, lambda_set, original, &[], @@ -7112,10 +7324,7 @@ fn assign_to_symbol<'a>( original, ) } - Value(_) => { - // symbol is already defined; nothing else to do here - result - } + Value(_symbol) => result, NotASymbol => with_hole( env, loc_arg.value, @@ -7193,9 +7402,9 @@ fn evaluate_arguments_then_runtime_error<'a>( // but, we also still evaluate and specialize the arguments to give better error messages let arg_symbols = Vec::from_iter_in( - loc_args - .iter() - .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), + loc_args.iter().map(|(var, arg_expr)| { + possible_reuse_symbol_or_specialize(env, procs, layout_cache, &arg_expr.value, *var) + }), arena, ) .into_bump_slice(); @@ -7260,8 +7469,14 @@ fn call_by_name<'a>( let arena = env.arena; let arg_symbols = Vec::from_iter_in( - loc_args.iter().map(|(_, arg_expr)| { - possible_reuse_symbol(env, procs, &arg_expr.value) + loc_args.iter().map(|(arg_var, arg_expr)| { + possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &arg_expr.value, + *arg_var, + ) }), arena, ) @@ -7352,11 +7567,9 @@ fn call_by_name_help<'a>( // the arguments given to the function, stored in symbols let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena); - field_symbols.extend( - loc_args - .iter() - .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), - ); + field_symbols.extend(loc_args.iter().map(|(arg_var, arg_expr)| { + possible_reuse_symbol_or_specialize(env, procs, layout_cache, &arg_expr.value, *arg_var) + })); // If required, add an extra argument to the layout that is the captured environment // afterwards, we MUST make sure the number of arguments in the layout matches the @@ -7428,16 +7641,7 @@ fn call_by_name_help<'a>( // imported symbols cannot capture anything let captured = &[]; - construct_closure_data( - env, - procs, - layout_cache, - lambda_set, - proc_name, - captured, - assigned, - hole, - ) + construct_closure_data(env, lambda_set, proc_name, captured, assigned, hole) } else { debug_assert_eq!( argument_layouts.len(), @@ -7493,13 +7697,20 @@ fn call_by_name_help<'a>( proc_name, ); + let has_closure = argument_layouts.len() != top_level_layout.arguments.len(); + let closure_argument = env.unique_symbol(); + + if has_closure { + field_symbols.push(closure_argument); + } + let field_symbols = field_symbols.into_bump_slice(); let call = self::Call { call_type: CallType::ByName { name: proc_name, ret_layout, - arg_layouts: argument_layouts, + arg_layouts: top_level_layout.arguments, specialization_id: env.next_call_specialization_id(), }, arguments: field_symbols, @@ -7507,8 +7718,31 @@ fn call_by_name_help<'a>( let result = build_call(env, call, assigned, *ret_layout, hole); - let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); - assign_to_symbols(env, procs, layout_cache, iter, result) + // NOTE: the zip omits the closure symbol, if it exists, + // because loc_args then is shorter than field_symbols + debug_assert!([0, 1].contains(&(field_symbols.len() - loc_args.len()))); + let iter = loc_args.into_iter().zip(field_symbols.iter()).rev(); + let result = assign_to_symbols(env, procs, layout_cache, iter, result); + + if has_closure { + let partial_proc = procs.partial_procs.get_symbol(proc_name).unwrap(); + + let captured = match partial_proc.captured_symbols { + CapturedSymbols::None => &[], + CapturedSymbols::Captured(slice) => slice, + }; + + construct_closure_data( + env, + lambda_set, + proc_name, + captured.iter(), + closure_argument, + env.arena.alloc(result), + ) + } else { + result + } } PendingSpecializations::Making => { let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name); @@ -7788,11 +8022,9 @@ fn call_specialized_proc<'a>( let result = construct_closure_data( env, - procs, - layout_cache, lambda_set, proc_name, - symbols, + symbols.iter().copied(), closure_data_symbol, env.arena.alloc(new_hole), ); @@ -7892,6 +8124,7 @@ pub struct WhenBranch<'a> { #[allow(clippy::type_complexity)] fn from_can_pattern<'a>( env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, can_pattern: &roc_can::pattern::Pattern, ) -> Result< @@ -7902,13 +8135,14 @@ fn from_can_pattern<'a>( RuntimeError, > { let mut assignments = Vec::new_in(env.arena); - let pattern = from_can_pattern_help(env, layout_cache, can_pattern, &mut assignments)?; + let pattern = from_can_pattern_help(env, procs, layout_cache, can_pattern, &mut assignments)?; Ok((pattern, assignments)) } fn from_can_pattern_help<'a>( env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, can_pattern: &roc_can::pattern::Pattern, assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, @@ -8030,7 +8264,7 @@ fn from_can_pattern_help<'a>( render_as: RenderAs::Tag, alternatives: vec![Ctor { tag_id: TagId(0), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), arity: 0, }], }, @@ -8043,12 +8277,12 @@ fn from_can_pattern_help<'a>( alternatives: vec![ Ctor { tag_id: TagId(0), - name: ffalse, + name: CtorName::Tag(ffalse), arity: 0, }, Ctor { tag_id: TagId(1), - name: ttrue, + name: CtorName::Tag(ttrue), arity: 0, }, ], @@ -8064,7 +8298,7 @@ fn from_can_pattern_help<'a>( for (i, tag_name) in tag_names.into_iter().enumerate() { ctors.push(Ctor { tag_id: TagId(i as _), - name: tag_name, + name: CtorName::Tag(tag_name), arity: 0, }) } @@ -8103,7 +8337,13 @@ fn from_can_pattern_help<'a>( let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) { mono_args.push(( - from_can_pattern_help(env, layout_cache, &loc_pat.value, assignments)?, + from_can_pattern_help( + env, + procs, + layout_cache, + &loc_pat.value, + assignments, + )?, *layout, )); } @@ -8155,7 +8395,7 @@ fn from_can_pattern_help<'a>( for (i, (tag_name, args)) in tags.iter().enumerate() { ctors.push(Ctor { tag_id: TagId(i as _), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), arity: args.len(), }) } @@ -8181,6 +8421,7 @@ fn from_can_pattern_help<'a>( mono_args.push(( from_can_pattern_help( env, + procs, layout_cache, &loc_pat.value, assignments, @@ -8206,7 +8447,7 @@ fn from_can_pattern_help<'a>( for (i, (tag_name, args)) in tags.iter().enumerate() { ctors.push(Ctor { tag_id: TagId(i as _), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), // don't include tag discriminant in arity arity: args.len() - 1, }) @@ -8226,6 +8467,7 @@ fn from_can_pattern_help<'a>( mono_args.push(( from_can_pattern_help( env, + procs, layout_cache, &loc_pat.value, assignments, @@ -8251,7 +8493,7 @@ fn from_can_pattern_help<'a>( ctors.push(Ctor { tag_id: TagId(0), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), arity: fields.len(), }); @@ -8269,6 +8511,7 @@ fn from_can_pattern_help<'a>( mono_args.push(( from_can_pattern_help( env, + procs, layout_cache, &loc_pat.value, assignments, @@ -8298,7 +8541,7 @@ fn from_can_pattern_help<'a>( if i == nullable_id as usize { ctors.push(Ctor { tag_id: TagId(i as _), - name: nullable_name.clone(), + name: CtorName::Tag(nullable_name.clone()), // don't include tag discriminant in arity arity: 0, }); @@ -8308,7 +8551,7 @@ fn from_can_pattern_help<'a>( ctors.push(Ctor { tag_id: TagId(i as _), - name: tag_name.clone(), + name: CtorName::Tag(tag_name.clone()), // don't include tag discriminant in arity arity: args.len() - 1, }); @@ -8319,7 +8562,7 @@ fn from_can_pattern_help<'a>( if i == nullable_id as usize { ctors.push(Ctor { tag_id: TagId(i as _), - name: nullable_name.clone(), + name: CtorName::Tag(nullable_name.clone()), // don't include tag discriminant in arity arity: 0, }); @@ -8342,6 +8585,7 @@ fn from_can_pattern_help<'a>( mono_args.push(( from_can_pattern_help( env, + procs, layout_cache, &loc_pat.value, assignments, @@ -8369,13 +8613,13 @@ fn from_can_pattern_help<'a>( ctors.push(Ctor { tag_id: TagId(nullable_id as _), - name: nullable_name.clone(), + name: CtorName::Tag(nullable_name.clone()), arity: 0, }); ctors.push(Ctor { tag_id: TagId(!nullable_id as _), - name: nullable_name.clone(), + name: CtorName::Tag(nullable_name.clone()), // FIXME drop tag arity: other_fields.len() - 1, }); @@ -8398,6 +8642,7 @@ fn from_can_pattern_help<'a>( mono_args.push(( from_can_pattern_help( env, + procs, layout_cache, &loc_pat.value, assignments, @@ -8428,8 +8673,13 @@ fn from_can_pattern_help<'a>( let arg_layout = layout_cache .from_var(env.arena, *arg_var, env.subs) .unwrap(); - let mono_arg_pattern = - from_can_pattern_help(env, layout_cache, &loc_arg_pattern.value, assignments)?; + let mono_arg_pattern = from_can_pattern_help( + env, + procs, + layout_cache, + &loc_arg_pattern.value, + assignments, + )?; Ok(Pattern::OpaqueUnwrap { opaque: *opaque, argument: Box::new((mono_arg_pattern, arg_layout)), @@ -8472,6 +8722,7 @@ fn from_can_pattern_help<'a>( // this field is destructured by the pattern mono_destructs.push(from_can_record_destruct( env, + procs, layout_cache, &destruct.value, field_layout, @@ -8563,6 +8814,7 @@ fn from_can_pattern_help<'a>( fn from_can_record_destruct<'a>( env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, can_rd: &roc_can::pattern::RecordDestruct, field_layout: Layout<'a>, @@ -8579,7 +8831,7 @@ fn from_can_record_destruct<'a>( DestructType::Required(can_rd.symbol) } roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard( - from_can_pattern_help(env, layout_cache, &loc_pattern.value, assignments)?, + from_can_pattern_help(env, procs, layout_cache, &loc_pattern.value, assignments)?, ), }, }) @@ -8631,9 +8883,9 @@ pub fn num_argument_to_int_or_float( num_argument_to_int_or_float(subs, target_info, var, true) } - Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => IntOrFloat::DecimalFloatType, + Symbol::NUM_DECIMAL => IntOrFloat::DecimalFloatType, - Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { + Symbol::NUM_NAT | Symbol::NUM_NATURAL => { let int_width = match target_info.ptr_width() { roc_target::PtrWidth::Bytes4 => IntWidth::U32, roc_target::PtrWidth::Bytes8 => IntWidth::U64, diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index eef312cab3..8a3a417cb4 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -12,6 +12,7 @@ use roc_types::subs::{ Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable, }; use roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError}; +use std::cmp::Ordering; use std::collections::hash_map::{DefaultHasher, Entry}; use std::collections::HashMap; use std::hash::{Hash, Hasher}; @@ -311,6 +312,50 @@ impl<'a> UnionLayout<'a> { .append(alloc.intersperse(tags_doc, ", ")) .append(alloc.text("]")) } + Recursive(tags) => { + let tags_doc = tags.iter().map(|fields| { + alloc.text("C ").append(alloc.intersperse( + fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + )) + }); + alloc + .text("[") + .append(alloc.intersperse(tags_doc, ", ")) + .append(alloc.text("]")) + } + NonNullableUnwrapped(fields) => { + let fields_doc = alloc.text("C ").append(alloc.intersperse( + fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + )); + alloc + .text("[") + .append(fields_doc) + .append(alloc.text("]")) + } + NullableUnwrapped { + nullable_id, + other_fields, + } => { + let fields_doc = alloc.text("C ").append( + alloc.intersperse( + other_fields + .iter() + .map(|x| x.to_doc(alloc, Parens::InTypeParam)), + " ", + ), + ); + let tags_doc = if nullable_id { + alloc.concat(vec![alloc.text(", "), fields_doc]) + } else { + alloc.concat(vec![fields_doc, alloc.text(", ")]) + }; + alloc + .text("[") + .append(tags_doc) + .append(alloc.text("]")) + } _ => alloc.text("TODO"), } } @@ -939,6 +984,16 @@ pub const fn round_up_to_alignment(width: u32, alignment: u32) -> u32 { } } +#[inline(always)] +pub fn is_unresolved_var(subs: &Subs, var: Variable) -> bool { + use Content::*; + let content = subs.get_content_without_compacting(var); + matches!( + content, + FlexVar(..) | RigidVar(..) | FlexAbleVar(..) | RigidAbleVar(..), + ) +} + impl<'a> Layout<'a> { pub const VOID: Self = Layout::Union(UnionLayout::NonRecursive(&[])); pub const UNIT: Self = Layout::Struct { @@ -971,12 +1026,24 @@ impl<'a> Layout<'a> { } match symbol { - Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => { - return Ok(Layout::Builtin(Builtin::Decimal)) + Symbol::NUM_DECIMAL => return Ok(Layout::Builtin(Builtin::Decimal)), + + Symbol::NUM_NAT | Symbol::NUM_NATURAL => { + return Ok(Layout::usize(env.target_info)) } - Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { - return Ok(Layout::usize(env.target_info)) + Symbol::NUM_NUM | Symbol::NUM_INT | Symbol::NUM_INTEGER + if is_unresolved_var(env.subs, actual_var) => + { + // default to i64 + return Ok(Layout::i64()); + } + + Symbol::NUM_FRAC | Symbol::NUM_FLOATINGPOINT + if is_unresolved_var(env.subs, actual_var) => + { + // default to f64 + return Ok(Layout::f64()); } _ => Self::from_var(env, actual_var), @@ -1262,7 +1329,7 @@ impl<'a> Layout<'a> { /// But if we're careful when to invalidate certain keys, we still get some benefit #[derive(Debug)] pub struct LayoutCache<'a> { - target_info: TargetInfo, + pub target_info: TargetInfo, _marker: std::marker::PhantomData<&'a u8>, } @@ -1645,7 +1712,7 @@ fn layout_from_flat_type<'a>( Ok(Layout::f32()) } - Symbol::NUM_NUM | Symbol::NUM_AT_NUM => { + Symbol::NUM_NUM => { // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer debug_assert_eq!(args.len(), 1); @@ -1685,41 +1752,39 @@ fn layout_from_flat_type<'a>( Record(fields, ext_var) => { // extract any values from the ext_var - let mut pairs = Vec::with_capacity_in(fields.len(), arena); + let mut sortables = Vec::with_capacity_in(fields.len(), arena); let it = match fields.unsorted_iterator(subs, ext_var) { Ok(it) => it, Err(RecordFieldsError) => return Err(LayoutProblem::Erroneous), }; - for (label, field) in it { - // drop optional fields - let var = match field { - RecordField::Optional(_) => continue, - RecordField::Required(var) => var, - RecordField::Demanded(var) => var, - }; - pairs.push((label, Layout::from_var(env, var)?)); + for (label, field) in it { + match field { + RecordField::Required(field_var) | RecordField::Demanded(field_var) => { + sortables.push((label, Layout::from_var(env, field_var)?)); + } + RecordField::Optional(_) => { + // drop optional fields + } + } } - pairs.sort_by(|(label1, layout1), (label2, layout2)| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); - - size2.cmp(&size1).then(label1.cmp(label2)) + sortables.sort_by(|(label1, layout1), (label2, layout2)| { + cmp_fields(label1, layout1, label2, layout2, target_info) }); let ordered_field_names = - Vec::from_iter_in(pairs.iter().map(|(label, _)| *label), arena); + Vec::from_iter_in(sortables.iter().map(|(label, _)| *label), arena); let field_order_hash = FieldOrderHash::from_ordered_fields(ordered_field_names.as_slice()); - let mut layouts = Vec::from_iter_in(pairs.into_iter().map(|t| t.1), arena); - - if layouts.len() == 1 { + if sortables.len() == 1 { // If the record has only one field that isn't zero-sized, // unwrap it. - Ok(layouts.pop().unwrap()) + Ok(sortables.pop().unwrap().1) } else { + let layouts = Vec::from_iter_in(sortables.into_iter().map(|t| t.1), arena); + Ok(Layout::Struct { field_order_hash, field_layouts: layouts.into_bump_slice(), @@ -1731,7 +1796,7 @@ fn layout_from_flat_type<'a>( debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); - Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) + Ok(layout_from_tag_union(env, &tags)) } FunctionOrTagUnion(tag_name, _, ext_var) => { debug_assert!( @@ -1742,7 +1807,7 @@ fn layout_from_flat_type<'a>( let union_tags = UnionTags::from_tag_name_index(tag_name); let (tags, _) = union_tags.unsorted_tags_and_ext(subs, ext_var); - Ok(layout_from_tag_union(arena, &tags, subs, env.target_info)) + Ok(layout_from_tag_union(env, &tags)) } RecursiveTagUnion(rec_var, tags, ext_var) => { let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); @@ -1885,10 +1950,7 @@ fn sort_record_fields_help<'a>( |(label1, _, res_layout1), (label2, _, res_layout2)| match res_layout1 { Ok(layout1) | Err(layout1) => match res_layout2 { Ok(layout2) | Err(layout2) => { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); - - size2.cmp(&size1).then(label1.cmp(label2)) + cmp_fields(label1, layout1, label2, layout2, target_info) } }, }, @@ -2071,23 +2133,14 @@ fn is_recursive_tag_union(layout: &Layout) -> bool { } fn union_sorted_tags_help_new<'a>( - arena: &'a Bump, + env: &mut Env<'a, '_>, tags_list: &[(&'_ TagName, &[Variable])], opt_rec_var: Option, - subs: &Subs, - target_info: TargetInfo, ) -> UnionVariant<'a> { // sort up front; make sure the ordering stays intact! - let mut tags_list = Vec::from_iter_in(tags_list.iter(), arena); + let mut tags_list = Vec::from_iter_in(tags_list.iter(), env.arena); tags_list.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; - match tags_list.len() { 0 => { // trying to instantiate a type with no values @@ -2098,39 +2151,29 @@ fn union_sorted_tags_help_new<'a>( let tag_name = tag_name.clone(); // just one tag in the union (but with arguments) can be a struct - let mut layouts = Vec::with_capacity_in(tags_list.len(), arena); + let mut layouts = Vec::with_capacity_in(tags_list.len(), env.arena); - // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int - match tag_name { - TagName::Private(Symbol::NUM_AT_NUM) => { - let var = arguments[0]; - layouts - .push(unwrap_num_tag(subs, var, target_info).expect("invalid num layout")); - } - _ => { - for &var in arguments { - match Layout::from_var(&mut env, var) { - Ok(layout) => { - layouts.push(layout); - } - Err(LayoutProblem::UnresolvedTypeVar(_)) => { - // If we encounter an unbound type var (e.g. `Ok *`) - // then it's zero-sized; In the future we may drop this argument - // completely, but for now we represent it with the empty tag union - layouts.push(Layout::VOID) - } - Err(LayoutProblem::Erroneous) => { - // An erroneous type var will code gen to a runtime - // error, so we don't need to store any data for it. - } - } + for &var in arguments { + match Layout::from_var(env, var) { + Ok(layout) => { + layouts.push(layout); + } + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty tag union + layouts.push(Layout::VOID) + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. } } } layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -2151,7 +2194,7 @@ fn union_sorted_tags_help_new<'a>( } num_tags => { // default path - let mut answer = Vec::with_capacity_in(tags_list.len(), arena); + let mut answer = Vec::with_capacity_in(tags_list.len(), env.arena); let mut has_any_arguments = false; let mut nullable: Option<(TagIdIntType, TagName)> = None; @@ -2174,17 +2217,19 @@ fn union_sorted_tags_help_new<'a>( continue; } - let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); + let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, env.arena); for &var in arguments { - match Layout::from_var(&mut env, var) { + match Layout::from_var(env, var) { Ok(layout) => { has_any_arguments = true; // make sure to not unroll recursive types! let self_recursion = opt_rec_var.is_some() - && subs.get_root_key_without_compacting(var) - == subs.get_root_key_without_compacting(opt_rec_var.unwrap()) + && env.subs.get_root_key_without_compacting(var) + == env + .subs + .get_root_key_without_compacting(opt_rec_var.unwrap()) && is_recursive_tag_union(&layout); if self_recursion { @@ -2207,8 +2252,8 @@ fn union_sorted_tags_help_new<'a>( } arg_layouts.sort_by(|layout1, layout2| { - let size1 = layout1.alignment_bytes(target_info); - let size2 = layout2.alignment_bytes(target_info); + let size1 = layout1.alignment_bytes(env.target_info); + let size2 = layout2.alignment_bytes(env.target_info); size2.cmp(&size1) }); @@ -2229,7 +2274,7 @@ fn union_sorted_tags_help_new<'a>( 3..=MAX_ENUM_SIZE if !has_any_arguments => { // type can be stored in a byte // needs the sorted tag names to determine the tag_id - let mut tag_names = Vec::with_capacity_in(answer.len(), arena); + let mut tag_names = Vec::with_capacity_in(answer.len(), env.arena); for (tag_name, _) in answer { tag_names.push(tag_name); @@ -2303,37 +2348,26 @@ pub fn union_sorted_tags_help<'a>( let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena); let mut contains_zero_sized = false; - // special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int - match tag_name { - TagName::Private(Symbol::NUM_AT_NUM) => { - layouts.push( - unwrap_num_tag(subs, arguments[0], target_info) - .expect("invalid num layout"), - ); - } - _ => { - for var in arguments { - match Layout::from_var(&mut env, var) { - Ok(layout) => { - // Drop any zero-sized arguments like {} - if !layout.is_dropped_because_empty() { - layouts.push(layout); - } else { - contains_zero_sized = true; - } - } - Err(LayoutProblem::UnresolvedTypeVar(_)) => { - // If we encounter an unbound type var (e.g. `Ok *`) - // then it's zero-sized; In the future we may drop this argument - // completely, but for now we represent it with the empty tag union - layouts.push(Layout::VOID) - } - Err(LayoutProblem::Erroneous) => { - // An erroneous type var will code gen to a runtime - // error, so we don't need to store any data for it. - } + for var in arguments { + match Layout::from_var(&mut env, var) { + Ok(layout) => { + // Drop any zero-sized arguments like {} + if !layout.is_dropped_because_empty() { + layouts.push(layout); + } else { + contains_zero_sized = true; } } + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty tag union + layouts.push(Layout::VOID) + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + } } } @@ -2488,128 +2522,95 @@ pub fn union_sorted_tags_help<'a>( } } -fn layout_from_newtype<'a>( - arena: &'a Bump, - tags: &UnsortedUnionTags, - subs: &Subs, - target_info: TargetInfo, -) -> Layout<'a> { - debug_assert!(tags.is_newtype_wrapper(subs)); +fn layout_from_newtype<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> { + debug_assert!(tags.is_newtype_wrapper(env.subs)); - let (tag_name, var) = tags.get_newtype(subs); + let (_tag_name, var) = tags.get_newtype(env.subs); - if tag_name == &TagName::Private(Symbol::NUM_AT_NUM) { - unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") - } else { - let mut env = Env { - arena, - subs, - seen: Vec::new_in(arena), - target_info, - }; - - match Layout::from_var(&mut env, var) { - Ok(layout) => layout, - Err(LayoutProblem::UnresolvedTypeVar(_)) => { - // If we encounter an unbound type var (e.g. `Ok *`) - // then it's zero-sized; In the future we may drop this argument - // completely, but for now we represent it with the empty tag union - Layout::VOID - } - Err(LayoutProblem::Erroneous) => { - // An erroneous type var will code gen to a runtime - // error, so we don't need to store any data for it. - todo!() - } + match Layout::from_var(env, var) { + Ok(layout) => layout, + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + // If we encounter an unbound type var (e.g. `Ok *`) + // then it's zero-sized; In the future we may drop this argument + // completely, but for now we represent it with the empty tag union + Layout::VOID + } + Err(LayoutProblem::Erroneous) => { + // An erroneous type var will code gen to a runtime + // error, so we don't need to store any data for it. + todo!() } } } -fn layout_from_tag_union<'a>( - arena: &'a Bump, - tags: &UnsortedUnionTags, - subs: &Subs, - target_info: TargetInfo, -) -> Layout<'a> { +fn layout_from_tag_union<'a>(env: &mut Env<'a, '_>, tags: &UnsortedUnionTags) -> Layout<'a> { use UnionVariant::*; - if tags.is_newtype_wrapper(subs) { - return layout_from_newtype(arena, tags, subs, target_info); + if tags.is_newtype_wrapper(env.subs) { + return layout_from_newtype(env, tags); } let tags_vec = &tags.tags; - match tags_vec.get(0) { - Some((tag_name, arguments)) if *tag_name == &TagName::Private(Symbol::NUM_AT_NUM) => { - debug_assert_eq!(arguments.len(), 1); + let opt_rec_var = None; + let variant = union_sorted_tags_help_new(env, tags_vec, opt_rec_var); - let &var = arguments.iter().next().unwrap(); + match variant { + Never => Layout::VOID, + Unit | UnitWithArguments => Layout::UNIT, + BoolUnion { .. } => Layout::bool(), + ByteUnion(_) => Layout::u8(), + Newtype { + arguments: field_layouts, + .. + } => { + let answer1 = if field_layouts.len() == 1 { + field_layouts[0] + } else { + Layout::struct_no_name_order(field_layouts.into_bump_slice()) + }; - unwrap_num_tag(subs, var, target_info).expect("invalid Num argument") + answer1 } - _ => { - let opt_rec_var = None; - let variant = - union_sorted_tags_help_new(arena, tags_vec, opt_rec_var, subs, target_info); + Wrapped(variant) => { + use WrappedVariant::*; match variant { - Never => Layout::VOID, - Unit | UnitWithArguments => Layout::UNIT, - BoolUnion { .. } => Layout::bool(), - ByteUnion(_) => Layout::u8(), - Newtype { - arguments: field_layouts, - .. + NonRecursive { + sorted_tag_layouts: tags, } => { - let answer1 = if field_layouts.len() == 1 { - field_layouts[0] - } else { - Layout::struct_no_name_order(field_layouts.into_bump_slice()) - }; + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); + tag_layouts.extend(tags.iter().map(|r| r.1)); - answer1 + Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice())) } - Wrapped(variant) => { - use WrappedVariant::*; - match variant { - NonRecursive { - sorted_tag_layouts: tags, - } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - tag_layouts.extend(tags.iter().map(|r| r.1)); + Recursive { + sorted_tag_layouts: tags, + } => { + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); + tag_layouts.extend(tags.iter().map(|r| r.1)); - Layout::Union(UnionLayout::NonRecursive(tag_layouts.into_bump_slice())) - } - - Recursive { - sorted_tag_layouts: tags, - } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - tag_layouts.extend(tags.iter().map(|r| r.1)); - - debug_assert!(tag_layouts.len() > 1); - Layout::Union(UnionLayout::Recursive(tag_layouts.into_bump_slice())) - } - - NullableWrapped { - nullable_id, - nullable_name: _, - sorted_tag_layouts: tags, - } => { - let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); - tag_layouts.extend(tags.iter().map(|r| r.1)); - - Layout::Union(UnionLayout::NullableWrapped { - nullable_id, - other_tags: tag_layouts.into_bump_slice(), - }) - } - - NullableUnwrapped { .. } => todo!(), - NonNullableUnwrapped { .. } => todo!(), - } + debug_assert!(tag_layouts.len() > 1); + Layout::Union(UnionLayout::Recursive(tag_layouts.into_bump_slice())) } + + NullableWrapped { + nullable_id, + nullable_name: _, + sorted_tag_layouts: tags, + } => { + let mut tag_layouts = Vec::with_capacity_in(tags.len(), env.arena); + tag_layouts.extend(tags.iter().map(|r| r.1)); + + Layout::Union(UnionLayout::NullableWrapped { + nullable_id, + other_tags: tag_layouts.into_bump_slice(), + }) + } + + NullableUnwrapped { .. } => todo!(), + NonNullableUnwrapped { .. } => todo!(), } } } @@ -2707,88 +2708,6 @@ fn layout_from_num_content<'a>( } } -fn unwrap_num_tag<'a>( - subs: &Subs, - var: Variable, - target_info: TargetInfo, -) -> Result, LayoutProblem> { - match subs.get_content_without_compacting(var) { - Content::Alias(Symbol::NUM_INTEGER, args, _, _) => { - debug_assert!(args.len() == 1); - - let precision_var = subs[args.all_variables().into_iter().next().unwrap()]; - - let precision = subs.get_content_without_compacting(precision_var); - - match precision { - Content::Alias(symbol, args, _, _) => { - debug_assert!(args.is_empty()); - - let layout = match *symbol { - Symbol::NUM_SIGNED128 => Layout::i128(), - Symbol::NUM_SIGNED64 => Layout::i64(), - Symbol::NUM_SIGNED32 => Layout::i32(), - Symbol::NUM_SIGNED16 => Layout::i16(), - Symbol::NUM_SIGNED8 => Layout::i8(), - Symbol::NUM_UNSIGNED128 => Layout::u128(), - Symbol::NUM_UNSIGNED64 => Layout::u64(), - Symbol::NUM_UNSIGNED32 => Layout::u32(), - Symbol::NUM_UNSIGNED16 => Layout::u16(), - Symbol::NUM_UNSIGNED8 => Layout::u8(), - Symbol::NUM_NATURAL => Layout::usize(target_info), - - _ => unreachable!("not a valid int variant: {:?} {:?}", symbol, args), - }; - - Ok(layout) - } - Content::FlexVar(_) | Content::RigidVar(_) => { - // default to i64 - Ok(Layout::i64()) - } - _ => unreachable!("not a valid int variant: {:?}", precision), - } - } - Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _, _) => { - debug_assert!(args.len() == 1); - - let precision_var = subs[args.all_variables().into_iter().next().unwrap()]; - - let precision = subs.get_content_without_compacting(precision_var); - - match precision { - Content::Alias(Symbol::NUM_BINARY32, args, _, _) => { - debug_assert!(args.is_empty()); - - Ok(Layout::f32()) - } - Content::Alias(Symbol::NUM_BINARY64, args, _, _) => { - debug_assert!(args.is_empty()); - - Ok(Layout::f64()) - } - Content::Alias(Symbol::NUM_DECIMAL, args, _, _) => { - debug_assert!(args.is_empty()); - - Ok(Layout::Builtin(Builtin::Decimal)) - } - Content::FlexVar(_) | Content::RigidVar(_) => { - // default to f64 - Ok(Layout::f64()) - } - _ => unreachable!("not a valid float variant: {:?}", precision), - } - } - Content::FlexVar(_) | Content::RigidVar(_) => { - // If this was still a (Num *) then default to compiling it to i64 - Ok(Layout::default_integer()) - } - other => { - todo!("TODO non structure Num.@Num flat_type {:?}", other); - } - } -} - fn dict_layout_from_key_value<'a>( env: &mut Env<'a, '_>, key_var: Variable, @@ -2851,7 +2770,7 @@ impl LayoutId { // Returns something like "foo#1" when given a symbol that interns to "foo" // and a LayoutId of 1. pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String { - let ident_string = symbol.ident_str(interns); + let ident_string = symbol.as_str(interns); let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap(); format!("{}_{}_{}", module_string, ident_string, self.0) } @@ -2998,3 +2917,20 @@ mod test { assert_eq!(layout.stack_size_without_alignment(target_info), 5); } } + +/// Compare two fields when sorting them for code gen. +/// This is called by both code gen and bindgen, so that +/// their field orderings agree. +#[inline(always)] +pub fn cmp_fields( + label1: &Lowercase, + layout1: &Layout<'_>, + label2: &Lowercase, + layout2: &Layout<'_>, + target_info: TargetInfo, +) -> Ordering { + let size1 = layout1.alignment_bytes(target_info); + let size2 = layout2.alignment_bytes(target_info); + + size2.cmp(&size1).then(label1.cmp(label2)) +} diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs index ed813d0c0a..f5477a5976 100644 --- a/compiler/mono/src/layout_soa.rs +++ b/compiler/mono/src/layout_soa.rs @@ -343,8 +343,7 @@ impl LambdaSet { TagName::Closure(symbol) => { layouts.symbols.push(*symbol); } - TagName::Global(_) => unreachable!("lambda set tags must be closure tags"), - TagName::Private(_) => unreachable!("lambda set tags must be closure tags"), + TagName::Tag(_) => unreachable!("lambda set tags must be closure tags"), } } @@ -678,11 +677,9 @@ impl Layout { } match symbol { - Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => Ok(Layout::Decimal), + Symbol::NUM_DECIMAL => Ok(Layout::Decimal), - Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => { - Ok(layouts.usize()) - } + Symbol::NUM_NAT | Symbol::NUM_NATURAL => Ok(layouts.usize()), _ => { // at this point we throw away alias information diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index d927845749..efffd4c9c9 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -4,6 +4,7 @@ pub mod borrow; pub mod code_gen_help; +mod copy; pub mod inc_dec; pub mod ir; pub mod layout; diff --git a/compiler/mono/src/low_level.rs b/compiler/mono/src/low_level.rs index 03092a25cb..948e90b5b3 100644 --- a/compiler/mono/src/low_level.rs +++ b/compiler/mono/src/low_level.rs @@ -83,6 +83,35 @@ impl HigherOrder { HigherOrder::ListAll { .. } => 1, } } + + /// Index in the array of arguments of the symbol that is the closure data + /// (captured environment for this function) + pub const fn closure_data_index(&self) -> usize { + use HigherOrder::*; + + match self { + ListMap { .. } + | ListMapWithIndex { .. } + | ListSortWith { .. } + | ListKeepIf { .. } + | ListKeepOks { .. } + | ListKeepErrs { .. } + | ListAny { .. } + | ListAll { .. } + | ListFindUnsafe { .. } => 2, + ListMap2 { .. } => 3, + ListMap3 { .. } => 4, + ListMap4 { .. } => 5, + ListWalk { .. } | ListWalkUntil { .. } | ListWalkBackwards { .. } | DictWalk { .. } => { + 3 + } + } + } + + /// Index of the function symbol in the argument list + pub const fn function_index(&self) -> usize { + self.closure_data_index() - 1 + } } #[allow(dead_code)] @@ -152,7 +181,7 @@ enum FirstOrder { NumSqrtUnchecked, NumLogUnchecked, NumRound, - NumToFloat, + NumToFrac, NumPow, NumCeiling, NumPowInt, diff --git a/compiler/parse/fuzz/dict.txt b/compiler/parse/fuzz/dict.txt index c22976ccb2..c2e458bbda 100644 --- a/compiler/parse/fuzz/dict.txt +++ b/compiler/parse/fuzz/dict.txt @@ -30,7 +30,6 @@ ">=" ">" "^" -"%%" "%" "->" \ No newline at end of file diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 83d3bbfe34..a47f2da392 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -188,11 +188,9 @@ pub enum Expr<'a> { Underscore(&'a str), // Tags - GlobalTag(&'a str), - PrivateTag(&'a str), + Tag(&'a str), - // Reference to an opaque type, e.g. $Opaq - // TODO(opaques): $->@ in the above comment + // Reference to an opaque type, e.g. @Opaq OpaqueRef(&'a str), // Pattern Matching @@ -441,12 +439,7 @@ pub enum TypeAnnotation<'a> { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Tag<'a> { - Global { - name: Loc<&'a str>, - args: &'a [Loc>], - }, - - Private { + Apply { name: Loc<&'a str>, args: &'a [Loc>], }, @@ -522,8 +515,7 @@ pub enum Pattern<'a> { // Identifier Identifier(&'a str), - GlobalTag(&'a str), - PrivateTag(&'a str), + Tag(&'a str), OpaqueRef(&'a str), @@ -578,8 +570,7 @@ pub enum Base { impl<'a> Pattern<'a> { pub fn from_ident(arena: &'a Bump, ident: Ident<'a>) -> Pattern<'a> { match ident { - Ident::GlobalTag(string) => Pattern::GlobalTag(string), - Ident::PrivateTag(string) => Pattern::PrivateTag(string), + Ident::Tag(string) => Pattern::Tag(string), Ident::OpaqueRef(string) => Pattern::OpaqueRef(string), Ident::Access { module_name, parts } => { if parts.len() == 1 { @@ -628,8 +619,7 @@ impl<'a> Pattern<'a> { match (self, other) { (Identifier(x), Identifier(y)) => x == y, - (GlobalTag(x), GlobalTag(y)) => x == y, - (PrivateTag(x), PrivateTag(y)) => x == y, + (Tag(x), Tag(y)) => x == y, (Apply(constructor_x, args_x), Apply(constructor_y, args_y)) => { let equivalent_args = args_x .iter() @@ -927,7 +917,7 @@ impl<'a> Expr<'a> { } pub fn is_tag(&self) -> bool { - matches!(self, Expr::GlobalTag(_) | Expr::PrivateTag(_)) + matches!(self, Expr::Tag(_)) } } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 4d0654b31f..2ede367506 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -196,6 +196,7 @@ fn parse_loc_term_or_underscore<'a>( ) -> ParseResult<'a, Loc>, EExpr<'a>> { one_of!( loc_expr_in_parens_etc_help(min_indent), + loc!(specialize(EExpr::If, if_expr_help(min_indent, options))), loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())), @@ -603,7 +604,7 @@ fn append_body_definition<'a>( // UserId x = UserId 42 // We optimistically parsed the first line as an alias; we now turn it // into an annotation. - let loc_name = arena.alloc(header.name.map(|x| Pattern::GlobalTag(x))); + let loc_name = arena.alloc(header.name.map(|x| Pattern::Tag(x))); let ann_pattern = Pattern::Apply(loc_name, header.vars); let vars_region = Region::across_all(header.vars.iter().map(|v| &v.region)); let region_ann_pattern = Region::span_across(&loc_name.region, &vars_region); @@ -697,7 +698,7 @@ fn append_annotation_definition<'a>( match &loc_pattern.value { Pattern::Apply( Loc { - value: Pattern::GlobalTag(name), + value: Pattern::Tag(name), .. }, alias_arguments, @@ -711,7 +712,7 @@ fn append_annotation_definition<'a>( loc_ann, kind, ), - Pattern::GlobalTag(name) => append_type_definition( + Pattern::Tag(name) => append_type_definition( arena, defs, region, @@ -872,12 +873,12 @@ fn parse_defs_end<'a>( { Pattern::Apply( Loc { - value: Pattern::GlobalTag(name), + value: Pattern::Tag(name), region, }, args, ) => Some((name, *region, args)), - Pattern::GlobalTag(name) => Some((name, loc_pattern.region, &[])), + Pattern::Tag(name) => Some((name, loc_pattern.region, &[])), _ => None, }; @@ -1019,7 +1020,7 @@ fn finish_parsing_alias_or_opaque<'a>( .map_err(|fail| (MadeProgress, fail, state.clone()))?; let (loc_def, state) = match &expr.value { - Expr::GlobalTag(name) => { + Expr::Tag(name) => { let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena); for argument in arguments { @@ -1509,8 +1510,8 @@ fn parse_expr_operator<'a>( } } } - Err((NoProgress, _, _)) => { - todo!() + Err((NoProgress, expr, e)) => { + todo!("{:?} {:?}", expr, e) } }, } @@ -1542,11 +1543,11 @@ fn parse_expr_end<'a>( .. }, state, - )) if matches!(expr_state.expr.value, Expr::GlobalTag(..)) => { + )) if matches!(expr_state.expr.value, Expr::Tag(..)) => { // This is an ability definition, `Ability arg1 ... has ...`. let name = expr_state.expr.map_owned(|e| match e { - Expr::GlobalTag(name) => name, + Expr::Tag(name) => name, _ => unreachable!(), }); @@ -1762,8 +1763,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::Underscore(opt_name)), - Expr::GlobalTag(value) => Ok(Pattern::GlobalTag(value)), - Expr::PrivateTag(value) => Ok(Pattern::PrivateTag(value)), + Expr::Tag(value) => Ok(Pattern::Tag(value)), Expr::OpaqueRef(value) => Ok(Pattern::OpaqueRef(value)), Expr::Apply(loc_val, loc_args, _) => { let region = loc_val.region; @@ -2436,8 +2436,7 @@ where fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> { match src { - Ident::GlobalTag(string) => Expr::GlobalTag(string), - Ident::PrivateTag(string) => Expr::PrivateTag(string), + Ident::Tag(string) => Expr::Tag(string), Ident::OpaqueRef(string) => Expr::OpaqueRef(string), Ident::Access { module_name, parts } => { let mut iter = parts.iter(); @@ -2762,7 +2761,6 @@ where "&&" => good!(BinOp::And, 2), "||" => good!(BinOp::Or, 2), "//" => good!(BinOp::DoubleSlash, 2), - "%%" => good!(BinOp::DoublePercent, 2), "->" => { // makes no progress, so it does not interfere with `_ if isGood -> ...` Err((NoProgress, to_error("->", state.pos()), state)) diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index c2afb90038..131cf4c465 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -5,7 +5,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::Position; -/// A global tag, for example. Must start with an uppercase letter +/// A tag, for example. Must start with an uppercase letter /// and then contain only letters and numbers afterwards - no dots allowed! #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct UppercaseIdent<'a>(&'a str); @@ -35,11 +35,8 @@ impl<'a> From<&'a UppercaseIdent<'a>> for &'a str { #[derive(Debug, Clone, PartialEq, Eq)] pub enum Ident<'a> { /// Foo or Bar - GlobalTag(&'a str), + Tag(&'a str), /// @Foo or @Bar - PrivateTag(&'a str), - /// $Foo or $Bar - // TODO(opaques): $->@ in the above comment OpaqueRef(&'a str), /// foo or foo.bar or Foo.Bar.baz.qux Access { @@ -57,7 +54,7 @@ impl<'a> Ident<'a> { use self::Ident::*; match self { - GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => string.len(), + Tag(string) | OpaqueRef(string) => string.len(), Access { module_name, parts } => { let mut len = if module_name.is_empty() { 0 @@ -101,31 +98,14 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { } pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { - move |arena, state: State<'a>| { - if state.bytes().starts_with(b"@") { - match chomp_private_tag_or_opaque( - /* private tag */ true, - state.bytes(), - state.pos(), - ) { - Err(BadIdent::Start(_)) => Err((NoProgress, (), state)), - Err(_) => Err((MadeProgress, (), state)), - Ok(ident) => { - let width = ident.len(); - Ok((MadeProgress, ident, state.advance(width))) - } - } - } else { - uppercase_ident().parse(arena, state) - } - } + move |arena, state: State<'a>| uppercase_ident().parse(arena, state) } /// This could be: /// /// * A module name /// * A type name -/// * A global tag +/// * A tag pub fn uppercase<'a>() -> impl Parser<'a, UppercaseIdent<'a>, ()> { move |_, state: State<'a>| match chomp_uppercase_part(state.bytes()) { Err(progress) => Err((progress, (), state)), @@ -140,7 +120,7 @@ pub fn uppercase<'a>() -> impl Parser<'a, UppercaseIdent<'a>, ()> { /// /// * A module name /// * A type name -/// * A global tag +/// * A tag pub fn uppercase_ident<'a>() -> impl Parser<'a, &'a str, ()> { move |_, state: State<'a>| match chomp_uppercase_part(state.bytes()) { Err(progress) => Err((progress, (), state)), @@ -242,7 +222,6 @@ pub enum BadIdent { WeirdDotAccess(Position), WeirdDotQualified(Position), StrayDot(Position), - BadPrivateTag(Position), BadOpaqueRef(Position), } @@ -311,21 +290,13 @@ fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { } } -/// a `@Token` private tag -fn chomp_private_tag_or_opaque( - private_tag: bool, // If false, opaque - buffer: &[u8], - pos: Position, -) -> Result<&str, BadIdent> { +/// a `@Token` opaque +fn chomp_opaque_ref(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { // assumes the leading `@` has NOT been chomped already - debug_assert_eq!(buffer.get(0), Some(if private_tag { &b'@' } else { &b'$' })); + debug_assert_eq!(buffer.get(0), Some(&b'@')); use encode_unicode::CharExt; - let bad_ident = if private_tag { - BadIdent::BadPrivateTag - } else { - BadIdent::BadOpaqueRef - }; + let bad_ident = BadIdent::BadOpaqueRef; match chomp_uppercase_part(&buffer[1..]) { Ok(name) => { @@ -362,15 +333,11 @@ fn chomp_identifier_chain<'a>( } Err(fail) => return Err((1, fail)), }, - c @ ('@' | '$') => match chomp_private_tag_or_opaque(c == '@', buffer, pos) { + '@' => match chomp_opaque_ref(buffer, pos) { Ok(tagname) => { let bytes_parsed = tagname.len(); - let ident = if c == '@' { - Ident::PrivateTag - } else { - Ident::OpaqueRef - }; + let ident = Ident::OpaqueRef; return Ok((bytes_parsed as u32, ident(tagname))); } @@ -451,9 +418,9 @@ fn chomp_identifier_chain<'a>( BadIdent::Underscore(pos.bump_column(chomped as u32 + 1)), )) } else if first_is_uppercase { - // just one segment, starting with an uppercase letter; that's a global tag + // just one segment, starting with an uppercase letter; that's a tag let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; - Ok((chomped as u32, Ident::GlobalTag(value))) + Ok((chomped as u32, Ident::Tag(value))) } else { // just one segment, starting with a lowercase letter; that's a normal identifier let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 09e676075f..df09c1cb73 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -213,10 +213,10 @@ fn loc_ident_pattern_help<'a>( specialize(|_, pos| EPattern::Start(pos), loc!(parse_ident)).parse(arena, state)?; match loc_ident.value { - Ident::GlobalTag(tag) => { + Ident::Tag(tag) => { let loc_tag = Loc { region: loc_ident.region, - value: Pattern::GlobalTag(tag), + value: Pattern::Tag(tag), }; // Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)` @@ -240,14 +240,10 @@ fn loc_ident_pattern_help<'a>( Ok((MadeProgress, loc_tag, state)) } } - Ident::PrivateTag(name) | Ident::OpaqueRef(name) => { + Ident::OpaqueRef(name) => { let loc_pat = Loc { region: loc_ident.region, - value: if matches!(loc_ident.value, Ident::PrivateTag(..)) { - Pattern::PrivateTag(name) - } else { - Pattern::OpaqueRef(name) - }, + value: Pattern::OpaqueRef(name), }; // Make sure `@Foo Bar 1` is parsed as `@Foo (Bar) 1`, and not `@Foo (Bar 1)` diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 45b34b7157..f7e90ac1e0 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -214,16 +214,9 @@ fn tag_type<'a>(min_indent: u32) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> let (_, args, state) = specialize_ref(ETypeTagUnion::Type, loc_applied_args_e(min_indent)) .parse(arena, state)?; - let result = if name.value.starts_with('@') { - Tag::Private { - name, - args: args.into_bump_slice(), - } - } else { - Tag::Global { - name, - args: args.into_bump_slice(), - } + let result = Tag::Apply { + name, + args: args.into_bump_slice(), }; Ok((MadeProgress, result, state)) @@ -482,6 +475,8 @@ fn expression<'a>( space0_before_e(term(min_indent), min_indent, EType::TIndentStart) .parse(arena, state)?; + let region = Region::span_across(&first.region, &return_type.region); + // prepare arguments let mut arguments = Vec::with_capacity_in(rest.len() + 1, arena); arguments.push(first); @@ -489,7 +484,7 @@ fn expression<'a>( let output = arena.alloc(arguments); let result = Loc { - region: return_type.region, + region, value: TypeAnnotation::Function(output, arena.alloc(return_type)), }; let progress = p1.or(p2).or(p3); diff --git a/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast b/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast index 103b1338b6..89c0bd7b00 100644 --- a/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast @@ -15,7 +15,7 @@ Defs( Newline, ], ), - typ: @33-36 Function( + typ: @18-36 Function( [ @18-19 BoundVariable( "a", diff --git a/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.result-ast b/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.result-ast index 1d7fd2505b..c650e71f0e 100644 --- a/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ability_multi_line.expr.result-ast @@ -15,7 +15,7 @@ Defs( Newline, ], ), - typ: @23-26 Function( + typ: @18-26 Function( [ @18-19 BoundVariable( "a", @@ -35,7 +35,7 @@ Defs( Newline, ], ), - typ: @42-45 Function( + typ: @37-45 Function( [ @37-38 BoundVariable( "a", diff --git a/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast b/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast index 4ce7c5f245..f97c020789 100644 --- a/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast @@ -10,8 +10,8 @@ Defs( members: [ AbilityMember { name: @9-13 "hash", - typ: @21-37 Where( - @21-24 Function( + typ: @16-37 Where( + @16-24 Function( [ @16-17 BoundVariable( "a", diff --git a/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast b/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast index a9a00da02a..05f8825676 100644 --- a/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast @@ -10,8 +10,8 @@ Defs( members: [ AbilityMember { name: @8-11 "ab1", - typ: @19-33 Where( - @19-21 Function( + typ: @14-33 Where( + @14-21 Function( [ @14-15 BoundVariable( "a", @@ -47,8 +47,8 @@ Defs( members: [ AbilityMember { name: @43-46 "ab2", - typ: @54-68 Where( - @54-56 Function( + typ: @49-68 Where( + @49-56 Function( [ @49-50 BoundVariable( "a", diff --git a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast index 54d3d56502..eec49d33ac 100644 --- a/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast @@ -3,7 +3,7 @@ Defs( @26-46 Value( AnnotatedBody { ann_pattern: @0-8 Apply( - @0-6 GlobalTag( + @0-6 Tag( "UserId", ), [ @@ -15,7 +15,7 @@ Defs( ann_type: @11-25 TagUnion { ext: None, tags: [ - @13-23 Global { + @13-23 Apply { name: @13-19 "UserId", args: [ @20-23 Apply( @@ -29,7 +29,7 @@ Defs( }, comment: None, body_pattern: @26-34 Apply( - @26-32 GlobalTag( + @26-32 Tag( "UserId", ), [ @@ -39,7 +39,7 @@ Defs( ], ), body_expr: @37-46 Apply( - @37-43 GlobalTag( + @37-43 Tag( "UserId", ), [ diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.result-ast similarity index 92% rename from compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast rename to compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.result-ast index 483292bdcf..af657692b6 100644 --- a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.result-ast @@ -1,5 +1,5 @@ Apply( - @0-4 GlobalTag( + @0-4 Tag( "Whee", ), [ diff --git a/compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.roc b/compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.roc similarity index 100% rename from compiler/parse/tests/snapshots/pass/apply_parenthetical_global_tag_args.expr.roc rename to compiler/parse/tests/snapshots/pass/apply_parenthetical_tag_args.expr.roc diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast deleted file mode 100644 index 185e681f5b..0000000000 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.result-ast +++ /dev/null @@ -1,14 +0,0 @@ -Apply( - @0-5 PrivateTag( - "@Whee", - ), - [ - @6-8 Num( - "12", - ), - @9-11 Num( - "34", - ), - ], - Space, -) diff --git a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc b/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc deleted file mode 100644 index ba23819345..0000000000 --- a/compiler/parse/tests/snapshots/pass/apply_private_tag.expr.roc +++ /dev/null @@ -1 +0,0 @@ -@Whee 12 34 \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/apply_tag.expr.result-ast similarity index 88% rename from compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast rename to compiler/parse/tests/snapshots/pass/apply_tag.expr.result-ast index f939664412..6557b3805f 100644 --- a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/apply_tag.expr.result-ast @@ -1,5 +1,5 @@ Apply( - @0-4 GlobalTag( + @0-4 Tag( "Whee", ), [ diff --git a/compiler/parse/tests/snapshots/pass/apply_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/apply_tag.expr.roc similarity index 100% rename from compiler/parse/tests/snapshots/pass/apply_global_tag.expr.roc rename to compiler/parse/tests/snapshots/pass/apply_tag.expr.roc diff --git a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast deleted file mode 100644 index 1761f76aa1..0000000000 --- a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.result-ast +++ /dev/null @@ -1,3 +0,0 @@ -PrivateTag( - "@Whee", -) diff --git a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc b/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc deleted file mode 100644 index 476a77dfc2..0000000000 --- a/compiler/parse/tests/snapshots/pass/basic_private_tag.expr.roc +++ /dev/null @@ -1 +0,0 @@ -@Whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/basic_tag.expr.result-ast similarity index 56% rename from compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast rename to compiler/parse/tests/snapshots/pass/basic_tag.expr.result-ast index 8c0a66666f..bed3b6ff1d 100644 --- a/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/basic_tag.expr.result-ast @@ -1,3 +1,3 @@ -GlobalTag( +Tag( "Whee", ) diff --git a/compiler/parse/tests/snapshots/pass/basic_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/basic_tag.expr.roc similarity index 100% rename from compiler/parse/tests/snapshots/pass/basic_global_tag.expr.roc rename to compiler/parse/tests/snapshots/pass/basic_tag.expr.roc diff --git a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast index 4cdfd0b739..95c8fc9192 100644 --- a/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast @@ -3,7 +3,7 @@ Defs( @0-36 Value( Body( @0-5 Apply( - @0-5 GlobalTag( + @0-5 Tag( "Email", ), [ @@ -13,7 +13,7 @@ Defs( ], ), @12-36 Apply( - @12-17 GlobalTag( + @12-17 Tag( "Email", ), [ diff --git a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast index adec062681..3c649e6647 100644 --- a/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/nested_def_annotation.module.result-ast @@ -14,7 +14,7 @@ ann_pattern: @11-23 Identifier( "wrappedNotEq", ), - ann_type: @34-38 Function( + ann_type: @26-38 Function( [ @26-27 BoundVariable( "a", diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast index 10d11945aa..4dfbe2dc65 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.result-ast @@ -1,3 +1,3 @@ OpaqueRef( - "$Age", + "@Age", ) diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc index fd75aeaae1..6783cae16a 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr.expr.roc @@ -1 +1 @@ -$Age +@Age diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast index b9db6ccfa8..c42661df16 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.result-ast @@ -1,6 +1,6 @@ Apply( @0-4 OpaqueRef( - "$Age", + "@Age", ), [ @5-6 Var { diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc index e8b6053bba..c7a719c935 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_expr_with_arguments.expr.roc @@ -1 +1 @@ -$Age m n +@Age m n diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast index 41f8660339..0dd1a3462e 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.result-ast @@ -8,7 +8,7 @@ When( patterns: [ @12-16 SpaceBefore( OpaqueRef( - "$Age", + "@Age", ), [ Newline, diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc index 49693af6b1..c18631c5fc 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern.expr.roc @@ -1,2 +1,2 @@ when n is - $Age -> 1 + @Age -> 1 diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast index 6f159172e8..a572677762 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.result-ast @@ -9,7 +9,7 @@ When( @12-20 SpaceBefore( Apply( @12-16 OpaqueRef( - "$Add", + "@Add", ), [ @17-18 Identifier( diff --git a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc index d5fcf3be4b..8ee3928e79 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc +++ b/compiler/parse/tests/snapshots/pass/opaque_reference_pattern_with_arguments.expr.roc @@ -1,2 +1,2 @@ when n is - $Add n m -> n + m + @Add n m -> n + m diff --git a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast index d45bbdf656..9395ab1ab7 100644 --- a/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/pattern_with_space_in_parens.expr.result-ast @@ -1,12 +1,12 @@ When( @5-22 Apply( - @5-11 GlobalTag( + @5-11 Tag( "Delmin", ), [ @13-19 ParensAround( Apply( - @13-16 GlobalTag( + @13-16 Tag( "Del", ), [ @@ -29,12 +29,12 @@ When( patterns: [ @30-48 SpaceBefore( Apply( - @30-36 GlobalTag( + @30-36 Tag( "Delmin", ), [ @38-44 Apply( - @38-41 GlobalTag( + @38-41 Tag( "Del", ), [ @@ -54,17 +54,17 @@ When( ), ], value: @52-73 Apply( - @52-56 GlobalTag( + @52-56 Tag( "Node", ), [ - @57-62 GlobalTag( + @57-62 Tag( "Black", ), @63-64 Num( "0", ), - @65-70 GlobalTag( + @65-70 Tag( "False", ), @71-73 Var { diff --git a/compiler/parse/tests/snapshots/pass/plus_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/plus_if.expr.result-ast new file mode 100644 index 0000000000..e8a3539470 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/plus_if.expr.result-ast @@ -0,0 +1,25 @@ +BinOps( + [ + ( + @0-1 Num( + "1", + ), + @2-3 Star, + ), + ], + @4-25 If( + [ + ( + @7-11 Tag( + "True", + ), + @17-18 Num( + "1", + ), + ), + ], + @24-25 Num( + "1", + ), + ), +) diff --git a/compiler/parse/tests/snapshots/pass/plus_if.expr.roc b/compiler/parse/tests/snapshots/pass/plus_if.expr.roc new file mode 100644 index 0000000000..50a84b0a4d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/plus_if.expr.roc @@ -0,0 +1 @@ +1 * if True then 1 else 1 diff --git a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast deleted file mode 100644 index b770fe6077..0000000000 --- a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.result-ast +++ /dev/null @@ -1,6 +0,0 @@ -MalformedIdent( - "@One.Two.Whee", - BadPrivateTag( - @4, - ), -) diff --git a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc b/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc deleted file mode 100644 index e5c825a346..0000000000 --- a/compiler/parse/tests/snapshots/pass/private_qualified_tag.expr.roc +++ /dev/null @@ -1 +0,0 @@ -@One.Two.Whee \ No newline at end of file diff --git a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast b/compiler/parse/tests/snapshots/pass/qualified_tag.expr.result-ast similarity index 100% rename from compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.result-ast rename to compiler/parse/tests/snapshots/pass/qualified_tag.expr.result-ast diff --git a/compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.roc b/compiler/parse/tests/snapshots/pass/qualified_tag.expr.roc similarity index 100% rename from compiler/parse/tests/snapshots/pass/qualified_global_tag.expr.roc rename to compiler/parse/tests/snapshots/pass/qualified_tag.expr.roc diff --git a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast index 289456aed3..4f2719f360 100644 --- a/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_func_type_decl.expr.result-ast @@ -32,7 +32,7 @@ Defs( RequiredValue( @48-55 "putLine", [], - @65-75 Function( + @58-75 Function( [ @58-61 Apply( "", diff --git a/compiler/parse/tests/snapshots/pass/record_type_with_function.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_type_with_function.expr.result-ast new file mode 100644 index 0000000000..82720a8cee --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/record_type_with_function.expr.result-ast @@ -0,0 +1,83 @@ +Defs( + [ + @0-77 Value( + Annotation( + @0-1 Identifier( + "x", + ), + @4-77 Record { + fields: [ + @6-24 RequiredValue( + @6-10 "init", + [], + @13-24 Function( + [ + @13-15 Record { + fields: [], + ext: None, + }, + ], + @19-24 Apply( + "", + "Model", + [], + ), + ), + ), + @26-54 RequiredValue( + @26-32 "update", + [], + @35-54 Function( + [ + @35-40 Apply( + "", + "Model", + [], + ), + @42-45 Apply( + "", + "Str", + [], + ), + ], + @49-54 Apply( + "", + "Model", + [], + ), + ), + ), + @56-75 RequiredValue( + @56-60 "view", + [], + @63-75 Function( + [ + @63-68 Apply( + "", + "Model", + [], + ), + ], + @72-75 Apply( + "", + "Str", + [], + ), + ), + ), + ], + ext: None, + }, + ), + ), + ], + @79-81 SpaceBefore( + Num( + "42", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/record_type_with_function.expr.roc b/compiler/parse/tests/snapshots/pass/record_type_with_function.expr.roc new file mode 100644 index 0000000000..3ce2213720 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/record_type_with_function.expr.roc @@ -0,0 +1,3 @@ +x : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } + +42 diff --git a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast index 1776ed2ff8..5301d17702 100644 --- a/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/record_with_if.expr.result-ast @@ -6,7 +6,7 @@ Record( @5-26 If( [ ( - @8-12 GlobalTag( + @8-12 Tag( "True", ), @18-19 Num( diff --git a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast index 93bc481021..0b8a1d1b48 100644 --- a/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/tag_pattern.expr.result-ast @@ -1,6 +1,6 @@ Closure( [ - @1-6 GlobalTag( + @1-6 Tag( "Thing", ), ], diff --git a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast index bd43d845a3..4575da7337 100644 --- a/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast @@ -5,7 +5,7 @@ Defs( @0-7 Identifier( "doStuff", ), - @20-30 Function( + @10-30 Function( [ @10-16 Apply( "", diff --git a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast index e0f8d41ece..bdd18adf88 100644 --- a/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_if_guard.expr.result-ast @@ -50,7 +50,7 @@ When( WhenBranch { patterns: [ @54-56 SpaceBefore( - GlobalTag( + Tag( "Ok", ), [ diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast index 9192d1f1b6..2844887e56 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens.expr.result-ast @@ -8,7 +8,7 @@ ParensAround( WhenBranch { patterns: [ @15-17 SpaceBefore( - GlobalTag( + Tag( "Ok", ), [ diff --git a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast index 6f74fb5e4d..419e8c07ad 100644 --- a/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/when_in_parens_indented.expr.result-ast @@ -9,7 +9,7 @@ ParensAround( WhenBranch { patterns: [ @15-17 SpaceBefore( - GlobalTag( + Tag( "Ok", ), [ diff --git a/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast index 9f9d62c54f..d404a9cd7a 100644 --- a/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast @@ -5,14 +5,14 @@ Defs( @0-1 Identifier( "f", ), - @15-27 Where( - @15-16 Function( + @4-27 Where( + @4-16 Function( [ @4-5 BoundVariable( "a", ), ], - @15-16 Function( + @10-16 Function( [ @10-11 BoundVariable( "b", diff --git a/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast index b823357d7e..753ced4c9f 100644 --- a/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast @@ -5,14 +5,14 @@ Defs( @0-1 Identifier( "f", ), - @15-48 Where( - @15-16 Function( + @4-48 Where( + @4-16 Function( [ @4-5 BoundVariable( "a", ), ], - @15-16 Function( + @10-16 Function( [ @10-11 BoundVariable( "b", diff --git a/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast index 226a3edb57..2a5ad71478 100644 --- a/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast @@ -5,15 +5,15 @@ Defs( @0-1 Identifier( "f", ), - @15-67 Where( - @15-16 SpaceBefore( + @4-67 Where( + @4-16 SpaceBefore( Function( [ @4-5 BoundVariable( "a", ), ], - @15-16 Function( + @10-16 Function( [ @10-11 BoundVariable( "b", diff --git a/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast b/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast index 29ac20747f..d554d5cf2b 100644 --- a/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast @@ -5,8 +5,8 @@ Defs( @0-1 Identifier( "f", ), - @9-29 Where( - @9-12 SpaceBefore( + @4-29 Where( + @4-12 SpaceBefore( Function( [ @4-5 BoundVariable( diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 759276523c..4db9b53768 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -77,7 +77,7 @@ mod test_parse { 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"); + assert!(res == "pass" || res == "fail", "a pass or fail filename was neither \"pass\" nor \"fail\", but rather: {:?}", res); let res_dir = base.join(&res); for file in list(&res_dir) { let test = if let Some(test) = file.strip_suffix(".roc") { @@ -122,6 +122,7 @@ mod test_parse { snapshot_tests! { fail/type_argument_no_arrow.expr, fail/type_double_comma.expr, + pass/plus_if.expr, pass/list_closing_indent_not_enough.expr, pass/ability_single_line.expr, pass/ability_multi_line.expr, @@ -131,9 +132,8 @@ mod test_parse { pass/add_with_spaces.expr, pass/annotated_record_destructure.expr, pass/annotated_tag_destructure.expr, - pass/apply_global_tag.expr, - pass/apply_parenthetical_global_tag_args.expr, - pass/apply_private_tag.expr, + pass/apply_tag.expr, + pass/apply_parenthetical_tag_args.expr, pass/apply_three_args.expr, pass/apply_two_args.expr, pass/apply_unary_negation.expr, @@ -141,8 +141,7 @@ mod test_parse { pass/basic_apply.expr, pass/basic_docs.expr, pass/basic_field.expr, - pass/basic_global_tag.expr, - pass/basic_private_tag.expr, + pass/basic_tag.expr, pass/basic_var.expr, pass/closure_with_underscores.expr, pass/comment_after_def.module, @@ -162,6 +161,7 @@ mod test_parse { pass/equals_with_spaces.expr, pass/equals.expr, pass/expect.expr, + pass/record_type_with_function.expr, pass/float_with_underscores.expr, pass/full_app_header_trailing_commas.header, pass/full_app_header.header, @@ -232,10 +232,9 @@ mod test_parse { pass/pos_inf_float.expr, pass/positive_float.expr, pass/positive_int.expr, - pass/private_qualified_tag.expr, pass/provides_type.header, pass/qualified_field.expr, - pass/qualified_global_tag.expr, + pass/qualified_tag.expr, pass/qualified_var.expr, pass/record_destructure_def.expr, pass/record_func_type_decl.expr, @@ -301,7 +300,12 @@ mod test_parse { let input_path = parent.join(&format!("{}.{}.roc", name, ty)); let result_path = parent.join(&format!("{}.{}.result-ast", name, ty)); - let input = std::fs::read_to_string(&input_path).unwrap(); + let input = std::fs::read_to_string(&input_path).unwrap_or_else(|err| { + panic!( + "Could not find a snapshot test result at {:?} - {:?}", + input_path, err + ) + }); let result = func(&input); @@ -316,7 +320,17 @@ mod test_parse { if std::env::var("ROC_PARSER_SNAPSHOT_TEST_OVERWRITE").is_ok() { std::fs::write(&result_path, actual_result).unwrap(); } else { - let expected_result = std::fs::read_to_string(&result_path).unwrap(); + let expected_result = std::fs::read_to_string(&result_path).unwrap_or_else(|e| { + panic!( + "Error opening test output file {}:\n\ + {:?} + Supposing the file is missing, consider running the tests with:\n\ + `env ROC_PARSER_SNAPSHOT_TEST_OVERWRITE=1 cargo test ...`\n\ + and committing the file that creates.", + result_path.display(), + e + ); + }); assert_multiline_str_eq!(expected_result, actual_result); } @@ -646,125 +660,17 @@ mod test_parse { // } // #[test] - // fn ann_private_open_union() { + // fn ann_open_union() { // let arena = Bump::new(); // let newline = bumpalo::vec![in &arena; Newline]; // let newlines = bumpalo::vec![in &arena; Newline, Newline]; - // let tag1 = Tag::Private { - // name: Located::new(0, 0, 8, 13, "@True"), - // args: &[], - // }; - // let tag2arg1 = Located::new(0, 0, 24, 27, TypeAnnotation::Apply("", "Two", &[])); - // let tag2arg2 = Located::new(0, 0, 28, 34, TypeAnnotation::Apply("", "Things", &[])); - // let tag2args = bumpalo::vec![in &arena; tag2arg1, tag2arg2]; - // let tag2 = Tag::Private { - // name: Located::new(0, 0, 15, 23, "@Perhaps"), - // args: tag2args.into_bump_slice(), - // }; - // let tags = bumpalo::vec![in &arena; - // Located::new(0, 0, 8, 13, tag1), - // Located::new(0, 0, 15, 34, tag2) - // ]; - // let loc_wildcard = Located::new(0, 0, 36, 37, TypeAnnotation::Wildcard); - // let applied_ann = TypeAnnotation::TagUnion { - // tags: tags.into_bump_slice(), - // ext: Some(arena.alloc(loc_wildcard)), - // }; - // let signature = Def::Annotation( - // Located::new(0, 0, 0, 3, Identifier("foo")), - // Located::new(0, 0, 6, 37, applied_ann), - // ); - // let def = Def::Body( - // arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - // arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), - // ); - // let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); - // let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); - - // let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); - // let defs = &[loc_ann, loc_def]; - // let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - // let loc_ret = Located::new(3, 3, 0, 2, ret); - // let expected = Defs(defs, arena.alloc(loc_ret)); - - // assert_parses_to( - // indoc!( - // r#" - // foo : [ @True, @Perhaps Two Things ]* - // foo = True - - // 42 - // "# - // ), - // expected, - // ); - // } - - // #[test] - // fn ann_private_closed_union() { - // let arena = Bump::new(); - // let newline = bumpalo::vec![in &arena; Newline]; - // let newlines = bumpalo::vec![in &arena; Newline, Newline]; - // let tag1 = Tag::Private { - // name: Located::new(0, 0, 8, 13, "@True"), - // args: &[], - // }; - // let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply("", "Thing", &[])); - // let tag2args = bumpalo::vec![in &arena; tag2arg]; - // let tag2 = Tag::Private { - // name: Located::new(0, 0, 15, 23, "@Perhaps"), - // args: tag2args.into_bump_slice(), - // }; - // let tags = bumpalo::vec![in &arena; - // Located::new(0, 0, 8, 13, tag1), - // Located::new(0, 0, 15, 29, tag2) - // ]; - // let applied_ann = TypeAnnotation::TagUnion { - // tags: tags.into_bump_slice(), - // ext: None, - // }; - // let signature = Def::Annotation( - // Located::new(0, 0, 0, 3, Identifier("foo")), - // Located::new(0, 0, 6, 31, applied_ann), - // ); - // let def = Def::Body( - // arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - // arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), - // ); - // let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); - // let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); - - // let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); - // let defs = &[loc_ann, loc_def]; - // let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); - // let loc_ret = Located::new(3, 3, 0, 2, ret); - // let expected = Defs(defs, arena.alloc(loc_ret)); - - // assert_parses_to( - // indoc!( - // r#" - // foo : [ @True, @Perhaps Thing ] - // foo = True - - // 42 - // "# - // ), - // expected, - // ); - // } - - // #[test] - // fn ann_global_open_union() { - // let arena = Bump::new(); - // let newline = bumpalo::vec![in &arena; Newline]; - // let newlines = bumpalo::vec![in &arena; Newline, Newline]; - // let tag1 = Tag::Global { + // let tag1 = Tag::Apply { // name: Located::new(0, 0, 8, 12, "True"), // args: &[], // }; // let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[])); // let tag2args = bumpalo::vec![in &arena; tag2arg]; - // let tag2 = Tag::Global { + // let tag2 = Tag::Apply { // name: Located::new(0, 0, 14, 21, "Perhaps"), // args: tag2args.into_bump_slice(), // }; @@ -783,7 +689,7 @@ mod test_parse { // ); // let def = Def::Body( // arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - // arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), + // arena.alloc(Located::new(1, 1, 6, 10, Expr::Tag("True"))), // ); // let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); // let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); @@ -808,17 +714,17 @@ mod test_parse { // } // #[test] - // fn ann_global_closed_union() { + // fn ann_closed_union() { // let arena = Bump::new(); // let newline = bumpalo::vec![in &arena; Newline]; // let newlines = bumpalo::vec![in &arena; Newline, Newline]; - // let tag1 = Tag::Global { + // let tag1 = Tag::Apply { // name: Located::new(0, 0, 8, 12, "True"), // args: &[], // }; // let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[])); // let tag2args = bumpalo::vec![in &arena; tag2arg]; - // let tag2 = Tag::Global { + // let tag2 = Tag::Apply { // name: Located::new(0, 0, 14, 21, "Perhaps"), // args: tag2args.into_bump_slice(), // }; @@ -836,7 +742,7 @@ mod test_parse { // ); // let def = Def::Body( // arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - // arena.alloc(Located::new(1, 1, 6, 10, Expr::GlobalTag("True"))), + // arena.alloc(Located::new(1, 1, 6, 10, Expr::Tag("True"))), // ); // let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); // let loc_def = &*arena.alloc(Located::new(1, 1, 0, 10, spaced_def)); diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index e28177345f..f8913b1b64 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -125,20 +125,11 @@ pub enum Problem { span_has_clauses: Region, bound_var_names: Vec, }, - // TODO(abilities): remove me when ability hierarchies are supported - AbilityMemberBindsExternalAbility { - member: Symbol, - ability: Symbol, - region: Region, - }, - AliasUsesAbility { - loc_name: Loc, - ability: Symbol, - }, AbilityNotOnToplevel { region: Region, }, AbilityUsedAsType(Lowercase, Symbol, Region), + NestedSpecialization(Symbol, Region), } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index e273cb2a10..3eed5060d5 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -377,6 +377,17 @@ impl LineInfo { end: self.convert_pos(region.end()), } } + + pub fn convert_line_column(&self, lc: LineColumn) -> Position { + let offset = self.line_offsets[lc.line as usize] + lc.column; + Position::new(offset) + } + + pub fn convert_line_column_region(&self, lc_region: LineColumnRegion) -> Region { + let start = self.convert_line_column(lc_region.start); + let end = self.convert_line_column(lc_region.end); + Region::new(start, end) + } } #[test] diff --git a/compiler/roc_target/Cargo.toml b/compiler/roc_target/Cargo.toml index 28211dbdc9..5ef3aba2d9 100644 --- a/compiler/roc_target/Cargo.toml +++ b/compiler/roc_target/Cargo.toml @@ -6,4 +6,4 @@ license = "UPL-1.0" edition = "2018" [dependencies] -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" diff --git a/compiler/roc_target/src/lib.rs b/compiler/roc_target/src/lib.rs index 5174d4082e..be98e06621 100644 --- a/compiler/roc_target/src/lib.rs +++ b/compiler/roc_target/src/lib.rs @@ -12,6 +12,10 @@ impl TargetInfo { self.architecture.ptr_width() } + pub const fn ptr_alignment_bytes(&self) -> usize { + self.architecture.ptr_alignment_bytes() + } + pub const fn default_aarch64() -> Self { TargetInfo { architecture: Architecture::Aarch64, @@ -64,6 +68,10 @@ impl Architecture { X86_32 | Wasm32 => PtrWidth::Bytes4, } } + + pub const fn ptr_alignment_bytes(&self) -> usize { + self.ptr_width() as usize + } } impl From for Architecture { diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index b78faa8421..d083f524fa 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -8,11 +8,13 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } roc_error_macros = { path = "../../error_macros" } +roc_exhaustive = { path = "../exhaustive" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } roc_can = { path = "../can" } roc_unify = { path = "../unify" } +roc_debug_flags = { path = "../debug_flags" } arrayvec = "0.7.2" bumpalo = { version = "3.8.0", features = ["collections"] } @@ -28,3 +30,5 @@ pretty_assertions = "1.0.0" indoc = "1.0.3" tempfile = "3.2.0" bumpalo = { version = "3.8.0", features = ["collections"] } +regex = "1.5.5" +lazy_static = "1.4.0" diff --git a/compiler/solve/src/ability.rs b/compiler/solve/src/ability.rs index a9796590cf..fc51b01d37 100644 --- a/compiler/solve/src/ability.rs +++ b/compiler/solve/src/ability.rs @@ -7,6 +7,7 @@ use roc_types::types::{Category, PatternCategory}; use roc_unify::unify::MustImplementAbility; use roc_unify::unify::MustImplementConstraints; +use crate::solve::instantiate_rigids; use crate::solve::{IncompleteAbilityImplementation, TypeError}; #[derive(Debug, Clone)] @@ -170,12 +171,12 @@ pub fn type_implementing_member( ability: Symbol, ) -> Symbol { debug_assert_eq!({ - let ability_implementations_for_specialization = - specialization_must_implement_constraints - .clone() - .get_unique(); - - ability_implementations_for_specialization.len() + specialization_must_implement_constraints + .clone() + .get_unique() + .into_iter() + .filter(|mia| mia.ability == ability) + .count() }, 1, "Multiple variables bound to an ability - this is ambiguous and should have been caught in canonicalization: {:?}", @@ -188,3 +189,32 @@ pub fn type_implementing_member( .unwrap() .typ } + +pub fn resolve_ability_specialization( + subs: &mut Subs, + abilities_store: &AbilitiesStore, + ability_member: Symbol, + specialization_var: Variable, +) -> Option { + use roc_unify::unify::{unify, Mode}; + + let member_def = abilities_store + .member_def(ability_member) + .expect("Not an ability member symbol"); + + let snapshot = subs.snapshot(); + instantiate_rigids(subs, member_def.signature_var); + let (_, must_implement_ability) = + unify(subs, specialization_var, member_def.signature_var, Mode::EQ).expect_success( + "If resolving a specialization, the specialization must be known to typecheck.", + ); + + subs.rollback_to(snapshot); + + let specializing_type = + type_implementing_member(&must_implement_ability, member_def.parent_ability); + + let specialization = abilities_store.get_specialization(ability_member, specializing_type)?; + + Some(specialization.symbol) +} diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index c0f5f7ef94..93570bb62e 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -40,8 +40,6 @@ pub fn run_solve( Vec, AbilitiesStore, ) { - let env = solve::Env::default(); - for (var, name) in rigid_variables.named { subs.rigid_var(var, name); } @@ -61,7 +59,6 @@ pub fn run_solve( // Run the solver to populate Subs. let (solved_subs, solved_env) = solve::run( constraints, - &env, &mut problems, subs, &mut aliases, diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index dbe6f50c5d..3af78043f6 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -5,6 +5,8 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::{Constraints, LetConstraint}; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::MutMap; +use roc_debug_flags::{dbg_do, ROC_VERIFY_RIGID_LET_GENERALIZED}; +use roc_error_macros::internal_error; use roc_module::ident::TagName; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; @@ -15,8 +17,8 @@ use roc_types::subs::{ }; use roc_types::types::Type::{self, *}; use roc_types::types::{ - gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory, - Reason, TypeExtension, + gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, OptAbleType, + OptAbleVar, PatternCategory, Reason, TypeExtension, }; use roc_unify::unify::{unify, Mode, Unified::*}; @@ -99,6 +101,7 @@ pub enum TypeError { ErrorType, Vec, ), + Exhaustive(roc_exhaustive::Error), } use roc_types::types::Alias; @@ -112,7 +115,7 @@ struct DelayedAliasVariables { } impl DelayedAliasVariables { - fn recursion_variables(self, variables: &mut [Variable]) -> &mut [Variable] { + fn recursion_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { let start = self.start as usize + (self.type_variables_len + self.lambda_set_variables_len) as usize; let length = self.recursion_variables_len as usize; @@ -120,14 +123,14 @@ impl DelayedAliasVariables { &mut variables[start..][..length] } - fn lambda_set_variables(self, variables: &mut [Variable]) -> &mut [Variable] { + fn lambda_set_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { let start = self.start as usize + self.type_variables_len as usize; let length = self.lambda_set_variables_len as usize; &mut variables[start..][..length] } - fn type_variables(self, variables: &mut [Variable]) -> &mut [Variable] { + fn type_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { let start = self.start as usize; let length = self.type_variables_len as usize; @@ -137,30 +140,38 @@ impl DelayedAliasVariables { #[derive(Debug, Default)] pub struct Aliases { - aliases: Vec<(Symbol, Type, DelayedAliasVariables)>, - variables: Vec, + aliases: Vec<(Symbol, Type, DelayedAliasVariables, AliasKind)>, + variables: Vec, } impl Aliases { pub fn insert(&mut self, symbol: Symbol, alias: Alias) { - // debug_assert!(self.get(&symbol).is_none()); - let alias_variables = { let start = self.variables.len() as _; - self.variables - .extend(alias.type_variables.iter().map(|x| x.value.1)); + self.variables.extend( + alias + .type_variables + .iter() + .map(|x| OptAbleVar::from(&x.value)), + ); self.variables.extend(alias.lambda_set_variables.iter().map( |x| match x.as_inner() { - Type::Variable(v) => *v, + Type::Variable(v) => OptAbleVar::unbound(*v), _ => unreachable!("lambda set type is not a variable"), }, )); let recursion_variables_len = alias.recursion_variables.len() as _; - self.variables.extend(alias.recursion_variables); + self.variables.extend( + alias + .recursion_variables + .iter() + .copied() + .map(OptAbleVar::unbound), + ); DelayedAliasVariables { start, @@ -170,7 +181,8 @@ impl Aliases { } }; - self.aliases.push((symbol, alias.typ, alias_variables)); + self.aliases + .push((symbol, alias.typ, alias_variables, alias.kind)); } fn instantiate_result_result( @@ -195,164 +207,84 @@ impl Aliases { register(subs, rank, pools, content) } - /// Instantiate an alias of the form `Foo a : [ @Foo a ]` - fn instantiate_num_at_alias( + /// Build an alias of the form `Num range := range` + fn build_num_opaque( subs: &mut Subs, rank: Rank, pools: &mut Pools, - tag_name_slice: SubsSlice, - range_slice: SubsSlice, + symbol: Symbol, + range_var: Variable, ) -> Variable { - let variable_slices = SubsSlice::extend_new(&mut subs.variable_slices, [range_slice]); - - let union_tags = UnionTags::from_slices(tag_name_slice, variable_slices); - let ext_var = Variable::EMPTY_TAG_UNION; - let flat_type = FlatType::TagUnion(union_tags, ext_var); - let content = Content::Structure(flat_type); + let content = Content::Alias( + symbol, + AliasVariables::insert_into_subs(subs, [range_var], []), + range_var, + AliasKind::Opaque, + ); register(subs, rank, pools, content) } - fn instantiate_builtin_aliases( + fn instantiate_builtin_aliases_real_var( &mut self, subs: &mut Subs, rank: Rank, pools: &mut Pools, symbol: Symbol, alias_variables: AliasVariables, - ) -> Option { + ) -> Option<(Variable, AliasKind)> { match symbol { Symbol::RESULT_RESULT => { let var = Self::instantiate_result_result(subs, rank, pools, alias_variables); - Some(var) + Some((var, AliasKind::Structural)) } - Symbol::NUM_NUM => { - let var = Self::instantiate_num_at_alias( - subs, - rank, - pools, - Subs::NUM_AT_NUM, - SubsSlice::new(alias_variables.variables_start, 1), - ); - - Some(var) - } - Symbol::NUM_FLOATINGPOINT => { - let var = Self::instantiate_num_at_alias( - subs, - rank, - pools, - Subs::NUM_AT_FLOATINGPOINT, - SubsSlice::new(alias_variables.variables_start, 1), - ); - - Some(var) - } - Symbol::NUM_INTEGER => { - let var = Self::instantiate_num_at_alias( - subs, - rank, - pools, - Subs::NUM_AT_INTEGER, - SubsSlice::new(alias_variables.variables_start, 1), - ); - - Some(var) + Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT => { + // Num range := range | Integer range := range | FloatingPoint range := range + let range_var = subs.variables[alias_variables.variables_start as usize]; + Some((range_var, AliasKind::Opaque)) } Symbol::NUM_INT => { - // [ @Integer range ] - let integer_content_var = Self::instantiate_builtin_aliases( - self, + // Int range : Num (Integer range) + // + // build `Integer range := range` + let integer_content_var = Self::build_num_opaque( subs, rank, pools, Symbol::NUM_INTEGER, - alias_variables, - ) - .unwrap(); - - // Integer range (alias variable is the same as `Int range`) - let integer_alias_variables = alias_variables; - let integer_content = Content::Alias( - Symbol::NUM_INTEGER, - integer_alias_variables, - integer_content_var, - AliasKind::Structural, - ); - let integer_alias_var = register(subs, rank, pools, integer_content); - - // [ @Num (Integer range) ] - let num_alias_variables = - AliasVariables::insert_into_subs(subs, [integer_alias_var], []); - let num_content_var = Self::instantiate_builtin_aliases( - self, - subs, - rank, - pools, - Symbol::NUM_NUM, - num_alias_variables, - ) - .unwrap(); - - let num_content = Content::Alias( - Symbol::NUM_NUM, - num_alias_variables, - num_content_var, - AliasKind::Structural, + subs.variables[alias_variables.variables_start as usize], ); - Some(register(subs, rank, pools, num_content)) + // build `Num (Integer range) := Integer range` + let num_content_var = + Self::build_num_opaque(subs, rank, pools, Symbol::NUM_NUM, integer_content_var); + + Some((num_content_var, AliasKind::Structural)) } - Symbol::NUM_FLOAT => { - // [ @FloatingPoint range ] - let fpoint_content_var = Self::instantiate_builtin_aliases( - self, + Symbol::NUM_FRAC => { + // Frac range : Num (FloatingPoint range) + // + // build `FloatingPoint range := range` + let fpoint_content_var = Self::build_num_opaque( subs, rank, pools, Symbol::NUM_FLOATINGPOINT, - alias_variables, - ) - .unwrap(); - - // FloatingPoint range (alias variable is the same as `Float range`) - let fpoint_alias_variables = alias_variables; - let fpoint_content = Content::Alias( - Symbol::NUM_FLOATINGPOINT, - fpoint_alias_variables, - fpoint_content_var, - AliasKind::Structural, - ); - let fpoint_alias_var = register(subs, rank, pools, fpoint_content); - - // [ @Num (FloatingPoint range) ] - let num_alias_variables = - AliasVariables::insert_into_subs(subs, [fpoint_alias_var], []); - let num_content_var = Self::instantiate_builtin_aliases( - self, - subs, - rank, - pools, - Symbol::NUM_NUM, - num_alias_variables, - ) - .unwrap(); - - let num_content = Content::Alias( - Symbol::NUM_NUM, - num_alias_variables, - num_content_var, - AliasKind::Structural, + subs.variables[alias_variables.variables_start as usize], ); - Some(register(subs, rank, pools, num_content)) + // build `Num (FloatingPoint range) := FloatingPoint range` + let num_content_var = + Self::build_num_opaque(subs, rank, pools, Symbol::NUM_NUM, fpoint_content_var); + + Some((num_content_var, AliasKind::Structural)) } _ => None, } } - fn instantiate( + fn instantiate_real_var( &mut self, subs: &mut Subs, rank: Rank, @@ -360,26 +292,35 @@ impl Aliases { arena: &bumpalo::Bump, symbol: Symbol, alias_variables: AliasVariables, - ) -> Result { + ) -> Result<(Variable, AliasKind), ()> { // hardcoded instantiations for builtin aliases - if let Some(var) = - Self::instantiate_builtin_aliases(self, subs, rank, pools, symbol, alias_variables) - { - return Ok(var); + if let Some((var, kind)) = Self::instantiate_builtin_aliases_real_var( + self, + subs, + rank, + pools, + symbol, + alias_variables, + ) { + return Ok((var, kind)); } - let (typ, delayed_variables) = match self.aliases.iter_mut().find(|(s, _, _)| *s == symbol) - { - None => return Err(()), - Some((_, typ, delayed_variables)) => (typ, delayed_variables), - }; + let (typ, delayed_variables, &mut kind) = + match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) { + None => return Err(()), + Some((_, typ, delayed_variables, kind)) => (typ, delayed_variables, kind), + }; let mut substitutions: MutMap<_, _> = Default::default(); - for rec_var in delayed_variables + for OptAbleVar { + var: rec_var, + opt_ability, + } in delayed_variables .recursion_variables(&mut self.variables) .iter_mut() { + debug_assert!(opt_ability.is_none()); let new_var = subs.fresh_unnamed_flex_var(); substitutions.insert(*rec_var, new_var); *rec_var = new_var; @@ -391,10 +332,10 @@ impl Aliases { for (old, new) in old_type_variables.iter_mut().zip(new_type_variables) { // if constraint gen duplicated a type these variables could be the same // (happens very often in practice) - if *old != *new { - substitutions.insert(*old, *new); + if old.var != *new { + substitutions.insert(old.var, *new); - *old = *new; + old.var = *new; } } @@ -406,9 +347,10 @@ impl Aliases { .iter_mut() .zip(new_lambda_set_variables) { - if *old != *new { - substitutions.insert(*old, *new); - *old = *new; + debug_assert!(old.opt_ability.is_none()); + if old.var != *new { + substitutions.insert(old.var, *new); + old.var = *new; } } @@ -416,25 +358,26 @@ impl Aliases { typ.substitute_variables(&substitutions); } - // assumption: an alias does not (transitively) syntactically contain itself - // (if it did it would have to be a recursive tag union) let mut t = Type::EmptyRec; std::mem::swap(typ, &mut t); + // assumption: an alias does not (transitively) syntactically contain itself + // (if it did it would have to be a recursive tag union, which we should have fixed up + // during canonicalization) let alias_variable = type_to_variable(subs, rank, pools, arena, self, &t); { - match self.aliases.iter_mut().find(|(s, _, _)| *s == symbol) { + match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) { None => unreachable!(), - Some((_, typ, _)) => { + Some((_, typ, _, _)) => { // swap typ back std::mem::swap(typ, &mut t); } } } - Ok(alias_variable) + Ok((alias_variable, kind)) } } @@ -513,10 +456,13 @@ impl Pools { self.0.iter() } - pub fn split_last(&self) -> (&Vec, &[Vec]) { - self.0 - .split_last() - .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")) + pub fn split_last(mut self) -> (Vec, Vec>) { + let last = self + .0 + .pop() + .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")); + + (last, self.0) } pub fn extend_to(&mut self, n: usize) { @@ -534,7 +480,6 @@ struct State { pub fn run( constraints: &Constraints, - env: &Env, problems: &mut Vec, mut subs: Subs, aliases: &mut Aliases, @@ -543,7 +488,6 @@ pub fn run( ) -> (Solved, Env) { let env = run_in_place( constraints, - env, problems, &mut subs, aliases, @@ -555,9 +499,8 @@ pub fn run( } /// Modify an existing subs in-place instead -pub fn run_in_place( +fn run_in_place( constraints: &Constraints, - env: &Env, problems: &mut Vec, subs: &mut Subs, aliases: &mut Aliases, @@ -567,11 +510,10 @@ pub fn run_in_place( let mut pools = Pools::default(); let state = State { - env: env.clone(), + env: Env::default(), mark: Mark::NONE.next(), }; let rank = Rank::toplevel(); - let arena = Bump::new(); let mut deferred_must_implement_abilities = DeferredMustImplementAbility::default(); @@ -579,7 +521,6 @@ pub fn run_in_place( let state = solve( &arena, constraints, - env, state, rank, &mut pools, @@ -638,7 +579,6 @@ enum Work<'a> { fn solve( arena: &Bump, constraints: &Constraints, - env: &Env, mut state: State, rank: Rank, pools: &mut Pools, @@ -650,7 +590,7 @@ fn solve( deferred_must_implement_abilities: &mut DeferredMustImplementAbility, ) -> State { let initial = Work::Constraint { - env, + env: &Env::default(), rank, constraint, }; @@ -756,6 +696,8 @@ fn solve( pools.get_mut(next_rank).extend(pool_variables); debug_assert_eq!( + // Check that no variable ended up in a higher rank than the next rank.. that + // would mean we generalized one level more than we need to! { let offenders = pools .get(next_rank) @@ -778,11 +720,10 @@ fn solve( // pop pool generalize(subs, young_mark, visit_mark, next_rank, pools); - - pools.get_mut(next_rank).clear(); + debug_assert!(pools.get(next_rank).is_empty()); // check that things went well - debug_assert!({ + dbg_do!(ROC_VERIFY_RIGID_LET_GENERALIZED, { let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; // NOTE the `subs.redundant` check does not come from elm. @@ -798,11 +739,7 @@ fn solve( let failing: Vec<_> = it.collect(); println!("Rigids {:?}", &rigid_vars); println!("Failing {:?}", failing); - - // nicer error message - failing.is_empty() - } else { - true + debug_assert!(false); } }); @@ -854,7 +791,7 @@ fn solve( copy } - Eq(type_index, expectation_index, category_index, region) => { + Eq(roc_can::constraint::Eq(type_index, expectation_index, category_index, region)) => { let category = &constraints.categories[category_index.index()]; let actual = @@ -1183,26 +1120,9 @@ fn solve( let actual = either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index); - let mut new_desc = subs.get(actual); - match new_desc.content { - Content::Structure(FlatType::TagUnion(tags, _)) => { - let new_ext = subs.fresh_unnamed_flex_var(); - subs.set_rank(new_ext, new_desc.rank); - let new_union = Content::Structure(FlatType::TagUnion(tags, new_ext)); - new_desc.content = new_union; - subs.set(actual, new_desc); - state - } - _ => { - // Today, an "open" constraint doesn't affect any types - // other than tag unions. Recursive tag unions are constructed - // at a later time (during occurs checks after tag unions are - // resolved), so that's not handled here either. - // NB: Handle record types here if we add presence constraints - // to their type inference as well. - state - } - } + open_tag_union(subs, actual); + + state } IncludesTag(index) => { let includes_tag = &constraints.includes_tags[index.index()]; @@ -1267,12 +1187,217 @@ fn solve( } } } + &Exhaustive(eq, sketched_rows, context, exhaustive_mark) => { + // A few cases: + // 1. Either condition or branch types already have a type error. In this case just + // propagate it. + // 2. Types are correct, but there are redundancies. In this case we want + // exhaustiveness checking to pull those out. + // 3. Condition and branch types are "almost equal", that is one or the other is + // only missing a few more tags. In this case we want to run + // exhaustiveness checking both ways, to see which one is missing tags. + // 4. Condition and branch types aren't "almost equal", this is just a normal type + // error. + + let (real_var, real_region, expected_type, category_and_expected) = match eq { + Ok(eq) => { + let roc_can::constraint::Eq(real_var, expected, category, real_region) = + constraints.eq[eq.index()]; + let expected = &constraints.expectations[expected.index()]; + ( + real_var, + real_region, + expected.get_type_ref(), + Ok((category, expected)), + ) + } + Err(peq) => { + let roc_can::constraint::PatternEq( + real_var, + expected, + category, + real_region, + ) = constraints.pattern_eq[peq.index()]; + let expected = &constraints.pattern_expectations[expected.index()]; + ( + real_var, + real_region, + expected.get_type_ref(), + Err((category, expected)), + ) + } + }; + + let real_var = + either_type_index_to_var(constraints, subs, rank, pools, aliases, real_var); + + let branches_var = type_to_var(subs, rank, pools, aliases, expected_type); + + let real_content = subs.get_content_without_compacting(real_var); + let branches_content = subs.get_content_without_compacting(branches_var); + let already_have_error = matches!( + (real_content, branches_content), + ( + Content::Error | Content::Structure(FlatType::Erroneous(_)), + _ + ) | ( + _, + Content::Error | Content::Structure(FlatType::Erroneous(_)) + ) + ); + + let snapshot = subs.snapshot(); + let outcome = unify(subs, real_var, branches_var, Mode::EQ); + + let should_check_exhaustiveness; + match outcome { + Success { + vars, + must_implement_ability, + } => { + subs.commit_snapshot(snapshot); + + introduce(subs, rank, pools, &vars); + if !must_implement_ability.is_empty() { + internal_error!("Didn't expect ability vars to land here"); + } + + // Case 1: unify error types, but don't check exhaustiveness. + // Case 2: run exhaustiveness to check for redundant branches. + should_check_exhaustiveness = !already_have_error; + } + Failure(..) => { + // Rollback and check for almost-equality. + subs.rollback_to(snapshot); + + let almost_eq_snapshot = subs.snapshot(); + // TODO: turn this on for bidirectional exhaustiveness checking + // open_tag_union(subs, real_var); + open_tag_union(subs, branches_var); + let almost_eq = matches!( + unify(subs, real_var, branches_var, Mode::EQ), + Success { .. } + ); + + subs.rollback_to(almost_eq_snapshot); + + if almost_eq { + // Case 3: almost equal, check exhaustiveness. + should_check_exhaustiveness = true; + } else { + // Case 4: incompatible types, report type error. + // Re-run first failed unification to get the type diff. + match unify(subs, real_var, branches_var, Mode::EQ) { + Failure(vars, actual_type, expected_type, _bad_impls) => { + introduce(subs, rank, pools, &vars); + + // Figure out the problem - it might be pattern or value + // related. + let problem = match category_and_expected { + Ok((category, expected)) => { + let real_category = + constraints.categories[category.index()].clone(); + TypeError::BadExpr( + real_region, + real_category, + actual_type, + expected.replace_ref(expected_type), + ) + } + + Err((category, expected)) => { + let real_category = constraints.pattern_categories + [category.index()] + .clone(); + TypeError::BadPattern( + real_region, + real_category, + expected_type, + expected.replace_ref(actual_type), + ) + } + }; + + problems.push(problem); + should_check_exhaustiveness = false; + } + _ => internal_error!("Must be failure"), + } + } + } + BadType(vars, problem) => { + subs.commit_snapshot(snapshot); + + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + should_check_exhaustiveness = false; + } + } + + let sketched_rows = constraints.sketched_rows[sketched_rows.index()].clone(); + + if should_check_exhaustiveness { + use roc_can::exhaustive::{check, ExhaustiveSummary}; + + let ExhaustiveSummary { + errors, + exhaustive, + redundancies, + } = check(subs, sketched_rows, context); + + // Store information about whether the "when" is exhaustive, and + // which (if any) of its branches are redundant. Codegen may use + // this for branch-fixing and redundant elimination. + if !exhaustive { + exhaustive_mark.set_non_exhaustive(subs); + } + for redundant_mark in redundancies { + redundant_mark.set_redundant(subs); + } + + // Store the errors. + problems.extend(errors.into_iter().map(TypeError::Exhaustive)); + } + + state + } }; } state } +fn open_tag_union(subs: &mut Subs, var: Variable) { + let mut stack = vec![var]; + while let Some(var) = stack.pop() { + use {Content::*, FlatType::*}; + + let mut desc = subs.get(var); + if let Structure(TagUnion(tags, ext)) = desc.content { + if let Structure(EmptyTagUnion) = subs.get_content_without_compacting(ext) { + let new_ext = subs.fresh_unnamed_flex_var(); + subs.set_rank(new_ext, desc.rank); + let new_union = Structure(TagUnion(tags, new_ext)); + desc.content = new_union; + subs.set(var, desc); + } + + // Also open up all nested tag unions. + let all_vars = tags.variables().into_iter(); + stack.extend(all_vars.flat_map(|slice| subs[slice]).map(|var| subs[var])); + } + + // Today, an "open" constraint doesn't affect any types + // other than tag unions. Recursive tag unions are constructed + // at a later time (during occurs checks after tag unions are + // resolved), so that's not handled here either. + // NB: Handle record types here if we add presence constraints + // to their type inference as well. + } +} + /// If a symbol claims to specialize an ability member, check that its solved type in fact /// does specialize the ability, and record the specialization. #[allow(clippy::too_many_arguments)] @@ -1774,15 +1899,11 @@ fn type_to_variable<'a>( type_arguments, lambda_set_variables, }) => { - let kind = AliasKind::Structural; - let alias_variables = { let length = type_arguments.len() + lambda_set_variables.len(); let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); - for (target_index, (_, arg_type)) in - (new_variables.indices()).zip(type_arguments) - { + for (target_index, arg_type) in (new_variables.indices()).zip(type_arguments) { let copy_var = helper!(arg_type); subs.variables[target_index] = copy_var; } @@ -1801,13 +1922,17 @@ fn type_to_variable<'a>( } }; - let instantiated = - aliases.instantiate(subs, rank, pools, arena, *symbol, alias_variables); + let instantiated = aliases.instantiate_real_var( + subs, + rank, + pools, + arena, + *symbol, + alias_variables, + ); - let alias_variable = match instantiated { - Err(_) => unreachable!("Alias {:?} is not available", symbol), - Ok(alias_variable) => alias_variable, - }; + let (alias_variable, kind) = instantiated + .unwrap_or_else(|_| unreachable!("Alias {:?} is not available", symbol)); let content = Content::Alias(*symbol, alias_variables, alias_variable, kind); @@ -1827,10 +1952,39 @@ fn type_to_variable<'a>( let length = type_arguments.len() + lambda_set_variables.len(); let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); - for (target_index, (_, arg_type)) in + for (target_index, OptAbleType { typ, opt_ability }) in (new_variables.indices()).zip(type_arguments) { - let copy_var = helper!(arg_type); + let copy_var = match opt_ability { + None => helper!(typ), + Some(ability) => { + // If this type argument is marked as being bound to an ability, we must + // now correctly instantiate it as so. + match RegisterVariable::from_type(subs, rank, pools, arena, typ) { + RegisterVariable::Direct(var) => { + use Content::*; + match *subs.get_content_without_compacting(var) { + FlexVar(opt_name) => subs + .set_content(var, FlexAbleVar(opt_name, *ability)), + RigidVar(..) => internal_error!("Rigid var in type arg for {:?} - this is a bug in the solver, or our understanding", actual), + RigidAbleVar(..) | FlexAbleVar(..) => internal_error!("Able var in type arg for {:?} - this is a bug in the solver, or our understanding", actual), + _ => { + // TODO associate the type to the bound ability, and check + // that it correctly implements the ability. + } + } + var + } + RegisterVariable::Deferred => { + // TODO associate the type to the bound ability, and check + // that it correctly implements the ability. + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer(typ, var)); + var + } + } + } + }; subs.variables[target_index] = copy_var; } @@ -1869,9 +2023,7 @@ fn type_to_variable<'a>( let length = type_arguments.len() + lambda_set_variables.len(); let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); - for (target_index, (_, arg_type)) in - (new_variables.indices()).zip(type_arguments) - { + for (target_index, arg_type) in (new_variables.indices()).zip(type_arguments) { let copy_var = helper!(arg_type); subs.variables[target_index] = copy_var; } @@ -2100,7 +2252,8 @@ fn register_tag_arguments<'a>( VariableSubsSlice::default() } else { let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - let it = (new_variables.indices()).zip(arguments); + let it = new_variables.indices().zip(arguments); + for (target_index, argument) in it { let var = RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack); subs.variables[target_index] = var; @@ -2119,7 +2272,7 @@ fn insert_tags_fast_path<'a>( tags: &'a [(TagName, Vec)], stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> UnionTags { - if let [(TagName::Global(tag_name), arguments)] = tags { + if let [(TagName::Tag(tag_name), arguments)] = tags { let variable_slice = register_tag_arguments(subs, rank, pools, arena, stack, arguments); let new_variable_slices = SubsSlice::extend_new(&mut subs.variable_slices, [variable_slice]); @@ -2289,7 +2442,7 @@ fn generalize( young_rank: Rank, pools: &mut Pools, ) { - let young_vars = pools.get(young_rank); + let young_vars = std::mem::take(pools.get_mut(young_rank)); let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars); // Get the ranks right for each entry. @@ -2300,12 +2453,12 @@ fn generalize( } } - let (last_pool, all_but_last_pool) = rank_table.split_last(); + let (mut last_pool, all_but_last_pool) = rank_table.split_last(); // For variables that have rank lowerer than young_rank, register them in // the appropriate old pool if they are not redundant. for vars in all_but_last_pool { - for &var in vars { + for var in vars { if !subs.redundant(var) { let rank = subs.get_rank(var); @@ -2316,7 +2469,7 @@ fn generalize( // For variables with rank young_rank, if rank < young_rank: register in old pool, // otherwise generalize - for &var in last_pool { + for var in last_pool.drain(..) { if !subs.redundant(var) { let desc_rank = subs.get_rank(var); @@ -2327,32 +2480,38 @@ fn generalize( } } } + + // re-use the last_vector (which likely has a good capacity for future runs + *pools.get_mut(young_rank) = last_pool; } /// Sort the variables into buckets by rank. +#[inline] fn pool_to_rank_table( subs: &mut Subs, young_mark: Mark, young_rank: Rank, - young_vars: &[Variable], + mut young_vars: Vec, ) -> Pools { let mut pools = Pools::new(young_rank.into_usize() + 1); // the vast majority of young variables have young_rank - // using `retain` here prevents many `pools.get_mut(young_rank)` lookups - let mut young_vars = young_vars.to_vec(); - young_vars.retain(|var| { - let rank = subs.get_rank_set_mark(*var, young_mark); + let mut i = 0; + while i < young_vars.len() { + let var = young_vars[i]; + let rank = subs.get_rank_set_mark(var, young_mark); if rank != young_rank { debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); - pools.get_mut(rank).push(*var); - false + pools.get_mut(rank).push(var); + + // swap an element in; don't increment i + young_vars.swap_remove(i); } else { - true + i += 1; } - }); + } std::mem::swap(pools.get_mut(young_rank), &mut young_vars); @@ -2527,19 +2686,49 @@ fn adjust_rank_content( } } - // THEORY: the recursion var has the same rank as the tag union itself + // The recursion var may have a higher rank than the tag union itself, if it is + // erroneous and escapes into a region where it is let-generalized before it is + // constrained back down to the rank it originated from. + // + // For example, see the `recursion_var_specialization_error` reporting test - + // there, we have + // + // Job a : [ Job (List (Job a)) a ] + // + // job : Job Str + // + // when job is + // Job lst _ -> lst == "" + // + // In this case, `lst` is generalized and has a higher rank for the type + // `(List (Job a)) as a` - notice that only the recursion var `a` is active + // here, not the entire recursive tag union. In the body of this branch, `lst` + // becomes a type error, but the nested recursion var `a` is left untouched, + // because it is nested under the of `lst`, not the surface type that becomes + // an error. + // + // Had this not become a type error, `lst` would then be constrained against + // `job`, and its rank would get pulled back down. So, this can only happen in + // the presence of type errors. + // + // In all other cases, the recursion var has the same rank as the tag union itself // all types it uses are also in the tags already, so it cannot influence the - // rank - - if cfg!(debug_assertions) { + // rank. + if cfg!(debug_assertions) + && !matches!( + subs.get_content_without_compacting(*rec_var), + Content::Error | Content::FlexVar(..) + ) + { let rec_var_rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var); debug_assert!( rank >= rec_var_rank, - "rank was {:?} but recursion var {:?} has higher rank {:?}", + "rank was {:?} but recursion var <{:?}>{:?} has higher rank {:?}", rank, rec_var, + subs.get_content_without_compacting(*rec_var), rec_var_rank ); } diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index da03df77e2..a0411d7fbd 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -10,14 +10,60 @@ mod helpers; #[cfg(test)] mod solve_expr { use crate::helpers::with_larger_debug_stack; + use lazy_static::lazy_static; + use regex::Regex; + use roc_can::traverse::find_type_at; use roc_load::LoadedModule; - use roc_types::pretty_print::{content_to_string, name_all_type_vars}; + use roc_module::symbol::{Interns, ModuleId}; + use roc_problem::can::Problem; + use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Region}; + use roc_reporting::report::{can_problem, type_problem, RocDocAllocator}; + use roc_solve::solve::TypeError; + use roc_types::pretty_print::name_and_print_var; + use std::path::PathBuf; // HELPERS - fn run_load_and_infer(src: &str) -> Result { + lazy_static! { + static ref RE_TYPE_QUERY: Regex = + Regex::new(r#"(?P\^+)(?:\{-(?P\d+)\})?"#).unwrap(); + } + + #[derive(Debug, Clone, Copy)] + struct TypeQuery(Region); + + fn parse_queries(src: &str) -> Vec { + let line_info = LineInfo::new(src); + let mut queries = vec![]; + for (i, line) in src.lines().enumerate() { + for capture in RE_TYPE_QUERY.captures_iter(line) { + let wher = capture.name("where").unwrap(); + let subtract_col = capture + .name("sub") + .and_then(|m| str::parse(m.as_str()).ok()) + .unwrap_or(0); + let (start, end) = (wher.start() as u32, wher.end() as u32); + let (start, end) = (start - subtract_col, end - subtract_col); + let last_line = i as u32 - 1; + let start_lc = LineColumn { + line: last_line, + column: start, + }; + let end_lc = LineColumn { + line: last_line, + column: end, + }; + let lc_region = LineColumnRegion::new(start_lc, end_lc); + let region = line_info.convert_line_column_region(lc_region); + + queries.push(TypeQuery(region)); + } + } + queries + } + + fn run_load_and_infer(src: &str) -> Result<(LoadedModule, String), std::io::Error> { use bumpalo::Bump; - use std::path::PathBuf; use tempfile::tempdir; let arena = &Bump::new(); @@ -54,75 +100,89 @@ mod solve_expr { }; let loaded = loaded.expect("failed to load module"); - Ok(loaded) + Ok((loaded, module_src.to_string())) } - fn infer_eq_help( + fn format_problems( src: &str, - ) -> Result< - ( - Vec, - Vec, - String, - ), - std::io::Error, - > { - let LoadedModule { - module_id: home, - mut can_problems, - mut type_problems, - interns, - mut solved, - exposed_to_host, - .. - } = run_load_and_infer(src)?; + home: ModuleId, + interns: &Interns, + can_problems: Vec, + type_problems: Vec, + ) -> (String, String) { + let filename = PathBuf::from("test.roc"); + let src_lines: Vec<&str> = src.split('\n').collect(); + let lines = LineInfo::new(src); + let alloc = RocDocAllocator::new(&src_lines, home, &interns); + + let mut can_reports = vec![]; + let mut type_reports = vec![]; + + for problem in can_problems { + let report = can_problem(&alloc, &lines, filename.clone(), problem.clone()); + can_reports.push(report.pretty(&alloc)); + } + + for problem in type_problems { + if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) { + type_reports.push(report.pretty(&alloc)); + } + } + + let mut can_reports_buf = String::new(); + let mut type_reports_buf = String::new(); + use roc_reporting::report::CiWrite; + alloc + .stack(can_reports) + .1 + .render_raw(70, &mut CiWrite::new(&mut can_reports_buf)) + .unwrap(); + alloc + .stack(type_reports) + .1 + .render_raw(70, &mut CiWrite::new(&mut type_reports_buf)) + .unwrap(); + + (can_reports_buf, type_reports_buf) + } + + fn infer_eq_help(src: &str) -> Result<(String, String, String), std::io::Error> { + let ( + LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + interns, + mut solved, + exposed_to_host, + .. + }, + src, + ) = run_load_and_infer(src)?; let mut can_problems = can_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default(); - let subs = solved.inner_mut(); - - // assert!(can_problems.is_empty()); - // assert!(type_problems.is_empty()); - // let CanExprOut { - // output, - // var_store, - // var, - // constraint, - // home, - // interns, - // problems: mut can_problems, - // .. - // } = can_expr(src); - // let mut subs = Subs::new(var_store.into()); - - // TODO fix this - // assert_correct_variable_usage(&constraint); - - // name type vars - for var in exposed_to_host.values() { - name_all_type_vars(*var, subs); - } - - let content = { - debug_assert!(exposed_to_host.len() == 1); - let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); - subs.get_content_without_compacting(variable) - }; - - let actual_str = content_to_string(content, subs, home, &interns); - // Disregard UnusedDef problems, because those are unavoidable when // returning a function from the test expression. can_problems.retain(|prob| !matches!(prob, roc_problem::can::Problem::UnusedDef(_, _))); + let (can_problems, type_problems) = + format_problems(&src, home, &interns, can_problems, type_problems); + + let subs = solved.inner_mut(); + + debug_assert!(exposed_to_host.len() == 1); + let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); + let actual_str = name_and_print_var(variable, subs, home, &interns); + Ok((type_problems, can_problems, actual_str)) } fn promote_expr_to_module(src: &str) -> String { let mut buffer = String::from(indoc!( r#" - app "test" + app "test" imports [] provides [ main ] to "./platform" @@ -143,7 +203,11 @@ mod solve_expr { fn infer_eq(src: &str, expected: &str) { let (_, can_problems, actual) = infer_eq_help(src).unwrap(); - assert_eq!(can_problems, Vec::new(), "Canonicalization problems: "); + assert!( + can_problems.is_empty(), + "Canonicalization problems: {}", + can_problems + ); assert_eq!(actual, expected.to_string()); } @@ -151,16 +215,72 @@ mod solve_expr { fn infer_eq_without_problem(src: &str, expected: &str) { let (type_problems, can_problems, actual) = infer_eq_help(src).unwrap(); - assert_eq!(can_problems, Vec::new(), "Canonicalization problems: "); + assert!( + can_problems.is_empty(), + "Canonicalization problems: {}", + can_problems + ); if !type_problems.is_empty() { // fail with an assert, but print the problems normally so rust doesn't try to diff // an empty vec with the problems. - panic!("expected:\n{:?}\ninferred:\n{:?}", expected, actual); + panic!( + "expected:\n{:?}\ninferred:\n{:?}\nproblems:\n{}", + expected, actual, type_problems, + ); } assert_eq!(actual, expected.to_string()); } + fn infer_queries(src: &str, expected: &[&'static str]) { + let ( + LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + mut declarations_by_id, + mut solved, + interns, + .. + }, + src, + ) = run_load_and_infer(src).unwrap(); + + let decls = declarations_by_id.remove(&home).unwrap(); + let subs = solved.inner_mut(); + + let can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + let (can_problems, type_problems) = + format_problems(&src, home, &interns, can_problems, type_problems); + + assert!( + can_problems.is_empty(), + "Canonicalization problems: {}", + can_problems + ); + assert!(type_problems.is_empty(), "Type problems: {}", type_problems); + + let queries = parse_queries(&src); + assert!(!queries.is_empty(), "No queries provided!"); + + let mut solved_queries = Vec::with_capacity(queries.len()); + for TypeQuery(region) in queries.into_iter() { + let start = region.start().offset; + let end = region.end().offset; + let text = &src[start as usize..end as usize]; + let var = find_type_at(region, &decls) + .expect(&format!("No type for {} ({:?})!", &text, region)); + + let actual_str = name_and_print_var(var, subs, home, &interns); + + solved_queries.push(format!("{} : {}", text, actual_str)); + } + + assert_eq!(solved_queries, expected) + } + fn check_inferred_abilities<'a, I>(src: &'a str, expected_specializations: I) where I: IntoIterator, @@ -172,7 +292,7 @@ mod solve_expr { interns, abilities_store, .. - } = run_load_and_infer(src).unwrap(); + } = run_load_and_infer(src).unwrap().0; let can_problems = can_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default(); @@ -190,11 +310,11 @@ mod solve_expr { .into_iter() .map(|(member, typ)| { let member_data = abilities_store.member_def(member).unwrap(); - let member_str = member.ident_str(&interns).as_str(); - let ability_str = member_data.parent_ability.ident_str(&interns).as_str(); + let member_str = member.as_str(&interns); + let ability_str = member_data.parent_ability.as_str(&interns); ( format!("{}:{}", ability_str, member_str), - typ.ident_str(&interns).as_str(), + typ.as_str(&interns), ) }) .collect::>(); @@ -1383,18 +1503,6 @@ mod solve_expr { ); } - #[test] - fn single_private_tag_pattern() { - infer_eq( - indoc!( - r#" - \@Foo -> 42 - "# - ), - "[ @Foo ] -> Num *", - ); - } - #[test] fn two_tag_pattern() { infer_eq( @@ -1422,18 +1530,6 @@ mod solve_expr { ); } - #[test] - fn private_tag_application() { - infer_eq( - indoc!( - r#" - @Foo "happy" 2020 - "# - ), - "[ @Foo Str (Num *) ]*", - ); - } - #[test] fn record_extraction() { infer_eq( @@ -1488,7 +1584,7 @@ mod solve_expr { } #[test] - fn global_tag_with_field() { + fn tag_with_field() { infer_eq( indoc!( r#" @@ -1500,19 +1596,6 @@ mod solve_expr { ); } - #[test] - fn private_tag_with_field() { - infer_eq( - indoc!( - r#" - when @Foo "blah" is - @Foo x -> x - "# - ), - "Str", - ); - } - #[test] fn qualified_annotation_num_integer() { infer_eq( @@ -2473,11 +2556,10 @@ mod solve_expr { } // this test is related to a bug where ext_var would have an incorrect rank. - // This match has duplicate cases, but that's not important because exhaustiveness happens - // after inference. + // This match has duplicate cases, but we ignore that. #[test] fn to_bit_record() { - infer_eq_without_problem( + infer_eq( indoc!( r#" foo = \rec -> @@ -3308,11 +3390,11 @@ mod solve_expr { } #[test] - fn num_to_float() { + fn num_to_frac() { infer_eq_without_problem( indoc!( r#" - Num.toFloat + Num.toFrac "# ), "Num * -> Float *", @@ -4090,7 +4172,7 @@ mod solve_expr { } #[test] - fn double_tag_application_pattern_global() { + fn double_tag_application_pattern() { infer_eq_without_problem( indoc!( r#" @@ -4115,31 +4197,6 @@ mod solve_expr { ); } - #[test] - fn double_tag_application_pattern_private() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [ main ] to "./platform" - - Foo : [ @Foo [ @Bar ] I64, @Empty ] - - foo : Foo - foo = @Foo @Bar 1 - - main = - when foo is - @Foo @Bar 1 -> - @Foo @Bar 2 - - x -> - x - "# - ), - "[ @Empty, @Foo [ @Bar ] I64 ]", - ); - } - #[test] fn recursive_function_with_rigid() { infer_eq_without_problem( @@ -5199,95 +5256,123 @@ mod solve_expr { { u8: (\n -> when n is - 123u8 -> n), + 123u8 -> n + _ -> n), u16: (\n -> when n is - 123u16 -> n), + 123u16 -> n + _ -> n), u32: (\n -> when n is - 123u32 -> n), + 123u32 -> n + _ -> n), u64: (\n -> when n is - 123u64 -> n), + 123u64 -> n + _ -> n), u128: (\n -> when n is - 123u128 -> n), + 123u128 -> n + _ -> n), i8: (\n -> when n is - 123i8 -> n), + 123i8 -> n + _ -> n), i16: (\n -> when n is - 123i16 -> n), + 123i16 -> n + _ -> n), i32: (\n -> when n is - 123i32 -> n), + 123i32 -> n + _ -> n), i64: (\n -> when n is - 123i64 -> n), + 123i64 -> n + _ -> n), i128: (\n -> when n is - 123i128 -> n), + 123i128 -> n + _ -> n), nat: (\n -> when n is - 123nat -> n), + 123nat -> n + _ -> n), bu8: (\n -> when n is - 0b11u8 -> n), + 0b11u8 -> n + _ -> n), bu16: (\n -> when n is - 0b11u16 -> n), + 0b11u16 -> n + _ -> n), bu32: (\n -> when n is - 0b11u32 -> n), + 0b11u32 -> n + _ -> n), bu64: (\n -> when n is - 0b11u64 -> n), + 0b11u64 -> n + _ -> n), bu128: (\n -> when n is - 0b11u128 -> n), + 0b11u128 -> n + _ -> n), bi8: (\n -> when n is - 0b11i8 -> n), + 0b11i8 -> n + _ -> n), bi16: (\n -> when n is - 0b11i16 -> n), + 0b11i16 -> n + _ -> n), bi32: (\n -> when n is - 0b11i32 -> n), + 0b11i32 -> n + _ -> n), bi64: (\n -> when n is - 0b11i64 -> n), + 0b11i64 -> n + _ -> n), bi128: (\n -> when n is - 0b11i128 -> n), + 0b11i128 -> n + _ -> n), bnat: (\n -> when n is - 0b11nat -> n), + 0b11nat -> n + _ -> n), dec: (\n -> when n is - 123.0dec -> n), + 123.0dec -> n + _ -> n), f32: (\n -> when n is - 123.0f32 -> n), + 123.0f32 -> n + _ -> n), f64: (\n -> when n is - 123.0f64 -> n), + 123.0f64 -> n + _ -> n), fdec: (\n -> when n is - 123dec -> n), + 123dec -> n + _ -> n), ff32: (\n -> when n is - 123f32 -> n), + 123f32 -> n + _ -> n), ff64: (\n -> when n is - 123f64 -> n), + 123f64 -> n + _ -> n), } "# ), @@ -5313,6 +5398,24 @@ mod solve_expr { ) } + #[test] + fn issue_2458_swapped_order() { + infer_eq_without_problem( + indoc!( + r#" + Bar a : Foo a + Foo a : [ Blah (Result (Bar a) { val: a }) ] + + v : Bar U8 + v = Blah (Ok (Blah (Err { val: 1 }))) + + v + "# + ), + "Bar U8", + ) + } + // https://github.com/rtfeldman/roc/issues/2379 #[test] fn copy_vars_referencing_copied_vars() { @@ -5331,26 +5434,19 @@ mod solve_expr { } #[test] - fn copy_vars_referencing_copied_vars_specialized() { + fn generalize_and_specialize_recursion_var() { infer_eq_without_problem( indoc!( r#" - Job a : [ Job [ Command ] (Job a) (List (Job a)) a ] + Job a : [ Job (List (Job a)) a ] job : Job Str when job is - Job _ j lst _ -> - when j is - Job _ _ _ s -> - { j, lst, s } + Job lst s -> P lst s "# ), - // TODO: this means that we're doing our job correctly, as now both `Job a`s have been - // specialized to the same type, and the second destructuring proves the reified type - // is `Job Str`. But we should just print the structure of the recursive type directly. - // See https://github.com/rtfeldman/roc/issues/2513 - "{ j : a, lst : List a, s : Str }", + "[ P (List [ Job (List a) Str ] as a) Str ]*", ) } @@ -5400,7 +5496,7 @@ mod solve_expr { r#" Age := U32 - $Age 21 + @Age 21 "# ), r#"Age"#, @@ -5415,7 +5511,7 @@ mod solve_expr { Age := U32 a : Age - a = $Age 21 + a = @Age 21 a "# @@ -5431,7 +5527,7 @@ mod solve_expr { r#" Id n := [ Id U32 n ] - $Id (Id 21 "sasha") + @Id (Id 21 "sasha") "# ), r#"Id Str"#, @@ -5446,7 +5542,7 @@ mod solve_expr { Id n := [ Id U32 n ] a : Id Str - a = $Id (Id 21 "sasha") + a = @Id (Id 21 "sasha") a "# @@ -5464,8 +5560,8 @@ mod solve_expr { condition : Bool if condition - then $Id (Id 21 (Y "sasha")) - else $Id (Id 21 (Z "felix")) + then @Id (Id 21 (Y "sasha")) + else @Id (Id 21 (Z "felix")) "# ), r#"Id [ Y Str, Z Str ]*"#, @@ -5483,8 +5579,8 @@ mod solve_expr { v : Id [ Y Str, Z Str ] v = if condition - then $Id (Id 21 (Y "sasha")) - else $Id (Id 21 (Z "felix")) + then @Id (Id 21 (Y "sasha")) + else @Id (Id 21 (Z "felix")) v "# @@ -5500,7 +5596,7 @@ mod solve_expr { r#" Age := U32 - \$Age n -> n + \@Age n -> n "# ), r#"Age -> U32"#, @@ -5515,7 +5611,7 @@ mod solve_expr { Age := U32 v : Age -> U32 - v = \$Age n -> n + v = \@Age n -> n v "# ), @@ -5530,7 +5626,7 @@ mod solve_expr { r#" Id n := [ Id U32 n ] - \$Id (Id _ n) -> n + \@Id (Id _ n) -> n "# ), r#"Id a -> a"#, @@ -5545,7 +5641,7 @@ mod solve_expr { Id n := [ Id U32 n ] v : Id a -> a - v = \$Id (Id _ n) -> n + v = \@Id (Id _ n) -> n v "# @@ -5563,7 +5659,7 @@ mod solve_expr { strToBool : Str -> Bool - \$Id (Id _ n) -> strToBool n + \@Id (Id _ n) -> strToBool n "# ), r#"Id Str -> Bool"#, @@ -5580,7 +5676,7 @@ mod solve_expr { strToBool : Str -> Bool v : Id Str -> Bool - v = \$Id (Id _ n) -> strToBool n + v = \@Id (Id _ n) -> strToBool n v "# @@ -5598,9 +5694,10 @@ mod solve_expr { \id -> when id is - $Id (Id _ A) -> "" - $Id (Id _ B) -> "" - $Id (Id _ (C { a: "" })) -> "" + @Id (Id _ A) -> "" + @Id (Id _ B) -> "" + @Id (Id _ (C { a: "" })) -> "" + @Id (Id _ (C { a: _ })) -> "" # any other string, for exhautiveness "# ), r#"Id [ A, B, C { a : Str }* ] -> Str"#, @@ -5617,9 +5714,10 @@ mod solve_expr { f : Id [ A, B, C { a : Str }e ] -> Str f = \id -> when id is - $Id (Id _ A) -> "" - $Id (Id _ B) -> "" - $Id (Id _ (C { a: "" })) -> "" + @Id (Id _ A) -> "" + @Id (Id _ B) -> "" + @Id (Id _ (C { a: "" })) -> "" + @Id (Id _ (C { a: _ })) -> "" # any other string, for exhautiveness f "# @@ -5635,7 +5733,7 @@ mod solve_expr { r#" app "test" provides [ effectAlways ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a effectAlways : a -> Effect a effectAlways = \x -> @@ -5703,8 +5801,8 @@ mod solve_expr { insert : Outer k, k -> Outer k insert = \m, var -> when m is - $Outer Empty -> $Outer (Wrapped var) - $Outer (Wrapped _) -> $Outer (Wrapped var) + @Outer Empty -> @Outer (Wrapped var) + @Outer (Wrapped _) -> @Outer (Wrapped var) insert "# @@ -5720,9 +5818,9 @@ mod solve_expr { r#" Outer k := [ Empty, Wrapped k ] - when ($Outer Empty) is - $Outer Empty -> $Outer (Wrapped "") - $Outer (Wrapped k) -> $Outer (Wrapped k) + when (@Outer Empty) is + @Outer Empty -> @Outer (Wrapped "") + @Outer (Wrapped k) -> @Outer (Wrapped k) "# ), r#"Outer Str"#, @@ -5736,9 +5834,9 @@ mod solve_expr { r#" Outer := [ A, B ] - when ($Outer A) is - $Outer A -> $Outer A - $Outer B -> $Outer B + when (@Outer A) is + @Outer A -> @Outer A + @Outer B -> @Outer B "# ), r#"Outer"#, @@ -5795,7 +5893,7 @@ mod solve_expr { Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n "# ), [("Hash:hash", "Id")], @@ -5815,8 +5913,8 @@ mod solve_expr { Id := U64 - hash = \$Id n -> n - hash32 = \$Id n -> Num.toU32 n + hash = \@Id n -> n + hash32 = \@Id n -> Num.toU32 n "# ), [("Hash:hash", "Id"), ("Hash:hash32", "Id")], @@ -5840,11 +5938,11 @@ mod solve_expr { Id := U64 - hash = \$Id n -> n - hash32 = \$Id n -> Num.toU32 n + hash = \@Id n -> n + hash32 = \@Id n -> Num.toU32 n - eq = \$Id m, $Id n -> m == n - le = \$Id m, $Id n -> m < n + eq = \@Id m, @Id n -> m == n + le = \@Id m, @Id n -> m < n "# ), [ @@ -5869,7 +5967,7 @@ mod solve_expr { Id := U64 hash : Id -> U64 - hash = \$Id n -> n + hash = \@Id n -> n "# ), [("Hash:hash", "Id")], @@ -5907,9 +6005,9 @@ mod solve_expr { Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n - zero = hash ($Id 0) + zero = hash (@Id 0) "# ), "U64", @@ -6000,9 +6098,9 @@ mod solve_expr { hashEq = \x, y -> hash x == hash y Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n - result = hashEq ($Id 100) ($Id 101) + result = hashEq (@Id 100) (@Id 101) "# ), "Bool", @@ -6022,15 +6120,271 @@ mod solve_expr { mulHashes = \x, y -> hash x * hash y Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n Three := {} - hash = \$Three _ -> 3 + hash = \@Three _ -> 3 - result = mulHashes ($Id 100) ($Three {}) + result = mulHashes (@Id 100) (@Three {}) "# ), "U64", ) } + + #[test] + fn intermediate_branch_types() { + infer_queries( + indoc!( + r#" + app "test" provides [ foo ] to "./platform" + + foo : Bool -> Str + foo = \ob -> + # ^^ + when ob is + # ^^ + True -> "A" + # ^^^^ + False -> "B" + # ^^^^^ + "# + ), + &[ + "ob : Bool", + "ob : Bool", + "True : [ False, True ]", + "False : [ False, True ]", + ], + ) + } + + #[test] + fn nested_open_tag_union() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ go ] to "./platform" + + Expr : [ + Wrap Expr, + Val I64, + ] + + go : Expr -> Expr + go = \e -> + when P e is + P (Wrap (Val _)) -> Wrap e + + # This branch should force the first argument to `P` and + # the first argument to `Wrap` to be an open tag union. + # This tests checks that we don't regress on that. + P y1 -> Wrap y1 + "# + ), + indoc!(r#"Expr -> Expr"#), + ) + } + + #[test] + fn opaque_and_alias_unify() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ always ] to "./platform" + + Effect a := {} -> a + + Task a err : Effect (Result a err) + + always : a -> Task a * + always = \x -> @Effect (\{} -> Ok x) + "# + ), + "a -> Task a *", + ); + } + + #[test] + fn export_rigid_to_lower_rank() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ foo ] to "./platform" + + F a : { foo : a } + + foo = \arg -> + x : F b + x = arg + x.foo + "# + ), + "F b -> b", + ); + } + + #[test] + fn alias_in_opaque() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ foo ] to "./platform" + + MyError : [ Error ] + + MyResult := Result U8 MyError + + foo = @MyResult (Err Error) + "# + ), + "MyResult", + ) + } + + #[test] + fn alias_propagates_able_var() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ zeroEncoder ] to "./platform" + + Encoder fmt := List U8, fmt -> List U8 | fmt has Format + + Format has it : fmt -> {} | fmt has Format + + zeroEncoder = @Encoder \lst, _ -> lst + "# + ), + "Encoder a | a has Format", + ) + } + + #[test] + fn encoder() { + infer_queries( + indoc!( + r#" + app "test" provides [ myU8Bytes ] to "./platform" + + Encoder fmt := List U8, fmt -> List U8 | fmt has Format + + Encoding has + toEncoder : val -> Encoder fmt | val has Encoding, fmt has Format + + Format has + u8 : U8 -> Encoder fmt | fmt has Format + + appendWith : List U8, Encoder fmt, fmt -> List U8 | fmt has Format + appendWith = \lst, (@Encoder doFormat), fmt -> doFormat lst fmt + + toBytes : val, fmt -> List U8 | val has Encoding, fmt has Format + toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt + + + Linear := {} + + # impl Format for Linear + u8 = \n -> @Encoder (\lst, @Linear {} -> List.append lst n) + #^^{-1} + + MyU8 := U8 + + # impl Encoding for MyU8 + toEncoder = \@MyU8 n -> u8 n + #^^^^^^^^^{-1} + + myU8Bytes = toBytes (@MyU8 15) (@Linear {}) + #^^^^^^^^^{-1} + "# + ), + &[ + "u8 : U8 -> Encoder Linear", + "toEncoder : MyU8 -> Encoder fmt | fmt has Format", + "myU8Bytes : List U8", + ], + ) + } + + #[test] + fn decoder() { + infer_queries( + indoc!( + r#" + app "test" provides [ myU8 ] to "./platform" + + DecodeError : [ TooShort, Leftover (List U8) ] + + Decoder val fmt := List U8, fmt -> { result: Result val DecodeError, rest: List U8 } | fmt has DecoderFormatting + + Decoding has + decoder : Decoder val fmt | val has Decoding, fmt has DecoderFormatting + + DecoderFormatting has + u8 : Decoder U8 fmt | fmt has DecoderFormatting + + decodeWith : List U8, Decoder val fmt, fmt -> { result: Result val DecodeError, rest: List U8 } | fmt has DecoderFormatting + decodeWith = \lst, (@Decoder doDecode), fmt -> doDecode lst fmt + + fromBytes : List U8, fmt -> Result val DecodeError + | fmt has DecoderFormatting, val has Decoding + fromBytes = \lst, fmt -> + when decodeWith lst decoder fmt is + { result, rest } -> + when result is + Ok val -> if List.isEmpty rest then Ok val else Err (Leftover rest) + Err e -> Err e + + + Linear := {} + + # impl DecoderFormatting for Linear + u8 = @Decoder \lst, @Linear {} -> + #^^{-1} + when List.first lst is + Ok n -> { result: Ok n, rest: List.dropFirst lst } + Err _ -> { result: Err TooShort, rest: [] } + + MyU8 := U8 + + # impl Decoding for MyU8 + decoder = @Decoder \lst, fmt -> + #^^^^^^^{-1} + when decodeWith lst u8 fmt is + { result, rest } -> + { result: Result.map result (\n -> @MyU8 n), rest } + + myU8 : Result MyU8 _ + myU8 = fromBytes [ 15 ] (@Linear {}) + #^^^^{-1} + "# + ), + &[ + "u8 : Decoder U8 Linear", + "decoder : Decoder MyU8 fmt | fmt has DecoderFormatting", + "myU8 : Result MyU8 DecodeError", + ], + ) + } + + #[test] + fn task_wildcard_wildcard() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ tforever ] to "./platform" + + Effect a := {} -> a + + eforever : Effect a -> Effect b + + Task a err : Effect (Result a err) + + tforever : Task val err -> Task * * + tforever = \task -> eforever task + "# + ), + "Task val err -> Task * *", + ); + } } diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index 203b669d48..ff2e79a7b5 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -37,22 +37,22 @@ bumpalo = { version = "3.8.0", features = ["collections"] } either = "1.6.1" libc = "0.2.106" inkwell = { path = "../../vendor/inkwell" } -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" libloading = "0.7.1" -wasmer-wasi = "2.0.0" +wasmer-wasi = "2.2.1" tempfile = "3.2.0" indoc = "1.0.3" # Wasmer singlepass compiler only works on x86_64. [target.'cfg(target_arch = "x86_64")'.dev-dependencies] -wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] } +wasmer = { version = "2.2.1", default-features = false, features = ["singlepass", "universal"] } [target.'cfg(not(target_arch = "x86_64"))'.dev-dependencies] -wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } +wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] } [features] default = ["gen-llvm"] -gen-llvm = ["roc_build/llvm"] +gen-llvm = [] gen-dev = [] gen-wasm = [] wasm-cli-run = [] diff --git a/compiler/test_gen/build.rs b/compiler/test_gen/build.rs index ea60706621..ab974b93be 100644 --- a/compiler/test_gen/build.rs +++ b/compiler/test_gen/build.rs @@ -1,7 +1,7 @@ use roc_builtins::bitcode; use std::env; use std::ffi::OsStr; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; const PLATFORM_FILENAME: &str = "wasm_test_platform"; @@ -15,11 +15,44 @@ fn main() { } fn build_wasm() { - let out_dir = env::var("OUT_DIR").unwrap(); + let source_path = format!("src/helpers/{}.c", PLATFORM_FILENAME); + println!("cargo:rerun-if-changed={}", source_path); + let out_dir = env::var("OUT_DIR").unwrap(); println!("cargo:rustc-env={}={}", OUT_DIR_VAR, out_dir); - build_wasm_platform_and_builtins(&out_dir); + // Zig can produce *either* an object containing relocations OR an object containing libc code + // But we want both, so we have to compile twice with different flags, then link them + + // Create an object file with relocations + let platform_path = build_wasm_platform(&out_dir, &source_path); + + // Compile again to get libc path + let (libc_path, compiler_rt_path) = build_wasm_libc_compilerrt(&out_dir, &source_path); + let mut libc_pathbuf = PathBuf::from(&libc_path); + libc_pathbuf.pop(); + let libc_dir = libc_pathbuf.to_str().unwrap(); + + let args = &[ + "wasm-ld", + bitcode::BUILTINS_WASM32_OBJ_PATH, + &platform_path, + &compiler_rt_path, + "-L", + libc_dir, + "-lc", + "-o", + &format!("{}/{}.o", out_dir, PLATFORM_FILENAME), + "--export-all", + "--no-entry", + // "--emit-relocs", // TODO: resize stack by relocating __heap_base (issue #2480) here and in repl_test build + ]; + + let zig = zig_executable(); + + // println!("{} {}", zig, args.join(" ")); + + run_command(&zig, args); } fn zig_executable() -> String { @@ -29,29 +62,46 @@ fn zig_executable() -> String { } } -/// Create an all-in-one object file: platform + builtins + libc -fn build_wasm_platform_and_builtins(out_dir: &str) { - println!("cargo:rerun-if-changed=src/helpers/{}.c", PLATFORM_FILENAME); +fn build_wasm_platform(out_dir: &str, source_path: &str) -> String { + let platform_path = format!("{}/{}.o", out_dir, PLATFORM_FILENAME); - // See discussion with Luuk de Gram (Zig contributor) - // https://github.com/rtfeldman/roc/pull/2181#pullrequestreview-839608063 - // This builds a library file that exports everything. It has no linker data but we don't need that. - let args = [ - "build-lib", - "-target", - "wasm32-wasi", - "-lc", - "-dynamic", // -dynamic ensures libc code goes into the binary - bitcode::BUILTINS_WASM32_OBJ_PATH, - &format!("src/helpers/{}.c", PLATFORM_FILENAME), - &format!("-femit-bin={}/{}.o", out_dir, PLATFORM_FILENAME), - ]; + run_command( + &zig_executable(), + &[ + "build-lib", + "-target", + "wasm32-wasi", + "-lc", + source_path, + &format!("-femit-bin={}", &platform_path), + ], + ); - let zig = zig_executable(); + platform_path +} - // println!("{} {}", zig, args.join(" ")); +fn build_wasm_libc_compilerrt(out_dir: &str, source_path: &str) -> (String, String) { + let zig_cache_dir = format!("{}/zig-cache-wasm32", out_dir); - run_command(Path::new("."), &zig, args); + run_command( + &zig_executable(), + &[ + "build-lib", + "-dynamic", // ensure libc code is actually generated (not just linked against header) + "-target", + "wasm32-wasi", + "-lc", + source_path, + "-femit-bin=/dev/null", + "--global-cache-dir", + &zig_cache_dir, + ], + ); + + ( + run_command("find", &[&zig_cache_dir, "-name", "libc.a"]), + run_command("find", &[&zig_cache_dir, "-name", "compiler_rt.o"]), + ) } fn feature_is_enabled(feature_name: &str) -> bool { @@ -62,26 +112,35 @@ fn feature_is_enabled(feature_name: &str) -> bool { env::var(cargo_env_var).is_ok() } -fn run_command>(path: P, command_str: &str, args: I) -> String -where - I: IntoIterator, - S: AsRef, -{ +fn run_command(command_str: &str, args: &[&str]) -> String { let output_result = Command::new(OsStr::new(&command_str)) - .current_dir(path) + .current_dir(Path::new(".")) .args(args) .output(); + + let fail = |err: String| { + panic!( + "\n\nFailed command:\n\t{} {}\n\n{}", + command_str, + args.join(" "), + err + ); + }; + match output_result { Ok(output) => match output.status.success() { - true => std::str::from_utf8(&output.stdout).unwrap().to_string(), + true => std::str::from_utf8(&output.stdout) + .unwrap() + .trim() + .to_string(), false => { let error_str = match std::str::from_utf8(&output.stderr) { Ok(stderr) => stderr.to_string(), Err(_) => format!("Failed to run \"{}\"", command_str), }; - panic!("{} failed: {}", command_str, error_str); + fail(error_str) } }, - Err(reason) => panic!("{} failed: {}", command_str, reason), + Err(reason) => fail(reason.to_string()), } } diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs index f268f0ae2b..bd2fa3e852 100644 --- a/compiler/test_gen/src/gen_abilities.rs +++ b/compiler/test_gen/src/gen_abilities.rs @@ -10,6 +10,9 @@ use crate::helpers::wasm::assert_evals_to; #[cfg(test)] use indoc::indoc; +#[cfg(test)] +use roc_std::RocList; + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn hash_specialization() { @@ -23,9 +26,9 @@ fn hash_specialization() { Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n - main = hash ($Id 1234) + main = hash (@Id 1234) "# ), 1234, @@ -46,13 +49,13 @@ fn hash_specialization_multiple_add() { Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n One := {} - hash = \$One _ -> 1 + hash = \@One _ -> 1 - main = hash ($Id 1234) + hash ($One {}) + main = hash (@Id 1234) + hash (@One {}) "# ), 1235, @@ -73,11 +76,11 @@ fn alias_member_specialization() { Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n main = aliasedHash = hash - aliasedHash ($Id 1234) + aliasedHash (@Id 1234) "# ), 1234, @@ -100,9 +103,9 @@ fn ability_constrained_in_non_member_usage() { mulHashes = \x, y -> hash x * hash y Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n - result = mulHashes ($Id 5) ($Id 7) + result = mulHashes (@Id 5) (@Id 7) "# ), 35, @@ -124,9 +127,9 @@ fn ability_constrained_in_non_member_usage_inferred() { mulHashes = \x, y -> hash x * hash y Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n - result = mulHashes ($Id 5) ($Id 7) + result = mulHashes (@Id 5) (@Id 7) "# ), 35, @@ -149,12 +152,12 @@ fn ability_constrained_in_non_member_multiple_specializations() { mulHashes = \x, y -> hash x * hash y Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n Three := {} - hash = \$Three _ -> 3 + hash = \@Three _ -> 3 - result = mulHashes ($Id 100) ($Three {}) + result = mulHashes (@Id 100) (@Three {}) "# ), 300, @@ -176,12 +179,12 @@ fn ability_constrained_in_non_member_multiple_specializations_inferred() { mulHashes = \x, y -> hash x * hash y Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n Three := {} - hash = \$Three _ -> 3 + hash = \@Three _ -> 3 - result = mulHashes ($Id 100) ($Three {}) + result = mulHashes (@Id 100) (@Three {}) "# ), 300, @@ -204,15 +207,119 @@ fn ability_used_as_type_still_compiles() { mulHashes = \x, y -> hash x * hash y Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n Three := {} - hash = \$Three _ -> 3 + hash = \@Three _ -> 3 - result = mulHashes ($Id 100) ($Three {}) + result = mulHashes (@Id 100) (@Three {}) "# ), 300, u64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn encode() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ myU8Bytes ] to "./platform" + + Encoder fmt := List U8, fmt -> List U8 | fmt has Format + + Encoding has + toEncoder : val -> Encoder fmt | val has Encoding, fmt has Format + + Format has + u8 : U8 -> Encoder fmt | fmt has Format + + appendWith : List U8, Encoder fmt, fmt -> List U8 | fmt has Format + appendWith = \lst, (@Encoder doFormat), fmt -> doFormat lst fmt + + toBytes : val, fmt -> List U8 | val has Encoding, fmt has Format + toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt + + + Linear := {} + + # impl Format for Linear + u8 = \n -> @Encoder (\lst, @Linear {} -> List.append lst n) + + Rgba := { r : U8, g : U8, b : U8, a : U8 } + + # impl Encoding for Rgba + toEncoder = \@Rgba {r, g, b, a} -> + @Encoder \lst, fmt -> lst + |> appendWith (u8 r) fmt + |> appendWith (u8 g) fmt + |> appendWith (u8 b) fmt + |> appendWith (u8 a) fmt + + myU8Bytes = toBytes (@Rgba { r: 106, g: 90, b: 205, a: 255 }) (@Linear {}) + "# + ), + RocList::from_slice(&[106, 90, 205, 255]), + RocList + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn decode() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ myU8 ] to "./platform" + + DecodeError : [ TooShort, Leftover (List U8) ] + + Decoder val fmt := List U8, fmt -> { result: Result val DecodeError, rest: List U8 } | fmt has DecoderFormatting + + Decoding has + decoder : Decoder val fmt | val has Decoding, fmt has DecoderFormatting + + DecoderFormatting has + u8 : Decoder U8 fmt | fmt has DecoderFormatting + + decodeWith : List U8, Decoder val fmt, fmt -> { result: Result val DecodeError, rest: List U8 } | fmt has DecoderFormatting + decodeWith = \lst, (@Decoder doDecode), fmt -> doDecode lst fmt + + fromBytes : List U8, fmt -> Result val DecodeError + | fmt has DecoderFormatting, val has Decoding + fromBytes = \lst, fmt -> + when decodeWith lst decoder fmt is + { result, rest } -> + Result.after result \val -> + if List.isEmpty rest + then Ok val + else Err (Leftover rest) + + + Linear := {} + + # impl DecoderFormatting for Linear + u8 = @Decoder \lst, @Linear {} -> + when List.first lst is + Ok n -> { result: Ok n, rest: List.dropFirst lst } + Err _ -> { result: Err TooShort, rest: [] } + + MyU8 := U8 + + # impl Decoding for MyU8 + decoder = @Decoder \lst, fmt -> + { result, rest } = decodeWith lst u8 fmt + { result: Result.map result (\n -> @MyU8 n), rest } + + myU8 = + when fromBytes [ 15 ] (@Linear {}) is + Ok (@MyU8 n) -> n + _ -> 27u8 + "# + ), + 15, + u8 + ); +} diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 1ad158a473..c492936129 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -800,7 +800,7 @@ fn list_walk_until_sum() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn list_walk_imlements_position() { +fn list_walk_implements_position() { assert_evals_to!( r#" Option a : [ Some a, None ] @@ -2534,7 +2534,7 @@ fn list_keep_oks() { RocList ); assert_evals_to!( - "List.keepOks [1,2] (\\x -> x % 2)", + "List.keepOks [1,2] (\\x -> Num.remChecked x 2)", RocList::from_slice(&[1, 0]), RocList ); @@ -2561,7 +2561,7 @@ fn list_keep_errs() { assert_evals_to!( indoc!( r#" - List.keepErrs [0,1,2] (\x -> x % 0 |> Result.mapErr (\_ -> 32)) + List.keepErrs [0,1,2] (\x -> Num.remChecked x 0 |> Result.mapErr (\_ -> 32)) "# ), RocList::from_slice(&[32, 32, 32]), diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 83f6c16519..c88520023e 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -473,7 +473,7 @@ fn f64_sqrt() { assert_evals_to!( indoc!( r#" - when Num.sqrt 100 is + when Num.sqrtChecked 100 is Ok val -> val Err _ -> -1 "# @@ -489,9 +489,7 @@ fn f64_log() { assert_evals_to!( indoc!( r#" - when Num.log 7.38905609893 is - Ok val -> val - Err _ -> -1 + Num.log 7.38905609893 "# ), 1.999999999999912, @@ -501,11 +499,11 @@ fn f64_log() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn f64_log_one() { +fn f64_log_checked_one() { assert_evals_to!( indoc!( r#" - when Num.log 1 is + when Num.logChecked 1 is Ok val -> val Err _ -> -1 "# @@ -521,7 +519,7 @@ fn f64_sqrt_zero() { assert_evals_to!( indoc!( r#" - when Num.sqrt 0 is + when Num.sqrtChecked 0 is Ok val -> val Err _ -> -1 "# @@ -533,11 +531,11 @@ fn f64_sqrt_zero() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn f64_sqrt_negative() { +fn f64_sqrt_checked_negative() { assert_evals_to!( indoc!( r#" - when Num.sqrt -1 is + when Num.sqrtChecked -1 is Err _ -> 42 Ok val -> val "# @@ -549,11 +547,11 @@ fn f64_sqrt_negative() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn f64_log_zero() { +fn f64_log_checked_zero() { assert_evals_to!( indoc!( r#" - when Num.log 0 is + when Num.logChecked 0 is Err _ -> 42 Ok val -> val "# @@ -569,13 +567,12 @@ fn f64_log_negative() { assert_evals_to!( indoc!( r#" - when Num.log -1 is - Err _ -> 42 - Ok val -> val + Num.log -1 "# ), - 42.0, - f64 + true, + f64, + |f: f64| f.is_nan() ); } @@ -594,8 +591,12 @@ fn f64_round() { fn f64_abs() { assert_evals_to!("Num.abs -4.7", 4.7, f64); assert_evals_to!("Num.abs 5.8", 5.8, f64); - //assert_evals_to!("Num.abs Num.maxFloat", f64::MAX, f64); - //assert_evals_to!("Num.abs Num.minFloat", -f64::MIN, f64); + + #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] + { + assert_evals_to!("Num.abs Num.maxF64", f64::MAX, f64); + assert_evals_to!("Num.abs Num.minF64", f64::MAX, f64); + } } #[test] @@ -1082,9 +1083,7 @@ fn gen_rem_i64() { assert_evals_to!( indoc!( r#" - when Num.rem 8 3 is - Ok val -> val - Err _ -> -1 + Num.rem 8 3 "# ), 2, @@ -1094,11 +1093,11 @@ fn gen_rem_i64() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn gen_rem_div_by_zero_i64() { +fn gen_rem_checked_div_by_zero_i64() { assert_evals_to!( indoc!( r#" - when Num.rem 8 0 is + when Num.remChecked 8 0 is Err DivByZero -> 4 Ok _ -> -23 "# @@ -1215,6 +1214,86 @@ fn bitwise_or() { assert_evals_to!("Num.bitwiseOr 1 2", 3, i64); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn lt_u8() { + assert_evals_to!("1u8 < 2u8", true, bool); + assert_evals_to!("1u8 < 1u8", false, bool); + assert_evals_to!("2u8 < 1u8", false, bool); + assert_evals_to!("0u8 < 0u8", false, bool); + assert_evals_to!("128u8 < 0u8", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn lte_u8() { + assert_evals_to!("1u8 <= 1u8", true, bool); + assert_evals_to!("2u8 <= 1u8", false, bool); + assert_evals_to!("1u8 <= 2u8", true, bool); + assert_evals_to!("0u8 <= 0u8", true, bool); + assert_evals_to!("128u8 <= 0u8", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn gt_u8() { + assert_evals_to!("2u8 > 1u8", true, bool); + assert_evals_to!("2u8 > 2u8", false, bool); + assert_evals_to!("1u8 > 1u8", false, bool); + assert_evals_to!("0u8 > 0u8", false, bool); + assert_evals_to!("0u8 > 128u8", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn gte_u8() { + assert_evals_to!("1u8 >= 1u8", true, bool); + assert_evals_to!("1u8 >= 2u8", false, bool); + assert_evals_to!("2u8 >= 1u8", true, bool); + assert_evals_to!("0u8 >= 0u8", true, bool); + assert_evals_to!("0u8 >= 128u8", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn lt_u64() { + assert_evals_to!("1u64 < 2u64", true, bool); + assert_evals_to!("1u64 < 1u64", false, bool); + assert_evals_to!("2u64 < 1u64", false, bool); + assert_evals_to!("0u64 < 0u64", false, bool); + assert_evals_to!("9223372036854775808u64 < 0u64", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn lte_u64() { + assert_evals_to!("1u64 <= 1u64", true, bool); + assert_evals_to!("2u64 <= 1u64", false, bool); + assert_evals_to!("1u64 <= 2u64", true, bool); + assert_evals_to!("0u64 <= 0u64", true, bool); + assert_evals_to!("9223372036854775808u64 <= 0u64", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn gt_u64() { + assert_evals_to!("2u64 > 1u64", true, bool); + assert_evals_to!("2u64 > 2u64", false, bool); + assert_evals_to!("1u64 > 1u64", false, bool); + assert_evals_to!("0u64 > 0u64", false, bool); + assert_evals_to!("0u64 > 9223372036854775808u64", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn gte_u64() { + assert_evals_to!("1u64 >= 1u64", true, bool); + assert_evals_to!("1u64 >= 2u64", false, bool); + assert_evals_to!("2u64 >= 1u64", true, bool); + assert_evals_to!("0u64 >= 0u64", true, bool); + assert_evals_to!("0u64 >= 9223372036854775808u64", false, bool); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn lt_i64() { @@ -1444,18 +1523,18 @@ fn gen_basic_fn() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn int_to_float() { - assert_evals_to!("Num.toFloat 0x9", 9.0, f64); + assert_evals_to!("Num.toFrac 0x9", 9.0, f64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn num_to_float() { - assert_evals_to!("Num.toFloat 9", 9.0, f64); +fn num_to_frac() { + assert_evals_to!("Num.toFrac 9", 9.0, f64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] -fn num_to_float_f64_to_f32() { +fn num_to_frac_f64_to_f32() { assert_evals_to!( indoc!( r#" @@ -1463,7 +1542,7 @@ fn num_to_float_f64_to_f32() { f64 = 9.0 f32 : F32 - f32 = Num.toFloat f64 + f32 = Num.toFrac f64 f32 "# ), @@ -1474,7 +1553,7 @@ fn num_to_float_f64_to_f32() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] -fn num_to_float_f32_to_f32() { +fn num_to_frac_f32_to_f32() { assert_evals_to!( indoc!( r#" @@ -1483,7 +1562,7 @@ fn num_to_float_f32_to_f32() { arg = 9.0 ret : F32 - ret = Num.toFloat arg + ret = Num.toFrac arg ret "# ), @@ -1494,7 +1573,7 @@ fn num_to_float_f32_to_f32() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn num_to_float_f64_to_f64() { +fn num_to_frac_f64_to_f64() { assert_evals_to!( indoc!( r#" @@ -1503,7 +1582,7 @@ fn num_to_float_f64_to_f64() { arg = 9.0 ret : F64 - ret = Num.toFloat arg + ret = Num.toFrac arg ret "# ), @@ -1514,7 +1593,7 @@ fn num_to_float_f64_to_f64() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn num_to_float_f32_to_f64() { +fn num_to_frac_f32_to_f64() { assert_evals_to!( indoc!( r#" @@ -1523,7 +1602,7 @@ fn num_to_float_f32_to_f64() { f32 = 9.0 f64 : F64 - f64 = Num.toFloat f32 + f64 = Num.toFrac f32 f64 "# ), @@ -1535,7 +1614,7 @@ fn num_to_float_f32_to_f64() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn float_to_float() { - assert_evals_to!("Num.toFloat 0.5", 0.5, f64); + assert_evals_to!("Num.toFrac 0.5", 0.5, f64); } #[test] @@ -1573,7 +1652,7 @@ fn floor() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn pow_int() { assert_evals_to!("Num.powInt 2 3", 8, i64); } @@ -2245,6 +2324,62 @@ fn max_u8() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_f64() { + assert_evals_to!( + indoc!( + r#" + Num.maxF64 + "# + ), + f64::MAX, + f64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_f64() { + assert_evals_to!( + indoc!( + r#" + Num.minF64 + "# + ), + f64::MIN, + f64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn max_f32() { + assert_evals_to!( + indoc!( + r#" + Num.maxF32 + "# + ), + f32::MAX, + f32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn min_f32() { + assert_evals_to!( + indoc!( + r#" + Num.minF32 + "# + ), + f32::MIN, + f32 + ); +} + macro_rules! num_conversion_tests { ($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [ $($support_gen:literal),* ])? )*))*) => {$($( #[test] @@ -3094,7 +3229,7 @@ fn to_float_f32() { n = 100 f : F32 - f = Num.toFloat n + f = Num.toFrac n f "# ), @@ -3113,7 +3248,7 @@ fn to_float_f64() { n = 100 f : F64 - f = Num.toFloat n + f = Num.toFrac n f "# ), @@ -3150,7 +3285,7 @@ fn upcast_of_int_checked_is_zext() { "# ), 1, - u16 + u8 ) } @@ -3195,3 +3330,51 @@ fn dec_float_suffix() { i128 ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ceiling_to_u32() { + assert_evals_to!( + indoc!( + r#" + n : U32 + n = Num.ceiling 124.5 + n + "# + ), + 125, + u32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn floor_to_u32() { + assert_evals_to!( + indoc!( + r#" + n : U32 + n = Num.floor 124.5 + n + "# + ), + 124, + u32 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn round_to_u32() { + assert_evals_to!( + indoc!( + r#" + n : U32 + n = Num.round 124.49 + n + "# + ), + 124, + u32 + ); +} diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 16f4248dc8..b26fa93af2 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -328,7 +328,7 @@ fn return_unnamed_fn() { indoc!( r#" wrapper = \{} -> - alwaysFloatIdentity : Int * -> (Float a -> Float a) + alwaysFloatIdentity : Int * -> (Frac a -> Frac a) alwaysFloatIdentity = \_ -> (\a -> a) @@ -593,6 +593,27 @@ fn top_level_constant() { ); } +#[test] +#[ignore] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn top_level_destructure() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + {a, b} = { a: 1, b: 2 } + + main = + + a + b + "# + ), + 3, + i64 + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn linked_list_len_0() { @@ -1113,7 +1134,7 @@ fn io_poc_effect() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a succeed : a -> Effect a succeed = \x -> @Effect \{} -> x @@ -1172,7 +1193,7 @@ fn return_wrapped_function_pointer() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a foo : Effect {} foo = @Effect \{} -> {} @@ -1217,7 +1238,7 @@ fn return_wrapped_closure() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a foo : Effect {} foo = @@ -1799,7 +1820,7 @@ fn unified_empty_closure_bool() { A -> (\_ -> 3.14) B -> (\_ -> 3.14) - main : Float * + main : Frac * main = (foo {}) 0 "# @@ -1825,7 +1846,7 @@ fn unified_empty_closure_byte() { B -> (\_ -> 3.14) C -> (\_ -> 3.14) - main : Float * + main : Frac * main = (foo {}) 0 "# @@ -1843,7 +1864,7 @@ fn task_always_twice() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a effectAlways : a -> Effect a effectAlways = \x -> @@ -1869,7 +1890,7 @@ fn task_always_twice() { Ok x -> transform x Err e -> fail e - main : Task {} (Float *) + main : Task {} (Frac *) main = after (always "foo") (\_ -> always {}) "# @@ -1888,7 +1909,7 @@ fn wildcard_rigid() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a Task a err : Effect (Result a err) @@ -1900,7 +1921,7 @@ fn wildcard_rigid() { @Effect inner - main : Task {} (Float *) + main : Task {} (Frac *) main = always {} "# ), @@ -1918,7 +1939,7 @@ fn alias_of_alias_with_type_arguments() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect a ] + Effect a := a Task a err : Effect (Result a err) @@ -1929,7 +1950,7 @@ fn alias_of_alias_with_type_arguments() { @Effect inner - main : Task {} (Float *) + main : Task {} (Frac *) main = always {} "# ), @@ -1948,7 +1969,7 @@ fn todo_bad_error_message() { r#" app "test" provides [ main ] to "./platform" - Effect a : [ @Effect ({} -> a) ] + Effect a := {} -> a effectAlways : a -> Effect a effectAlways = \x -> @@ -1961,7 +1982,7 @@ fn todo_bad_error_message() { Task a err : Effect (Result a err) - always : a -> Task a (Float *) + always : a -> Task a (Frac *) always = \x -> effectAlways (Ok x) # the problem is that this restricts to `Task {} *` @@ -1976,7 +1997,7 @@ fn todo_bad_error_message() { # but here it must be `forall b. Task b {}` Err e -> fail e - main : Task {} (Float *) + main : Task {} (Frac *) main = after (always "foo") (\_ -> always {}) "# @@ -2482,7 +2503,7 @@ fn backpassing_result() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm"))] #[should_panic(expected = "Shadowing { original_region: @57-58, shadow: @74-75 Ident")] fn function_malformed_pattern() { assert_evals_to!( @@ -2972,6 +2993,7 @@ fn mix_function_and_closure_level_of_indirection() { #[test] #[cfg(any(feature = "gen-llvm"))] +#[cfg_attr(debug_assertions, ignore)] // this test stack-overflows the compiler in debug mode fn do_pass_bool_byte_closure_layout() { // see https://github.com/rtfeldman/roc/pull/1706 // the distinction is actually important, dropping that info means some functions just get @@ -3079,7 +3101,7 @@ fn nested_rigid_alias() { r#" app "test" provides [ main ] to "./platform" - Identity a : [ @Identity a ] + Identity a := a foo : Identity a -> Identity a foo = \list -> @@ -3106,15 +3128,15 @@ fn nested_rigid_tag_union() { r#" app "test" provides [ main ] to "./platform" - foo : [ @Identity a ] -> [ @Identity a ] + foo : [ Identity a ] -> [ Identity a ] foo = \list -> - p2 : [ @Identity a ] + p2 : [ Identity a ] p2 = list p2 main = - when foo (@Identity "foo") is + when foo (Identity "foo") is _ -> "hello world" "# ), @@ -3200,7 +3222,7 @@ fn recursively_build_effect() { always {} |> after \_ -> nestHelp (m - 1) - XEffect a : [ @XEffect ({} -> a) ] + XEffect a := {} -> a always : a -> XEffect a always = \x -> @XEffect (\{} -> x) @@ -3322,3 +3344,152 @@ fn box_and_unbox_tag_union() { (u8, u8) ) } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn closure_called_in_its_defining_scope() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main : Str + main = + g : Str + g = "hello world" + + getG : {} -> Str + getG = \{} -> g + + getG {} + "# + ), + RocStr::from("hello world"), + RocStr + ) +} + +#[test] +#[ignore] +#[cfg(any(feature = "gen-llvm"))] +fn issue_2894() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main : U32 + main = + g : { x : U32 } + g = { x: 1u32 } + + getG : {} -> { x : U32 } + getG = \{} -> g + + h : {} -> U32 + h = \{} -> (getG {}).x + + h {} + "# + ), + 1u32, + u32 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn polymorphic_def_used_in_closure() { + assert_evals_to!( + indoc!( + r#" + a : I64 -> _ + a = \g -> + f = { r: g, h: 32 } + + h1 : U64 + h1 = (\{} -> f.h) {} + h1 + a 1 + "# + ), + 32, + u64 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn polymorphic_lambda_set_usage() { + assert_evals_to!( + indoc!( + r#" + id1 = \x -> x + id2 = \y -> y + id = if True then id1 else id2 + + id 9u8 + "# + ), + 9, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn polymorphic_lambda_set_multiple_specializations() { + assert_evals_to!( + indoc!( + r#" + id1 = \x -> x + id2 = \y -> y + id = if True then id1 else id2 + + (id 9u8) + Num.toU8 (id 16u16) + "# + ), + 25, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_map2_conslist() { + // this had an RC problem, https://github.com/rtfeldman/roc/issues/2968 + assert_evals_to!( + indoc!( + r#" + ConsList a : [ Nil, Cons a (ConsList a) ] + + x : List (ConsList Str) + x = List.map2 [ ] [ Nil ] Cons + + when List.first x is + _ -> "" + "# + ), + RocStr::default(), + RocStr + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn polymorphic_lambda_captures_polymorphic_value() { + assert_evals_to!( + indoc!( + r#" + x = 2 + + f1 = \_ -> x + + f = if True then f1 else f1 + f {} + "# + ), + 2, + u64 + ) +} diff --git a/compiler/test_gen/src/gen_refcount.rs b/compiler/test_gen/src/gen_refcount.rs index 25ade5e934..631c0635ce 100644 --- a/compiler/test_gen/src/gen_refcount.rs +++ b/compiler/test_gen/src/gen_refcount.rs @@ -59,10 +59,7 @@ fn list_int_inc() { ), RocList>, &[ - // TODO be smarter about coalescing polymorphic list values - Live(1), // list0 - Live(1), // list1 - Live(1), // list2 + Live(3), // list Live(1) // result ] ); @@ -80,10 +77,7 @@ fn list_int_dealloc() { ), usize, &[ - // TODO be smarter about coalescing polymorphic list values - Deallocated, // list0 - Deallocated, // list1 - Deallocated, // list2 + Deallocated, // list Deallocated // result ] ); diff --git a/compiler/test_gen/src/gen_tags.rs b/compiler/test_gen/src/gen_tags.rs index eefcb272cf..25648b37e4 100644 --- a/compiler/test_gen/src/gen_tags.rs +++ b/compiler/test_gen/src/gen_tags.rs @@ -7,10 +7,9 @@ use crate::helpers::dev::assert_evals_to; #[cfg(feature = "gen-wasm")] use crate::helpers::wasm::assert_evals_to; -// use crate::assert_wasm_evals_to as assert_evals_to; -#[allow(unused_imports)] +#[cfg(test)] use indoc::indoc; -#[allow(unused_imports)] +#[cfg(test)] use roc_std::{RocList, RocStr}; #[test] @@ -1055,10 +1054,10 @@ fn phantom_polymorphic() { r"# Point coordinate : [ Point coordinate I64 I64 ] - World : [ @World ] + World := {} zero : Point World - zero = Point @World 0 0 + zero = Point (@World {}) 0 0 add : Point a -> Point a add = \(Point c x y) -> (Point c x y) @@ -1201,6 +1200,7 @@ fn applied_tag_function_result() { #[test] #[cfg(any(feature = "gen-llvm"))] +#[ignore = "This test has incorrect refcounts: https://github.com/rtfeldman/roc/issues/2968"] fn applied_tag_function_linked_list() { assert_evals_to!( indoc!( @@ -1220,6 +1220,27 @@ fn applied_tag_function_linked_list() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn applied_tag_function_pair() { + assert_evals_to!( + indoc!( + r#" + Pair a : [ Pair a a ] + + x : List (Pair Str) + x = List.map2 [ "a", "b" ] [ "c", "d" ] Pair + + when List.first x is + Ok (Pair "a" "c") -> 1 + _ -> 0 + "# + ), + 1, + i64 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[should_panic(expected = "")] // TODO: this only panics because it returns 0 instead of 1! @@ -1580,3 +1601,105 @@ fn issue_2725_alias_polymorphic_lambda() { i64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn opaque_assign_to_symbol() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + Variable := U8 + + fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ] + fromUtf8 = \char -> + Ok (@Variable char) + + out = + when fromUtf8 98 is + Ok (@Variable n) -> n + _ -> 1 + "# + ), + 98, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn issue_2777_default_branch_codegen() { + assert_evals_to!( + indoc!( + r#" + f1 = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + _ -> "unknown" + + r1 = Red |> f1 |> Str.concat (f1 Orange) + + f2 = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + Green -> "green" + _ -> "unknown" + + r2 = Red |> f2 |> Str.concat (f2 Orange) + + f3 = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + Green -> "green" + _ -> "unknown" + + r3 = Orange |> f3 |> Str.concat (f3 Red) + + f4 = \color -> + when color is + Red -> "red" + Yellow | Gold -> "yellow" + _ -> "unknown" + + r4 = Red |> f4 |> Str.concat (f4 Orange) + + [r1, r2, r3, r4] + "# + ), + RocList::from_slice(&[ + RocStr::from("redunknown"), + RocStr::from("redunknown"), + RocStr::from("unknownred"), + RocStr::from("redunknown"), + ]), + RocList + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[should_panic(expected = "Erroneous")] +fn issue_2900_unreachable_pattern() { + assert_evals_to!( + indoc!( + r#" + foo : [ Foo, Bar, Baz, Blah ] -> Str + foo = \arg -> + when arg is + Foo -> "foo" + AnUnreachableTag -> "blah" + _ -> "other" + + foo Foo + "# + ), + RocStr::from("foo"), + RocStr, + |x| x, + true // ignore type errors + ) +} diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 0ecd1bb37c..ab7ce02c2d 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -2,6 +2,7 @@ use libloading::Library; use roc_build::link::{link, LinkType}; use roc_builtins::bitcode; use roc_collections::all::MutMap; +use roc_load::Threading; use roc_region::all::LineInfo; use tempfile::tempdir; @@ -55,6 +56,7 @@ pub fn helper( Default::default(), roc_target::TargetInfo::default_x86_64(), roc_reporting::report::RenderTarget::ColorTerminal, + Threading::Single, ); let mut loaded = loaded.expect("failed to load module"); @@ -107,15 +109,12 @@ pub fn helper( let mut delayed_errors = Vec::new(); for (home, (module_path, src)) in loaded.sources { - use roc_reporting::report::{ - can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, - }; + use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; let can_problems = loaded.can_problems.remove(&home).unwrap_or_default(); let type_problems = loaded.type_problems.remove(&home).unwrap_or_default(); - let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default(); - let error_count = can_problems.len() + type_problems.len() + mono_problems.len(); + let error_count = can_problems.len() + type_problems.len(); if error_count == 0 { continue; @@ -156,15 +155,6 @@ pub fn helper( lines.push(buf); } } - - for problem in mono_problems { - let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - lines.push(buf); - } } if !lines.is_empty() { diff --git a/compiler/test_gen/src/helpers/from_wasmer_memory.rs b/compiler/test_gen/src/helpers/from_wasmer_memory.rs index 84267772e2..20a05aab0c 100644 --- a/compiler/test_gen/src/helpers/from_wasmer_memory.rs +++ b/compiler/test_gen/src/helpers/from_wasmer_memory.rs @@ -1,5 +1,6 @@ use roc_gen_wasm::wasm32_sized::Wasm32Sized; use roc_std::{ReferenceCount, RocDec, RocList, RocOrder, RocStr}; +use std::convert::TryInto; pub trait FromWasmerMemory: Wasm32Sized { fn decode(memory: &wasmer::Memory, offset: u32) -> Self; @@ -17,9 +18,9 @@ macro_rules! from_wasm_memory_primitive_decode { let raw_ptr = ptr as *mut u8; let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) }; - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset); - let foobar = (ptr.deref(memory, 0, width as u32)).unwrap(); - let wasm_slice = unsafe { std::mem::transmute(foobar) }; + let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; + let index = offset as usize; + let wasm_slice = &memory_bytes[index..][..width]; slice.copy_from_slice(wasm_slice); @@ -108,12 +109,16 @@ impl FromWasmerMemory for &'_ T { impl FromWasmerMemory for [T; N] { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset); - let width = ::SIZE_OF_WASM as u32 * N as u32; - let foobar = (ptr.deref(memory, 0, width)).unwrap(); - let wasm_slice: &[T; N] = unsafe { &*(foobar as *const _ as *const [T; N]) }; + let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; + let index = offset as usize; - wasm_slice.clone() + debug_assert!(memory_bytes.len() >= index + (N * ::SIZE_OF_WASM)); + + let slice_bytes: &[u8] = &memory_bytes[index..][..N]; + let slice: &[T] = unsafe { std::mem::transmute(slice_bytes) }; + let array: &[T; N] = slice.try_into().expect("incorrect length"); + + array.clone() } } diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index 99252b15c5..068a38bc3b 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -5,6 +5,7 @@ use roc_build::link::module_to_dylib; use roc_build::program::FunctionIterator; use roc_collections::all::MutSet; use roc_gen_llvm::llvm::externs::add_default_roc_externs; +use roc_load::Threading; use roc_mono::ir::OptLevel; use roc_region::all::LineInfo; use roc_reporting::report::RenderTarget; @@ -59,6 +60,7 @@ fn create_llvm_module<'a>( Default::default(), target_info, RenderTarget::ColorTerminal, + Threading::AllAvailable, ); let mut loaded = match loaded { @@ -83,15 +85,12 @@ fn create_llvm_module<'a>( let mut delayed_errors = Vec::new(); for (home, (module_path, src)) in loaded.sources { - use roc_reporting::report::{ - can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE, - }; + use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; let can_problems = loaded.can_problems.remove(&home).unwrap_or_default(); let type_problems = loaded.type_problems.remove(&home).unwrap_or_default(); - let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default(); - let error_count = can_problems.len() + type_problems.len() + mono_problems.len(); + let error_count = can_problems.len() + type_problems.len(); if error_count == 0 { continue; @@ -144,16 +143,6 @@ fn create_llvm_module<'a>( lines.push(buf); } } - - for problem in mono_problems { - let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - delayed_errors.push(buf.clone()); - lines.push(buf); - } } if !lines.is_empty() { @@ -427,13 +416,14 @@ fn wasm_roc_panic(address: u32, tag_id: u32) { MEMORY.with(|f| { let memory = f.borrow().unwrap(); - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(address); - let width = 100; - let c_ptr = (ptr.deref(memory, 0, width)).unwrap(); + let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; + let index = address as usize; + let slice = &memory_bytes[index..]; + let c_ptr: *const u8 = slice.as_ptr(); use std::ffi::CStr; use std::os::raw::c_char; - let slice = unsafe { CStr::from_ptr(c_ptr as *const _ as *const c_char) }; + let slice = unsafe { CStr::from_ptr(c_ptr as *const c_char) }; string = slice.to_str().unwrap(); }); diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 0c59df3cf5..09565b5bf4 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -1,17 +1,16 @@ -use core::cell::Cell; +use super::RefCount; +use crate::helpers::from_wasmer_memory::FromWasmerMemory; +use roc_collections::all::MutSet; +use roc_gen_wasm::wasm32_result::Wasm32Result; use roc_gen_wasm::wasm_module::{Export, ExportType}; +use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME}; +use roc_load::Threading; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::path::{Path, PathBuf}; use wasmer::{Memory, WasmPtr}; -use super::RefCount; -use crate::helpers::from_wasmer_memory::FromWasmerMemory; -use roc_collections::all::MutSet; -use roc_gen_wasm::wasm32_result::Wasm32Result; -use roc_gen_wasm::{DEBUG_LOG_SETTINGS, MEMORY_NAME}; - // Should manually match build.rs const PLATFORM_FILENAME: &str = "wasm_test_platform"; const OUT_DIR_VAR: &str = "TEST_GEN_OUT"; @@ -37,12 +36,12 @@ fn promote_expr_to_module(src: &str) -> String { pub fn compile_and_load<'a, T: Wasm32Result>( arena: &'a bumpalo::Bump, src: &str, - _test_wrapper_type_info: PhantomData, + test_wrapper_type_info: PhantomData, ) -> wasmer::Instance { let platform_bytes = load_platform_and_builtins(); let compiled_bytes = - compile_roc_to_wasm_bytes(arena, &platform_bytes, src, _test_wrapper_type_info); + compile_roc_to_wasm_bytes(arena, &platform_bytes, src, test_wrapper_type_info); if DEBUG_LOG_SETTINGS.keep_test_binary { let build_dir_hash = src_hash(src); @@ -92,6 +91,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>( Default::default(), roc_target::TargetInfo::default_wasm32(), roc_reporting::report::RenderTarget::ColorTerminal, + Threading::Single, ); let loaded = loaded.expect("failed to load module"); @@ -280,7 +280,7 @@ where // Read the actual refcount values let refcount_ptr_array: WasmPtr, wasmer::Array> = WasmPtr::new(4 + refcount_vector_addr as u32); - let refcount_ptrs: &[Cell>] = refcount_ptr_array + let refcount_ptrs = refcount_ptr_array .deref(memory, 0, num_refcounts as u32) .unwrap(); diff --git a/compiler/test_gen/src/helpers/wasm_test_platform.c b/compiler/test_gen/src/helpers/wasm_test_platform.c index 2a70f8dc65..fdc84fc2f7 100644 --- a/compiler/test_gen/src/helpers/wasm_test_platform.c +++ b/compiler/test_gen/src/helpers/wasm_test_platform.c @@ -1,5 +1,6 @@ #include #include +#include // Makes test runs take 50% longer, due to linking #define ENABLE_PRINTF 0 @@ -138,9 +139,9 @@ void roc_panic(char *msg, unsigned int tag_id) //-------------------------- -void *roc_memcpy(void *dest, const void *src, size_t n) +void roc_memcpy(void *dest, const void *src, size_t n) { - return memcpy(dest, src, n); + memcpy(dest, src, n); } //-------------------------- diff --git a/compiler/test_mono/Cargo.toml b/compiler/test_mono/Cargo.toml index 9f336dbe73..fb06a403be 100644 --- a/compiler/test_mono/Cargo.toml +++ b/compiler/test_mono/Cargo.toml @@ -19,6 +19,5 @@ roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_reporting = { path = "../../reporting" } test_mono_macros = { path = "../test_mono_macros" } -pretty_assertions = "1.0.0" bumpalo = { version = "3.8.0", features = ["collections"] } indoc = "1.0.3" diff --git a/compiler/test_mono/generated/closure_in_list.txt b/compiler/test_mono/generated/closure_in_list.txt index 18496e4156..4687f8cc52 100644 --- a/compiler/test_mono/generated/closure_in_list.txt +++ b/compiler/test_mono/generated/closure_in_list.txt @@ -1,21 +1,21 @@ -procedure List.7 (#Attr.2): - let Test.8 : U64 = lowlevel ListLen #Attr.2; - ret Test.8; +procedure List.6 (#Attr.2): + let List.139 : U64 = lowlevel ListLen #Attr.2; + ret List.139; procedure Test.1 (Test.5): let Test.2 : I64 = 41i64; - let Test.12 : {I64} = Struct {Test.2}; - let Test.11 : List {I64} = Array [Test.12]; - ret Test.11; + let Test.11 : {I64} = Struct {Test.2}; + let Test.10 : List {I64} = Array [Test.11]; + ret Test.10; -procedure Test.3 (Test.10, #Attr.12): +procedure Test.3 (Test.9, #Attr.12): let Test.2 : I64 = StructAtIndex 0 #Attr.12; let Test.2 : I64 = 41i64; ret Test.2; procedure Test.0 (): - let Test.9 : {} = Struct {}; - let Test.7 : List {I64} = CallByName Test.1 Test.9; - let Test.6 : U64 = CallByName List.7 Test.7; + let Test.8 : {} = Struct {}; + let Test.7 : List {I64} = CallByName Test.1 Test.8; + let Test.6 : U64 = CallByName List.6 Test.7; dec Test.7; ret Test.6; diff --git a/compiler/test_mono/generated/dict.txt b/compiler/test_mono/generated/dict.txt index 4244fb0e0c..31ae33d719 100644 --- a/compiler/test_mono/generated/dict.txt +++ b/compiler/test_mono/generated/dict.txt @@ -1,13 +1,13 @@ -procedure Dict.2 (): - let Test.4 : Dict [] [] = lowlevel DictEmpty ; - ret Test.4; +procedure Dict.1 (): + let Dict.28 : Dict [] [] = lowlevel DictEmpty ; + ret Dict.28; -procedure Dict.8 (#Attr.2): - let Test.3 : U64 = lowlevel DictSize #Attr.2; +procedure Dict.7 (#Attr.2): + let Dict.27 : U64 = lowlevel DictSize #Attr.2; dec #Attr.2; - ret Test.3; + ret Dict.27; procedure Test.0 (): - let Test.2 : Dict [] [] = CallByName Dict.2; - let Test.1 : U64 = CallByName Dict.8 Test.2; + let Test.2 : Dict [] [] = CallByName Dict.1; + let Test.1 : U64 = CallByName Dict.7 Test.2; ret Test.1; diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt index d25fa169f9..ae00ee7d57 100644 --- a/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -1,23 +1,23 @@ -procedure List.3 (#Attr.2, #Attr.3): - let Test.20 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.20; - if Test.17 then - let Test.19 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.18 : [C {}, C {}] = Ok Test.19; - ret Test.18; +procedure List.2 (#Attr.2, #Attr.3): + let List.144 : U64 = lowlevel ListLen #Attr.2; + let List.141 : Int1 = lowlevel NumLt #Attr.3 List.144; + if List.141 then + let List.143 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.142 : [C {}, C {}] = Ok List.143; + ret List.142; else - let Test.16 : {} = Struct {}; - let Test.15 : [C {}, C {}] = Err Test.16; - ret Test.15; + let List.140 : {} = Struct {}; + let List.139 : [C {}, C {}] = Err List.140; + ret List.139; procedure Test.2 (Test.6): - let Test.24 : Str = "bar"; - ret Test.24; + let Test.18 : Str = "bar"; + ret Test.18; procedure Test.0 (): - joinpoint Test.22 Test.3: + joinpoint Test.16 Test.3: let Test.14 : U64 = 0i64; - let Test.7 : [C {}, C {}] = CallByName List.3 Test.3 Test.14; + let Test.7 : [C {}, C {}] = CallByName List.2 Test.3 Test.14; dec Test.3; let Test.11 : U8 = 1i64; let Test.12 : U8 = GetTagId Test.7; @@ -32,11 +32,11 @@ procedure Test.0 (): let Test.10 : Str = "bad!"; ret Test.10; in - let Test.25 : Int1 = false; - if Test.25 then + let Test.19 : Int1 = false; + if Test.19 then let Test.1 : List {} = Array []; - jump Test.22 Test.1; + jump Test.16 Test.1; else - let Test.23 : {} = Struct {}; - let Test.21 : List {} = Array [Test.23]; - jump Test.22 Test.21; + let Test.17 : {} = Struct {}; + let Test.15 : List {} = Array [Test.17]; + jump Test.16 Test.15; diff --git a/compiler/test_mono/generated/factorial.txt b/compiler/test_mono/generated/factorial.txt index f318bd7aa3..a0ad7c91ac 100644 --- a/compiler/test_mono/generated/factorial.txt +++ b/compiler/test_mono/generated/factorial.txt @@ -1,24 +1,24 @@ -procedure Num.23 (#Attr.2, #Attr.3): - let Test.14 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Test.14; +procedure Num.20 (#Attr.2, #Attr.3): + let Num.274 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.274; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.12 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.12; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.273; -procedure Test.1 (Test.17, Test.18): +procedure Test.1 (Test.15, Test.16): joinpoint Test.7 Test.2 Test.3: - let Test.15 : I64 = 0i64; - let Test.16 : Int1 = lowlevel Eq Test.15 Test.2; - if Test.16 then + let Test.13 : I64 = 0i64; + let Test.14 : Int1 = lowlevel Eq Test.13 Test.2; + if Test.14 then ret Test.3; else - let Test.13 : I64 = 1i64; - let Test.10 : I64 = CallByName Num.23 Test.2 Test.13; - let Test.11 : I64 = CallByName Num.24 Test.2 Test.3; + let Test.12 : I64 = 1i64; + let Test.10 : I64 = CallByName Num.20 Test.2 Test.12; + let Test.11 : I64 = CallByName Num.21 Test.2 Test.3; jump Test.7 Test.10 Test.11; in - jump Test.7 Test.17 Test.18; + jump Test.7 Test.15 Test.16; procedure Test.0 (): let Test.5 : I64 = 10i64; diff --git a/compiler/test_mono/generated/has_none.txt b/compiler/test_mono/generated/has_none.txt index 5cd44fc90f..05a4158eab 100644 --- a/compiler/test_mono/generated/has_none.txt +++ b/compiler/test_mono/generated/has_none.txt @@ -1,30 +1,30 @@ -procedure Test.3 (Test.29): - joinpoint Test.13 Test.4: - let Test.23 : Int1 = 1i64; - let Test.24 : Int1 = GetTagId Test.4; - let Test.25 : Int1 = lowlevel Eq Test.23 Test.24; - if Test.25 then - let Test.14 : Int1 = false; - ret Test.14; +procedure Test.4 (Test.30): + joinpoint Test.14 Test.5: + let Test.24 : Int1 = 1i64; + let Test.25 : Int1 = GetTagId Test.5; + let Test.26 : Int1 = lowlevel Eq Test.24 Test.25; + if Test.26 then + let Test.15 : Int1 = false; + ret Test.15; else - let Test.19 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.4; - let Test.20 : U8 = 1i64; - let Test.21 : U8 = GetTagId Test.19; - let Test.22 : Int1 = lowlevel Eq Test.20 Test.21; - if Test.22 then - let Test.15 : Int1 = true; - ret Test.15; + let Test.20 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.5; + let Test.21 : U8 = 1i64; + let Test.22 : U8 = GetTagId Test.20; + let Test.23 : Int1 = lowlevel Eq Test.21 Test.22; + if Test.23 then + let Test.16 : Int1 = true; + ret Test.16; else - let Test.7 : TODO = UnionAtIndex (Id 0) (Index 1) Test.4; - jump Test.13 Test.7; + let Test.8 : [, C [C I64, C ] *self] = UnionAtIndex (Id 0) (Index 1) Test.5; + jump Test.14 Test.8; in - jump Test.13 Test.29; + jump Test.14 Test.30; procedure Test.0 (): - let Test.28 : I64 = 3i64; - let Test.26 : [C I64, C ] = Just Test.28; - let Test.27 : TODO = Nil ; - let Test.12 : TODO = Cons Test.26 Test.27; - let Test.11 : Int1 = CallByName Test.3 Test.12; - dec Test.12; - ret Test.11; + let Test.29 : I64 = 3i64; + let Test.27 : [C I64, C ] = Just Test.29; + let Test.28 : [, C [C I64, C ] *self] = Nil ; + let Test.13 : [, C [C I64, C ] *self] = Cons Test.27 Test.28; + let Test.12 : Int1 = CallByName Test.4 Test.13; + dec Test.13; + ret Test.12; diff --git a/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/compiler/test_mono/generated/if_guard_bind_variable_false.txt index abd10f26af..cf492ae71d 100644 --- a/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -1,16 +1,16 @@ procedure Bool.7 (#Attr.2, #Attr.3): - let Test.11 : Int1 = lowlevel Eq #Attr.2 #Attr.3; - ret Test.11; + let Bool.14 : Int1 = lowlevel Eq #Attr.2 #Attr.3; + ret Bool.14; procedure Test.1 (Test.3): let Test.6 : I64 = 10i64; - joinpoint Test.8 Test.13: - if Test.13 then + joinpoint Test.8 Test.12: + if Test.12 then let Test.7 : I64 = 0i64; ret Test.7; else - let Test.12 : I64 = 42i64; - ret Test.12; + let Test.11 : I64 = 42i64; + ret Test.11; in let Test.10 : I64 = 5i64; let Test.9 : Int1 = CallByName Bool.7 Test.6 Test.10; diff --git a/compiler/test_mono/generated/ir_int_add.txt b/compiler/test_mono/generated/ir_int_add.txt index bee729da6d..5343090dd9 100644 --- a/compiler/test_mono/generated/ir_int_add.txt +++ b/compiler/test_mono/generated/ir_int_add.txt @@ -1,19 +1,19 @@ -procedure List.7 (#Attr.2): - let Test.7 : U64 = lowlevel ListLen #Attr.2; - ret Test.7; +procedure List.6 (#Attr.2): + let List.139 : U64 = lowlevel ListLen #Attr.2; + ret List.139; -procedure Num.22 (#Attr.2, #Attr.3): - let Test.5 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.5; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.275 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): - let Test.10 : U64 = 5i64; - let Test.11 : U64 = 4i64; - let Test.8 : U64 = CallByName Num.22 Test.10 Test.11; - let Test.9 : U64 = 3i64; - let Test.3 : U64 = CallByName Num.22 Test.8 Test.9; - let Test.6 : List I64 = Array [1i64, 2i64]; - let Test.4 : U64 = CallByName List.7 Test.6; - dec Test.6; - let Test.2 : U64 = CallByName Num.22 Test.3 Test.4; + let Test.8 : U64 = 5i64; + let Test.9 : U64 = 4i64; + let Test.6 : U64 = CallByName Num.19 Test.8 Test.9; + let Test.7 : U64 = 3i64; + let Test.3 : U64 = CallByName Num.19 Test.6 Test.7; + let Test.5 : List I64 = Array [1i64, 2i64]; + let Test.4 : U64 = CallByName List.6 Test.5; + dec Test.5; + let Test.2 : U64 = CallByName Num.19 Test.3 Test.4; ret Test.2; diff --git a/compiler/test_mono/generated/ir_plus.txt b/compiler/test_mono/generated/ir_plus.txt index 3bcafd0029..8a3eff1485 100644 --- a/compiler/test_mono/generated/ir_plus.txt +++ b/compiler/test_mono/generated/ir_plus.txt @@ -1,9 +1,9 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.4 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.4; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.0 (): let Test.2 : I64 = 1i64; let Test.3 : I64 = 2i64; - let Test.1 : I64 = CallByName Num.22 Test.2 Test.3; + let Test.1 : I64 = CallByName Num.19 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/ir_round.txt b/compiler/test_mono/generated/ir_round.txt index a62c2c4a09..19f78559ec 100644 --- a/compiler/test_mono/generated/ir_round.txt +++ b/compiler/test_mono/generated/ir_round.txt @@ -1,6 +1,6 @@ procedure Num.45 (#Attr.2): - let Test.3 : I64 = lowlevel NumRound #Attr.2; - ret Test.3; + let Num.273 : I64 = lowlevel NumRound #Attr.2; + ret Num.273; procedure Test.0 (): let Test.2 : Float64 = 3.6f64; diff --git a/compiler/test_mono/generated/ir_two_defs.txt b/compiler/test_mono/generated/ir_two_defs.txt index 39d09c2318..d99d01140c 100644 --- a/compiler/test_mono/generated/ir_two_defs.txt +++ b/compiler/test_mono/generated/ir_two_defs.txt @@ -1,9 +1,9 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.6; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.0 (): let Test.4 : I64 = 3i64; let Test.5 : I64 = 4i64; - let Test.3 : I64 = CallByName Num.22 Test.4 Test.5; + let Test.3 : I64 = CallByName Num.19 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/ir_when_idiv.txt b/compiler/test_mono/generated/ir_when_idiv.txt index 999d7f762e..a4c3915acf 100644 --- a/compiler/test_mono/generated/ir_when_idiv.txt +++ b/compiler/test_mono/generated/ir_when_idiv.txt @@ -1,14 +1,14 @@ procedure Num.40 (#Attr.2, #Attr.3): - let Test.15 : I64 = 0i64; - let Test.12 : Int1 = lowlevel NotEq #Attr.3 Test.15; - if Test.12 then - let Test.14 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; - let Test.13 : [C {}, C I64] = Ok Test.14; - ret Test.13; + let Num.278 : I64 = 0i64; + let Num.275 : Int1 = lowlevel NotEq #Attr.3 Num.278; + if Num.275 then + let Num.277 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3; + let Num.276 : [C {}, C I64] = Ok Num.277; + ret Num.276; else - let Test.11 : {} = Struct {}; - let Test.10 : [C {}, C I64] = Err Test.11; - ret Test.10; + let Num.274 : {} = Struct {}; + let Num.273 : [C {}, C I64] = Err Num.274; + ret Num.273; procedure Test.0 (): let Test.8 : I64 = 1000i64; diff --git a/compiler/test_mono/generated/ir_when_just.txt b/compiler/test_mono/generated/ir_when_just.txt index c3e60612d4..d6619036ab 100644 --- a/compiler/test_mono/generated/ir_when_just.txt +++ b/compiler/test_mono/generated/ir_when_just.txt @@ -1,18 +1,18 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.6; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.0 (): - let Test.11 : I64 = 41i64; - let Test.1 : [C I64, C ] = Just Test.11; - let Test.8 : U8 = 0i64; - let Test.9 : U8 = GetTagId Test.1; - let Test.10 : Int1 = lowlevel Eq Test.8 Test.9; - if Test.10 then + let Test.10 : I64 = 41i64; + let Test.1 : [C I64, C ] = Just Test.10; + let Test.7 : U8 = 0i64; + let Test.8 : U8 = GetTagId Test.1; + let Test.9 : Int1 = lowlevel Eq Test.7 Test.8; + if Test.9 then let Test.3 : I64 = UnionAtIndex (Id 0) (Index 0) Test.1; let Test.5 : I64 = 1i64; - let Test.4 : I64 = CallByName Num.22 Test.3 Test.5; + let Test.4 : I64 = CallByName Num.19 Test.3 Test.5; ret Test.4; else - let Test.7 : I64 = 1i64; - ret Test.7; + let Test.6 : I64 = 1i64; + ret Test.6; diff --git a/compiler/test_mono/generated/is_nil.txt b/compiler/test_mono/generated/is_nil.txt index 72af4853cd..9b47d797ca 100644 --- a/compiler/test_mono/generated/is_nil.txt +++ b/compiler/test_mono/generated/is_nil.txt @@ -1,18 +1,18 @@ -procedure Test.2 (Test.3): - let Test.12 : Int1 = 1i64; - let Test.13 : Int1 = GetTagId Test.3; - let Test.14 : Int1 = lowlevel Eq Test.12 Test.13; - if Test.14 then - let Test.10 : Int1 = true; - ret Test.10; - else - let Test.11 : Int1 = false; +procedure Test.3 (Test.4): + let Test.13 : Int1 = 1i64; + let Test.14 : Int1 = GetTagId Test.4; + let Test.15 : Int1 = lowlevel Eq Test.13 Test.14; + if Test.15 then + let Test.11 : Int1 = true; ret Test.11; + else + let Test.12 : Int1 = false; + ret Test.12; procedure Test.0 (): - let Test.15 : I64 = 2i64; - let Test.16 : TODO = Nil ; - let Test.9 : TODO = Cons Test.15 Test.16; - let Test.8 : Int1 = CallByName Test.2 Test.9; - dec Test.9; - ret Test.8; + let Test.16 : I64 = 2i64; + let Test.17 : [, C I64 *self] = Nil ; + let Test.10 : [, C I64 *self] = Cons Test.16 Test.17; + let Test.9 : Int1 = CallByName Test.3 Test.10; + dec Test.10; + ret Test.9; diff --git a/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt b/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt index 1cb2cc555f..ac2eaef4f3 100644 --- a/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt +++ b/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt @@ -1,40 +1,40 @@ -procedure List.10 (#Attr.2): - let Test.20 : U64 = 0i64; - let Test.21 : U64 = lowlevel ListLen #Attr.2; - let Test.16 : Int1 = lowlevel NotEq Test.20 Test.21; - if Test.16 then - let Test.19 : U64 = 0i64; - let Test.18 : I64 = lowlevel ListGetUnsafe #Attr.2 Test.19; - let Test.17 : [C Int1, C I64] = Ok Test.18; - ret Test.17; +procedure List.9 (#Attr.2): + let List.145 : U64 = 0i64; + let List.146 : U64 = lowlevel ListLen #Attr.2; + let List.141 : Int1 = lowlevel NotEq List.145 List.146; + if List.141 then + let List.144 : U64 = 0i64; + let List.143 : I64 = lowlevel ListGetUnsafe #Attr.2 List.144; + let List.142 : [C Int1, C I64] = Ok List.143; + ret List.142; else - let Test.15 : Int1 = true; - let Test.14 : [C Int1, C I64] = Err Test.15; - ret Test.14; + let List.140 : Int1 = true; + let List.139 : [C Int1, C I64] = Err List.140; + ret List.139; -procedure Str.28 (#Attr.2): +procedure Str.27 (#Attr.2): let #Attr.3 : {I64, U8} = lowlevel StrToNum #Attr.2; - let Test.9 : U8 = StructAtIndex 1 #Attr.3; - let Test.10 : U8 = 0i64; - let Test.6 : Int1 = lowlevel NumGt Test.9 Test.10; - if Test.6 then - let Test.8 : Int1 = false; - let Test.7 : [C Int1, C I64] = Err Test.8; - ret Test.7; + let Str.70 : U8 = StructAtIndex 1 #Attr.3; + let Str.71 : U8 = 0i64; + let Str.67 : Int1 = lowlevel NumGt Str.70 Str.71; + if Str.67 then + let Str.69 : Int1 = false; + let Str.68 : [C Int1, C I64] = Err Str.69; + ret Str.68; else - let Test.5 : I64 = StructAtIndex 0 #Attr.3; - let Test.4 : [C Int1, C I64] = Ok Test.5; - ret Test.4; + let Str.66 : I64 = StructAtIndex 0 #Attr.3; + let Str.65 : [C Int1, C I64] = Ok Str.66; + ret Str.65; procedure Test.0 (): - let Test.11 : Int1 = true; - if Test.11 then - let Test.13 : List I64 = Array []; - let Test.12 : [C Int1, C I64] = CallByName List.10 Test.13; - dec Test.13; - ret Test.12; + let Test.4 : Int1 = true; + if Test.4 then + let Test.6 : List I64 = Array []; + let Test.5 : [C Int1, C I64] = CallByName List.9 Test.6; + dec Test.6; + ret Test.5; else let Test.3 : Str = ""; - let Test.2 : [C Int1, C I64] = CallByName Str.28 Test.3; + let Test.2 : [C Int1, C I64] = CallByName Str.27 Test.3; dec Test.3; ret Test.2; diff --git a/compiler/test_mono/generated/issue_2810.txt b/compiler/test_mono/generated/issue_2810.txt new file mode 100644 index 0000000000..31f6b7125e --- /dev/null +++ b/compiler/test_mono/generated/issue_2810.txt @@ -0,0 +1,6 @@ +procedure Test.0 (): + let Test.19 : [C [C [C *self, C ]], C ] = SystemTool ; + let Test.17 : [C [C *self, C ]] = Job Test.19; + let Test.16 : [C [C [C *self, C ]], C ] = FromJob Test.17; + let Test.7 : [C [C *self, C ]] = Job Test.16; + ret Test.7; diff --git a/compiler/test_mono/generated/linked_list_length_twice.txt b/compiler/test_mono/generated/linked_list_length_twice.txt index 3b1d0984f3..dbed7f9abb 100644 --- a/compiler/test_mono/generated/linked_list_length_twice.txt +++ b/compiler/test_mono/generated/linked_list_length_twice.txt @@ -1,25 +1,25 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.10 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.10; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.274 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.274; -procedure Test.3 (Test.5): +procedure Test.4 (Test.6): let Test.16 : Int1 = 1i64; - let Test.17 : Int1 = GetTagId Test.5; + let Test.17 : Int1 = GetTagId Test.6; let Test.18 : Int1 = lowlevel Eq Test.16 Test.17; if Test.18 then let Test.12 : I64 = 0i64; ret Test.12; else - let Test.6 : TODO = UnionAtIndex (Id 0) (Index 1) Test.5; + let Test.7 : [, C I64 *self] = UnionAtIndex (Id 0) (Index 1) Test.6; let Test.14 : I64 = 1i64; - let Test.15 : I64 = CallByName Test.3 Test.6; - let Test.13 : I64 = CallByName Num.22 Test.14 Test.15; + let Test.15 : I64 = CallByName Test.4 Test.7; + let Test.13 : I64 = CallByName Num.19 Test.14 Test.15; ret Test.13; procedure Test.0 (): - let Test.2 : TODO = Nil ; - let Test.8 : I64 = CallByName Test.3 Test.2; - let Test.9 : I64 = CallByName Test.3 Test.2; - dec Test.2; - let Test.7 : I64 = CallByName Num.22 Test.8 Test.9; - ret Test.7; + let Test.3 : [, C I64 *self] = Nil ; + let Test.9 : I64 = CallByName Test.4 Test.3; + let Test.10 : I64 = CallByName Test.4 Test.3; + dec Test.3; + let Test.8 : I64 = CallByName Num.19 Test.9 Test.10; + ret Test.8; diff --git a/compiler/test_mono/generated/list_append.txt b/compiler/test_mono/generated/list_append.txt index c899f2abd7..93bb4798f8 100644 --- a/compiler/test_mono/generated/list_append.txt +++ b/compiler/test_mono/generated/list_append.txt @@ -1,9 +1,9 @@ -procedure List.5 (#Attr.2, #Attr.3): - let Test.4 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; - ret Test.4; +procedure List.4 (#Attr.2, #Attr.3): + let List.139 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; + ret List.139; procedure Test.0 (): let Test.2 : List I64 = Array [1i64]; let Test.3 : I64 = 2i64; - let Test.1 : List I64 = CallByName List.5 Test.2 Test.3; + let Test.1 : List I64 = CallByName List.4 Test.2 Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/list_append_closure.txt b/compiler/test_mono/generated/list_append_closure.txt index 43227f7089..80d4aae9c5 100644 --- a/compiler/test_mono/generated/list_append_closure.txt +++ b/compiler/test_mono/generated/list_append_closure.txt @@ -1,10 +1,10 @@ -procedure List.5 (#Attr.2, #Attr.3): - let Test.7 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; - ret Test.7; +procedure List.4 (#Attr.2, #Attr.3): + let List.139 : List I64 = lowlevel ListAppend #Attr.2 #Attr.3; + ret List.139; procedure Test.1 (Test.2): let Test.6 : I64 = 42i64; - let Test.5 : List I64 = CallByName List.5 Test.2 Test.6; + let Test.5 : List I64 = CallByName List.4 Test.2 Test.6; ret Test.5; procedure Test.0 (): diff --git a/compiler/test_mono/generated/list_cannot_update_inplace.txt b/compiler/test_mono/generated/list_cannot_update_inplace.txt index 42ba0d2a8e..273525877f 100644 --- a/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,50 +1,45 @@ -procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.24 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.24; - if Test.17 then - let Test.19 : {List I64, I64} = CallByName List.58 #Attr.2 #Attr.3 #Attr.4; - let Test.18 : List I64 = StructAtIndex 0 Test.19; - inc Test.18; - dec Test.19; - ret Test.18; +procedure List.3 (List.63, List.64, List.65): + let List.142 : {List I64, I64} = CallByName List.57 List.63 List.64 List.65; + let List.141 : List I64 = StructAtIndex 0 List.142; + inc List.141; + dec List.142; + ret List.141; + +procedure List.57 (#Attr.2, #Attr.3, #Attr.4): + let List.147 : U64 = lowlevel ListLen #Attr.2; + let List.145 : Int1 = lowlevel NumLt #Attr.3 List.147; + if List.145 then + let List.146 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.146; else - ret #Attr.2; + let List.144 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret List.144; -procedure List.58 (#Attr.2, #Attr.3, #Attr.4): - let Test.23 : U64 = lowlevel ListLen #Attr.2; - let Test.21 : Int1 = lowlevel NumLt #Attr.3 Test.23; - if Test.21 then - let Test.22 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret Test.22; - else - let Test.20 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; - ret Test.20; +procedure List.6 (#Attr.2): + let List.140 : U64 = lowlevel ListLen #Attr.2; + ret List.140; -procedure List.7 (#Attr.2): - let Test.9 : U64 = lowlevel ListLen #Attr.2; - ret Test.9; - -procedure Num.22 (#Attr.2, #Attr.3): - let Test.7 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.7; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.1 (): - let Test.10 : List I64 = Array [1i64, 2i64, 3i64]; - ret Test.10; + let Test.8 : List I64 = Array [1i64, 2i64, 3i64]; + ret Test.8; procedure Test.2 (Test.3): - let Test.14 : U64 = 0i64; - let Test.15 : I64 = 0i64; - let Test.13 : List I64 = CallByName List.4 Test.3 Test.14 Test.15; - ret Test.13; + let Test.12 : U64 = 0i64; + let Test.13 : I64 = 0i64; + let Test.11 : List I64 = CallByName List.3 Test.3 Test.12 Test.13; + ret Test.11; procedure Test.0 (): - let Test.12 : List I64 = CallByName Test.1; - let Test.11 : List I64 = CallByName Test.2 Test.12; - let Test.5 : U64 = CallByName List.7 Test.11; - dec Test.11; - let Test.8 : List I64 = CallByName Test.1; - let Test.6 : U64 = CallByName List.7 Test.8; - dec Test.8; - let Test.4 : U64 = CallByName Num.22 Test.5 Test.6; + let Test.10 : List I64 = CallByName Test.1; + let Test.9 : List I64 = CallByName Test.2 Test.10; + let Test.5 : U64 = CallByName List.6 Test.9; + dec Test.9; + let Test.7 : List I64 = CallByName Test.1; + let Test.6 : U64 = CallByName List.6 Test.7; + dec Test.7; + let Test.4 : U64 = CallByName Num.19 Test.5 Test.6; ret Test.4; diff --git a/compiler/test_mono/generated/list_get.txt b/compiler/test_mono/generated/list_get.txt index b8d8636c97..7e5e29fff8 100644 --- a/compiler/test_mono/generated/list_get.txt +++ b/compiler/test_mono/generated/list_get.txt @@ -1,19 +1,19 @@ -procedure List.3 (#Attr.2, #Attr.3): - let Test.13 : U64 = lowlevel ListLen #Attr.2; - let Test.10 : Int1 = lowlevel NumLt #Attr.3 Test.13; - if Test.10 then - let Test.12 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.11 : [C {}, C I64] = Ok Test.12; - ret Test.11; +procedure List.2 (#Attr.2, #Attr.3): + let List.144 : U64 = lowlevel ListLen #Attr.2; + let List.141 : Int1 = lowlevel NumLt #Attr.3 List.144; + if List.141 then + let List.143 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.142 : [C {}, C I64] = Ok List.143; + ret List.142; else - let Test.9 : {} = Struct {}; - let Test.8 : [C {}, C I64] = Err Test.9; - ret Test.8; + let List.140 : {} = Struct {}; + let List.139 : [C {}, C I64] = Err List.140; + ret List.139; procedure Test.1 (Test.2): let Test.6 : List I64 = Array [1i64, 2i64, 3i64]; let Test.7 : U64 = 0i64; - let Test.5 : [C {}, C I64] = CallByName List.3 Test.6 Test.7; + let Test.5 : [C {}, C I64] = CallByName List.2 Test.6 Test.7; dec Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/list_len.txt b/compiler/test_mono/generated/list_len.txt index 13d10a54ce..b3c1b27a5b 100644 --- a/compiler/test_mono/generated/list_len.txt +++ b/compiler/test_mono/generated/list_len.txt @@ -1,21 +1,21 @@ -procedure List.7 (#Attr.2): - let Test.10 : U64 = lowlevel ListLen #Attr.2; - ret Test.10; +procedure List.6 (#Attr.2): + let List.139 : U64 = lowlevel ListLen #Attr.2; + ret List.139; -procedure List.7 (#Attr.2): - let Test.8 : U64 = lowlevel ListLen #Attr.2; - ret Test.8; +procedure List.6 (#Attr.2): + let List.140 : U64 = lowlevel ListLen #Attr.2; + ret List.140; -procedure Num.22 (#Attr.2, #Attr.3): - let Test.6 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.6; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.0 (): - let Test.9 : List I64 = Array [1i64, 2i64, 3i64]; - let Test.4 : U64 = CallByName List.7 Test.9; - dec Test.9; - let Test.7 : List Float64 = Array [1f64]; - let Test.5 : U64 = CallByName List.7 Test.7; + let Test.7 : List I64 = Array [1i64, 2i64, 3i64]; + let Test.4 : U64 = CallByName List.6 Test.7; dec Test.7; - let Test.3 : U64 = CallByName Num.22 Test.4 Test.5; + let Test.6 : List Float64 = Array [1f64]; + let Test.5 : U64 = CallByName List.6 Test.6; + dec Test.6; + let Test.3 : U64 = CallByName Num.19 Test.4 Test.5; ret Test.3; diff --git a/compiler/test_mono/generated/list_map_closure_borrows.txt b/compiler/test_mono/generated/list_map_closure_borrows.txt new file mode 100644 index 0000000000..e816d7c4a1 --- /dev/null +++ b/compiler/test_mono/generated/list_map_closure_borrows.txt @@ -0,0 +1,61 @@ +procedure List.2 (#Attr.2, #Attr.3): + let List.145 : U64 = lowlevel ListLen #Attr.2; + let List.142 : Int1 = lowlevel NumLt #Attr.3 List.145; + if List.142 then + let List.144 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.143 : [C {}, C Str] = Ok List.144; + ret List.143; + else + let List.141 : {} = Struct {}; + let List.140 : [C {}, C Str] = Err List.141; + ret List.140; + +procedure List.5 (#Attr.2, #Attr.3): + let List.146 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; + ret List.146; + +procedure Str.16 (#Attr.2, #Attr.3): + let Str.65 : Str = lowlevel StrRepeat #Attr.2 #Attr.3; + ret Str.65; + +procedure Str.3 (#Attr.2, #Attr.3): + let Str.66 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.66; + +procedure Test.1 (): + let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; + let Test.22 : Str = "g"; + let Test.20 : Str = CallByName Str.3 Test.21 Test.22; + dec Test.22; + let Test.19 : List Str = Array [Test.20]; + ret Test.19; + +procedure Test.2 (): + let Test.15 : List Str = CallByName Test.1; + let Test.16 : {} = Struct {}; + let Test.14 : List Str = CallByName List.5 Test.15 Test.16; + dec Test.15; + ret Test.14; + +procedure Test.3 (Test.4): + let Test.18 : U64 = 2i64; + let Test.17 : Str = CallByName Str.16 Test.4 Test.18; + ret Test.17; + +procedure Test.0 (): + let Test.12 : List Str = CallByName Test.2; + let Test.13 : U64 = 0i64; + let Test.6 : [C {}, C Str] = CallByName List.2 Test.12 Test.13; + dec Test.12; + let Test.9 : U8 = 1i64; + let Test.10 : U8 = GetTagId Test.6; + let Test.11 : Int1 = lowlevel Eq Test.9 Test.10; + if Test.11 then + let Test.5 : Str = UnionAtIndex (Id 1) (Index 0) Test.6; + inc Test.5; + dec Test.6; + ret Test.5; + else + dec Test.6; + let Test.8 : Str = "Hello, World!\n"; + ret Test.8; diff --git a/compiler/test_mono/generated/list_map_closure_owns.txt b/compiler/test_mono/generated/list_map_closure_owns.txt new file mode 100644 index 0000000000..446a193f6c --- /dev/null +++ b/compiler/test_mono/generated/list_map_closure_owns.txt @@ -0,0 +1,60 @@ +procedure List.2 (#Attr.2, #Attr.3): + let List.145 : U64 = lowlevel ListLen #Attr.2; + let List.142 : Int1 = lowlevel NumLt #Attr.3 List.145; + if List.142 then + let List.144 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.143 : [C {}, C Str] = Ok List.144; + ret List.143; + else + let List.141 : {} = Struct {}; + let List.140 : [C {}, C Str] = Err List.141; + ret List.140; + +procedure List.5 (#Attr.2, #Attr.3): + inc #Attr.2; + let List.146 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; + decref #Attr.2; + ret List.146; + +procedure Str.3 (#Attr.2, #Attr.3): + let Str.66 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.66; + +procedure Test.1 (): + let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; + let Test.22 : Str = "g"; + let Test.20 : Str = CallByName Str.3 Test.21 Test.22; + dec Test.22; + let Test.19 : List Str = Array [Test.20]; + ret Test.19; + +procedure Test.2 (): + let Test.15 : List Str = CallByName Test.1; + let Test.16 : {} = Struct {}; + let Test.14 : List Str = CallByName List.5 Test.15 Test.16; + dec Test.15; + ret Test.14; + +procedure Test.3 (Test.4): + let Test.18 : Str = "!"; + let Test.17 : Str = CallByName Str.3 Test.4 Test.18; + dec Test.18; + ret Test.17; + +procedure Test.0 (): + let Test.12 : List Str = CallByName Test.2; + let Test.13 : U64 = 0i64; + let Test.6 : [C {}, C Str] = CallByName List.2 Test.12 Test.13; + dec Test.12; + let Test.9 : U8 = 1i64; + let Test.10 : U8 = GetTagId Test.6; + let Test.11 : Int1 = lowlevel Eq Test.9 Test.10; + if Test.11 then + let Test.5 : Str = UnionAtIndex (Id 1) (Index 0) Test.6; + inc Test.5; + dec Test.6; + ret Test.5; + else + dec Test.6; + let Test.8 : Str = "Hello, World!\n"; + ret Test.8; diff --git a/compiler/test_mono/generated/list_pass_to_function.txt b/compiler/test_mono/generated/list_pass_to_function.txt index 99a6e73255..05dcf76ea7 100644 --- a/compiler/test_mono/generated/list_pass_to_function.txt +++ b/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,29 +1,24 @@ -procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.16 : U64 = lowlevel ListLen #Attr.2; - let Test.9 : Int1 = lowlevel NumLt #Attr.3 Test.16; - if Test.9 then - let Test.11 : {List I64, I64} = CallByName List.58 #Attr.2 #Attr.3 #Attr.4; - let Test.10 : List I64 = StructAtIndex 0 Test.11; - inc Test.10; - dec Test.11; - ret Test.10; - else - ret #Attr.2; +procedure List.3 (List.63, List.64, List.65): + let List.140 : {List I64, I64} = CallByName List.57 List.63 List.64 List.65; + let List.139 : List I64 = StructAtIndex 0 List.140; + inc List.139; + dec List.140; + ret List.139; -procedure List.58 (#Attr.2, #Attr.3, #Attr.4): - let Test.15 : U64 = lowlevel ListLen #Attr.2; - let Test.13 : Int1 = lowlevel NumLt #Attr.3 Test.15; - if Test.13 then - let Test.14 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret Test.14; +procedure List.57 (#Attr.2, #Attr.3, #Attr.4): + let List.145 : U64 = lowlevel ListLen #Attr.2; + let List.143 : Int1 = lowlevel NumLt #Attr.3 List.145; + if List.143 then + let List.144 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.144; else - let Test.12 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; - ret Test.12; + let List.142 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret List.142; procedure Test.2 (Test.3): let Test.6 : U64 = 0i64; let Test.7 : I64 = 0i64; - let Test.5 : List I64 = CallByName List.4 Test.3 Test.6 Test.7; + let Test.5 : List I64 = CallByName List.3 Test.3 Test.6 Test.7; ret Test.5; procedure Test.0 (): diff --git a/compiler/test_mono/generated/list_sort_asc.txt b/compiler/test_mono/generated/list_sort_asc.txt new file mode 100644 index 0000000000..50253ab644 --- /dev/null +++ b/compiler/test_mono/generated/list_sort_asc.txt @@ -0,0 +1,22 @@ +procedure List.28 (#Attr.2, #Attr.3): + let List.143 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3; + let Bool.14 : Int1 = lowlevel ListIsUnique #Attr.2; + if Bool.14 then + ret List.143; + else + decref #Attr.2; + ret List.143; + +procedure List.54 (List.98): + let List.141 : {} = Struct {}; + let List.140 : List I64 = CallByName List.28 List.98 List.141; + ret List.140; + +procedure Num.46 (#Attr.2, #Attr.3): + let Num.273 : U8 = lowlevel NumCompare #Attr.2 #Attr.3; + ret Num.273; + +procedure Test.0 (): + let Test.2 : List I64 = Array [4i64, 3i64, 2i64, 1i64]; + let Test.1 : List I64 = CallByName List.54 Test.2; + ret Test.1; diff --git a/compiler/test_mono/generated/monomorphized_ints_aliased.txt b/compiler/test_mono/generated/monomorphized_ints_aliased.txt index b461c2a04e..d9433bf0af 100644 --- a/compiler/test_mono/generated/monomorphized_ints_aliased.txt +++ b/compiler/test_mono/generated/monomorphized_ints_aliased.txt @@ -1,21 +1,21 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.12 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.12; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.5 (Test.7, Test.8): - let Test.18 : U64 = 1i64; - ret Test.18; + let Test.17 : U64 = 1i64; + ret Test.17; procedure Test.6 (Test.7, Test.8): - let Test.15 : U64 = 1i64; - ret Test.15; + let Test.14 : U64 = 1i64; + ret Test.14; procedure Test.0 (): - let Test.16 : U8 = 100i64; - let Test.17 : U32 = 100i64; - let Test.10 : U64 = CallByName Test.5 Test.16 Test.17; - let Test.13 : U32 = 100i64; - let Test.14 : U8 = 100i64; - let Test.11 : U64 = CallByName Test.6 Test.13 Test.14; - let Test.9 : U64 = CallByName Num.22 Test.10 Test.11; + let Test.15 : U8 = 100i64; + let Test.16 : U32 = 100i64; + let Test.10 : U64 = CallByName Test.5 Test.15 Test.16; + let Test.12 : U32 = 100i64; + let Test.13 : U8 = 100i64; + let Test.11 : U64 = CallByName Test.6 Test.12 Test.13; + let Test.9 : U64 = CallByName Num.19 Test.10 Test.11; ret Test.9; diff --git a/compiler/test_mono/generated/nested_pattern_match.txt b/compiler/test_mono/generated/nested_pattern_match.txt index 6bb9eebf35..272fa01449 100644 --- a/compiler/test_mono/generated/nested_pattern_match.txt +++ b/compiler/test_mono/generated/nested_pattern_match.txt @@ -1,30 +1,30 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.0 (): - let Test.20 : I64 = 41i64; - let Test.19 : [C I64, C ] = Just Test.20; - let Test.2 : [C [C I64, C ], C ] = Just Test.19; - joinpoint Test.16: - let Test.9 : I64 = 1i64; - ret Test.9; + let Test.19 : I64 = 41i64; + let Test.18 : [C I64, C ] = Just Test.19; + let Test.2 : [C [C I64, C ], C ] = Just Test.18; + joinpoint Test.15: + let Test.8 : I64 = 1i64; + ret Test.8; in - let Test.14 : U8 = 0i64; - let Test.15 : U8 = GetTagId Test.2; - let Test.18 : Int1 = lowlevel Eq Test.14 Test.15; - if Test.18 then - let Test.11 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 : U8 = 0i64; - let Test.13 : U8 = GetTagId Test.11; - let Test.17 : Int1 = lowlevel Eq Test.12 Test.13; - if Test.17 then - let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.13 : U8 = 0i64; + let Test.14 : U8 = GetTagId Test.2; + let Test.17 : Int1 = lowlevel Eq Test.13 Test.14; + if Test.17 then + let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.11 : U8 = 0i64; + let Test.12 : U8 = GetTagId Test.10; + let Test.16 : Int1 = lowlevel Eq Test.11 Test.12; + if Test.16 then + let Test.9 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.9; let Test.7 : I64 = 1i64; - let Test.6 : I64 = CallByName Num.22 Test.5 Test.7; + let Test.6 : I64 = CallByName Num.19 Test.5 Test.7; ret Test.6; else - jump Test.16; + jump Test.15; else - jump Test.16; + jump Test.15; diff --git a/compiler/test_mono/generated/opaque_assign_to_symbol.txt b/compiler/test_mono/generated/opaque_assign_to_symbol.txt new file mode 100644 index 0000000000..23eee2ceea --- /dev/null +++ b/compiler/test_mono/generated/opaque_assign_to_symbol.txt @@ -0,0 +1,8 @@ +procedure Test.3 (Test.4): + let Test.8 : [C {}, C U8] = Ok Test.4; + ret Test.8; + +procedure Test.0 (): + let Test.7 : U8 = 98i64; + let Test.6 : [C {}, C U8] = CallByName Test.3 Test.7; + ret Test.6; diff --git a/compiler/test_mono/generated/optional_when.txt b/compiler/test_mono/generated/optional_when.txt index b2649e0a96..7eaa7288b5 100644 --- a/compiler/test_mono/generated/optional_when.txt +++ b/compiler/test_mono/generated/optional_when.txt @@ -1,11 +1,11 @@ -procedure Num.24 (#Attr.2, #Attr.3): - let Test.18 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.18; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.275 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.6): - let Test.22 : Int1 = false; - let Test.23 : Int1 = lowlevel Eq Test.22 Test.6; - if Test.23 then + let Test.21 : Int1 = false; + let Test.22 : Int1 = lowlevel Eq Test.21 Test.6; + if Test.22 then let Test.8 : I64 = 3i64; ret Test.8; else @@ -13,10 +13,10 @@ procedure Test.1 (Test.6): ret Test.10; procedure Test.1 (Test.6): - let Test.30 : Int1 = StructAtIndex 1 Test.6; - let Test.31 : Int1 = false; - let Test.32 : Int1 = lowlevel Eq Test.31 Test.30; - if Test.32 then + let Test.29 : Int1 = StructAtIndex 1 Test.6; + let Test.30 : Int1 = false; + let Test.31 : Int1 = lowlevel Eq Test.30 Test.29; + if Test.31 then let Test.8 : I64 = StructAtIndex 0 Test.6; ret Test.8; else @@ -24,19 +24,19 @@ procedure Test.1 (Test.6): ret Test.10; procedure Test.0 (): - let Test.40 : I64 = 7i64; - let Test.41 : Int1 = false; - let Test.39 : {I64, Int1} = Struct {Test.40, Test.41}; - let Test.35 : I64 = CallByName Test.1 Test.39; - let Test.38 : Int1 = false; - let Test.36 : I64 = CallByName Test.1 Test.38; - let Test.25 : I64 = CallByName Num.24 Test.35 Test.36; - let Test.33 : I64 = 11i64; - let Test.34 : Int1 = true; - let Test.27 : {I64, Int1} = Struct {Test.33, Test.34}; - let Test.26 : I64 = CallByName Test.1 Test.27; - let Test.16 : I64 = CallByName Num.24 Test.25 Test.26; - let Test.24 : Int1 = true; - let Test.17 : I64 = CallByName Test.1 Test.24; - let Test.15 : I64 = CallByName Num.24 Test.16 Test.17; + let Test.39 : I64 = 7i64; + let Test.40 : Int1 = false; + let Test.38 : {I64, Int1} = Struct {Test.39, Test.40}; + let Test.34 : I64 = CallByName Test.1 Test.38; + let Test.37 : Int1 = false; + let Test.35 : I64 = CallByName Test.1 Test.37; + let Test.24 : I64 = CallByName Num.21 Test.34 Test.35; + let Test.32 : I64 = 11i64; + let Test.33 : Int1 = true; + let Test.26 : {I64, Int1} = Struct {Test.32, Test.33}; + let Test.25 : I64 = CallByName Test.1 Test.26; + let Test.16 : I64 = CallByName Num.21 Test.24 Test.25; + let Test.23 : Int1 = true; + let Test.17 : I64 = CallByName Test.1 Test.23; + let Test.15 : I64 = CallByName Num.21 Test.16 Test.17; ret Test.15; diff --git a/compiler/test_mono/generated/peano.txt b/compiler/test_mono/generated/peano.txt index bee928bf10..8b7370a838 100644 --- a/compiler/test_mono/generated/peano.txt +++ b/compiler/test_mono/generated/peano.txt @@ -1,6 +1,6 @@ procedure Test.0 (): - let Test.10 : TODO = Z ; - let Test.9 : TODO = S Test.10; - let Test.8 : TODO = S Test.9; - let Test.2 : TODO = S Test.8; - ret Test.2; + let Test.11 : [, C *self] = Z ; + let Test.10 : [, C *self] = S Test.11; + let Test.9 : [, C *self] = S Test.10; + let Test.3 : [, C *self] = S Test.9; + ret Test.3; diff --git a/compiler/test_mono/generated/peano1.txt b/compiler/test_mono/generated/peano1.txt index 8d6b38bd55..63db968efc 100644 --- a/compiler/test_mono/generated/peano1.txt +++ b/compiler/test_mono/generated/peano1.txt @@ -1,15 +1,15 @@ procedure Test.0 (): - let Test.14 : TODO = Z ; - let Test.13 : TODO = S Test.14; - let Test.12 : TODO = S Test.13; - let Test.2 : TODO = S Test.12; - let Test.9 : Int1 = 1i64; - let Test.10 : Int1 = GetTagId Test.2; - dec Test.2; - let Test.11 : Int1 = lowlevel Eq Test.9 Test.10; - if Test.11 then - let Test.7 : I64 = 0i64; - ret Test.7; - else - let Test.8 : I64 = 1i64; + let Test.15 : [, C *self] = Z ; + let Test.14 : [, C *self] = S Test.15; + let Test.13 : [, C *self] = S Test.14; + let Test.3 : [, C *self] = S Test.13; + let Test.10 : Int1 = 1i64; + let Test.11 : Int1 = GetTagId Test.3; + dec Test.3; + let Test.12 : Int1 = lowlevel Eq Test.10 Test.11; + if Test.12 then + let Test.8 : I64 = 0i64; ret Test.8; + else + let Test.9 : I64 = 1i64; + ret Test.9; diff --git a/compiler/test_mono/generated/peano2.txt b/compiler/test_mono/generated/peano2.txt index a708c54baf..73dd42687a 100644 --- a/compiler/test_mono/generated/peano2.txt +++ b/compiler/test_mono/generated/peano2.txt @@ -1,26 +1,26 @@ procedure Test.0 (): - let Test.20 : TODO = Z ; - let Test.19 : TODO = S Test.20; - let Test.18 : TODO = S Test.19; - let Test.2 : TODO = S Test.18; - let Test.15 : Int1 = 0i64; - let Test.16 : Int1 = GetTagId Test.2; - let Test.17 : Int1 = lowlevel Eq Test.15 Test.16; - if Test.17 then - let Test.11 : TODO = UnionAtIndex (Id 0) (Index 0) Test.2; - inc Test.11; - dec Test.2; - let Test.12 : Int1 = 0i64; - let Test.13 : Int1 = GetTagId Test.11; - dec Test.11; - let Test.14 : Int1 = lowlevel Eq Test.12 Test.13; - if Test.14 then - let Test.7 : I64 = 1i64; - ret Test.7; - else - let Test.8 : I64 = 0i64; + let Test.21 : [, C *self] = Z ; + let Test.20 : [, C *self] = S Test.21; + let Test.19 : [, C *self] = S Test.20; + let Test.3 : [, C *self] = S Test.19; + let Test.16 : Int1 = 0i64; + let Test.17 : Int1 = GetTagId Test.3; + let Test.18 : Int1 = lowlevel Eq Test.16 Test.17; + if Test.18 then + let Test.12 : [, C *self] = UnionAtIndex (Id 0) (Index 0) Test.3; + inc Test.12; + dec Test.3; + let Test.13 : Int1 = 0i64; + let Test.14 : Int1 = GetTagId Test.12; + dec Test.12; + let Test.15 : Int1 = lowlevel Eq Test.13 Test.14; + if Test.15 then + let Test.8 : I64 = 1i64; ret Test.8; + else + let Test.9 : I64 = 0i64; + ret Test.9; else - dec Test.2; - let Test.9 : I64 = 0i64; - ret Test.9; + dec Test.3; + let Test.10 : I64 = 0i64; + ret Test.10; diff --git a/compiler/test_mono/generated/quicksort_help.txt b/compiler/test_mono/generated/quicksort_help.txt index c9bad93164..2c00aa804b 100644 --- a/compiler/test_mono/generated/quicksort_help.txt +++ b/compiler/test_mono/generated/quicksort_help.txt @@ -1,37 +1,37 @@ +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; + +procedure Num.20 (#Attr.2, #Attr.3): + let Num.274 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.274; + procedure Num.22 (#Attr.2, #Attr.3): - let Test.19 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.19; + let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.275; -procedure Num.23 (#Attr.2, #Attr.3): - let Test.22 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Test.22; - -procedure Num.25 (#Attr.2, #Attr.3): - let Test.26 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Test.26; - -procedure Test.1 (Test.27, Test.28, Test.29): +procedure Test.1 (Test.24, Test.25, Test.26): joinpoint Test.12 Test.2 Test.3 Test.4: - let Test.14 : Int1 = CallByName Num.25 Test.3 Test.4; + let Test.14 : Int1 = CallByName Num.22 Test.3 Test.4; if Test.14 then dec Test.2; - let Test.25 : List [] = Array []; - let Test.24 : I64 = 0i64; - let Test.23 : {I64, List []} = Struct {Test.24, Test.25}; - let Test.5 : I64 = StructAtIndex 0 Test.23; - let Test.6 : List [] = StructAtIndex 1 Test.23; + let Test.23 : List [] = Array []; + let Test.22 : I64 = 0i64; + let Test.21 : {I64, List []} = Struct {Test.22, Test.23}; + let Test.5 : I64 = StructAtIndex 0 Test.21; + let Test.6 : List [] = StructAtIndex 1 Test.21; inc Test.6; - dec Test.23; - let Test.21 : I64 = 1i64; - let Test.20 : I64 = CallByName Num.23 Test.5 Test.21; - let Test.16 : List I64 = CallByName Test.1 Test.6 Test.3 Test.20; + dec Test.21; + let Test.20 : I64 = 1i64; + let Test.19 : I64 = CallByName Num.20 Test.5 Test.20; + let Test.16 : List I64 = CallByName Test.1 Test.6 Test.3 Test.19; let Test.18 : I64 = 1i64; - let Test.17 : I64 = CallByName Num.22 Test.5 Test.18; + let Test.17 : I64 = CallByName Num.19 Test.5 Test.18; jump Test.12 Test.16 Test.17 Test.4; else ret Test.2; in - jump Test.12 Test.27 Test.28 Test.29; + jump Test.12 Test.24 Test.25 Test.26; procedure Test.0 (): let Test.9 : List I64 = Array []; diff --git a/compiler/test_mono/generated/quicksort_swap.txt b/compiler/test_mono/generated/quicksort_swap.txt index 467ec71cf3..e435934819 100644 --- a/compiler/test_mono/generated/quicksort_swap.txt +++ b/compiler/test_mono/generated/quicksort_swap.txt @@ -1,72 +1,67 @@ -procedure List.3 (#Attr.2, #Attr.3): - let Test.42 : U64 = lowlevel ListLen #Attr.2; - let Test.39 : Int1 = lowlevel NumLt #Attr.3 Test.42; - if Test.39 then - let Test.41 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.40 : [C {}, C I64] = Ok Test.41; - ret Test.40; +procedure List.2 (#Attr.2, #Attr.3): + let List.156 : U64 = lowlevel ListLen #Attr.2; + let List.153 : Int1 = lowlevel NumLt #Attr.3 List.156; + if List.153 then + let List.155 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.154 : [C {}, C I64] = Ok List.155; + ret List.154; else - let Test.38 : {} = Struct {}; - let Test.37 : [C {}, C I64] = Err Test.38; - ret Test.37; + let List.152 : {} = Struct {}; + let List.151 : [C {}, C I64] = Err List.152; + ret List.151; -procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.20 : U64 = lowlevel ListLen #Attr.2; - let Test.13 : Int1 = lowlevel NumLt #Attr.3 Test.20; - if Test.13 then - let Test.15 : {List I64, I64} = CallByName List.58 #Attr.2 #Attr.3 #Attr.4; - let Test.14 : List I64 = StructAtIndex 0 Test.15; - inc Test.14; - dec Test.15; - ret Test.14; - else - ret #Attr.2; +procedure List.3 (List.63, List.64, List.65): + let List.143 : {List I64, I64} = CallByName List.57 List.63 List.64 List.65; + let List.142 : List I64 = StructAtIndex 0 List.143; + inc List.142; + dec List.143; + ret List.142; -procedure List.58 (#Attr.2, #Attr.3, #Attr.4): - let Test.19 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.19; - if Test.17 then - let Test.18 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret Test.18; +procedure List.57 (#Attr.2, #Attr.3, #Attr.4): + let List.160 : U64 = lowlevel ListLen #Attr.2; + let List.158 : Int1 = lowlevel NumLt #Attr.3 List.160; + if List.158 then + let List.159 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.159; else - let Test.16 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; - ret Test.16; + let List.157 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret List.157; procedure Test.1 (Test.2): - let Test.43 : U64 = 0i64; - let Test.35 : [C {}, C I64] = CallByName List.3 Test.2 Test.43; - let Test.36 : U64 = 0i64; - let Test.34 : [C {}, C I64] = CallByName List.3 Test.2 Test.36; - let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.34, Test.35}; - joinpoint Test.31: - let Test.22 : List I64 = Array []; - ret Test.22; + let Test.28 : U64 = 0i64; + let Test.26 : [C {}, C I64] = CallByName List.2 Test.2 Test.28; + let Test.27 : U64 = 0i64; + let Test.25 : [C {}, C I64] = CallByName List.2 Test.2 Test.27; + let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.25, Test.26}; + joinpoint Test.22: + let Test.13 : List I64 = Array []; + ret Test.13; in - let Test.28 : [C {}, C I64] = StructAtIndex 1 Test.8; - let Test.29 : U8 = 1i64; - let Test.30 : U8 = GetTagId Test.28; - let Test.33 : Int1 = lowlevel Eq Test.29 Test.30; - if Test.33 then - let Test.25 : [C {}, C I64] = StructAtIndex 0 Test.8; - let Test.26 : U8 = 1i64; - let Test.27 : U8 = GetTagId Test.25; - let Test.32 : Int1 = lowlevel Eq Test.26 Test.27; - if Test.32 then - let Test.24 : [C {}, C I64] = StructAtIndex 0 Test.8; - let Test.4 : I64 = UnionAtIndex (Id 1) (Index 0) Test.24; - let Test.23 : [C {}, C I64] = StructAtIndex 1 Test.8; - let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) Test.23; - let Test.21 : U64 = 0i64; - let Test.10 : List I64 = CallByName List.4 Test.2 Test.21 Test.5; + let Test.19 : [C {}, C I64] = StructAtIndex 1 Test.8; + let Test.20 : U8 = 1i64; + let Test.21 : U8 = GetTagId Test.19; + let Test.24 : Int1 = lowlevel Eq Test.20 Test.21; + if Test.24 then + let Test.16 : [C {}, C I64] = StructAtIndex 0 Test.8; + let Test.17 : U8 = 1i64; + let Test.18 : U8 = GetTagId Test.16; + let Test.23 : Int1 = lowlevel Eq Test.17 Test.18; + if Test.23 then + let Test.15 : [C {}, C I64] = StructAtIndex 0 Test.8; + let Test.4 : I64 = UnionAtIndex (Id 1) (Index 0) Test.15; + let Test.14 : [C {}, C I64] = StructAtIndex 1 Test.8; + let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) Test.14; + let Test.12 : U64 = 0i64; + let Test.10 : List I64 = CallByName List.3 Test.2 Test.12 Test.5; let Test.11 : U64 = 0i64; - let Test.9 : List I64 = CallByName List.4 Test.10 Test.11 Test.4; + let Test.9 : List I64 = CallByName List.3 Test.10 Test.11 Test.4; ret Test.9; else dec Test.2; - jump Test.31; + jump Test.22; else dec Test.2; - jump Test.31; + jump Test.22; procedure Test.0 (): let Test.7 : List I64 = Array [1i64, 2i64]; diff --git a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index efbc0ca6e2..82db9ad2db 100644 --- a/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -1,16 +1,16 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.1 (Test.4): let Test.2 : I64 = StructAtIndex 0 Test.4; let Test.3 : I64 = StructAtIndex 1 Test.4; - let Test.7 : I64 = CallByName Num.22 Test.2 Test.3; + let Test.7 : I64 = CallByName Num.19 Test.2 Test.3; ret Test.7; procedure Test.0 (): - let Test.9 : I64 = 4i64; - let Test.10 : I64 = 9i64; - let Test.6 : {I64, I64} = Struct {Test.9, Test.10}; + let Test.8 : I64 = 4i64; + let Test.9 : I64 = 9i64; + let Test.6 : {I64, I64} = Struct {Test.8, Test.9}; let Test.5 : I64 = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/compiler/test_mono/generated/record_optional_field_function_use_default.txt index b3a418d3f3..6adb8079f7 100644 --- a/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,13 +1,13 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.9 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.9; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.1 (Test.4): let Test.8 : I64 = 10i64; - let Test.7 : I64 = CallByName Num.22 Test.8 Test.4; + let Test.7 : I64 = CallByName Num.19 Test.8 Test.4; ret Test.7; procedure Test.0 (): - let Test.10 : I64 = 9i64; - let Test.5 : I64 = CallByName Test.1 Test.10; + let Test.9 : I64 = 9i64; + let Test.5 : I64 = CallByName Test.1 Test.9; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index 4c10690d37..959902f6d8 100644 --- a/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -1,16 +1,16 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.1 (Test.2): let Test.3 : I64 = StructAtIndex 0 Test.2; let Test.4 : I64 = StructAtIndex 1 Test.2; - let Test.7 : I64 = CallByName Num.22 Test.3 Test.4; + let Test.7 : I64 = CallByName Num.19 Test.3 Test.4; ret Test.7; procedure Test.0 (): - let Test.9 : I64 = 4i64; - let Test.10 : I64 = 9i64; - let Test.6 : {I64, I64} = Struct {Test.9, Test.10}; + let Test.8 : I64 = 4i64; + let Test.9 : I64 = 9i64; + let Test.6 : {I64, I64} = Struct {Test.8, Test.9}; let Test.5 : I64 = CallByName Test.1 Test.6; ret Test.5; diff --git a/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/compiler/test_mono/generated/record_optional_field_let_use_default.txt index 14a00e3638..aad801aab9 100644 --- a/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -1,13 +1,13 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.1 (Test.2): let Test.3 : I64 = 10i64; - let Test.7 : I64 = CallByName Num.22 Test.3 Test.2; + let Test.7 : I64 = CallByName Num.19 Test.3 Test.2; ret Test.7; procedure Test.0 (): - let Test.9 : I64 = 9i64; - let Test.5 : I64 = CallByName Test.1 Test.9; + let Test.8 : I64 = 9i64; + let Test.5 : I64 = CallByName Test.1 Test.8; ret Test.5; diff --git a/compiler/test_mono/generated/rigids.txt b/compiler/test_mono/generated/rigids.txt index bb5a06de40..7aab37c374 100644 --- a/compiler/test_mono/generated/rigids.txt +++ b/compiler/test_mono/generated/rigids.txt @@ -1,68 +1,63 @@ -procedure List.3 (#Attr.2, #Attr.3): - let Test.44 : U64 = lowlevel ListLen #Attr.2; - let Test.41 : Int1 = lowlevel NumLt #Attr.3 Test.44; - if Test.41 then - let Test.43 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - let Test.42 : [C {}, C I64] = Ok Test.43; - ret Test.42; +procedure List.2 (#Attr.2, #Attr.3): + let List.156 : U64 = lowlevel ListLen #Attr.2; + let List.153 : Int1 = lowlevel NumLt #Attr.3 List.156; + if List.153 then + let List.155 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let List.154 : [C {}, C I64] = Ok List.155; + ret List.154; else - let Test.40 : {} = Struct {}; - let Test.39 : [C {}, C I64] = Err Test.40; - ret Test.39; + let List.152 : {} = Struct {}; + let List.151 : [C {}, C I64] = Err List.152; + ret List.151; -procedure List.4 (#Attr.2, #Attr.3, #Attr.4): - let Test.24 : U64 = lowlevel ListLen #Attr.2; - let Test.17 : Int1 = lowlevel NumLt #Attr.3 Test.24; - if Test.17 then - let Test.19 : {List I64, I64} = CallByName List.58 #Attr.2 #Attr.3 #Attr.4; - let Test.18 : List I64 = StructAtIndex 0 Test.19; - inc Test.18; - dec Test.19; - ret Test.18; - else - ret #Attr.2; +procedure List.3 (List.63, List.64, List.65): + let List.143 : {List I64, I64} = CallByName List.57 List.63 List.64 List.65; + let List.142 : List I64 = StructAtIndex 0 List.143; + inc List.142; + dec List.143; + ret List.142; -procedure List.58 (#Attr.2, #Attr.3, #Attr.4): - let Test.23 : U64 = lowlevel ListLen #Attr.2; - let Test.21 : Int1 = lowlevel NumLt #Attr.3 Test.23; - if Test.21 then - let Test.22 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret Test.22; +procedure List.57 (#Attr.2, #Attr.3, #Attr.4): + let List.160 : U64 = lowlevel ListLen #Attr.2; + let List.158 : Int1 = lowlevel NumLt #Attr.3 List.160; + if List.158 then + let List.159 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.159; else - let Test.20 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; - ret Test.20; + let List.157 : {List I64, I64} = Struct {#Attr.2, #Attr.4}; + ret List.157; procedure Test.1 (Test.2, Test.3, Test.4): - let Test.38 : [C {}, C I64] = CallByName List.3 Test.4 Test.3; - let Test.37 : [C {}, C I64] = CallByName List.3 Test.4 Test.2; - let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.37, Test.38}; - joinpoint Test.34: - let Test.25 : List I64 = Array []; - ret Test.25; + let Test.29 : [C {}, C I64] = CallByName List.2 Test.4 Test.3; + let Test.28 : [C {}, C I64] = CallByName List.2 Test.4 Test.2; + let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.28, Test.29}; + joinpoint Test.25: + let Test.16 : List I64 = Array []; + ret Test.16; in - let Test.31 : [C {}, C I64] = StructAtIndex 1 Test.13; - let Test.32 : U8 = 1i64; - let Test.33 : U8 = GetTagId Test.31; - let Test.36 : Int1 = lowlevel Eq Test.32 Test.33; - if Test.36 then - let Test.28 : [C {}, C I64] = StructAtIndex 0 Test.13; - let Test.29 : U8 = 1i64; - let Test.30 : U8 = GetTagId Test.28; - let Test.35 : Int1 = lowlevel Eq Test.29 Test.30; - if Test.35 then - let Test.27 : [C {}, C I64] = StructAtIndex 0 Test.13; - let Test.6 : I64 = UnionAtIndex (Id 1) (Index 0) Test.27; - let Test.26 : [C {}, C I64] = StructAtIndex 1 Test.13; - let Test.7 : I64 = UnionAtIndex (Id 1) (Index 0) Test.26; - let Test.15 : List I64 = CallByName List.4 Test.4 Test.2 Test.7; - let Test.14 : List I64 = CallByName List.4 Test.15 Test.3 Test.6; + let Test.22 : [C {}, C I64] = StructAtIndex 1 Test.13; + let Test.23 : U8 = 1i64; + let Test.24 : U8 = GetTagId Test.22; + let Test.27 : Int1 = lowlevel Eq Test.23 Test.24; + if Test.27 then + let Test.19 : [C {}, C I64] = StructAtIndex 0 Test.13; + let Test.20 : U8 = 1i64; + let Test.21 : U8 = GetTagId Test.19; + let Test.26 : Int1 = lowlevel Eq Test.20 Test.21; + if Test.26 then + let Test.18 : [C {}, C I64] = StructAtIndex 0 Test.13; + let Test.6 : I64 = UnionAtIndex (Id 1) (Index 0) Test.18; + let Test.17 : [C {}, C I64] = StructAtIndex 1 Test.13; + let Test.7 : I64 = UnionAtIndex (Id 1) (Index 0) Test.17; + let Test.15 : List I64 = CallByName List.3 Test.4 Test.2 Test.7; + let Test.14 : List I64 = CallByName List.3 Test.15 Test.3 Test.6; ret Test.14; else dec Test.4; - jump Test.34; + jump Test.25; else dec Test.4; - jump Test.34; + jump Test.25; procedure Test.0 (): let Test.10 : U64 = 0i64; diff --git a/compiler/test_mono/generated/somehow_drops_definitions.txt b/compiler/test_mono/generated/somehow_drops_definitions.txt index 067e533b9c..1ac4ec978e 100644 --- a/compiler/test_mono/generated/somehow_drops_definitions.txt +++ b/compiler/test_mono/generated/somehow_drops_definitions.txt @@ -1,27 +1,27 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.27 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.27; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.274 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.274; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.22 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.22; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.273; procedure Test.1 (): - let Test.28 : I64 = 1i64; - ret Test.28; + let Test.26 : I64 = 1i64; + ret Test.26; procedure Test.2 (): - let Test.23 : I64 = 2i64; - ret Test.23; + let Test.22 : I64 = 2i64; + ret Test.22; procedure Test.3 (Test.6): - let Test.26 : I64 = CallByName Test.1; - let Test.25 : I64 = CallByName Num.22 Test.6 Test.26; - ret Test.25; + let Test.25 : I64 = CallByName Test.1; + let Test.24 : I64 = CallByName Num.19 Test.6 Test.25; + ret Test.24; procedure Test.4 (Test.7): let Test.21 : I64 = CallByName Test.2; - let Test.20 : I64 = CallByName Num.24 Test.7 Test.21; + let Test.20 : I64 = CallByName Num.21 Test.7 Test.21; ret Test.20; procedure Test.5 (Test.8, Test.9): @@ -44,8 +44,8 @@ procedure Test.0 (): let Test.11 : I64 = CallByName Test.5 Test.12 Test.13; ret Test.11; in - let Test.24 : Int1 = true; - if Test.24 then + let Test.23 : Int1 = true; + if Test.23 then let Test.3 : Int1 = false; jump Test.19 Test.3; else diff --git a/compiler/test_mono/generated/specialize_ability_call.txt b/compiler/test_mono/generated/specialize_ability_call.txt index 9e3179be06..ebc12cb8bf 100644 --- a/compiler/test_mono/generated/specialize_ability_call.txt +++ b/compiler/test_mono/generated/specialize_ability_call.txt @@ -1,7 +1,7 @@ -procedure Test.5 (Test.8): - ret Test.8; +procedure Test.7 (Test.9): + ret Test.9; procedure Test.0 (): - let Test.10 : U64 = 1234i64; - let Test.9 : U64 = CallByName Test.5 Test.10; - ret Test.9; + let Test.11 : U64 = 1234i64; + let Test.10 : U64 = CallByName Test.7 Test.11; + ret Test.10; diff --git a/compiler/test_mono/generated/specialize_closures.txt b/compiler/test_mono/generated/specialize_closures.txt index 1d6423ffb7..251e80285d 100644 --- a/compiler/test_mono/generated/specialize_closures.txt +++ b/compiler/test_mono/generated/specialize_closures.txt @@ -1,10 +1,10 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.28 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.28; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.274 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.274; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.25 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.25; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.273; procedure Test.1 (Test.2, Test.3): let Test.17 : U8 = GetTagId Test.2; @@ -23,29 +23,29 @@ procedure Test.1 (Test.2, Test.3): procedure Test.7 (Test.10, #Attr.12): let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.27 : I64 = CallByName Num.22 Test.10 Test.4; - ret Test.27; + let Test.26 : I64 = CallByName Num.19 Test.10 Test.4; + ret Test.26; procedure Test.8 (Test.11, #Attr.12): let Test.6 : Int1 = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; if Test.6 then - let Test.24 : I64 = CallByName Num.24 Test.11 Test.5; + let Test.24 : I64 = CallByName Num.21 Test.11 Test.5; ret Test.24; else ret Test.11; procedure Test.0 (): + let Test.4 : I64 = 1i64; let Test.5 : I64 = 2i64; let Test.6 : Int1 = true; - let Test.4 : I64 = 1i64; joinpoint Test.22 Test.14: let Test.15 : I64 = 42i64; let Test.13 : I64 = CallByName Test.1 Test.14 Test.15; ret Test.13; in - let Test.26 : Int1 = true; - if Test.26 then + let Test.25 : Int1 = true; + if Test.25 then let Test.7 : [C I64, C I64 Int1] = ClosureTag(Test.7) Test.4; jump Test.22 Test.7; else diff --git a/compiler/test_mono/generated/specialize_lowlevel.txt b/compiler/test_mono/generated/specialize_lowlevel.txt index 8a15d461e3..74bc5c3bbb 100644 --- a/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/compiler/test_mono/generated/specialize_lowlevel.txt @@ -1,24 +1,24 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.24 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.24; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.274 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.274; -procedure Num.24 (#Attr.2, #Attr.3): - let Test.21 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Test.21; +procedure Num.21 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.273; procedure Test.6 (Test.8, #Attr.12): let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.23 : I64 = CallByName Num.22 Test.8 Test.4; - ret Test.23; + let Test.22 : I64 = CallByName Num.19 Test.8 Test.4; + ret Test.22; procedure Test.7 (Test.9, #Attr.12): let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.20 : I64 = CallByName Num.24 Test.9 Test.5; + let Test.20 : I64 = CallByName Num.21 Test.9 Test.5; ret Test.20; procedure Test.0 (): - let Test.5 : I64 = 2i64; let Test.4 : I64 = 1i64; + let Test.5 : I64 = 2i64; let Test.12 : I64 = 42i64; joinpoint Test.19 Test.13: let Test.14 : U8 = GetTagId Test.13; @@ -35,8 +35,8 @@ procedure Test.0 (): jump Test.15 Test.17; in - let Test.22 : Int1 = true; - if Test.22 then + let Test.21 : Int1 = true; + if Test.21 then let Test.6 : [C I64, C I64] = ClosureTag(Test.6) Test.4; jump Test.19 Test.6; else diff --git a/compiler/test_mono/generated/when_nested_maybe.txt b/compiler/test_mono/generated/when_nested_maybe.txt index 6bb9eebf35..272fa01449 100644 --- a/compiler/test_mono/generated/when_nested_maybe.txt +++ b/compiler/test_mono/generated/when_nested_maybe.txt @@ -1,30 +1,30 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.8 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.8; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.0 (): - let Test.20 : I64 = 41i64; - let Test.19 : [C I64, C ] = Just Test.20; - let Test.2 : [C [C I64, C ], C ] = Just Test.19; - joinpoint Test.16: - let Test.9 : I64 = 1i64; - ret Test.9; + let Test.19 : I64 = 41i64; + let Test.18 : [C I64, C ] = Just Test.19; + let Test.2 : [C [C I64, C ], C ] = Just Test.18; + joinpoint Test.15: + let Test.8 : I64 = 1i64; + ret Test.8; in - let Test.14 : U8 = 0i64; - let Test.15 : U8 = GetTagId Test.2; - let Test.18 : Int1 = lowlevel Eq Test.14 Test.15; - if Test.18 then - let Test.11 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.12 : U8 = 0i64; - let Test.13 : U8 = GetTagId Test.11; - let Test.17 : Int1 = lowlevel Eq Test.12 Test.13; - if Test.17 then - let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; - let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.10; + let Test.13 : U8 = 0i64; + let Test.14 : U8 = GetTagId Test.2; + let Test.17 : Int1 = lowlevel Eq Test.13 Test.14; + if Test.17 then + let Test.10 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.11 : U8 = 0i64; + let Test.12 : U8 = GetTagId Test.10; + let Test.16 : Int1 = lowlevel Eq Test.11 Test.12; + if Test.16 then + let Test.9 : [C I64, C ] = UnionAtIndex (Id 0) (Index 0) Test.2; + let Test.5 : I64 = UnionAtIndex (Id 0) (Index 0) Test.9; let Test.7 : I64 = 1i64; - let Test.6 : I64 = CallByName Num.22 Test.5 Test.7; + let Test.6 : I64 = CallByName Num.19 Test.5 Test.7; ret Test.6; else - jump Test.16; + jump Test.15; else - jump Test.16; + jump Test.15; diff --git a/compiler/test_mono/generated/when_on_record.txt b/compiler/test_mono/generated/when_on_record.txt index b313695135..71fec29389 100644 --- a/compiler/test_mono/generated/when_on_record.txt +++ b/compiler/test_mono/generated/when_on_record.txt @@ -1,9 +1,9 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.5 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.5; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.0 (): - let Test.6 : I64 = 2i64; + let Test.5 : I64 = 2i64; let Test.4 : I64 = 3i64; - let Test.3 : I64 = CallByName Num.22 Test.6 Test.4; + let Test.3 : I64 = CallByName Num.19 Test.5 Test.4; ret Test.3; diff --git a/compiler/test_mono/generated/when_on_two_values.txt b/compiler/test_mono/generated/when_on_two_values.txt index 2085f6c7df..f3d84fff9e 100644 --- a/compiler/test_mono/generated/when_on_two_values.txt +++ b/compiler/test_mono/generated/when_on_two_values.txt @@ -1,28 +1,28 @@ -procedure Num.22 (#Attr.2, #Attr.3): - let Test.7 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Test.7; +procedure Num.19 (#Attr.2, #Attr.3): + let Num.273 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.273; procedure Test.0 (): - let Test.16 : I64 = 3i64; - let Test.15 : I64 = 2i64; - let Test.4 : {I64, I64} = Struct {Test.15, Test.16}; - joinpoint Test.12: + let Test.15 : I64 = 3i64; + let Test.14 : I64 = 2i64; + let Test.4 : {I64, I64} = Struct {Test.14, Test.15}; + joinpoint Test.11: let Test.2 : I64 = StructAtIndex 0 Test.4; let Test.3 : I64 = StructAtIndex 1 Test.4; - let Test.6 : I64 = CallByName Num.22 Test.2 Test.3; + let Test.6 : I64 = CallByName Num.19 Test.2 Test.3; ret Test.6; in - let Test.10 : I64 = StructAtIndex 1 Test.4; - let Test.11 : I64 = 3i64; - let Test.14 : Int1 = lowlevel Eq Test.11 Test.10; - if Test.14 then - let Test.8 : I64 = StructAtIndex 0 Test.4; - let Test.9 : I64 = 4i64; - let Test.13 : Int1 = lowlevel Eq Test.9 Test.8; - if Test.13 then + let Test.9 : I64 = StructAtIndex 1 Test.4; + let Test.10 : I64 = 3i64; + let Test.13 : Int1 = lowlevel Eq Test.10 Test.9; + if Test.13 then + let Test.7 : I64 = StructAtIndex 0 Test.4; + let Test.8 : I64 = 4i64; + let Test.12 : Int1 = lowlevel Eq Test.8 Test.7; + if Test.12 then let Test.5 : I64 = 9i64; ret Test.5; else - jump Test.12; + jump Test.11; else - jump Test.12; + jump Test.11; diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index 86e93346f1..abdcc5b7b3 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -5,9 +5,6 @@ // we actually want to compare against the literal float bits #![allow(clippy::float_cmp)] -#[macro_use] -extern crate pretty_assertions; - #[macro_use] extern crate indoc; @@ -19,9 +16,9 @@ const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024; use test_mono_macros::*; use roc_collections::all::MutMap; +use roc_load::Threading; use roc_module::symbol::Symbol; use roc_mono::ir::Proc; - use roc_mono::ir::ProcLayout; const TARGET_INFO: roc_target::TargetInfo = roc_target::TargetInfo::default_x86_64(); @@ -102,6 +99,7 @@ fn compiles_to_ir(test_name: &str, src: &str) { Default::default(), TARGET_INFO, roc_reporting::report::RenderTarget::Generic, + Threading::Single, ); let mut loaded = match loaded { @@ -123,14 +121,12 @@ fn compiles_to_ir(test_name: &str, src: &str) { let can_problems = loaded.can_problems.remove(&home).unwrap_or_default(); let type_problems = loaded.type_problems.remove(&home).unwrap_or_default(); - let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default(); if !can_problems.is_empty() { println!("Ignoring {} canonicalization problems", can_problems.len()); } assert!(type_problems.is_empty()); - assert_eq!(mono_problems, Vec::new()); debug_assert_eq!(exposed_to_host.values.len(), 1); @@ -1233,6 +1229,7 @@ fn monomorphized_applied_tag() { } #[mono_test] +#[ignore = "Cannot compile polymorphic closures yet"] fn aliased_polymorphic_closure() { indoc!( r#" @@ -1283,6 +1280,23 @@ fn issue_2583_specialize_errors_behind_unified_branches() { ) } +#[mono_test] +fn issue_2810() { + indoc!( + r#" + Command : [ Command Tool ] + + Job : [ Job Command ] + + Tool : [ SystemTool, FromJob Job ] + + a : Job + a = Job (Command (FromJob (Job (Command SystemTool)))) + a + "# + ) +} + #[mono_test] fn issue_2811() { indoc!( @@ -1306,9 +1320,58 @@ fn specialize_ability_call() { Id := U64 hash : Id -> U64 - hash = \$Id n -> n + hash = \@Id n -> n - main = hash ($Id 1234) + main = hash (@Id 1234) + "# + ) +} + +#[mono_test] +fn opaque_assign_to_symbol() { + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + Variable := U8 + + fromUtf8 : U8 -> Result Variable [ InvalidVariableUtf8 ] + fromUtf8 = \char -> + Ok (@Variable char) + + out = fromUtf8 98 + "# + ) +} + +#[mono_test] +fn encode() { + indoc!( + r#" + app "test" provides [ myU8Bytes ] to "./platform" + + Encoder fmt := List U8, fmt -> List U8 | fmt has Format + + Encoding has + toEncoder : val -> Encoder fmt | val has Encoding, fmt has Format + + Format has + u8 : U8 -> Encoder fmt | fmt has Format + + + Linear := {} + + # impl Format for Linear + u8 = \n -> @Encoder (\lst, @Linear {} -> List.append lst n) + + MyU8 := U8 + + # impl Encoding for MyU8 + toEncoder = \@MyU8 n -> u8 n + + myU8Bytes = + when toEncoder (@MyU8 15) is + @Encoder doEncode -> doEncode [] (@Linear {}) "# ) } @@ -1331,3 +1394,50 @@ fn specialize_ability_call() { // "# // ) // } + +#[mono_test] +fn list_map_closure_borrows() { + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + list = [ Str.concat "lllllllllllllllllllllooooooooooong" "g" ] + + example1 = List.map list \string -> Str.repeat string 2 + + out = + when List.get example1 0 is + Ok s -> s + Err _ -> "Hello, World!\n" + "# + ) +} + +#[mono_test] +fn list_map_closure_owns() { + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + list = [ Str.concat "lllllllllllllllllllllooooooooooong" "g" ] + + example2 = List.map list \string -> Str.concat string "!" + + out = + when List.get example2 0 is + Ok s -> s + Err _ -> "Hello, World!\n" + "# + ) +} + +#[mono_test] +fn list_sort_asc() { + indoc!( + r#" + app "test" provides [ out ] to "./platform" + + out = List.sortAsc [ 4,3,2,1 ] + "# + ) +} diff --git a/compiler/test_mono_macros/src/lib.rs b/compiler/test_mono_macros/src/lib.rs index e6646e4e36..00f023125a 100644 --- a/compiler/test_mono_macros/src/lib.rs +++ b/compiler/test_mono_macros/src/lib.rs @@ -14,9 +14,11 @@ pub fn mono_test(_args: TokenStream, item: TokenStream) -> TokenStream { let body = task_fn.block.clone(); let visibility = &task_fn.vis; + let attributes = task_fn.attrs; let result = quote! { #[test] + #(#attributes)* #visibility fn #name(#args) -> () { compiles_to_ir(#name_str, #body); diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index 550cb5f333..e00c368701 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -10,6 +10,7 @@ roc_collections = { path = "../collections" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_error_macros = {path="../../error_macros"} +roc_debug_flags = {path="../debug_flags"} ven_ena = { path = "../../vendor/ena" } bumpalo = { version = "3.8.0", features = ["collections"] } static_assertions = "1.1.0" diff --git a/compiler/types/src/builtin_aliases.rs b/compiler/types/src/builtin_aliases.rs index 23b3983cc9..0154e8863d 100644 --- a/compiler/types/src/builtin_aliases.rs +++ b/compiler/types/src/builtin_aliases.rs @@ -28,53 +28,58 @@ pub fn aliases() -> MutMap { aliases.insert(symbol, alias); }; - // Int range : [ @Int range ] + // Int range : Num (Integer range) add_alias( Symbol::NUM_INT, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: int_alias_content(flex(TVAR1)), + kind: AliasKind::Structural, }, ); - // Float range : [ @Float range ] + // Frac range : Num (FloatingPoint range) add_alias( - Symbol::NUM_FLOAT, + Symbol::NUM_FRAC, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], - typ: float_alias_content(flex(TVAR1)), + typ: frac_alias_content(flex(TVAR1)), + kind: AliasKind::Structural, }, ); - // Num range : [ @Num range ] + // Num range := range add_alias( Symbol::NUM_NUM, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: num_alias_content(flex(TVAR1)), + kind: AliasKind::Opaque, }, ); - // Integer range : [ @Integer range ] + // Integer range := range add_alias( Symbol::NUM_INTEGER, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: integer_alias_content(flex(TVAR1)), + kind: AliasKind::Opaque, }, ); - // Natural : [ @Natural ] + // Natural := [] add_alias( Symbol::NUM_NATURAL, BuiltinAlias { region: Region::zero(), vars: vec![], typ: natural_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -85,16 +90,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: nat_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed128 : [ @Signed128 ] + // Signed128 := [] add_alias( Symbol::NUM_SIGNED128, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed128_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -105,6 +112,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i128_alias_content(), + kind: AliasKind::Structural, }, ); @@ -115,16 +123,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u128_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed64 : [ @Signed64 ] + // Signed64 := [] add_alias( Symbol::NUM_SIGNED64, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed64_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -135,6 +145,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i64_alias_content(), + kind: AliasKind::Structural, }, ); @@ -145,16 +156,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u64_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed32 : [ @Signed32 ] + // Signed32 := [] add_alias( Symbol::NUM_SIGNED32, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed32_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -165,6 +178,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i32_alias_content(), + kind: AliasKind::Structural, }, ); @@ -175,16 +189,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u32_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed16 : [ @Signed16 ] + // Signed16 := [] add_alias( Symbol::NUM_SIGNED16, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed16_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -195,6 +211,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i16_alias_content(), + kind: AliasKind::Structural, }, ); @@ -205,16 +222,18 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u16_alias_content(), + kind: AliasKind::Structural, }, ); - // Signed8 : [ @Signed8 ] + // Signed8 := [] add_alias( Symbol::NUM_SIGNED8, BuiltinAlias { region: Region::zero(), vars: vec![], typ: signed8_alias_content(), + kind: AliasKind::Opaque, }, ); @@ -225,6 +244,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: i8_alias_content(), + kind: AliasKind::Structural, }, ); @@ -235,76 +255,84 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: u8_alias_content(), + kind: AliasKind::Structural, }, ); - // Decimal : [ @Decimal ] + // Decimal := [] add_alias( Symbol::NUM_DECIMAL, BuiltinAlias { region: Region::zero(), vars: vec![], typ: decimal_alias_content(), + kind: AliasKind::Opaque, }, ); - // Binary64 : [ @Binary64 ] + // Binary64 := [] add_alias( Symbol::NUM_BINARY64, BuiltinAlias { region: Region::zero(), vars: vec![], typ: binary64_alias_content(), + kind: AliasKind::Opaque, }, ); - // Binary32 : [ @Binary32 ] + // Binary32 := [] add_alias( Symbol::NUM_BINARY32, BuiltinAlias { region: Region::zero(), vars: vec![], typ: binary32_alias_content(), + kind: AliasKind::Opaque, }, ); - // FloatingPoint range : [ @FloatingPoint range ] + // FloatingPoint range := range add_alias( Symbol::NUM_FLOATINGPOINT, BuiltinAlias { region: Region::zero(), vars: vec![Loc::at(Region::zero(), "range".into())], typ: floatingpoint_alias_content(flex(TVAR1)), + kind: AliasKind::Opaque, }, ); - // Dec : Float Decimal + // Dec : Frac Decimal add_alias( Symbol::NUM_DEC, BuiltinAlias { region: Region::zero(), vars: Vec::new(), typ: dec_alias_content(), + kind: AliasKind::Structural, }, ); - // F64 : Float Binary64 + // F64 : Frac Binary64 add_alias( Symbol::NUM_F64, BuiltinAlias { region: Region::zero(), vars: Vec::new(), typ: f64_alias_content(), + kind: AliasKind::Structural, }, ); - // F32 : Float Binary32 + // F32 : Frac Binary32 add_alias( Symbol::NUM_F32, BuiltinAlias { region: Region::zero(), vars: Vec::new(), typ: f32_alias_content(), + kind: AliasKind::Structural, }, ); @@ -315,6 +343,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: bool_alias_content(), + kind: AliasKind::Structural, }, ); @@ -325,6 +354,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: str_utf8_byte_problem_alias_content(), + kind: AliasKind::Structural, }, ); @@ -335,6 +365,7 @@ pub fn aliases() -> MutMap { region: Region::zero(), vars: Vec::new(), typ: str_utf8_byte_problem_alias_content(), + kind: AliasKind::Structural, }, ); @@ -350,16 +381,16 @@ pub fn flex(tvar: VarId) -> SolvedType { pub fn num_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_NUM, - vec![("range".into(), range.clone())], + vec![(range.clone())], vec![], Box::new(num_alias_content(range)), - AliasKind::Structural, + AliasKind::Opaque, ) } #[inline(always)] fn num_alias_content(range: SolvedType) -> SolvedType { - single_private_tag(Symbol::NUM_AT_NUM, vec![range]) + range } // FLOATING POINT @@ -368,33 +399,33 @@ fn num_alias_content(range: SolvedType) -> SolvedType { pub fn floatingpoint_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_FLOATINGPOINT, - vec![("range".into(), range.clone())], + vec![(range.clone())], vec![], Box::new(floatingpoint_alias_content(range)), - AliasKind::Structural, + AliasKind::Opaque, ) } #[inline(always)] fn floatingpoint_alias_content(range: SolvedType) -> SolvedType { - single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, vec![range]) + range } -// FLOAT +// FRAC #[inline(always)] -pub fn float_type(range: SolvedType) -> SolvedType { +pub fn frac_type(range: SolvedType) -> SolvedType { SolvedType::Alias( - Symbol::NUM_FLOAT, - vec![("range".into(), range.clone())], + Symbol::NUM_FRAC, + vec![(range.clone())], vec![], - Box::new(float_alias_content(range)), + Box::new(frac_alias_content(range)), AliasKind::Structural, ) } #[inline(always)] -fn float_alias_content(range: SolvedType) -> SolvedType { +fn frac_alias_content(range: SolvedType) -> SolvedType { num_type(floatingpoint_type(range)) } @@ -413,7 +444,7 @@ pub fn f64_type() -> SolvedType { #[inline(always)] fn f64_alias_content() -> SolvedType { - float_alias_content(binary64_type()) + frac_alias_content(binary64_type()) } // F32 @@ -431,7 +462,7 @@ pub fn f32_type() -> SolvedType { #[inline(always)] fn f32_alias_content() -> SolvedType { - float_alias_content(binary32_type()) + frac_alias_content(binary32_type()) } // Nat @@ -638,7 +669,7 @@ fn i8_alias_content() -> SolvedType { pub fn int_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_INT, - vec![("range".into(), range.clone())], + vec![(range.clone())], vec![], Box::new(int_alias_content(range)), AliasKind::Structural, @@ -656,16 +687,16 @@ fn int_alias_content(range: SolvedType) -> SolvedType { pub fn integer_type(range: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::NUM_INTEGER, - vec![("range".into(), range.clone())], + vec![(range.clone())], vec![], Box::new(integer_alias_content(range)), - AliasKind::Structural, + AliasKind::Opaque, ) } #[inline(always)] fn integer_alias_content(range: SolvedType) -> SolvedType { - single_private_tag(Symbol::NUM_AT_INTEGER, vec![range]) + range } #[inline(always)] @@ -681,7 +712,7 @@ pub fn binary64_type() -> SolvedType { #[inline(always)] pub fn binary64_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_BINARY64, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -697,7 +728,7 @@ pub fn binary32_type() -> SolvedType { #[inline(always)] fn binary32_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_BINARY32, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -713,7 +744,7 @@ pub fn natural_type() -> SolvedType { #[inline(always)] fn natural_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_NATURAL, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -729,7 +760,7 @@ pub fn signed128_type() -> SolvedType { #[inline(always)] fn signed128_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED128, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -745,7 +776,7 @@ pub fn signed64_type() -> SolvedType { #[inline(always)] fn signed64_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED64, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -761,7 +792,7 @@ pub fn signed32_type() -> SolvedType { #[inline(always)] fn signed32_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED32, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -777,7 +808,7 @@ pub fn signed16_type() -> SolvedType { #[inline(always)] fn signed16_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED16, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -793,7 +824,7 @@ pub fn signed8_type() -> SolvedType { #[inline(always)] fn signed8_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_SIGNED8, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -809,7 +840,7 @@ pub fn unsigned128_type() -> SolvedType { #[inline(always)] fn unsigned128_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED128, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -825,7 +856,7 @@ pub fn unsigned64_type() -> SolvedType { #[inline(always)] fn unsigned64_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED64, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -841,7 +872,7 @@ pub fn unsigned32_type() -> SolvedType { #[inline(always)] fn unsigned32_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED32, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -857,7 +888,7 @@ pub fn unsigned16_type() -> SolvedType { #[inline(always)] fn unsigned16_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED16, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] @@ -873,12 +904,12 @@ pub fn unsigned8_type() -> SolvedType { #[inline(always)] fn unsigned8_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_UNSIGNED8, vec![]) + SolvedType::EmptyTagUnion } #[inline(always)] fn decimal_alias_content() -> SolvedType { - single_private_tag(Symbol::NUM_AT_DECIMAL, vec![]) + SolvedType::EmptyTagUnion } // Dec @@ -896,7 +927,7 @@ pub fn dec_type() -> SolvedType { #[inline(always)] fn dec_alias_content() -> SolvedType { - float_alias_content(decimal_type()) + frac_alias_content(decimal_type()) } #[inline(always)] @@ -924,8 +955,8 @@ pub fn bool_type() -> SolvedType { fn bool_alias_content() -> SolvedType { SolvedType::TagUnion( vec![ - (TagName::Global("False".into()), vec![]), - (TagName::Global("True".into()), vec![]), + (TagName::Tag("False".into()), vec![]), + (TagName::Tag("True".into()), vec![]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -936,9 +967,9 @@ pub fn ordering_type() -> SolvedType { // [ LT, EQ, GT ] SolvedType::TagUnion( vec![ - (TagName::Global("EQ".into()), vec![]), - (TagName::Global("GT".into()), vec![]), - (TagName::Global("LT".into()), vec![]), + (TagName::Tag("EQ".into()), vec![]), + (TagName::Tag("GT".into()), vec![]), + (TagName::Tag("LT".into()), vec![]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -948,7 +979,7 @@ pub fn ordering_type() -> SolvedType { pub fn result_type(a: SolvedType, e: SolvedType) -> SolvedType { SolvedType::Alias( Symbol::RESULT_RESULT, - vec![("ok".into(), a.clone()), ("err".into(), e.clone())], + vec![a.clone(), e.clone()], vec![], Box::new(result_alias_content(a, e)), AliasKind::Structural, @@ -964,8 +995,8 @@ pub fn box_type(a: SolvedType) -> SolvedType { fn result_alias_content(a: SolvedType, e: SolvedType) -> SolvedType { SolvedType::TagUnion( vec![ - (TagName::Global("Err".into()), vec![e]), - (TagName::Global("Ok".into()), vec![a]), + (TagName::Tag("Err".into()), vec![e]), + (TagName::Tag("Ok".into()), vec![a]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -1025,12 +1056,12 @@ pub fn str_utf8_byte_problem_alias_content() -> SolvedType { // [ CodepointTooLarge, EncodesSurrogateHalf, OverlongEncoding, InvalidStartByte, UnexpectedEndOfSequence, ExpectedContinuation ] SolvedType::TagUnion( vec![ - (TagName::Global("CodepointTooLarge".into()), vec![]), - (TagName::Global("EncodesSurrogateHalf".into()), vec![]), - (TagName::Global("ExpectedContinuation".into()), vec![]), - (TagName::Global("InvalidStartByte".into()), vec![]), - (TagName::Global("OverlongEncoding".into()), vec![]), - (TagName::Global("UnexpectedEndOfSequence".into()), vec![]), + (TagName::Tag("CodepointTooLarge".into()), vec![]), + (TagName::Tag("EncodesSurrogateHalf".into()), vec![]), + (TagName::Tag("ExpectedContinuation".into()), vec![]), + (TagName::Tag("InvalidStartByte".into()), vec![]), + (TagName::Tag("OverlongEncoding".into()), vec![]), + (TagName::Tag("UnexpectedEndOfSequence".into()), vec![]), ], Box::new(SolvedType::EmptyTagUnion), ) @@ -1045,10 +1076,3 @@ pub fn set_type(a: SolvedType) -> SolvedType { pub fn dict_type(key: SolvedType, value: SolvedType) -> SolvedType { SolvedType::Apply(Symbol::DICT_DICT, vec![key, value]) } - -pub fn single_private_tag(symbol: Symbol, type_arguments: Vec) -> SolvedType { - SolvedType::TagUnion( - vec![(TagName::Private(symbol), type_arguments)], - Box::new(SolvedType::EmptyTagUnion), - ) -} diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index d3111cad1d..6500a1b4ce 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -116,7 +116,7 @@ fn find_names_needed( } match &subs.get_content_without_compacting(variable).clone() { - RecursionVar { opt_name: None, .. } | FlexVar(None) | FlexAbleVar(None, _) => { + RecursionVar { opt_name: None, .. } | FlexVar(None) => { let root = subs.get_root_key_without_compacting(variable); // If this var is *not* its own root, then the @@ -135,6 +135,15 @@ fn find_names_needed( } } } + FlexAbleVar(None, _) => { + let root = subs.get_root_key_without_compacting(variable); + if !root_appearances.contains_key(&root) { + roots.push(root); + } + // Able vars are always printed at least twice (in the signature, and in the "has" + // clause set). + root_appearances.insert(root, Appearances::Multiple); + } RecursionVar { opt_name: Some(name_index), .. @@ -224,7 +233,11 @@ fn find_names_needed( } } -pub fn name_all_type_vars(variable: Variable, subs: &mut Subs) { +struct NamedResult { + recursion_structs_to_expand: Vec, +} + +fn name_all_type_vars(variable: Variable, subs: &mut Subs) -> NamedResult { let mut roots = Vec::new(); let mut letters_used = 0; let mut appearances = MutMap::default(); @@ -233,13 +246,30 @@ pub fn name_all_type_vars(variable: Variable, subs: &mut Subs) { // Populate names_needed find_names_needed(variable, subs, &mut roots, &mut appearances, &mut taken); + let mut recursion_structs_to_expand = vec![]; + for root in roots { // show the type variable number instead of `*`. useful for debugging // set_root_name(root, (format!("<{:?}>", root).into()), subs); - if let Some(Appearances::Multiple) = appearances.get(&root) { - letters_used = name_root(letters_used, root, subs, &mut taken); + match appearances.get(&root) { + Some(Appearances::Multiple) => { + letters_used = name_root(letters_used, root, subs, &mut taken); + } + Some(Appearances::Single) => { + if let Content::RecursionVar { structure, .. } = + subs.get_content_without_compacting(root) + { + recursion_structs_to_expand.push(*structure); + letters_used = name_root(letters_used, root, subs, &mut taken); + } + } + _ => {} } } + + NamedResult { + recursion_structs_to_expand, + } } fn name_root( @@ -271,6 +301,11 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) { let content = FlexVar(Some(name_index)); subs.set_content(root, content); } + &FlexAbleVar(None, ability) => { + let name_index = SubsIndex::push_new(&mut subs.field_names, name); + let content = FlexAbleVar(Some(name_index), ability); + subs.set_content(root, content); + } RecursionVar { opt_name: None, structure, @@ -298,17 +333,22 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) { #[derive(Default)] struct Context<'a> { able_variables: Vec<(&'a str, Symbol)>, + recursion_structs_to_expand: Vec, } -pub fn content_to_string( +fn content_to_string( content: &Content, subs: &Subs, home: ModuleId, interns: &Interns, + named_result: NamedResult, ) -> String { let mut buf = String::new(); let env = Env { home, interns }; - let mut ctx = Context::default(); + let mut ctx = Context { + able_variables: vec![], + recursion_structs_to_expand: named_result.recursion_structs_to_expand, + }; write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary); @@ -324,6 +364,17 @@ pub fn content_to_string( buf } +pub fn name_and_print_var( + var: Variable, + subs: &mut Subs, + home: ModuleId, + interns: &Interns, +) -> String { + let named_result = name_all_type_vars(var, subs); + let content = subs.get_content_without_compacting(var); + content_to_string(content, subs, home, interns, named_result) +} + pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Content { debug_assert_eq!(args.len(), 1); @@ -367,12 +418,34 @@ fn write_content<'a>( ctx.able_variables.push((name, *ability)); buf.push_str(name); } - RecursionVar { opt_name, .. } => match opt_name { + RecursionVar { + opt_name, + structure, + } => match opt_name { Some(name_index) => { - let name = &subs.field_names[name_index.index as usize]; - buf.push_str(name.as_str()) + if let Some(idx) = ctx + .recursion_structs_to_expand + .iter() + .position(|v| v == structure) + { + ctx.recursion_structs_to_expand.swap_remove(idx); + + write_content( + env, + ctx, + subs.get_content_without_compacting(*structure), + subs, + buf, + parens, + ); + } else { + let name = &subs.field_names[name_index.index as usize]; + buf.push_str(name.as_str()) + } + } + None => { + unreachable!("This should always be filled in!") } - None => buf.push_str(WILDCARD), }, Structure(flat_type) => write_flat_type(env, ctx, flat_type, subs, buf, parens), Alias(symbol, args, _actual, _kind) => { @@ -423,7 +496,7 @@ fn write_content<'a>( write_integer(env, ctx, content, subs, buf, parens, write_parens) } - Symbol::NUM_FLOAT => write_float( + Symbol::NUM_FRAC => write_float( env, ctx, get_single_arg(subs, args), @@ -449,13 +522,12 @@ fn write_content<'a>( ); } - // useful for debugging - if false { + roc_debug_flags::dbg_do!(roc_debug_flags::ROC_PRETTY_PRINT_ALIAS_CONTENTS, { buf.push_str("[[ but really "); let content = subs.get_content_without_compacting(*_actual); write_content(env, ctx, content, subs, buf, parens); buf.push_str("]]"); - } + }); }), } } @@ -992,7 +1064,7 @@ fn write_fn<'a>( fn write_symbol(env: &Env, symbol: Symbol, buf: &mut String) { let interns = &env.interns; - let ident = symbol.ident_str(interns); + let ident_str = symbol.as_str(interns); let module_id = symbol.module_id(); // Don't qualify the symbol if it's in our home module, @@ -1002,5 +1074,5 @@ fn write_symbol(env: &Env, symbol: Symbol, buf: &mut String) { buf.push('.'); } - buf.push_str(ident.as_str()); + buf.push_str(ident_str); } diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index a9acc39cfa..7a899f6c46 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -1,5 +1,5 @@ use crate::subs::{VarId, VarStore, Variable}; -use crate::types::{AliasKind, Problem, RecordField, Type, TypeExtension}; +use crate::types::{AliasKind, OptAbleType, Problem, RecordField, Type, TypeExtension}; use roc_collections::all::{ImMap, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -55,7 +55,7 @@ pub enum SolvedType { Alias( Symbol, - Vec<(Lowercase, SolvedType)>, + Vec, Vec, Box, AliasKind, @@ -63,7 +63,7 @@ pub enum SolvedType { HostExposedAlias { name: Symbol, - arguments: Vec<(Lowercase, SolvedType)>, + arguments: Vec, lambda_set_variables: Vec, actual_var: VarId, actual: Box, @@ -78,6 +78,7 @@ pub struct BuiltinAlias { pub region: Region, pub vars: Vec>, pub typ: SolvedType, + pub kind: AliasKind, } #[derive(Debug, Clone, Default)] @@ -211,8 +212,12 @@ pub fn to_type( Alias(symbol, solved_type_variables, solved_lambda_sets, solved_actual, kind) => { let mut type_variables = Vec::with_capacity(solved_type_variables.len()); - for (lowercase, solved_arg) in solved_type_variables { - type_variables.push((lowercase.clone(), to_type(solved_arg, free_vars, var_store))); + for solved_arg in solved_type_variables { + type_variables.push(OptAbleType { + typ: to_type(solved_arg, free_vars, var_store), + // TODO: is this always correct? + opt_ability: None, + }); } let mut lambda_set_variables = Vec::with_capacity(solved_lambda_sets.len()); @@ -243,8 +248,8 @@ pub fn to_type( } => { let mut type_variables = Vec::with_capacity(solved_type_variables.len()); - for (lowercase, solved_arg) in solved_type_variables { - type_variables.push((lowercase.clone(), to_type(solved_arg, free_vars, var_store))); + for solved_arg in solved_type_variables { + type_variables.push(to_type(solved_arg, free_vars, var_store)); } let mut lambda_set_variables = Vec::with_capacity(solved_lambda_sets.len()); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 1a327c85c3..9efc792704 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -13,8 +13,8 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; // if your changes cause this number to go down, great! // please change it to the lower number. // if it went up, maybe check that the change is really required -roc_error_macros::assert_sizeof_all!(Descriptor, 6 * 8); -roc_error_macros::assert_sizeof_all!(Content, 4 * 8); +roc_error_macros::assert_sizeof_all!(Descriptor, 5 * 8); +roc_error_macros::assert_sizeof_all!(Content, 3 * 8 + 4); roc_error_macros::assert_sizeof_all!(FlatType, 3 * 8); roc_error_macros::assert_sizeof_all!(UnionTags, 12); roc_error_macros::assert_sizeof_all!(RecordFields, 2 * 8); @@ -59,9 +59,10 @@ pub enum ErrorTypeContext { struct ErrorTypeState { taken: MutSet, - normals: u32, + letters_used: u32, problems: Vec, context: ErrorTypeContext, + recursive_tag_unions_seen: Vec, } #[derive(Clone, Copy, Debug)] @@ -114,7 +115,6 @@ fn round_to_multiple_of(value: usize, base: usize) -> usize { enum SerializedTagName { Global(SubsSlice), - Private(Symbol), Closure(Symbol), } @@ -204,14 +204,13 @@ impl Subs { for tag_name in tag_names { let serialized = match tag_name { - TagName::Global(uppercase) => { + TagName::Tag(uppercase) => { let slice = SubsSlice::extend_new( &mut buf, uppercase.as_str().as_bytes().iter().copied(), ); SerializedTagName::Global(slice) } - TagName::Private(symbol) => SerializedTagName::Private(*symbol), TagName::Closure(symbol) => SerializedTagName::Closure(*symbol), }; @@ -352,9 +351,8 @@ impl Subs { offset += bytes.len(); let string = unsafe { std::str::from_utf8_unchecked(bytes) }; - TagName::Global(string.into()) + TagName::Tag(string.into()) } - SerializedTagName::Private(symbol) => TagName::Private(*symbol), SerializedTagName::Closure(symbol) => TagName::Closure(*symbol), }; @@ -395,7 +393,7 @@ pub struct Subs { pub struct TagNameCache { globals: Vec, globals_slices: Vec>, - /// Currently private tags and closure tags; in the future just closure tags + /// Just closure tags symbols: Vec, symbols_slices: Vec>, } @@ -403,29 +401,27 @@ pub struct TagNameCache { impl TagNameCache { pub fn get_mut(&mut self, tag_name: &TagName) -> Option<&mut SubsSlice> { match tag_name { - TagName::Global(uppercase) => { + TagName::Tag(uppercase) => { // force into block match self.globals.iter().position(|u| u == uppercase) { Some(index) => Some(&mut self.globals_slices[index]), None => None, } } - TagName::Private(symbol) | TagName::Closure(symbol) => { - match self.symbols.iter().position(|s| s == symbol) { - Some(index) => Some(&mut self.symbols_slices[index]), - None => None, - } - } + TagName::Closure(symbol) => match self.symbols.iter().position(|s| s == symbol) { + Some(index) => Some(&mut self.symbols_slices[index]), + None => None, + }, } } pub fn push(&mut self, tag_name: &TagName, slice: SubsSlice) { match tag_name { - TagName::Global(uppercase) => { + TagName::Tag(uppercase) => { self.globals.push(uppercase.clone()); self.globals_slices.push(slice); } - TagName::Private(symbol) | TagName::Closure(symbol) => { + TagName::Closure(symbol) => { self.symbols.push(*symbol); self.symbols_slices.push(slice); } @@ -647,7 +643,7 @@ impl SubsSlice { let start = subs.tag_names.len() as u32; subs.tag_names - .extend(std::iter::repeat(TagName::Global(Uppercase::default())).take(length)); + .extend(std::iter::repeat(TagName::Tag(Uppercase::default())).take(length)); Self::new(start, length as u16) } @@ -772,7 +768,15 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: AliasKind::Opaque => "Opaque", }; - write!(f, "{}({:?}, {:?}, {:?})", wrap, name, slice, actual) + write!( + f, + "{}({:?}, {:?}, <{:?}>{:?})", + wrap, + name, + slice, + actual, + SubsFmtContent(subs.get_content_without_compacting(*actual), subs) + ) } Content::RangedNumber(typ, range) => { let slice = subs.get_subs_slice(*range); @@ -833,7 +837,16 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f let (it, new_ext) = tags.sorted_iterator_and_ext(subs, *ext); for (name, slice) in it { - write!(f, "{:?} {:?}, ", name, slice)?; + write!(f, "{:?} ", name)?; + for var in slice { + write!( + f, + "<{:?}>{:?} ", + var, + SubsFmtContent(subs.get_content_without_compacting(*var), subs) + )?; + } + write!(f, ", ")?; } write!(f, "]<{:?}>", new_ext) @@ -953,6 +966,77 @@ impl From for Option { } } +/// Marks whether a when expression is exhaustive using a variable. +#[derive(Clone, Copy, Debug)] +pub struct ExhaustiveMark(Variable); + +impl ExhaustiveMark { + pub fn new(var_store: &mut VarStore) -> Self { + Self(var_store.fresh()) + } + + // NOTE: only ever use this if you *know* a pattern match is surely exhaustive! + // Otherwise you will get unpleasant unification errors. + pub fn known_exhaustive() -> Self { + Self(Variable::EMPTY_TAG_UNION) + } + + pub fn variable_for_introduction(&self) -> Variable { + debug_assert!( + self.0 != Variable::EMPTY_TAG_UNION, + "Attempting to introduce known mark" + ); + self.0 + } + + pub fn set_non_exhaustive(&self, subs: &mut Subs) { + subs.set_content(self.0, Content::Error); + } + + pub fn is_non_exhaustive(&self, subs: &Subs) -> bool { + matches!(subs.get_content_without_compacting(self.0), Content::Error) + } +} + +/// Marks whether a when branch is redundant using a variable. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RedundantMark(Variable); + +impl RedundantMark { + pub fn new(var_store: &mut VarStore) -> Self { + Self(var_store.fresh()) + } + + // NOTE: only ever use this if you *know* a pattern match is surely exhaustive! + // Otherwise you will get unpleasant unification errors. + pub fn known_non_redundant() -> Self { + Self(Variable::EMPTY_TAG_UNION) + } + + pub fn variable_for_introduction(&self) -> Variable { + debug_assert!( + self.0 != Variable::EMPTY_TAG_UNION, + "Attempting to introduce known mark" + ); + self.0 + } + + pub fn set_redundant(&self, subs: &mut Subs) { + subs.set_content(self.0, Content::Error); + } + + pub fn is_redundant(&self, subs: &Subs) -> bool { + matches!(subs.get_content_without_compacting(self.0), Content::Error) + } +} + +pub fn new_marks(var_store: &mut VarStore) -> (RedundantMark, ExhaustiveMark) { + ( + RedundantMark::new(var_store), + ExhaustiveMark::new(var_store), + ) +} + #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Variable(u32); @@ -992,22 +1076,7 @@ define_const_var! { ORDER_ENUM, :pub ORDER, - // [ @Signed8 ] - AT_SIGNED8, - AT_SIGNED16, - AT_SIGNED32, - AT_SIGNED64, - AT_SIGNED128, - - AT_UNSIGNED8, - AT_UNSIGNED16, - AT_UNSIGNED32, - AT_UNSIGNED64, - AT_UNSIGNED128, - - AT_NATURAL, - - // Signed8 : [ @Signed8 ] + // Signed8 := [] :pub SIGNED8, :pub SIGNED16, :pub SIGNED32, @@ -1022,22 +1091,7 @@ define_const_var! { :pub NATURAL, - // [ @Integer Signed8 ] - AT_INTEGER_SIGNED8, - AT_INTEGER_SIGNED16, - AT_INTEGER_SIGNED32, - AT_INTEGER_SIGNED64, - AT_INTEGER_SIGNED128, - - AT_INTEGER_UNSIGNED8, - AT_INTEGER_UNSIGNED16, - AT_INTEGER_UNSIGNED32, - AT_INTEGER_UNSIGNED64, - AT_INTEGER_UNSIGNED128, - - AT_INTEGER_NATURAL, - - // Integer Signed8 : [ @Integer Signed8 ] + // Integer Signed8 := Signed8 INTEGER_SIGNED8, INTEGER_SIGNED16, INTEGER_SIGNED32, @@ -1052,22 +1106,7 @@ define_const_var! { INTEGER_NATURAL, - // [ @Num (Integer Signed8) ] - AT_NUM_INTEGER_SIGNED8, - AT_NUM_INTEGER_SIGNED16, - AT_NUM_INTEGER_SIGNED32, - AT_NUM_INTEGER_SIGNED64, - AT_NUM_INTEGER_SIGNED128, - - AT_NUM_INTEGER_UNSIGNED8, - AT_NUM_INTEGER_UNSIGNED16, - AT_NUM_INTEGER_UNSIGNED32, - AT_NUM_INTEGER_UNSIGNED64, - AT_NUM_INTEGER_UNSIGNED128, - - AT_NUM_INTEGER_NATURAL, - - // Num (Integer Signed8) + // Num (Integer Signed8) := Integer Signed8 NUM_INTEGER_SIGNED8, NUM_INTEGER_SIGNED16, NUM_INTEGER_SIGNED32, @@ -1097,32 +1136,17 @@ define_const_var! { :pub NAT, - // [ @Binary32 ] - AT_BINARY32, - AT_BINARY64, - AT_DECIMAL, - - // Binary32 : [ @Binary32 ] + // Binary32 : [] BINARY32, BINARY64, DECIMAL, - // [ @Float Binary32 ] - AT_FLOAT_BINARY32, - AT_FLOAT_BINARY64, - AT_FLOAT_DECIMAL, - - // Float Binary32 : [ @Float Binary32 ] + // Float Binary32 := Binary32 FLOAT_BINARY32, FLOAT_BINARY64, FLOAT_DECIMAL, - // [ @Num (Float Binary32) ] - AT_NUM_FLOAT_BINARY32, - AT_NUM_FLOAT_BINARY64, - AT_NUM_FLOAT_DECIMAL, - - // Num (Float Binary32) + // Num (Float Binary32) := Float Binary32 NUM_FLOAT_BINARY32, NUM_FLOAT_BINARY64, NUM_FLOAT_DECIMAL, @@ -1256,80 +1280,47 @@ impl fmt::Debug for VarId { fn integer_type( subs: &mut Subs, - num_at_signed64: Symbol, num_signed64: Symbol, num_i64: Symbol, - at_signed64: Variable, signed64: Variable, - at_integer_signed64: Variable, integer_signed64: Variable, - at_num_integer_signed64: Variable, num_integer_signed64: Variable, var_i64: Variable, ) { - // define the type Signed64 (which is an alias for [ @Signed64 ]) + // define the type Signed64 := [] { - let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_signed64), [])]); - - subs.set_content(at_signed64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - subs.set_content(signed64, { Content::Alias( num_signed64, AliasVariables::default(), - at_signed64, - AliasKind::Structural, + Variable::EMPTY_TAG_UNION, + AliasKind::Opaque, ) }); } - // define the type `Num.Integer Num.Signed64` + // define the type `Num.Integer Num.Signed64 := Num.Signed64` { - let tags = UnionTags::insert_into_subs( - subs, - [(TagName::Private(Symbol::NUM_AT_INTEGER), [signed64])], - ); - subs.set_content(at_integer_signed64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - let vars = AliasVariables::insert_into_subs(subs, [signed64], []); subs.set_content(integer_signed64, { - Content::Alias( - Symbol::NUM_INTEGER, - vars, - at_signed64, - AliasKind::Structural, - ) + Content::Alias(Symbol::NUM_INTEGER, vars, signed64, AliasKind::Opaque) }); } - // define the type `Num.Num (Num.Integer Num.Signed64)` + // define the type `Num.Num (Num.Integer Num.Signed64) := Num.Integer Num.Signed64` { - let tags = UnionTags::insert_into_subs( - subs, - [(TagName::Private(Symbol::NUM_AT_NUM), [integer_signed64])], - ); - subs.set_content(at_num_integer_signed64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - let vars = AliasVariables::insert_into_subs(subs, [integer_signed64], []); subs.set_content(num_integer_signed64, { - Content::Alias( - Symbol::NUM_NUM, - vars, - at_num_integer_signed64, - AliasKind::Structural, - ) + Content::Alias(Symbol::NUM_NUM, vars, integer_signed64, AliasKind::Opaque) }); + } + // define the type `Num.I64 : Num.Num (Num.Integer Num.Signed64)` + { subs.set_content(var_i64, { Content::Alias( num_i64, @@ -1344,154 +1335,110 @@ fn integer_type( fn define_integer_types(subs: &mut Subs) { integer_type( subs, - Symbol::NUM_AT_SIGNED128, Symbol::NUM_SIGNED128, Symbol::NUM_I128, - Variable::AT_SIGNED128, Variable::SIGNED128, - Variable::AT_INTEGER_SIGNED128, Variable::INTEGER_SIGNED128, - Variable::AT_NUM_INTEGER_SIGNED128, Variable::NUM_INTEGER_SIGNED128, Variable::I128, ); integer_type( subs, - Symbol::NUM_AT_SIGNED64, Symbol::NUM_SIGNED64, Symbol::NUM_I64, - Variable::AT_SIGNED64, Variable::SIGNED64, - Variable::AT_INTEGER_SIGNED64, Variable::INTEGER_SIGNED64, - Variable::AT_NUM_INTEGER_SIGNED64, Variable::NUM_INTEGER_SIGNED64, Variable::I64, ); integer_type( subs, - Symbol::NUM_AT_SIGNED32, Symbol::NUM_SIGNED32, Symbol::NUM_I32, - Variable::AT_SIGNED32, Variable::SIGNED32, - Variable::AT_INTEGER_SIGNED32, Variable::INTEGER_SIGNED32, - Variable::AT_NUM_INTEGER_SIGNED32, Variable::NUM_INTEGER_SIGNED32, Variable::I32, ); integer_type( subs, - Symbol::NUM_AT_SIGNED16, Symbol::NUM_SIGNED16, Symbol::NUM_I16, - Variable::AT_SIGNED16, Variable::SIGNED16, - Variable::AT_INTEGER_SIGNED16, Variable::INTEGER_SIGNED16, - Variable::AT_NUM_INTEGER_SIGNED16, Variable::NUM_INTEGER_SIGNED16, Variable::I16, ); integer_type( subs, - Symbol::NUM_AT_SIGNED8, Symbol::NUM_SIGNED8, Symbol::NUM_I8, - Variable::AT_SIGNED8, Variable::SIGNED8, - Variable::AT_INTEGER_SIGNED8, Variable::INTEGER_SIGNED8, - Variable::AT_NUM_INTEGER_SIGNED8, Variable::NUM_INTEGER_SIGNED8, Variable::I8, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED128, Symbol::NUM_UNSIGNED128, Symbol::NUM_U128, - Variable::AT_UNSIGNED128, Variable::UNSIGNED128, - Variable::AT_INTEGER_UNSIGNED128, Variable::INTEGER_UNSIGNED128, - Variable::AT_NUM_INTEGER_UNSIGNED128, Variable::NUM_INTEGER_UNSIGNED128, Variable::U128, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED64, Symbol::NUM_UNSIGNED64, Symbol::NUM_U64, - Variable::AT_UNSIGNED64, Variable::UNSIGNED64, - Variable::AT_INTEGER_UNSIGNED64, Variable::INTEGER_UNSIGNED64, - Variable::AT_NUM_INTEGER_UNSIGNED64, Variable::NUM_INTEGER_UNSIGNED64, Variable::U64, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED32, Symbol::NUM_UNSIGNED32, Symbol::NUM_U32, - Variable::AT_UNSIGNED32, Variable::UNSIGNED32, - Variable::AT_INTEGER_UNSIGNED32, Variable::INTEGER_UNSIGNED32, - Variable::AT_NUM_INTEGER_UNSIGNED32, Variable::NUM_INTEGER_UNSIGNED32, Variable::U32, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED16, Symbol::NUM_UNSIGNED16, Symbol::NUM_U16, - Variable::AT_UNSIGNED16, Variable::UNSIGNED16, - Variable::AT_INTEGER_UNSIGNED16, Variable::INTEGER_UNSIGNED16, - Variable::AT_NUM_INTEGER_UNSIGNED16, Variable::NUM_INTEGER_UNSIGNED16, Variable::U16, ); integer_type( subs, - Symbol::NUM_AT_UNSIGNED8, Symbol::NUM_UNSIGNED8, Symbol::NUM_U8, - Variable::AT_UNSIGNED8, Variable::UNSIGNED8, - Variable::AT_INTEGER_UNSIGNED8, Variable::INTEGER_UNSIGNED8, - Variable::AT_NUM_INTEGER_UNSIGNED8, Variable::NUM_INTEGER_UNSIGNED8, Variable::U8, ); integer_type( subs, - Symbol::NUM_AT_NATURAL, Symbol::NUM_NATURAL, Symbol::NUM_NAT, - Variable::AT_NATURAL, Variable::NATURAL, - Variable::AT_INTEGER_NATURAL, Variable::INTEGER_NATURAL, - Variable::AT_NUM_INTEGER_NATURAL, Variable::NUM_INTEGER_NATURAL, Variable::NAT, ); @@ -1501,80 +1448,47 @@ fn define_integer_types(subs: &mut Subs) { fn float_type( subs: &mut Subs, - num_at_binary64: Symbol, num_binary64: Symbol, num_f64: Symbol, - at_binary64: Variable, binary64: Variable, - at_float_binary64: Variable, float_binary64: Variable, - at_num_float_binary64: Variable, num_float_binary64: Variable, var_f64: Variable, ) { - // define the type Binary64 (which is an alias for [ @Binary64 ]) + // define the type Binary64 := [] { - let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_binary64), [])]); - - subs.set_content(at_binary64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - subs.set_content(binary64, { Content::Alias( num_binary64, AliasVariables::default(), - at_binary64, + Variable::EMPTY_TAG_UNION, AliasKind::Structural, ) }); } - // define the type `Num.Float Num.Binary64` + // define the type `Num.Float Num.Binary64 := Num.Binary64` { - let tags = UnionTags::insert_into_subs( - subs, - [(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), [binary64])], - ); - subs.set_content(at_float_binary64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - let vars = AliasVariables::insert_into_subs(subs, [binary64], []); subs.set_content(float_binary64, { - Content::Alias( - Symbol::NUM_FLOATINGPOINT, - vars, - at_binary64, - AliasKind::Structural, - ) + Content::Alias(Symbol::NUM_FLOATINGPOINT, vars, binary64, AliasKind::Opaque) + }); + } + + // define the type `Num.Num (Num.Float Num.Binary64) := Num.Float Num.Binary64` + { + let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []); + subs.set_content(num_float_binary64, { + Content::Alias(Symbol::NUM_NUM, vars, float_binary64, AliasKind::Opaque) }); } // define the type `F64: Num.Num (Num.Float Num.Binary64)` { - let tags = UnionTags::insert_into_subs( - subs, - [(TagName::Private(Symbol::NUM_AT_NUM), [float_binary64])], - ); - subs.set_content(at_num_float_binary64, { - Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) - }); - - let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []); - subs.set_content(num_float_binary64, { - Content::Alias( - Symbol::NUM_NUM, - vars, - at_num_float_binary64, - AliasKind::Structural, - ) - }); - subs.set_content(var_f64, { Content::Alias( num_f64, @@ -1589,42 +1503,30 @@ fn float_type( fn define_float_types(subs: &mut Subs) { float_type( subs, - Symbol::NUM_AT_BINARY32, Symbol::NUM_BINARY32, Symbol::NUM_F32, - Variable::AT_BINARY32, Variable::BINARY32, - Variable::AT_FLOAT_BINARY32, Variable::FLOAT_BINARY32, - Variable::AT_NUM_FLOAT_BINARY32, Variable::NUM_FLOAT_BINARY32, Variable::F32, ); float_type( subs, - Symbol::NUM_AT_BINARY64, Symbol::NUM_BINARY64, Symbol::NUM_F64, - Variable::AT_BINARY64, Variable::BINARY64, - Variable::AT_FLOAT_BINARY64, Variable::FLOAT_BINARY64, - Variable::AT_NUM_FLOAT_BINARY64, Variable::NUM_FLOAT_BINARY64, Variable::F64, ); float_type( subs, - Symbol::NUM_AT_DECIMAL, Symbol::NUM_DECIMAL, Symbol::NUM_DEC, - Variable::AT_DECIMAL, Variable::DECIMAL, - Variable::AT_FLOAT_DECIMAL, Variable::FLOAT_DECIMAL, - Variable::AT_NUM_FLOAT_DECIMAL, Variable::NUM_FLOAT_DECIMAL, Variable::DEC, ); @@ -1634,12 +1536,9 @@ impl Subs { pub const RESULT_TAG_NAMES: SubsSlice = SubsSlice::new(0, 2); pub const TAG_NAME_ERR: SubsIndex = SubsIndex::new(0); pub const TAG_NAME_OK: SubsIndex = SubsIndex::new(1); - pub const NUM_AT_NUM: SubsSlice = SubsSlice::new(2, 1); - pub const NUM_AT_INTEGER: SubsSlice = SubsSlice::new(3, 1); - pub const NUM_AT_FLOATINGPOINT: SubsSlice = SubsSlice::new(4, 1); - pub const TAG_NAME_INVALID_NUM_STR: SubsIndex = SubsIndex::new(5); - pub const TAG_NAME_BAD_UTF_8: SubsIndex = SubsIndex::new(6); - pub const TAG_NAME_OUT_OF_BOUNDS: SubsIndex = SubsIndex::new(7); + pub const TAG_NAME_INVALID_NUM_STR: SubsIndex = SubsIndex::new(2); + pub const TAG_NAME_BAD_UTF_8: SubsIndex = SubsIndex::new(3); + pub const TAG_NAME_OUT_OF_BOUNDS: SubsIndex = SubsIndex::new(4); pub fn new() -> Self { Self::with_capacity(0) @@ -1650,16 +1549,12 @@ impl Subs { let mut tag_names = Vec::with_capacity(32); - tag_names.push(TagName::Global("Err".into())); - tag_names.push(TagName::Global("Ok".into())); + tag_names.push(TagName::Tag("Err".into())); + tag_names.push(TagName::Tag("Ok".into())); - tag_names.push(TagName::Private(Symbol::NUM_AT_NUM)); - tag_names.push(TagName::Private(Symbol::NUM_AT_INTEGER)); - tag_names.push(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT)); - - tag_names.push(TagName::Global("InvalidNumStr".into())); - tag_names.push(TagName::Global("BadUtf8".into())); - tag_names.push(TagName::Global("OutOfBounds".into())); + tag_names.push(TagName::Tag("InvalidNumStr".into())); + tag_names.push(TagName::Tag("BadUtf8".into())); + tag_names.push(TagName::Tag("OutOfBounds".into())); let mut subs = Subs { utable: UnificationTable::default(), @@ -1699,8 +1594,8 @@ impl Subs { let bool_union_tags = UnionTags::insert_into_subs( &mut subs, [ - (TagName::Global("False".into()), []), - (TagName::Global("True".into()), []), + (TagName::Tag("False".into()), []), + (TagName::Tag("True".into()), []), ], ); @@ -1889,7 +1784,14 @@ impl Subs { } pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec)> { - occurs(self, &[], var) + occurs(self, &[], var, false) + } + + pub fn occurs_including_recursion_vars( + &self, + var: Variable, + ) -> Result<(), (Variable, Vec)> { + occurs(self, &[], var, true) } pub fn mark_tag_union_recursive( @@ -1965,9 +1867,10 @@ impl Subs { let mut state = ErrorTypeState { taken, - normals: 0, + letters_used: 0, problems: Vec::new(), context, + recursive_tag_unions_seen: Vec::new(), }; (var_to_err_type(self, &mut state, var), state.problems) @@ -2125,8 +2028,8 @@ impl From for Descriptor { } } -roc_error_macros::assert_sizeof_all!(Content, 4 * 8); -roc_error_macros::assert_sizeof_all!((Symbol, AliasVariables, Variable), 3 * 8); +roc_error_macros::assert_sizeof_all!(Content, 3 * 8 + 4); +roc_error_macros::assert_sizeof_all!((Symbol, AliasVariables, Variable), 2 * 8 + 4); roc_error_macros::assert_sizeof_all!(AliasVariables, 8); roc_error_macros::assert_sizeof_all!(FlatType, 3 * 8); @@ -2134,6 +2037,9 @@ roc_error_macros::assert_sizeof_aarch64!((Variable, Option), 4 * 8); roc_error_macros::assert_sizeof_wasm!((Variable, Option), 4 * 4); roc_error_macros::assert_sizeof_default!((Variable, Option), 4 * 8); +roc_error_macros::assert_copyable!(Content); +roc_error_macros::assert_copyable!(Descriptor); + #[derive(Clone, Copy, Debug)] pub enum Content { /// A type variable which the user did not name in an annotation, @@ -2268,23 +2174,6 @@ impl Content { Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)) ) } - - #[cfg(debug_assertions)] - #[allow(dead_code)] - pub fn dbg(self, subs: &Subs) -> Self { - let home = roc_module::symbol::ModuleIds::default().get_or_insert(&"#Dbg".into()); - let interns = roc_module::symbol::Interns { - all_ident_ids: roc_module::symbol::IdentIds::exposed_builtins(0), - ..Default::default() - }; - - eprintln!( - "{}", - crate::pretty_print::content_to_string(&self, subs, home, &interns) - ); - - self - } } #[derive(Clone, Copy, Debug)] @@ -2378,10 +2267,10 @@ impl UnionTags { slice.length == 1 } - pub fn is_newtype_wrapper_of_global_tag(&self, subs: &Subs) -> bool { + pub fn is_newtype_wrapper_of_tag(&self, subs: &Subs) -> bool { self.is_newtype_wrapper(subs) && { let tags = &subs.tag_names[self.tag_names().indices()]; - matches!(tags[0], TagName::Global(_)) + matches!(tags[0], TagName::Tag(_)) } } @@ -2500,12 +2389,24 @@ impl UnionTags { pub fn iter_all( &self, - ) -> impl Iterator, SubsIndex)> { + ) -> impl Iterator, SubsIndex)> + ExactSizeIterator + { self.tag_names() .into_iter() .zip(self.variables().into_iter()) } + /// Iterator over (TagName, &[Variable]) pairs obtained by + /// looking up slices in the given Subs + pub fn iter_from_subs<'a>( + &'a self, + subs: &'a Subs, + ) -> impl Iterator + ExactSizeIterator { + self.iter_all().map(move |(name_index, payload_index)| { + (&subs[name_index], subs.get_subs_slice(subs[payload_index])) + }) + } + #[inline(always)] pub fn unsorted_iterator<'a>( &'a self, @@ -2603,7 +2504,7 @@ impl<'a> UnsortedUnionTags<'a> { } } -pub type SortedTagsIterator<'a> = Box + 'a>; +pub type SortedTagsIterator<'a> = Box + 'a>; pub type SortedTagsSlicesIterator<'a> = Box + 'a>; pub fn is_empty_tag_union(subs: &Subs, mut var: Variable) -> bool { @@ -2739,12 +2640,13 @@ impl RecordFields { field_types_start, } } + #[inline(always)] pub fn unsorted_iterator<'a>( &'a self, subs: &'a Subs, ext: Variable, - ) -> Result)> + 'a, RecordFieldsError> + ) -> Result)> + 'a, RecordFieldsError> { let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)?; @@ -2859,6 +2761,7 @@ fn occurs( subs: &Subs, seen: &[Variable], input_var: Variable, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> { use self::Content::*; use self::FlatType::*; @@ -2882,47 +2785,77 @@ fn occurs( new_seen.push(root_var); match flat_type { - Apply(_, args) => { - short_circuit(subs, root_var, &new_seen, subs.get_subs_slice(*args).iter()) - } + Apply(_, args) => short_circuit( + subs, + root_var, + &new_seen, + subs.get_subs_slice(*args).iter(), + include_recursion_var, + ), Func(arg_vars, closure_var, ret_var) => { let it = once(ret_var) .chain(once(closure_var)) .chain(subs.get_subs_slice(*arg_vars).iter()); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } Record(vars_by_field, ext_var) => { let slice = SubsSlice::new(vars_by_field.variables_start, vars_by_field.length); let it = once(ext_var).chain(subs.get_subs_slice(slice).iter()); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } TagUnion(tags, ext_var) => { for slice_index in tags.variables() { let slice = subs[slice_index]; for var_index in slice { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help( + subs, + root_var, + &new_seen, + var, + include_recursion_var, + )?; } } - short_circuit_help(subs, root_var, &new_seen, *ext_var) + short_circuit_help( + subs, + root_var, + &new_seen, + *ext_var, + include_recursion_var, + ) } FunctionOrTagUnion(_, _, ext_var) => { let it = once(ext_var); - short_circuit(subs, root_var, &new_seen, it) + short_circuit(subs, root_var, &new_seen, it, include_recursion_var) } - RecursiveTagUnion(_rec_var, tags, ext_var) => { - // TODO rec_var is excluded here, verify that this is correct + RecursiveTagUnion(rec_var, tags, ext_var) => { + if include_recursion_var { + new_seen.push(subs.get_root_key_without_compacting(*rec_var)); + } for slice_index in tags.variables() { let slice = subs[slice_index]; for var_index in slice { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help( + subs, + root_var, + &new_seen, + var, + include_recursion_var, + )?; } } - short_circuit_help(subs, root_var, &new_seen, *ext_var) + short_circuit_help( + subs, + root_var, + &new_seen, + *ext_var, + include_recursion_var, + ) } EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()), } @@ -2933,7 +2866,7 @@ fn occurs( for var_index in args.into_iter() { let var = subs[var_index]; - short_circuit_help(subs, root_var, &new_seen, var)?; + short_circuit_help(subs, root_var, &new_seen, var, include_recursion_var)?; } Ok(()) @@ -2942,7 +2875,7 @@ fn occurs( let mut new_seen = seen.to_owned(); new_seen.push(root_var); - short_circuit_help(subs, root_var, &new_seen, *typ)?; + short_circuit_help(subs, root_var, &new_seen, *typ, include_recursion_var)?; // _range_vars excluded because they are not explicitly part of the type. Ok(()) @@ -2957,12 +2890,13 @@ fn short_circuit<'a, T>( root_key: Variable, seen: &[Variable], iter: T, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> where T: Iterator, { for var in iter { - short_circuit_help(subs, root_key, seen, *var)?; + short_circuit_help(subs, root_key, seen, *var, include_recursion_var)?; } Ok(()) @@ -2974,8 +2908,9 @@ fn short_circuit_help( root_key: Variable, seen: &[Variable], var: Variable, + include_recursion_var: bool, ) -> Result<(), (Variable, Vec)> { - if let Err((v, mut vec)) = occurs(subs, seen, var) { + if let Err((v, mut vec)) = occurs(subs, seen, var, include_recursion_var) { vec.push(root_key); return Err((v, vec)); } @@ -3416,7 +3351,10 @@ fn content_to_err_type( ErrorType::RigidAbleVar(name, ability) } - RecursionVar { opt_name, .. } => { + RecursionVar { + opt_name, + structure, + } => { let name = match opt_name { Some(name_index) => subs.field_names[name_index.index as usize].clone(), None => { @@ -3429,7 +3367,11 @@ fn content_to_err_type( } }; - ErrorType::FlexVar(name) + if state.recursive_tag_unions_seen.contains(&var) { + ErrorType::FlexVar(name) + } else { + var_to_err_type(subs, state, structure) + } } Alias(symbol, args, aliased_to, kind) => { @@ -3610,6 +3552,8 @@ fn flat_type_to_err_type( } RecursiveTagUnion(rec_var, tags, ext_var) => { + state.recursive_tag_unions_seen.push(rec_var); + let mut err_tags = SendMap::default(); for (name_index, slice_index) in tags.iter_all() { @@ -3660,11 +3604,12 @@ fn flat_type_to_err_type( } fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase { - let (name, new_index) = name_type_var(state.normals, &mut state.taken.iter(), |var, str| { - var.as_str() == str - }); + let (name, new_index) = + name_type_var(state.letters_used, &mut state.taken.iter(), |var, str| { + var.as_str() == str + }); - state.normals = new_index; + state.letters_used = new_index; state.taken.insert(name.clone()); diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 0f3536cd54..0ba04ba799 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -120,13 +120,15 @@ impl RecordField { } } - pub fn instantiate_aliases( + pub fn instantiate_aliases<'a, F>( &mut self, region: Region, - aliases: &ImMap, + aliases: &'a F, var_store: &mut VarStore, introduced: &mut ImSet, - ) { + ) where + F: Fn(Symbol) -> Option<&'a Alias>, + { use RecordField::*; match self { @@ -168,13 +170,15 @@ impl LambdaSet { &mut self.0 } - fn instantiate_aliases( + fn instantiate_aliases<'a, F>( &mut self, region: Region, - aliases: &ImMap, + aliases: &'a F, var_store: &mut VarStore, introduced: &mut ImSet, - ) { + ) where + F: Fn(Symbol) -> Option<&'a Alias>, + { self.0 .instantiate_aliases(region, aliases, var_store, introduced) } @@ -183,10 +187,40 @@ impl LambdaSet { #[derive(PartialEq, Eq, Clone)] pub struct AliasCommon { pub symbol: Symbol, - pub type_arguments: Vec<(Lowercase, Type)>, + pub type_arguments: Vec, pub lambda_set_variables: Vec, } +#[derive(Clone, Copy, Debug)] +pub struct OptAbleVar { + pub var: Variable, + pub opt_ability: Option, +} + +impl OptAbleVar { + pub fn unbound(var: Variable) -> Self { + Self { + var, + opt_ability: None, + } + } +} + +#[derive(PartialEq, Eq, Debug)] +pub struct OptAbleType { + pub typ: Type, + pub opt_ability: Option, +} + +impl OptAbleType { + pub fn unbound(typ: Type) -> Self { + Self { + typ, + opt_ability: None, + } + } +} + #[derive(PartialEq, Eq)] pub enum Type { EmptyRec, @@ -204,14 +238,14 @@ pub enum Type { DelayedAlias(AliasCommon), Alias { symbol: Symbol, - type_arguments: Vec<(Lowercase, Type)>, + type_arguments: Vec, lambda_set_variables: Vec, actual: Box, kind: AliasKind, }, HostExposedAlias { name: Symbol, - type_arguments: Vec<(Lowercase, Type)>, + type_arguments: Vec, lambda_set_variables: Vec, actual_var: Variable, actual: Box, @@ -296,6 +330,16 @@ impl Clone for Type { } } +impl Clone for OptAbleType { + fn clone(&self) -> Self { + // This passes through `Type`, so defer to that to bump the clone counter. + Self { + typ: self.typ.clone(), + opt_ability: self.opt_ability, + } + } +} + #[derive(PartialEq, Eq, Clone)] pub enum TypeExtension { Open(Box), @@ -381,7 +425,7 @@ impl fmt::Debug for Type { }) => { write!(f, "(DelayedAlias {:?}", symbol)?; - for (_, arg) in type_arguments { + for arg in type_arguments { write!(f, " {:?}", arg)?; } @@ -405,8 +449,11 @@ impl fmt::Debug for Type { } => { write!(f, "(Alias {:?}", symbol)?; - for (_, arg) in type_arguments { - write!(f, " {:?}", arg)?; + for arg in type_arguments { + write!(f, " {:?}", &arg.typ)?; + if let Some(ab) = arg.opt_ability { + write!(f, ":{:?}", ab)?; + } } for (lambda_set, greek_letter) in @@ -429,7 +476,7 @@ impl fmt::Debug for Type { } => { write!(f, "HostExposedAlias {:?}", name)?; - for (_, arg) in arguments { + for arg in arguments { write!(f, " {:?}", arg)?; } @@ -690,7 +737,7 @@ impl Type { lambda_set_variables, .. }) => { - for (_, value) in type_arguments.iter_mut() { + for value in type_arguments.iter_mut() { stack.push(value); } @@ -704,8 +751,8 @@ impl Type { actual, .. } => { - for (_, value) in type_arguments.iter_mut() { - stack.push(value); + for value in type_arguments.iter_mut() { + stack.push(&mut value.typ); } for lambda_set in lambda_set_variables.iter_mut() { @@ -720,7 +767,7 @@ impl Type { actual: actual_type, .. } => { - for (_, value) in type_arguments.iter_mut() { + for value in type_arguments.iter_mut() { stack.push(value); } @@ -799,7 +846,7 @@ impl Type { lambda_set_variables, .. }) => { - for (_, value) in type_arguments.iter_mut() { + for value in type_arguments.iter_mut() { stack.push(value); } @@ -813,8 +860,8 @@ impl Type { actual, .. } => { - for (_, value) in type_arguments.iter_mut() { - stack.push(value); + for value in type_arguments.iter_mut() { + stack.push(&mut value.typ); } for lambda_set in lambda_set_variables.iter_mut() { stack.push(lambda_set.as_inner_mut()); @@ -828,7 +875,7 @@ impl Type { actual: actual_type, .. } => { - for (_, value) in type_arguments.iter_mut() { + for value in type_arguments.iter_mut() { stack.push(value); } @@ -899,7 +946,7 @@ impl Type { lambda_set_variables: _no_aliases_in_lambda_sets, .. }) => { - for (_, ta) in type_arguments { + for ta in type_arguments { ta.substitute_alias(rep_symbol, rep_args, actual)?; } @@ -910,8 +957,8 @@ impl Type { actual: alias_actual, .. } => { - for (_, ta) in type_arguments { - ta.substitute_alias(rep_symbol, rep_args, actual)?; + for ta in type_arguments { + ta.typ.substitute_alias(rep_symbol, rep_args, actual)?; } alias_actual.substitute_alias(rep_symbol, rep_args, actual) } @@ -981,9 +1028,7 @@ impl Type { .. }) => { symbol == &rep_symbol - || type_arguments - .iter() - .any(|v| v.1.contains_symbol(rep_symbol)) + || type_arguments.iter().any(|v| v.contains_symbol(rep_symbol)) || lambda_set_variables .iter() .any(|v| v.0.contains_symbol(rep_symbol)) @@ -1064,13 +1109,28 @@ impl Type { result } - pub fn instantiate_aliases( + pub fn shallow_structural_dealias(&self) -> &Self { + let mut result = self; + while let Type::Alias { + actual, + kind: AliasKind::Structural, + .. + } = result + { + result = actual; + } + result + } + + pub fn instantiate_aliases<'a, F>( &mut self, region: Region, - aliases: &ImMap, + aliases: &'a F, var_store: &mut VarStore, new_lambda_set_variables: &mut ImSet, - ) { + ) where + F: Fn(Symbol) -> Option<&'a Alias>, + { use Type::*; match self { @@ -1106,24 +1166,52 @@ impl Type { ext.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables); } } - DelayedAlias(AliasCommon { .. }) => { - // do nothing, yay + DelayedAlias(AliasCommon { + type_arguments, + lambda_set_variables, + symbol: _, + }) => { + debug_assert!(lambda_set_variables + .iter() + .all(|lambda_set| matches!(lambda_set.0, Type::Variable(..)))); + type_arguments.iter_mut().for_each(|t| { + t.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables) + }); } HostExposedAlias { type_arguments: type_args, lambda_set_variables, actual: actual_type, .. + } => { + for arg in type_args { + arg.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables); + } + + for arg in lambda_set_variables { + arg.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables); + } + + actual_type.instantiate_aliases( + region, + aliases, + var_store, + new_lambda_set_variables, + ); } - | Alias { + Alias { type_arguments: type_args, lambda_set_variables, actual: actual_type, .. } => { for arg in type_args { - arg.1 - .instantiate_aliases(region, aliases, var_store, new_lambda_set_variables); + arg.typ.instantiate_aliases( + region, + aliases, + var_store, + new_lambda_set_variables, + ); } for arg in lambda_set_variables { @@ -1138,16 +1226,14 @@ impl Type { ); } Apply(symbol, args, _) => { - if let Some(alias) = aliases.get(symbol) { + if let Some(alias) = aliases(*symbol) { // TODO switch to this, but we still need to check for recursion with the // `else` branch if false { let mut type_var_to_arg = Vec::new(); - for (loc_var, arg_ann) in alias.type_variables.iter().zip(args) { - let name = loc_var.value.0.clone(); - - type_var_to_arg.push((name, arg_ann.clone())); + for (_, arg_ann) in alias.type_variables.iter().zip(args) { + type_var_to_arg.push(arg_ann.clone()); } let mut lambda_set_variables = @@ -1187,7 +1273,12 @@ impl Type { // TODO substitute further in args for ( Loc { - value: (lowercase, placeholder), + value: + AliasVar { + var: placeholder, + opt_bound_ability, + .. + }, .. }, filler, @@ -1200,7 +1291,10 @@ impl Type { var_store, new_lambda_set_variables, ); - named_args.push((lowercase.clone(), filler.clone())); + named_args.push(OptAbleType { + typ: filler.clone(), + opt_ability: *opt_bound_ability, + }); substitution.insert(*placeholder, filler); } @@ -1364,7 +1458,7 @@ fn symbols_help(initial: &Type) -> Vec { .. }) => { output.push(*symbol); - stack.extend(type_arguments.iter().map(|v| &v.1)); + stack.extend(type_arguments); } Alias { symbol: alias_symbol, @@ -1472,7 +1566,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { lambda_set_variables, .. }) => { - for (_, arg) in type_arguments { + for arg in type_arguments { variables_help(arg, accum); } @@ -1485,8 +1579,8 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { actual, .. } => { - for (_, arg) in type_arguments { - variables_help(arg, accum); + for arg in type_arguments { + variables_help(&arg.typ, accum); } variables_help(actual, accum); } @@ -1495,7 +1589,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { actual, .. } => { - for (_, arg) in arguments { + for arg in arguments { variables_help(arg, accum); } variables_help(actual, accum); @@ -1604,7 +1698,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { lambda_set_variables, .. }) => { - for (_, arg) in type_arguments { + for arg in type_arguments { variables_help_detailed(arg, accum); } @@ -1621,8 +1715,8 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { actual, .. } => { - for (_, arg) in type_arguments { - variables_help_detailed(arg, accum); + for arg in type_arguments { + variables_help_detailed(&arg.typ, accum); } variables_help_detailed(actual, accum); } @@ -1631,7 +1725,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { actual, .. } => { - for (_, arg) in arguments { + for arg in arguments { variables_help_detailed(arg, accum); } variables_help_detailed(actual, accum); @@ -1670,6 +1764,7 @@ pub enum PReason { }, WhenMatch { index: HumanIndex, + sub_pattern: HumanIndex, }, TagArg { tag_name: TagName, @@ -1693,6 +1788,9 @@ pub enum AnnotationSource { TypedBody { region: Region, }, + RequiredSymbol { + region: Region, + }, } impl AnnotationSource { @@ -1701,6 +1799,7 @@ impl AnnotationSource { &Self::TypedIfBranch { region, .. } | &Self::TypedWhenBranch { region, .. } | &Self::TypedBody { region, .. } => region, + &Self::RequiredSymbol { region, .. } => region, } } } @@ -1711,6 +1810,10 @@ pub enum Reason { name: Option, arg_index: HumanIndex, }, + TypedArg { + name: Option, + arg_index: HumanIndex, + }, FnCall { name: Option, arity: u8, @@ -1727,6 +1830,7 @@ pub enum Reason { IntLiteral, NumLiteral, StrInterpolation, + WhenBranches, WhenBranch { index: HumanIndex, }, @@ -1794,6 +1898,9 @@ pub enum Category { DefaultValue(Lowercase), // for setting optional fields AbilityMemberSpecialization(Symbol), + + Expect, + Unknown, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -1826,10 +1933,37 @@ pub enum AliasKind { Opaque, } +#[derive(Clone, Debug, PartialEq)] +pub struct AliasVar { + pub name: Lowercase, + pub var: Variable, + /// `Some` if this variable is bound to an ability; `None` otherwise. + pub opt_bound_ability: Option, +} + +impl AliasVar { + pub fn unbound(name: Lowercase, var: Variable) -> AliasVar { + Self { + name, + var, + opt_bound_ability: None, + } + } +} + +impl From<&AliasVar> for OptAbleVar { + fn from(av: &AliasVar) -> OptAbleVar { + OptAbleVar { + var: av.var, + opt_ability: av.opt_bound_ability, + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct Alias { pub region: Region, - pub type_variables: Vec>, + pub type_variables: Vec>, /// lambda set variables, e.g. the one annotating the arrow in /// a |c|-> b @@ -1990,7 +2124,7 @@ fn write_error_type_help( if write_parens { buf.push('('); } - buf.push_str(symbol.ident_str(interns).as_str()); + buf.push_str(symbol.as_str(interns)); for arg in arguments { buf.push(' '); @@ -2484,7 +2618,10 @@ pub fn gather_tags_unsorted_iter( // TODO investigate this likely can happen when there is a type error RigidVar(_) => break, - other => unreachable!("something weird ended up in a tag union type: {:?}", other), + other => unreachable!( + "something weird ended up in a tag union type: {:?} at {:?}", + other, var + ), } } diff --git a/compiler/unify/Cargo.toml b/compiler/unify/Cargo.toml index d8622ed928..e92e2aa3e3 100644 --- a/compiler/unify/Cargo.toml +++ b/compiler/unify/Cargo.toml @@ -19,3 +19,6 @@ path = "../module" [dependencies.roc_types] path = "../types" + +[dependencies.roc_debug_flags] +path = "../debug_flags" diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 10738f6382..4afbb0abe6 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,4 +1,5 @@ use bitflags::bitflags; +use roc_debug_flags::{dbg_do, ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS}; use roc_error_macros::internal_error; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -11,14 +12,14 @@ use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, macro_rules! mismatch { () => {{ - if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { - println!( + dbg_do!(ROC_PRINT_MISMATCHES, { + eprintln!( "Mismatch in {} Line {} Column {}", file!(), line!(), column!() ); - } + }) Outcome { mismatches: vec![Mismatch::TypeMismatch], @@ -26,17 +27,16 @@ macro_rules! mismatch { } }}; ($msg:expr) => {{ - if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { - println!( + dbg_do!(ROC_PRINT_MISMATCHES, { + eprintln!( "Mismatch in {} Line {} Column {}", file!(), line!(), column!() ); - println!($msg); - println!(""); - } - + eprintln!($msg); + eprintln!(""); + }); Outcome { mismatches: vec![Mismatch::TypeMismatch], @@ -47,16 +47,16 @@ macro_rules! mismatch { mismatch!($msg) }}; ($msg:expr, $($arg:tt)*) => {{ - if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { - println!( + dbg_do!(ROC_PRINT_MISMATCHES, { + eprintln!( "Mismatch in {} Line {} Column {}", file!(), line!(), column!() ); - println!($msg, $($arg)*); - println!(""); - } + eprintln!($msg, $($arg)*); + eprintln!(""); + }); Outcome { mismatches: vec![Mismatch::TypeMismatch], @@ -64,16 +64,16 @@ macro_rules! mismatch { } }}; (%not_able, $var:expr, $ability:expr, $msg:expr, $($arg:tt)*) => {{ - if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { - println!( + dbg_do!(ROC_PRINT_MISMATCHES, { + eprintln!( "Mismatch in {} Line {} Column {}", file!(), line!(), column!() ); - println!($msg, $($arg)*); - println!(""); - } + eprintln!($msg, $($arg)*); + eprintln!(""); + }); Outcome { mismatches: vec![Mismatch::TypeMismatch, Mismatch::DoesNotImplementAbiity($var, $ability)], @@ -119,6 +119,21 @@ impl Mode { fn as_eq(self) -> Self { (self - Mode::PRESENT) | Mode::EQ } + + #[cfg(debug_assertions)] + fn pretty_print(&self) -> &str { + if self.contains(Mode::EQ | Mode::RIGID_AS_FLEX) { + "~*" + } else if self.contains(Mode::PRESENT | Mode::RIGID_AS_FLEX) { + "+=*" + } else if self.contains(Mode::EQ) { + "~" + } else if self.contains(Mode::PRESENT) { + "+=" + } else { + unreachable!("Bad mode!") + } + } } #[derive(Debug)] @@ -279,9 +294,11 @@ pub fn unify_pool( /// NOTE: Only run this on individual tests! Run on multiple threads, this would clobber each others' output. #[cfg(debug_assertions)] fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option<&Outcome>) { + use roc_types::subs::SubsFmtContent; + static mut UNIFICATION_DEPTH: usize = 0; - if std::env::var("ROC_PRINT_UNIFICATIONS").is_ok() { + dbg_do!(ROC_PRINT_UNIFICATIONS, { let prefix = match opt_outcome { None => "❔", Some(outcome) if outcome.mismatches.is_empty() => "✅", @@ -308,7 +325,7 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option // println!("\n --------------- \n"); let content_1 = subs.get(ctx.first).content; let content_2 = subs.get(ctx.second).content; - let mode = if ctx.mode.is_eq() { "~" } else { "+=" }; + let mode = ctx.mode.pretty_print(); eprintln!( "{}{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}", " ".repeat(use_depth), @@ -316,14 +333,14 @@ fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, opt_outcome: Option ctx.first, ctx.second, ctx.first, - roc_types::subs::SubsFmtContent(&content_1, subs), + SubsFmtContent(&content_1, subs), mode, ctx.second, - roc_types::subs::SubsFmtContent(&content_2, subs), + SubsFmtContent(&content_2, subs), ); unsafe { UNIFICATION_DEPTH = new_depth }; - } + }) } fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { @@ -357,8 +374,11 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { Structure(flat_type) => { unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) } - Alias(symbol, args, real_var, kind) => { - unify_alias(subs, pool, &ctx, *symbol, *args, *real_var, *kind) + Alias(symbol, args, real_var, AliasKind::Structural) => { + unify_alias(subs, pool, &ctx, *symbol, *args, *real_var) + } + Alias(symbol, args, real_var, AliasKind::Opaque) => { + unify_opaque(subs, pool, &ctx, *symbol, *args, *real_var) } &RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars), Error => { @@ -447,6 +467,57 @@ fn check_valid_range( } } +#[inline(always)] +#[allow(clippy::too_many_arguments)] +fn unify_two_aliases( + subs: &mut Subs, + pool: &mut Pool, + ctx: &Context, + symbol: Symbol, + args: AliasVariables, + real_var: Variable, + other_args: AliasVariables, + other_real_var: Variable, + other_content: &Content, +) -> Outcome { + if args.len() == other_args.len() { + let mut outcome = Outcome::default(); + let it = args + .all_variables() + .into_iter() + .zip(other_args.all_variables().into_iter()); + + let args_unification_snapshot = subs.snapshot(); + + for (l, r) in it { + let l_var = subs[l]; + let r_var = subs[r]; + outcome.union(unify_pool(subs, pool, l_var, r_var, ctx.mode)); + } + + if outcome.mismatches.is_empty() { + outcome.union(merge(subs, ctx, *other_content)); + } + + let args_unification_had_changes = !subs + .vars_since_snapshot(&args_unification_snapshot) + .is_empty(); + subs.commit_snapshot(args_unification_snapshot); + + if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() { + // We need to unify the real vars because unification of type variables + // may have made them larger, which then needs to be reflected in the `real_var`. + outcome.union(unify_pool(subs, pool, real_var, other_real_var, ctx.mode)); + } + + outcome + } else { + dbg!(args.len(), other_args.len()); + mismatch!("{:?}", symbol) + } +} + +// Unifies a structural alias #[inline(always)] fn unify_alias( subs: &mut Subs, @@ -455,72 +526,40 @@ fn unify_alias( symbol: Symbol, args: AliasVariables, real_var: Variable, - kind: AliasKind, ) -> Outcome { let other_content = &ctx.second_desc.content; - let either_is_opaque = - kind == AliasKind::Opaque || matches!(other_content, Alias(_, _, _, AliasKind::Opaque)); + let kind = AliasKind::Structural; match other_content { FlexVar(_) => { // Alias wins merge(subs, ctx, Alias(symbol, args, real_var, kind)) } - RecursionVar { structure, .. } if !either_is_opaque => { - unify_pool(subs, pool, real_var, *structure, ctx.mode) + RecursionVar { structure, .. } => unify_pool(subs, pool, real_var, *structure, ctx.mode), + RigidVar(_) | RigidAbleVar(..) | FlexAbleVar(..) => { + unify_pool(subs, pool, real_var, ctx.second, ctx.mode) } - RigidVar(_) | RigidAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), - FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => { - // Opaque type wins - let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind)); - outcome.must_implement_ability.push(MustImplementAbility { typ: symbol, ability: *ability }); - outcome - } - Alias(other_symbol, other_args, other_real_var, _) - // Opaques types are only equal if the opaque symbols are equal! - if !either_is_opaque || symbol == *other_symbol => - { + Alias(_, _, _, AliasKind::Opaque) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + Alias(other_symbol, other_args, other_real_var, AliasKind::Structural) => { if symbol == *other_symbol { - if args.len() == other_args.len() { - let mut outcome = Outcome::default(); - let it = args - .all_variables() - .into_iter() - .zip(other_args.all_variables().into_iter()); - - let args_unification_snapshot = subs.snapshot(); - - for (l, r) in it { - let l_var = subs[l]; - let r_var = subs[r]; - outcome.union(unify_pool(subs, pool, l_var, r_var, ctx.mode)); - } - - if outcome.mismatches.is_empty() { - outcome.union(merge(subs, ctx, *other_content)); - } - - let args_unification_had_changes = !subs.vars_since_snapshot(&args_unification_snapshot).is_empty(); - subs.commit_snapshot(args_unification_snapshot); - - if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() { - // We need to unify the real vars because unification of type variables - // may have made them larger, which then needs to be reflected in the `real_var`. - outcome.union(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode)); - } - - outcome - } else { - dbg!(args.len(), other_args.len()); - mismatch!("{:?}", symbol) - } + unify_two_aliases( + subs, + pool, + ctx, + symbol, + args, + real_var, + *other_args, + *other_real_var, + other_content, + ) } else { unify_pool(subs, pool, real_var, *other_real_var, ctx.mode) } } - Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), - RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => { + Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); if outcome.mismatches.is_empty() { check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode) @@ -529,9 +568,69 @@ fn unify_alias( } } Error => merge(subs, ctx, Error), + } +} + +#[inline(always)] +fn unify_opaque( + subs: &mut Subs, + pool: &mut Pool, + ctx: &Context, + symbol: Symbol, + args: AliasVariables, + real_var: Variable, +) -> Outcome { + let other_content = &ctx.second_desc.content; + + let kind = AliasKind::Opaque; + + match other_content { + FlexVar(_) => { + // Alias wins + merge(subs, ctx, Alias(symbol, args, real_var, kind)) + } + // RigidVar(_) | RigidAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + FlexAbleVar(_, ability) if args.is_empty() => { + // Opaque type wins + let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind)); + outcome.must_implement_ability.push(MustImplementAbility { + typ: symbol, + ability: *ability, + }); + outcome + } + Alias(_, _, other_real_var, AliasKind::Structural) => { + unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode) + } + Alias(other_symbol, other_args, other_real_var, AliasKind::Opaque) => { + // Opaques types are only equal if the opaque symbols are equal! + if symbol == *other_symbol { + unify_two_aliases( + subs, + pool, + ctx, + symbol, + args, + real_var, + *other_args, + *other_real_var, + other_content, + ) + } else { + mismatch!("{:?}", symbol) + } + } + RangedNumber(other_real_var, other_range_vars) => { + // This opaque might be a number, check if it unifies with the target ranged number var. + let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); + if outcome.mismatches.is_empty() { + check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) + } else { + outcome + } + } other => { - // The type on the left is an alias, but the one on the right is not! - debug_assert!(either_is_opaque); + // The type on the left is an opaque, but the one on the right is not! mismatch!("Cannot unify opaque {:?} with {:?}", symbol, other) } } @@ -576,7 +675,15 @@ fn unify_structure( RecursionVar { structure, .. } => match flat_type { FlatType::TagUnion(_, _) => { // unify the structure with this unrecursive tag union - unify_pool(subs, pool, ctx.first, *structure, ctx.mode) + let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + + if outcome.mismatches.is_empty() { + outcome.union(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + } + + outcome } FlatType::RecursiveTagUnion(rec, _, _) => { debug_assert!(is_recursion_var(subs, *rec)); @@ -585,7 +692,15 @@ fn unify_structure( } FlatType::FunctionOrTagUnion(_, _, _) => { // unify the structure with this unrecursive tag union - unify_pool(subs, pool, ctx.first, *structure, ctx.mode) + let mut outcome = unify_pool(subs, pool, ctx.first, *structure, ctx.mode); + + if outcome.mismatches.is_empty() { + outcome.union(fix_tag_union_recursion_variable( + subs, ctx, ctx.first, other, + )); + } + + outcome } // Only tag unions can be recursive; everything else is an error. _ => mismatch!( @@ -643,6 +758,57 @@ fn unify_structure( } } +/// Ensures that a non-recursive tag union, when unified with a recursion var to become a recursive +/// tag union, properly contains a recursion variable that recurses on itself. +// +// When might this not be the case? For example, in the code +// +// Indirect : [ Indirect ConsList ] +// +// ConsList : [ Nil, Cons Indirect ] +// +// l : ConsList +// l = Cons (Indirect (Cons (Indirect Nil))) +// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a +// # ~~~~~~~~~~~~~~~~~~~~~ region-b +// l +// +// Suppose `ConsList` has the expanded type `[ Nil, Cons [ Indirect ] ] as `. +// After unifying the tag application annotated "region-b" with the recursion variable ``, +// the tentative total-type of the application annotated "region-a" would be +// ` = [ Nil, Cons [ Indirect ] ] as `. That is, the type of the recursive tag union +// would be inlined at the site "v", rather than passing through the correct recursion variable +// "rec" first. +// +// This is not incorrect from a type perspective, but causes problems later on for e.g. layout +// determination, which expects recursion variables to be placed correctly. Attempting to detect +// this during layout generation does not work so well because it may be that there *are* recursive +// tag unions that should be inlined, and not pass through recursion variables. So instead, try to +// resolve these cases here. +// +// See tests labeled "issue_2810" for more examples. +fn fix_tag_union_recursion_variable( + subs: &mut Subs, + ctx: &Context, + tag_union_promoted_to_recursive: Variable, + recursion_var: &Content, +) -> Outcome { + debug_assert!(matches!( + subs.get_content_without_compacting(tag_union_promoted_to_recursive), + Structure(FlatType::RecursiveTagUnion(..)) + )); + + let has_recursing_recursive_variable = subs + .occurs_including_recursion_vars(tag_union_promoted_to_recursive) + .is_err(); + + if !has_recursing_recursive_variable { + merge(subs, ctx, *recursion_var) + } else { + Outcome::default() + } +} + fn unify_record( subs: &mut Subs, pool: &mut Pool, @@ -1192,7 +1358,17 @@ fn maybe_mark_tag_union_recursive(subs: &mut Subs, tag_union_var: Variable) { } } - panic!("recursive loop does not contain a tag union") + // Might not be any tag union if we only pass through `Apply`s. Otherwise, we have a bug! + if chain.iter().all(|&v| { + matches!( + subs.get_content_without_compacting(v), + Content::Structure(FlatType::Apply(..)) + ) + }) { + return; + } else { + internal_error!("recursive loop does not contain a tag union") + } } } } @@ -1647,11 +1823,12 @@ fn unify_flex( other: &Content, ) -> Outcome { match other { - FlexVar(None) => { - // If both are flex, and only left has a name, keep the name around. + FlexVar(other_opt_name) => { + // Prefer using right's name. + let opt_name = opt_name.or(*other_opt_name); match opt_able_bound { - Some(ability) => merge(subs, ctx, FlexAbleVar(*opt_name, ability)), - None => merge(subs, ctx, FlexVar(*opt_name)), + Some(ability) => merge(subs, ctx, FlexAbleVar(opt_name, ability)), + None => merge(subs, ctx, FlexVar(opt_name)), } } @@ -1683,8 +1860,7 @@ fn unify_flex( } } - FlexVar(Some(_)) - | RigidVar(_) + RigidVar(_) | RigidAbleVar(_, _) | RecursionVar { .. } | Structure(_) @@ -1692,7 +1868,6 @@ fn unify_flex( | RangedNumber(..) => { // TODO special-case boolean here // In all other cases, if left is flex, defer to right. - // (This includes using right's name if both are flex and named.) merge(subs, ctx, *other) } diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 1fe3c231e2..39e39bb68f 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -5,8 +5,6 @@ license = "UPL-1.0" authors = ["The Roc Contributors"] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] pulldown-cmark = { version = "0.8.0", default-features = false } roc_ast = { path = "../ast" } @@ -28,6 +26,3 @@ peg = "0.8.0" [dev-dependencies] pretty_assertions = "1.0.0" -tempfile = "3.2.0" -uuid = { version = "0.8.2", features = ["v4"] } -indoc = "1.0.3" diff --git a/docs/src/lib.rs b/docs/src/lib.rs index d09134d98d..9e91906b25 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -6,13 +6,12 @@ use html::mark_node_to_html; use roc_can::scope::Scope; use roc_code_markup::markup::nodes::MarkupNode; use roc_code_markup::slow_pool::SlowPool; -use roc_collections::all::MutMap; use roc_highlight::highlight_parser::{highlight_defs, highlight_expr}; use roc_load::docs::DocEntry::DocDef; use roc_load::docs::{DocEntry, TypeAnnotation}; use roc_load::docs::{ModuleDocumentation, RecordField}; -use roc_load::{LoadedModule, LoadingProblem}; -use roc_module::symbol::{IdentIds, Interns, ModuleId}; +use roc_load::{LoadedModule, LoadingProblem, Threading}; +use roc_module::symbol::{IdentIdsByModule, Interns, ModuleId}; use roc_parse::ident::{parse_ident, Ident}; use roc_parse::state::State; use roc_region::all::Region; @@ -22,11 +21,13 @@ use std::path::{Path, PathBuf}; mod docs_error; mod html; -pub fn generate_docs_html(filenames: Vec, build_dir: &Path) { +const BUILD_DIR: &str = "./generated-docs"; + +pub fn generate_docs_html(filenames: Vec) { + let build_dir = Path::new(BUILD_DIR); let loaded_modules = load_modules_for_files(filenames); - // - // TODO: get info from a file like "elm.json" + // TODO: get info from a package module; this is all hardcoded for now. let mut package = roc_load::docs::Documentation { name: "roc/builtins".to_string(), version: "1.0.0".to_string(), @@ -58,49 +59,61 @@ pub fn generate_docs_html(filenames: Vec, build_dir: &Path) { .expect("TODO gracefully handle failing to make the favicon"); let template_html = include_str!("./static/index.html") - .replace("", &format!("{}search.js", base_url())) - .replace("", &format!("{}styles.css", base_url())) - .replace( - "", - &format!("{}favicon.svg", base_url()), - ) + .replace("", "/search.js") + .replace("", "/styles.css") + .replace("", "/favicon.svg") .replace( "", render_sidebar(package.modules.iter().flat_map(|loaded_module| { - loaded_module.documentation.values().map(move |d| { - let exposed_values = loaded_module - .exposed_values - .iter() - .map(|symbol| symbol.ident_str(&loaded_module.interns).to_string()) - .collect::>(); + loaded_module + .documentation + .iter() + .filter_map(move |(module_id, module)| { + // TODO it seems this `documentation` dictionary has entries for + // every module, but only the current module has any info in it. + // We disregard the others, but probably this shouldn't bother + // being a hash map in the first place if only one of its entries + // actually has interesting information in it? + if *module_id == loaded_module.module_id { + let exposed_values = loaded_module + .exposed_values + .iter() + .map(|symbol| symbol.as_str(&loaded_module.interns).to_string()) + .collect::>(); - (exposed_values, d) - }) + Some((module, exposed_values)) + } else { + None + } + }) })) .as_str(), ); // Write each package's module docs html file for loaded_module in package.modules.iter_mut() { - for module_docs in loaded_module.documentation.values() { - let module_dir = build_dir.join(module_docs.name.replace('.', "/").as_str()); + for (module_id, module_docs) in loaded_module.documentation.iter() { + if *module_id == loaded_module.module_id { + let module_dir = build_dir.join(module_docs.name.replace('.', "/").as_str()); - fs::create_dir_all(&module_dir) - .expect("TODO gracefully handle not being able to create the module dir"); + fs::create_dir_all(&module_dir) + .expect("TODO gracefully handle not being able to create the module dir"); - let rendered_module = template_html - .replace( - "", - render_name_and_version(package.name.as_str(), package.version.as_str()) - .as_str(), - ) - .replace( - "", - render_module_documentation(module_docs, loaded_module).as_str(), + let rendered_module = template_html + .replace( + "", + render_name_and_version(package.name.as_str(), package.version.as_str()) + .as_str(), + ) + .replace( + "", + render_module_documentation(module_docs, loaded_module).as_str(), + ); + + fs::write(module_dir.join("index.html"), rendered_module).expect( + "TODO gracefully handle failing to write index.html inside module's dir", ); - - fs::write(module_dir.join("index.html"), rendered_module) - .expect("TODO gracefully handle failing to write html"); + } } } @@ -336,12 +349,12 @@ fn render_name_and_version(name: &str, version: &str) -> String { buf } -fn render_sidebar<'a, I: Iterator, &'a ModuleDocumentation)>>( +fn render_sidebar<'a, I: Iterator)>>( modules: I, ) -> String { let mut buf = String::new(); - for (exposed_values, module) in modules { + for (module, exposed_values) in modules { let mut sidebar_entry_content = String::new(); let name = module.name.as_str(); @@ -412,7 +425,7 @@ fn render_sidebar<'a, I: Iterator, &'a ModuleDocumentation)> pub fn load_modules_for_files(filenames: Vec) -> Vec { let arena = Bump::new(); - let mut modules = vec![]; + let mut modules = Vec::with_capacity(filenames.len()); for filename in filenames { let mut src_dir = filename.clone(); @@ -425,11 +438,12 @@ pub fn load_modules_for_files(filenames: Vec) -> Vec { Default::default(), roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter roc_reporting::report::RenderTarget::ColorTerminal, + Threading::AllAvailable, ) { Ok(loaded) => modules.push(loaded), Err(LoadingProblem::FormattedReport(report)) => { - println!("{}", report); - panic!(); + eprintln!("{}", report); + std::process::exit(1); } Err(e) => panic!("{:?}", e), } @@ -711,7 +725,7 @@ struct DocUrl { fn doc_url<'a>( home: ModuleId, exposed_values: &[&str], - dep_idents: &MutMap, + dep_idents: &IdentIdsByModule, scope: &Scope, interns: &'a Interns, mut module_name: &'a str, @@ -844,8 +858,8 @@ fn markdown_to_html( } } } - Ok((_, Ident::GlobalTag(type_name), _)) => { - // This looks like a global tag name, but it could + Ok((_, Ident::Tag(type_name), _)) => { + // This looks like a tag name, but it could // be a type alias that's in scope, e.g. [I64] let DocUrl { url, title } = doc_url( loaded_module.module_id, diff --git a/docs/src/static/styles.css b/docs/src/static/styles.css index 799d849a86..486fa8972a 100644 --- a/docs/src/static/styles.css +++ b/docs/src/static/styles.css @@ -53,6 +53,7 @@ a { .entry-name { white-space: pre-wrap; + font-family: var(--font-mono); } .pkg-full-name a { diff --git a/docs_cli/Cargo.toml b/docs_cli/Cargo.toml new file mode 100644 index 0000000000..5cf22bf54c --- /dev/null +++ b/docs_cli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "roc_docs_cli" +version = "0.1.0" +license = "UPL-1.0" +authors = ["The Roc Contributors"] +edition = "2018" + +# This binary is only used on static build servers, e.g. Netlify. +# Having its own (extremely minimal) CLI means docs can be generated +# on a build server after building this crate from source, without +# having to install non-Rust dependencies (LLVM, Zig, wasm things, etc.) +# It gets called in www/build.sh via `cargo run --bin roc-docs` +[[bin]] +name = "roc-docs" +path = "src/main.rs" +test = false +bench = false + +[dependencies] +roc_docs = { path = "../docs" } +clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions", "derive"] } diff --git a/docs_cli/src/main.rs b/docs_cli/src/main.rs new file mode 100644 index 0000000000..26ac6abcda --- /dev/null +++ b/docs_cli/src/main.rs @@ -0,0 +1,50 @@ +use clap::{Arg, Command}; +use roc_docs::generate_docs_html; +use std::fs::{self, FileType}; +use std::io; +use std::path::{Path, PathBuf}; + +pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; + +fn main() -> io::Result<()> { + let matches = Command::new("roc-docs") + .about("Build HTML documentation files from the given .roc files") + .arg( + Arg::new(DIRECTORY_OR_FILES) + .multiple_values(true) + .required(true) + .help("The directory or files to build documentation for") + .allow_invalid_utf8(true), + ) + .get_matches(); + + let mut roc_files = Vec::new(); + + // Populate roc_files + for os_str in matches.values_of_os(DIRECTORY_OR_FILES).unwrap() { + let metadata = fs::metadata(os_str)?; + roc_files_recursive(os_str, metadata.file_type(), &mut roc_files)?; + } + + generate_docs_html(roc_files); + + Ok(()) +} + +fn roc_files_recursive>( + path: P, + file_type: FileType, + roc_files: &mut Vec, +) -> io::Result<()> { + if file_type.is_dir() { + for entry_res in fs::read_dir(path)? { + let entry = entry_res?; + + roc_files_recursive(entry.path(), entry.file_type()?, roc_files)?; + } + } else { + roc_files.push(path.as_ref().to_path_buf()); + } + + Ok(()) +} diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 5dbfac12f7..c3c5c63559 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -26,6 +26,7 @@ use pipelines::RectResources; use roc_ast::lang::env::Env; use roc_ast::mem_pool::pool::Pool; use roc_ast::module::load_module; +use roc_load::Threading; use roc_module::symbol::IdentIds; use roc_types::subs::VarStore; use std::collections::HashSet; @@ -74,7 +75,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box Result<(wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> { +) -> Result<(wgpu::Device, wgpu::Queue, wgpu::TextureFormat), wgpu::RequestDeviceError> { if force_fallback_adapter { log::error!("Falling back to software renderer. GPU acceleration has been disabled."); } @@ -418,7 +417,13 @@ async fn create_device( If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor "#); - adapter + let color_format = surface.get_preferred_format(&adapter).unwrap(); + + if color_format != wgpu::TextureFormat::Bgra8UnormSrgb { + log::warn!("Your preferred TextureFormat {:?} is different than expected. Colors may look different, please report this issue on github and tag @Anton-4.", color_format); + } + + let request_res = adapter .request_device( &wgpu::DeviceDescriptor { label: None, @@ -427,7 +432,12 @@ async fn create_device( }, None, ) - .await + .await; + + match request_res { + Ok((device, queue)) => Ok((device, queue, color_format)), + Err(err) => Err(err), + } } fn draw_rects( diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index bc4f6f540e..b557623ffd 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -234,7 +234,7 @@ pub mod test_ed_model { use roc_ast::lang::env::Env; use roc_ast::mem_pool::pool::Pool; use roc_ast::module::load_module; - use roc_load::LoadedModule; + use roc_load::{LoadedModule, Threading}; use roc_module::symbol::IdentIds; use roc_module::symbol::ModuleIds; use roc_types::subs::VarStore; @@ -330,7 +330,7 @@ pub mod test_ed_model { writeln!(file, "{}", clean_code_str) .unwrap_or_else(|_| panic!("Failed to write {:?} to file: {:?}", clean_code_str, file)); - let loaded_module = load_module(&temp_file_full_path); + let loaded_module = load_module(&temp_file_full_path, Threading::AllAvailable); let mut ed_model = init_dummy_model( clean_code_str, diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index ba546e85d1..b07aeb51ee 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -59,9 +59,9 @@ use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_region::all::Region; +use roc_types::pretty_print::name_and_print_var; use roc_types::solved_types::Solved; -use roc_types::subs::{Subs, Variable}; -use roc_types::{pretty_print::content_to_string, subs::VarStore}; +use roc_types::subs::{Subs, VarStore, Variable}; use snafu::OptionExt; use threadpool::ThreadPool; use winit::event::VirtualKeyCode; @@ -462,17 +462,10 @@ impl<'a> EdModel<'a> { let subs = solved.inner_mut(); - let content = subs.get_content_without_compacting(var); + let pretty_var = + name_and_print_var(var, subs, self.module.env.home, &self.loaded_module.interns); - PoolStr::new( - &content_to_string( - content, - subs, - self.module.env.home, - &self.loaded_module.interns, - ), - self.module.env.pool, - ) + PoolStr::new(&pretty_var, self.module.env.pool) } fn run_solve( @@ -3169,7 +3162,7 @@ pub mod test_ed_update { assert_type_tooltips_clean( ovec!["val = [ [ 0, 1, \"2\" ], [ 3, 4, 5 ┃] ]"], - ovec!["List (Num *)", "List (List )"], + ovec!["List (Num *)", "List "], )?; Ok(()) diff --git a/editor/src/editor/mvc/let_update.rs b/editor/src/editor/mvc/let_update.rs index de59fbf901..482b5d4c9a 100644 --- a/editor/src/editor/mvc/let_update.rs +++ b/editor/src/editor/mvc/let_update.rs @@ -25,7 +25,7 @@ pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< let val_expr2_node = Expr2::Blank; let val_expr_id = ed_model.module.env.pool.add(val_expr2_node); - let ident_id = ed_model.module.env.ident_ids.add(val_name_string.into()); + let ident_id = ed_model.module.env.ident_ids.add_str(&val_name_string); let var_symbol = Symbol::new(ed_model.module.env.home, ident_id); let body = Expr2::Var(var_symbol); let body_id = ed_model.module.env.pool.add(body); diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index e548dd2e25..72323b25c2 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -26,13 +26,8 @@ pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< let val_expr_node = Expr2::Blank; let val_expr_id = ed_model.module.env.pool.add(val_expr_node); - let val_name_string = new_char.to_string(); - - let ident_id = ed_model - .module - .env - .ident_ids - .add(val_name_string.clone().into()); + let ident_str = new_char.to_string(); + let ident_id = ed_model.module.env.ident_ids.add_str(&ident_str); let module_ident_ids_opt = ed_model .loaded_module @@ -42,7 +37,7 @@ pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< if let Some(module_ident_ids_ref) = module_ident_ids_opt { // this might create different IdentId for interns and env.ident_ids which may be a problem - module_ident_ids_ref.add(val_name_string.into()); + module_ident_ids_ref.add_str(&ident_str); } else { KeyNotFound { key_str: format!("{:?}", ed_model.module.env.home), diff --git a/editor/src/graphics/colors.rs b/editor/src/graphics/colors.rs index 924c9ab301..67dc2801a8 100644 --- a/editor/src/graphics/colors.rs +++ b/editor/src/graphics/colors.rs @@ -1,4 +1,4 @@ -use palette::{FromColor, Hsv, Srgb}; +use palette::{FromColor, Hsv, LinSrgb, Srgb}; pub type RgbaTup = (f32, f32, f32, f32); pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0); @@ -21,11 +21,11 @@ pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup { } pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup { - let rgb = Srgb::from_color(Hsv::new( + let rgb = LinSrgb::from(Srgb::from_color(Hsv::new( hue as f32, (saturation as f32) / 100.0, (brightness as f32) / 100.0, - )); + ))); (rgb.red, rgb.green, rgb.blue, alpha) } diff --git a/error_macros/src/lib.rs b/error_macros/src/lib.rs index 2cd1eeb732..41ec7b6172 100644 --- a/error_macros/src/lib.rs +++ b/error_macros/src/lib.rs @@ -66,6 +66,14 @@ macro_rules! assert_sizeof_all { }; } +/// Assert that a type has `Copy` +#[macro_export] +macro_rules! assert_copyable { + ($t: ty) => { + static_assertions::assert_impl_all!($t: Copy); + }; +} + // LARGE SCALE PROJECTS // // This section is for "todo!"-style macros enabled in sections where large-scale changes to the diff --git a/examples/algorithms/fibonacci-platform/host.zig b/examples/algorithms/fibonacci-platform/host.zig index d31cbad4d9..55f55e2658 100644 --- a/examples/algorithms/fibonacci-platform/host.zig +++ b/examples/algorithms/fibonacci-platform/host.zig @@ -2,6 +2,7 @@ const std = @import("std"); const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; +const maxInt = std.math.maxInt; comptime { // This is a workaround for https://github.com/ziglang/zig/issues/8218 @@ -12,7 +13,8 @@ comptime { // -fcompiler-rt in link.rs instead of doing this. Note that this // workaround is present in many host.zig files, so make sure to undo // it everywhere! - if (std.builtin.os.tag == .macos) { + const builtin = @import("builtin"); + if (builtin.os.tag == .macos) { _ = @import("compiler_rt"); } } @@ -24,14 +26,16 @@ const Allocator = mem.Allocator; // extern fn roc__mainForHost_1_exposed(i64, *i64) void; extern fn roc__mainForHost_1_exposed(i64) i64; -const Align = extern struct { a: usize, b: usize }; -extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void; +const Align = 2 * @alignOf(usize); +extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; +extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; const DEBUG: bool = false; -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { var ptr = malloc(size); const stdout = std.io.getStdOut().writer(); @@ -42,25 +46,25 @@ export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { } } -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; } - return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size); + return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); } -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { +export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; } - free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr))); + free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { _ = tag_id; const stderr = std.io.getStdErr().writer(); @@ -69,19 +73,27 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void { + return memcpy(dst, src, size); +} + +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { + return memset(dst, value, size); +} + pub export fn main() u8 { const stdout = std.io.getStdOut().writer(); const stderr = std.io.getStdErr().writer(); // start time var ts1: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable; const result = roc__mainForHost_1_exposed(10); // end time var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable; stdout.print("{d}\n", .{result}) catch unreachable; diff --git a/examples/algorithms/quicksort-platform/host.zig b/examples/algorithms/quicksort-platform/host.zig index 0bd71a48b8..a7f7f6b9b1 100644 --- a/examples/algorithms/quicksort-platform/host.zig +++ b/examples/algorithms/quicksort-platform/host.zig @@ -1,4 +1,7 @@ const std = @import("std"); +const builtin = @import("builtin"); +const str = @import("str"); +const RocStr = str.RocStr; const testing = std.testing; const expectEqual = testing.expectEqual; const expect = testing.expect; @@ -12,7 +15,7 @@ comptime { // -fcompiler-rt in link.rs instead of doing this. Note that this // workaround is present in many host.zig files, so make sure to undo // it everywhere! - if (std.builtin.os.tag == .macos) { + if (builtin.os.tag == .macos) { _ = @import("compiler_rt"); } } @@ -21,18 +24,17 @@ const mem = std.mem; const Allocator = mem.Allocator; extern fn roc__mainForHost_1_exposed_generic(output: *RocList, input: *RocList) void; -// extern fn roc__mainForHost_1_exposed_generic(input: *RocList) RocList; -const Align = extern struct { a: usize, b: usize }; -extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void; +const Align = 2 * @alignOf(usize); +extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; +extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; const DEBUG: bool = false; -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { var ptr = malloc(size); const stdout = std.io.getStdOut().writer(); @@ -43,25 +45,25 @@ export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { } } -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; } - return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size); + return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); } -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { +export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; } - free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr))); + free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { _ = tag_id; const stderr = std.io.getStdErr().writer(); @@ -87,7 +89,6 @@ const Unit = extern struct {}; pub export fn main() u8 { const stdout = std.io.getStdOut().writer(); - const stderr = std.io.getStdErr().writer(); var raw_numbers: [NUM_NUMS + 1]i64 = undefined; @@ -104,7 +105,7 @@ pub export fn main() u8 { // start time var ts1: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable; // actually call roc to populate the callresult var callresult: RocList = undefined; @@ -118,7 +119,7 @@ pub export fn main() u8 { // end time var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable; for (result) |x, i| { if (i == 0) { diff --git a/examples/benchmarks/AStar.roc b/examples/benchmarks/AStar.roc index 4f5a8e0fa1..d6aec65d8f 100644 --- a/examples/benchmarks/AStar.roc +++ b/examples/benchmarks/AStar.roc @@ -12,13 +12,12 @@ Model position : } initialModel : position -> Model position -initialModel = \start -> - { - evaluated: Set.empty, - openSet: Set.single start, - costs: Dict.single start 0, - cameFrom: Dict.empty, - } +initialModel = \start -> { + evaluated: Set.empty, + openSet: Set.single start, + costs: Dict.single start 0, + cameFrom: Dict.empty, +} cheapestOpen : (position -> F64), Model position -> Result position {} cheapestOpen = \costFn, model -> @@ -58,7 +57,7 @@ updateCost = \current, neighbor, model -> distanceTo = reconstructPath newCameFrom neighbor |> List.len - |> Num.toFloat + |> Num.toFrac newModel = { model & diff --git a/examples/benchmarks/Base64/Decode.roc b/examples/benchmarks/Base64/Decode.roc index e78585ad5b..ad5c92ef17 100644 --- a/examples/benchmarks/Base64/Decode.roc +++ b/examples/benchmarks/Base64/Decode.roc @@ -20,11 +20,10 @@ loopHelp = \{ remaining, string } -> c = Num.intCast z combined = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy 16 a) (Num.shiftLeftBy 8 b)) c - Loop - { - remaining: remaining - 3, - string: Str.concat string (bitsToChars combined 0), - } + Loop { + remaining: remaining - 3, + string: Str.concat string (bitsToChars combined 0), + } else if remaining == 0 then Bytes.Decode.succeed (Done string) else if remaining == 2 then diff --git a/examples/benchmarks/Bytes/Decode.roc b/examples/benchmarks/Bytes/Decode.roc index d5e22e8f78..6a5df2c8f3 100644 --- a/examples/benchmarks/Bytes/Decode.roc +++ b/examples/benchmarks/Bytes/Decode.roc @@ -4,7 +4,7 @@ State : { bytes : List U8, cursor : Nat } DecodeProblem : [ OutOfBytes ] -Decoder a : [ @Decoder (State -> [ Good State a, Bad DecodeProblem ]) ] +Decoder a := State -> [ Good State a, Bad DecodeProblem ] decode : List U8, Decoder a -> Result a DecodeProblem decode = \bytes, @Decoder decoder -> diff --git a/examples/benchmarks/Bytes/Encode.roc b/examples/benchmarks/Bytes/Encode.roc index 37321e8506..195ccd2407 100644 --- a/examples/benchmarks/Bytes/Encode.roc +++ b/examples/benchmarks/Bytes/Encode.roc @@ -132,11 +132,10 @@ encodeHelp = \encoder, offset, output -> List.walk bs { output, offset } - \accum, byte -> - { - offset: accum.offset + 1, - output: List.set accum.output offset byte, - } + \accum, byte -> { + offset: accum.offset + 1, + output: List.set accum.output offset byte, + } Sequence _ encoders -> List.walk diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index caf08906a6..c26e46d8a5 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -6,7 +6,7 @@ app "deriv" # based on: https://github.com/koka-lang/koka/blob/master/test/bench/haskell/deriv.hs IO a : Task.Task a [] -main : IO {} +main : Task.Task {} [] main = Task.after Task.getInt @@ -41,8 +41,8 @@ Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]* divmod = \l, r -> - when Pair (l // r) (l % r) is - Pair div (Ok mod) -> + when Pair (Num.divTruncChecked l r) (Num.remChecked l r) is + Pair (Ok div) (Ok mod) -> Ok { div, mod } _ -> diff --git a/examples/benchmarks/RBTreeCk.roc b/examples/benchmarks/RBTreeCk.roc index 225df33161..90c6fb3d4f 100644 --- a/examples/benchmarks/RBTreeCk.roc +++ b/examples/benchmarks/RBTreeCk.roc @@ -23,12 +23,12 @@ makeMapHelp = \freq, n, m, acc -> _ -> powerOf10 = - (n % 10 |> resultWithDefault 0) == 0 + n % 10 == 0 m1 = insert m n powerOf10 isFrequency = - (n % freq |> resultWithDefault 0) == 0 + n % freq == 0 x = (if isFrequency then Cons m1 acc else acc) @@ -43,15 +43,6 @@ fold = \f, tree, b -> Node _ l k v r -> fold f r (f k v (fold f l b)) -resultWithDefault : Result a e, a -> a -resultWithDefault = \res, default -> - when res is - Ok v -> - v - - Err _ -> - default - main : Task.Task {} [] main = Task.after diff --git a/examples/benchmarks/platform/Effect.roc b/examples/benchmarks/platform/Effect.roc index dfbd8c4db4..11f9614bf3 100644 --- a/examples/benchmarks/platform/Effect.roc +++ b/examples/benchmarks/platform/Effect.roc @@ -7,4 +7,4 @@ putLine : Str -> Effect {} putInt : I64 -> Effect {} -getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } +getInt : Effect { value : I64, isError : Bool } diff --git a/examples/benchmarks/platform/Package-Config.roc b/examples/benchmarks/platform/Package-Config.roc index f78bdf17c2..c73c5b39ce 100644 --- a/examples/benchmarks/platform/Package-Config.roc +++ b/examples/benchmarks/platform/Package-Config.roc @@ -1,5 +1,5 @@ platform "benchmarks" - requires {} { main : Effect {} } + requires {} { main : Task {} [] } exposes [] packages {} imports [ Task.{ Task } ] diff --git a/examples/benchmarks/platform/Task.roc b/examples/benchmarks/platform/Task.roc index 37ccbcedb9..1cc96bc6c5 100644 --- a/examples/benchmarks/platform/Task.roc +++ b/examples/benchmarks/platform/Task.roc @@ -79,14 +79,14 @@ getInt : Task I64 [] getInt = Effect.after Effect.getInt - \{ isError, value, errorCode } -> + \{ isError, value } -> when isError is True -> - when errorCode is - # A -> Task.fail InvalidCharacter - # B -> Task.fail IOError - _ -> - Task.succeed -1 + # when errorCode is + # # A -> Task.fail InvalidCharacter + # # B -> Task.fail IOError + # _ -> + Task.succeed -1 False -> Task.succeed value diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index 857dc3b43d..d349cb64b6 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -15,7 +15,8 @@ comptime { // -fcompiler-rt in link.rs instead of doing this. Note that this // workaround is present in many host.zig files, so make sure to undo // it everywhere! - if (std.builtin.os.tag == .macos) { + const builtin = @import("builtin"); + if (builtin.os.tag == .macos) { _ = @import("compiler_rt"); } } @@ -30,15 +31,15 @@ extern fn roc__mainForHost_1_Fx_size() i64; extern fn roc__mainForHost_1_Fx_result_size() i64; const Align = 2 * @alignOf(usize); -extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; -extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; +extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; const DEBUG: bool = false; -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { var ptr = malloc(size); const stdout = std.io.getStdOut().writer(); @@ -49,7 +50,7 @@ export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { } } -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; @@ -58,7 +59,7 @@ export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); } -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { +export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; @@ -67,7 +68,7 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { _ = tag_id; const stderr = std.io.getStdErr().writer(); @@ -98,7 +99,7 @@ pub export fn main() callconv(.C) u8 { } var ts1: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable; roc__mainForHost_1_exposed_generic(output); @@ -107,7 +108,7 @@ pub export fn main() callconv(.C) u8 { call_the_closure(closure_data_pointer); var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable; const delta = to_seconds(ts2) - to_seconds(ts1); @@ -162,7 +163,6 @@ export fn roc_fx_putLine(rocPath: *str.RocStr) callconv(.C) void { const GetInt = extern struct { value: i64, - error_code: bool, is_error: bool, }; @@ -176,14 +176,14 @@ comptime { fn roc_fx_getInt_64bit() callconv(.C) GetInt { if (roc_fx_getInt_help()) |value| { - const get_int = GetInt{ .is_error = false, .value = value, .error_code = false }; + const get_int = GetInt{ .is_error = false, .value = value }; return get_int; } else |err| switch (err) { error.InvalidCharacter => { - return GetInt{ .is_error = true, .value = 0, .error_code = false }; + return GetInt{ .is_error = true, .value = 0 }; }, else => { - return GetInt{ .is_error = true, .value = 0, .error_code = true }; + return GetInt{ .is_error = true, .value = 0 }; }, } @@ -214,8 +214,3 @@ fn roc_fx_getInt_help() !i64 { return std.fmt.parseInt(i64, line, 10); } - -fn readLine() []u8 { - const stdin = std.io.getStdIn().reader(); - return (stdin.readUntilDelimiterOrEof(&line_buf, '\n') catch unreachable) orelse ""; -} diff --git a/examples/breakout/.gitignore b/examples/breakout/.gitignore new file mode 100644 index 0000000000..593993d712 --- /dev/null +++ b/examples/breakout/.gitignore @@ -0,0 +1 @@ +breakout diff --git a/examples/breakout/breakout.roc b/examples/breakout/breakout.roc new file mode 100644 index 0000000000..1625f9b411 --- /dev/null +++ b/examples/breakout/breakout.roc @@ -0,0 +1,165 @@ +app "breakout" + packages { pf: "platform" } + imports [ pf.Game.{ Bounds, Elem, Event } ] + provides [ program ] { Model } to pf + +paddleWidth = 0.2# width of the paddle, as a % of screen width +paddleHeight = 50# height of the paddle, in pixels +paddleSpeed = 65# how many pixels the paddle moves per keypress +blockHeight = 80# height of a block, in pixels +blockBorder = 0.025# border of a block, as a % of its width +ballSize = 55 +numRows = 4 +numCols = 8 +numBlocks = numRows * numCols + +Model : { + # Screen height and width + height : F32, + width : F32, + # Paddle X-coordinate + paddleX : F32, + # Ball coordinates + ballX : F32, + ballY : F32, + dBallX : F32, + # delta x - how much it moves per tick + dBallY : F32, + # delta y - how much it moves per tick + } + +init : Bounds -> Model +init = \{ width, height } -> { + # Screen height and width + width, + height, + # Paddle X-coordinate + paddleX: (width * 0.5) - (paddleWidth * width * 0.5), + # Ball coordinates + ballX: width * 0.5, + ballY: height * 0.4, + # Delta - how much ball moves in each tick + dBallX: 4, + dBallY: 4, +} + +update : Model, Event -> Model +update = \model, event -> + when event is + Resize size -> + { model & width: size.width, height: size.height } + + KeyDown Left -> + { model & paddleX: model.paddleX - paddleSpeed } + + KeyDown Right -> + { model & paddleX: model.paddleX + paddleSpeed } + + Tick _ -> + tick model + + _ -> + model + +tick : Model -> Model +tick = \model -> + model + |> moveBall + +moveBall : Model -> Model +moveBall = \model -> + ballX = model.ballX + model.dBallX + ballY = model.ballY + model.dBallY + + paddleTop = model.height - blockHeight - (paddleHeight * 2) + paddleLeft = model.paddleX + paddleRight = paddleLeft + (model.width * paddleWidth) + + # If its y used to be less than the paddle, and now it's greater than or equal, + # then this is the frame where the ball collided with it. + crossingPaddle = model.ballY < paddleTop && ballY >= paddleTop + + # If it collided with the paddle, bounce off. + directionChange = + if crossingPaddle && (ballX >= paddleLeft && ballX <= paddleRight) then + -1f32 + else + 1f32 + + dBallX = model.dBallX * directionChange + dBallY = model.dBallY * directionChange + + { model & ballX, ballY, dBallX, dBallY } + +render : Model -> List Elem +render = \model -> + + blocks = List.map + (List.range 0 numBlocks) + \index -> + col = + Num.rem index numCols + |> Num.toF32 + + row = + index + // numCols + |> Num.toF32 + + red = col / Num.toF32 numCols + green = row / Num.toF32 numRows + blue = Num.toF32 index / Num.toF32 numBlocks + + color = { r: red * 0.8, g: 0.2 + green * 0.6, b: 0.2 + blue * 0.8, a: 1 } + + { row, col, color } + + blockWidth = model.width / numCols + + rects = + List.joinMap + blocks + \{ row, col, color } -> + left = Num.toF32 col * blockWidth + top = Num.toF32 (row * blockHeight) + border = blockBorder * blockWidth + + outer = Rect { + left, + top, + width: blockWidth, + height: blockHeight, + color: { r: color.r * 0.8, g: color.g * 0.8, b: color.b * 0.8, a: 1 }, + } + + inner = Rect { + left: left + border, + top: top + border, + width: blockWidth - (border * 2), + height: blockHeight - (border * 2), + color, + } + + [ outer, inner ] + + ball = + color = { r: 0.7, g: 0.3, b: 0.9, a: 1.0 } + width = ballSize + height = ballSize + left = model.ballX + top = model.ballY + + Rect { left, top, width, height, color } + + paddle = + color = { r: 0.8, g: 0.8, b: 0.8, a: 1.0 } + width = model.width * paddleWidth + height = paddleHeight + left = model.paddleX + top = model.height - blockHeight - height + + Rect { left, top, width, height, color } + + List.concat rects [ paddle, ball ] + +program = { init, update, render } diff --git a/examples/breakout/hello.roc b/examples/breakout/hello.roc new file mode 100644 index 0000000000..f1a486f889 --- /dev/null +++ b/examples/breakout/hello.roc @@ -0,0 +1,17 @@ +app "breakout" + packages { pf: "platform" } + imports [ pf.Game.{ Bounds, Elem, Event } ] + provides [ program ] { Model } to pf + +Model : { text : Str } + +init : Bounds -> Model +init = \_ -> { text: "Hello, World!" } + +update : Model, Event -> Model +update = \model, _ -> model + +render : Model -> List Elem +render = \model -> [ Text model.text ] + +program = { init, update, render } diff --git a/examples/breakout/platform/.gitignore b/examples/breakout/platform/.gitignore new file mode 100644 index 0000000000..eb5a316cbd --- /dev/null +++ b/examples/breakout/platform/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/breakout/platform/Action.roc b/examples/breakout/platform/Action.roc new file mode 100644 index 0000000000..ad15ee728b --- /dev/null +++ b/examples/breakout/platform/Action.roc @@ -0,0 +1,20 @@ +interface Action + exposes [ Action, none, update, map ] + imports [] + +Action state : [ None, Update state ] + +none : Action * +none = None + +update : state -> Action state +update = Update + +map : Action a, (a -> b) -> Action b +map = \action, transform -> + when action is + None -> + None + + Update state -> + Update (transform state) diff --git a/examples/breakout/platform/Cargo.lock b/examples/breakout/platform/Cargo.lock new file mode 100644 index 0000000000..8f8005c26d --- /dev/null +++ b/examples/breakout/platform/Cargo.lock @@ -0,0 +1,2835 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61caed9aec6daeee1ea38ccf5fb225e4f96c1eeead1b4a5c267324a63cf02326" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c4da790adcb2ce5e758c064b4f3ec17a30349f9961d3e5e6c9688b052a9e18" +dependencies = [ + "alsa-sys", + "bitflags", + "libc", + "nix 0.20.0", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "approx" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" +dependencies = [ + "num-traits", +] + +[[package]] +name = "approx" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "ash" +version = "0.35.1+1.2.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7fd04def1c9101b5fb488c131022d2d6f87753ef4b1b11b279e2af404fae6b9" +dependencies = [ + "libloading", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bindgen" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", +] + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "bytemuck" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "calloop" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" +dependencies = [ + "log", + "nix 0.22.0", +] + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom 5.1.2", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cgmath" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" +dependencies = [ + "approx 0.4.0", + "num-traits", +] + +[[package]] +name = "clang-sys" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi", +] + +[[package]] +name = "cocoa" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + +[[package]] +name = "combine" +version = "4.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "confy" +version = "0.4.0" +source = "git+https://github.com/rust-cli/confy#664992aecd97b4af0eda8d9d2825885662e1c6b4" +dependencies = [ + "directories-next", + "serde", + "serde_yaml", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "copypasta" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys 0.8.3", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation 0.9.2", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + +[[package]] +name = "coreaudio-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" +dependencies = [ + "bitflags", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7e3347be6a09b46aba228d6608386739fb70beff4f61e07422da87b0bb31fa" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f45f0a21f617cd2c788889ef710b63f075c949259593ea09c826f1e47a2418" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.3", + "coreaudio-rs", + "jni", + "js-sys", + "lazy_static", + "libc", + "mach", + "ndk 0.3.0", + "ndk-glue 0.3.0", + "nix 0.20.0", + "oboe", + "parking_lot", + "stdweb", + "thiserror", + "web-sys", + "winapi", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "d3d12" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" +dependencies = [ + "bitflags", + "libloading", + "winapi", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core 0.13.1", + "darling_macro 0.13.1", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core 0.10.2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core 0.13.1", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + +[[package]] +name = "futures" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" + +[[package]] +name = "futures-executor" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" + +[[package]] +name = "futures-macro" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" + +[[package]] +name = "futures-task" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" + +[[package]] +name = "futures-util" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "glow" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glyph_brush" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21932fbf719272848eec4583740d978203c6e7da4c4e203358f5b95946c97409" +dependencies = [ + "glyph_brush_draw_cache", + "glyph_brush_layout", + "log", + "ordered-float", + "rustc-hash", + "twox-hash", +] + +[[package]] +name = "glyph_brush_draw_cache" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6010675390f6889e09a21e2c8b575b3ee25667ea8237a8d59423f73cb8c28610" +dependencies = [ + "ab_glyph", + "crossbeam-channel", + "crossbeam-deque", + "linked-hash-map", + "rayon", + "rustc-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc32c2334f00ca5ac3695c5009ae35da21da8c62d255b5b96d56e2597a637a38" +dependencies = [ + "ab_glyph", + "approx 0.5.0", + "xi-unicode", +] + +[[package]] +name = "gpu-alloc" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" +dependencies = [ + "bitflags", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" +dependencies = [ + "bitflags", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "host" +version = "0.1.0" +dependencies = [ + "arrayvec", + "bytemuck", + "cgmath", + "colored", + "confy", + "copypasta", + "env_logger", + "fs_extra", + "futures", + "glyph_brush", + "libc", + "log", + "nonempty", + "page_size", + "palette", + "pest", + "pest_derive", + "roc_std", + "rodio", + "serde", + "snafu", + "threadpool", + "wgpu", + "wgpu_glyph", + "winit", +] + +[[package]] +name = "hound" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inplace_it" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[package]] +name = "libc" +version = "0.2.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memmap2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.23.1" +source = "git+https://github.com/gfx-rs/metal-rs?rev=44af5cc#44af5cca340617d42d701264f9bf71d1f3e68096" +dependencies = [ + "bitflags", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minimp3" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372" +dependencies = [ + "minimp3-sys", + "slice-deque", + "thiserror", +] + +[[package]] +name = "minimp3-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" +dependencies = [ + "cc", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "naga" +version = "0.8.0" +source = "git+https://github.com/gfx-rs/naga?rev=8e2e39e#8e2e39e4d8fa5bbb657c3b170b4f6607d703e284" +dependencies = [ + "bit-set", + "bitflags", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" +dependencies = [ + "jni-sys", + "ndk-sys 0.2.2", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.2.2", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.3.0", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-glue" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.3.0", + "ndk-macro 0.2.0", + "ndk-sys 0.2.2", +] + +[[package]] +name = "ndk-glue" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc291b8de2095cba8dab7cf381bf582ff4c17a09acf854c32e46545b08085d28" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.5.0", + "ndk-macro 0.3.0", + "ndk-sys 0.2.2", +] + +[[package]] +name = "ndk-glue" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c0d14b0858eb9962a5dac30b809b19f19da7e4547d64af2b0bb051d2e55d79" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.6.0", + "ndk-macro 0.3.0", + "ndk-sys 0.3.0", +] + +[[package]] +name = "ndk-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" +dependencies = [ + "darling 0.10.2", + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" +dependencies = [ + "darling 0.13.1", + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] +name = "nix" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" +dependencies = [ + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "oboe" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2463c8f2e19b4e0d0710a21f8e4011501ff28db1c95d7a5482a553b2100502d2" +dependencies = [ + "jni", + "ndk 0.6.0", + "ndk-glue 0.6.0", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd" +dependencies = [ + "cc", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef05f2882a8b3e7acc10c153ade2631f7bfc8ce00d2bf3fb8f4e9d2ae6ea5c3" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "page_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "palette" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9735f7e1e51a3f740bacd5dc2724b61a7806f23597a8736e679f38ee3435d18" +dependencies = [ + "approx 0.5.0", + "num-traits", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7799c3053ea8a6d8a1193c7ba42f534e7863cf52e378a7f90406f4a645d33bad" +dependencies = [ + "find-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "phf" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ac8b67553a7ca9457ce0e526948cad581819238f4a9d1ea74545851fa24f37" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43f3220d96e0080cc9ea234978ccd80d904eafb17be31bb0f76daaea6493082" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b706f5936eb50ed880ae3009395b43ed19db5bff2ebd459c95e7bf013a89ab86" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68318426de33640f02be62b4ae8eb1261be2efbc337b60c54d845bf4484e0d9" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "profiling" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9145ac0af1d93c638c98c40cf7d25665f427b2a44ad0a99b1dccf3e2f25bb987" + +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "range-alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" + +[[package]] +name = "raw-window-handle" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7" +dependencies = [ + "cty", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "renderdoc-sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" + +[[package]] +name = "roc_std" +version = "0.1.0" +dependencies = [ + "static_assertions 0.1.1", +] + +[[package]] +name = "rodio" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d98f5e557b61525057e2bc142c8cd7f0e70d75dc32852309bec440e6e046bf9" +dependencies = [ + "claxon", + "cpal", + "hound", + "lewton", + "minimp3", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dcde03d87d4c973c04be249e7d8f0b35db1c848c487bd43032808e59dd8328d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "siphasher" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "slice-deque" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" +dependencies = [ + "libc", + "mach", + "winapi", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "smithay-client-toolkit" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a" +dependencies = [ + "bitflags", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.22.0", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + +[[package]] +name = "smithay-clipboard" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610b551bd25378bfd2b8e7a0fcbd83d427e8f2f6a40c47ae0f70688e9949dd55" +dependencies = [ + "smithay-client-toolkit", + "wayland-client", +] + +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags", + "num-traits", +] + +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stdweb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "ttf-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccbe8381883510b6a2d8f1e32905bddd178c11caef8083086d0c0c9ab0ac281" + +[[package]] +name = "twox-hash" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" +dependencies = [ + "cfg-if 1.0.0", + "rand", + "static_assertions 1.1.0", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "wayland-client" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.22.0", + "scoped-tls", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +dependencies = [ + "nix 0.22.0", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-cursor" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +dependencies = [ + "nix 0.22.0", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +dependencies = [ + "dlib", + "lazy_static", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "js-sys", + "log", + "naga", + "parking_lot", + "raw-window-handle", + "smallvec", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "bitflags", + "cfg_aliases", + "codespan-reporting", + "copyless", + "fxhash", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "arrayvec", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "js-sys", + "khronos-egl", + "libloading", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=0545e36#0545e36aa82709cca78c14eb3813f10eea7a9275" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wgpu_glyph" +version = "0.16.0" +source = "git+https://github.com/Anton-4/wgpu_glyph?rev=257d109#257d1098cbafa3c8a0a2465937b06fc730fc6ffb" +dependencies = [ + "bytemuck", + "glyph_brush", + "log", + "wgpu", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winit" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b43cc931d58b99461188607efd7acb2a093e65fc621f54cad78517a6063e73a" +dependencies = [ + "bitflags", + "cocoa", + "core-foundation 0.9.2", + "core-graphics 0.22.3", + "core-video-sys", + "dispatch", + "instant", + "lazy_static", + "libc", + "log", + "mio", + "ndk 0.5.0", + "ndk-glue 0.5.0", + "ndk-sys 0.2.2", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "smithay-client-toolkit", + "wasm-bindgen", + "wayland-client", + "wayland-protocols", + "web-sys", + "winapi", + "x11-dl", +] + +[[package]] +name = "x11-clipboard" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" +dependencies = [ + "xcb", +] + +[[package]] +name = "x11-dl" +version = "2.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + +[[package]] +name = "xcb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" +dependencies = [ + "libc", + "log", + "quick-xml", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom 7.1.0", +] + +[[package]] +name = "xi-unicode" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/examples/breakout/platform/Cargo.toml b/examples/breakout/platform/Cargo.toml new file mode 100644 index 0000000000..47281be89d --- /dev/null +++ b/examples/breakout/platform/Cargo.toml @@ -0,0 +1,75 @@ +[package] +name = "host" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" + +# Needed to be able to run on non-Windows systems for some reason. Without this, cargo panics with: +# +# error: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should. +resolver = "2" + +[lib] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" + +[dependencies] +roc_std = { path = "../../../roc_std" } +libc = "0.2" +arrayvec = "0.7.2" +page_size = "0.4.2" +# when changing winit version, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time. +winit = "0.26.1" +wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" } +wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" } +glyph_brush = "0.7.2" +log = "0.4.14" +env_logger = "0.9.0" +futures = "0.3.17" +cgmath = "0.18.0" +snafu = { version = "0.6.10", features = ["backtraces"] } +colored = "2.0.0" +pest = "2.1.3" +pest_derive = "2.1.0" +copypasta = "0.7.1" +palette = "0.6.0" +confy = { git = 'https://github.com/rust-cli/confy', features = [ + "yaml_conf" +], default-features = false } +serde = { version = "1.0.130", features = ["derive"] } +nonempty = "0.7.0" +fs_extra = "1.2.0" +rodio = { version = "0.14.0", optional = true } # to play sounds +threadpool = "1.8.1" + +[package.metadata.cargo-udeps.ignore] +# confy is currently unused but should not be removed +normal = ["confy"] +#development = [] +#build = [] + +[features] +default = [] +with_sound = ["rodio"] + +[dependencies.bytemuck] +version = "1.7.2" +features = ["derive"] + +[workspace] + +# Optimizations based on https://deterministic.space/high-performance-rust.html +[profile.release] +lto = "fat" +codegen-units = 1 + +# debug = true # enable when profiling +[profile.bench] +lto = "thin" +codegen-units = 1 diff --git a/examples/breakout/platform/Elem.roc b/examples/breakout/platform/Elem.roc new file mode 100644 index 0000000000..519d007b99 --- /dev/null +++ b/examples/breakout/platform/Elem.roc @@ -0,0 +1,193 @@ +interface Elem + exposes [ Elem, PressEvent, row, col, text, button, none, translate, list ] + imports [ Action.{ Action } ] + +Elem state : + # PERFORMANCE NOTE: + # If there are 8 or fewer tags here, then on a 64-bit system, the tag can be stored + # in the pointer - for massive memory savings. Try extremely hard to always limit the number + # of tags in this union to 8 or fewer! + [ + Button (ButtonConfig state) (Elem state), + Text Str, + Col (List (Elem state)), + Row (List (Elem state)), + Lazy (Result { state, elem : Elem state } [ NotCached ] -> { state, elem : Elem state }), + # TODO FIXME: using this definition of Lazy causes a stack overflow in the compiler! + # Lazy (Result (Cached state) [ NotCached ] -> Cached state), + None, + ] + +## Used internally in the type definition of Lazy +Cached state : { state, elem : Elem state } + +ButtonConfig state : { onPress : state, PressEvent -> Action state } + +PressEvent : { button : [ Touch, Mouse [ Left, Right, Middle ] ] } + +text : Str -> Elem * +text = \str -> + Text str + +button : { onPress : state, PressEvent -> Action state }, Elem state -> Elem state +button = \config, label -> + Button config label + +row : List (Elem state) -> Elem state +row = \children -> + Row children + +col : List (Elem state) -> Elem state +col = \children -> + Col children + +lazy : state, (state -> Elem state) -> Elem state +lazy = \state, render -> + # This function gets called by the host during rendering. It will + # receive the cached state and element (wrapped in Ok) if we've + # ever rendered this before, and Err otherwise. + Lazy + \result -> + when result is + Ok cached if cached.state == state -> + # If we have a cached value, and the new state is the + # same as the cached one, then we can return exactly + # what we had cached. + cached + + _ -> + # Either the state changed or else we didn't have a + # cached value to use. Either way, we need to render + # with the new state and store that for future use. + { state, elem: render state } + +none : Elem * +none = None# I've often wanted this in elm/html. Usually end up resorting to (Html.text "") - this seems nicer. +## Change an element's state type. +## +## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed. +## State : { photo : Photo } +## +## render : State -> Elem State +## render = \state -> +## child : Elem State +## child = +## Photo.render state.photo +## |> Elem.translate .photo &photo +## +## col {} [ child, otherElems ] +## +translate = \child, toChild, toParent -> + when child is + Text str -> + Text str + + Col elems -> + Col (List.map elems \elem -> translate elem toChild toParent) + + Row elems -> + Row (List.map elems \elem -> translate elem toChild toParent) + + Button config label -> + onPress = \parentState, event -> + toChild parentState + |> config.onPress event + |> Action.map \c -> toParent parentState c + + Button { onPress } (translate label toChild toParent) + + Lazy renderChild -> + Lazy + \parentState -> + { elem, state } = renderChild (toChild parentState) + + { + elem: translate toChild toParent newChild, + state: toParent parentState state, + } + + None -> + None + +## Render a list of elements, using [Elem.translate] on each of them. +## +## Convenient when you have a [List] in your state and want to make +## a [List] of child elements out of it. +## +## TODO: indent the following once https://github.com/rtfeldman/roc/issues/2585 is fixed. +## State : { photos : List Photo } +## +## render : State -> Elem State +## render = \state -> +## children : List (Elem State) +## children = +## Elem.list Photo.render state .photos &photos +## +## col {} children +## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed +list : (child -> Elem child), parent, (parent -> List child), (parent, List child -> parent) -> List (Elem parent) +list = \renderChild, parent, toChildren, toParent -> + List.mapWithIndex + (toChildren parent) + \index, child -> + toChild = \par -> List.get (toChildren par) index + + newChild = translateOrDrop + child + toChild + \par, ch -> + toChildren par + |> List.set ch index + |> toParent + + renderChild newChild + +## Internal helper function for Elem.list +## +## Tries to translate a child to a parent, but +## if the child has been removed from the parent, +## drops it. +## +## TODO: format as multiline type annotation once https://github.com/rtfeldman/roc/issues/2586 is fixed +translateOrDrop : Elem child, (parent -> Result child *), (parent, child -> parent) -> Elem parent +translateOrDrop = \child, toChild, toParent -> + when child is + Text str -> + Text str + + Col elems -> + Col (List.map elems \elem -> translateOrDrop elem toChild toParent) + + Row elems -> + Row (List.map elems \elem -> translateOrDrop elem toChild toParent) + + Button config label -> + onPress = \parentState, event -> + when toChild parentState is + Ok newChild -> + newChild + |> config.onPress event + |> Action.map \c -> toParent parentState c + + Err _ -> + # The child was removed from the list before this onPress handler resolved. + # (For example, by a previous event handler that fired simultaneously.) + Action.none + + Button { onPress } (translateOrDrop label toChild toParent) + + Lazy childState renderChild -> + Lazy + (toParent childState) + \parentState -> + when toChild parentState is + Ok newChild -> + renderChild newChild + |> translateOrDrop toChild toParent + + Err _ -> + None + + # I don't think this should ever happen in practice. + None -> + None diff --git a/examples/breakout/platform/Game.roc b/examples/breakout/platform/Game.roc new file mode 100644 index 0000000000..bb270cb299 --- /dev/null +++ b/examples/breakout/platform/Game.roc @@ -0,0 +1,13 @@ +interface Game + exposes [ Bounds, Elem, Event ] + imports [] + +Rgba : { r : F32, g : F32, b : F32, a : F32 } + +Bounds : { height : F32, width : F32 } + +Elem : [ Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text Str ] + +KeyCode : [ Left, Right, Other ] + +Event : [ Resize { width : F32, height : F32 }, KeyDown KeyCode, KeyUp KeyCode, Tick U128 ] diff --git a/examples/breakout/platform/Package-Config.roc b/examples/breakout/platform/Package-Config.roc new file mode 100644 index 0000000000..87004e3735 --- /dev/null +++ b/examples/breakout/platform/Package-Config.roc @@ -0,0 +1,14 @@ +platform "gui" + requires { Model } { program : _ } + exposes [ Game ] + packages {} + imports [ Game.{ Bounds, Elem, Event } ] + provides [ programForHost ] + +# TODO allow changing the window title - maybe via a Task, since that shouldn't happen all the time +programForHost : { + init : (Bounds -> Model) as Init, + update : (Model, Event -> Model) as Update, + render : (Model -> List Elem) as Render, +} +programForHost = program diff --git a/examples/breakout/platform/build.rs b/examples/breakout/platform/build.rs new file mode 100644 index 0000000000..73159e387c --- /dev/null +++ b/examples/breakout/platform/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-lib=dylib=app"); + println!("cargo:rustc-link-search=."); +} diff --git a/examples/breakout/platform/host.c b/examples/breakout/platform/host.c new file mode 100644 index 0000000000..b9214bcf33 --- /dev/null +++ b/examples/breakout/platform/host.c @@ -0,0 +1,3 @@ +extern int rust_main(); + +int main() { return rust_main(); } \ No newline at end of file diff --git a/examples/breakout/platform/src/graphics/colors.rs b/examples/breakout/platform/src/graphics/colors.rs new file mode 100644 index 0000000000..71be9c8fa5 --- /dev/null +++ b/examples/breakout/platform/src/graphics/colors.rs @@ -0,0 +1,50 @@ +use cgmath::Vector4; +use palette::{FromColor, Hsv, Srgb}; + +/// This order is optimized for what Roc will send +#[repr(C)] +#[derive(Copy, Clone, Debug, PartialEq, Default)] +pub struct Rgba { + a: f32, + b: f32, + g: f32, + r: f32, +} + +impl Rgba { + pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0); + + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Self { r, g, b, a } + } + + pub const fn to_array(self) -> [f32; 4] { + [self.r, self.g, self.b, self.a] + } + + pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> Self { + Self::from_hsba(hue, saturation, brightness, 1.0) + } + + pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> Self { + let rgb = Srgb::from_color(Hsv::new( + hue as f32, + (saturation as f32) / 100.0, + (brightness as f32) / 100.0, + )); + + Self::new(rgb.red, rgb.green, rgb.blue, alpha) + } +} + +impl From for [f32; 4] { + fn from(rgba: Rgba) -> Self { + rgba.to_array() + } +} + +impl From for Vector4 { + fn from(rgba: Rgba) -> Self { + Vector4::new(rgba.r, rgba.b, rgba.g, rgba.a) + } +} diff --git a/examples/breakout/platform/src/graphics/lowlevel/buffer.rs b/examples/breakout/platform/src/graphics/lowlevel/buffer.rs new file mode 100644 index 0000000000..a5a7f54161 --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/buffer.rs @@ -0,0 +1,96 @@ +// Contains parts of https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +// Contains parts of https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl +// By Héctor Ramón, Iced contributors Licensed under the MIT license. +// The license is included in the LEGAL_DETAILS file in the root directory of this distribution. + +// Thank you Héctor Ramón and Iced contributors! + +use std::mem; + +use super::{quad::Quad, vertex::Vertex}; +use crate::graphics::primitives::rect::RectElt; +use wgpu::util::DeviceExt; + +pub struct RectBuffers { + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub quad_buffer: wgpu::Buffer, +} + +pub const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTS: [Vertex; 4] = [ + Vertex { + _position: [0.0, 0.0], + }, + Vertex { + _position: [1.0, 0.0], + }, + Vertex { + _position: [1.0, 1.0], + }, + Vertex { + _position: [0.0, 1.0], + }, +]; + +pub const MAX_QUADS: usize = 1_000; + +pub fn create_rect_buffers( + gpu_device: &wgpu::Device, + cmd_encoder: &mut wgpu::CommandEncoder, + rects: &[RectElt], +) -> RectBuffers { + let vertex_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&QUAD_VERTS), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&QUAD_INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + + let quad_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: mem::size_of::() as u64 * MAX_QUADS as u64, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let quads: Vec = rects.iter().map(|rect| to_quad(rect)).collect(); + + let buffer_size = (quads.len() as u64) * Quad::SIZE; + + let staging_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&quads), + usage: wgpu::BufferUsages::COPY_SRC, + }); + + cmd_encoder.copy_buffer_to_buffer(&staging_buffer, 0, &quad_buffer, 0, buffer_size); + + RectBuffers { + vertex_buffer, + index_buffer, + quad_buffer, + } +} + +pub fn to_quad(rect_elt: &RectElt) -> Quad { + Quad { + pos: rect_elt.rect.pos.into(), + width: rect_elt.rect.width, + height: rect_elt.rect.height, + color: (rect_elt.color.to_array()), + border_color: rect_elt.border_color.into(), + border_width: rect_elt.border_width, + } +} diff --git a/examples/breakout/platform/src/graphics/lowlevel/mod.rs b/examples/breakout/platform/src/graphics/lowlevel/mod.rs new file mode 100644 index 0000000000..0add45385d --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/mod.rs @@ -0,0 +1,5 @@ +pub mod buffer; +pub mod ortho; +pub mod pipelines; +pub mod vertex; +pub mod quad; diff --git a/examples/breakout/platform/src/graphics/lowlevel/ortho.rs b/examples/breakout/platform/src/graphics/lowlevel/ortho.rs new file mode 100644 index 0000000000..2f4577871a --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/ortho.rs @@ -0,0 +1,118 @@ +use cgmath::{Matrix4, Ortho}; +use wgpu::util::DeviceExt; +use wgpu::{ + BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, + ShaderStages, +}; + +// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu + +#[repr(C)] +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct Uniforms { + // We can't use cgmath with bytemuck directly so we'll have + // to convert the Matrix4 into a 4x4 f32 array + ortho: [[f32; 4]; 4], +} + +impl Uniforms { + fn new(w: u32, h: u32) -> Self { + let ortho: Matrix4 = Ortho:: { + left: 0.0, + right: w as f32, + bottom: h as f32, + top: 0.0, + near: -1.0, + far: 1.0, + } + .into(); + Self { + ortho: ortho.into(), + } + } +} + +// update orthographic buffer according to new window size +pub fn update_ortho_buffer( + inner_width: u32, + inner_height: u32, + gpu_device: &wgpu::Device, + ortho_buffer: &Buffer, + cmd_queue: &wgpu::Queue, +) { + let new_uniforms = Uniforms::new(inner_width, inner_height); + + let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Ortho uniform buffer"), + contents: bytemuck::cast_slice(&[new_uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC, + }); + + // get a command encoder for the current frame + let mut encoder = gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Resize"), + }); + + // overwrite the new buffer over the old one + encoder.copy_buffer_to_buffer( + &new_ortho_buffer, + 0, + ortho_buffer, + 0, + (std::mem::size_of::() * vec![new_uniforms].as_slice().len()) + as wgpu::BufferAddress, + ); + + cmd_queue.submit(Some(encoder.finish())); +} + +#[derive(Debug)] +pub struct OrthoResources { + pub buffer: Buffer, + pub bind_group_layout: BindGroupLayout, + pub bind_group: BindGroup, +} + +pub fn init_ortho( + inner_width: u32, + inner_height: u32, + gpu_device: &wgpu::Device, +) -> OrthoResources { + let uniforms = Uniforms::new(inner_width, inner_height); + + let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Ortho uniform buffer"), + contents: bytemuck::cast_slice(&[uniforms]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + // bind groups consist of extra resources that are provided to the shaders + let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + label: Some("Ortho bind group layout"), + }); + + let ortho_bind_group = gpu_device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &ortho_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: ortho_buffer.as_entire_binding(), + }], + label: Some("Ortho bind group"), + }); + + OrthoResources { + buffer: ortho_buffer, + bind_group_layout: ortho_bind_group_layout, + bind_group: ortho_bind_group, + } +} diff --git a/examples/breakout/platform/src/graphics/lowlevel/pipelines.rs b/examples/breakout/platform/src/graphics/lowlevel/pipelines.rs new file mode 100644 index 0000000000..a0dc7908ec --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/pipelines.rs @@ -0,0 +1,72 @@ +use super::ortho::{init_ortho, OrthoResources}; +use super::quad::Quad; +use super::vertex::Vertex; +use std::borrow::Cow; + +pub struct RectResources { + pub pipeline: wgpu::RenderPipeline, + pub ortho: OrthoResources, +} + +pub fn make_rect_pipeline( + gpu_device: &wgpu::Device, + surface_config: &wgpu::SurfaceConfiguration, +) -> RectResources { + let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device); + + let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&ortho.bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = create_render_pipeline( + gpu_device, + &pipeline_layout, + surface_config.format, + &wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/quad.wgsl"))), + }, + ); + + RectResources { pipeline, ortho } +} + +pub fn create_render_pipeline( + device: &wgpu::Device, + layout: &wgpu::PipelineLayout, + color_format: wgpu::TextureFormat, + shader_module_desc: &wgpu::ShaderModuleDescriptor, +) -> wgpu::RenderPipeline { + let shader = device.create_shader_module(shader_module_desc); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render pipeline"), + layout: Some(layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::DESC, Quad::DESC], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[wgpu::ColorTargetState { + format: color_format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + operation: wgpu::BlendOperation::Add, + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + }, + alpha: wgpu::BlendComponent::REPLACE, + }), + write_mask: wgpu::ColorWrites::ALL, + }], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }) +} diff --git a/examples/breakout/platform/src/graphics/lowlevel/quad.rs b/examples/breakout/platform/src/graphics/lowlevel/quad.rs new file mode 100644 index 0000000000..9c1fd85ae6 --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/quad.rs @@ -0,0 +1,31 @@ + + +/// A polygon with 4 corners +#[derive(Copy, Clone)] +pub struct Quad { + pub pos: [f32; 2], + pub width: f32, + pub height: f32, + pub color: [f32; 4], + pub border_color: [f32; 4], + pub border_width: f32, +} + +unsafe impl bytemuck::Pod for Quad {} +unsafe impl bytemuck::Zeroable for Quad {} + +impl Quad { + pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + 1 => Float32x2, + 2 => Float32, + 3 => Float32, + 4 => Float32x4, + 5 => Float32x4, + 6 => Float32, + ), + }; +} \ No newline at end of file diff --git a/examples/breakout/platform/src/graphics/lowlevel/vertex.rs b/examples/breakout/platform/src/graphics/lowlevel/vertex.rs new file mode 100644 index 0000000000..aa45bb7fb7 --- /dev/null +++ b/examples/breakout/platform/src/graphics/lowlevel/vertex.rs @@ -0,0 +1,35 @@ +// Inspired by https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +// Inspired by https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl +// By Héctor Ramón, Iced contributors Licensed under the MIT license. +// The license is included in the LEGAL_DETAILS file in the root directory of this distribution. + +// Thank you Héctor Ramón and Iced contributors! +use bytemuck::{Pod, Zeroable}; + + +#[repr(C)] +#[derive(Copy, Clone, Zeroable, Pod)] +pub struct Vertex { + pub _position: [f32; 2], +} + +impl Vertex { + pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; + pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + // position + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + }, + ], + }; +} diff --git a/examples/breakout/platform/src/graphics/mod.rs b/examples/breakout/platform/src/graphics/mod.rs new file mode 100644 index 0000000000..0eb7fcd6da --- /dev/null +++ b/examples/breakout/platform/src/graphics/mod.rs @@ -0,0 +1,4 @@ +pub mod colors; +pub mod lowlevel; +pub mod primitives; +pub mod style; diff --git a/examples/breakout/platform/src/graphics/primitives/mod.rs b/examples/breakout/platform/src/graphics/primitives/mod.rs new file mode 100644 index 0000000000..a9adb18862 --- /dev/null +++ b/examples/breakout/platform/src/graphics/primitives/mod.rs @@ -0,0 +1,2 @@ +pub mod rect; +pub mod text; diff --git a/examples/breakout/platform/src/graphics/primitives/rect.rs b/examples/breakout/platform/src/graphics/primitives/rect.rs new file mode 100644 index 0000000000..8183fc6f7a --- /dev/null +++ b/examples/breakout/platform/src/graphics/primitives/rect.rs @@ -0,0 +1,27 @@ +use crate::graphics::colors::Rgba; +use cgmath::Vector2; + +#[derive(Debug, Copy, Clone)] +pub struct RectElt { + pub rect: Rect, + pub color: Rgba, + pub border_width: f32, + pub border_color: Rgba, +} + +/// These fields are ordered this way because in Roc, the corresponding stuct is: +/// +/// { top : F32, left : F32, width : F32, height : F32 } +/// +/// alphabetically, that's { height, left, top, width } - which works out to the same as: +/// +/// struct Rect { height: f32, pos: Vector2, width: f32 } +/// +/// ...because Vector2 is a repr(C) struct of { x: f32, y: f32 } +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct Rect { + pub height: f32, + pub pos: Vector2, + pub width: f32, +} diff --git a/examples/breakout/platform/src/graphics/primitives/text.rs b/examples/breakout/platform/src/graphics/primitives/text.rs new file mode 100644 index 0000000000..4ecaa28d9b --- /dev/null +++ b/examples/breakout/platform/src/graphics/primitives/text.rs @@ -0,0 +1,134 @@ +// Adapted from https://github.com/sotrh/learn-wgpu +// by Benjamin Hansen - license information can be found in the COPYRIGHT +// file in the root directory of this distribution. +// +// Thank you, Benjamin! + +use crate::graphics::colors::Rgba; +use crate::graphics::style::DEFAULT_FONT_SIZE; +use ab_glyph::{FontArc, InvalidFont}; +use cgmath::Vector2; +use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder}; + +#[derive(Debug)] +pub struct Text<'a> { + pub position: Vector2, + pub area_bounds: Vector2, + pub color: Rgba, + pub text: &'a str, + pub size: f32, + pub visible: bool, + pub centered: bool, +} + +impl<'a> Default for Text<'a> { + fn default() -> Self { + Self { + position: (0.0, 0.0).into(), + area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(), + color: Rgba::WHITE, + text: "", + size: DEFAULT_FONT_SIZE, + visible: true, + centered: false, + } + } +} + +// pub fn layout_from_text(text: &Text) -> wgpu_glyph::Layout { +// wgpu_glyph::Layout::default().h_align(if text.centered { +// wgpu_glyph::HorizontalAlign::Center +// } else { +// wgpu_glyph::HorizontalAlign::Left +// }) +// } + +// fn section_from_text<'a>( +// text: &'a Text, +// layout: wgpu_glyph::Layout, +// ) -> wgpu_glyph::Section<'a> { +// Section { +// screen_position: text.position.into(), +// bounds: text.area_bounds.into(), +// layout, +// ..Section::default() +// } +// .add_text( +// wgpu_glyph::Text::new(text.text) +// .with_color(text.color) +// .with_scale(text.size), +// ) +// } + +// pub fn owned_section_from_text(text: &Text) -> OwnedSection { +// let layout = layout_from_text(text); + +// OwnedSection { +// screen_position: text.position.into(), +// bounds: text.area_bounds.into(), +// layout, +// ..OwnedSection::default() +// } +// .add_text( +// glyph_brush::OwnedText::new(text.text) +// .with_color(Vector4::from(text.color)) +// .with_scale(text.size), +// ) +// } + +// pub fn owned_section_from_glyph_texts( +// text: Vec, +// screen_position: (f32, f32), +// area_bounds: (f32, f32), +// layout: wgpu_glyph::Layout, +// ) -> glyph_brush::OwnedSection { +// glyph_brush::OwnedSection { +// screen_position, +// bounds: area_bounds, +// layout, +// text, +// } +// } + +// pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) { +// let layout = layout_from_text(text); + +// let section = section_from_text(text, layout); + +// glyph_brush.queue(section.clone()); +// } + +// fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect { +// let position = glyph.glyph.position; +// let px_scale = glyph.glyph.scale; +// let width = glyph_width(&glyph.glyph); +// let height = px_scale.y; +// let top_y = glyph_top_y(&glyph.glyph); + +// Rect { +// pos: [position.x, top_y].into(), +// width, +// height, +// } +// } + +// pub fn glyph_top_y(glyph: &Glyph) -> f32 { +// let height = glyph.scale.y; + +// glyph.position.y - height * 0.75 +// } + +// pub fn glyph_width(glyph: &Glyph) -> f32 { +// glyph.scale.x * 0.4765 +// } + +pub fn build_glyph_brush( + gpu_device: &wgpu::Device, + render_format: wgpu::TextureFormat, +) -> Result, InvalidFont> { + let inconsolata = FontArc::try_from_slice(include_bytes!( + "../../../../../../editor/Inconsolata-Regular.ttf" + ))?; + + Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format)) +} diff --git a/examples/breakout/platform/src/graphics/shaders/quad.wgsl b/examples/breakout/platform/src/graphics/shaders/quad.wgsl new file mode 100644 index 0000000000..a561e2fc24 --- /dev/null +++ b/examples/breakout/platform/src/graphics/shaders/quad.wgsl @@ -0,0 +1,60 @@ + + +struct Globals { + ortho: mat4x4; +}; + +@group(0) +@binding(0) +var globals: Globals; + +struct VertexInput { + @location(0) position: vec2; +}; + +struct Quad { + @location(1) pos: vec2; // can't use the name "position" twice for compatibility with metal on MacOS + @location(2) width: f32; + @location(3) height: f32; + @location(4) color: vec4; + @location(5) border_color: vec4; + @location(6) border_width: f32; +}; + +struct VertexOutput { + @builtin(position) position: vec4; + @location(0) color: vec4; + @location(1) border_color: vec4; + @location(2) border_width: f32; +}; + +@stage(vertex) +fn vs_main( + input: VertexInput, + quad: Quad +) -> VertexOutput { + + var transform: mat4x4 = mat4x4( + vec4(quad.width, 0.0, 0.0, 0.0), + vec4(0.0, quad.height, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(quad.pos, 0.0, 1.0) + ); + + var out: VertexOutput; + + out.position = globals.ortho * transform * vec4(input.position, 0.0, 1.0);; + out.color = quad.color; + out.border_color = quad.border_color; + out.border_width = quad.border_width; + + return out; +} + + +@stage(fragment) +fn fs_main( + input: VertexOutput +) -> @location(0) vec4 { + return input.color; +} diff --git a/examples/breakout/platform/src/graphics/style.rs b/examples/breakout/platform/src/graphics/style.rs new file mode 100644 index 0000000000..11e609075b --- /dev/null +++ b/examples/breakout/platform/src/graphics/style.rs @@ -0,0 +1 @@ +pub const DEFAULT_FONT_SIZE: f32 = 30.0; diff --git a/examples/breakout/platform/src/gui.rs b/examples/breakout/platform/src/gui.rs new file mode 100644 index 0000000000..ef0412ea25 --- /dev/null +++ b/examples/breakout/platform/src/gui.rs @@ -0,0 +1,513 @@ +use crate::{ + graphics::{ + colors::Rgba, + lowlevel::buffer::create_rect_buffers, + lowlevel::{buffer::MAX_QUADS, ortho::update_ortho_buffer}, + lowlevel::{buffer::QUAD_INDICES, pipelines}, + primitives::{ + rect::{Rect, RectElt}, + text::build_glyph_brush, + }, + }, + roc::{self, Bounds, RocElem, RocElemTag, RocEvent}, +}; +use cgmath::{Vector2, Vector4}; +use glyph_brush::{GlyphCruncher, OwnedSection}; +use pipelines::RectResources; +use std::{ + error::Error, + time::{Duration, Instant}, +}; +use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView}; +use wgpu_glyph::GlyphBrush; +use winit::{ + dpi::PhysicalSize, + event, + event::{ElementState, Event, ModifiersState, StartCause}, + event_loop::ControlFlow, + platform::run_return::EventLoopExtRunReturn, +}; + +// Inspired by: +// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license +// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license +// +// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/ + +const TIME_BETWEEN_TICKS: Duration = Duration::new(0, 1000 / 60); + +pub fn run_event_loop(title: &str, window_bounds: Bounds) -> Result<(), Box> { + let (mut model, mut elems) = roc::init_and_render(window_bounds); + + // Open window and create a surface + let mut event_loop = winit::event_loop::EventLoop::new(); + + let window = winit::window::WindowBuilder::new() + .with_inner_size(PhysicalSize::new(window_bounds.width, window_bounds.height)) + .with_title(title) + .build(&event_loop) + .unwrap(); + + macro_rules! update_and_rerender { + ($event:expr) => { + // TODO use (model, elems) = ... once we've upgraded rust versions + let pair = roc::update_and_render(model, $event); + + model = pair.0; + elems = pair.1; + + window.request_redraw(); + }; + } + + let instance = wgpu::Instance::new(wgpu::Backends::all()); + let surface = unsafe { instance.create_surface(&window) }; + + // Initialize GPU + let (gpu_device, cmd_queue) = futures::executor::block_on(async { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .expect(r#"Request adapter + If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor + "#); + + adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + }, + None, + ) + .await + .expect("Request device") + }); + + // Create staging belt and a local pool + let mut staging_belt = wgpu::util::StagingBelt::new(1024); + let mut local_pool = futures::executor::LocalPool::new(); + let local_spawner = local_pool.spawner(); + + // Prepare swap chain + let render_format = wgpu::TextureFormat::Bgra8Unorm; + let mut size = window.inner_size(); + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }; + + surface.configure(&gpu_device, &surface_config); + + let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config); + + let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; + let mut keyboard_modifiers = ModifiersState::empty(); + + // Render loop + let app_start_time = Instant::now(); + let mut next_tick = app_start_time + TIME_BETWEEN_TICKS; + + window.request_redraw(); + + event_loop.run_return(|event, _, control_flow| { + match event { + // Close + Event::WindowEvent { + event: event::WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + // Resize + Event::WindowEvent { + event: event::WindowEvent::Resized(new_size), + .. + } => { + size = new_size; + + surface.configure( + &gpu_device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: render_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ); + + update_ortho_buffer( + size.width, + size.height, + &gpu_device, + &rect_resources.ortho.buffer, + &cmd_queue, + ); + + update_and_rerender!(RocEvent::Resize(Bounds { + height: size.height as f32, + width: size.width as f32, + })); + } + // Keyboard input + Event::WindowEvent { + event: + event::WindowEvent::KeyboardInput { + input: + event::KeyboardInput { + virtual_keycode: Some(keycode), + state: input_state, + .. + }, + .. + }, + .. + } => { + let roc_event = match input_state { + ElementState::Pressed => RocEvent::KeyDown(keycode.into()), + ElementState::Released => RocEvent::KeyUp(keycode.into()), + }; + + model = roc::update(model, roc_event); + } + // Modifiers Changed + Event::WindowEvent { + event: event::WindowEvent::ModifiersChanged(modifiers), + .. + } => { + keyboard_modifiers = modifiers; + } + Event::RedrawRequested { .. } => { + // Get a command cmd_encoder for the current frame + let mut cmd_encoder = + gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Redraw"), + }); + + let surface_texture = surface + .get_current_texture() + .expect("Failed to acquire next SwapChainTexture"); + + let view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + for elem in elems.iter() { + let (_bounds, drawable) = to_drawable( + elem, + Bounds { + width: size.width as f32, + height: size.height as f32, + }, + &mut glyph_brush, + ); + + process_drawable( + drawable, + &mut staging_belt, + &mut glyph_brush, + &mut cmd_encoder, + &view, + &gpu_device, + &rect_resources, + wgpu::LoadOp::Load, + Bounds { + width: size.width as f32, + height: size.height as f32, + }, + ); + } + + staging_belt.finish(); + cmd_queue.submit(Some(cmd_encoder.finish())); + surface_texture.present(); + + // Recall unused staging buffers + use futures::task::SpawnExt; + + local_spawner + .spawn(staging_belt.recall()) + .expect("Recall staging belt"); + + local_pool.run_until_stalled(); + } + Event::NewEvents(StartCause::ResumeTimeReached { + requested_resume, .. + }) => { + // Only run this logic if this is the tick we originally requested. + if requested_resume == next_tick { + let now = Instant::now(); + + // Set a new next_tick *before* running update and rerender, + // so their runtime isn't factored into when we want to render next. + next_tick = now + TIME_BETWEEN_TICKS; + + let tick = now.saturating_duration_since(app_start_time); + + update_and_rerender!(RocEvent::Tick(tick)); + + *control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick); + } + } + _ => { + // Keep waiting until the next tick. + *control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick); + } + } + }); + + Ok(()) +} + +fn draw_rects( + all_rects: &[RectElt], + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, +) { + let rect_buffers = create_rect_buffers(gpu_device, cmd_encoder, all_rects); + + let mut render_pass = begin_render_pass(cmd_encoder, texture_view, load_op); + + render_pass.set_pipeline(&rect_resources.pipeline); + render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]); + + render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..)); + render_pass.set_vertex_buffer(1, rect_buffers.quad_buffer.slice(..)); + + render_pass.set_index_buffer( + rect_buffers.index_buffer.slice(..), + wgpu::IndexFormat::Uint16, + ); + + render_pass.draw_indexed(0..QUAD_INDICES.len() as u32, 0, 0..MAX_QUADS as u32); +} + +fn begin_render_pass<'a>( + cmd_encoder: &'a mut CommandEncoder, + texture_view: &'a TextureView, + load_op: LoadOp, +) -> RenderPass<'a> { + cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachment { + view: texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: load_op, + store: true, + }, + }], + depth_stencil_attachment: None, + label: None, + }) +} + +#[derive(Clone, Debug)] +struct Drawable { + pos: Vector2, + bounds: Bounds, + content: DrawableContent, +} + +#[derive(Clone, Debug)] +enum DrawableContent { + /// This stores an actual Section because an earlier step needs to know the bounds of + /// the text, and making a Section is a convenient way to compute those bounds. + Text(OwnedSection, Vector2), + FillRect { + color: Rgba, + border_width: f32, + border_color: Rgba, + }, +} + +fn process_drawable( + drawable: Drawable, + staging_belt: &mut wgpu::util::StagingBelt, + glyph_brush: &mut GlyphBrush<()>, + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, + texture_size: Bounds, +) { + draw( + drawable.bounds, + drawable.content, + drawable.pos, + staging_belt, + glyph_brush, + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + texture_size, + ); +} + +fn draw( + bounds: Bounds, + content: DrawableContent, + pos: Vector2, + staging_belt: &mut wgpu::util::StagingBelt, + glyph_brush: &mut GlyphBrush<()>, + cmd_encoder: &mut CommandEncoder, + texture_view: &TextureView, + gpu_device: &wgpu::Device, + rect_resources: &RectResources, + load_op: LoadOp, + texture_size: Bounds, +) { + use DrawableContent::*; + + match content { + Text(section, offset) => { + glyph_brush.queue(section.with_screen_position(pos + offset).to_borrowed()); + + glyph_brush + .draw_queued( + gpu_device, + staging_belt, + cmd_encoder, + texture_view, + texture_size.width as u32, // TODO why do we make these be u32 and then cast to f32 in orthorgraphic_projection? + texture_size.height as u32, + ) + .expect("Failed to draw text element"); + } + FillRect { + color, + border_width, + border_color, + } => { + // TODO store all these colors and things in FillRect + let rect_elt = RectElt { + rect: Rect { + pos, + width: bounds.width, + height: bounds.height, + }, + color, + border_width, + border_color, + }; + + // TODO inline draw_rects into here! + draw_rects( + &[rect_elt], + cmd_encoder, + texture_view, + gpu_device, + rect_resources, + load_op, + ); + } + } +} + +/// focused_elem is the currently-focused element (or NULL if nothing has the focus) +fn to_drawable( + elem: &RocElem, + bounds: Bounds, + glyph_brush: &mut GlyphBrush<()>, +) -> (Bounds, Drawable) { + use RocElemTag::*; + + match elem.tag() { + Rect => { + let rect = unsafe { &elem.entry().rect }; + + let bounds = Bounds { + width: rect.width, + height: rect.height, + }; + + let drawable = Drawable { + pos: (rect.left, rect.top).into(), + bounds, + content: DrawableContent::FillRect { + color: rect.color, + border_width: 1.0, + border_color: rect.color, + }, + }; + + (bounds, drawable) + } + Text => { + let text = unsafe { &elem.entry().text }; + let is_centered = true; // TODO don't hardcode this + let layout = wgpu_glyph::Layout::default().h_align(if is_centered { + wgpu_glyph::HorizontalAlign::Center + } else { + wgpu_glyph::HorizontalAlign::Left + }); + + let section = owned_section_from_str(text.as_str(), bounds, layout); + + // Calculate the bounds and offset by measuring glyphs + let text_bounds; + let offset; + + match glyph_brush.glyph_bounds(section.to_borrowed()) { + Some(glyph_bounds) => { + text_bounds = Bounds { + width: glyph_bounds.max.x - glyph_bounds.min.x, + height: glyph_bounds.max.y - glyph_bounds.min.y, + }; + + offset = (-glyph_bounds.min.x, -glyph_bounds.min.y).into(); + } + None => { + text_bounds = Bounds { + width: 0.0, + height: 0.0, + }; + + offset = (0.0, 0.0).into(); + } + } + + let drawable = Drawable { + pos: (0.0, 0.0).into(), // TODO store the pos in Text and read it here + bounds: text_bounds, + content: DrawableContent::Text(section, offset), + }; + + (text_bounds, drawable) + } + } +} + +fn owned_section_from_str( + string: &str, + bounds: Bounds, + layout: wgpu_glyph::Layout, +) -> OwnedSection { + // TODO don't hardcode any of this! + let color = Rgba::WHITE; + let size: f32 = 40.0; + + OwnedSection { + bounds: (bounds.width, bounds.height), + layout, + ..OwnedSection::default() + } + .add_text( + glyph_brush::OwnedText::new(string) + .with_color(Vector4::from(color)) + .with_scale(size), + ) +} diff --git a/examples/breakout/platform/src/lib.rs b/examples/breakout/platform/src/lib.rs new file mode 100644 index 0000000000..196a65c017 --- /dev/null +++ b/examples/breakout/platform/src/lib.rs @@ -0,0 +1,16 @@ +mod graphics; +mod gui; +mod roc; + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + let bounds = roc::Bounds { + width: 1900.0, + height: 1000.0, + }; + + gui::run_event_loop("RocOut!", bounds).expect("Error running event loop"); + + // Exit code + 0 +} diff --git a/examples/breakout/platform/src/main.rs b/examples/breakout/platform/src/main.rs new file mode 100644 index 0000000000..51175f934b --- /dev/null +++ b/examples/breakout/platform/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(host::rust_main()); +} diff --git a/examples/breakout/platform/src/roc.rs b/examples/breakout/platform/src/roc.rs new file mode 100644 index 0000000000..a8aeb73d31 --- /dev/null +++ b/examples/breakout/platform/src/roc.rs @@ -0,0 +1,435 @@ +use crate::graphics::colors::Rgba; +use core::alloc::Layout; +use core::ffi::c_void; +use core::mem::ManuallyDrop; +use roc_std::{ReferenceCount, RocList, RocStr}; +use std::ffi::CStr; +use std::fmt::Debug; +use std::mem::MaybeUninit; +use std::os::raw::c_char; +use std::time::Duration; +use winit::event::VirtualKeyCode; + +extern "C" { + // program + + #[link_name = "roc__programForHost_1_exposed_generic"] + fn roc_program() -> (); + + #[link_name = "roc__programForHost_size"] + fn roc_program_size() -> i64; + + // init + + #[link_name = "roc__programForHost_1_Init_caller"] + fn call_init(size: *const Bounds, closure_data: *const u8, output: *mut Model); + + #[link_name = "roc__programForHost_1_Init_size"] + fn init_size() -> i64; + + #[link_name = "roc__programForHost_1_Init_result_size"] + fn init_result_size() -> i64; + + // update + + #[link_name = "roc__programForHost_1_Update_caller"] + fn call_update( + model: *const Model, + event: *const RocEvent, + closure_data: *const u8, + output: *mut Model, + ); + + #[link_name = "roc__programForHost_1_Update_size"] + fn update_size() -> i64; + + #[link_name = "roc__programForHost_1_Update_result_size"] + fn update_result_size() -> i64; + + // render + + #[link_name = "roc__programForHost_1_Render_caller"] + fn call_render(model: *const Model, closure_data: *const u8, output: *mut RocList); + + #[link_name = "roc__programForHost_1_Render_size"] + fn roc_render_size() -> i64; +} + +#[repr(C)] +pub union RocEventEntry { + pub key_down: RocKeyCode, + pub key_up: RocKeyCode, + pub resize: Bounds, + pub tick: [u8; 16], // u128 is unsupported in repr(C) +} + +#[repr(u8)] +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RocEventTag { + KeyDown = 0, + KeyUp, + Resize, + Tick, +} + +#[repr(C)] +#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits +pub struct RocEvent { + entry: RocEventEntry, + tag: RocEventTag, +} + +impl Debug for RocEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use RocEventTag::*; + + match self.tag() { + KeyDown => unsafe { self.entry().key_down }.fmt(f), + KeyUp => unsafe { self.entry().key_up }.fmt(f), + Resize => unsafe { self.entry().resize }.fmt(f), + Tick => unsafe { self.entry().tick }.fmt(f), + } + } +} + +impl RocEvent { + #[cfg(target_pointer_width = "64")] + pub fn tag(&self) -> RocEventTag { + self.tag + } + + pub fn entry(&self) -> &RocEventEntry { + &self.entry + } + + #[allow(non_snake_case)] + pub fn Resize(size: Bounds) -> Self { + Self { + tag: RocEventTag::Resize, + entry: RocEventEntry { resize: size }, + } + } + + #[allow(non_snake_case)] + pub fn KeyDown(keycode: RocKeyCode) -> Self { + Self { + tag: RocEventTag::KeyDown, + entry: RocEventEntry { key_down: keycode }, + } + } + + #[allow(non_snake_case)] + pub fn KeyUp(keycode: RocKeyCode) -> Self { + Self { + tag: RocEventTag::KeyUp, + entry: RocEventEntry { key_up: keycode }, + } + } + + #[allow(non_snake_case)] + pub fn Tick(duration: Duration) -> Self { + Self { + tag: RocEventTag::Tick, + entry: RocEventEntry { + tick: duration.as_nanos().to_ne_bytes(), + }, + } + } +} + +#[repr(u8)] +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RocKeyCode { + Left = 0, + Other, + Right, +} + +impl From for RocKeyCode { + fn from(keycode: VirtualKeyCode) -> Self { + use VirtualKeyCode::*; + + match keycode { + Left => RocKeyCode::Left, + Right => RocKeyCode::Right, + _ => RocKeyCode::Other, + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +#[repr(transparent)] +#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ElemId(*const RocElemEntry); + +#[repr(C)] +pub union RocElemEntry { + pub rect: ManuallyDrop, + pub text: ManuallyDrop, +} + +#[repr(u8)] +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RocElemTag { + Rect = 0, + Text = 1, +} + +#[repr(C)] +#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits +pub struct RocElem { + entry: RocElemEntry, + tag: RocElemTag, +} + +impl Debug for RocElem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use RocElemTag::*; + + match self.tag() { + Rect => unsafe { &*self.entry().rect }.fmt(f), + Text => unsafe { &*self.entry().text }.fmt(f), + } + } +} + +impl RocElem { + #[cfg(target_pointer_width = "64")] + pub fn tag(&self) -> RocElemTag { + self.tag + } + + #[allow(unused)] + pub fn entry(&self) -> &RocElemEntry { + &self.entry + } + + #[allow(unused)] + pub fn rect(styles: ButtonStyles) -> RocElem { + todo!("restore rect() method") + // let rect = RocRect { styles }; + // let entry = RocElemEntry { + // rect: ManuallyDrop::new(rect), + // }; + + // Self::elem_from_tag(entry, RocElemTag::Rect) + } + + #[allow(unused)] + pub fn text>(into_roc_str: T) -> RocElem { + todo!("TODO restore text method") + // let entry = RocElemEntry { + // text: ManuallyDrop::new(into_roc_str.into()), + // }; + + // Self::elem_from_tag(entry, RocElemTag::Text) + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct RocRect { + pub color: Rgba, + + // These must be in this order for alphabetization! + pub height: f32, + pub left: f32, + pub top: f32, + pub width: f32, +} + +unsafe impl ReferenceCount for RocElem { + /// Increment the reference count. + fn increment(&self) { + use RocElemTag::*; + + match self.tag() { + Rect => { /* nothing to increment! */ } + Text => unsafe { &*self.entry().text }.increment(), + } + } + + /// Decrement the reference count. + /// + /// # Safety + /// + /// The caller must ensure that `ptr` points to a value with a non-zero + /// reference count. + unsafe fn decrement(ptr: *const Self) { + use RocElemTag::*; + + let elem = &*ptr; + + match elem.tag() { + Rect => { /* nothing to decrement! */ } + Text => ReferenceCount::decrement(&*elem.entry().text), + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default)] +pub struct ButtonStyles { + pub bg_color: Rgba, + pub border_color: Rgba, + pub border_width: f32, + pub text_color: Rgba, +} + +#[derive(Copy, Clone, Debug, Default)] +#[repr(C)] +pub struct Bounds { + pub height: f32, + pub width: f32, +} + +type Model = c_void; + +/// Call the app's init function, then render and return that result +pub fn init_and_render(bounds: Bounds) -> (*const Model, RocList) { + let closure_data_buf; + let closure_layout; + + // Call init to get the initial model + let model = unsafe { + let ret_val_layout = Layout::array::(init_result_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model; + + closure_layout = Layout::array::(init_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + closure_data_buf = std::alloc::alloc(closure_layout); + + call_init(&bounds, closure_data_buf, ret_val_buf); + + ret_val_buf + }; + + // Call render passing the model to get the initial Elems + let elems = unsafe { + let mut ret_val: MaybeUninit> = MaybeUninit::uninit(); + + // Reuse the buffer from the previous closure if possible + let closure_data_buf = + std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize); + + call_render(model, closure_data_buf, ret_val.as_mut_ptr()); + + std::alloc::dealloc(closure_data_buf, closure_layout); + + ret_val.assume_init() + }; + + (model, elems) +} + +/// Call the app's update function, then render and return that result +pub fn update(model: *const Model, event: RocEvent) -> *const Model { + let closure_data_buf; + let closure_layout; + + // Call update to get the new model + unsafe { + let ret_val_layout = Layout::array::(update_result_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model; + + closure_layout = Layout::array::(update_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + closure_data_buf = std::alloc::alloc(closure_layout); + + call_update(model, &event, closure_data_buf, ret_val_buf); + + ret_val_buf + } +} + +/// Call the app's update function, then render and return that result +pub fn update_and_render(model: *const Model, event: RocEvent) -> (*const Model, RocList) { + let closure_data_buf; + let closure_layout; + + // Call update to get the new model + let model = unsafe { + let ret_val_layout = Layout::array::(update_result_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model; + + closure_layout = Layout::array::(update_size() as usize).unwrap(); + + // TODO allocate on the stack if it's under a certain size + closure_data_buf = std::alloc::alloc(closure_layout); + + call_update(model, &event, closure_data_buf, ret_val_buf); + + ret_val_buf + }; + + // Call render passing the model to get the initial Elems + let elems = unsafe { + let mut ret_val: MaybeUninit> = MaybeUninit::uninit(); + + // Reuse the buffer from the previous closure if possible + let closure_data_buf = + std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize); + + call_render(model, closure_data_buf, ret_val.as_mut_ptr()); + + std::alloc::dealloc(closure_data_buf, closure_layout); + + ret_val.assume_init() + }; + + (model, elems) +} diff --git a/examples/false-interpreter/Variable.roc b/examples/false-interpreter/Variable.roc index c4675c06c9..866ec789b1 100644 --- a/examples/false-interpreter/Variable.roc +++ b/examples/false-interpreter/Variable.roc @@ -4,7 +4,7 @@ interface Variable # Variables in False can only be single letters. Thus, the valid variables are "a" to "z". # This opaque type deals with ensure we always have valid variables. -Variable : [ @Variable U8 ] +Variable := U8 totalCount : Nat totalCount = diff --git a/examples/false-interpreter/platform/File.roc b/examples/false-interpreter/platform/File.roc index 939f31b7e8..e64efe749a 100644 --- a/examples/false-interpreter/platform/File.roc +++ b/examples/false-interpreter/platform/File.roc @@ -2,7 +2,7 @@ interface File exposes [ line, Handle, withOpen, chunk ] imports [ pf.Effect, Task.{ Task } ] -Handle : [ @Handle U64 ] +Handle := U64 line : Handle -> Task.Task Str * line = \@Handle handle -> Effect.after (Effect.getFileLine handle) Task.succeed diff --git a/examples/false-interpreter/platform/src/lib.rs b/examples/false-interpreter/platform/src/lib.rs index 0b60869fa7..7efee7307c 100644 --- a/examples/false-interpreter/platform/src/lib.rs +++ b/examples/false-interpreter/platform/src/lib.rs @@ -74,7 +74,9 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut #[no_mangle] pub extern "C" fn rust_main() -> i32 { - let arg = env::args().nth(1).unwrap(); + let arg = env::args() + .nth(1) + .expect("Please pass a .false file as a command-line argument to the false interpreter!"); let arg = RocStr::from(arg.as_str()); let size = unsafe { roc_main_size() } as usize; diff --git a/examples/gui/Hello.roc b/examples/gui/Hello.roc index 653ccbd8e1..4738b97b11 100644 --- a/examples/gui/Hello.roc +++ b/examples/gui/Hello.roc @@ -8,14 +8,12 @@ render = styles = { bgColor: rgba 100 50 50 1, borderColor: rgba 10 20 30 1, borderWidth: 10, textColor: rgba 220 220 250 1 } - Col - [ - Row - [ - Button (Text "Corner ") styles, - Button (Text "Top Mid ") { styles & bgColor: rgba 100 100 50 1 }, - Button (Text "Top Right ") { styles & bgColor: rgba 50 50 150 1 }, - ], - Button (Text "Mid Left ") { styles & bgColor: rgba 150 100 100 1 }, - Button (Text "Bottom Left") { styles & bgColor: rgba 150 50 50 1 }, - ] + Col [ + Row [ + Button (Text "Corner ") styles, + Button (Text "Top Mid ") { styles & bgColor: rgba 100 100 50 1 }, + Button (Text "Top Right ") { styles & bgColor: rgba 50 50 150 1 }, + ], + Button (Text "Mid Left ") { styles & bgColor: rgba 150 100 100 1 }, + Button (Text "Bottom Left") { styles & bgColor: rgba 150 50 50 1 }, + ] diff --git a/examples/gui/platform/.gitignore b/examples/gui/platform/.gitignore new file mode 100644 index 0000000000..eb5a316cbd --- /dev/null +++ b/examples/gui/platform/.gitignore @@ -0,0 +1 @@ +target diff --git a/examples/gui/platform/src/focus.rs b/examples/gui/platform/src/focus.rs new file mode 100644 index 0000000000..71b46b6b1c --- /dev/null +++ b/examples/gui/platform/src/focus.rs @@ -0,0 +1,172 @@ +use crate::roc::{ElemId, RocElem, RocElemTag}; + +#[derive(Debug, PartialEq, Eq)] +pub struct Focus { + focused: Option, + focused_ancestors: Vec<(ElemId, usize)>, +} + +impl Default for Focus { + fn default() -> Self { + Self { + focused: None, + focused_ancestors: Vec::new(), + } + } +} + +impl Focus { + pub fn focused_elem(&self) -> Option { + self.focused + } + + /// e.g. the user pressed Tab. + /// + /// This is in contrast to next_local, which advances within a button group. + /// For example, if I have three radio buttons in a group, pressing the + /// arrow keys will cycle through them over and over without exiting the group - + /// whereas pressing Tab will cycle through them once and then exit the group. + pub fn next_global(&mut self, root: &RocElem) { + match self.focused { + Some(focused) => { + // while let Some((ancestor_id, index)) = self.focused_ancestors.pop() { + // let ancestor = ancestor_id.elem(); + + // // TODO FIXME - right now this will re-traverse a lot of ground! To prevent this, + // // we should remember past indices searched, and tell the ancestors "hey stop searching when" + // // you reach these indices, because they were already covered previously. + // // One potentially easy way to do this: pass a min_index and max_index, and only look between those! + // // + // // Related idea: instead of doing .pop() here, iterate normally so we can `break;` after storing + // // `new_ancestors = Some(next_ancestors);` - this way, we still have access to the full ancestry, and + // // can maybe even pass it in to make it clear what work has already been done! + // if let Some((new_id, new_ancestors)) = + // Self::next_focusable_sibling(focused, Some(ancestor), Some(index)) + // { + // // We found the next element to focus, so record that. + // self.focused = Some(new_id); + + // // We got a path to the new focusable's ancestor(s), so add them to the path. + // // (This may restore some of the ancestors we've been .pop()-ing as we iterated.) + // self.focused_ancestors.extend(new_ancestors); + + // return; + // } + + // // Need to write a bunch of tests for this, especially tests of focus wrapping around - e.g. + // // what happens if it wraps around to a sibling? What happens if it wraps around to something + // // higher up the tree? Lower down the tree? What if nothing is focusable? + // // A separate question: what if we should have a separate text-to-speech concept separate from focus? + // } + } + None => { + // Nothing was focused in the first place, so try to focus the root. + if root.is_focusable() { + self.focused = Some(root.id()); + self.focused_ancestors = Vec::new(); + } else if let Some((new_id, new_ancestors)) = + Self::next_focusable_sibling(root, None, None) + { + // If the root itself is not focusable, use its next focusable sibling. + self.focused = Some(new_id); + self.focused_ancestors = new_ancestors; + } + + // Regardless of whether we found a focusable Elem, we're done. + return; + } + } + } + + /// Return the next focusable sibling element after this one. + /// If this element has no siblings, or no *next* sibling after the given index + /// (e.g. the given index refers to the last element in a Row element), return None. + fn next_focusable_sibling( + elem: &RocElem, + ancestor: Option<&RocElem>, + opt_index: Option, + ) -> Option<(ElemId, Vec<(ElemId, usize)>)> { + use RocElemTag::*; + + match elem.tag() { + Button | Text => None, + Row | Col => { + let children = unsafe { &elem.entry().row_or_col.children.as_slice() }; + let iter = match opt_index { + Some(focus_index) => children[0..focus_index].iter(), + None => children.iter(), + }; + + for child in iter { + if let Some(focused) = Self::next_focusable_sibling(child, ancestor, None) { + return Some(focused); + } + } + + None + } + } + } +} + +#[test] +fn next_global_button_root() { + use crate::roc::{ButtonStyles, RocElem}; + + let child = RocElem::text(""); + let root = RocElem::button(ButtonStyles::default(), child); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Buttons should be focusable, so advancing focus should give the button focus. + assert_eq!(focus.focused_elem(), Some(root.id())); + + // Since the button is at the root, advancing again should maintain focus on it. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), Some(root.id())); +} + +#[test] +fn next_global_text_root() { + let root = RocElem::text(""); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Text should not be focusable, so advancing focus should have no effect here. + assert_eq!(focus.focused_elem(), None); + + // Just to double-check, advancing a second time should not change this. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), None); +} + +#[test] +fn next_global_row() { + use crate::roc::{ButtonStyles, RocElem}; + + let child = RocElem::text(""); + let button = RocElem::button(ButtonStyles::default(), child); + let button_id = button.id(); + let root = RocElem::row(&[button] as &[_]); + let mut focus = Focus::default(); + + // At first, nothing should be focused. + assert_eq!(focus.focused_elem(), None); + + focus.next_global(&root); + + // Buttons should be focusable, so advancing focus should give the first button in the row focus. + assert_eq!(focus.focused_elem(), Some(button_id)); + + // Since the button is the only element in the row, advancing again should maintain focus on it. + focus.next_global(&root); + assert_eq!(focus.focused_elem(), Some(button_id)); +} diff --git a/examples/hello-world/zig-platform/host.zig b/examples/hello-world/zig-platform/host.zig index f20686dcb0..18a1427207 100644 --- a/examples/hello-world/zig-platform/host.zig +++ b/examples/hello-world/zig-platform/host.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const str = @import("str"); const RocStr = str.RocStr; const testing = std.testing; @@ -14,21 +15,21 @@ comptime { // -fcompiler-rt in link.rs instead of doing this. Note that this // workaround is present in many host.zig files, so make sure to undo // it everywhere! - if (std.builtin.os.tag == .macos) { + if (builtin.os.tag == .macos) { _ = @import("compiler_rt"); } } const Align = 2 * @alignOf(usize); -extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; -extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; +extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; const DEBUG: bool = false; -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { var ptr = malloc(size); const stdout = std.io.getStdOut().writer(); @@ -39,7 +40,7 @@ export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { } } -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; @@ -48,7 +49,7 @@ export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); } -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { +export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; @@ -57,7 +58,7 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { _ = tag_id; const stderr = std.io.getStdErr().writer(); @@ -87,7 +88,7 @@ pub fn main() u8 { // start time var ts1: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable; // actually call roc to populate the callresult var callresult = RocStr.empty(); @@ -95,7 +96,7 @@ pub fn main() u8 { // end time var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable; // stdout the result stdout.print("{s}", .{callresult.asSlice()}) catch unreachable; diff --git a/examples/interactive/effects-platform/Package-Config.roc b/examples/interactive/effects-platform/Package-Config.roc index 4e4d6abea2..b37f558538 100644 --- a/examples/interactive/effects-platform/Package-Config.roc +++ b/examples/interactive/effects-platform/Package-Config.roc @@ -1,5 +1,5 @@ platform "effects" - requires {} { main : Effect {} } + requires {} { main : Effect.Effect {} } exposes [] packages {} imports [ pf.Effect ] diff --git a/examples/interactive/effects-platform/host.zig b/examples/interactive/effects-platform/host.zig index c2f71363b3..24a9b1eea4 100644 --- a/examples/interactive/effects-platform/host.zig +++ b/examples/interactive/effects-platform/host.zig @@ -15,7 +15,8 @@ comptime { // -fcompiler-rt in link.rs instead of doing this. Note that this // workaround is present in many host.zig files, so make sure to undo // it everywhere! - if (std.builtin.os.tag == .macos) { + const builtin = @import("builtin"); + if (builtin.os.tag == .macos) { _ = @import("compiler_rt"); } } @@ -29,25 +30,47 @@ extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; extern fn roc__mainForHost_1_Fx_size() i64; extern fn roc__mainForHost_1_Fx_result_size() i64; -extern fn malloc(size: usize) callconv(.C) ?*c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; +const Align = 2 * @alignOf(usize); +extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; +extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { - return malloc(size); +const DEBUG: bool = false; + +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { + if (DEBUG) { + var ptr = malloc(size); + const stdout = std.io.getStdOut().writer(); + stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable; + return ptr; + } else { + return malloc(size); + } } -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { - return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); +export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; + } + + return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); } -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { - free(@alignCast(16, @ptrCast([*]u8, c_ptr))); +export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { + if (DEBUG) { + const stdout = std.io.getStdOut().writer(); + stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; + } + + free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { + _ = tag_id; + const stderr = std.io.getStdErr().writer(); const msg = @ptrCast([*:0]const u8, c_ptr); stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; @@ -67,7 +90,6 @@ const Unit = extern struct {}; pub export fn main() u8 { const allocator = std.heap.page_allocator; - const stdout = std.io.getStdOut().writer(); const stderr = std.io.getStdErr().writer(); // NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes @@ -80,14 +102,14 @@ pub export fn main() u8 { } var ts1: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable; roc__mainForHost_1_exposed_generic(output); call_the_closure(output); var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable; const delta = to_seconds(ts2) - to_seconds(ts1); @@ -130,11 +152,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void { } pub export fn roc_fx_getLine() str.RocStr { - if (roc_fx_getLine_help()) |value| { - return value; - } else |err| { - return str.RocStr.empty(); - } + return roc_fx_getLine_help() catch return str.RocStr.empty(); } fn roc_fx_getLine_help() !RocStr { @@ -188,8 +206,3 @@ fn roc_fx_getInt_help() !i64 { return std.fmt.parseInt(i64, line, 10); } - -fn readLine() []u8 { - const stdin = std.io.getStdIn().reader(); - return (stdin.readUntilDelimiterOrEof(&line_buf, '\n') catch unreachable) orelse ""; -} diff --git a/examples/interactive/tui-platform/Package-Config.roc b/examples/interactive/tui-platform/Package-Config.roc index 726147e9cb..e358361e3c 100644 --- a/examples/interactive/tui-platform/Package-Config.roc +++ b/examples/interactive/tui-platform/Package-Config.roc @@ -1,5 +1,5 @@ platform "tui" - requires { Model } { main : Effect {} } + requires { Model } { main : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } } exposes [] packages {} imports [] diff --git a/examples/interactive/tui-platform/host.zig b/examples/interactive/tui-platform/host.zig index 2f8dcca83b..dfdf6f1707 100644 --- a/examples/interactive/tui-platform/host.zig +++ b/examples/interactive/tui-platform/host.zig @@ -15,7 +15,8 @@ comptime { // -fcompiler-rt in link.rs instead of doing this. Note that this // workaround is present in many host.zig files, so make sure to undo // it everywhere! - if (std.builtin.os.tag == .macos) { + const builtin = @import("builtin"); + if (builtin.os.tag == .macos) { _ = @import("compiler_rt"); } } @@ -79,15 +80,15 @@ fn view(input: ConstModel) RocStr { } const Align = 2 * @alignOf(usize); -extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; -extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; +extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; +extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque; extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; const DEBUG: bool = false; -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { var ptr = malloc(size); const stdout = std.io.getStdOut().writer(); @@ -98,7 +99,7 @@ export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { } } -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { +export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable; @@ -107,7 +108,7 @@ export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignmen return realloc(@alignCast(Align, @ptrCast([*]u8, c_ptr)), new_size); } -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { +export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { if (DEBUG) { const stdout = std.io.getStdOut().writer(); stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable; @@ -116,7 +117,7 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(Align, @ptrCast([*]u8, c_ptr))); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { +export fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void { _ = tag_id; const stderr = std.io.getStdErr().writer(); @@ -136,17 +137,15 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { const Unit = extern struct {}; pub export fn main() callconv(.C) u8 { - const allocator = std.heap.page_allocator; - var ts1: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts1) catch unreachable; const program = roc__mainForHost_1_exposed(); call_the_closure(program); var ts2: std.os.timespec = undefined; - std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts2) catch unreachable; const delta = to_seconds(ts2) - to_seconds(ts1); @@ -161,13 +160,15 @@ fn to_seconds(tms: std.os.timespec) f64 { } fn call_the_closure(program: Program) void { - const allocator = std.heap.page_allocator; + _ = program; + + var allocator = std.heap.page_allocator; const stdout = std.io.getStdOut().writer(); const stdin = std.io.getStdIn().reader(); var buf: [1000]u8 = undefined; - var model = init(allocator); + var model = init(&allocator); while (true) { const line = (stdin.readUntilDelimiterOrEof(buf[0..], '\n') catch unreachable) orelse return; @@ -178,7 +179,7 @@ fn call_the_closure(program: Program) void { const to_append = RocStr.init(line.ptr, line.len); - model = update(allocator, model, to_append); + model = update(&allocator, model, to_append); const viewed = view(model); for (viewed.asSlice()) |char| { @@ -266,8 +267,3 @@ fn roc_fx_getInt_help() !i64 { return std.fmt.parseInt(i64, line, 10); } - -fn readLine() []u8 { - const stdin = std.io.getStdIn().reader(); - return (stdin.readUntilDelimiterOrEof(&line_buf, '\n') catch unreachable) orelse ""; -} diff --git a/examples/interactive/tui.roc b/examples/interactive/tui.roc index 03b05efc17..3f09cbfdf5 100644 --- a/examples/interactive/tui.roc +++ b/examples/interactive/tui.roc @@ -6,9 +6,8 @@ app "tui" Model : Str main : Program Model -main = - { - init: \{ } -> "Hello World", - update: \model, new -> Str.concat model new, - view: \model -> Str.concat model "!", - } +main = { + init: \{ } -> "Hello World", + update: \model, new -> Str.concat model new, + view: \model -> Str.concat model "!", +} diff --git a/flake.lock b/flake.lock index cf736beb1b..ff2f6f9f75 100644 --- a/flake.lock +++ b/flake.lock @@ -16,6 +16,21 @@ } }, "flake-utils_2": { + "locked": { + "lastModified": 1637014545, + "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { "locked": { "lastModified": 1629481132, "narHash": "sha256-JHgasjPR0/J1J3DRm4KxM4zTyAj4IOJY8vIl75v/kPI=", @@ -46,13 +61,13 @@ "type": "github" } }, - "nixpkgs-unstable": { + "nixpkgs_2": { "locked": { - "lastModified": 1647350163, - "narHash": "sha256-OcMI+PFEHTONthXuEQNddt16Ml7qGvanL3x8QOl2Aao=", + "lastModified": 1637453606, + "narHash": "sha256-Gy6cwUswft9xqsjWxFYEnx/63/qzaFUwatcbV5GF/GQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3eb07eeafb52bcbf02ce800f032f18d666a9498d", + "rev": "8afc4e543663ca0a6a4f496262cd05233737e732", "type": "github" }, "original": { @@ -62,7 +77,7 @@ "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { "locked": { "lastModified": 1631288242, "narHash": "sha256-sXm4KiKs7qSIf5oTAmrlsEvBW193sFj+tKYVirBaXz0=", @@ -82,21 +97,40 @@ "inputs": { "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", - "nixpkgs-unstable": "nixpkgs-unstable", + "rust-overlay": "rust-overlay", "zig": "zig" } }, - "zig": { + "rust-overlay": { "inputs": { "flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1650415318, - "narHash": "sha256-HZDhiEELVjhZf9pR0Y3cYVFI7SZVCiSc1g3yqvMYdFk=", + "lastModified": 1651459928, + "narHash": "sha256-V/4b3OG7W6WgkTZXlaYXIg1lYqXy+VgRQReWE4apjgU=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "979ae5a43bdb7662c9dfd2f57826afde0ad438bb", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "zig": { + "inputs": { + "flake-utils": "flake-utils_3", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1651452199, + "narHash": "sha256-lq6mTsoPeOCsji/oMFf6953/uOtOhZZ7HSdtjNVDh6I=", "owner": "roarkanize", "repo": "zig-overlay", - "rev": "da3b287c1d29761e576ad81ac94aed260744d20d", + "rev": "c3bd59086dbc731c240c924fd2bc3581720d0035", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 527c677247..56dda42463 100644 --- a/flake.nix +++ b/flake.nix @@ -3,17 +3,21 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-21.11"; - nixpkgs-unstable = { url = "github:NixOS/nixpkgs/nixpkgs-unstable"; }; - zig = { url = "github:roarkanize/zig-overlay"; }; - flake-utils.url = "github:numtide/flake-utils"; + rust-overlay.url = "github:oxalica/rust-overlay"; # rust from nixpkgs has some libc problems, this is patched in the rust-overlay + zig.url = "github:roarkanize/zig-overlay"; # using an overlay allows for quick updates fater zig releases + flake-utils.url = "github:numtide/flake-utils"; # to easily make configs for all architectures }; - outputs = { self, nixpkgs, nixpkgs-unstable, zig, flake-utils }: + outputs = { self, nixpkgs, rust-overlay, zig, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let - pkgs = nixpkgs.legacyPackages.${system}; - unstable-pkgs = nixpkgs-unstable.legacyPackages.${system}; - llvmPkgs = pkgs.llvmPackages_12; + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { inherit system overlays; }; + llvmPkgs = pkgs.llvmPackages_13; + + # get current working directory + cwd = builtins.toString ./.; + rust = pkgs.rust-bin.fromRustupToolchainFile "${cwd}/rust-toolchain.toml"; linuxInputs = with pkgs; lib.optionals stdenv.isLinux [ @@ -39,10 +43,10 @@ Foundation Metal Security - ]); + ]); - # zig 0.8.1 from pkgs is broken on aarch64-darwin, hence the workaround - zig-toolchain = zig.packages.${system}."0.8.1"; + # zig 0.9.1 from pkgs is broken on aarch64-darwin, hence the workaround + zig-toolchain = zig.packages.${system}."0.9.1"; sharedInputs = (with pkgs; [ # build libraries @@ -65,22 +69,20 @@ # faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker llvmPkgs.lld # debugir - ]) ++ (with unstable-pkgs; [ - rustc - cargo - clippy - rustfmt + rust ]); in { + devShell = pkgs.mkShell { buildInputs = sharedInputs ++ darwinInputs ++ linuxInputs; - LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}"; + LLVM_SYS_130_PREFIX = "${llvmPkgs.llvm.dev}"; NIX_GLIBC_PATH = if pkgs.stdenv.isLinux then "${pkgs.glibc_multi.out}/lib" else ""; LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath - ([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ]); + ([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] ++ linuxInputs); }; - } - ); + + } + ); } diff --git a/highlight/src/tokenizer.rs b/highlight/src/tokenizer.rs index 9eddb99da1..95f8a9fb12 100644 --- a/highlight/src/tokenizer.rs +++ b/highlight/src/tokenizer.rs @@ -63,7 +63,6 @@ pub enum Token { OpAnd = 0b_0110_1101, OpOr = 0b_0110_1110, OpDoubleSlash = 0b_0110_1111, - OpDoublePercent = 0b_0111_0001, OpBackpassing = 0b_0111_1010, TodoNextThing = 0b_1000_0000, @@ -71,8 +70,6 @@ pub enum Token { Malformed, MalformedOperator, - PrivateTag, - String, NumberBase, @@ -150,7 +147,6 @@ fn consume_all_tokens(state: &mut LexState, bytes: &[u8], consumer: &mut impl Co b']' => (Token::CloseSquare, 1), b',' => (Token::Comma, 1), b'_' => lex_underscore(bytes), - b'@' => lex_private_tag(bytes), b'a'..=b'z' => lex_ident(false, bytes), b'A'..=b'Z' => lex_ident(true, bytes), b'0'..=b'9' => lex_number(bytes), @@ -395,7 +391,6 @@ fn lex_operator(bytes: &[u8]) -> (Token, usize) { b"&" => Token::Ampersand, b"||" => Token::OpOr, b"//" => Token::OpDoubleSlash, - b"%%" => Token::OpDoublePercent, b"->" => Token::Arrow, b"<-" => Token::OpBackpassing, op => { @@ -410,15 +405,6 @@ fn is_ident_continue(ch: u8) -> bool { matches!(ch, b'a'..=b'z'|b'A'..=b'Z'|b'0'..=b'9'|b'_') } -fn lex_private_tag(bytes: &[u8]) -> (Token, usize) { - debug_assert!(bytes[0] == b'@'); - let mut i = 1; - while i < bytes.len() && is_ident_continue(bytes[i]) { - i += 1; - } - (Token::PrivateTag, i) -} - fn lex_ident(uppercase: bool, bytes: &[u8]) -> (Token, usize) { let mut i = 0; while i < bytes.len() && is_ident_continue(bytes[i]) { diff --git a/highlight/tests/peg_grammar.rs b/highlight/tests/peg_grammar.rs index 7b8bde04c5..f8e052f22f 100644 --- a/highlight/tests/peg_grammar.rs +++ b/highlight/tests/peg_grammar.rs @@ -72,10 +72,7 @@ mod test_peg_grammar { rule tag() = - private_tag() - / [T::UppercaseIdent] - - rule private_tag() = [T::PrivateTag] {} + [T::UppercaseIdent] rule list() = empty_list() @@ -368,7 +365,6 @@ mod test_peg_grammar { / [T::OpSlash] / [T::OpDoubleSlash] / [T::OpPercent] - / [T::OpDoublePercent] rule mul_level_expr() = unary_expr() (mul_level_op() unary_expr())* diff --git a/linker/Cargo.toml b/linker/Cargo.toml index c95a3ee5e2..497c8088a1 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -22,11 +22,11 @@ roc_mono = { path = "../compiler/mono" } roc_build = { path = "../compiler/build" } 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"] } +clap = { version = "3.1.15", 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.3" object = { version = "0.26.2", features = ["read", "write"] } serde = { version = "1.0.130", features = ["derive"] } bincode = "1.3.3" -target-lexicon = "0.12.2" +target-lexicon = "0.12.3" tempfile = "3.2.0" diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 1b4a2a8d14..581ae2bb97 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -1,5 +1,5 @@ use bincode::{deserialize_from, serialize_into}; -use clap::{App, AppSettings, Arg, ArgMatches}; +use clap::{Arg, ArgMatches, Command}; use iced_x86::{Decoder, DecoderOptions, Instruction, OpCodeOperandKind, OpKind}; use memmap2::{Mmap, MmapMut}; use object::write; @@ -21,7 +21,6 @@ use std::io::{BufReader, BufWriter}; use std::mem; use std::os::raw::c_char; use std::path::Path; -use std::process::Command; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; use tempfile::Builder; @@ -49,64 +48,65 @@ fn report_timing(label: &str, duration: Duration) { println!("\t{:9.3} ms {}", duration.as_secs_f64() * 1000.0, label,); } -pub fn build_app<'a>() -> App<'a> { - App::new("link") +pub fn build_app<'a>() -> Command<'a> { + Command::new("link") .about("Preprocesses a platform and surgically links it to an application.") - .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand_required(true) + .arg_required_else_help(true) .subcommand( - App::new(CMD_PREPROCESS) + Command::new(CMD_PREPROCESS) .about("Preprocesses a dynamically linked platform to prepare for linking.") .arg( Arg::new(EXEC) - .about("The dynamically linked platform executable") + .help("The dynamically linked platform executable") .required(true), ) .arg( Arg::new(METADATA) - .about("Where to save the metadata from preprocessing") + .help("Where to save the metadata from preprocessing") .required(true), ) .arg( Arg::new(OUT) - .about("The modified version of the dynamically linked platform executable") + .help("The modified version of the dynamically linked platform executable") .required(true), ) .arg( Arg::new(SHARED_LIB) - .about("The name of the shared library used in building the platform") + .help("The name of the shared library used in building the platform") .default_value("libapp.so"), ) .arg( Arg::new(FLAG_VERBOSE) .long(FLAG_VERBOSE) .short('v') - .about("Enable verbose printing") + .help("Enable verbose printing") .required(false), ) .arg( Arg::new(FLAG_TIME) .long(FLAG_TIME) .short('t') - .about("Print timing information") + .help("Print timing information") .required(false), ), ) .subcommand( - App::new(CMD_SURGERY) + Command::new(CMD_SURGERY) .about("Links a preprocessed platform with a Roc application.") .arg( Arg::new(APP) - .about("The Roc application object file waiting to be linked") + .help("The Roc application object file waiting to be linked") .required(true), ) .arg( Arg::new(METADATA) - .about("The metadata created by preprocessing the platform") + .help("The metadata created by preprocessing the platform") .required(true), ) .arg( Arg::new(OUT) - .about( + .help( "The modified version of the dynamically linked platform. \ It will be consumed to make linking faster.", ) @@ -116,14 +116,14 @@ pub fn build_app<'a>() -> App<'a> { Arg::new(FLAG_VERBOSE) .long(FLAG_VERBOSE) .short('v') - .about("Enable verbose printing") + .help("Enable verbose printing") .required(false), ) .arg( Arg::new(FLAG_TIME) .long(FLAG_TIME) .short('t') - .about("Print timing information") + .help("Print timing information") .required(false), ), ) @@ -243,7 +243,7 @@ fn generate_dynamic_lib( ) .expect("failed to write object to file"); - let output = Command::new("ld") + let output = std::process::Command::new("ld") .args(&[ "-shared", "-soname", diff --git a/nix/debugir.nix b/nix/debugir.nix index dd0af8ee12..f5251e19ef 100644 --- a/nix/debugir.nix +++ b/nix/debugir.nix @@ -5,14 +5,14 @@ pkgs.stdenv.mkDerivation { src = pkgs.fetchFromGitHub { owner = "vaivaswatha"; repo = "debugir"; - rev = "db871e6cee7f653e284b226e2567a2574635247c"; - sha256 = "0rgh9gawf92mjya1plxlgi9azkwca3gq8qa5hri18k4b7sbjm6lx"; + rev = "b981e0b74872d9896ba447dd6391dfeb63332b80"; + sha256 = "Gzey0SF0NZkpiObk5e29nbc41dn4Olv1dx+6YixaZH0="; }; - buildInputs = with pkgs; [ cmake libxml2 llvmPackages_12.llvm.dev ]; + buildInputs = with pkgs; [ cmake libxml2 llvmPackages_13.llvm.dev ]; buildPhase = '' mkdir build cd build - cmake -DLLVM_DIR=${pkgs.llvmPackages_12.llvm.dev} -DCMAKE_BUILD_TYPE=Release ../ + cmake -DLLVM_DIR=${pkgs.llvmPackages_13.llvm.dev} -DCMAKE_BUILD_TYPE=Release ../ cmake --build ../ cp ../debugir . ''; diff --git a/nix/sources.json b/nix/sources.json index 04dc2dbb42..fe360b18d1 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -29,10 +29,10 @@ "homepage": "", "owner": "NixOS", "repo": "nixpkgs", - "rev": "684c73c9e6ac8f4d0c6dea3251292e758ac375b5", - "sha256": "0hl2nzizn4pwd3sn9gxkngzn88k9in01xm14afpj7716j8y0j2qa", + "rev": "b283b64580d1872333a99af2b4cef91bb84580cf", + "sha256": "0gmrpfzc622xl1lv3ffaj104j2q3nmia7jywafqmgmrcdm9axkkp", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/684c73c9e6ac8f4d0c6dea3251292e758ac375b5.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/b283b64580d1872333a99af2b4cef91bb84580cf.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/nix/zig.nix b/nix/zig.nix index b9ac0a4111..5d2ce85315 100644 --- a/nix/zig.nix +++ b/nix/zig.nix @@ -1,7 +1,7 @@ { pkgs }: let - version = "0.8.1"; + version = "0.9.1"; osName = if pkgs.stdenv.isDarwin then "macos" else "linux"; @@ -14,13 +14,13 @@ let # If your system is not aarch64, we assume it's x86_64 sha256 = if pkgs.stdenv.isDarwin then if isAarch64 then - "5351297e3b8408213514b29c0a938002c5cf9f97eee28c2f32920e1227fd8423" # macos-aarch64 + "8c473082b4f0f819f1da05de2dbd0c1e891dff7d85d2c12b6ee876887d438287" # macos-aarch64 else - "16b0e1defe4c1807f2e128f72863124bffdd906cefb21043c34b673bf85cd57f" # macos-x86_64 + "2d94984972d67292b55c1eb1c00de46580e9916575d083003546e9a01166754c" # macos-x86_64 else if isAarch64 then - "2166dc9f2d8df387e8b4122883bb979d739281e1ff3f3d5483fec3a23b957510" # linux-aarch64 + "5d99a39cded1870a3fa95d4de4ce68ac2610cca440336cfd252ffdddc2b90e66" # linux-aarch64 else - "6c032fc61b5d77a3f3cf781730fa549f8f059ffdb3b3f6ad1c2994d2b2d87983"; # linux-x86_64 + "be8da632c1d3273f766b69244d80669fe4f5e27798654681d77c992f17c237d7"; # linux-x86_64 in pkgs.stdenv.mkDerivation { pname = "zig"; version = version; diff --git a/packages/parser/src/Bytes/Parser.roc b/packages/parser/src/Bytes/Parser.roc index 31a61654d6..a1dc6648c0 100644 --- a/packages/parser/src/Bytes/Parser.roc +++ b/packages/parser/src/Bytes/Parser.roc @@ -17,10 +17,7 @@ interface Parser exposes [ Parser ] imports [] -Parser a : - [ - @Parser (Bytes -> Result { answer : a, rest : Bytes } Problem) - ] +Parser a := Bytes -> Result { answer : a, rest : Bytes } Problem Problem : [ diff --git a/packages/parser/src/Str/Parser.roc b/packages/parser/src/Str/Parser.roc index 9962b83a14..7dc755f4c0 100644 --- a/packages/parser/src/Str/Parser.roc +++ b/packages/parser/src/Str/Parser.roc @@ -19,10 +19,7 @@ interface Parser exposes [ Parser ] imports [] -Parser a : - [ - @Parser (Str -> Result { answer : a, rest : Str } RawProblem), - ] +Parser a := Str -> Result { answer : a, rest : Str } RawProblem Problem : [ diff --git a/packages/unicode/src/Unicode/CodePoint/Internal.roc b/packages/unicode/src/Unicode/CodePoint/Internal.roc index 5bac8917c7..5ff40f9513 100644 --- a/packages/unicode/src/Unicode/CodePoint/Internal.roc +++ b/packages/unicode/src/Unicode/CodePoint/Internal.roc @@ -10,7 +10,7 @@ interface Unicode.CodePoint.Internal [] ## This is just here so that both Unicode.Scalar and Unicode.CodePoint can access it. -CodePoint : [ @CodePoint U32 ] +CodePoint := U32 fromU32Unchecked : U32 -> CodePoint fromU32Unchecked = \u32 -> @CodePoint u32 diff --git a/packages/unicode/src/Unicode/Scalar.roc b/packages/unicode/src/Unicode/Scalar.roc index e8a3537d9a..b6a06d3e38 100644 --- a/packages/unicode/src/Unicode/Scalar.roc +++ b/packages/unicode/src/Unicode/Scalar.roc @@ -18,7 +18,7 @@ interface Unicode.Scalar ] ## A [Unicode Scalar Value](http://www.unicode.org/glossary/#unicode_scalar_value) -Scalar : [ @Scalar U32 ] +Scalar := U32 toStr : Scalar -> Str toStr = \@Scalar u32 diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index c6b6dc2011..89651e21a5 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -23,7 +23,7 @@ rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", rev = "e7433 target-lexicon = "0.12.2" # TODO: make llvm optional -roc_build = {path = "../compiler/build", features = ["llvm"]} +roc_build = {path = "../compiler/build"} roc_builtins = {path = "../compiler/builtins"} roc_collections = {path = "../compiler/collections"} roc_gen_llvm = {path = "../compiler/gen_llvm"} diff --git a/repl_cli/src/lib.rs b/repl_cli/src/lib.rs index 155d3ffb81..755d597e08 100644 --- a/repl_cli/src/lib.rs +++ b/repl_cli/src/lib.rs @@ -23,7 +23,7 @@ use roc_repl_eval::{ReplApp, ReplAppMemory}; use roc_reporting::report::DEFAULT_PALETTE; use roc_std::RocStr; use roc_target::TargetInfo; -use roc_types::pretty_print::{content_to_string, name_all_type_vars}; +use roc_types::pretty_print::name_and_print_var; const BLUE: &str = "\u{001b}[36m"; const PINK: &str = "\u{001b}[35m"; @@ -227,9 +227,8 @@ fn gen_and_eval_llvm<'a>( let main_fn_var = *main_fn_var; // pretty-print the expr type string for later. - name_all_type_vars(main_fn_var, &mut subs); + let expr_type_str = name_and_print_var(main_fn_var, &mut subs, home, &interns); let content = subs.get_content_without_compacting(main_fn_var); - let expr_type_str = content_to_string(content, &subs, home, &interns); let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { Some(layout) => *layout, @@ -443,7 +442,7 @@ pub fn main() -> io::Result<()> { break; } Err(err) => { - println!("Error: {:?}", err); + eprintln!("REPL error: {:?}", err); break; } } diff --git a/repl_eval/src/eval.rs b/repl_eval/src/eval.rs index bc718a7c6c..8dfdacb32c 100644 --- a/repl_eval/src/eval.rs +++ b/repl_eval/src/eval.rs @@ -5,7 +5,7 @@ use std::cmp::{max_by_key, min_by_key}; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_module::called_via::CalledVia; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::TagName; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::ProcLayout; use roc_mono::layout::{ @@ -86,16 +86,21 @@ enum NewtypeKind<'a> { /// /// The returned list of newtype containers is ordered by increasing depth. As an example, /// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`. -fn unroll_newtypes<'a>( +/// +/// If we pass through aliases, the top-level alias that should be displayed to the user is passed +/// back as an option. +/// +/// Returns (new type containers, optional alias content, real content). +fn unroll_newtypes_and_aliases<'a>( env: &Env<'a, 'a>, mut content: &'a Content, -) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) { +) -> (Vec<'a, NewtypeKind<'a>>, Option<&'a Content>, &'a Content) { let mut newtype_containers = Vec::with_capacity_in(1, env.arena); - let mut force_alias_content = None; + let mut alias_content = None; loop { match content { Content::Structure(FlatType::TagUnion(tags, _)) - if tags.is_newtype_wrapper_of_global_tag(env.subs) => + if tags.is_newtype_wrapper_of_tag(env.subs) => { let (tag_name, vars): (&TagName, &[Variable]) = tags .unsorted_iterator(env.subs, Variable::EMPTY_TAG_UNION) @@ -113,23 +118,23 @@ fn unroll_newtypes<'a>( newtype_containers.push(NewtypeKind::RecordField( env.arena.alloc_str(label.as_str()), )); - let field_var = *field.as_inner(); - content = env.subs.get_content_without_compacting(field_var); + content = env.subs.get_content_without_compacting(field.into_inner()); } Content::Alias(_, _, real_var, _) => { // We need to pass through aliases too, because their underlying types may have - // unrolled newtypes. In such cases return the list of unrolled newtypes, but keep - // the content as the alias for readability. For example, + // unrolled newtypes. For example, // T : { a : Str } // v : T // v = { a : "value" } // v - // Here we need the newtype container to be `[RecordField(a)]`, but the content to - // remain as the alias `T`. - force_alias_content = Some(content); + // Here we need the newtype container to be `[RecordField(a)]`. + // + // At the end of the day what we should show to the user is the alias content, not + // what's inside, so keep that around too. + alias_content = Some(content); content = env.subs.get_content_without_compacting(*real_var); } - _ => return (newtype_containers, force_alias_content.unwrap_or(content)), + _ => return (newtype_containers, alias_content, content), } } } @@ -140,8 +145,8 @@ fn apply_newtypes<'a>( mut expr: Expr<'a>, ) -> Expr<'a> { let arena = env.arena; - // Reverse order of what we receieve from `unroll_newtypes` since we want the deepest - // container applied first. + // Reverse order of what we receieve from `unroll_newtypes_and_aliases` since + // we want the deepest container applied first. for container in newtype_containers.into_iter().rev() { match container { NewtypeKind::Tag(tag_name) => { @@ -162,13 +167,6 @@ fn apply_newtypes<'a>( expr } -fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { - while let Content::Alias(_, _, real, _) = content { - content = env.subs.get_content_without_compacting(*real); - } - content -} - fn unroll_recursion_var<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { while let Content::RecursionVar { structure, .. } = content { content = env.subs.get_content_without_compacting(*structure); @@ -278,13 +276,13 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( layout: &Layout<'a>, content: &'a Content, ) -> Result, ToAstProblem> { - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); + let (newtype_containers, alias_content, raw_content) = + unroll_newtypes_and_aliases(env, content); macro_rules! num_helper { ($ty:ty) => { app.call_function(main_fn_name, |_, num: $ty| { - num_to_ast(env, number_literal_to_ast(env.arena, num), content) + number_literal_to_ast(env.arena, num) }) }; } @@ -292,17 +290,18 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( let result = match layout { Layout::Builtin(Builtin::Bool) => Ok(app .call_function(main_fn_name, |mem: &A::Memory, num: bool| { - bool_to_ast(env, mem, num, content) + bool_to_ast(env, mem, num, raw_content) })), Layout::Builtin(Builtin::Int(int_width)) => { + use Content::*; use IntWidth::*; - let result = match (content, int_width) { - (Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)), U8) => num_helper!(u8), + let result = match (alias_content, int_width) { + (Some(Alias(Symbol::NUM_UNSIGNED8, ..)), U8) => num_helper!(u8), (_, U8) => { // This is not a number, it's a tag union or something else app.call_function(main_fn_name, |mem: &A::Memory, num: u8| { - byte_to_ast(env, mem, num, content) + byte_to_ast(env, mem, num, raw_content) }) } // The rest are numbers... for now @@ -344,14 +343,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function( main_fn_name, |mem: &A::Memory, (addr, len): (usize, usize)| { - list_to_ast(env, mem, addr, len, elem_layout, content) + list_to_ast(env, mem, addr, len, elem_layout, raw_content) }, )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } Layout::Struct { field_layouts, .. } => { - let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match content { + let struct_addr_to_ast = |mem: &'a A::Memory, addr: usize| match raw_content { Content::Structure(FlatType::Record(fields, _)) => { Ok(struct_to_ast(env, mem, addr, *fields)) } @@ -413,7 +412,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( main_fn_name, size as usize, |mem: &'a A::Memory, addr: usize| { - addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Unreachable, + raw_content, + ) }, )) } @@ -432,7 +438,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( addr, layout, WhenRecursive::Loop(*layout), - content, + raw_content, ) }, )) @@ -447,7 +453,14 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( main_fn_name, size as usize, |mem: &A::Memory, addr| { - addr_to_ast(env, mem, addr, layout, WhenRecursive::Unreachable, content) + addr_to_ast( + env, + mem, + addr, + layout, + WhenRecursive::Unreachable, + raw_content, + ) }, )) } @@ -457,11 +470,7 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> { match tag_name { - TagName::Global(_) => Expr::GlobalTag( - env.arena - .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), - ), - TagName::Private(_) => Expr::PrivateTag( + TagName::Tag(_) => Expr::Tag( env.arena .alloc_str(&tag_name.as_ident_str(env.interns, env.home)), ), @@ -489,13 +498,14 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ($method: ident, $ty: ty) => {{ let num: $ty = mem.$method(addr); - num_to_ast(env, number_literal_to_ast(env.arena, num), content) + number_literal_to_ast(env.arena, num) }}; } - let (newtype_containers, content) = unroll_newtypes(env, content); - let content = unroll_aliases(env, content); - let expr = match (content, layout) { + let (newtype_containers, _alias_content, raw_content) = + unroll_newtypes_and_aliases(env, content); + + let expr = match (raw_content, layout) { (Content::Structure(FlatType::Func(_, _, _)), _) | (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION, (_, Layout::Builtin(Builtin::Bool)) => { @@ -503,7 +513,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( // num is always false at the moment. let num: bool = mem.deref_bool(addr); - bool_to_ast(env, mem, num, content) + bool_to_ast(env, mem, num, raw_content) } (_, Layout::Builtin(Builtin::Int(int_width))) => { use IntWidth::*; @@ -534,14 +544,14 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let elem_addr = mem.deref_usize(addr); let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize); - list_to_ast(env, mem, elem_addr, len, elem_layout, content) + list_to_ast(env, mem, elem_addr, len, elem_layout, raw_content) } (_, Layout::Builtin(Builtin::Str)) => { let string = mem.deref_str(addr); let arena_str = env.arena.alloc_str(string); Expr::Str(StrLiteral::PlainLine(arena_str)) } - (_, Layout::Struct{field_layouts, ..}) => match content { + (_, Layout::Struct{field_layouts, ..}) => match raw_content { Content::Structure(FlatType::Record(fields, _)) => { struct_to_ast(env, mem, addr, *fields) } @@ -566,7 +576,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } }, (_, Layout::RecursivePointer) => { - match (content, when_recursive) { + match (raw_content, when_recursive) { (Content::RecursionVar { structure, opt_name: _, @@ -580,7 +590,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( (_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => { let union_layout = UnionLayout::NonRecursive(union_layouts); - let tags = match content { + let tags = match raw_content { Content::Structure(FlatType::TagUnion(tags, _)) => tags, other => unreachable!("Weird content for nonrecursive Union layout: {:?}", other), }; @@ -614,7 +624,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => { - let (rec_var, tags) = match content { + let (rec_var, tags) = match raw_content { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), _ => unreachable!("any other content would have a different layout"), }; @@ -644,7 +654,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -672,7 +682,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } (_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -706,7 +716,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } } (_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => { - let (rec_var, tags) = match unroll_recursion_var(env, content) { + let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -803,7 +813,8 @@ fn list_to_ast<'a, M: ReplAppMemory>( for index in 0..len { let offset_bytes = index * elem_size; let elem_addr = addr + offset_bytes; - let (newtype_containers, elem_content) = unroll_newtypes(env, elem_content); + let (newtype_containers, _alias_content, elem_content) = + unroll_newtypes_and_aliases(env, elem_content); let expr = addr_to_ast( env, mem, @@ -890,34 +901,20 @@ fn struct_to_ast<'a, M: ReplAppMemory>( let arena = env.arena; let subs = env.subs; let mut output = Vec::with_capacity_in(record_fields.len(), arena); - - let sorted_fields: Vec<_> = Vec::from_iter_in( - record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD), - arena, - ); - let mut layout_cache = LayoutCache::new(env.target_info); - // We recalculate the layouts here because we will have compiled the record so that its fields - // are sorted by descending alignment, and then alphabetic, but the type of the record is - // always only sorted alphabetically. We want to arrange the rendered record in the order of - // the type. - let field_to_layout: MutMap = sorted_fields - .iter() - .map(|(label, field)| { - let layout = layout_cache - .from_var(arena, *field.as_inner(), env.subs) - .unwrap(); - (label.clone(), layout) - }) - .collect(); - if sorted_fields.len() == 1 { + if record_fields.len() == 1 { // this is a 1-field wrapper record around another record or 1-tag tag union - let (label, field) = sorted_fields.into_iter().next().unwrap(); + let (label, field) = record_fields + .sorted_iterator(subs, Variable::EMPTY_RECORD) + .next() + .unwrap(); let inner_content = env.subs.get_content_without_compacting(field.into_inner()); - debug_assert_eq!(field_to_layout.len(), 1); - let inner_layouts = arena.alloc([field_to_layout.into_values().next().unwrap()]); + let field_layout = layout_cache + .from_var(arena, field.into_inner(), env.subs) + .unwrap(); + let inner_layouts = arena.alloc([field_layout]); let loc_expr = &*arena.alloc(Loc { value: addr_to_ast( @@ -944,23 +941,25 @@ fn struct_to_ast<'a, M: ReplAppMemory>( Expr::Record(Collection::with_items(output)) } else { - debug_assert_eq!(sorted_fields.len(), field_to_layout.len()); - // We'll advance this as we iterate through the fields let mut field_addr = addr; - for (label, field) in sorted_fields.into_iter() { - let var = field.into_inner(); - - let content = subs.get_content_without_compacting(var); - let field_layout = field_to_layout.get(&label).unwrap(); + // We recalculate the layouts here because we will have compiled the record so that its fields + // are sorted by descending alignment, and then alphabetic, but the type of the record is + // always only sorted alphabetically. We want to arrange the rendered record in the order of + // the type. + for (label, field) in record_fields.sorted_iterator(subs, Variable::EMPTY_RECORD) { + let content = subs.get_content_without_compacting(field.into_inner()); + let field_layout = layout_cache + .from_var(arena, field.into_inner(), env.subs) + .unwrap(); let loc_expr = &*arena.alloc(Loc { value: addr_to_ast( env, mem, field_addr, - field_layout, + &field_layout, WhenRecursive::Unreachable, content, ), @@ -1036,11 +1035,7 @@ fn bool_to_ast<'a, M: ReplAppMemory>( let loc_tag_expr = { let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; + let tag_expr = Expr::Tag(arena.alloc_str(tag_name)); &*arena.alloc(Loc { value: tag_expr, @@ -1117,19 +1112,9 @@ fn byte_to_ast<'a, M: ReplAppMemory>( FlatType::TagUnion(tags, _) if tags.len() == 1 => { let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - // If this tag union represents a number, skip right to - // returning it as an Expr::Num - if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { - return Expr::Num(env.arena.alloc_str(&value.to_string())); - } - let loc_tag_expr = { let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; + let tag_expr = Expr::Tag(arena.alloc_str(tag_name)); &*arena.alloc(Loc { value: tag_expr, @@ -1183,7 +1168,7 @@ fn byte_to_ast<'a, M: ReplAppMemory>( } } other => { - unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); + unreachable!("Unexpected FlatType {:?} in byte_to_ast", other); } } } @@ -1193,79 +1178,7 @@ fn byte_to_ast<'a, M: ReplAppMemory>( byte_to_ast(env, mem, value, content) } other => { - unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); - } - } -} - -fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> Expr<'a> { - use Content::*; - - let arena = env.arena; - - match content { - Structure(flat_type) => { - match flat_type { - FlatType::Apply(Symbol::NUM_NUM, _) => num_expr, - FlatType::TagUnion(tags, _) => { - // This was a single-tag union that got unwrapped at runtime. - debug_assert_eq!(tags.len(), 1); - - let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); - - // If this tag union represents a number, skip right to - // returning it as an Expr::Num - if let TagName::Private(Symbol::NUM_AT_NUM) = &tag_name { - return num_expr; - } - - let loc_tag_expr = { - let tag_name = &tag_name.as_ident_str(env.interns, env.home); - let tag_expr = if tag_name.starts_with('@') { - Expr::PrivateTag(arena.alloc_str(tag_name)) - } else { - Expr::GlobalTag(arena.alloc_str(tag_name)) - }; - - &*arena.alloc(Loc { - value: tag_expr, - region: Region::zero(), - }) - }; - - let payload = { - // Since this has the layout of a number, there should be - // exactly one payload in this tag. - debug_assert_eq!(payload_vars.len(), 1); - - let var = *payload_vars.iter().next().unwrap(); - let content = env.subs.get_content_without_compacting(var); - - let loc_payload = &*arena.alloc(Loc { - value: num_to_ast(env, num_expr, content), - region: Region::zero(), - }); - - arena.alloc([loc_payload]) - }; - - Expr::Apply(loc_tag_expr, payload, CalledVia::Space) - } - other => { - panic!("Unexpected FlatType {:?} in num_to_ast", other); - } - } - } - Alias(_, _, var, _) => { - let content = env.subs.get_content_without_compacting(*var); - - num_to_ast(env, num_expr, content) - } - RangedNumber(typ, _) => { - num_to_ast(env, num_expr, env.subs.get_content_without_compacting(*typ)) - } - other => { - panic!("Unexpected FlatType {:?} in num_to_ast", other); + unreachable!("Unexpected FlatType {:?} in byte_to_ast", other); } } } diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index efe32c4ca1..a20b20f6f9 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -1,4 +1,5 @@ use bumpalo::Bump; +use roc_load::Threading; use roc_reporting::report::Palette; use std::path::{Path, PathBuf}; @@ -7,7 +8,7 @@ use roc_fmt::annotation::{Newlines, Parens}; use roc_load::{LoadingProblem, MonomorphizedModule}; use roc_parse::ast::Expr; use roc_region::all::LineInfo; -use roc_reporting::report::{can_problem, mono_problem, type_problem, RocDocAllocator}; +use roc_reporting::report::{can_problem, type_problem, RocDocAllocator}; use roc_target::TargetInfo; use crate::eval::ToAstProblem; @@ -61,6 +62,7 @@ pub fn compile_to_mono<'a>( exposed_types, target_info, roc_reporting::report::RenderTarget::ColorTerminal, + Threading::Single, ); let mut loaded = match loaded { @@ -78,7 +80,6 @@ pub fn compile_to_mono<'a>( sources, can_problems, type_problems, - mono_problems, .. } = &mut loaded; @@ -87,9 +88,8 @@ pub fn compile_to_mono<'a>( for (home, (module_path, src)) in sources.iter() { let can_probs = can_problems.remove(home).unwrap_or_default(); let type_probs = type_problems.remove(home).unwrap_or_default(); - let mono_probs = mono_problems.remove(home).unwrap_or_default(); - let error_count = can_probs.len() + type_probs.len() + mono_probs.len(); + let error_count = can_probs.len() + type_probs.len(); if error_count == 0 { continue; @@ -119,15 +119,6 @@ pub fn compile_to_mono<'a>( lines.push(buf); } } - - for problem in mono_probs { - let report = mono_problem(&alloc, &line_info, module_path.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - lines.push(buf); - } } if !lines.is_empty() { diff --git a/repl_test/Cargo.toml b/repl_test/Cargo.toml index 7523407865..4ab8c31292 100644 --- a/repl_test/Cargo.toml +++ b/repl_test/Cargo.toml @@ -12,17 +12,17 @@ lazy_static = "1.4.0" [dev-dependencies] indoc = "1.0.3" strip-ansi-escapes = "0.1.1" -wasmer-wasi = "2.0.0" +wasmer-wasi = "2.2.1" roc_repl_cli = {path = "../repl_cli"} roc_test_utils = {path = "../test_utils"} # Wasmer singlepass compiler only works on x86_64. [target.'cfg(target_arch = "x86_64")'.dev-dependencies] -wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] } +wasmer = { version = "2.2.1", default-features = false, features = ["singlepass", "universal"] } [target.'cfg(not(target_arch = "x86_64"))'.dev-dependencies] -wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } +wasmer = { version = "2.2.1", default-features = false, features = ["cranelift", "universal"] } [features] wasm = [] diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 53b692266c..7e3f2aa4c2 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -56,7 +56,7 @@ fn float_addition() { #[cfg(not(feature = "wasm"))] #[test] fn num_rem() { - expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*"); + expect_success("299 % 10", "9 : Int *"); } #[cfg(not(feature = "wasm"))] @@ -901,33 +901,33 @@ fn parse_problem() { #[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes! #[test] -fn mono_problem() { +fn exhaustiveness_problem() { expect_failure( - r#" + indoc!( + r#" t : [A, B, C] t = A when t is A -> "a" - "#, + "# + ), indoc!( r#" - ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── + ── UNSAFE PATTERN ────────────────────────────────────────────────────────────── - This when does not cover all the possibilities: - - 7│> when t is - 8│> A -> "a" - - Other possibilities include: - - B - C - - I would have to crash if I saw one of those! Add branches for them! - - - Enter an expression, or :help, or :exit/:q."# + This when does not cover all the possibilities: + + 7│> when t is + 8│> A -> "a" + + Other possibilities include: + + B + C + + I would have to crash if I saw one of those! Add branches for them! + "# ), ); } @@ -1019,7 +1019,7 @@ fn opaque_apply() { r#" Age := U32 - $Age 23 + @Age 23 "# ), "23 : Age", @@ -1033,7 +1033,7 @@ fn opaque_apply_polymorphic() { r#" F t u := [ Package t u ] - $F (Package "" { a: "" }) + @F (Package "" { a: "" }) "# ), r#"Package "" { a: "" } : F Str { a : Str }"#, @@ -1047,9 +1047,9 @@ fn opaque_pattern_and_call() { r#" F t u := [ Package t u ] - f = \$F (Package A {}) -> $F (Package {} A) + f = \@F (Package A {}) -> @F (Package {} A) - f ($F (Package A {})) + f (@F (Package A {})) "# ), r#"Package {} A : F {} [ A ]*"#, @@ -1138,3 +1138,42 @@ fn issue_2818() { r" : {} -> List Str", ) } + +#[test] +fn issue_2810_recursive_layout_inside_nonrecursive() { + expect_success( + indoc!( + r#" + Command : [ Command Tool ] + + Job : [ Job Command ] + + Tool : [ SystemTool, FromJob Job ] + + a : Job + a = Job (Command (FromJob (Job (Command SystemTool)))) + a + "# + ), + "Job (Command (FromJob (Job (Command SystemTool)))) : Job", + ) +} + +#[test] +fn render_nullable_unwrapped_passing_through_alias() { + expect_success( + indoc!( + r#" + Deep : [ L DeepList ] + + DeepList : [ Nil, Cons Deep ] + + v : DeepList + v = (Cons (L (Cons (L (Cons (L Nil)))))) + + v + "# + ), + "Cons (L (Cons (L (Cons (L Nil))))) : DeepList", + ) +} diff --git a/repl_wasm/build.rs b/repl_wasm/build.rs index eb820852c8..b050c20673 100644 --- a/repl_wasm/build.rs +++ b/repl_wasm/build.rs @@ -1,6 +1,6 @@ use std::env; use std::ffi::OsStr; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; use roc_builtins::bitcode; @@ -10,7 +10,8 @@ const PRE_LINKED_BINARY: &str = "data/pre_linked_binary.o"; fn main() { println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=src/{}.c", PLATFORM_FILENAME); + let source_path = format!("src/{}.c", PLATFORM_FILENAME); + println!("cargo:rerun-if-changed={}", source_path); // 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 @@ -23,26 +24,38 @@ fn main() { std::fs::create_dir_all("./data").unwrap(); - // Build a pre-linked binary with platform, builtins and all their libc dependencies - // This builds a library file that exports all symbols. It has no linker data but we don't need it. - // See discussion with Luuk de Gram (Zig contributor) - // https://github.com/rtfeldman/roc/pull/2181#pullrequestreview-839608063 - let args = [ - "build-lib", - "-target", - "wasm32-wasi", - "-lc", - "-dynamic", // -dynamic ensures libc code goes into the binary + // Zig can produce *either* an object containing relocations OR an object containing libc code + // But we want both, so we have to compile twice with different flags, then link them + + // Create an object file with relocations + let out_dir = env::var("OUT_DIR").unwrap(); + let platform_obj = build_wasm_platform(&out_dir, &source_path); + + // Compile again to get libc path + let (libc_archive, compiler_rt_obj) = build_wasm_libc_compilerrt(&out_dir, &source_path); + let mut libc_pathbuf = PathBuf::from(&libc_archive); + libc_pathbuf.pop(); + let libc_dir = libc_pathbuf.to_str().unwrap(); + + let args = &[ + "wasm-ld", bitcode::BUILTINS_WASM32_OBJ_PATH, - &format!("src/{}.c", PLATFORM_FILENAME), - &format!("-femit-bin={}", PRE_LINKED_BINARY), + &platform_obj, + &compiler_rt_obj, + "-L", + libc_dir, + "-lc", + "-o", + PRE_LINKED_BINARY, + "--export-all", + "--no-entry", ]; let zig = zig_executable(); // println!("{} {}", zig, args.join(" ")); - run_command(Path::new("."), &zig, args); + run_command(&zig, args); } fn zig_executable() -> String { @@ -52,26 +65,77 @@ fn zig_executable() -> String { } } -fn run_command>(path: P, command_str: &str, args: I) -> String -where - I: IntoIterator, - S: AsRef, -{ +fn build_wasm_platform(out_dir: &str, source_path: &str) -> String { + let platform_obj = format!("{}/{}.o", out_dir, PLATFORM_FILENAME); + + run_command( + &zig_executable(), + &[ + "build-lib", + "-target", + "wasm32-wasi", + "-lc", + source_path, + &format!("-femit-bin={}", &platform_obj), + ], + ); + + platform_obj +} + +fn build_wasm_libc_compilerrt(out_dir: &str, source_path: &str) -> (String, String) { + let zig_cache_dir = format!("{}/zig-cache-wasm32", out_dir); + + run_command( + &zig_executable(), + &[ + "build-lib", + "-dynamic", // ensure libc code is actually generated (not just linked against header) + "-target", + "wasm32-wasi", + "-lc", + source_path, + "-femit-bin=/dev/null", + "--global-cache-dir", + &zig_cache_dir, + ], + ); + + ( + run_command("find", &[&zig_cache_dir, "-name", "libc.a"]), + run_command("find", &[&zig_cache_dir, "-name", "compiler_rt.o"]), + ) +} + +fn run_command(command_str: &str, args: &[&str]) -> String { let output_result = Command::new(OsStr::new(&command_str)) - .current_dir(path) + .current_dir(Path::new(".")) .args(args) .output(); + + let fail = |err: String| { + panic!( + "\n\nFailed command:\n\t{} {}\n\n{}", + command_str, + args.join(" "), + err + ); + }; + match output_result { Ok(output) => match output.status.success() { - true => std::str::from_utf8(&output.stdout).unwrap().to_string(), + true => std::str::from_utf8(&output.stdout) + .unwrap() + .trim() + .to_string(), false => { let error_str = match std::str::from_utf8(&output.stderr) { Ok(stderr) => stderr.to_string(), Err(_) => format!("Failed to run \"{}\"", command_str), }; - panic!("{} failed: {}", command_str, error_str); + fail(error_str) } }, - Err(reason) => panic!("{} failed: {}", command_str, reason), + Err(reason) => fail(reason.to_string()), } } diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs index 7ca51ebc57..e58df978bf 100644 --- a/repl_wasm/src/repl.rs +++ b/repl_wasm/src/repl.rs @@ -12,7 +12,7 @@ use roc_repl_eval::{ }; use roc_reporting::report::DEFAULT_PALETTE_HTML; use roc_target::TargetInfo; -use roc_types::pretty_print::{content_to_string, name_all_type_vars}; +use roc_types::pretty_print::name_and_print_var; use crate::{js_create_app, js_get_result_and_memory, js_run_app}; @@ -184,9 +184,8 @@ pub async fn entrypoint_from_js(src: String) -> Result { let main_fn_var = *main_fn_var; // pretty-print the expr type string for later. - name_all_type_vars(main_fn_var, &mut subs); + let expr_type_str = name_and_print_var(main_fn_var, &mut subs, module_id, &interns); let content = subs.get_content_without_compacting(main_fn_var); - let expr_type_str = content_to_string(content, &subs, module_id, &interns); let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { Some(layout) => *layout, diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index 63e16ad7c3..9ca5360dc6 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -15,7 +15,6 @@ roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_can = { path = "../compiler/can" } roc_solve = { path = "../compiler/solve" } -roc_mono = { path = "../compiler/mono" } ven_pretty = { path = "../vendor/pretty" } distance = "0.4.0" bumpalo = { version = "3.8.0", features = ["collections"] } @@ -30,4 +29,3 @@ roc_target = { path = "../compiler/roc_target" } roc_test_utils = { path = "../test_utils" } pretty_assertions = "1.0.0" indoc = "1.0.3" -tempfile = "3.2.0" diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 9d755bee9c..0e1a9012df 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -40,12 +40,11 @@ const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS"; const INVALID_EXTENSION_TYPE: &str = "INVALID_EXTENSION_TYPE"; const ABILITY_HAS_TYPE_VARIABLES: &str = "ABILITY HAS TYPE VARIABLES"; const HAS_CLAUSE_IS_NOT_AN_ABILITY: &str = "HAS CLAUSE IS NOT AN ABILITY"; -const ALIAS_USES_ABILITY: &str = "ALIAS USES ABILITY"; const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE"; const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAUSE"; -const ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE: &str = "ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE"; const ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULTIPLE VARIABLES"; const ABILITY_NOT_ON_TOPLEVEL: &str = "ABILITY NOT ON TOP-LEVEL"; +const SPECIALIZATION_NOT_ON_TOPLEVEL: &str = "SPECIALIZATION NOT ON TOP-LEVEL"; const ABILITY_USED_AS_TYPE: &str = "ABILITY USED AS TYPE"; pub fn can_problem<'b>( @@ -201,7 +200,7 @@ pub fn can_problem<'b>( WhenBranch => unreachable!("all patterns are allowed in a When"), }; - let suggestion = vec![ + let suggestion = [ alloc.reflow( "Patterns like this don't cover all possible shapes of the input type. Use a ", ), @@ -603,37 +602,6 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } - Problem::AliasUsesAbility { - loc_name: Loc { - region, - value: name, - }, - ability, - } => { - doc = alloc.stack([ - alloc.concat([ - alloc.reflow("The definition of the "), - alloc.symbol_unqualified(name), - alloc.reflow(" aliases references the ability "), - alloc.symbol_unqualified(ability), - alloc.reflow(":"), - ]), - alloc.region(lines.convert_region(region)), - alloc.concat([ - alloc.reflow("Abilities are not types, but you can add an ability constraint to a type variable "), - alloc.type_variable("a".into()), - alloc.reflow(" by writing"), - ]), - alloc.type_block(alloc.concat([ - alloc.reflow("| a has "), - alloc.symbol_unqualified(ability), - ])), - alloc.reflow(" at the end of the type."), - ]); - title = ALIAS_USES_ABILITY.to_string(); - severity = Severity::RuntimeError; - } - Problem::IllegalHasClause { region } => { doc = alloc.stack([ alloc.concat([ @@ -716,35 +684,11 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } - Problem::AbilityMemberBindsExternalAbility { - member, - ability, - region, - } => { - doc = alloc.stack([ - alloc.concat([ - alloc.reflow("The definition of the ability member "), - alloc.symbol_unqualified(member), - alloc.reflow(" includes a has clause binding an ability it is not a part of:"), - ]), - alloc.region(lines.convert_region(region)), - alloc.reflow("Currently, ability members can only bind variables to the ability they are a part of."), - alloc.concat([ - alloc.hint(""), - alloc.reflow("Did you mean to bind the "), - alloc.symbol_unqualified(ability), - alloc.reflow(" ability instead?"), - ]), - ]); - title = ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE.to_string(); - severity = Severity::RuntimeError; - } - Problem::AbilityNotOnToplevel { region } => { - doc = alloc.stack(vec![ - alloc.concat(vec![alloc.reflow( - "This ability definition is not on the top-level of a module:", - )]), + doc = alloc.stack([ + alloc + .concat([alloc + .reflow("This ability definition is not on the top-level of a module:")]), alloc.region(lines.convert_region(region)), alloc.reflow("Abilities can only be defined on the top-level of a Roc module."), ]); @@ -753,8 +697,8 @@ pub fn can_problem<'b>( } Problem::AbilityUsedAsType(suggested_var_name, ability, region) => { - doc = alloc.stack(vec![ - alloc.concat(vec![ + doc = alloc.stack([ + alloc.concat([ alloc.reflow("You are attempting to use the ability "), alloc.symbol_unqualified(ability), alloc.reflow(" as a type directly:"), @@ -768,7 +712,7 @@ pub fn can_problem<'b>( .append(alloc.reflow("Perhaps you meant to include a ")) .append(alloc.keyword("has")) .append(alloc.reflow(" annotation, like")), - alloc.type_block(alloc.concat(vec![ + alloc.type_block(alloc.concat([ alloc.type_variable(suggested_var_name), alloc.space(), alloc.keyword("has"), @@ -779,6 +723,19 @@ pub fn can_problem<'b>( title = ABILITY_USED_AS_TYPE.to_string(); severity = Severity::RuntimeError; } + Problem::NestedSpecialization(member, region) => { + doc = alloc.stack([ + alloc.concat([ + alloc.reflow("This specialization of the "), + alloc.symbol_unqualified(member), + alloc.reflow(" ability member is in a nested scope:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.reflow("Specializations can only be defined on the top-level of a module."), + ]); + title = SPECIALIZATION_NOT_ON_TOPLEVEL.to_string(); + severity = Severity::Warning; + } }; Report { @@ -925,13 +882,10 @@ fn to_bad_ident_expr_report<'b>( ]) } - BadPrivateTag(pos) | BadOpaqueRef(pos) => { + BadOpaqueRef(pos) => { use BadIdentNext::*; - let kind = if matches!(bad_ident, BadPrivateTag(..)) { - "a private tag" - } else { - "an opaque reference" - }; + let kind = "an opaque reference"; + match what_is_next(alloc.src_lines, lines.convert_pos(pos)) { LowercaseAccess(width) => { let region = Region::new(pos, pos.bump_column(width)); @@ -983,7 +937,7 @@ fn to_bad_ident_expr_report<'b>( alloc.reflow(r"But after the "), alloc.keyword("@"), alloc.reflow(r" symbol I found a lowercase letter. "), - alloc.reflow(r"All tag names (global and private)"), + alloc.reflow(r"All opaque references "), alloc.reflow(r" must start with an uppercase letter, like "), alloc.parser_suggestion("@UUID"), alloc.reflow(" or "), @@ -1256,9 +1210,9 @@ fn pretty_runtime_error<'b>( EmptySingleQuote | MultipleCharsInSingleQuote | Unknown | BadIdent(_) => { alloc.nil() } - QualifiedIdentifier => alloc.tip().append( - alloc.reflow("In patterns, only private and global tags can be qualified"), - ), + QualifiedIdentifier => alloc + .tip() + .append(alloc.reflow("In patterns, only tags can be qualified")), }; doc = alloc.stack([ diff --git a/reporting/src/error/mod.rs b/reporting/src/error/mod.rs index 2bf7e77288..88379cf382 100644 --- a/reporting/src/error/mod.rs +++ b/reporting/src/error/mod.rs @@ -1,4 +1,3 @@ pub mod canonicalize; -pub mod mono; pub mod parse; pub mod r#type; diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs deleted file mode 100644 index 7ca6608c7d..0000000000 --- a/reporting/src/error/mono.rs +++ /dev/null @@ -1,237 +0,0 @@ -use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; -use roc_module::ident::TagName; -use roc_region::all::LineInfo; -use std::path::PathBuf; -use ven_pretty::DocAllocator; - -pub fn mono_problem<'b>( - alloc: &'b RocDocAllocator<'b>, - lines: &LineInfo, - filename: PathBuf, - problem: roc_mono::ir::MonoProblem, -) -> Report<'b> { - use roc_exhaustive::Context::*; - use roc_exhaustive::Error::*; - use roc_mono::ir::MonoProblem::*; - - match problem { - PatternProblem(Incomplete(region, context, missing)) => match context { - BadArg => { - let doc = alloc.stack([ - alloc.reflow("This pattern does not cover all the possibilities:"), - alloc.region(lines.convert_region(region)), - alloc.reflow("Other possibilities include:"), - unhandled_patterns_to_doc_block(alloc, missing), - alloc.concat([ - alloc.reflow( - "I would have to crash if I saw one of those! \ - So rather than pattern matching in function arguments, put a ", - ), - alloc.keyword("when"), - alloc.reflow(" in the function body to account for all possibilities."), - ]), - ]); - - Report { - filename, - title: "UNSAFE PATTERN".to_string(), - doc, - severity: Severity::RuntimeError, - } - } - BadDestruct => { - let doc = alloc.stack([ - alloc.reflow("This pattern does not cover all the possibilities:"), - alloc.region(lines.convert_region(region)), - alloc.reflow("Other possibilities include:"), - unhandled_patterns_to_doc_block(alloc, missing), - alloc.concat([ - alloc.reflow( - "I would have to crash if I saw one of those! \ - You can use a binding to deconstruct a value if there is only ONE possibility. \ - Use a " - ), - alloc.keyword("when"), - alloc.reflow(" to account for all possibilities."), - ]), - ]); - - Report { - filename, - title: "UNSAFE PATTERN".to_string(), - doc, - severity: Severity::RuntimeError, - } - } - BadCase => { - let doc = alloc.stack([ - alloc.concat([ - alloc.reflow("This "), - alloc.keyword("when"), - alloc.reflow(" does not cover all the possibilities:"), - ]), - alloc.region(lines.convert_region(region)), - alloc.reflow("Other possibilities include:"), - unhandled_patterns_to_doc_block(alloc, missing), - alloc.reflow( - "I would have to crash if I saw one of those! \ - Add branches for them!", - ), - // alloc.hint().append(alloc.reflow("or use a hole.")), - ]); - - Report { - filename, - title: "UNSAFE PATTERN".to_string(), - doc, - severity: Severity::RuntimeError, - } - } - }, - PatternProblem(Redundant { - overall_region, - branch_region, - index, - }) => { - let doc = alloc.stack([ - alloc.concat([ - alloc.reflow("The "), - alloc.string(index.ordinal()), - alloc.reflow(" pattern is redundant:"), - ]), - alloc.region_with_subregion( - lines.convert_region(overall_region), - lines.convert_region(branch_region), - ), - alloc.reflow( - "Any value of this shape will be handled by \ - a previous pattern, so this one should be removed.", - ), - ]); - - Report { - filename, - title: "REDUNDANT PATTERN".to_string(), - doc, - severity: Severity::Warning, - } - } - } -} - -pub fn unhandled_patterns_to_doc_block<'b>( - alloc: &'b RocDocAllocator<'b>, - patterns: Vec, -) -> RocDocBuilder<'b> { - alloc - .vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v))) - .indent(4) - .annotate(Annotation::TypeBlock) -} - -fn pattern_to_doc<'b>( - alloc: &'b RocDocAllocator<'b>, - pattern: roc_exhaustive::Pattern, -) -> RocDocBuilder<'b> { - pattern_to_doc_help(alloc, pattern, false) -} - -const AFTER_TAG_INDENT: &str = " "; - -fn pattern_to_doc_help<'b>( - alloc: &'b RocDocAllocator<'b>, - pattern: roc_exhaustive::Pattern, - in_type_param: bool, -) -> RocDocBuilder<'b> { - use roc_exhaustive::Literal::*; - use roc_exhaustive::Pattern::*; - use roc_exhaustive::RenderAs; - - match pattern { - Anything => alloc.text("_"), - Literal(l) => match l { - Int(i) => alloc.text(i.to_string()), - U128(i) => alloc.text(i.to_string()), - Bit(true) => alloc.text("True"), - Bit(false) => alloc.text("False"), - Byte(b) => alloc.text(b.to_string()), - Float(f) => alloc.text(f.to_string()), - Decimal(d) => alloc.text(d.to_string()), - Str(s) => alloc.string(s.into()), - }, - Ctor(union, tag_id, args) => { - match union.render_as { - RenderAs::Guard => { - // #Guard - debug_assert_eq!( - union.alternatives[tag_id.0 as usize].name, - TagName::Global("#Guard".into()) - ); - debug_assert!(args.len() == 2); - let tag = pattern_to_doc_help(alloc, args[1].clone(), in_type_param); - alloc.concat([ - tag, - alloc.text(AFTER_TAG_INDENT), - alloc.text("(note the lack of an "), - alloc.keyword("if"), - alloc.text(" clause)"), - ]) - } - RenderAs::Record(field_names) => { - let mut arg_docs = Vec::with_capacity(args.len()); - - for (label, v) in field_names.into_iter().zip(args.into_iter()) { - match &v { - Anything => { - arg_docs.push(alloc.text(label.to_string())); - } - Literal(_) | Ctor(_, _, _) => { - arg_docs.push( - alloc - .text(label.to_string()) - .append(alloc.reflow(": ")) - .append(pattern_to_doc_help(alloc, v, false)), - ); - } - } - } - - alloc - .text("{ ") - .append(alloc.intersperse(arg_docs, alloc.reflow(", "))) - .append(" }") - } - RenderAs::Tag | RenderAs::Opaque => { - let has_args = !args.is_empty(); - let arg_docs = args - .into_iter() - .map(|v| pattern_to_doc_help(alloc, v, true)); - - let tag = &union.alternatives[tag_id.0 as usize]; - let tag_name = match union.render_as { - RenderAs::Tag => alloc.tag_name(tag.name.clone()), - RenderAs::Opaque => match tag.name { - TagName::Private(opaque) => alloc.wrapped_opaque_name(opaque), - _ => unreachable!(), - }, - _ => unreachable!(), - }; - - // We assume the alternatives are sorted. If not, this assert will trigger - debug_assert!(tag_id == tag.tag_id); - - let docs = std::iter::once(tag_name).chain(arg_docs); - - if in_type_param && has_args { - alloc - .text("(") - .append(alloc.intersperse(docs, alloc.space())) - .append(")") - } else { - alloc.intersperse(docs, alloc.space()) - } - } - } - } - } -} diff --git a/reporting/src/error/parse.rs b/reporting/src/error/parse.rs index b03c5d0826..12e0ea1d9c 100644 --- a/reporting/src/error/parse.rs +++ b/reporting/src/error/parse.rs @@ -38,17 +38,6 @@ fn hint_for_tag_name<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { ]) } -fn hint_for_private_tag_name<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { - alloc.concat([ - alloc.hint("Private tag names "), - alloc.reflow("start with an `@` symbol followed by an uppercase letter, like "), - alloc.parser_suggestion("@UID"), - alloc.text(" or "), - alloc.parser_suggestion("@SecretKey"), - alloc.text("."), - ]) -} - fn record_patterns_look_like<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { alloc.concat([ alloc.reflow(r"Record pattern look like "), @@ -2475,23 +2464,6 @@ fn to_ttag_union_report<'a>( severity: Severity::RuntimeError, } } - Next::Other(Some('@')) => { - let doc = alloc.stack([ - alloc.reflow( - r"I am partway through parsing a tag union type, but I got stuck here:", - ), - alloc.region_with_subregion(lines.convert_region(surroundings), region), - alloc.reflow(r"I was expecting to see a private tag name."), - hint_for_private_tag_name(alloc), - ]); - - Report { - filename, - doc, - title: "WEIRD TAG NAME".to_string(), - severity: Severity::RuntimeError, - } - } _ => { let doc = alloc.stack([ alloc.reflow(r"I am partway through parsing a tag union type, but I got stuck here:"), @@ -3443,6 +3415,25 @@ fn to_imports_report<'a>( } } + EImports::ListEnd(pos) => { + let surroundings = Region::new(start, pos); + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + let doc = alloc.stack([ + alloc.reflow(r"I am partway through parsing a imports list, but I got stuck here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat([alloc.reflow("I am expecting a comma or end of list, like")]), + alloc.parser_suggestion("imports [ Math, Util ]").indent(4), + ]); + + Report { + filename, + doc, + title: "WEIRD IMPORTS".to_string(), + severity: Severity::RuntimeError, + } + } + _ => todo!("unhandled parse error {:?}", parse_problem), } } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 3e0a301298..56674d0559 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -1,6 +1,7 @@ use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{HumanIndex, MutSet, SendMap}; +use roc_exhaustive::CtorName; use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -16,6 +17,12 @@ use ven_pretty::DocAllocator; const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; +const OPAQUE_NUM_SYMBOLS: &[Symbol] = &[ + Symbol::NUM_NUM, + Symbol::NUM_INTEGER, + Symbol::NUM_FLOATINGPOINT, +]; + pub fn type_problem<'b>( alloc: &'b RocDocAllocator<'b>, lines: &LineInfo, @@ -192,6 +199,7 @@ pub fn type_problem<'b>( }; Some(report) } + Exhaustive(problem) => Some(exhaustive_problem(alloc, lines, filename, problem)), } } @@ -532,12 +540,18 @@ fn to_expr_report<'b>( the_name_text, alloc.text(" definition:"), ]), + RequiredSymbol { .. } => alloc.concat([ + alloc.text("type annotation of "), + the_name_text, + alloc.text(" required symbol:"), + ]), }; let it_is = match annotation_source { TypedIfBranch { index, .. } => format!("The {} branch is", index.ordinal()), TypedWhenBranch { index, .. } => format!("The {} branch is", index.ordinal()), TypedBody { .. } => "The body is".into(), + RequiredSymbol { .. } => "The provided type is".into(), }; let expectation_context = ExpectationContext::Annotation { @@ -631,9 +645,9 @@ fn to_expr_report<'b>( alloc.reflow(" condition to evaluate to a "), alloc.type_str("Bool"), alloc.reflow("—either "), - alloc.global_tag_name("True".into()), + alloc.tag("True".into()), alloc.reflow(" or "), - alloc.global_tag_name("False".into()), + alloc.tag("False".into()), alloc.reflow("."), ]), // Note: Elm has a hint here about truthiness. I think that @@ -670,9 +684,9 @@ fn to_expr_report<'b>( alloc.reflow(" condition to evaluate to a "), alloc.type_str("Bool"), alloc.reflow("—either "), - alloc.global_tag_name("True".into()), + alloc.tag("True".into()), alloc.reflow(" or "), - alloc.global_tag_name("False".into()), + alloc.tag("False".into()), alloc.reflow("."), ]), // Note: Elm has a hint here about truthiness. I think that @@ -708,9 +722,9 @@ fn to_expr_report<'b>( alloc.reflow(" guard condition to evaluate to a "), alloc.type_str("Bool"), alloc.reflow("—either "), - alloc.global_tag_name("True".into()), + alloc.tag("True".into()), alloc.reflow(" or "), - alloc.global_tag_name("False".into()), + alloc.tag("False".into()), alloc.reflow("."), ]), ) @@ -1151,6 +1165,88 @@ fn to_expr_report<'b>( ) } + Reason::WhenBranches => { + let snippet = alloc.region_with_subregion( + lines.convert_region(region), + lines.convert_region(expr_region), + ); + + let this_is = alloc.concat([ + alloc.reflow("The "), + alloc.keyword("when"), + alloc.reflow(" condition is"), + ]); + + let wanted = alloc.reflow("But the branch patterns have type:"); + let details = Some(alloc.concat([ + alloc.reflow("The branches must be cases of the "), + alloc.keyword("when"), + alloc.reflow(" condition's type!"), + ])); + + let lines = [ + alloc.concat([ + alloc.reflow("The branches of this "), + alloc.keyword("when"), + alloc.reflow(" expression don't match the condition:"), + ]), + snippet, + type_comparison( + alloc, + found, + expected_type, + ExpectationContext::WhenCondition, + add_category(alloc, this_is, &category), + wanted, + details, + ), + ]; + + Report { + title: "TYPE MISMATCH".to_string(), + filename, + doc: alloc.stack(lines), + severity: Severity::RuntimeError, + } + } + + Reason::TypedArg { name, arg_index } => { + let name = match name { + Some(n) => alloc.symbol_unqualified(n), + None => alloc.text(" this definition "), + }; + let doc = alloc.stack([ + alloc + .text("The ") + .append(alloc.text(arg_index.ordinal())) + .append(alloc.text(" argument to ")) + .append(name.clone()) + .append(alloc.text(" is weird:")), + alloc.region(lines.convert_region(region)), + pattern_type_comparison( + alloc, + expected_type, + found, + add_category(alloc, alloc.text("The argument matches"), &category), + alloc.concat([ + alloc.text("But the annotation on "), + name, + alloc.text(" says the "), + alloc.text(arg_index.ordinal()), + alloc.text(" argument should be:"), + ]), + vec![], + ), + ]); + + Report { + filename, + title: "TYPE MISMATCH".to_string(), + doc, + severity: Severity::RuntimeError, + } + } + Reason::LowLevelOpArg { op, arg_index } => { panic!( "Compiler bug: argument #{} to low-level operation {:?} was the wrong type!", @@ -1224,7 +1320,10 @@ fn count_arguments(tipe: &ErrorType) -> usize { enum ExpectationContext<'a> { /// An expected type was discovered from a type annotation. Corresponds to /// [`Expected::FromAnnotation`](Expected::FromAnnotation). - Annotation { on: RocDocBuilder<'a> }, + Annotation { + on: RocDocBuilder<'a>, + }, + WhenCondition, /// When we don't know the context, or it's not relevant. Arbitrary, } @@ -1233,6 +1332,7 @@ impl<'a> std::fmt::Debug for ExpectationContext<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ExpectationContext::Annotation { .. } => f.write_str("Annotation"), + ExpectationContext::WhenCondition => f.write_str("WhenCondition"), ExpectationContext::Arbitrary => f.write_str("Arbitrary"), } } @@ -1341,7 +1441,7 @@ fn format_category<'b>( alloc.text(" of type:"), ), Float => ( - alloc.concat([this_is, alloc.text(" a float")]), + alloc.concat([this_is, alloc.text(" a frac")]), alloc.text(" of type:"), ), Str => ( @@ -1380,51 +1480,29 @@ fn format_category<'b>( ), TagApply { - tag_name: TagName::Global(name), + tag_name: TagName::Tag(name), args_count: 0, } => ( alloc.concat([ alloc.text(format!("{}his ", t)), - alloc.global_tag_name(name.to_owned()), + alloc.tag(name.to_owned()), if name.as_str() == "True" || name.as_str() == "False" { alloc.text(" boolean") } else { - alloc.text(" global tag") + alloc.text(" tag") }, ]), alloc.text(" has the type:"), ), - TagApply { - tag_name: TagName::Private(name), - args_count: 0, - } => ( - alloc.concat([ - alloc.text(format!("{}his ", t)), - alloc.private_tag_name(*name), - alloc.text(" private tag"), - ]), - alloc.text(" has the type:"), - ), TagApply { - tag_name: TagName::Global(name), + tag_name: TagName::Tag(name), args_count: _, } => ( alloc.concat([ alloc.text(format!("{}his ", t)), - alloc.global_tag_name(name.to_owned()), - alloc.text(" global tag application"), - ]), - alloc.text(" has the type:"), - ), - TagApply { - tag_name: TagName::Private(name), - args_count: _, - } => ( - alloc.concat([ - alloc.text("This "), - alloc.private_tag_name(*name), - alloc.text(" private tag application"), + alloc.tag(name.to_owned()), + alloc.text(" tag application"), ]), alloc.text(" has the type:"), ), @@ -1494,7 +1572,7 @@ fn format_category<'b>( alloc.concat([this_is, alloc.text(" an uniqueness attribute")]), alloc.text(" of type:"), ), - Storage(_file, _line) => ( + Storage(..) | Unknown => ( alloc.concat([this_is, alloc.text(" a value")]), alloc.text(" of type:"), ), @@ -1506,6 +1584,10 @@ fn format_category<'b>( alloc.concat([this_is, alloc.text(" a declared specialization")]), alloc.text(" of type:"), ), + Expect => ( + alloc.concat([this_is, alloc.text(" an expectation")]), + alloc.text(" of type:"), + ), } } @@ -1594,14 +1676,17 @@ fn to_pattern_report<'b>( severity: Severity::RuntimeError, } } - PReason::WhenMatch { index } => { - if index == HumanIndex::FIRST { - let doc = alloc.stack([ + PReason::WhenMatch { index, sub_pattern } => { + let doc = match (index, sub_pattern) { + (HumanIndex::FIRST, HumanIndex::FIRST) => alloc.stack([ alloc .text("The 1st pattern in this ") .append(alloc.keyword("when")) .append(alloc.text(" is causing a mismatch:")), - alloc.region(lines.convert_region(region)), + alloc.region_with_subregion( + lines.convert_region(region), + lines.convert_region(expr_region), + ), pattern_type_comparison( alloc, found, @@ -1620,44 +1705,55 @@ fn to_pattern_report<'b>( ]), vec![], ), - ]); + ]), + (index, sub_pattern) => { + let (first, index) = match sub_pattern { + HumanIndex::FIRST => { + let doc = alloc + .string(format!("The {} pattern in this ", index.ordinal())) + .append(alloc.keyword("when")) + .append(alloc.text(" does not match the previous ones:")); + (doc, index) + } - Report { - filename, - title: "TYPE MISMATCH".to_string(), - doc, - severity: Severity::RuntimeError, - } - } else { - let doc = alloc.stack([ - alloc - .string(format!("The {} pattern in this ", index.ordinal())) - .append(alloc.keyword("when")) - .append(alloc.text(" does not match the previous ones:")), - alloc.region(lines.convert_region(region)), - pattern_type_comparison( - alloc, - found, - expected_type, - add_pattern_category( - alloc, - alloc.string(format!( - "The {} pattern is trying to match", - index.ordinal() - )), - &category, + _ => { + let doc = alloc.string(format!( + "The {} pattern in this branch does not match the previous ones:", + sub_pattern.ordinal() + )); + (doc, sub_pattern) + } + }; + + alloc.stack([ + first, + alloc.region_with_subregion( + lines.convert_region(region), + lines.convert_region(expr_region), ), - alloc.text("But all the previous branches match:"), - vec![], - ), - ]); - - Report { - filename, - title: "TYPE MISMATCH".to_string(), - doc, - severity: Severity::RuntimeError, + pattern_type_comparison( + alloc, + found, + expected_type, + add_pattern_category( + alloc, + alloc.string(format!( + "The {} pattern is trying to match", + index.ordinal() + )), + &category, + ), + alloc.text("But all the previous branches match:"), + vec![], + ), + ]) } + }; + Report { + filename, + title: "TYPE MISMATCH".to_string(), + doc, + severity: Severity::RuntimeError, } } PReason::TagArg { .. } | PReason::PatternGuard => { @@ -2255,7 +2351,10 @@ fn to_diff<'b>( } } - (Alias(_, _, _, AliasKind::Opaque), _) | (_, Alias(_, _, _, AliasKind::Opaque)) => { + (Alias(sym, _, _, AliasKind::Opaque), _) | (_, Alias(sym, _, _, AliasKind::Opaque)) + // Skip the hint for numbers; it's not as useful as saying "this type is not a number" + if !OPAQUE_NUM_SYMBOLS.contains(&sym) => + { let (left, left_able) = to_doc(alloc, Parens::InFn, type1); let (right, right_able) = to_doc(alloc, Parens::InFn, type2); @@ -2329,8 +2428,8 @@ fn to_diff<'b>( _ => false, }; let is_float = |t: &ErrorType| match t { - ErrorType::Type(Symbol::NUM_FLOAT, _) => true, - ErrorType::Alias(Symbol::NUM_FLOAT, _, _, _) => true, + ErrorType::Type(Symbol::NUM_FRAC, _) => true, + ErrorType::Alias(Symbol::NUM_FRAC, _, _, _) => true, ErrorType::Type(Symbol::NUM_NUM, args) => { matches!( @@ -2636,22 +2735,24 @@ fn diff_tag_union<'b>( let all_fields_shared = left.peek().is_none() && right.peek().is_none(); let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) { - (true, true) => match left.peek() { - Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo( + (true, true) => match (left.peek(), right.peek()) { + (Some((f, _, _, _)), Some(_)) => Status::Different(vec![Problem::TagTypo( f.clone(), fields2.keys().cloned().collect(), )]), - None => { - if right.peek().is_none() { - Status::Similar - } else { - let result = - Status::Different(vec![Problem::TagsMissing(right.map(|v| v.0).collect())]); - // we just used the values in `right`. in - right = right_keys.iter().map(to_unknown_docs).peekable(); - result - } + (Some(_), None) => { + let status = + Status::Different(vec![Problem::TagsMissing(left.map(|v| v.0).collect())]); + left = left_keys.iter().map(to_unknown_docs).peekable(); + status } + (None, Some(_)) => { + let status = + Status::Different(vec![Problem::TagsMissing(right.map(|v| v.0).collect())]); + right = right_keys.iter().map(to_unknown_docs).peekable(); + status + } + (None, None) => Status::Similar, }, (false, true) => match left.peek() { Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo( @@ -3243,14 +3344,41 @@ fn type_problem_to_pretty<'b>( alloc.reflow("You can convert between "), alloc.type_str("Int"), alloc.reflow(" and "), - alloc.type_str("Float"), + alloc.type_str("Frac"), alloc.reflow(" using functions like "), - alloc.symbol_qualified(Symbol::NUM_TO_FLOAT), + alloc.symbol_qualified(Symbol::NUM_TO_FRAC), alloc.reflow(" and "), alloc.symbol_qualified(Symbol::NUM_ROUND), alloc.reflow("."), ])), + (TagsMissing(missing), ExpectationContext::WhenCondition) => match missing.split_last() { + None => alloc.nil(), + Some(split) => { + let missing_tags = match split { + (f1, []) => alloc.tag_name(f1.clone()).append(alloc.reflow(" tag.")), + (last, init) => alloc + .intersperse(init.iter().map(|v| alloc.tag_name(v.clone())), ", ") + .append(alloc.reflow(" and ")) + .append(alloc.tag_name(last.clone())) + .append(alloc.reflow(" tags.")), + }; + + let tip1 = alloc + .tip() + .append(alloc.reflow("Looks like the branches are missing coverage of the ")) + .append(missing_tags); + + let tip2 = alloc + .tip() + .append(alloc.reflow("Maybe you need to add a catch-all branch, like ")) + .append(alloc.keyword("_")) + .append(alloc.reflow("?")); + + alloc.stack([tip1, tip2]) + } + }, + (TagsMissing(missing), _) => match missing.split_last() { None => alloc.nil(), Some((f1, [])) => { @@ -3404,3 +3532,248 @@ fn report_record_field_typo<'b>( severity: Severity::RuntimeError, } } + +fn exhaustive_problem<'a>( + alloc: &'a RocDocAllocator<'a>, + lines: &LineInfo, + filename: PathBuf, + problem: roc_exhaustive::Error, +) -> Report<'a> { + use roc_exhaustive::Context::*; + use roc_exhaustive::Error::*; + + match problem { + Incomplete(region, context, missing) => match context { + BadArg => { + let doc = alloc.stack([ + alloc.reflow("This pattern does not cover all the possibilities:"), + alloc.region(lines.convert_region(region)), + alloc.reflow("Other possibilities include:"), + unhandled_patterns_to_doc_block(alloc, missing), + alloc.concat([ + alloc.reflow( + "I would have to crash if I saw one of those! \ + So rather than pattern matching in function arguments, put a ", + ), + alloc.keyword("when"), + alloc.reflow(" in the function body to account for all possibilities."), + ]), + ]); + + Report { + filename, + title: "UNSAFE PATTERN".to_string(), + doc, + severity: Severity::RuntimeError, + } + } + BadDestruct => { + let doc = alloc.stack([ + alloc.reflow("This pattern does not cover all the possibilities:"), + alloc.region(lines.convert_region(region)), + alloc.reflow("Other possibilities include:"), + unhandled_patterns_to_doc_block(alloc, missing), + alloc.concat([ + alloc.reflow( + "I would have to crash if I saw one of those! \ + You can use a binding to deconstruct a value if there is only ONE possibility. \ + Use a " + ), + alloc.keyword("when"), + alloc.reflow(" to account for all possibilities."), + ]), + ]); + + Report { + filename, + title: "UNSAFE PATTERN".to_string(), + doc, + severity: Severity::RuntimeError, + } + } + BadCase => { + let doc = alloc.stack([ + alloc.concat([ + alloc.reflow("This "), + alloc.keyword("when"), + alloc.reflow(" does not cover all the possibilities:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.reflow("Other possibilities include:"), + unhandled_patterns_to_doc_block(alloc, missing), + alloc.reflow( + "I would have to crash if I saw one of those! \ + Add branches for them!", + ), + // alloc.hint().append(alloc.reflow("or use a hole.")), + ]); + + Report { + filename, + title: "UNSAFE PATTERN".to_string(), + doc, + severity: Severity::RuntimeError, + } + } + }, + Redundant { + overall_region, + branch_region, + index, + } => { + let doc = alloc.stack([ + alloc.concat([ + alloc.reflow("The "), + alloc.string(index.ordinal()), + alloc.reflow(" pattern is redundant:"), + ]), + alloc.region_with_subregion( + lines.convert_region(overall_region), + lines.convert_region(branch_region), + ), + alloc.reflow( + "Any value of this shape will be handled by \ + a previous pattern, so this one should be removed.", + ), + ]); + + Report { + filename, + title: "REDUNDANT PATTERN".to_string(), + doc, + severity: Severity::Warning, + } + } + } +} + +pub fn unhandled_patterns_to_doc_block<'b>( + alloc: &'b RocDocAllocator<'b>, + patterns: Vec, +) -> RocDocBuilder<'b> { + alloc + .vcat( + patterns + .into_iter() + .map(|v| exhaustive_pattern_to_doc(alloc, v)), + ) + .indent(4) + .annotate(Annotation::TypeBlock) +} + +fn exhaustive_pattern_to_doc<'b>( + alloc: &'b RocDocAllocator<'b>, + pattern: roc_exhaustive::Pattern, +) -> RocDocBuilder<'b> { + pattern_to_doc_help(alloc, pattern, false) +} + +const AFTER_TAG_INDENT: &str = " "; + +fn pattern_to_doc_help<'b>( + alloc: &'b RocDocAllocator<'b>, + pattern: roc_exhaustive::Pattern, + in_type_param: bool, +) -> RocDocBuilder<'b> { + use roc_can::exhaustive::{GUARD_CTOR, NONEXHAUSIVE_CTOR}; + use roc_exhaustive::Literal::*; + use roc_exhaustive::Pattern::*; + use roc_exhaustive::RenderAs; + + match pattern { + Anything => alloc.text("_"), + Literal(l) => match l { + Int(i) => alloc.text(i.to_string()), + U128(i) => alloc.text(i.to_string()), + Bit(true) => alloc.text("True"), + Bit(false) => alloc.text("False"), + Byte(b) => alloc.text(b.to_string()), + Float(f) => alloc.text(f.to_string()), + Decimal(d) => alloc.text(d.to_string()), + Str(s) => alloc.string(s.into()), + }, + Ctor(union, tag_id, args) => { + match union.render_as { + RenderAs::Guard => { + // #Guard + debug_assert!(union.alternatives[tag_id.0 as usize] + .name + .is_tag(&TagName::Tag(GUARD_CTOR.into()))); + debug_assert!(args.len() == 2); + let tag = pattern_to_doc_help(alloc, args[1].clone(), in_type_param); + alloc.concat([ + tag, + alloc.text(AFTER_TAG_INDENT), + alloc.text("(note the lack of an "), + alloc.keyword("if"), + alloc.text(" clause)"), + ]) + } + RenderAs::Record(field_names) => { + let mut arg_docs = Vec::with_capacity(args.len()); + + for (label, v) in field_names.into_iter().zip(args.into_iter()) { + match &v { + Anything => { + arg_docs.push(alloc.text(label.to_string())); + } + Literal(_) | Ctor(_, _, _) => { + arg_docs.push( + alloc + .text(label.to_string()) + .append(alloc.reflow(": ")) + .append(pattern_to_doc_help(alloc, v, false)), + ); + } + } + } + + alloc + .text("{ ") + .append(alloc.intersperse(arg_docs, alloc.reflow(", "))) + .append(" }") + } + RenderAs::Tag | RenderAs::Opaque => { + let ctor = &union.alternatives[tag_id.0 as usize]; + match &ctor.name { + CtorName::Tag(TagName::Tag(name)) if name.as_str() == NONEXHAUSIVE_CTOR => { + return pattern_to_doc_help( + alloc, + roc_exhaustive::Pattern::Anything, + in_type_param, + ) + } + _ => {} + } + + let tag_name = match (union.render_as, &ctor.name) { + (RenderAs::Tag, CtorName::Tag(tag)) => alloc.tag_name(tag.clone()), + (RenderAs::Opaque, CtorName::Opaque(opaque)) => { + alloc.wrapped_opaque_name(*opaque) + } + _ => unreachable!(), + }; + + let has_args = !args.is_empty(); + let arg_docs = args + .into_iter() + .map(|v| pattern_to_doc_help(alloc, v, true)); + + // We assume the alternatives are sorted. If not, this assert will trigger + debug_assert!(tag_id == ctor.tag_id); + + let docs = std::iter::once(tag_name).chain(arg_docs); + + if in_type_param && has_args { + alloc + .text("(") + .append(alloc.intersperse(docs, alloc.space())) + .append(")") + } else { + alloc.intersperse(docs, alloc.space()) + } + } + } + } + } +} diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 2f26a35c75..b42c18b3de 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -7,7 +7,6 @@ use std::path::{Path, PathBuf}; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder, Render, RenderAnnotated}; pub use crate::error::canonicalize::can_problem; -pub use crate::error::mono::mono_problem; pub use crate::error::parse::parse_problem; pub use crate::error::r#type::type_problem; @@ -385,26 +384,26 @@ impl<'a> RocDocAllocator<'a> { pub fn tag_name(&'a self, tn: TagName) -> DocBuilder<'a, Self, Annotation> { match tn { - TagName::Global(uppercase) => self.global_tag_name(uppercase), - TagName::Private(symbol) => self.private_tag_name(symbol), - TagName::Closure(_symbol) => unreachable!("closure tags are internal only"), + TagName::Tag(uppercase) => self.tag(uppercase), + TagName::Closure(symbol) => self.symbol_qualified(symbol), + // TagName::Closure(_symbol) => unreachable!("closure tags are internal only"), } } pub fn symbol_unqualified(&'a self, symbol: Symbol) -> DocBuilder<'a, Self, Annotation> { - self.text(format!("{}", symbol.ident_str(self.interns))) + self.text(symbol.as_str(self.interns)) .annotate(Annotation::Symbol) } pub fn symbol_foreign_qualified(&'a self, symbol: Symbol) -> DocBuilder<'a, Self, Annotation> { if symbol.module_id() == self.home || symbol.module_id().is_builtin() { // Render it unqualified if it's in the current module or a builtin - self.text(format!("{}", symbol.ident_str(self.interns))) + self.text(symbol.as_str(self.interns)) .annotate(Annotation::Symbol) } else { self.text(format!( "{}.{}", symbol.module_string(self.interns), - symbol.ident_str(self.interns), + symbol.as_str(self.interns), )) .annotate(Annotation::Symbol) } @@ -413,40 +412,25 @@ impl<'a> RocDocAllocator<'a> { self.text(format!( "{}.{}", symbol.module_string(self.interns), - symbol.ident_str(self.interns), + symbol.as_str(self.interns), )) .annotate(Annotation::Symbol) } - pub fn private_tag_name(&'a self, symbol: Symbol) -> DocBuilder<'a, Self, Annotation> { - if symbol.module_id() == self.home { - // Render it unqualified if it's in the current module. - self.text(format!("{}", symbol.ident_str(self.interns))) - .annotate(Annotation::PrivateTag) - } else { - self.text(format!( - "{}.{}", - symbol.module_string(self.interns), - symbol.ident_str(self.interns), - )) - .annotate(Annotation::PrivateTag) - } - } - - pub fn global_tag_name(&'a self, uppercase: Uppercase) -> DocBuilder<'a, Self, Annotation> { + pub fn tag(&'a self, uppercase: Uppercase) -> DocBuilder<'a, Self, Annotation> { self.text(format!("{}", uppercase)) - .annotate(Annotation::GlobalTag) + .annotate(Annotation::Tag) } pub fn opaque_name(&'a self, opaque: Symbol) -> DocBuilder<'a, Self, Annotation> { let fmt = if opaque.module_id() == self.home { // Render it unqualified if it's in the current module. - format!("{}", opaque.ident_str(self.interns)) + opaque.as_str(self.interns).to_string() } else { format!( "{}.{}", opaque.module_string(self.interns), - opaque.ident_str(self.interns), + opaque.as_str(self.interns), ) }; @@ -456,8 +440,7 @@ impl<'a> RocDocAllocator<'a> { pub fn wrapped_opaque_name(&'a self, opaque: Symbol) -> DocBuilder<'a, Self, Annotation> { debug_assert_eq!(opaque.module_id(), self.home, "Opaque wrappings can only be defined in the same module they're defined in, but this one is defined elsewhere: {:?}", opaque); - // TODO(opaques): $->@ - self.text(format!("${}", opaque.ident_str(self.interns))) + self.text(format!("@{}", opaque.as_str(self.interns))) .annotate(Annotation::Opaque) } @@ -807,8 +790,7 @@ pub enum Annotation { Emphasized, Url, Keyword, - GlobalTag, - PrivateTag, + Tag, RecordField, TypeVariable, Alias, @@ -900,8 +882,7 @@ where Url => { self.write_str("<")?; } - GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion - | TypeVariable + Tag | Keyword | RecordField | Symbol | Typo | TypoSuggestion | TypeVariable if !self.in_type_block && !self.in_code_block => { self.write_str("`")?; @@ -931,8 +912,7 @@ where Url => { self.write_str(">")?; } - GlobalTag | PrivateTag | Keyword | RecordField | Symbol | Typo | TypoSuggestion - | TypeVariable + Tag | Keyword | RecordField | Symbol | Typo | TypoSuggestion | TypeVariable if !self.in_type_block && !self.in_code_block => { self.write_str("`")?; @@ -1024,7 +1004,7 @@ where ParserSuggestion => { self.write_str(self.palette.parser_suggestion)?; } - TypeBlock | GlobalTag | PrivateTag | RecordField => { /* nothing yet */ } + TypeBlock | Tag | RecordField => { /* nothing yet */ } } self.style_stack.push(*annotation); Ok(()) @@ -1042,7 +1022,7 @@ where self.write_str(self.palette.reset)?; } - TypeBlock | GlobalTag | PrivateTag | Opaque | RecordField => { /* nothing yet */ } + TypeBlock | Tag | Opaque | RecordField => { /* nothing yet */ } }, } Ok(()) diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 269e0598ff..4a429c4c5b 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -17,7 +17,7 @@ use roc_problem::can::Problem; use roc_region::all::Loc; use roc_solve::solve::{self, Aliases}; use roc_types::subs::{Content, Subs, VarStore, Variable}; -use roc_types::types::Type; +use roc_types::types::{AliasVar, Type}; use std::hash::Hash; use std::path::{Path, PathBuf}; @@ -35,10 +35,8 @@ pub fn infer_expr( abilities_store: &mut AbilitiesStore, expr_var: Variable, ) -> (Content, Subs) { - let env = solve::Env::default(); let (solved, _) = solve::run( constraints, - &env, problems, subs, aliases, @@ -151,9 +149,14 @@ pub fn can_expr_with<'a>( // rules multiple times unnecessarily. let loc_expr = operator::desugar_expr(arena, &loc_expr); - let mut scope = Scope::new_with_aliases(home, &mut var_store); + let mut scope = Scope::new(home, IdentIds::default()); + + // to skip loading other modules, we populate the scope with the builtin aliases + // that makes the reporting tests much faster + add_aliases(&mut scope, &mut var_store); + let dep_idents = IdentIds::exposed_builtins(0); - let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default()); + let mut env = Env::new(home, &dep_idents, &module_ids); let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, @@ -183,15 +186,8 @@ pub fn can_expr_with<'a>( let constraint = introduce_builtin_imports(&mut constraints, imports, constraint, &mut var_store); - let mut all_ident_ids = MutMap::default(); - - // When pretty printing types, we may need the exposed builtins, - // so include them in the Interns we'll ultimately return. - for (module_id, ident_ids) in IdentIds::exposed_builtins(0) { - all_ident_ids.insert(module_id, ident_ids); - } - - all_ident_ids.insert(home, env.ident_ids); + let mut all_ident_ids = IdentIds::exposed_builtins(1); + all_ident_ids.insert(home, scope.locals.ident_ids); let interns = Interns { module_ids: env.module_ids.clone(), @@ -211,6 +207,37 @@ pub fn can_expr_with<'a>( }) } +fn add_aliases(scope: &mut Scope, var_store: &mut VarStore) { + use roc_types::solved_types::{BuiltinAlias, FreeVars}; + + let solved_aliases = roc_types::builtin_aliases::aliases(); + + for (symbol, builtin_alias) in solved_aliases { + let BuiltinAlias { + region, + vars, + typ, + kind, + } = builtin_alias; + + let mut free_vars = FreeVars::default(); + let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); + + let mut variables = Vec::new(); + // make sure to sort these variables to make them line up with the type arguments + let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); + type_variables.sort(); + for (loc_name, (_, var)) in vars.iter().zip(type_variables) { + variables.push(Loc::at( + loc_name.region, + AliasVar::unbound(loc_name.value.clone(), var), + )); + } + + scope.add_alias(symbol, region, variables, typ, kind); + } +} + #[allow(dead_code)] pub fn mut_map_from_pairs(pairs: I) -> MutMap where diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 8d25660f64..4c4310be3c 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -12,21 +12,16 @@ mod test_reporting { use bumpalo::Bump; use indoc::indoc; use roc_can::abilities::AbilitiesStore; - use roc_can::def::Declaration; - use roc_can::pattern::Pattern; - use roc_load::{self, LoadedModule, LoadingProblem}; + use roc_load::{self, LoadedModule, LoadingProblem, Threading}; use roc_module::symbol::{Interns, ModuleId}; - use roc_mono::ir::{Procs, Stmt, UpdateModeIds}; - use roc_mono::layout::LayoutCache; use roc_region::all::LineInfo; use roc_reporting::report::{ - can_problem, mono_problem, parse_problem, type_problem, RenderTarget, Report, Severity, - ANSI_STYLE_CODES, DEFAULT_PALETTE, + can_problem, parse_problem, type_problem, RenderTarget, Report, Severity, ANSI_STYLE_CODES, + DEFAULT_PALETTE, }; use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_solve::solve; use roc_test_utils::assert_multiline_str_eq; - use roc_types::pretty_print::name_all_type_vars; use roc_types::subs::Subs; use std::path::PathBuf; @@ -96,6 +91,7 @@ mod test_reporting { exposed_types, roc_target::TargetInfo::default_x86_64(), RenderTarget::Generic, + Threading::Single, ); drop(file); @@ -114,7 +110,6 @@ mod test_reporting { String, Vec, Vec, - Vec, ModuleId, Interns, ), @@ -126,81 +121,13 @@ mod test_reporting { mut can_problems, mut type_problems, interns, - mut solved, - exposed_to_host, - mut declarations_by_id, - abilities_store, .. } = result?; let can_problems = can_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default(); - let subs = solved.inner_mut(); - - for var in exposed_to_host.values() { - name_all_type_vars(*var, subs); - } - - let mut mono_problems = Vec::new(); - - // MONO - - if type_problems.is_empty() && can_problems.is_empty() { - let arena = Bump::new(); - - assert!(exposed_to_host.len() == 1); - let (sym, _var) = exposed_to_host.into_iter().next().unwrap(); - - let home_decls = declarations_by_id.remove(&home).unwrap(); - let (loc_expr, var) = home_decls - .into_iter() - .find_map(|decl| match decl { - Declaration::Declare(def) => match def.loc_pattern.value { - Pattern::Identifier(s) if s == sym => Some((def.loc_expr, def.expr_var)), - _ => None, - }, - _ => None, - }) - .expect("No expression to monomorphize found!"); - - // Compile and add all the Procs before adding main - let mut procs = Procs::new_in(&arena); - let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone(); - let mut update_mode_ids = UpdateModeIds::new(); - - // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let target_info = roc_target::TargetInfo::default_x86_64(); - let mut layout_cache = LayoutCache::new(target_info); - let mut mono_env = roc_mono::ir::Env { - arena: &arena, - subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - update_mode_ids: &mut update_mode_ids, - target_info, - // call_specialization_counter=0 is reserved - call_specialization_counter: 1, - abilities_store: &abilities_store, - }; - let _mono_expr = Stmt::new( - &mut mono_env, - loc_expr.value, - var, - &mut procs, - &mut layout_cache, - ); - } - - Ok(( - module_src, - type_problems, - can_problems, - mono_problems, - home, - interns, - )) + Ok((module_src, type_problems, can_problems, home, interns)) } fn list_reports_new(subdir: &str, arena: &Bump, src: &str, finalize_render: F) -> String @@ -215,7 +142,7 @@ mod test_reporting { match infer_expr_help_new(subdir, arena, src) { Err(LoadingProblem::FormattedReport(fail)) => fail, - Ok((module_src, type_problems, can_problems, mono_problems, home, interns)) => { + Ok((module_src, type_problems, can_problems, home, interns)) => { let lines = LineInfo::new(&module_src); let src_lines: Vec<&str> = module_src.split('\n').collect(); let mut reports = Vec::new(); @@ -235,11 +162,6 @@ mod test_reporting { } } - for problem in mono_problems { - let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone()); - reports.push(report); - } - let has_reports = !reports.is_empty(); let doc = alloc @@ -267,14 +189,13 @@ mod test_reporting { ( Vec, Vec, - Vec, ModuleId, Interns, ), ParseErrOut<'a>, > { let CanExprOut { - loc_expr, + loc_expr: _, output, var_store, var, @@ -303,7 +224,7 @@ mod test_reporting { let mut unify_problems = Vec::new(); let mut abilities_store = AbilitiesStore::default(); - let (_content, mut subs) = infer_expr( + let (_content, _subs) = infer_expr( subs, &mut unify_problems, &constraints, @@ -313,45 +234,7 @@ mod test_reporting { var, ); - name_all_type_vars(var, &mut subs); - - let mut mono_problems = Vec::new(); - - // MONO - - if unify_problems.is_empty() && can_problems.is_empty() { - let arena = Bump::new(); - - // Compile and add all the Procs before adding main - let mut procs = Procs::new_in(&arena); - let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone(); - let mut update_mode_ids = UpdateModeIds::new(); - - // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let target_info = roc_target::TargetInfo::default_x86_64(); - let mut layout_cache = LayoutCache::new(target_info); - let mut mono_env = roc_mono::ir::Env { - arena: &arena, - subs: &mut subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - update_mode_ids: &mut update_mode_ids, - target_info, - // call_specialization_counter=0 is reserved - call_specialization_counter: 1, - abilities_store: &abilities_store, - }; - let _mono_expr = Stmt::new( - &mut mono_env, - loc_expr.value, - var, - &mut procs, - &mut layout_cache, - ); - } - - Ok((unify_problems, can_problems, mono_problems, home, interns)) + Ok((unify_problems, can_problems, home, interns)) } fn list_reports(arena: &Bump, src: &str, buf: &mut String, callback: F) @@ -380,7 +263,7 @@ mod test_reporting { callback(doc.pretty(&alloc).append(alloc.line()), buf) } - Ok((type_problems, can_problems, mono_problems, home, interns)) => { + Ok((type_problems, can_problems, home, interns)) => { let mut reports = Vec::new(); let alloc = RocDocAllocator::new(&src_lines, home, &interns); @@ -398,11 +281,6 @@ mod test_reporting { } } - for problem in mono_problems { - let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone()); - reports.push(report); - } - let has_reports = !reports.is_empty(); let doc = alloc @@ -650,13 +528,12 @@ mod test_reporting { Booly : [ Yes, No, Maybe ] - x = - No + x : List Booly + x = [] x "# ), - // Booly is called a "variable" indoc!( r#" ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ @@ -673,26 +550,6 @@ mod test_reporting { Since these aliases have the same name, it's easy to use the wrong one on accident. Give one of them a new name. - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `Booly` is not used anywhere in your code. - - 1│ Booly : [ Yes, No ] - ^^^^^^^^^^^^^^^^^^^ - - If you didn't intend on using `Booly` then remove it so future readers - of your code don't wonder why it is there. - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `Booly` is not used anywhere in your code. - - 3│ Booly : [ Yes, No, Maybe ] - ^^^^^^^^^^^^^^^^^^^^^^^^^^ - - If you didn't intend on using `Booly` then remove it so future readers - of your code don't wonder why it is there. "# ), ) @@ -943,7 +800,7 @@ mod test_reporting { ); let arena = Bump::new(); - let (_type_problems, _can_problems, _mono_problems, home, interns) = + let (_type_problems, _can_problems, home, interns) = infer_expr_help(&arena, src).expect("parse error"); let mut buf = String::new(); @@ -974,7 +831,7 @@ mod test_reporting { ); let arena = Bump::new(); - let (_type_problems, _can_problems, _mono_problems, home, mut interns) = + let (_type_problems, _can_problems, home, mut interns) = infer_expr_help(&arena, src).expect("parse error"); let mut buf = String::new(); @@ -1197,6 +1054,7 @@ mod test_reporting { when 1 is 2 -> "foo" 3 -> {} + _ -> "" "# ), indoc!( @@ -1205,10 +1063,10 @@ mod test_reporting { The 2nd branch of this `when` does not match all the previous branches: - 1│ when 1 is - 2│ 2 -> "foo" - 3│ 3 -> {} - ^^ + 1│ when 1 is + 2│ 2 -> "foo" + 3│> 3 -> {} + 4│ _ -> "" The 2nd branch is a record of type: @@ -1408,7 +1266,7 @@ mod test_reporting { 4│ f Blue ^^^^ - This `Blue` global tag has the type: + This `Blue` tag has the type: [ Blue ]a @@ -1446,9 +1304,9 @@ mod test_reporting { 4│ f (Blue 3.14) ^^^^^^^^^ - This `Blue` global tag application has the type: + This `Blue` tag application has the type: - [ Blue (Float a) ]b + [ Blue (Frac a) ]b But `f` needs the 1st argument to be: @@ -1485,16 +1343,16 @@ mod test_reporting { 2│ x = if True then 3.14 else 4 ^^^^ - The 1st branch is a float of type: + The 1st branch is a frac of type: - Float a + Frac a But the type annotation on `x` says it should be: Int * - Tip: You can convert between Int and Float using functions like - `Num.toFloat` and `Num.round`. + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. "# ), ) @@ -1526,14 +1384,14 @@ mod test_reporting { This `when` expression produces: - Float a + Frac a But the type annotation on `x` says it should be: Int * - Tip: You can convert between Int and Float using functions like - `Num.toFloat` and `Num.round`. + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. "# ), ) @@ -1560,16 +1418,16 @@ mod test_reporting { 2│ x = \_ -> 3.14 ^^^^ - The body is a float of type: + The body is a frac of type: - Float a + Frac a But the type annotation on `x` says it should be: Int * - Tip: You can convert between Int and Float using functions like - `Num.toFloat` and `Num.round`. + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. "# ), ) @@ -1667,18 +1525,20 @@ mod test_reporting { r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2│ {} -> 42 - ^^ + 1│> when 1 is + 2│ {} -> 42 - The first pattern is trying to match record values of type: + The `when` condition is a number of type: + + Num a + + But the branch patterns have type: {}a - But the expression between `when` and `is` has the type: - - Num a + The branches must be cases of the `when` condition's type! "# ), ) @@ -1728,18 +1588,20 @@ mod test_reporting { r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2│ { foo: True } -> 42 - ^^^^^^^^^^^^^ + 1│> when { foo: 1 } is + 2│ { foo: True } -> 42 - The first pattern is trying to match record values of type: + The `when` condition is a record of type: + + { foo : Num a } + + But the branch patterns have type: { foo : [ True ] } - But the expression between `when` and `is` has the type: - - { foo : Num a } + The branches must be cases of the `when` condition's type! "# ), ) @@ -1758,18 +1620,20 @@ mod test_reporting { r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2│ { foo: True } -> 42 - ^^^^^^^^^^^^^ + 1│> when { foo: "" } is + 2│ { foo: True } -> 42 - The first pattern is trying to match record values of type: + The `when` condition is a record of type: + + { foo : Str } + + But the branch patterns have type: { foo : [ True ] } - But the expression between `when` and `is` has the type: - - { foo : Str } + The branches must be cases of the `when` condition's type! "# ), ) @@ -1782,7 +1646,7 @@ mod test_reporting { indoc!( r#" when { foo: 1 } is - { foo: 2 } -> foo + { foo: _ } -> foo "# ), indoc!( @@ -1791,7 +1655,7 @@ mod test_reporting { I cannot find a `foo` value - 2│ { foo: 2 } -> foo + 2│ { foo: _ } -> foo ^^^ Did you mean one of these? @@ -1854,18 +1718,18 @@ mod test_reporting { r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The 2nd pattern in this branch does not match the previous ones: 2│ {} | 1 -> 3 - ^^^^^^ + ^ - The first pattern is trying to match numbers: + The 2nd pattern is trying to match numbers: Num a - But the expression between `when` and `is` has the type: + But all the previous branches match: - { foo : Num a } + {}a "# ), ) @@ -1926,14 +1790,14 @@ mod test_reporting { The body is a record of type: - { x : Float a } + { x : Frac a } But the type annotation says it should be: { x : Int * } - Tip: You can convert between Int and Float using functions like - `Num.toFloat` and `Num.round`. + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. "# ), ) @@ -2069,7 +1933,7 @@ mod test_reporting { report_problem_as( indoc!( r#" - x : { a : Num.Int *, b : Num.Float *, c : Str } + x : { a : Num.Int *, b : Num.Frac *, c : Str } x = { b: 4.0 } x @@ -2081,17 +1945,17 @@ mod test_reporting { Something is off with the body of the `x` definition: - 1│ x : { a : Num.Int *, b : Num.Float *, c : Str } + 1│ x : { a : Num.Int *, b : Num.Frac *, c : Str } 2│ x = { b: 4.0 } ^^^^^^^^^^ The body is a record of type: - { b : Float a } + { b : Frac a } But the type annotation on `x` says it should be: - { a : Int *, b : Float *, c : Str } + { a : Int *, b : Frac *, c : Str } Tip: Looks like the c and a fields are missing. "# @@ -2173,7 +2037,7 @@ mod test_reporting { 2│ f = \_ -> Foo ^^^ - This `Foo` global tag has the type: + This `Foo` tag has the type: [ Foo ]a @@ -2300,7 +2164,7 @@ mod test_reporting { 5│ Ok ^^ - This `Ok` global tag has the type: + This `Ok` tag has the type: [ Ok ]a @@ -2530,7 +2394,7 @@ mod test_reporting { } #[test] - fn int_float() { + fn int_frac() { report_problem_as( indoc!( r#" @@ -2546,16 +2410,16 @@ mod test_reporting { 1│ 0x4 + 3.14 ^^^^ - This argument is a float of type: + This argument is a frac of type: - Float a + Frac a But `add` needs the 2nd argument to be: Num (Integer a) - Tip: You can convert between Int and Float using functions like - `Num.toFloat` and `Num.round`. + Tip: You can convert between Int and Frac using functions like + `Num.toFrac` and `Num.round`. "# ), ) @@ -2734,11 +2598,10 @@ mod test_reporting { [ Left a ] - Tip: Seems like a tag typo. Maybe `Right` should be `Left`? + Tip: Looks like a closed tag union does not have the `Right` tag. - Tip: Can more type annotations be added? Type annotations always help - me give more specific messages, and I think they could help a lot in - this case + Tip: Closed tag unions can't grow, because that might change the size + in memory. Can you use an open tag union? "# ), ) @@ -2927,7 +2790,7 @@ mod test_reporting { Other possibilities include: - { a: Just _, b } + { a: Just _ } I would have to crash if I saw one of those! Add branches for them! "# @@ -3019,7 +2882,7 @@ mod test_reporting { This argument is a record of type: - { y : Float a } + { y : Frac a } But `f` needs the 1st argument to be: @@ -3264,15 +3127,15 @@ mod test_reporting { ── DUPLICATE FIELD NAME ────────────────────────────────── /code/proj/Main.roc ─ This record type defines the `.foo` field twice! - + 1│ a : { foo : Num.I64, bar : {}, foo : Str } ^^^^^^^^^^^^^ ^^^^^^^^^ - + In the rest of the program, I will only use the latter definition: - + 1│ a : { foo : Num.I64, bar : {}, foo : Str } ^^^^^^^^^ - + For clarity, remove the previous `.foo` definitions from this record type. "# @@ -3296,15 +3159,15 @@ mod test_reporting { ── DUPLICATE TAG NAME ──────────────────────────────────── /code/proj/Main.roc ─ This tag union type defines the `Foo` tag twice! - + 1│ a : [ Foo Num.I64, Bar {}, Foo Str ] ^^^^^^^^^^^ ^^^^^^^ - + In the rest of the program, I will only use the latter definition: - + 1│ a : [ Foo Num.I64, Bar {}, Foo Str ] ^^^^^^^ - + For clarity, remove the previous `Foo` definitions from this tag union type. "# @@ -3433,10 +3296,10 @@ mod test_reporting { ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Num` alias expects 1 type argument, but it got 2 instead: - + 1│ a : Num.Num Num.I64 Num.F64 ^^^^^^^^^^^^^^^^^^^^^^^ - + Are there missing parentheses? "# ), @@ -3459,10 +3322,10 @@ mod test_reporting { ── TOO MANY TYPE ARGUMENTS ─────────────────────────────── /code/proj/Main.roc ─ The `Num` alias expects 1 type argument, but it got 2 instead: - + 1│ f : Str -> Num.Num Num.I64 Num.F64 ^^^^^^^^^^^^^^^^^^^^^^^ - + Are there missing parentheses? "# ), @@ -3602,7 +3465,7 @@ mod test_reporting { 4│ x = Cons {} (Cons "foo" Nil) ^^^^^^^^^^^^^^^^^^^^^^^^ - This `Cons` global tag application has the type: + This `Cons` tag application has the type: [ Cons {} [ Cons Str [ Cons {} a, Nil ] as a, Nil ], Nil ] @@ -3643,11 +3506,12 @@ mod test_reporting { 5│ x = ACons 0 (BCons 1 (ACons "foo" BNil )) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - This `ACons` global tag application has the type: + This `ACons` tag application has the type: [ ACons (Num (Integer Signed64)) [ - BCons (Num (Integer Signed64)) [ ACons Str [ BCons I64 a, BNil ], - ANil ], BNil ], ANil ] + BCons (Num (Integer Signed64)) [ ACons Str [ BCons I64 [ + ACons I64 (BList I64 I64), ANil ] as ∞, BNil ], ANil ], BNil ], + ANil ] But the type annotation on `x` says it should be: @@ -4140,18 +4004,20 @@ mod test_reporting { r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 4│ { x, y } -> x + y - ^^^^^^^^ + 3│> when r is + 4│ { x, y } -> x + y - The first pattern is trying to match record values of type: + This `r` value is a: + + { x : I64, y ? I64 } + + But the branch patterns have type: { x : I64, y : I64 } - But the expression between `when` and `is` has the type: - - { x : I64, y ? I64 } + The branches must be cases of the `when` condition's type! Tip: To extract the `.y` field it must be non-optional, but the type says this field is optional. Learn more about optional fields at TODO. @@ -4248,18 +4114,21 @@ mod test_reporting { r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 4│ { x, y : "foo" } -> x + 0 - ^^^^^^^^^^^^^^^^ + 3│> when r is + 4│ { x, y : "foo" } -> x + 0 + 5│ _ -> 0 - The first pattern is trying to match record values of type: + This `r` value is a: + + { x : I64, y : I64 } + + But the branch patterns have type: { x : I64, y : Str } - But the expression between `when` and `is` has the type: - - { x : I64, y : I64 } + The branches must be cases of the `when` condition's type! "# ), ) @@ -4283,18 +4152,21 @@ mod test_reporting { r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 4│ { x, y ? "foo" } -> (\g, _ -> g) x y - ^^^^^^^^^^^^^^^^ + 3│> when r is + 4│ { x, y ? "foo" } -> (\g, _ -> g) x y + 5│ _ -> 0 - The first pattern is trying to match record values of type: + This `r` value is a: + + { x : I64, y ? I64 } + + But the branch patterns have type: { x : I64, y ? Str } - But the expression between `when` and `is` has the type: - - { x : I64, y ? I64 } + The branches must be cases of the `when` condition's type! "# ), ) @@ -4420,7 +4292,7 @@ mod test_reporting { } #[test] - fn qualified_global_tag() { + fn qualified_tag() { report_problem_as( indoc!( r#" @@ -4491,30 +4363,6 @@ mod test_reporting { ) } - #[test] - fn qualified_private_tag() { - report_problem_as( - indoc!( - r#" - @Foo.Bar - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - I am very confused by this expression: - - 1│ @Foo.Bar - ^^^^ - - Looks like a private tag is treated like a module name. Maybe you - wanted a qualified name, like Json.Decode.string? - "# - ), - ) - } - #[test] fn type_annotation_double_colon() { report_problem_as( @@ -5179,36 +5027,6 @@ mod test_reporting { ) } - #[test] - fn invalid_private_tag_name() { - // TODO could do better by pointing out we're parsing a function type - report_problem_as( - indoc!( - r#" - f : [ @Foo Str, @100 I64 ] - f = 0 - - f - "# - ), - indoc!( - r#" - ── WEIRD TAG NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - - I am partway through parsing a tag union type, but I got stuck here: - - 1│ f : [ @Foo Str, @100 I64 ] - ^ - - I was expecting to see a private tag name. - - Hint: Private tag names start with an `@` symbol followed by an - uppercase letter, like @UID or @SecretKey. - "# - ), - ) - } - #[test] fn dict_type_formatting() { // TODO could do better by pointing out we're parsing a function type @@ -5992,32 +5810,7 @@ I need all branches in an `if` to have the same type! } #[test] - fn private_tag_not_uppercase() { - report_problem_as( - indoc!( - r#" - Num.add @foo 23 - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - I am trying to parse a private tag here: - - 1│ Num.add @foo 23 - ^ - - But after the `@` symbol I found a lowercase letter. All tag names - (global and private) must start with an uppercase letter, like @UUID - or @Secrets. - "# - ), - ) - } - - #[test] - fn private_tag_field_access() { + fn opaque_ref_field_access() { report_problem_as( indoc!( r#" @@ -6033,29 +5826,6 @@ I need all branches in an `if` to have the same type! 1│ @UUID.bar ^^^^ - It looks like a record field access on a private tag. - "# - ), - ) - } - - #[test] - fn opaque_ref_field_access() { - report_problem_as( - indoc!( - r#" - $UUID.bar - "# - ), - indoc!( - r#" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - I am very confused by this field access: - - 1│ $UUID.bar - ^^^^ - It looks like a record field access on an opaque reference. "# ), @@ -7093,25 +6863,22 @@ I need all branches in an `if` to have the same type! r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with the body of the `f` definition: + Something is off with the body of the `inner` definition: - 1│ f : a, b, * -> * - 2│ f = \_, _, x2 -> 3│ inner : * -> * 4│ inner = \y -> y - 5│ inner x2 - ^^^^^^^^ + ^ - The type annotation on `f` says this `inner` call should have the type: + The type annotation on `inner` says this `y` value should have the type: * - However, the type of this `inner` call is connected to another type in a + However, the type of this `y` value is connected to another type in a way that isn't reflected in this annotation. Tip: Any connection between types must use a named type variable, not - a `*`! Maybe the annotation on `f` should have a named type variable in - place of the `*`? + a `*`! Maybe the annotation on `inner` should have a named type variable + in place of the `*`? "# ), ) @@ -7210,7 +6977,7 @@ I need all branches in an `if` to have the same type! 6│ isEmpty (Name "boo") ^^^^^^^^^^ - This `Name` global tag application has the type: + This `Name` tag application has the type: [ Name Str ]a @@ -7352,10 +7119,10 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - Job : [ @Job { inputs : List Str } ] + Job : [ Job { inputs : List Str } ] job : { inputs ? List Str } -> Job job = \{ inputs } -> - @Job { inputs } + Job { inputs } job { inputs: [ "build", "test" ] } "# @@ -7390,11 +7157,11 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - Job : [ @Job { inputs : List Job } ] + Job : [ Job { inputs : List Job } ] job : { inputs : List Str } -> Job job = \{ inputs } -> - @Job { inputs } + Job { inputs } job { inputs: [ "build", "test" ] } "# @@ -7407,16 +7174,16 @@ I need all branches in an `if` to have the same type! 3│ job : { inputs : List Str } -> Job 4│ job = \{ inputs } -> - 5│ @Job { inputs } - ^^^^^^^^^^^^^^^ + 5│ Job { inputs } + ^^^^^^^^^^^^^^ - This `@Job` private tag application has the type: + This `Job` tag application has the type: - [ @Job { inputs : List Str } ] + [ Job { inputs : List Str } ] But the type annotation on `job` says it should be: - [ @Job { inputs : List a } ] as a + [ Job { inputs : List a } ] as a "# ), ) @@ -7498,7 +7265,7 @@ I need all branches in an `if` to have the same type! let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; let carets = "^".repeat(number.len() + $suffix.len()); let kind = match $suffix { - "dec"|"f32"|"f64" => "a float", + "dec"|"f32"|"f64" => "a frac", _ => "an integer", }; @@ -7558,11 +7325,6 @@ I need all branches in an `if` to have the same type! typ.get_mut(0..1).unwrap().make_ascii_uppercase(); let bad_suffix = if $suffix == "u8" { "i8" } else { "u8" }; let bad_type = if $suffix == "u8" { "I8" } else { "U8" }; - let carets = "^".repeat(number.len() + $suffix.len()); - let kind = match $suffix { - "dec"|"f32"|"f64" => "floats", - _ => "integers", - }; report_problem_as( &format!(indoc!( @@ -7576,20 +7338,23 @@ I need all branches in an `if` to have the same type! r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2│ {}{} -> 1 - {} + 1│> when {}{} is + 2│ {}{} -> 1 + 3│ _ -> 1 - The first pattern is trying to match {}: + The `when` condition is an integer of type: {} - But the expression between `when` and `is` has the type: + But the branch patterns have type: {} + + The branches must be cases of the `when` condition's type! "# - ), number, $suffix, carets, kind, typ, bad_type), + ), number, bad_suffix, number, $suffix, bad_type, typ), ) } )*} @@ -8174,18 +7939,21 @@ I need all branches in an `if` to have the same type! r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st pattern in this `when` is causing a mismatch: + The branches of this `when` expression don't match the condition: - 2│ 1u8 -> 1 - ^^^ + 1│> when -1 is + 2│ 1u8 -> 1 + 3│ _ -> 1 - The first pattern is trying to match integers: + The `when` condition is a number of type: + + I8, I16, I32, I64, I128, F32, F64, or Dec + + But the branch patterns have type: U8 - But the expression between `when` and `is` has the type: - - I8, I16, I32, I64, I128, F32, F64, or Dec + The branches must be cases of the `when` condition's type! "# ), ) @@ -8305,7 +8073,7 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - $Age 21 + @Age 21 "# ), indoc!( @@ -8314,7 +8082,7 @@ I need all branches in an `if` to have the same type! The opaque type Age referenced here is not defined: - 1│ $Age 21 + 1│ @Age 21 ^^^^ Note: It looks like there are no opaque types declared in this scope yet! @@ -8330,7 +8098,7 @@ I need all branches in an `if` to have the same type! r#" Age : Num.U8 - $Age 21 + @Age 21 "# ), indoc!( @@ -8339,7 +8107,7 @@ I need all branches in an `if` to have the same type! The opaque type Age referenced here is not defined: - 3│ $Age 21 + 3│ @Age 21 ^^^^ Note: There is an alias of the same name: @@ -8368,19 +8136,19 @@ I need all branches in an `if` to have the same type! report_problem_as( indoc!( r#" - OtherModule.$Age 21 + OtherModule.@Age 21 "# ), - // TODO: get rid of the first error. Consider parsing OtherModule.$Age to completion + // TODO: get rid of the first error. Consider parsing OtherModule.@Age to completion // and checking it during can. The reason the error appears is because it is parsed as - // Apply(Error(OtherModule), [ $Age, 21 ]) + // Apply(Error(OtherModule), [ @Age, 21 ]) indoc!( r#" ── OPAQUE TYPE NOT APPLIED ─────────────────────────────── /code/proj/Main.roc ─ This opaque type is not applied to an argument: - 1│ OtherModule.$Age 21 + 1│ OtherModule.@Age 21 ^^^^ Note: Opaque types always wrap exactly one argument! @@ -8389,7 +8157,7 @@ I need all branches in an `if` to have the same type! I am trying to parse a qualified name here: - 1│ OtherModule.$Age 21 + 1│ OtherModule.@Age 21 ^ I was expecting to see an identifier next, like height. A complete @@ -8408,11 +8176,11 @@ I need all branches in an `if` to have the same type! Age := Num.U8 21u8 - $Age age + @Age age "# ), // TODO(opaques): there is a potential for a better error message here, if the usage of - // `$Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to + // `@Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to // raise that declaration to the outer scope. indoc!( r#" @@ -8430,7 +8198,7 @@ I need all branches in an `if` to have the same type! The opaque type Age referenced here is not defined: - 5│ $Age age + 5│ @Age age ^^^^ Note: It looks like there are no opaque types declared in this scope yet! @@ -8478,7 +8246,7 @@ I need all branches in an `if` to have the same type! Age := Num.U8 n : Age - n = $Age "" + n = @Age "" n "# @@ -8491,7 +8259,7 @@ I need all branches in an `if` to have the same type! This expression is used in an unexpected way: - 4│ n = $Age "" + 4│ n = @Age "" ^^ This argument to an opaque type has type: @@ -8514,8 +8282,8 @@ I need all branches in an `if` to have the same type! F n := n if True - then $F "" - else $F {} + then @F "" + else @F {} "# ), indoc!( @@ -8524,7 +8292,7 @@ I need all branches in an `if` to have the same type! This expression is used in an unexpected way: - 5│ else $F {} + 5│ else @F {} ^^ This argument to an opaque type has type: @@ -8598,16 +8366,16 @@ I need all branches in an `if` to have the same type! r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This pattern is being used in an unexpected way: + The 1st argument to `f` is weird: 4│ f = \Age n -> n ^^^^^ - It is a `Age` tag of type: + The argument is a pattern that matches a `Age` tag of type: [ Age a ] - But it needs to match: + But the annotation on `f` says the 1st argument should be: Age @@ -8629,8 +8397,8 @@ I need all branches in an `if` to have the same type! \x -> when x is - $F A -> "" - $F {} -> "" + @F A -> "" + @F {} -> "" "# ), indoc!( @@ -8639,7 +8407,7 @@ I need all branches in an `if` to have the same type! The 2nd pattern in this `when` does not match the previous ones: - 6│ $F {} -> "" + 6│ @F {} -> "" ^^^^^ The 2nd pattern is trying to matchF unwrappings of type: @@ -8664,25 +8432,33 @@ I need all branches in an `if` to have the same type! v : F [ A, B, C ] when v is - $F A -> "" - $F B -> "" + @F A -> "" + @F B -> "" "# ), indoc!( r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This `when` does not cover all the possibilities: + The branches of this `when` expression don't match the condition: 5│> when v is - 6│> $F A -> "" - 7│> $F B -> "" + 6│ @F A -> "" + 7│ @F B -> "" - Other possibilities include: + This `v` value is a: - $F C + F [ A, B, C ] - I would have to crash if I saw one of those! Add branches for them! + But the branch patterns have type: + + F [ A, B ] + + The branches must be cases of the `when` condition's type! + + Tip: Looks like the branches are missing coverage of the `C` tag. + + Tip: Maybe you need to add a catch-all branch, like `_`? "# ), ) @@ -8698,8 +8474,8 @@ I need all branches in an `if` to have the same type! v : F Num.U8 when v is - $F 1 -> "" - $F 2 -> "" + @F 1 -> "" + @F 2 -> "" "# ), indoc!( @@ -8709,12 +8485,12 @@ I need all branches in an `if` to have the same type! This `when` does not cover all the possibilities: 5│> when v is - 6│> $F 1 -> "" - 7│> $F 2 -> "" + 6│> @F 1 -> "" + 7│> @F 2 -> "" Other possibilities include: - $F _ + @F _ I would have to crash if I saw one of those! Add branches for them! "# @@ -8870,21 +8646,21 @@ I need all branches in an `if` to have the same type! ^^^^^^^^^^^ Did you mean one of these? - + Type True Box Ok - + ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ I cannot find a `UnknownType` value - + 3│ insertHelper : UnknownType, Type -> Type ^^^^^^^^^^^ - + Did you mean one of these? - + Type True insertHelper @@ -9233,51 +9009,6 @@ I need all branches in an `if` to have the same type! ) } - #[test] - fn alias_using_ability() { - new_report_problem_as( - "alias_using_ability", - indoc!( - r#" - app "test" provides [ a ] to "./platform" - - Ability has ab : a -> {} | a has Ability - - Alias : Ability - - a : Alias - "# - ), - indoc!( - r#" - ── ALIAS USES ABILITY ──────────────────────────────────── /code/proj/Main.roc ─ - - The definition of the `Alias` aliases references the ability `Ability`: - - 5│ Alias : Ability - ^^^^^ - - Abilities are not types, but you can add an ability constraint to a - type variable `a` by writing - - | a has Ability - - at the end of the type. - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `ab` is not used anywhere in your code. - - 3│ Ability has ab : a -> {} | a has Ability - ^^ - - If you didn't intend on using `ab` then remove it so future readers of - your code don't wonder why it is there. - "# - ), - ) - } - #[test] fn ability_shadows_ability() { new_report_problem_as( @@ -9349,57 +9080,6 @@ I need all branches in an `if` to have the same type! If you didn't intend on using `Ability` then remove it so future readers of your code don't wonder why it is there. - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `ab` is not used anywhere in your code. - - 3│ Ability has ab : {} -> {} - ^^ - - If you didn't intend on using `ab` then remove it so future readers of - your code don't wonder why it is there. - "# - ), - ) - } - - #[test] - fn ability_member_binds_extra_ability() { - new_report_problem_as( - "ability_member_binds_extra_ability", - indoc!( - r#" - app "test" provides [ eq ] to "./platform" - - Eq has eq : a, a -> Bool.Bool | a has Eq - Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash - "# - ), - indoc!( - r#" - ── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ────────────── /code/proj/Main.roc ─ - - The definition of the ability member `hash` includes a has clause - binding an ability it is not a part of: - - 4│ Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash - ^^^^^^^^ - - Currently, ability members can only bind variables to the ability they - are a part of. - - Hint: Did you mean to bind the `Hash` ability instead? - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `hash` is not used anywhere in your code. - - 4│ Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash - ^^^^ - - If you didn't intend on using `hash` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9431,16 +9111,6 @@ I need all branches in an `if` to have the same type! looking at specializations! Hint: Did you mean to only bind `a` to `Eq`? - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `eq` is not used anywhere in your code. - - 3│ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq - ^^ - - If you didn't intend on using `eq` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9484,16 +9154,6 @@ I need all branches in an `if` to have the same type! a has Hash Otherwise, the function does not need to be part of the ability! - - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - - `hash` is not used anywhere in your code. - - 3│ Hash has hash : (a | a has Hash) -> Num.U64 - ^^^^ - - If you didn't intend on using `hash` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9550,7 +9210,7 @@ I need all branches in an `if` to have the same type! Id := U32 - hash = \$Id n -> n + hash = \@Id n -> n "# ), indoc!( @@ -9559,7 +9219,7 @@ I need all branches in an `if` to have the same type! Something is off with this specialization of `hash`: - 7│ hash = \$Id n -> n + 7│ hash = \@Id n -> n ^^^^ This value is a declared specialization of type: @@ -9588,7 +9248,7 @@ I need all branches in an `if` to have the same type! Id := U64 - eq = \$Id m, $Id n -> m == n + eq = \@Id m, @Id n -> m == n "# ), indoc!( @@ -9607,7 +9267,7 @@ I need all branches in an `if` to have the same type! `eq`, specialized here: - 9│ eq = \$Id m, $Id n -> m == n + 9│ eq = \@Id m, @Id n -> m == n ^^ "# ), @@ -9670,7 +9330,7 @@ I need all branches in an `if` to have the same type! You := {} AndI := {} - eq = \$You {}, $AndI {} -> False + eq = \@You {}, @AndI {} -> False "# ), indoc!( @@ -9679,7 +9339,7 @@ I need all branches in an `if` to have the same type! Something is off with this specialization of `eq`: - 9│ eq = \$You {}, $AndI {} -> False + 9│ eq = \@You {}, @AndI {} -> False ^^ This value is a declared specialization of type: @@ -9713,7 +9373,7 @@ I need all branches in an `if` to have the same type! Id := U64 hash : Id -> U32 - hash = \$Id n -> n + hash = \@Id n -> n "# ), indoc!( @@ -9723,7 +9383,7 @@ I need all branches in an `if` to have the same type! Something is off with the body of this definition: 8│ hash : Id -> U32 - 9│ hash = \$Id n -> n + 9│ hash = \@Id n -> n ^ This `n` value is a: @@ -9738,7 +9398,7 @@ I need all branches in an `if` to have the same type! Something is off with this specialization of `hash`: - 9│ hash = \$Id n -> n + 9│ hash = \@Id n -> n ^^^^ This value is a declared specialization of type: @@ -9766,13 +9426,13 @@ I need all branches in an `if` to have the same type! Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n User := {} noGoodVeryBadTerrible = { - nope: hash ($User {}), + nope: hash (@User {}), notYet: hash (A 1), } "# @@ -9786,7 +9446,7 @@ I need all branches in an `if` to have the same type! 15│ notYet: hash (A 1), ^^^ - This `A` global tag application has the type: + This `A` tag application has the type: [ A (Num a) ]b @@ -9798,7 +9458,7 @@ I need all branches in an `if` to have the same type! This expression has a type that does not implement the abilities it's expected to: - 14│ nope: hash ($User {}), + 14│ nope: hash (@User {}), ^^^^^^^^ This User opaque wrapping has the type: @@ -9864,10 +9524,10 @@ I need all branches in an `if` to have the same type! hash : a -> U64 | a has Hash Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n hashable : a | a has Hash - hashable = $Id 15 + hashable = @Id 15 "# ), indoc!( @@ -9877,7 +9537,7 @@ I need all branches in an `if` to have the same type! Something is off with the body of the `hashable` definition: 9│ hashable : a | a has Hash - 10│ hashable = $Id 15 + 10│ hashable = @Id 15 ^^^^^^ This Id opaque wrapping has the type: @@ -9913,12 +9573,12 @@ I need all branches in an `if` to have the same type! mulHashes = \x, y -> hash x * hash y Id := U64 - hash = \$Id n -> n + hash = \@Id n -> n Three := {} - hash = \$Three _ -> 3 + hash = \@Three _ -> 3 - result = mulHashes ($Id 100) ($Three {}) + result = mulHashes (@Id 100) (@Three {}) "# ), indoc!( @@ -9954,4 +9614,296 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn branches_have_more_cases_than_condition() { + new_report_problem_as( + "branches_have_more_cases_than_condition", + indoc!( + r#" + foo : Bool -> Str + foo = \bool -> + when bool is + True -> "true" + False -> "false" + Wat -> "surprise!" + foo + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The branches of this `when` expression don't match the condition: + + 6│> when bool is + 7│ True -> "true" + 8│ False -> "false" + 9│ Wat -> "surprise!" + + This `bool` value is a: + + Bool + + But the branch patterns have type: + + [ False, True, Wat ] + + The branches must be cases of the `when` condition's type! + "# + ), + ) + } + + #[test] + fn always_function() { + // from https://github.com/rtfeldman/roc/commit/1372737f5e53ee5bb96d7e1b9593985e5537023a + // There was a bug where this reported UnusedArgument("val") + // since it was used only in the returned function only. + // + // we want this to not give any warnings/errors! + report_problem_as( + indoc!( + r#" + always = \val -> \_ -> val + + always + "# + ), + "", + ) + } + + #[test] + fn imports_missing_comma() { + new_report_problem_as( + "imports_missing_comma", + indoc!( + r#" + app "test-missing-comma" + packages { pf: "platform" } + imports [ pf.Task Base64 ] + provides [ main, @Foo ] to pf + "# + ), + indoc!( + r#" + ── WEIRD IMPORTS ────────────────────────── tmp/imports_missing_comma/Test.roc ─ + + I am partway through parsing a imports list, but I got stuck here: + + 2│ packages { pf: "platform" } + 3│ imports [ pf.Task Base64 ] + ^ + + I am expecting a comma or end of list, like + + imports [ Math, Util ]"# + ), + ) + } + + #[test] + fn not_enough_cases_for_open_union() { + new_report_problem_as( + "branches_have_more_cases_than_condition", + indoc!( + r#" + foo : [A, B]a -> Str + foo = \it -> + when it is + A -> "" + foo + "# + ), + indoc!( + r#" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 6│> when it is + 7│> A -> "" + + Other possibilities include: + + B + _ + + I would have to crash if I saw one of those! Add branches for them! + "# + ), + ) + } + + #[test] + fn issue_2778_specialization_is_not_a_redundant_pattern() { + new_report_problem_as( + "issue_2778_specialization_is_not_a_redundant_pattern", + indoc!( + r#" + formatColor = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + _ -> "unknown" + + Red |> formatColor |> Str.concat (formatColor Orange) + "# + ), + "", // no problem + ) + } + + #[test] + fn nested_specialization() { + new_report_problem_as( + "nested_specialization", + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + Default has default : {} -> a | a has Default + + main = + A := {} + default = \{} -> @A {} + default {} + "# + ), + indoc!( + r#" + ── SPECIALIZATION NOT ON TOP-LEVEL ─────────────────────── /code/proj/Main.roc ─ + + This specialization of the `default` ability member is in a nested + scope: + + 7│ default = \{} -> @A {} + ^^^^^^^ + + Specializations can only be defined on the top-level of a module. + "# + ), + ) + } + + #[test] + fn recursion_var_specialization_error() { + new_report_problem_as( + "recursion_var_specialization_error", + indoc!( + r#" + Job a : [ Job (List (Job a)) ] + + job : Job Str + + when job is + Job lst -> lst == "" + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd argument to `isEq` is not what I expect: + + 9│ Job lst -> lst == "" + ^^ + + This argument is a string of type: + + Str + + But `isEq` needs the 2nd argument to be: + + List [ Job ∞ ] as ∞ + "# + ), + ) + } + + #[test] + fn type_error_in_apply_is_circular() { + new_report_problem_as( + "type_error_in_apply_is_circular", + indoc!( + r#" + app "test" provides [ go ] to "./platform" + + S a : { set : Set a } + + go : a, S a -> Result (List a) * + go = \goal, model -> + if goal == goal + then Ok [] + else + new = { model & set : Set.remove goal model.set } + go goal new + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 1st argument to `remove` is not what I expect: + + 10│ new = { model & set : Set.remove goal model.set } + ^^^^ + + This `goal` value is a: + + a + + But `remove` needs the 1st argument to be: + + Set 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 `Set` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + + I'm inferring a weird self-referential type for `new`: + + 10│ new = { model & set : Set.remove goal model.set } + ^^^ + + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. + + { set : Set ∞ } + + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + + I'm inferring a weird self-referential type for `model`: + + 6│ go = \goal, model -> + ^^^^^ + + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. + + S (Set ∞) + + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + + I'm inferring a weird self-referential type for `goal`: + + 6│ go = \goal, model -> + ^^^^ + + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. + + Set ∞ + "# + ), + ) + } } diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index ced8f5811e..3c2b09c8d1 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -674,20 +674,24 @@ includes in its union." ## Opaque Types -The tags discussed in the previous section are globally available, which means -they cannot be used to create opaque types. +In Elm, you can choose to expose (or not) custom types' constructors in order to create [opaque types](http://sporto.github.io/elm-patterns/advanced/opaque-types.html). +Since Roc's _tags_ can be constructed in any module without importing anything, Roc has a separate +_opaque type_ language feature to enable information hiding. -*Private tags* let you create opaque types. They work just like the *global tags* -from the previous section, except: +Opaque types in Roc have some similarities to type aliases, but also some important differences. -* Private tags begin with an `@` (e.g. `@Foo` instead of `Foo`) -* Private tags are scoped to the current module, rather than globally scoped -* Private tags can only be instantiated in the current module +* Opaque type are defined with `:=` (e.g. `Username := Str` instead of `Username : Str`) +* You can get an _opaque wrapper_ by writing an `@` symbol before the name of an opaque type. For example, `@Username` would be an opaque wrapper for the opaque type `Username`. +* Applying an _opaque wrapper_ to another value creates an _opaque value_, whose type is the one referred to by the opaque wrapper. So the expression `@Username "Sasha"` has the type `Username`. +* Applying and destructuring opaque wrappers works like tags; you can write `@Username str = user` to unwrap an opaque wrapper's payload, just like you would with a tag payload. +* Opaque types can only be wrapped and unwrapped in the same module where the opaque type itself is defined. +* You can export opaque type names (e.g. `Username`) to other modules, allowing them to be used in type annotations, but there is no way to export the opaque wrappers themselves. This means that an opaque type can only be wrapped and unwrapped (using `@` syntax) in the same module where it was defined. +* Opaque types are only equal if their names are the same _and_ they were defined in the same module. -For example, suppose I define these inside the `Username` module: +As an example, suppose I define these inside the `Username` module: ```elm -Username : [ @Username Str ] +Username := Str fromStr : Str -> Username fromStr = \str -> @@ -697,16 +701,17 @@ toStr : Username -> Str toStr = \@Username str -> str ``` -I can now expose the `Username` type alias, which other modules can use as an opaque type. -It's not even syntactically possible for me to expose the `@Username` tag, -because `@` tags are not allowed in the exposing list. Only code written in this -`Username` module can instantiate a `@Username` value. +I can now expose the `Username` opaque type, which other modules can use in type annotations. +However, it's not even syntactically possible for me to expose the `@Username` opaque wrapper, +because `@` is not allowed in the `exposing` list. Only code written in this `Username` module +can use the `@Username` wrapper. -> If I were to write `@Username` inside another module (e.g. `Main`), it would compile, -> but that `@Username` would be type-incompatible with the one created inside the `Username` module. -> Even trying to use `==` on them would be a type mismatch, because I would be comparing -> a `[ Username.@Username Str ]*` with a `[ Main.@Username Str ]*`, which are incompatible. +> If I were to define `Username := Str` inside another module (e.g. `Main`) and use `@Username`, +> it would compile, but that `Username` opaque type would not be considered equal to the one defined in +> the `Username` module. Although both opaque types have the name `Username`, they were defined in +> different modules. That means the two `Username` types would be type-incompatible with each other, +> and even attempting to use `==` to compare them would be a type mismatch. ## Modules and Shadowing @@ -744,9 +749,9 @@ these expose is very important. All imports and exports in Roc are enumerated explicitly; there is no `..` syntax. -> Since neither global tags nor private tags have a notion of "importing variants" -> (global tags are always available in all modules, and private tags are -> never available in other modules), there's also no `exposing (Foo(..))` equivalent. +> Since tags are available in all modules, Roc does not have a notion of +> "importing variants", and there's also no `exposing (Foo(..))` equivalent. +> (Later on, we'll talk about how opaque types work in Roc.) Like Elm, Roc does not allow shadowing. @@ -1274,7 +1279,7 @@ Roc's standard library has these modules: Some differences to note: -* All these standard modules are imported by default into every module. They also expose all their types (e.g. `Bool`, `List`, `Result`) but they do not expose any values - not even `negate` or `not`. (`True`, `False`, `Ok`, and `Err` are all global tags, so they do not need to be exposed; they are globally available regardless!) +* All these standard modules are imported by default into every module. They also expose all their types (e.g. `Bool`, `List`, `Result`) but they do not expose any values - not even `negate` or `not`. (`True`, `False`, `Ok`, and `Err` are all tags, so they do not need to be exposed; they are globally available regardless!) * In Roc it's called `Str` instead of `String`. * `List` refers to something more like Elm's `Array`, as noted earlier. * No `Char`. This is by design. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value). @@ -1297,7 +1302,6 @@ Here are various Roc expressions involving operators, and what they desugar to. | `a // b` | `Num.divTrunc a b` | | `a ^ b` | `Num.pow a b` | | `a % b` | `Num.rem a b` | -| `a %% b` | `Num.mod a b` | | `a >> b` | `Num.shr a b` | | `a << b` | `Num.shl a b` | | `-a` | `Num.neg a` | diff --git a/roc_std/Cargo.lock b/roc_std/Cargo.lock new file mode 100644 index 0000000000..5494d0afd5 --- /dev/null +++ b/roc_std/Cargo.lock @@ -0,0 +1,252 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "getrandom" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "indoc" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7906a9fababaeacb774f72410e497a1d18de916322e33797bb2cd29baa23c9e" +dependencies = [ + "unindent", +] + +[[package]] +name = "libc" +version = "0.2.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + +[[package]] +name = "pretty_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "roc_std" +version = "0.1.0" +dependencies = [ + "indoc", + "libc", + "pretty_assertions", + "quickcheck", + "quickcheck_macros", +] + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unindent" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/roc_std/src/roc_list.rs b/roc_std/src/roc_list.rs index 6c0b797b73..7581e36eda 100644 --- a/roc_std/src/roc_list.rs +++ b/roc_std/src/roc_list.rs @@ -63,7 +63,7 @@ where let new_size = elements_offset + core::mem::size_of::() * (self.len() + slice.len()); let new_ptr = if let Some((elements, storage)) = self.elements_and_storage() { - // Decrement the lists refence count. + // Decrement the list's refence count. let mut copy = storage.get(); let is_unique = copy.decrease(); @@ -275,6 +275,15 @@ where } } +impl From<&[T]> for RocList +where + T: ReferenceCount, +{ + fn from(slice: &[T]) -> Self { + Self::from_slice(slice) + } +} + impl IntoIterator for RocList where T: ReferenceCount, diff --git a/roc_std/src/roc_str.rs b/roc_std/src/roc_str.rs index b4c3f143fa..e62965f5bb 100644 --- a/roc_std/src/roc_str.rs +++ b/roc_std/src/roc_str.rs @@ -97,6 +97,18 @@ impl PartialEq for RocStr { impl Eq for RocStr {} +impl PartialOrd for RocStr { + fn partial_cmp(&self, other: &Self) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl Ord for RocStr { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_str().cmp(other.as_str()) + } +} + impl Debug for RocStr { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.deref().fmt(f) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index d7f514dc9c..98a35adca6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.58.0" # make sure to update the rust version in Earthfile as well +channel = "1.60.0" # make sure to update the rust version in Earthfile as well profile = "default" diff --git a/shell.nix b/shell.nix index ce867939ad..6b810869c2 100644 --- a/shell.nix +++ b/shell.nix @@ -2,8 +2,13 @@ let sources = import nix/sources.nix { }; - pkgs = import sources.nixpkgs { }; - unstable-pkgs = import sources.nixpkgs-unstable { }; + rust_overlay = import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"); + pkgs = import sources.nixpkgs { + overlays = [ rust_overlay ]; + }; + rust_toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml); + + #unstable-pkgs = import sources.nixpkgs-unstable { }; darwinInputs = with pkgs; lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ @@ -31,7 +36,7 @@ let alsa-lib ]; - llvmPkgs = pkgs.llvmPackages_12; + llvmPkgs = pkgs.llvmPackages_13; zig = import ./nix/zig.nix { inherit pkgs; }; debugir = import ./nix/debugir.nix { inherit pkgs; }; @@ -65,15 +70,15 @@ let # tools for development environment less - ]) ++ (with unstable-pkgs; [ - rustup - ]); + ]) ++ [ + rust_toolchain + ]; in pkgs.mkShell { buildInputs = inputs ++ darwinInputs ++ linuxInputs; # Additional Env vars - LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}"; + LLVM_SYS_130_PREFIX = "${llvmPkgs.llvm.dev}"; NIX_GLIBC_PATH = if pkgs.stdenv.isLinux then "${pkgs.glibc_multi.out}/lib" else ""; LD_LIBRARY_PATH = with pkgs; diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index f5e00b7817..3387cedff4 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -43,6 +43,7 @@ impl TmpDir { impl Drop for TmpDir { fn drop(&mut self) { - remove_dir_all::remove_dir_all(&self.path).unwrap(); + // we "discard" the Result because there is no problem when a dir was already removed before we call remove_dir_all + let _ = remove_dir_all::remove_dir_all(&self.path); } } diff --git a/vendor/inkwell/Cargo.toml b/vendor/inkwell/Cargo.toml index 46feb8bc37..64833638f4 100644 --- a/vendor/inkwell/Cargo.toml +++ b/vendor/inkwell/Cargo.toml @@ -23,7 +23,7 @@ edition = "2018" # commit of TheDan64/inkwell, push a new tag which points to the latest commit, # change the tag value in this Cargo.toml to point to that tag, and `cargo update`. # This way, GitHub Actions works and nobody's builds get broken. -inkwell = { git = "https://github.com/rtfeldman/inkwell", branch = "master", features = [ "llvm12-0" ] } +inkwell = { git = "https://github.com/rtfeldman/inkwell", branch = "master", features = [ "llvm13-0" ] } [features] target-arm = [] diff --git a/vendor/pathfinding/src/lib.rs b/vendor/pathfinding/src/lib.rs index 9ade5352b1..c4505702d1 100644 --- a/vendor/pathfinding/src/lib.rs +++ b/vendor/pathfinding/src/lib.rs @@ -95,17 +95,19 @@ use std::mem; /// set.sort(); /// assert_eq!(set, vec![7, 8, 9]); /// //``` -pub fn topological_sort(nodes: &[N], mut successors: FN) -> Result, N> +pub fn topological_sort(nodes: I, mut successors: FN) -> Result, N> where N: Eq + Hash + Clone, + I: Iterator, FN: FnMut(&N) -> IN, IN: IntoIterator, { - let mut unmarked: MutSet = nodes.iter().cloned().collect::>(); - let mut marked = HashSet::with_capacity_and_hasher(nodes.len(), default_hasher()); + let size_hint = nodes.size_hint().1.unwrap_or_default(); + let mut unmarked: MutSet = nodes.collect::>(); + let mut marked = HashSet::with_capacity_and_hasher(size_hint, default_hasher()); let mut temp = MutSet::default(); - let mut sorted = VecDeque::with_capacity(nodes.len()); - while let Some(node) = unmarked.iter().cloned().next() { + let mut sorted = VecDeque::with_capacity(size_hint); + while let Some(node) = unmarked.iter().next().cloned() { temp.clear(); visit( &node, diff --git a/wasi-libc-sys/.gitignore b/wasi-libc-sys/.gitignore new file mode 100644 index 0000000000..9802e8fdbf --- /dev/null +++ b/wasi-libc-sys/.gitignore @@ -0,0 +1 @@ +src/generated.rs diff --git a/wasi-libc-sys/Cargo.toml b/wasi-libc-sys/Cargo.toml new file mode 100644 index 0000000000..1732300332 --- /dev/null +++ b/wasi-libc-sys/Cargo.toml @@ -0,0 +1,8 @@ +[package] +authors = ["The Roc Contributors"] +description = "Rust wrapper for a WebAssembly test platform built on libc" +edition = "2018" +license = "UPL-1.0" +name = "wasi_libc_sys" +repository = "https://github.com/rtfeldman/roc" +version = "0.1.0" diff --git a/wasi-libc-sys/build.rs b/wasi-libc-sys/build.rs new file mode 100644 index 0000000000..d07f9c137b --- /dev/null +++ b/wasi-libc-sys/build.rs @@ -0,0 +1,75 @@ +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::path::Path; +use std::process::Command; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/dummy.c"); + + let out_dir = env::var("OUT_DIR").unwrap(); + let zig_cache_dir = format!("{}/zig-cache", out_dir); + let out_file = format!("{}/wasi-libc.a", out_dir); + + // Compile a dummy C program with Zig, with our own private cache directory + let zig = zig_executable(); + run_command( + Path::new("."), + &zig, + [ + "build-exe", + "-target", + "wasm32-wasi", + "-lc", + "-O", + "ReleaseSmall", + "--global-cache-dir", + &zig_cache_dir, + "src/dummy.c", + &format!("-femit-bin={}/dummy.wasm", out_dir), + ], + ); + + // Find the libc.a file that Zig wrote (as a side-effect of compiling the dummy program) + let find_cmd_output = run_command(Path::new("."), "find", [&zig_cache_dir, "-name", "libc.a"]); + let zig_libc_path = find_cmd_output.trim(); // get rid of a newline + + // Copy libc to where Cargo expects it + fs::copy(&zig_libc_path, &out_file).unwrap(); + + // Generate some Rust code to indicate where the file is + let generated_rust = format!("pub const WASI_LIBC_PATH: &str =\n \"{}\";\n", out_file); + fs::write("src/generated.rs", generated_rust).unwrap(); +} + +fn zig_executable() -> String { + match std::env::var("ROC_ZIG") { + Ok(path) => path, + Err(_) => "zig".into(), + } +} + +fn run_command>(path: P, command_str: &str, args: I) -> String +where + I: IntoIterator, + S: AsRef, +{ + let output_result = Command::new(OsStr::new(&command_str)) + .current_dir(path) + .args(args) + .output(); + match output_result { + Ok(output) => match output.status.success() { + true => std::str::from_utf8(&output.stdout).unwrap().to_string(), + false => { + let error_str = match std::str::from_utf8(&output.stderr) { + Ok(stderr) => stderr.to_string(), + Err(_) => format!("Failed to run \"{}\"", command_str), + }; + panic!("{} failed: {}", command_str, error_str); + } + }, + Err(reason) => panic!("{} failed: {}", command_str, reason), + } +} diff --git a/wasi-libc-sys/src/dummy.c b/wasi-libc-sys/src/dummy.c new file mode 100644 index 0000000000..5f247ac932 --- /dev/null +++ b/wasi-libc-sys/src/dummy.c @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) +{ + printf("I am a dummy C program. I only exist to help the build script to find libc.a and copy it"); +} diff --git a/wasi-libc-sys/src/lib.rs b/wasi-libc-sys/src/lib.rs new file mode 100644 index 0000000000..5241f4f764 --- /dev/null +++ b/wasi-libc-sys/src/lib.rs @@ -0,0 +1,18 @@ +// Rust's libc crate doesn't support Wasm, so we provide an implementation from Zig +// We define Rust signatures here as we need them, rather than trying to cover all of libc +#[cfg(target_family = "wasm")] +use core::ffi::c_void; +#[cfg(target_family = "wasm")] +extern "C" { + pub fn malloc(size: usize) -> *mut c_void; + pub fn free(p: *mut c_void); + pub fn realloc(p: *mut c_void, size: usize) -> *mut c_void; + pub fn memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void; + pub fn memset(dst: *mut c_void, ch: i32, n: usize) -> *mut c_void; +} + +// Tell users of this crate where to find the Wasm .a file +// If a non-Wasm target is using this crate, we assume it is a build script that wants to emit Wasm +// For Wasm target, it won't ever be used, but we expose it just to keep things simple +mod generated; +pub use generated::WASI_LIBC_PATH; diff --git a/www/build.sh b/www/build.sh index fbe0bdfbf4..d31c99bb64 100755 --- a/www/build.sh +++ b/www/build.sh @@ -28,21 +28,29 @@ rm deploy.zip popd -# pushd .. -# echo 'Generating docs...' -# cargo --version -# rustc --version -# # We run the CLI with --no-default-features because that way we don't have the -# # "llvm" feature and therefore don't depend on LLVM being installed on the -# # system. (Netlify's build servers have Rust installed, but not LLVM.) -# # -# # We set RUSTFLAGS to -Awarnings to ignore warnings during this build, -# # because when building without "the" llvm feature (which is only ever done -# # for this exact use case), the result is lots of "unused" warnings! -# # -# # We set ROC_DOCS_ROOT_DIR=builtins so that links will be generated relative to -# # "/builtins/" rather than "/" - which is what we want based on how the server -# # is set up to serve them. -# RUSTFLAGS=-Awarnings ROC_DOCS_URL_ROOT=builtins cargo run -p roc_cli --no-default-features docs compiler/builtins/docs/*.roc -# mv generated-docs/ www/build/builtins -# popd +pushd .. +echo 'Generating docs...' +cargo --version +rustc --version + +# We set RUSTFLAGS to -Awarnings to ignore warnings during this build, +# because when building without "the" llvm feature (which is only ever done +# for this exact use case), the result is lots of "unused" warnings! +RUSTFLAGS=-Awarnings + +# We set ROC_DOCS_ROOT_DIR=builtins so that links will be generated relative to +# "/builtins/" rather than "/" - which is what we want based on how the server +# is set up to serve them. +export ROC_DOCS_URL_ROOT=/builtins + +# These just need to be defined so that some env! macros don't fail. +BUILTINS_WASM32_O="" +BUILTINS_HOST_O="" + +# We run the CLI with --no-default-features because that way we don't have the +# "llvm" feature and therefore don't depend on LLVM being installed on the +# system. (Netlify's build servers have Rust installed, but not LLVM.) +cargo run --bin roc-docs compiler/builtins/roc/*.roc +mv generated-docs/*.* www/build # move all the .js, .css, etc. files to build/ +mv generated-docs/ www/build/builtins # move all the folders to build/builtins/ +popd