diff --git a/.cargo/config b/.cargo/config.toml similarity index 73% rename from .cargo/config rename to .cargo/config.toml index 544ab69523..4aa93e2080 100644 --- a/.cargo/config +++ b/.cargo/config.toml @@ -6,14 +6,21 @@ test-gen-llvm-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --f [target.wasm32-unknown-unknown] # Rust compiler flags for minimum-sized .wasm binary in the web REPL -# 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"] +# opt-level=s Optimizations should focus more on size than speed +# lto=fat Spend extra effort on link-time optimization across crates +# embed-bitcode=yes Turn back on lto since it is no longer default +rustflags = ["-Copt-level=s", "-Clto=fat", "-Cembed-bitcode=yes"] [target.'cfg(not(target = "wasm32-unknown-unknown"))'] # Sets the avx, avx2, sse2 and sse4.2 target-features correctly based on your CPU. rustflags = ["-Ctarget-cpu=native"] +# TODO: there is probably a more proper solution to this. +# We are pulling in roc_alloc and friends due to using roc_std. +# They ared defined in roc_glue, but windows linking breaks before we get there. +[target.'cfg(target_os = "windows")'] +rustflags = ["-Clink-args=/FORCE:UNRESOLVED"] + [env] # Gives us the path of the workspace root for use in cargo tests without having # to compute it per-package. diff --git a/.github/workflows/basic_cli_build_release.yml b/.github/workflows/basic_cli_build_release.yml index b9243dceed..df0e1966c4 100644 --- a/.github/workflows/basic_cli_build_release.yml +++ b/.github/workflows/basic_cli_build_release.yml @@ -1,4 +1,5 @@ -on: [workflow_dispatch] +on: + workflow_dispatch: # this cancels workflows currently in progress if you start a new one concurrency: @@ -6,48 +7,59 @@ concurrency: cancel-in-progress: true jobs: - - build-linux-x86_64-files: + fetch-releases: runs-on: [ubuntu-20.04] steps: - uses: actions/checkout@v3 - # note: moving this step to a bash script will not work, the GITHUB_TOKEN is not passed properly - name: Fetch releases data and save to file. Authorization is used to prevent rate limiting. + env: + AUTH_HEADER: 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' run: | - curl --request GET \ - --url https://api.github.com/repos/roc-lang/roc/releases \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - --output roc_releases.json + ./ci/get_releases_json.sh - # does a build with the surgical linker and also with the legacy linker - - run: ./ci/build_basic_cli.sh linux_x86_64 "--linker legacy" + - run: curl -OL $(./ci/get_latest_release_url.sh linuxTESTING_x86_64) + - run: curl -OL $(./ci/get_latest_release_url.sh macosTESTING_x86_64) + - run: curl -OL $(./ci/get_latest_release_url.sh macosTESTING_apple_silicon) - - name: Save .rh1, .rm2 and .o file + - name: Save roc_nightly archives + uses: actions/upload-artifact@v3 + with: + path: roc_nightly-* + + build-linux-x86_64-files: + runs-on: [ubuntu-20.04] + needs: [fetch-releases] + steps: + - uses: actions/checkout@v3 + + - name: Download the previously uploaded roc_nightly archives + uses: actions/download-artifact@v3 + + - name: build basic-cli with surgical linker and also with legacy linker + env: + CARGO_BUILD_TARGET: x86_64-unknown-linux-musl + run: ./ci/build_basic_cli.sh linuxTESTING_x86_64 "--linker legacy" + + - name: Save .rh, .rm and .o file uses: actions/upload-artifact@v3 with: name: linux-x86_64-files path: | - basic-cli/src/metadata_linux-x86_64.rm2 - basic-cli/src/linux-x86_64.rh1 + basic-cli/src/metadata_linux-x86_64.rm + basic-cli/src/linux-x86_64.rh basic-cli/src/linux-x86_64.o build-macos-x86_64-files: runs-on: [macos-11] # I expect the generated files to work on macOS 12 + needs: [fetch-releases] steps: - uses: actions/checkout@v3 - # note: moving this step to a bash script will not work, the GITHUB_TOKEN is not passed properly - - name: Fetch releases data and save to file. Authorization is used to prevent rate limiting due to shared IP of github's macos ci servers. - run: | - curl --request GET \ - --url https://api.github.com/repos/roc-lang/roc/releases \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - --output roc_releases.json + - name: Download the previously uploaded roc_nightly archives + uses: actions/download-artifact@v3 - - run: ./ci/build_basic_cli.sh macos_x86_64 + - run: ./ci/build_basic_cli.sh macosTESTING_x86_64 - name: Save .o files uses: actions/upload-artifact@v3 @@ -59,13 +71,14 @@ jobs: build-macos-apple-silicon-files: name: build apple silicon .o file runs-on: [self-hosted, macOS, ARM64] + needs: [fetch-releases] steps: - uses: actions/checkout@v3 - - name: fetch releases data and save to file - run: curl https://api.github.com/repos/roc-lang/roc/releases > roc_releases.json + - name: Download the previously uploaded roc_nightly archives + uses: actions/download-artifact@v3 - - run: ./ci/build_basic_cli.sh silicon + - run: ./ci/build_basic_cli.sh macosTESTING_apple_silicon - name: Save macos-arm64.o file uses: actions/upload-artifact@v3 @@ -84,43 +97,36 @@ jobs: - name: remove all folders except the ci folder run: ls | grep -v ci | xargs rm -rf - # note: moving this step to a bash script will not work, the GITHUB_TOKEN is not passed properly - - name: Fetch releases data and save to file. Authorization is used to prevent rate limiting. - run: | - curl --request GET \ - --url https://api.github.com/repos/roc-lang/roc/releases \ - --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'content-type: application/json' \ - --output roc_releases.json + - name: Download the previously uploaded files + uses: actions/download-artifact@v3 - - run: echo "ROC_RELEASE_URL=$(./ci/get_latest_release_url.sh linux_x86_64)" >> $GITHUB_ENV - - - name: Get the archive from the url. - run: curl -OL ${{ env.ROC_RELEASE_URL }} + - name: mv roc nightly and simplify name + run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux") ./roc_nightly.tar.gz - name: decompress the tar - run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf + run: tar -xzvf roc_nightly.tar.gz - name: delete tar - run: ls | grep -v "roc_nightly.*tar\.gz" | xargs rm -rf + run: rm roc_nightly.tar.gz - name: rename nightly folder run: mv roc_nightly* roc_nightly - run: git clone https://github.com/roc-lang/basic-cli.git - - name: Download the previously uploaded files - uses: actions/download-artifact@v3 - - run: cp macos-apple-silicon-files/* ./basic-cli/src - run: cp linux-x86_64-files/* ./basic-cli/src - run: cp macos-x86_64-files/* ./basic-cli/src - - run: ./roc_nightly/roc build --bundle=.tar.br ./basic-cli/src/main.roc + # change to tar.gz for fast test build + - run: | + echo "ARCHIVE_FORMAT='.tar.br'" >> "$GITHUB_ENV" - - run: echo "TAR_FILENAME=$(ls -d basic-cli/src/* | grep '.tar.br')" >> $GITHUB_ENV + - run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-cli/src/main.roc + + - run: echo "TAR_FILENAME=$(ls -d basic-cli/src/* | grep ${{ env.ARCHIVE_FORMAT }})" >> $GITHUB_ENV - name: Upload platform archive uses: actions/upload-artifact@v3 @@ -129,3 +135,43 @@ jobs: path: | ${{ env.TAR_FILENAME }} + test-release-ubuntu: + needs: [create-brotli-archive] + runs-on: [ubuntu-20.04] + steps: + + - name: Download the previously uploaded files + uses: actions/download-artifact@v3 + + - name: mv roc nightly and simplify name + run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux") ./roc_nightly.tar.gz + + - name: decompress the tar + run: tar -xzvf roc_nightly.tar.gz + + - name: delete tar + run: rm roc_nightly.tar.gz + + - name: rename nightly folder + run: mv roc_nightly* roc_nightly + + - run: cd basic-cli-platform && ls | grep "tar" | xargs tar -xzf + + - name: prep testing http-get.roc + run: | + mv roc_nightly basic-cli-platform/. + cd basic-cli-platform + mkdir examples + cd examples + curl -OL https://raw.githubusercontent.com/roc-lang/basic-cli/main/examples/http-get.roc + sed -i 's/pf:\ \"[^"]*/pf:\ \"\.\.\/main.roc/g' http-get.roc + cd .. + curl -OL https://raw.githubusercontent.com/roc-lang/basic-cli/main/ci/expect_scripts/http-get.exp + + - run: sudo apt install -y expect + + - name: execute test + run: | + cd basic-cli-platform + expect http-get.exp + diff --git a/.github/workflows/macos_x86_64.yml b/.github/workflows/macos_x86_64.yml index 286c69bea4..8d112a2a53 100644 --- a/.github/workflows/macos_x86_64.yml +++ b/.github/workflows/macos_x86_64.yml @@ -25,9 +25,5 @@ jobs: # swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine # this issue may be caused by using older versions of XCode - # TODO build basic-cli for macos 11 - #- name: test examples/helloWorld.roc separately because it is ignored by default - # run: cargo test --locked --release -p roc_cli cli_run::hello_world -- --ignored && sccache --show-stats - - name: test launching the editor run: cargo test --release --locked editor_launch_test::launch -- --ignored # `--ignored` to run this test that is ignored for "normal" runs diff --git a/.github/workflows/nightly_linux_x86_64.yml b/.github/workflows/nightly_linux_x86_64.yml index cf464875ad..6e506b8c82 100644 --- a/.github/workflows/nightly_linux_x86_64.yml +++ b/.github/workflows/nightly_linux_x86_64.yml @@ -16,9 +16,9 @@ jobs: - name: create version.txt run: ./ci/write_version.sh - - name: build release - run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked - # target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower. + - name: build release with lto + run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --features "editor" --bin roc + # target-cpu=x86-64 -> For maximal compatibility for all CPU's. This was also faster in our tests: https://roc.zulipchat.com/#narrow/stream/231635-compiler-development/topic/.2Ecargo.2Fconfig.2Etoml/near/325726299 - name: get commit SHA run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV @@ -41,6 +41,10 @@ jobs: DATE: ${{ env.DATE }} SHA: ${{ env.SHA }} run: echo "RELEASE_FOLDER_NAME=roc_nightly-linux_x86_64-$DATE-$SHA" >> $GITHUB_ENV + + # this makes the roc binary a lot smaller + - name: strip debug info + run: strip ./target/release-with-lto/roc - name: Make nightly release tar archive run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }} diff --git a/.github/workflows/nightly_macos_apple_silicon.yml b/.github/workflows/nightly_macos_apple_silicon.yml index 7bc89abef5..3c0e70fd23 100644 --- a/.github/workflows/nightly_macos_apple_silicon.yml +++ b/.github/workflows/nightly_macos_apple_silicon.yml @@ -36,7 +36,11 @@ jobs: run: ./ci/write_version.sh - name: build nightly release - run: cargo build --locked --release + run: cargo build --locked --profile=release-with-lto --features "editor" --bin roc + + # this makes the roc binary a lot smaller + - name: strip debug info + run: strip ./target/release-with-lto/roc - name: package release run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }} diff --git a/.github/workflows/nightly_macos_x86_64.yml b/.github/workflows/nightly_macos_x86_64.yml index 7b4f0e2eb0..43e5ce868f 100644 --- a/.github/workflows/nightly_macos_x86_64.yml +++ b/.github/workflows/nightly_macos_x86_64.yml @@ -22,7 +22,7 @@ jobs: # this issue may be caused by using older versions of XCode - name: build release - run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked + run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --features "editor" --bin roc # target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower. - name: get commit SHA @@ -36,6 +36,10 @@ jobs: DATE: ${{ env.DATE }} SHA: ${{ env.SHA }} run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV + + # this makes the roc binary a lot smaller + - name: strip debug info + run: strip ./target/release-with-lto/roc - name: package release run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }} diff --git a/.github/workflows/nix_macos_x86_64.yml b/.github/workflows/nix_macos_x86_64.yml index 755ebb608c..03fb59e743 100644 --- a/.github/workflows/nix_macos_x86_64.yml +++ b/.github/workflows/nix_macos_x86_64.yml @@ -19,13 +19,7 @@ jobs: with: clean: "true" - - uses: cachix/install-nix-action@v15 - - # to cache nix packages - - uses: cachix/cachix-action@v10 - with: - name: enigmaticsunrise - authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + - uses: cachix/install-nix-action@v20 - name: execute cli_run tests only, the full tests take too long but are run nightly run: nix develop -c cargo test --locked --release -p roc_cli diff --git a/.github/workflows/test_nightly_macos_apple_silicon.yml b/.github/workflows/test_nightly_macos_apple_silicon.yml index f423acf746..a09549e471 100644 --- a/.github/workflows/test_nightly_macos_apple_silicon.yml +++ b/.github/workflows/test_nightly_macos_apple_silicon.yml @@ -1,7 +1,5 @@ on: workflow_dispatch: - schedule: - - cron: '0 13 * * *' name: Test latest nightly release for macOS Apple Silicon @@ -35,16 +33,16 @@ jobs: run: mv roc_nightly* roc_nightly - name: test roc hello world - run: ./roc_nightly/roc examples/helloWorld.roc + run: cd roc_nightly && ./roc examples/helloWorld.roc - name: test platform switching rust - run: ./roc_nightly/roc examples/platform-switching/rocLovesRust.roc + run: cd roc_nightly && ./roc examples/platform-switching/rocLovesRust.roc - name: test platform switching zig - run: ./roc_nightly/roc examples/platform-switching/rocLovesZig.roc + run: cd roc_nightly && ./roc examples/platform-switching/rocLovesZig.roc - name: test platform switching c - run: ./roc_nightly/roc examples/platform-switching/rocLovesC.roc + run: cd roc_nightly && ./roc examples/platform-switching/rocLovesC.roc diff --git a/.github/workflows/test_nightly_many_os.yml b/.github/workflows/test_nightly_many_os.yml index 57170bb7cb..e9b6f536ad 100644 --- a/.github/workflows/test_nightly_many_os.yml +++ b/.github/workflows/test_nightly_many_os.yml @@ -1,7 +1,5 @@ on: workflow_dispatch: - schedule: - - cron: '0 13 * * *' name: Test latest nightly release for macOS, ubu 20.04, ubu 22.04 x86_64 @@ -52,10 +50,10 @@ jobs: run: mv roc_nightly* roc_nightly - name: test roc hello world - run: ./roc_nightly/roc examples/helloWorld.roc + run: cd roc_nightly && ./roc examples/helloWorld.roc - name: test platform switching rust - run: ./roc_nightly/roc examples/platform-switching/rocLovesRust.roc + run: cd roc_nightly && ./roc examples/platform-switching/rocLovesRust.roc - name: get OS to use for zig download if: startsWith(matrix.os, 'ubuntu') @@ -73,10 +71,10 @@ jobs: run: zig version - name: test platform switching zig - run: ./roc_nightly/roc examples/platform-switching/rocLovesZig.roc + run: cd roc_nightly && ./roc examples/platform-switching/rocLovesZig.roc - name: test platform switching c - run: ./roc_nightly/roc examples/platform-switching/rocLovesC.roc + run: cd roc_nightly && ./roc examples/platform-switching/rocLovesC.roc diff --git a/.github/workflows/ubuntu_x86_64.yml b/.github/workflows/ubuntu_x86_64.yml index df756ddd52..fd7f81ddf0 100644 --- a/.github/workflows/ubuntu_x86_64.yml +++ b/.github/workflows/ubuntu_x86_64.yml @@ -36,12 +36,6 @@ jobs: - name: regular rust tests run: cargo test --locked --release && sccache --show-stats - - name: test hellow_world separately because it is ignored by default, the use of url platforms causes issues within nix - run: cargo test --locked --release -p roc_cli cli_run::hello_world -- --ignored && sccache --show-stats - - - name: test parse_letter_counts separately because it is ignored by default, the use of url platforms causes issues within nix - run: cargo test --locked --release -p roc_cli cli_run::parse_letter_counts -- --ignored && sccache --show-stats - - name: check that the platform`s produced dylib is loadable run: cd examples/platform-switching/rust-platform && LD_LIBRARY_PATH=. cargo test --release --locked diff --git a/.github/workflows/windows_release_build.yml b/.github/workflows/windows_release_build.yml new file mode 100644 index 0000000000..b095c1205b --- /dev/null +++ b/.github/workflows/windows_release_build.yml @@ -0,0 +1,44 @@ +on: [pull_request] + +name: windows - release build + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + RUST_BACKTRACE: 1 + +jobs: + windows-release-build: + name: windows-release-build + runs-on: windows-2022 + env: + LLVM_SYS_130_PREFIX: C:\LLVM-13.0.1-win64 + + timeout-minutes: 150 + steps: + - uses: actions/checkout@v2 + + - run: Add-Content -Path "$env:GITHUB_ENV" -Value "GITHUB_RUNNER_CPU=$((Get-CimInstance Win32_Processor).Name)" + + - name: download and install zig + run: | + curl.exe --output "C:\zig-windows-x86_64-0.9.1.zip" --url https://ziglang.org/download/0.9.1/zig-windows-x86_64-0.9.1.zip + cd C:\ + 7z x zig-windows-x86_64-0.9.1.zip + Add-Content $env:GITHUB_PATH "C:\zig-windows-x86_64-0.9.1\" + + - name: zig version + run: zig version + + - name: install rust nightly 1.65 + run: rustup install nightly-2022-09-17 + + - name: set up llvm 13 + run: | + curl.exe -L -O -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z + 7z x LLVM-13.0.1-win64.7z -oC:\LLVM-13.0.1-win64 + + - name: cargo build release. Twice for zig lld-link error. + run: cargo build --locked --release || cargo build --locked --release \ No newline at end of file diff --git a/.github/workflows/windows.yml b/.github/workflows/windows_tests.yml similarity index 87% rename from .github/workflows/windows.yml rename to .github/workflows/windows_tests.yml index d45f7a3eb6..3388162109 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows_tests.yml @@ -1,6 +1,6 @@ on: [pull_request] -name: Test windows build +name: windows - subset of tests concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -10,8 +10,8 @@ env: RUST_BACKTRACE: 1 jobs: - windows-cargo-build: - name: windows-cargo-build + windows-test-subset: + name: windows-test-subset runs-on: windows-2022 env: LLVM_SYS_130_PREFIX: C:\LLVM-13.0.1-win64 @@ -41,7 +41,7 @@ jobs: - name: set up llvm 13 run: | - curl.exe -L -O https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z + curl.exe -L -O -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z 7z x LLVM-13.0.1-win64.7z -oC:\LLVM-13.0.1-win64 - name: Build tests --release without running. Twice for zig lld-link error. diff --git a/.gitignore b/.gitignore index d65db52827..62c229150a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,10 @@ zig-cache .envrc *.rs.bk *.o +*.so *.obj *.dll +*.dylib *.lib *.def *.tmp @@ -22,11 +24,10 @@ zig-cache vgcore.* # roc cache files -*.rh1 -*.rm1 +*.rh* +*.rm* preprocessedhost metadata -roc-cheaty-lib.so #editors .idea/ @@ -72,4 +73,11 @@ roc_linux_x86_64.tar.gz result # tutorial -www/src/roc-tutorial \ No newline at end of file +www/src/roc-tutorial + +# Only keep Cargo.lock dependencies for the main compiler. +# Examples and test only crates should be fine to be unlocked. +# This remove unneccessary lock file versioning. +# It also ensures the compiler can always pull in 1 version of things and doesn't get restricted by sub lockfiles. +/**/Cargo.lock +!/Cargo.lock \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81662fe873..8614af2132 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,18 +50,32 @@ This command will generate the documentation in the [`generated-docs`](generated - You can find good first issues [here][good-first-issues]. Once you have gained some experience you can take a look at the [intermediate issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22intermediate+issue%22). - [Fork](https://github.com/roc-lang/roc/fork) the repo so that you can apply your changes first on your own copy of the roc repo. - It's a good idea to open a draft pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Click the button "ready for review" when it's ready. + +### Commit signing + - All your commits need to be signed [to prevent impersonation](https://dev.to/martiliones/how-i-got-linus-torvalds-in-my-contributors-on-github-3k4g): - - If you don't have signing set up on your device and you only want to change a single file, it will be easier to use [github's edit button](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files). This will sign your commit automatically. - - For multi-file or complex changes you will want to set up signing on your device: - 1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below. - 2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key) - 3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) - 4. Make git sign your commits automatically: +- If you don't have signing set up on your device and you only want to change a single file, it will be easier to use [github's edit button](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files). This will sign your commit automatically. +- For multi-file or complex changes you will want to set up signing on your device: + 1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below. + 2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key) + 3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) + 4. Make git sign your commits automatically: ```sh git config --global commit.gpgsign true ``` +#### Commit signing on NixOS + +On NixOS pinentry can cause problems, the following setup works well for those with a KDE desktop. From `/etc/nixos/configuration.nix`: +``` +programs.gnupg.agent = { + enable = true; + pinentryFlavor = "qt"; + enableSSHSupport = true; + }; +``` + ### Forgot to sign commits? You can view your commits on github, those without the "Verified" badge still need to be signed. diff --git a/Cargo.lock b/Cargo.lock index 6e46a850ec..21f33166df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24606928a235e73cdef55a0c909719cadd72fce573e5713d58cb2952d8f5794c" +checksum = "fe21446ad43aa56417a767f3e2f3d7c4ca522904de1dd640529a76e9c5c3b33c" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -14,15 +14,15 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -45,14 +45,34 @@ dependencies = [ ] [[package]] -name = "aho-corasick" -version = "0.7.18" +name = "ahash" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] +[[package]] +name = "aligned" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80a21b9440a626c7fc8573a9e3d3a06b75c7c97754c2949bc7857b90353ca655" +dependencies = [ + "as-slice", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -68,28 +88,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "alsa" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b" -dependencies = [ - "alsa-sys", - "bitflags", - "libc", - "nix 0.23.1", -] - -[[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" @@ -133,6 +131,15 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "ash" version = "0.34.0+1.2.203" @@ -148,7 +155,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -161,9 +168,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", @@ -180,13 +187,19 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64-url" version = "1.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a99c239d0c7e77c85dddfa9cebce48704b3c49550fcd3b84dd637e4484899f" dependencies = [ - "base64", + "base64 0.13.1", ] [[package]] @@ -198,30 +211,11 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.59.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" -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" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] @@ -247,28 +241,16 @@ dependencies = [ "typenum", ] -[[package]] -name = "bitvec" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" -dependencies = [ - "funty 1.2.0", - "radium 0.6.2", - "tap", - "wyz 0.4.0", -] - [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "funty 2.0.0", - "radium 0.7.0", + "funty", + "radium", "tap", - "wyz 0.5.0", + "wyz", ] [[package]] @@ -293,9 +275,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] @@ -313,46 +295,34 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.2" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9" +checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" dependencies = [ "proc-macro2", "quote", @@ -367,9 +337,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "calloop" @@ -412,27 +382,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" -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.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -462,17 +414,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "clang-sys" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "2.34.0" @@ -486,32 +427,17 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.20" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", - "clap_derive", "clap_lex", "indexmap", - "once_cell", "strsim", "termcolor", - "textwrap 0.15.0", -] - -[[package]] -name = "clap_derive" -version = "3.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "textwrap 0.16.0", ] [[package]] @@ -523,12 +449,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "claxon" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" - [[package]] name = "cli_utils" version = "0.0.1" @@ -537,13 +457,12 @@ dependencies = [ "criterion", "rlimit", "roc_collections", + "roc_command_utils", "roc_load", "roc_module", "roc_reporting", - "roc_utils", "serde", "serde-xml-rs", - "strip-ansi-escapes", "tempfile", ] @@ -559,9 +478,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "4.4.1" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" dependencies = [ "error-code", "str-buf", @@ -570,9 +489,9 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ "bitflags", "block", @@ -620,20 +539,10 @@ dependencies = [ "winapi", ] -[[package]] -name = "combine" -version = "4.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "confy" -version = "0.5.0" -source = "git+https://github.com/rust-cli/confy#fd069f062aa3373c846f0d8c6e3b5e2a5cd0096b" +version = "0.5.1" +source = "git+https://github.com/rust-cli/confy#700337e5a3fbc12f4f49fc9b7ce449b1b71040ee" dependencies = [ "directories", "serde", @@ -643,15 +552,14 @@ dependencies = [ [[package]] name = "console" -version = "0.15.1" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode 0.3.6", + "lazy_static", "libc", - "once_cell", - "terminal_size", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -666,18 +574,18 @@ dependencies = [ [[package]] name = "const_format" -version = "0.2.26" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495" +checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.22" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d" +checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650" dependencies = [ "proc-macro2", "quote", @@ -698,9 +606,9 @@ checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" [[package]] name = "copypasta" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7216b5c1e9ad3867252505995b02d01c6fa7e6db0d8abd42634352ef377777e" +checksum = "133fc8675ee3a4ec9aa513584deda9aa0faeda3586b87f7f0f2ba082c66fb172" dependencies = [ "clipboard-win 3.1.1", "objc", @@ -792,55 +700,11 @@ dependencies = [ "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.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dff444d80630d7073077d38d40b4501fd518bd2b922c2a55edcc8b0f7be57e6" -dependencies = [ - "bindgen", -] - -[[package]] -name = "cpal" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74117836a5124f3629e4b474eed03e479abaf98988b4bb317e29f08cfe0e4116" -dependencies = [ - "alsa", - "core-foundation-sys 0.8.3", - "coreaudio-rs", - "jni", - "js-sys", - "lazy_static", - "libc", - "mach", - "ndk 0.6.0", - "ndk-glue 0.6.2", - "nix 0.23.1", - "oboe", - "parking_lot 0.11.2", - "stdweb", - "thiserror", - "web-sys", - "winapi", -] - [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -904,9 +768,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -914,9 +778,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -925,23 +789,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "memoffset", - "once_cell", + "memoffset 0.8.0", "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -949,19 +812,18 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if 1.0.0", - "once_cell", ] [[package]] name = "crypto-common" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccfd8c0ee4cce11e45b3fd6f9d5e69e0cc62912aa6a0cb1bf4617b0eba5a12f" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -969,13 +831,12 @@ dependencies = [ [[package]] name = "csv" -version = "1.1.6" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad" dependencies = [ - "bstr", "csv-core", - "itoa 0.4.8", + "itoa", "ryu", "serde", ] @@ -991,9 +852,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.22" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", @@ -1005,6 +866,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +[[package]] +name = "cvt" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac344c7efccb80cd25bc61b2170aec26f2f693fd40e765a539a1243db48c71" +dependencies = [ + "cfg-if 0.1.10", +] + [[package]] name = "d3d12" version = "0.4.1" @@ -1053,14 +923,15 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.3.4" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ "cfg-if 1.0.0", "hashbrown 0.12.3", "lock_api", - "parking_lot_core 0.9.3", + "once_cell", + "parking_lot_core 0.9.7", ] [[package]] @@ -1071,9 +942,9 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -1082,9 +953,9 @@ dependencies = [ [[package]] name = "dircpy" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ff6269b47c0c5220a0ff5eb140424340276ec89a10e58cbd4cf366de52dfa9" +checksum = "10b6622b9d0dc20c70e74ff24c56493278d7d9299ac8729deb923703616e5a7e" dependencies = [ "jwalk", "log", @@ -1093,11 +964,10 @@ dependencies = [ [[package]] name = "directories" -version = "2.0.2" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" dependencies = [ - "cfg-if 0.1.10", "dirs-sys", ] @@ -1174,9 +1044,9 @@ checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encode_unicode" @@ -1192,9 +1062,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if 1.0.0", ] @@ -1226,6 +1096,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -1246,36 +1127,27 @@ dependencies = [ "str-buf", ] -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - [[package]] name = "fd-lock" -version = "3.0.6" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" +checksum = "8ef1a30ae415c3a691a4f41afddc2dbcd6d70baf338368d85ebc1e8ed92cedb9" dependencies = [ "cfg-if 1.0.0", "rustix", - "windows-sys 0.36.1", + "windows-sys 0.45.0", ] [[package]] name = "filetime" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1289,9 +1161,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -1320,25 +1192,33 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] [[package]] -name = "fs_extra" -version = "1.2.0" +name = "fs_at" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +checksum = "ab60b097d8208fe02d24ae954c3248a9436f96edefa8f4b9fcb0f26d60d003a9" +dependencies = [ + "aligned", + "cfg-if 1.0.0", + "cvt", + "libc", + "nix 0.26.2", + "smart-default", + "windows-sys 0.45.0", +] [[package]] -name = "funty" -version = "1.2.0" +name = "fs_extra" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "funty" @@ -1348,9 +1228,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -1363,9 +1243,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -1373,15 +1253,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1390,15 +1270,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -1407,21 +1287,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1446,36 +1326,42 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", ] [[package]] -name = "getrandom" -version = "0.2.7" +name = "gethostname" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.26.1" +version = "0.27.2" 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" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "glow" @@ -1491,13 +1377,12 @@ dependencies = [ [[package]] name = "glyph_brush" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac02497410cdb5062cc056a33f2e1e19ff69fbf26a4be9a02bf29d6e17ea105b" +checksum = "4edefd123f28a0b1d41ec4a489c2b43020b369180800977801611084f342978d" dependencies = [ "glyph_brush_draw_cache", "glyph_brush_layout", - "log", "ordered-float", "rustc-hash", "twox-hash", @@ -1549,13 +1434,13 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda" +checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" dependencies = [ "bitflags", "gpu-descriptor-types", - "hashbrown 0.11.2", + "hashbrown 0.12.3", ] [[package]] @@ -1569,9 +1454,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", @@ -1592,30 +1477,30 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", "bumpalo", ] [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -1626,6 +1511,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hexf-parse" version = "0.2.1" @@ -1633,20 +1527,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] -name = "hound" -version = "3.4.0" +name = "html-escape" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.2", + "itoa", ] [[package]] @@ -1674,9 +1571,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -1687,7 +1584,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.2", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -1698,9 +1595,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http", "hyper", @@ -1711,12 +1608,11 @@ dependencies = [ [[package]] name = "iced-x86" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158f5204401d08f91d19176112146d75e99b3cf745092e268fa7be33e09adcec" +checksum = "1dd04b950d75b3498320253b17fb92745b2cc79ead8814aede2f7c1bab858bec" dependencies = [ "lazy_static", - "static_assertions", ] [[package]] @@ -1727,11 +1623,10 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -1766,9 +1661,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown 0.12.3", @@ -1776,14 +1671,14 @@ dependencies = [ [[package]] name = "indoc" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "inkwell" version = "0.1.0" -source = "git+https://github.com/roc-lang/inkwell?branch=master#9b63d543eaf996aa91fdeb20a2bc8b8558775648" +source = "git+https://github.com/roc-lang/inkwell?branch=inkwell-llvm-15#95a3a13c99289539f5cdd91193a4def38711117d" dependencies = [ "either", "inkwell_internals", @@ -1795,8 +1690,8 @@ dependencies = [ [[package]] name = "inkwell_internals" -version = "0.5.0" -source = "git+https://github.com/roc-lang/inkwell?branch=master#9b63d543eaf996aa91fdeb20a2bc8b8558775648" +version = "0.7.0" +source = "git+https://github.com/roc-lang/inkwell?branch=inkwell-llvm-15#95a3a13c99289539f5cdd91193a4def38711117d" dependencies = [ "proc-macro2", "quote", @@ -1805,19 +1700,19 @@ dependencies = [ [[package]] name = "inplace_it" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" +checksum = "e567468c50f3d4bc7397702e09b380139f9b9288b4e909b070571007f8b5bf78" [[package]] name = "insta" -version = "1.20.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a931b01c76064c5be919faa2ef0dc570e9a889dcd1e5fef08a8ca6eb4d6c0b" +checksum = "fea5b3894afe466b4bcf0388630fc15e11938a6074af0cd637c825ba2ec8a099" dependencies = [ "console", + "lazy_static", "linked-hash-map", - "once_cell", "similar", "yaml-rust", ] @@ -1836,15 +1731,19 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.7.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb" +checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "itertools" @@ -1866,29 +1765,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[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", -] +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jni-sys" @@ -1896,29 +1775,20 @@ 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.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] [[package]] name = "jwalk" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172752e853a067cbce46427de8470ddf308af7fd8ceaf9b682ef31a5021b6bb9" +checksum = "5dbcda57db8b6dc067e589628b7348639014e793d9e8137d8cf215e8b133a0bd" dependencies = [ "crossbeam", "rayon", @@ -1946,46 +1816,36 @@ 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.135" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if 1.0.0", "winapi", ] [[package]] -name = "libmimalloc-sys" -version = "0.1.25" +name = "libm" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ca136052550448f55df7898c6dbe651c6b574fe38a0d9ea687a9f8088a2e2c" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8c7cbf8b89019683667e347572e6d55a7df7ea36b0c4ce69961b0cde67b174" dependencies = [ "cc", + "libc", ] [[package]] @@ -1996,15 +1856,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.0.46" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "llvm-sys" -version = "130.0.4" +version = "130.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb6ea20e8a348f6db0b43a7f009fa7d981d22edf4cbe2e0c7b2247dbb25be61" +checksum = "b54ec4a457c4b55ffb2bd56ed44841161ae933fd4fe3dc379748fcd4193661d4" dependencies = [ "cc", "lazy_static", @@ -2015,9 +1875,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -2032,15 +1892,6 @@ 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 = "mach_object" version = "0.1.17" @@ -2081,12 +1932,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.5.0" @@ -2104,9 +1949,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.5.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] @@ -2120,6 +1965,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.23.1" @@ -2136,9 +1990,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.29" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f64ad83c969af2e732e907564deb0d0ed393cec4af80776f77dd77a1a427698" +checksum = "9dcb174b18635f7561a0c6c9fc2ce57218ac7523cf72c50af80e2d79ab8f3ba1" dependencies = [ "libmimalloc-sys", ] @@ -2155,52 +2009,33 @@ 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.5.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.36.1", + "windows-sys 0.45.0", ] [[package]] name = "morphic_lib" version = "0.1.0" dependencies = [ - "sha2", + "blake3", + "roc_collections", "smallvec", "thiserror", "typed-arena", @@ -2232,20 +2067,7 @@ 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", + "ndk-sys", "num_enum", "thiserror", ] @@ -2265,25 +2087,10 @@ dependencies = [ "lazy_static", "libc", "log", - "ndk 0.5.0", + "ndk", "ndk-context", "ndk-macro", - "ndk-sys 0.2.2", -] - -[[package]] -name = "ndk-glue" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f" -dependencies = [ - "lazy_static", - "libc", - "log", - "ndk 0.6.0", - "ndk-context", - "ndk-macro", - "ndk-sys 0.3.0", + "ndk-sys", ] [[package]] @@ -2305,15 +2112,6 @@ 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" version = "0.1.0" @@ -2333,38 +2131,51 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ "bitflags", "cc", "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.24.1" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags", "cfg-if 1.0.0", "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", + "static_assertions", ] [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -2372,9 +2183,18 @@ dependencies = [ [[package]] name = "nonempty" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f1f8e5676e1a1f2ee8b21f38238e1243c827531c9435624c7bfb305102cee4" +checksum = "aeaf4ad7403de93e699c191202f017118df734d3850b01e13a3a8b2e6953d3c9" + +[[package]] +name = "normpath" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972dec05f98e7c787ede35d7a9ea4735eb7788c299287352757b3def6cc1f7b5" +dependencies = [ + "windows-sys 0.45.0", +] [[package]] name = "nu-ansi-term" @@ -2386,17 +2206,6 @@ 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.15" @@ -2404,32 +2213,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "num_enum" -version = "0.5.7" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.7" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2437,15 +2247,6 @@ dependencies = [ "syn", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "objc" version = "0.2.7" @@ -2487,54 +2288,22 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "crc32fast", "flate2", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "indexmap", "memchr", ] -[[package]] -name = "oboe" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1" -dependencies = [ - "jni", - "ndk 0.6.0", - "ndk-context", - "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.15.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "oorandom" @@ -2544,18 +2313,18 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "ordered-float" -version = "3.0.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2" +checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf" dependencies = [ "num-traits", ] [[package]] name = "os_str_bytes" -version = "6.1.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "output_vt100" @@ -2574,29 +2343,29 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owned_ttf_parser" -version = "0.15.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1e509cfe7a12db2a90bfa057dfcdbc55a347f5da677c506b53dd099cfec9d" +checksum = "e25e9fb15717794fae58ab55c26e044103aad13186fbb625893f9a3bbcc24228" dependencies = [ "ttf-parser", ] [[package]] name = "packed_struct" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1" +checksum = "36b29691432cc9eff8b282278473b63df73bea49bc3ec5e67f31a3ae9c3ec190" dependencies = [ - "bitvec 0.22.3", + "bitvec", "packed_struct_codegen", "serde", ] [[package]] name = "packed_struct_codegen" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e3692b867ec1d48ccb441e951637a2cc3130d0912c0059e48319e1c83e44bc" +checksum = "9cd6706dfe50d53e0f6aa09e12c034c44faacd23e966ae5a209e8bdb8f179f98" dependencies = [ "proc-macro2", "quote", @@ -2605,9 +2374,9 @@ dependencies = [ [[package]] name = "page_size" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" +checksum = "1b7663cbd190cfd818d08efa8497f6cd383076688c49a391ef7c0d03cd12b561" dependencies = [ "libc", "winapi", @@ -2645,7 +2414,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -2655,14 +2424,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.7", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if 1.0.0", "instant", @@ -2674,23 +2443,17 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-sys 0.36.1", + "windows-sys 0.45.0", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "peg" version = "0.8.1" @@ -2720,15 +2483,15 @@ checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.3.1" +version = "2.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" +checksum = "8cbd939b234e95d72bc393d51788aec68aeeb5d51e748ca08ff3aad58cb722f7" dependencies = [ "thiserror", "ucd-trie", @@ -2736,9 +2499,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.3.1" +version = "2.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502b62a6d0245378b04ffe0a7fb4f4419a4815fce813bd8a0ec89a56e07d67b1" +checksum = "a81186863f3d0a27340815be8f2078dd8050b14cd71913db9fbda795e5f707d7" dependencies = [ "pest", "pest_generator", @@ -2746,9 +2509,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.3.1" +version = "2.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451e629bf49b750254da26132f1a5a9d11fd8a95a3df51d15c4abd1ba154cb6c" +checksum = "75a1ef20bf3193c15ac345acb32e26b3dc3223aff4d77ae4fc5359567683796b" dependencies = [ "pest", "pest_meta", @@ -2759,13 +2522,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.3.1" +version = "2.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec162c71c45e269dfc3fc2916eaeb97feab22993a21bcce4721d08cd7801a6" +checksum = "5e3b284b1f13a20dc5ebc90aff59a51b8d7137c221131b52a7260c08cbc1cc80" dependencies = [ "once_cell", "pest", - "sha1", + "sha2", ] [[package]] @@ -2824,9 +2587,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plotters" @@ -2848,18 +2611,18 @@ checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_assertions" @@ -2875,58 +2638,34 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "thiserror", - "toml", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "once_cell", + "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f61dcf0b917cd75d4521d7343d1ffff3d1583054133c9b5cbea3375c703c40d" +checksum = "74605f360ce573babfe43964cbe520294dcb081afbf8c108fc6e23036b4da2df" [[package]] name = "proptest" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70" dependencies = [ "bit-set", "bitflags", @@ -2940,6 +2679,7 @@ dependencies = [ "regex-syntax", "rusty-fork", "tempfile", + "unarray", ] [[package]] @@ -2965,15 +2705,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-xml" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" -dependencies = [ - "memchr", -] - [[package]] name = "quickcheck" version = "1.0.3" @@ -2998,19 +2729,13 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" - [[package]] name = "radium" version = "0.7.0" @@ -3050,9 +2775,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -3077,9 +2802,9 @@ dependencies = [ [[package]] name = "range-alloc" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" [[package]] name = "raw-window-handle" @@ -3092,21 +2817,19 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -3116,9 +2839,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -3136,9 +2859,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -3156,9 +2879,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -3171,15 +2894,18 @@ dependencies = [ [[package]] name = "remove_dir_all" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882f368737489ea543bc5c340e6f3d34a28c39980bd9a979e47322b26f60ac40" +checksum = "7174320e07c29945955cedd70b865995b286847111c8308d349a1f3a9e3af555" dependencies = [ + "aligned", + "cfg-if 1.0.0", + "cvt", + "fs_at", + "lazy_static", "libc", - "log", - "num_cpus", - "rayon", - "winapi", + "normpath", + "windows-sys 0.45.0", ] [[package]] @@ -3204,11 +2930,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "base64", + "base64 0.21.0", "bytes", "encoding_rs", "futures-core", @@ -3258,9 +2984,9 @@ dependencies = [ [[package]] name = "rlimit" -version = "0.6.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0bf25554376fd362f54332b8410a625c71f15445bca32ffdfdf4ec9ac91726" +checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e" dependencies = [ "libc", ] @@ -3273,6 +2999,7 @@ dependencies = [ "morphic_lib", "roc_collections", "roc_debug_flags", + "roc_error_macros", "roc_module", "roc_mono", ] @@ -3306,25 +3033,46 @@ dependencies = [ "winapi", ] +[[package]] +name = "roc_bitcode" +version = "0.0.1" +dependencies = [ + "dunce", + "roc_command_utils", + "tempfile", +] + +[[package]] +name = "roc_bitcode_bc" +version = "0.0.1" +dependencies = [ + "dunce", + "roc_command_utils", + "tempfile", +] + [[package]] name = "roc_build" version = "0.0.1" dependencies = [ "bumpalo", - "const_format", + "indoc", "inkwell", "libloading", - "roc_builtins", + "roc_bitcode", "roc_can", "roc_collections", + "roc_command_utils", "roc_constrain", "roc_error_macros", "roc_gen_dev", "roc_gen_llvm", "roc_gen_wasm", + "roc_linker", "roc_load", "roc_module", "roc_mono", + "roc_packaging", "roc_parse", "roc_problem", "roc_region", @@ -3334,7 +3082,6 @@ dependencies = [ "roc_target", "roc_types", "roc_unify", - "roc_utils", "serde_json", "target-lexicon", "tempfile", @@ -3345,12 +3092,10 @@ dependencies = [ name = "roc_builtins" version = "0.0.1" dependencies = [ - "dunce", "roc_collections", "roc_module", "roc_region", "roc_target", - "roc_utils", "tempfile", ] @@ -3358,7 +3103,7 @@ dependencies = [ name = "roc_can" version = "0.0.1" dependencies = [ - "bitvec 1.0.1", + "bitvec", "bumpalo", "indoc", "pretty_assertions", @@ -3380,23 +3125,23 @@ name = "roc_cli" version = "0.0.1" dependencies = [ "bumpalo", - "clap 3.2.20", + "clap 3.2.23", "cli_utils", "const_format", "criterion", - "errno", + "errno 0.3.0", "indoc", "inkwell", "libc", "libloading", "mimalloc", - "once_cell", "parking_lot 0.12.1", "pretty_assertions", "roc_build", "roc_builtins", "roc_can", "roc_collections", + "roc_command_utils", "roc_docs", "roc_editor", "roc_error_macros", @@ -3416,7 +3161,6 @@ dependencies = [ "roc_target", "roc_test_utils", "roc_tracing", - "roc_utils", "roc_wasm_interp", "serial_test", "signal-hook", @@ -3433,8 +3177,8 @@ dependencies = [ "bumpalo", "palette", "roc_ast", + "roc_error_utils", "roc_module", - "roc_utils", "serde", "snafu", ] @@ -3443,15 +3187,19 @@ dependencies = [ name = "roc_collections" version = "0.0.1" dependencies = [ - "bitvec 1.0.1", + "bitvec", "bumpalo", "fnv", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "im", "im-rc", "wyhash", ] +[[package]] +name = "roc_command_utils" +version = "0.0.1" + [[package]] name = "roc_constrain" version = "0.0.1" @@ -3490,7 +3238,6 @@ dependencies = [ name = "roc_derive_key" version = "0.0.1" dependencies = [ - "roc_can", "roc_collections", "roc_error_macros", "roc_module", @@ -3527,7 +3274,7 @@ dependencies = [ name = "roc_docs_cli" version = "0.0.1" dependencies = [ - "clap 3.2.20", + "clap 3.2.23", "libc", "roc_docs", ] @@ -3559,6 +3306,7 @@ dependencies = [ "roc_can", "roc_code_markup", "roc_collections", + "roc_command_utils", "roc_load", "roc_module", "roc_packaging", @@ -3569,8 +3317,6 @@ dependencies = [ "roc_solve", "roc_types", "roc_unify", - "roc_utils", - "rodio", "serde", "snafu", "tempfile", @@ -3586,6 +3332,13 @@ dependencies = [ name = "roc_error_macros" version = "0.0.1" +[[package]] +name = "roc_error_utils" +version = "0.0.1" +dependencies = [ + "snafu", +] + [[package]] name = "roc_exhaustive" version = "0.0.1" @@ -3641,6 +3394,7 @@ dependencies = [ "inkwell", "morphic_lib", "roc_alias_analysis", + "roc_bitcode_bc", "roc_builtins", "roc_collections", "roc_debug_flags", @@ -3657,7 +3411,7 @@ dependencies = [ name = "roc_gen_wasm" version = "0.0.1" dependencies = [ - "bitvec 1.0.1", + "bitvec", "bumpalo", "roc_builtins", "roc_collections", @@ -3673,18 +3427,23 @@ dependencies = [ name = "roc_glue" version = "0.0.1" dependencies = [ + "backtrace", "bumpalo", - "clap 3.2.20", "cli_utils", "dircpy", "fnv", "indexmap", "indoc", + "libc", + "libloading", "pretty_assertions", + "roc_build", "roc_builtins", "roc_can", "roc_collections", "roc_error_macros", + "roc_gen_llvm", + "roc_linker", "roc_load", "roc_module", "roc_mono", @@ -3692,7 +3451,6 @@ dependencies = [ "roc_reporting", "roc_std", "roc_target", - "roc_test_utils", "roc_tracing", "roc_types", "strum", @@ -3705,8 +3463,9 @@ dependencies = [ name = "roc_highlight" version = "0.0.1" dependencies = [ - "peg", - "roc_code_markup", + "html-escape", + "roc_parse", + "roc_region", ] [[package]] @@ -3738,16 +3497,18 @@ dependencies = [ "indoc", "libc", "mach_object", - "memmap2 0.5.7", + "memmap2 0.5.10", "object", - "roc_build", "roc_collections", "roc_error_macros", "roc_load", + "roc_module", "roc_mono", "roc_packaging", "roc_reporting", + "roc_target", "serde", + "serial_test", "target-lexicon", "tempfile", ] @@ -3822,9 +3583,9 @@ dependencies = [ name = "roc_mono" version = "0.0.1" dependencies = [ - "bitvec 1.0.1", + "bitvec", "bumpalo", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "parking_lot 0.12.1", "roc_builtins", "roc_can", @@ -3856,8 +3617,6 @@ dependencies = [ "bumpalo", "flate2", "fs_extra", - "indoc", - "pretty_assertions", "reqwest", "roc_error_macros", "roc_parse", @@ -3881,7 +3640,6 @@ dependencies = [ "roc_collections", "roc_module", "roc_region", - "roc_test_utils", ] [[package]] @@ -3983,7 +3741,6 @@ dependencies = [ "strip-ansi-escapes", "target-lexicon", "tempfile", - "test_gen", ] [[package]] @@ -3993,7 +3750,9 @@ dependencies = [ "bumpalo", "console_error_panic_hook", "futures", + "getrandom", "js-sys", + "roc_bitcode", "roc_builtins", "roc_collections", "roc_gen_wasm", @@ -4003,7 +3762,6 @@ dependencies = [ "roc_reporting", "roc_target", "roc_types", - "roc_utils", "tempfile", "wasi_libc_sys", "wasm-bindgen", @@ -4018,6 +3776,7 @@ dependencies = [ "distance", "indoc", "insta", + "itertools 0.10.5", "pretty_assertions", "roc_builtins", "roc_can", @@ -4101,6 +3860,12 @@ name = "roc_std" version = "0.0.1" dependencies = [ "arrayvec 0.7.2", + "libc", + "pretty_assertions", + "quickcheck", + "quickcheck_macros", + "serde", + "serde_json", "static_assertions", ] @@ -4118,7 +3883,7 @@ name = "roc_test_utils" version = "0.0.1" dependencies = [ "pretty_assertions", - "remove_dir_all 0.7.0", + "remove_dir_all 0.8.1", ] [[package]] @@ -4150,30 +3915,21 @@ name = "roc_unify" version = "0.0.1" dependencies = [ "bitflags", - "roc_can", "roc_collections", "roc_debug_flags", "roc_error_macros", "roc_module", - "roc_solve_problem", "roc_tracing", "roc_types", ] [[package]] -name = "roc_utils" +name = "roc_wasm_interp" version = "0.0.1" dependencies = [ - "snafu", -] - -[[package]] -name = "roc_wasm_interp" -version = "0.1.0" -dependencies = [ - "bitvec 1.0.1", + "bitvec", "bumpalo", - "clap 3.2.20", + "clap 3.2.23", "rand", "roc_wasm_module", ] @@ -4182,24 +3938,11 @@ dependencies = [ name = "roc_wasm_module" version = "0.0.1" dependencies = [ - "bitvec 1.0.1", + "bitvec", "bumpalo", "roc_error_macros", ] -[[package]] -name = "rodio" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0939e9f626e6c6f1989adb6226a039c855ca483053f0ee7c98b90e41cf731e" -dependencies = [ - "claxon", - "cpal", - "hound", - "lewton", - "minimp3", -] - [[package]] name = "rustc-demangle" version = "0.1.21" @@ -4218,28 +3961,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.12", + "semver 1.0.16", ] [[package]] name = "rustix" -version = "0.35.7" +version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ "bitflags", - "errno", + "errno 0.2.8", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.36.1", + "windows-sys 0.45.0", ] [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -4249,18 +3992,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] name = "rustversion" -version = "1.0.7" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "rusty-fork" @@ -4281,13 +4024,13 @@ source = "git+https://github.com/roc-lang/rustyline?rev=e74333c#e74333c0d618896b dependencies = [ "bitflags", "cfg-if 1.0.0", - "clipboard-win 4.4.1", + "clipboard-win 4.5.0", "dirs-next", "fd-lock", "libc", "log", "memchr", - "nix 0.23.1", + "nix 0.23.2", "radix_trie", "scopeguard", "smallvec", @@ -4308,9 +4051,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "same-file" @@ -4323,9 +4066,9 @@ dependencies = [ [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -4354,9 +4097,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.12" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "semver-parser" @@ -4369,18 +4112,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.144" +version = "1.0.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20" dependencies = [ "serde_derive", ] [[package]] name = "serde-xml-rs" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa" +checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" dependencies = [ "log", "serde", @@ -4400,9 +4143,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f" dependencies = [ "proc-macro2", "quote", @@ -4411,11 +4154,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ - "itoa 1.0.2", + "itoa", "ryu", "serde", ] @@ -4427,16 +4170,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.2", + "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec0091e1f5aa338283ce049bd9dfefd55e1f168ac233e85c1ffe0038fb48cbe" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ "indexmap", "ryu", @@ -4446,9 +4189,9 @@ dependencies = [ [[package]] name = "serial_test" -version = "0.9.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92761393ee4dc3ff8f4af487bd58f4307c9329bbedea02cac0089ad9c411e153" +checksum = "538c30747ae860d6fb88330addbbd3e0ddbe46d662d032855596d8a8ca260611" dependencies = [ "dashmap", "futures", @@ -4460,32 +4203,20 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "0.9.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6f5d1c3087fb119617cff2966fe3808a80e5eb59a8c1601d5994d66f4346a5" +checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", "syn", ] -[[package]] -name = "sha1" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest", -] - [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -4501,17 +4232,11 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -4519,18 +4244,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "similar" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" [[package]] name = "siphasher" @@ -4550,19 +4275,11 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - -[[package]] -name = "slice-deque" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ - "libc", - "mach", - "winapi", + "autocfg", ] [[package]] @@ -4576,9 +4293,20 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "smart-default" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "smithay-client-toolkit" @@ -4609,8 +4337,8 @@ dependencies = [ "dlib", "lazy_static", "log", - "memmap2 0.5.7", - "nix 0.24.1", + "memmap2 0.5.10", + "nix 0.24.3", "pkg-config", "wayland-client", "wayland-cursor", @@ -4629,9 +4357,9 @@ dependencies = [ [[package]] name = "snafu" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2" +checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045" dependencies = [ "backtrace", "doc-comment", @@ -4640,9 +4368,9 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5" +checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2" dependencies = [ "heck", "proc-macro2", @@ -4652,9 +4380,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -4676,18 +4404,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[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 = "str-buf" version = "1.0.6" @@ -4739,9 +4467,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -4767,19 +4495,19 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" +checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" [[package]] name = "tempfile" -version = "3.3.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if 1.0.0", - "fastrand", "libc", + "rand", "redox_syscall", "remove_dir_all 0.5.3", "winapi", @@ -4787,31 +4515,19 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "test_derive" version = "0.0.1" dependencies = [ "bumpalo", - "indoc", "insta", - "pretty_assertions", "roc_builtins", "roc_can", "roc_collections", @@ -4841,10 +4557,12 @@ dependencies = [ "lazy_static", "libc", "libloading", + "roc_bitcode", "roc_build", "roc_builtins", "roc_can", "roc_collections", + "roc_command_utils", "roc_constrain", "roc_debug_flags", "roc_error_macros", @@ -4864,7 +4582,6 @@ dependencies = [ "roc_target", "roc_types", "roc_unify", - "roc_utils", "roc_wasm_interp", "roc_wasm_module", "target-lexicon", @@ -4927,24 +4644,24 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", @@ -4953,10 +4670,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if 1.0.0", "once_cell", ] @@ -4971,21 +4689,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.11" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "itoa 1.0.2", - "libc", - "num_threads", + "itoa", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.4" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] [[package]] name = "tinytemplate" @@ -5008,15 +4735,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.24.1" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -5026,7 +4753,7 @@ dependencies = [ "num_cpus", "pin-project-lite", "socket2", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -5042,9 +4769,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -5056,13 +4783,30 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -5071,9 +4815,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", @@ -5094,9 +4838,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -5144,15 +4888,15 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ttf-parser" -version = "0.15.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" +checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" [[package]] name = "twox-hash" @@ -5167,21 +4911,27 @@ dependencies = [ [[package]] name = "typed-arena" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicase" @@ -5194,15 +4944,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -5215,21 +4965,21 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "untrusted" @@ -5239,15 +4989,21 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf8-width" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" + [[package]] name = "utf8parse" version = "0.2.0" @@ -5256,22 +5012,21 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" [[package]] name = "uuid" -version = "1.1.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ "getrandom", ] [[package]] name = "valgrind" -version = "0.1.0" +version = "0.0.1" dependencies = [ "bumpalo", "cli_utils", "indoc", "roc_build", - "roc_cli", "roc_linker", "roc_load", "roc_mono", @@ -5370,14 +5125,14 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" name = "wasi_libc_sys" version = "0.0.1" dependencies = [ - "roc_utils", + "roc_command_utils", ] [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -5385,9 +5140,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -5400,9 +5155,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -5412,9 +5167,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5422,9 +5177,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -5435,20 +5190,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "wayland-client" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" dependencies = [ "bitflags", "downcast-rs", "libc", - "nix 0.22.3", + "nix 0.24.3", "scoped-tls", "wayland-commons", "wayland-scanner", @@ -5457,11 +5212,11 @@ dependencies = [ [[package]] name = "wayland-commons" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" dependencies = [ - "nix 0.22.3", + "nix 0.24.3", "once_cell", "smallvec", "wayland-sys", @@ -5469,20 +5224,20 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" dependencies = [ - "nix 0.22.3", + "nix 0.24.3", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" dependencies = [ "bitflags", "wayland-client", @@ -5492,9 +5247,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" dependencies = [ "proc-macro2", "quote", @@ -5503,9 +5258,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.29.4" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" dependencies = [ "dlib", "lazy_static", @@ -5534,9 +5289,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] @@ -5669,25 +5424,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +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 = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "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-sys" version = "0.42.0" @@ -5695,85 +5446,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "winit" @@ -5792,9 +5537,9 @@ dependencies = [ "libc", "log", "mio", - "ndk 0.5.0", - "ndk-glue 0.5.2", - "ndk-sys 0.2.2", + "ndk", + "ndk-glue", + "ndk-sys", "objc", "parking_lot 0.11.2", "percent-encoding", @@ -5808,6 +5553,15 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "winnow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" @@ -5828,42 +5582,55 @@ dependencies = [ [[package]] name = "wyz" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" -dependencies = [ - "tap", -] - -[[package]] -name = "wyz" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] [[package]] name = "x11-clipboard" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7468a5768fea473e6c8c0d4b60d6d7001a64acceaac267207ca0281e1337e8" +checksum = "0827f86aa910c4e73329a4f619deabe88ebb4b042370bf023c2d5d8b4eb54695" dependencies = [ - "xcb", + "x11rb", ] [[package]] name = "x11-dl" -version = "2.19.1" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" dependencies = [ - "lazy_static", "libc", + "once_cell", "pkg-config", ] +[[package]] +name = "x11rb" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" +dependencies = [ + "gethostname", + "nix 0.24.3", + "winapi", + "winapi-wsapoll", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" +dependencies = [ + "nix 0.24.3", +] + [[package]] name = "xattr" version = "0.2.3" @@ -5873,17 +5640,6 @@ dependencies = [ "libc", ] -[[package]] -name = "xcb" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b127bf5bfe9dbb39118d6567e3773d4bbc795411a8e1ef7b7e056bccac0011a9" -dependencies = [ - "bitflags", - "libc", - "quick-xml", -] - [[package]] name = "xcursor" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index 2f3631b2b2..e405349946 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,44 +1,40 @@ [workspace] members = [ - "crates/compiler/*", - "crates/vendor/*", - "crates/glue", - "crates/editor", - "crates/ast", - "crates/cli", - "crates/code_markup", - "crates/highlight", - "crates/error_macros", - "crates/reporting", - "crates/packaging", - "crates/repl_cli", - "crates/repl_eval", - "crates/repl_test", - "crates/repl_wasm", - "crates/repl_expect", - "crates/test_utils", - "crates/valgrind", - "crates/tracing", - "crates/utils", - "crates/docs", - "crates/docs_cli", - "crates/linker", - "crates/wasi-libc-sys", - "crates/wasm_module", - "crates/wasm_interp", + "crates/compiler/*", + "crates/vendor/*", + "crates/glue", + "crates/editor", + "crates/ast", + "crates/cli", + "crates/cli_utils", + "crates/code_markup", + "crates/highlight", + "crates/error_macros", + "crates/reporting", + "crates/packaging", + "crates/repl_cli", + "crates/repl_eval", + "crates/repl_test", + "crates/repl_wasm", + "crates/repl_expect", + "crates/roc_std", + "crates/test_utils", + "crates/valgrind", + "crates/tracing", + "crates/utils/*", + "crates/docs", + "crates/docs_cli", + "crates/linker", + "crates/wasi-libc-sys", + "crates/wasm_module", + "crates/wasm_interp", ] + exclude = [ - "ci/benchmarks/bench-runner", - # Examples sometimes have Rust hosts in their platforms. The compiler should ignore those. - "crates/cli_testing_examples", - "examples", - # Ignore building these normally. They are only imported by tests. - # The tests will still correctly build them. - "crates/cli_utils", - "crates/compiler/test_mono_macros", - "crates/compiler/str", - # `cargo build` would cause roc_std to be built with default features which errors on windows - "crates/roc_std", + "ci/benchmarks/bench-runner", + # Examples sometimes have Rust hosts in their platforms. The compiler should ignore those. + "crates/cli_testing_examples", + "examples", ] # Needed to be able to run `cargo run -p roc_cli --no-default-features` - # see www/build.sh for more. @@ -47,6 +43,13 @@ exclude = [ # workspace, and without `resolver = "2"` here, you can't use `-p` like this. resolver = "2" +[workspace.package] +authors = ["The Roc Contributors"] +edition = "2021" +license = "UPL-1.0" +repository = "https://github.com/roc-lang/roc" +version = "0.0.1" + [workspace.dependencies] # NOTE: roc-lang/inkwell is a fork of TheDan64/inkwell which does not change anything. # @@ -65,72 +68,121 @@ resolver = "2" # 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/roc-lang/inkwell", branch = "master", features = [ "llvm13-0" ] } +inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-15", features = ["llvm13-0"] } -arrayvec = "0.7.2" +arrayvec = "0.7.2" # update roc_std/Cargo.toml on change +backtrace = "0.3.67" +base64-url = "1.4.13" bincode = "1.3.3" +bitflags = "1.3.2" bitvec = "1.0.1" -bumpalo = { version = "3.11.1", features = ["collections"] } -capstone = "0.11.0" -clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] } -const_format = { version = "0.2.23", features = ["const_generics"] } -criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]} +blake3 = "1.3.3" +brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli +bumpalo = { version = "3.12.0", features = ["collections"] } +bytemuck = { version = "1.13.1", features = ["derive"] } +capstone = { version = "0.11.0", default-features = false } +cgmath = "0.18.0" +clap = { version = "3.2.23", default-features = false, features = ["std", "color", "suggestions"] } +colored = "2.0.0" +confy = { git = 'https://github.com/rust-cli/confy', features = ["yaml_conf"], default-features = false } +console_error_panic_hook = "0.1.7" +const_format = { version = "0.2.30", features = ["const_generics"] } +copypasta = "0.8.2" +criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"] } +criterion-perf-events = { git = "https://github.com/Anton-4/criterion-perf-events" } crossbeam = "0.8.2" +dircpy = "0.3.14" distance = "0.4.0" encode_unicode = "1.0.0" -errno = "0.2.8" +errno = "0.3.0" +flate2 = "1.0.25" fnv = "1.0.7" -fs_extra = "1.2.0" -hashbrown = { version = "0.12.3", features = [ "bumpalo" ] } -iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] } -im = "15.0.0" -im-rc = "15.0.0" -indoc = "1.0.7" -insta = "1.20.0" +fs_extra = "1.3.0" +futures = "0.3.26" +glyph_brush = "0.7.7" +hashbrown = { version = "0.13.2", features = ["bumpalo"] } +iced-x86 = { version = "1.18.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] } +im = "15.1.0" +im-rc = "15.1.0" +indexmap = "1.9.2" +indoc = "1.0.9" +insta = "1.28.0" +js-sys = "0.3.61" lazy_static = "1.4.0" -libc = "0.2.135" -libloading = "0.7.1" +libc = "0.2.139" # update roc_std/Cargo.toml on change +libfuzzer-sys = "0.4" +libloading = "0.7.4" +log = "0.4.17" mach_object = "0.1" maplit = "1.0.2" -memmap2 = "0.5.7" -mimalloc = { version = "0.1.26", default-features = false } -packed_struct = "0.10.0" -page_size = "0.4.2" +memmap2 = "0.5.10" +mimalloc = { version = "0.1.34", default-features = false } +nonempty = "0.8.1" +object = { version = "0.30.3", features = ["read", "write"] } +packed_struct = "0.10.1" +page_size = "0.5.0" +palette = "0.6.1" parking_lot = "0.12" peg = "0.8.1" -pretty_assertions = "1.3.0" -quickcheck = "1.0.3" -quickcheck_macros = "1.0.0" -regex = "1.5.5" -rustyline = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"} -rustyline-derive = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"} -serde = { version = "1.0.144", features = ["derive"] } -signal-hook = "0.3.14" -snafu = { version = "0.7.1", features = ["backtraces"] } -static_assertions = "1.1.0" +perfcnt = "0.8.0" +pest = "2.5.6" +pest_derive = "2.5.6" +pretty_assertions = "1.3.0" # update roc_std/Cargo.toml on change +proc-macro2 = "1.0.51" +proptest = "1.1.0" +pulldown-cmark = { version = "0.9.2", default-features = false } +quickcheck = "1.0.3" # update roc_std/Cargo.toml on change +quickcheck_macros = "1.0.0" # update roc_std/Cargo.toml on change +quote = "1.0.23" +rand = "0.8.5" +regex = "1.7.1" +remove_dir_all = "0.8.1" +reqwest = { version = "0.11.14", default-features = false, features = ["blocking", "rustls-tls"] } # default-features=false removes libopenssl as a dependency on Linux, which might not be available! +rlimit = "0.9.1" +rustyline = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" } +rustyline-derive = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" } +serde = { version = "1.0.153", features = ["derive"] } # update roc_std/Cargo.toml on change +serde-xml-rs = "0.6.0" +serde_json = "1.0.94" # update roc_std/Cargo.toml on change +serial_test = "1.0.0" +signal-hook = "0.3.15" +snafu = { version = "0.7.4", features = ["backtraces"] } +static_assertions = "1.1.0" # update roc_std/Cargo.toml on change strip-ansi-escapes = "0.1.1" strum = { version = "0.24.1", features = ["derive"] } -target-lexicon = "0.12.3" -tempfile = "3.2.0" -unicode-segmentation = "1.10.0" +strum_macros = "0.24.3" +syn = { version = "1.0.109", features = ["full", "extra-traits"] } +tar = "0.4.38" +target-lexicon = "0.12.6" +tempfile = "=3.2.0" +threadpool = "1.8.1" +tracing = { version = "0.1.37", features = ["release_max_level_off"] } +tracing-appender = "0.2.2" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } +unicode-segmentation = "1.10.1" +uuid = { version = "1.3.0", features = ["v4"] } walkdir = "2.3.2" +wasm-bindgen = "0.2.84" +wasm-bindgen-futures = "0.4.34" +wgpu = "0.12.0" +wgpu_glyph = "0.16.0" +winapi = { version = "0.3.9", features = ["memoryapi"] } +winit = "0.26.1" wyhash = "0.5.0" -# 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. -object = { version = "0.29.0", features = ["read", "write"] } - # Optimizations based on https://deterministic.space/high-performance-rust.html [profile.release] -lto = "thin" codegen-units = 1 # debug = true # enable when profiling [profile.bench] -lto = "thin" codegen-units = 1 +lto = "thin" [profile.release-with-debug] -inherits = "release" debug = true +inherits = "release" + +[profile.release-with-lto] +lto = "thin" # TODO: We could consider full here since this is only used for packaged release on github. +inherits = "release" diff --git a/FAQ.md b/FAQ.md index 02e21ca116..ff7fcfa6cd 100644 --- a/FAQ.md +++ b/FAQ.md @@ -276,7 +276,7 @@ So why does Roc have the specific syntax changes it does? Here are some brief ex - No `<|` operator. In Elm, I almost exclusively found myself wanting to use this in conjunction with anonymous functions (e.g. `foo <| \bar -> ...`) or conditionals (e.g. `foo <| if bar then ...`). In Roc you can do both of these without the `<|`. That means the main remaining use for `<|` is to reduce parentheses, but I tend to think `|>` is better at that (or else the parens are fine), so after the other syntactic changes, I considered `<|` an unnecessary stylistic alternative to `|>` or parens. - The `|>` operator passes the expression before the `|>` as the _first_ argument to the function after the `|>` instead of as the last argument. See the section on currying for details on why this works this way. - `:` instead of `type alias` - I like to avoid reserved keywords for terms that are desirable in userspace, so that people don't have to name things `typ` because `type` is a reserved keyword, or `clazz` because `class` is reserved. (I couldn't think of satisfactory alternatives for `as`, `when`, `is`, or `if` other than different reserved keywords. I could see an argument for `then`—and maybe even `is`—being replaced with a `->` or `=>` or something, but I don't anticipate missing either of those words much in userspace. `then` is used in JavaScript promises, but I think there are several better names for that function.) -- No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.) +- No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.) Acronyms also use camelCase despite being capitalized in English, eg. `xmlHttpRequest` for a variable and `XmlHttpRequest` for a type. Each word starts with a capital letter, so if acronyms are only capitals it's harder to see where the words start. eg. `XMLHTTPRequest` is less clear than `XmlHttpRequest`, unless you already know the acronyms. - Trailing commas - I've seen people walk away (in some cases physically!) from Elm as soon as they saw the leading commas in collection literals. While I think they've made a mistake by not pushing past this aesthetic preference to give the language a chance, I also would prefer not put them in a position to make such a mistake in the first place. Secondarily, while I'm personally fine with either style, between the two I prefer the look of trailing commas. - The `!` unary prefix operator. I didn't want to have a `Basics` module (more on that in a moment), and without `Basics`, this would either need to be called fully-qualified (`Bool.not`) or else a module import of `Bool.{ not }` would be necessary. Both seemed less nice than supporting the `!` prefix that's common to so many widely-used languages, especially when we already have a unary prefix operator of `-` for negation (e.g. `-x`). - `!=` for the inequality operator (instead of Elm's `/=`) - this one pairs more naturally with the `!` prefix operator and is also very common in other languages. diff --git a/README.md b/README.md index 0ce48d4b8c..3d122ce452 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,28 @@ Roc is not ready for a 0.1 release yet, but we do have: - [frequently asked questions](https://github.com/roc-lang/roc/blob/main/FAQ.md) - [Zulip chat](https://roc.zulipchat.com) for help, questions and discussions -If you'd like to get involved in contributing to the language, the Zulip chat is also the best place to get help with [good first issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). +If you'd like to contribute, check out [good first issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). Don't hesitate to ask for help on our [Zulip chat](https://roc.zulipchat.com), we're friendly! ## Sponsors -We are very grateful to our sponsors [NoRedInk](https://www.noredink.com/), [rwx](https://www.rwx.com), and [Tweede golf](https://tweedegolf.nl/en). +We are very grateful for our corporate sponsors [Vendr](https://www.vendr.com/), [rwx](https://www.rwx.com), and [Tweede golf](https://tweedegolf.nl/en). -[NoRedInk logo](https://www.noredink.com/) +[Vendr logo](https://www.vendr.com)      -[rwx logo](https://www.rwx.com) +[rwx logo](https://www.rwx.com)      [tweede golf logo](https://tweedegolf.nl/en) -If you or your employer would like to sponsor Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)! +If you would like your company to become a corporate sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)! + +We'd also like to express our gratitude to each and every one of our fantastic [GitHub sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more: + +* [Christopher Dolan](https://github.com/cdolan) +* [Nick Gravgaard](https://github.com/nickgravgaard) +* [Aaron White](https://github.com/aaronwhite) +* [Zeljko Nesic](https://github.com/popara) +* [Shritesh Bhattarai](https://github.com/shritesh) +* [Richard Feldman](https://github.com/rtfeldman) +* [Ayaz Hafiz](https://github.com/ayazhafiz) + +Thank you all so much for helping Roc progress! diff --git a/ci/benchmarks/bench-runner/Cargo.lock b/ci/benchmarks/bench-runner/Cargo.lock deleted file mode 100644 index 465b503f3f..0000000000 --- a/ci/benchmarks/bench-runner/Cargo.lock +++ /dev/null @@ -1,422 +0,0 @@ -# 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 = "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 = "bench-runner" -version = "0.0.1" -dependencies = [ - "clap", - "data-encoding", - "is_executable", - "regex", - "ring", -] - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "bumpalo" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[package]] -name = "cc" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "3.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "clap_lex", - "indexmap", - "lazy_static", - "strsim", - "termcolor", - "textwrap", -] - -[[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]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "indexmap" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "is_executable" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" -dependencies = [ - "winapi", -] - -[[package]] -name = "js-sys" -version = "0.3.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" - -[[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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" - -[[package]] -name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - -[[package]] -name = "os_str_bytes" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" -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 = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[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.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" -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 = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasm-bindgen" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" - -[[package]] -name = "web-sys" -version = "0.3.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[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" diff --git a/ci/benchmarks/bench-runner/Cargo.toml b/ci/benchmarks/bench-runner/Cargo.toml index 0534892f3c..477b6672ae 100644 --- a/ci/benchmarks/bench-runner/Cargo.toml +++ b/ci/benchmarks/bench-runner/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] clap = { version = "3.1.15", features = ["derive"] } -regex = "1.5.5" +data-encoding = "2.3.2" is_executable = "1.0.1" +regex = "1.5.5" ring = "0.16.20" -data-encoding = "2.3.2" \ No newline at end of file diff --git a/ci/build_basic_cli.sh b/ci/build_basic_cli.sh index f472270ba3..5c78ce1df2 100755 --- a/ci/build_basic_cli.sh +++ b/ci/build_basic_cli.sh @@ -5,17 +5,24 @@ set -euxo pipefail git clone https://github.com/roc-lang/basic-cli.git -# Get the url of the latest release. We're not using the latest main source code for easier reproducibility. -RELEASE_URL=$(./ci/get_latest_release_url.sh $1) +cd basic-cli +git checkout new-release +cd .. -# get the archive from the url -curl -OL $RELEASE_URL +if [ "$(uname -m)" == "x86_64" ] && [ "$(uname -s)" == "Linux" ]; then + sudo apt-get install musl-tools + cd basic-cli/src # we cd to install the target for the right rust version + rustup target add x86_64-unknown-linux-musl + cd ../.. +fi + +mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "$1") ./roc_nightly.tar.gz # decompress the tar -ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf +tar -xzvf roc_nightly.tar.gz # delete tar -ls | grep -v "roc_nightly.*tar\.gz" | xargs rm -rf +rm roc_nightly.tar.gz # simplify dir name mv roc_nightly* roc_nightly @@ -23,14 +30,14 @@ mv roc_nightly* roc_nightly cd roc_nightly # build the basic cli platform -./roc build ../basic-cli/examples/file.roc +./roc build ../basic-cli/examples/countdown.roc # We need this extra variable so we can safely check if $2 is empty later EXTRA_ARGS=${2:-} # In some rare cases it's nice to be able to use the legacy linker, so we produce the .o file to be able to do that if [ -n "${EXTRA_ARGS}" ]; - then ./roc build $EXTRA_ARGS ../basic-cli/examples/file.roc + then ./roc build $EXTRA_ARGS ../basic-cli/examples/countdown.roc fi cd .. diff --git a/ci/get_releases_json.sh b/ci/get_releases_json.sh new file mode 100755 index 0000000000..560b38138f --- /dev/null +++ b/ci/get_releases_json.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# retrieves roc_releases.json, expects AUTH_HEADER to be set + +# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +set -euxo pipefail + +curl --request GET \ + --url https://api.github.com/repos/roc-lang/roc/releases \ + --header '$AUTH_HEADER' \ + --header 'content-type: application/json' \ + --output roc_releases.json \ No newline at end of file diff --git a/ci/package_release.sh b/ci/package_release.sh index ea4f9c1495..e99680f3e5 100755 --- a/ci/package_release.sh +++ b/ci/package_release.sh @@ -3,7 +3,7 @@ # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ set -euxo pipefail -cp target/release/roc ./roc # to be able to delete "target" later +cp target/release-with-lto/roc ./roc # to be able to delete "target" later # delete unnecessary files and folders git clean -fdx --exclude roc diff --git a/crates/README.md b/crates/README.md index 209992db82..db2dc86d80 100644 --- a/crates/README.md +++ b/crates/README.md @@ -60,7 +60,6 @@ The compiler includes the following sub-crates; - `roc_serialize` provides helpers for serializing and deserializing to/from bytes. - `roc_solve` The entry point of Roc's [type inference](https://en.wikipedia.org/wiki/Type_inference) system. Implements type inference and specialization of abilities. - `roc_solve_problem` provides types to describe problems that can occur during solving. -- `roc_str` provides `Roc` styled collection [reference counting](https://en.wikipedia.org/wiki/Reference_counting). See [README.md](./compiler/str/README.md) for more information. - `test_derive` Tests Roc's auto-derivers. - `test_gen` contains all of Roc's [code generation](https://en.wikipedia.org/wiki/Code_generation_(compiler)) tests. See [README.md](./compiler/test_gen/README.md) for more information. - `test_mono` Tests Roc's generation of the mono intermediate representation. diff --git a/crates/ast/Cargo.toml b/crates/ast/Cargo.toml index a42b2c14ae..801af48e14 100644 --- a/crates/ast/Cargo.toml +++ b/crates/ast/Cargo.toml @@ -1,39 +1,39 @@ [package] name = "roc_ast" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -roc_builtins = { path = "../compiler/builtins"} +roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } roc_collections = { path = "../compiler/collections" } -roc_region = { path = "../compiler/region" } +roc_error_macros = { path = "../error_macros" } +roc_load = { path = "../compiler/load" } roc_module = { path = "../compiler/module" } +roc_packaging = { path = "../packaging" } roc_parse = { path = "../compiler/parse" } roc_problem = { path = "../compiler/problem" } -roc_types = { path = "../compiler/types" } -roc_unify = { path = "../compiler/unify"} -roc_solve = { path = "../compiler/solve"} -roc_load = { path = "../compiler/load" } -roc_target = { path = "../compiler/roc_target" } -roc_error_macros = { path = "../error_macros" } -roc_packaging = { path = "../packaging" } +roc_region = { path = "../compiler/region" } roc_reporting = { path = "../reporting" } +roc_solve = { path = "../compiler/solve" } +roc_target = { path = "../compiler/roc_target" } +roc_types = { path = "../compiler/types" } +roc_unify = { path = "../compiler/unify" } ven_graph = { path = "../vendor/pathfinding" } arrayvec.workspace = true bumpalo.workspace = true +libc.workspace = true page_size.workspace = true snafu.workspace = true -libc.workspace = true [dev-dependencies] indoc.workspace = true [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.9", features = ["memoryapi"]} - +winapi.workspace = true diff --git a/crates/ast/src/constrain.rs b/crates/ast/src/constrain.rs index c6ea36e795..eb2d8080df 100644 --- a/crates/ast/src/constrain.rs +++ b/crates/ast/src/constrain.rs @@ -9,7 +9,7 @@ use roc_module::{ use roc_region::all::Region; use roc_types::{ subs::Variable, - types::{self, AnnotationSource, PReason, PatternCategory}, + types::{self, AnnotationSource, IndexOrField, PReason, PatternCategory}, types::{Category, Reason}, }; @@ -375,7 +375,7 @@ pub fn constrain_expr<'a>( env.pool.add(ext_type), ); - let category = Category::RecordAccessor(field.as_str(env.pool).into()); + let category = Category::Accessor(IndexOrField::Field(field.as_str(env.pool).into())); let record_expected = Expected::NoExpectation(record_type.shallow_clone()); let record_con = Eq( diff --git a/crates/ast/src/lang/core/expr/expr_to_expr2.rs b/crates/ast/src/lang/core/expr/expr_to_expr2.rs index 9a551a4109..cdbc12a054 100644 --- a/crates/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/crates/ast/src/lang/core/expr/expr_to_expr2.rs @@ -6,6 +6,7 @@ use roc_can::num::{ use roc_can::operator::desugar_expr; use roc_collections::all::MutSet; use roc_module::symbol::Symbol; +use roc_parse::ident::Accessor; use roc_parse::{ast::Expr, pattern::PatternType}; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; @@ -295,7 +296,7 @@ pub fn expr_to_expr2<'a>( ) } - RecordAccessorFunction(field) => ( + AccessorFunction(Accessor::RecordField(field)) => ( Expr2::Accessor { function_var: env.var_store.fresh(), record_var: env.var_store.fresh(), diff --git a/crates/ast/src/lang/core/types.rs b/crates/ast/src/lang/core/types.rs index 7ef0b52e51..75ace432c8 100644 --- a/crates/ast/src/lang/core/types.rs +++ b/crates/ast/src/lang/core/types.rs @@ -405,7 +405,7 @@ pub fn to_type2<'a>( Type2::Variable(var) } - Tuple { fields: _, ext: _ } => { + Tuple { elems: _, ext: _ } => { todo!("tuple type"); } Record { fields, ext, .. } => { diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ddc7c62344..6a4b2d9654 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -1,87 +1,81 @@ [package] name = "roc_cli" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -repository = "https://github.com/roc-lang/roc" -edition = "2021" description = "The Roc binary that brings together all functionality in the Roc toolset." default-run = "roc" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + [[bin]] +bench = false name = "roc" path = "src/main.rs" test = false -bench = false [features] -default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"] +default = ["target-aarch64", "target-x86_64", "target-wasm32"] -wasm32-cli-run = ["target-wasm32", "run-wasm32"] i386-cli-run = ["target-x86"] +wasm32-cli-run = ["target-wasm32", "run-wasm32"] editor = ["roc_editor"] run-wasm32 = ["roc_wasm_interp"] # Compiling for a different target than the current machine can cause linker errors. -target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"] target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"] +target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"] +target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"] target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"] target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"] -target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"] -target-all = [ - "target-aarch64", - "target-arm", - "target-x86", - "target-x86_64", - "target-wasm32" -] +target-all = ["target-aarch64", "target-arm", "target-x86", "target-x86_64", "target-wasm32"] sanitizers = ["roc_build/sanitizers"] [dependencies] -roc_collections = { path = "../compiler/collections" } +roc_build = { path = "../compiler/build" } +roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } +roc_collections = { path = "../compiler/collections" } roc_docs = { path = "../docs" } +roc_editor = { path = "../editor", optional = true } +roc_error_macros = { path = "../error_macros" } +roc_fmt = { path = "../compiler/fmt" } +roc_gen_llvm = { path = "../compiler/gen_llvm" } roc_glue = { path = "../glue" } +roc_linker = { path = "../linker" } +roc_load = { path = "../compiler/load" } +roc_module = { path = "../compiler/module" } +roc_mono = { path = "../compiler/mono" } +roc_packaging = { path = "../packaging" } roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } -roc_module = { path = "../compiler/module" } -roc_builtins = { path = "../compiler/builtins" } -roc_mono = { path = "../compiler/mono" } -roc_load = { path = "../compiler/load" } -roc_build = { path = "../compiler/build" } -roc_fmt = { path = "../compiler/fmt" } -roc_target = { path = "../compiler/roc_target" } -roc_packaging = { path = "../packaging" } -roc_reporting = { path = "../reporting" } -roc_error_macros = { path = "../error_macros" } -roc_editor = { path = "../editor", optional = true } -roc_linker = { path = "../linker" } roc_repl_cli = { path = "../repl_cli", optional = true } +roc_reporting = { path = "../reporting" } +roc_target = { path = "../compiler/roc_target" } roc_tracing = { path = "../tracing" } -roc_gen_llvm = {path = "../compiler/gen_llvm"} roc_wasm_interp = { path = "../wasm_interp", optional = true } ven_pretty = { path = "../vendor/pretty" } -indoc.workspace = true +bumpalo.workspace = true clap.workspace = true const_format.workspace = true -mimalloc.workspace = true -bumpalo.workspace = true -libc.workspace = true errno.workspace = true +indoc.workspace = true +inkwell.workspace = true +libc.workspace = true +libloading.workspace = true +mimalloc.workspace = true +signal-hook.workspace = true +strum.workspace = true target-lexicon.workspace = true tempfile.workspace = true -strum.workspace = true -libloading.workspace = true -signal-hook.workspace = true - -inkwell.workspace = true # for now, uses unix/libc functions that windows does not support [target.'cfg(not(windows))'.dependencies] @@ -89,15 +83,15 @@ roc_repl_expect = { path = "../repl_expect" } [dev-dependencies] -pretty_assertions = "1.3.0" -roc_test_utils = { path = "../test_utils" } -roc_utils = { path = "../utils" } -indoc = "1.0.7" -serial_test = "0.9.0" -criterion = { git = "https://github.com/Anton-4/criterion.rs"} cli_utils = { path = "../cli_utils" } -once_cell = "1.15.0" -parking_lot = "0.12" +roc_test_utils = { path = "../test_utils" } +roc_command_utils = { path = "../utils/command" } + +criterion.workspace = true +indoc.workspace = true +parking_lot.workspace = true +pretty_assertions.workspace = true +serial_test.workspace = true [[bench]] name = "time_bench" diff --git a/crates/cli/src/build.rs b/crates/cli/src/build.rs deleted file mode 100644 index 3b1a673eb0..0000000000 --- a/crates/cli/src/build.rs +++ /dev/null @@ -1,628 +0,0 @@ -use bumpalo::Bump; -use roc_build::{ - link::{ - legacy_host_filename, link, preprocess_host_wasm32, preprocessed_host_filename, - rebuild_host, LinkType, LinkingStrategy, - }, - program::{self, CodeGenBackend, CodeGenOptions}, -}; -use roc_builtins::bitcode; -use roc_load::{ - EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule, - LoadingProblem, Threading, -}; -use roc_mono::ir::OptLevel; -use roc_packaging::cache::RocCacheDir; -use roc_reporting::{ - cli::Problems, - report::{RenderTarget, DEFAULT_PALETTE}, -}; -use roc_target::TargetInfo; -use std::{ - path::Path, - time::{Duration, Instant}, -}; -use std::{path::PathBuf, thread::JoinHandle}; -use target_lexicon::Triple; - -fn report_timing(buf: &mut String, label: &str, duration: Duration) { - use std::fmt::Write; - - writeln!( - buf, - " {:9.3} ms {}", - duration.as_secs_f64() * 1000.0, - label, - ) - .unwrap() -} - -pub struct BuiltFile<'a> { - pub binary_path: PathBuf, - pub problems: Problems, - pub total_time: Duration, - pub expect_metadata: ExpectMetadata<'a>, -} - -pub enum BuildOrdering { - /// Run up through typechecking first; continue building iff that is successful. - BuildIfChecks, - /// Always build the Roc binary, even if there are type errors. - AlwaysBuild, -} - -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -pub enum BuildFileError<'a> { - LoadingProblem(LoadingProblem<'a>), - ErrorModule { - module: LoadedModule, - total_time: Duration, - }, -} - -impl<'a> BuildFileError<'a> { - fn from_mono_error(error: LoadMonomorphizedError<'a>, compilation_start: Instant) -> Self { - match error { - LoadMonomorphizedError::LoadingProblem(problem) => { - BuildFileError::LoadingProblem(problem) - } - LoadMonomorphizedError::ErrorModule(module) => BuildFileError::ErrorModule { - module, - total_time: compilation_start.elapsed(), - }, - } - } -} - -pub fn standard_load_config( - target: &Triple, - order: BuildOrdering, - threading: Threading, -) -> LoadConfig { - let target_info = TargetInfo::from(target); - - let exec_mode = match order { - BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck, - BuildOrdering::AlwaysBuild => ExecutionMode::Executable, - }; - - LoadConfig { - target_info, - render: RenderTarget::ColorTerminal, - palette: DEFAULT_PALETTE, - threading, - exec_mode, - } -} - -#[allow(clippy::too_many_arguments)] -pub fn build_file<'a>( - arena: &'a Bump, - target: &Triple, - app_module_path: PathBuf, - code_gen_options: CodeGenOptions, - emit_timings: bool, - link_type: LinkType, - linking_strategy: LinkingStrategy, - prebuilt_requested: bool, - wasm_dev_stack_bytes: Option, - roc_cache_dir: RocCacheDir<'_>, - load_config: LoadConfig, -) -> Result, BuildFileError<'a>> { - let compilation_start = Instant::now(); - - // Step 1: compile the app and generate the .o file - let loaded = - roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config) - .map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?; - - build_loaded_file( - arena, - target, - app_module_path, - code_gen_options, - emit_timings, - link_type, - linking_strategy, - prebuilt_requested, - wasm_dev_stack_bytes, - loaded, - compilation_start, - ) -} - -#[allow(clippy::too_many_arguments)] -fn build_loaded_file<'a>( - arena: &'a Bump, - target: &Triple, - app_module_path: PathBuf, - code_gen_options: CodeGenOptions, - emit_timings: bool, - link_type: LinkType, - linking_strategy: LinkingStrategy, - prebuilt_requested: bool, - wasm_dev_stack_bytes: Option, - loaded: roc_load::MonomorphizedModule<'a>, - compilation_start: Instant, -) -> Result, BuildFileError<'a>> { - let operating_system = roc_target::OperatingSystem::from(target.operating_system); - - let platform_main_roc = match &loaded.entry_point { - EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(), - _ => unreachable!(), - }; - - // the preprocessed host is stored beside the platform's main.roc - let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy { - if let roc_target::OperatingSystem::Wasi = operating_system { - // when compiling a wasm application, we implicitly assume here that the host is in zig - // and has a file called "host.zig" - platform_main_roc.with_file_name("host.zig") - } else { - platform_main_roc.with_file_name(legacy_host_filename(target).unwrap()) - } - } else { - platform_main_roc.with_file_name(preprocessed_host_filename(target).unwrap()) - }; - - // For example, if we're loading the platform from a URL, it's automatically prebuilt - // even if the --prebuilt-platform=true CLI flag wasn't set. - let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform; - - let cwd = app_module_path.parent().unwrap(); - let mut output_exe_path = cwd.join(&*loaded.output_path); - - if let Some(extension) = operating_system.executable_file_ext() { - output_exe_path.set_extension(extension); - } - - // We don't need to spawn a rebuild thread when using a prebuilt host. - let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) { - None - } else if is_platform_prebuilt { - if !preprocessed_host_path.exists() { - invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path); - - std::process::exit(1); - } - - if linking_strategy == LinkingStrategy::Surgical { - // Copy preprocessed host to executable location. - // The surgical linker will modify that copy in-place. - std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap(); - } - - None - } else { - // TODO this should probably be moved before load_and_monomorphize. - // To do this we will need to preprocess files just for their exported symbols. - // Also, we should no longer need to do this once we have platforms on - // a package repository, as we can then get prebuilt platforms from there. - - let exposed_values = loaded - .exposed_to_host - .values - .keys() - .map(|x| x.as_str(&loaded.interns).to_string()) - .collect(); - - let exposed_closure_types = loaded - .exposed_to_host - .closure_types - .iter() - .map(|x| { - format!( - "{}_{}", - x.module_string(&loaded.interns), - x.as_str(&loaded.interns) - ) - }) - .collect(); - - let join_handle = spawn_rebuild_thread( - code_gen_options.opt_level, - linking_strategy, - platform_main_roc.clone(), - preprocessed_host_path.clone(), - output_exe_path.clone(), - target, - exposed_values, - exposed_closure_types, - ); - - Some(join_handle) - }; - - let buf = &mut String::with_capacity(1024); - - let mut it = loaded.timings.iter().peekable(); - while let Some((module_id, module_timing)) = it.next() { - let module_name = loaded.interns.module_name(*module_id); - - buf.push_str(" "); - - if module_name.is_empty() { - // the App module - buf.push_str("Application Module"); - } else { - buf.push_str(module_name); - } - - buf.push('\n'); - - use std::fmt::Write; - write!(buf, "{}", module_timing).unwrap(); - - if it.peek().is_some() { - buf.push('\n'); - } - } - - // This only needs to be mutable for report_problems. This can't be done - // inside a nested scope without causing a borrow error! - let mut loaded = loaded; - let problems = program::report_problems_monomorphized(&mut loaded); - let loaded = loaded; - - enum HostRebuildTiming { - BeforeApp(u128), - ConcurrentWithApp(JoinHandle), - } - - let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread { - if linking_strategy == LinkingStrategy::Additive { - let rebuild_duration = rebuild_thread - .join() - .expect("Failed to (re)build platform."); - - if emit_timings && !is_platform_prebuilt { - println!( - "Finished rebuilding the platform in {} ms\n", - rebuild_duration - ); - } - - Some(HostRebuildTiming::BeforeApp(rebuild_duration)) - } else { - Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread)) - } - } else { - None - }; - - let (roc_app_bytes, code_gen_timing, expect_metadata) = program::gen_from_mono_module( - arena, - loaded, - &app_module_path, - target, - code_gen_options, - &preprocessed_host_path, - wasm_dev_stack_bytes, - ); - - buf.push('\n'); - buf.push_str(" "); - buf.push_str("Code Generation"); - buf.push('\n'); - - report_timing( - buf, - "Generate Assembly from Mono IR", - code_gen_timing.code_gen, - ); - - let compilation_end = compilation_start.elapsed(); - let size = roc_app_bytes.len(); - - if emit_timings { - println!( - "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}", - buf - ); - - println!( - "Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n", - compilation_end.as_millis(), - size, - ); - } - - if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing { - let rebuild_duration = thread.join().expect("Failed to (re)build platform."); - - if emit_timings && !is_platform_prebuilt { - println!( - "Finished rebuilding the platform in {} ms\n", - rebuild_duration - ); - } - } - - // Step 2: link the prebuilt platform and compiled app - let link_start = Instant::now(); - - match (linking_strategy, link_type) { - (LinkingStrategy::Surgical, _) => { - roc_linker::link_preprocessed_host( - target, - &platform_main_roc, - &roc_app_bytes, - &output_exe_path, - ); - } - (LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => { - // Just copy the object file to the output folder. - output_exe_path.set_extension(operating_system.object_file_ext()); - std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap(); - } - (LinkingStrategy::Legacy, _) => { - let app_o_file = tempfile::Builder::new() - .prefix("roc_app") - .suffix(&format!(".{}", operating_system.object_file_ext())) - .tempfile() - .map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?; - let app_o_file = app_o_file.path(); - - std::fs::write(app_o_file, &*roc_app_bytes).unwrap(); - - let builtins_host_tempfile = - bitcode::host_tempfile().expect("failed to write host builtins object to tempfile"); - - let mut inputs = vec![app_o_file.to_str().unwrap()]; - - if !matches!(link_type, LinkType::Dylib | LinkType::None) { - // the host has been compiled into a .o or .obj file - inputs.push(preprocessed_host_path.as_path().to_str().unwrap()); - } - - if matches!(code_gen_options.backend, program::CodeGenBackend::Assembly) { - inputs.push(builtins_host_tempfile.path().to_str().unwrap()); - } - - let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type) - .map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?; - - let exit_status = child - .wait() - .map_err(|_| todo!("gracefully handle error after `ld` spawned"))?; - - // Extend the lifetime of the tempfile so it doesn't get dropped - // (and thus deleted) before the child process is done using it! - let _ = builtins_host_tempfile; - - if !exit_status.success() { - todo!( - "gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}", - exit_status.code() - ); - } - } - } - - let linking_time = link_start.elapsed(); - - if emit_timings { - println!("Finished linking in {} ms\n", linking_time.as_millis()); - } - - let total_time = compilation_start.elapsed(); - - Ok(BuiltFile { - binary_path: output_exe_path, - problems, - total_time, - expect_metadata, - }) -} - -fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) { - let prefix = match prebuilt_requested { - true => "Because I was run with --prebuilt-platform=true, ", - false => "", - }; - - eprintln!( - indoc::indoc!( - r#" - {}I was expecting this file to exist: - - {} - - However, it was not there! - - If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false - "# - ), - prefix, - preprocessed_host_path.to_string_lossy(), - ); -} - -#[allow(clippy::too_many_arguments)] -fn spawn_rebuild_thread( - opt_level: OptLevel, - linking_strategy: LinkingStrategy, - platform_main_roc: PathBuf, - preprocessed_host_path: PathBuf, - output_exe_path: PathBuf, - target: &Triple, - exported_symbols: Vec, - exported_closure_types: Vec, -) -> std::thread::JoinHandle { - let thread_local_target = target.clone(); - std::thread::spawn(move || { - // Printing to stderr because we want stdout to contain only the output of the roc program. - // We are aware of the trade-offs. - // `cargo run` follows the same approach - eprintln!("🔨 Rebuilding platform..."); - - let rebuild_host_start = Instant::now(); - - match linking_strategy { - LinkingStrategy::Additive => { - let host_dest = rebuild_host( - opt_level, - &thread_local_target, - platform_main_roc.as_path(), - None, - ); - - preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path); - } - LinkingStrategy::Surgical => { - roc_linker::build_and_preprocess_host( - opt_level, - &thread_local_target, - platform_main_roc.as_path(), - preprocessed_host_path.as_path(), - exported_symbols, - exported_closure_types, - ); - - // Copy preprocessed host to executable location. - // The surgical linker will modify that copy in-place. - std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap(); - } - LinkingStrategy::Legacy => { - rebuild_host( - opt_level, - &thread_local_target, - platform_main_roc.as_path(), - None, - ); - } - } - - rebuild_host_start.elapsed().as_millis() - }) -} - -#[allow(clippy::too_many_arguments)] -pub fn check_file<'a>( - arena: &'a Bump, - roc_file_path: PathBuf, - emit_timings: bool, - roc_cache_dir: RocCacheDir<'_>, - threading: Threading, -) -> Result<(Problems, Duration), LoadingProblem<'a>> { - let compilation_start = Instant::now(); - - // only used for generating errors. We don't do code generation, so hardcoding should be fine - // we need monomorphization for when exhaustiveness checking - let target_info = TargetInfo::default_x86_64(); - - // Step 1: compile the app and generate the .o file - - let load_config = LoadConfig { - target_info, - // TODO: expose this from CLI? - render: RenderTarget::ColorTerminal, - palette: DEFAULT_PALETTE, - threading, - exec_mode: ExecutionMode::Check, - }; - let mut loaded = - roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?; - - let buf = &mut String::with_capacity(1024); - - let mut it = loaded.timings.iter().peekable(); - while let Some((module_id, module_timing)) = it.next() { - let module_name = loaded.interns.module_name(*module_id); - - buf.push_str(" "); - - if module_name.is_empty() { - // the App module - buf.push_str("Application Module"); - } else { - buf.push_str(module_name); - } - - buf.push('\n'); - - report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file); - report_timing(buf, "Parse header", module_timing.parse_header); - report_timing(buf, "Parse body", module_timing.parse_body); - report_timing(buf, "Canonicalize", module_timing.canonicalize); - report_timing(buf, "Constrain", module_timing.constrain); - report_timing(buf, "Solve", module_timing.solve); - report_timing(buf, "Other", module_timing.other()); - buf.push('\n'); - report_timing(buf, "Total", module_timing.total()); - - if it.peek().is_some() { - buf.push('\n'); - } - } - - let compilation_end = compilation_start.elapsed(); - - if emit_timings { - println!( - "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}", - buf - ); - - println!("Finished checking in {} ms\n", compilation_end.as_millis(),); - } - - Ok(( - program::report_problems_typechecked(&mut loaded), - compilation_end, - )) -} - -pub fn build_str_test<'a>( - arena: &'a Bump, - app_module_path: &Path, - app_module_source: &'a str, - assume_prebuild: bool, -) -> Result, BuildFileError<'a>> { - let triple = target_lexicon::Triple::host(); - - let code_gen_options = CodeGenOptions { - backend: CodeGenBackend::Llvm, - opt_level: OptLevel::Normal, - emit_debug_info: false, - }; - - let emit_timings = false; - let link_type = LinkType::Executable; - let linking_strategy = LinkingStrategy::Surgical; - let wasm_dev_stack_bytes = None; - - let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed; - let build_ordering = BuildOrdering::AlwaysBuild; - let threading = Threading::AtMost(2); - - let load_config = standard_load_config(&triple, build_ordering, threading); - - let compilation_start = std::time::Instant::now(); - - // Step 1: compile the app and generate the .o file - let loaded = roc_load::load_and_monomorphize_from_str( - arena, - PathBuf::from("valgrind_test.roc"), - app_module_source, - app_module_path.to_path_buf(), - roc_cache_dir, - load_config, - ) - .map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?; - - build_loaded_file( - arena, - &triple, - app_module_path.to_path_buf(), - code_gen_options, - emit_timings, - link_type, - linking_strategy, - assume_prebuild, - wasm_dev_stack_bytes, - loaded, - compilation_start, - ) -} diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 6d48900d05..0627edb390 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -3,13 +3,16 @@ #[macro_use] extern crate const_format; -use build::BuiltFile; use bumpalo::Bump; use clap::{Arg, ArgMatches, Command, ValueSource}; use roc_build::link::{LinkType, LinkingStrategy}; -use roc_build::program::{CodeGenBackend, CodeGenOptions}; +use roc_build::program::{ + handle_error_module, handle_loading_problem, standard_load_config, BuildFileError, + BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions, DEFAULT_ROC_FILENAME, +}; use roc_error_macros::{internal_error, user_error}; -use roc_load::{ExpectMetadata, LoadingProblem, Threading}; +use roc_gen_llvm::llvm::build::LlvmBackendMode; +use roc_load::{ExpectMetadata, Threading}; use roc_mono::ir::OptLevel; use roc_packaging::cache::RocCacheDir; use roc_packaging::tarball::Compression; @@ -29,14 +32,9 @@ use target_lexicon::{ #[cfg(not(target_os = "linux"))] use tempfile::TempDir; -pub mod build; mod format; pub use format::format; -use crate::build::{standard_load_config, BuildFileError, BuildOrdering}; - -const DEFAULT_ROC_FILENAME: &str = "main.roc"; - pub const CMD_BUILD: &str = "build"; pub const CMD_RUN: &str = "run"; pub const CMD_DEV: &str = "dev"; @@ -66,7 +64,8 @@ pub const FLAG_CHECK: &str = "check"; pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb"; pub const ROC_FILE: &str = "ROC_FILE"; pub const ROC_DIR: &str = "ROC_DIR"; -pub const GLUE_FILE: &str = "GLUE_FILE"; +pub const GLUE_DIR: &str = "GLUE_DIR"; +pub const GLUE_SPEC: &str = "GLUE_SPEC"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; @@ -281,17 +280,24 @@ pub fn build_app<'a>() -> Command<'a> { .subcommand(Command::new(CMD_GLUE) .about("Generate glue code between a platform's Roc API and its host language") .arg( - Arg::new(ROC_FILE) - .help("The .roc file for the platform module") + Arg::new(GLUE_SPEC) + .help("The specification for how to translate Roc types into output files.") .allow_invalid_utf8(true) .required(true) ) .arg( - Arg::new(GLUE_FILE) - .help("The filename for the generated glue code\n(Currently, this must be a .rs file because only Rust glue generation is supported so far.)") + Arg::new(GLUE_DIR) + .help("The directory for the generated glue code.\nNote: The implementation can write to any file in this directory.") .allow_invalid_utf8(true) .required(true) ) + .arg( + Arg::new(ROC_FILE) + .help("The .roc file whose exposed types should be translated.") + .allow_invalid_utf8(true) + .required(false) + .default_value(DEFAULT_ROC_FILENAME) + ) ) .subcommand(Command::new(CMD_GEN_STUB_LIB) .about("Generate a stubbed shared library that can be used for linking a platform binary.\nThe stubbed library has prototypes, but no function bodies.\n\nNote: This command will be removed in favor of just using `roc build` once all platforms support the surgical linker") @@ -332,6 +338,7 @@ pub fn build_app<'a>() -> Command<'a> { Arg::new(DIRECTORY_OR_FILES) .multiple_values(true) .required(false) + .allow_invalid_utf8(true) .help("(optional) The directory or files to open on launch"), ), ) @@ -360,7 +367,6 @@ pub fn test(_matches: &ArgMatches, _triple: Triple) -> io::Result { #[cfg(not(windows))] pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result { use roc_build::program::report_problems_monomorphized; - use roc_gen_llvm::llvm::build::LlvmBackendMode; use roc_load::{ExecutionMode, LoadConfig, LoadMonomorphizedError}; use roc_packaging::cache; use roc_target::TargetInfo; @@ -520,7 +526,7 @@ pub fn build( roc_cache_dir: RocCacheDir<'_>, link_type: LinkType, ) -> io::Result { - use build::build_file; + use roc_build::program::build_file; use BuildConfig::*; let filename = matches.value_of_os(ROC_FILE).unwrap(); @@ -594,15 +600,6 @@ pub fn build( // so we don't want to spend time freeing these values let arena = ManuallyDrop::new(Bump::new()); - let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) { - CodeGenBackend::Wasm - } else { - match matches.is_present(FLAG_DEV) { - true => CodeGenBackend::Assembly, - false => CodeGenBackend::Llvm, - } - }; - let opt_level = if let BuildConfig::BuildAndRunIfNoErrors = config { OptLevel::Development } else { @@ -618,6 +615,25 @@ pub fn build( } } }; + + let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) { + CodeGenBackend::Wasm + } else { + match matches.is_present(FLAG_DEV) { + true => CodeGenBackend::Assembly, + false => { + let backend_mode = match opt_level { + OptLevel::Development => LlvmBackendMode::BinaryDev, + OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => { + LlvmBackendMode::Binary + } + }; + + CodeGenBackend::Llvm(backend_mode) + } + } + }; + let emit_debug_info = matches.is_present(FLAG_DEBUG); let emit_timings = matches.is_present(FLAG_TIME); @@ -760,48 +776,6 @@ pub fn build( } } -fn handle_error_module( - mut module: roc_load::LoadedModule, - total_time: std::time::Duration, - filename: &OsStr, - print_run_anyway_hint: bool, -) -> io::Result { - debug_assert!(module.total_problems() > 0); - - let problems = roc_build::program::report_problems_typechecked(&mut module); - - problems.print_to_stdout(total_time); - - if print_run_anyway_hint { - // If you're running "main.roc" then you can just do `roc run` - // to re-run the program. - print!(".\n\nYou can run the program anyway with \x1B[32mroc run"); - - if filename != DEFAULT_ROC_FILENAME { - print!(" {}", &filename.to_string_lossy()); - } - - println!("\x1B[39m"); - } - - Ok(problems.exit_code()) -} - -fn handle_loading_problem(problem: LoadingProblem) -> io::Result { - match problem { - LoadingProblem::FormattedReport(report) => { - print!("{}", report); - Ok(1) - } - _ => { - // TODO: tighten up the types here, we should always end up with a - // formatted report from load. - print!("Failed with error: {:?}", problem); - Ok(1) - } - } -} - fn roc_run<'a, I: IntoIterator>( arena: &Bump, opt_level: OptLevel, @@ -1323,40 +1297,3 @@ impl std::str::FromStr for Target { } } } - -// These functions don't end up in the final Roc binary but Windows linker needs a definition inside the crate. -// On Windows, there seems to be less dead-code-elimination than on Linux or MacOS, or maybe it's done later. -#[cfg(windows)] -#[allow(unused_imports)] -use windows_roc_platform_functions::*; - -#[cfg(windows)] -mod windows_roc_platform_functions { - use core::ffi::c_void; - - /// # Safety - /// The Roc application needs this. - #[no_mangle] - pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { - libc::malloc(size) - } - - /// # Safety - /// The Roc application needs this. - #[no_mangle] - pub unsafe fn roc_realloc( - c_ptr: *mut c_void, - new_size: usize, - _old_size: usize, - _alignment: u32, - ) -> *mut c_void { - libc::realloc(c_ptr, new_size) - } - - /// # Safety - /// The Roc application needs this. - #[no_mangle] - pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { - libc::free(c_ptr) - } -} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index debaa4f7f4..35db201dfb 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,11 +1,11 @@ //! The `roc` binary that brings together all functionality in the Roc toolset. use roc_build::link::LinkType; -use roc_cli::build::check_file; +use roc_build::program::check_file; use roc_cli::{ build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GEN_STUB_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, - GLUE_FILE, ROC_FILE, + GLUE_DIR, GLUE_SPEC, ROC_FILE, }; use roc_docs::generate_docs_html; use roc_error_macros::user_error; @@ -88,12 +88,13 @@ fn main() -> io::Result<()> { } Some((CMD_GLUE, matches)) => { let input_path = Path::new(matches.value_of_os(ROC_FILE).unwrap()); - let output_path = Path::new(matches.value_of_os(GLUE_FILE).unwrap()); + let output_path = Path::new(matches.value_of_os(GLUE_DIR).unwrap()); + let spec_path = Path::new(matches.value_of_os(GLUE_SPEC).unwrap()); - if Some("rs") == output_path.extension().and_then(OsStr::to_str) { - roc_glue::generate(input_path, output_path) + if !output_path.exists() || output_path.is_dir() { + roc_glue::generate(input_path, output_path, spec_path) } else { - eprintln!("Currently, `roc glue` only supports generating Rust glue files (with the .rs extension). In the future, the plan is to decouple `roc glue` from any particular output format, by having it accept a second .roc file which gets executed as a plugin to generate glue code for any desired language. However, this has not yet been implemented, and for now only .rs is supported."); + eprintln!("`roc glue` must be given a directory to output into, because the glue might generate multiple files."); Ok(1) } diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index 9b39c38d7d..5aa6021c45 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -474,7 +474,8 @@ mod cli_run { } #[test] - #[ignore = "Prebuilt platforms cause problems with nix and NixOS. This is run explicitly tested on CI (.github/workflows/ubuntu_x86_64.yml)"] + #[serial(basic_cli_url)] + #[cfg_attr(windows, ignore)] fn hello_world() { test_roc_app_slim( "examples", @@ -533,6 +534,7 @@ mod cli_run { } #[test] + #[serial(zig_platform)] #[cfg_attr(windows, ignore)] fn platform_switching_zig() { test_roc_app_slim( @@ -675,6 +677,8 @@ mod cli_run { ) } + #[cfg_attr(windows, ignore)] // flaky error; issue #5024 + #[serial(breakout)] #[test] fn breakout() { test_roc_app_slim( @@ -686,6 +690,18 @@ mod cli_run { ) } + #[test] + #[serial(breakout)] + fn breakout_hello_gui() { + test_roc_app_slim( + "examples/gui/breakout", + "hello-gui.roc", + "hello-gui", + "", + UseValgrind::No, + ) + } + #[test] #[cfg_attr(windows, ignore)] fn quicksort() { @@ -790,10 +806,10 @@ mod cli_run { "False.roc", "false", &[], - &[Arg::ExamplePath("examples/hello.false")], + &[Arg::ExamplePath("examples/sqrt.false")], &[], - &("Hello, World!".to_string() + LINE_ENDING), - UseValgrind::No, + "1414", + UseValgrind::Yes, TestCliCommands::Many, ) } @@ -819,7 +835,7 @@ mod cli_run { &[], &[Arg::ExamplePath("input"), Arg::ExamplePath("output")], &[], - "Processed 3 files with 3 successes and 0 errors\n", + "Processed 4 files with 3 successes and 0 errors\n", UseValgrind::No, TestCliCommands::Run, ) @@ -849,6 +865,8 @@ mod cli_run { } #[test] + #[serial(parser_package)] + #[serial(zig_platform)] #[cfg_attr(windows, ignore)] fn parse_movies_csv() { test_roc_app_slim( @@ -861,7 +879,9 @@ mod cli_run { } #[test] - #[ignore = "Prebuilt platforms cause problems with nix and NixOS. This is run explicitly tested on CI (.github/workflows/ubuntu_x86_64.yml)"] + #[serial(parser_package)] + #[serial(basic_cli_url)] + #[cfg_attr(windows, ignore)] fn parse_letter_counts() { test_roc_app_slim( "examples/parser/examples", diff --git a/crates/cli/tests/editor.rs b/crates/cli/tests/editor.rs index 054e48065d..f6ec81e579 100644 --- a/crates/cli/tests/editor.rs +++ b/crates/cli/tests/editor.rs @@ -8,25 +8,40 @@ mod editor_launch_test { thread, }; - use cli_utils::helpers::build_roc_bin_cached; + use cli_utils::helpers::build_roc_bin; use roc_cli::CMD_EDIT; - use roc_utils::root_dir; + use roc_command_utils::root_dir; use std::io::Read; - // ignored because we don't want to bring up the editor window during regular tests, only on specific CI machines - #[ignore] + #[ignore = "We don't want to bring up the editor window during regular tests, only on specific CI machines."] #[test] - fn launch() { + fn launch_test() { + launch(None); + + // with a file arg + launch(Some("roc-projects/new-roc-project-1/main.roc")); + + // with a folder arg + launch(Some("roc-projects/new-roc-project-1")); + } + + fn launch(arg_path_str_opt: Option<&str>) { let root_dir = root_dir(); // The editor expects to be run from the root of the repo, so it can find the cli-platform to init a new project folder. env::set_current_dir(&root_dir) .unwrap_or_else(|_| panic!("Failed to set current dir to {:?}", root_dir)); - let roc_binary_path = build_roc_bin_cached(); + let roc_binary_path = build_roc_bin(&["--features", "editor"]); + + let mut cmd_args = vec![CMD_EDIT]; + + if let Some(arg_path_str) = arg_path_str_opt { + cmd_args.push(arg_path_str) + } let mut roc_process = Command::new(roc_binary_path) - .arg(CMD_EDIT) + .args(cmd_args) .stdout(Stdio::piped()) .spawn() .expect("Failed to start editor from cli."); diff --git a/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig b/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig index bb797f2bf3..857772ff27 100644 --- a/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig +++ b/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig @@ -115,7 +115,7 @@ pub export fn main() i32 { // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; - callresult.deinit(); + callresult.decref(); stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable; diff --git a/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig b/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig index 95f4ab1d89..7030d8276b 100644 --- a/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig +++ b/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig @@ -114,7 +114,7 @@ pub export fn main() i32 { // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; - callresult.deinit(); + callresult.decref(); stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable; diff --git a/crates/cli/tests/fixtures/packages/platform/host.zig b/crates/cli/tests/fixtures/packages/platform/host.zig index bb797f2bf3..857772ff27 100644 --- a/crates/cli/tests/fixtures/packages/platform/host.zig +++ b/crates/cli/tests/fixtures/packages/platform/host.zig @@ -115,7 +115,7 @@ pub export fn main() i32 { // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; - callresult.deinit(); + callresult.decref(); stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable; diff --git a/crates/cli_testing_examples/benchmarks/platform/host.zig b/crates/cli_testing_examples/benchmarks/platform/host.zig index e072951995..63dd9ef9e3 100644 --- a/crates/cli_testing_examples/benchmarks/platform/host.zig +++ b/crates/cli_testing_examples/benchmarks/platform/host.zig @@ -25,10 +25,10 @@ const mem = std.mem; const Allocator = mem.Allocator; extern fn roc__mainForHost_1_exposed_generic([*]u8) void; -extern fn roc__mainForHost_size() i64; -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 roc__mainForHost_1_exposed_size() i64; +extern fn roc__mainForHost_0_caller(*const u8, [*]u8, [*]u8) void; +extern fn roc__mainForHost_0_size() i64; +extern fn roc__mainForHost_0_result_size() i64; const Align = 2 * @alignOf(usize); extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; @@ -123,7 +123,7 @@ pub fn main() !u8 { const stderr = std.io.getStdErr().writer(); // The size might be zero; if so, make it at least 8 so that we don't have a nullptr - const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8); + const size = std.math.max(@intCast(usize, roc__mainForHost_1_exposed_size()), 8); const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?; var output = @ptrCast([*]u8, raw_output); @@ -155,7 +155,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void { const allocator = std.heap.page_allocator; // The size might be zero; if so, make it at least 8 so that we don't have a nullptr - const size = std.math.max(roc__mainForHost_1__Fx_result_size(), 8); + const size = std.math.max(roc__mainForHost_0_result_size(), 8); const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; var output = @ptrCast([*]u8, raw_output); @@ -165,7 +165,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void { const flags: u8 = 0; - roc__mainForHost_1__Fx_caller(&flags, closure_data_pointer, output); + roc__mainForHost_0_caller(&flags, closure_data_pointer, output); // The closure returns result, nothing interesting to do with it return; diff --git a/crates/cli_testing_examples/expects/zig-platform/host.zig b/crates/cli_testing_examples/expects/zig-platform/host.zig index 0d54bc79a3..7e80941376 100644 --- a/crates/cli_testing_examples/expects/zig-platform/host.zig +++ b/crates/cli_testing_examples/expects/zig-platform/host.zig @@ -134,7 +134,7 @@ pub fn main() u8 { // stdout the result stdout.print("{s}", .{callresult.asSlice()}) catch unreachable; - callresult.deinit(); + callresult.decref(); stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable; diff --git a/crates/cli_utils/Cargo.toml b/crates/cli_utils/Cargo.toml index 3ac3952ece..2530866df5 100644 --- a/crates/cli_utils/Cargo.toml +++ b/crates/cli_utils/Cargo.toml @@ -1,27 +1,25 @@ [package] name = "cli_utils" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -repository = "https://github.com/roc-lang/roc" -edition = "2021" description = "Provides shared code for cli tests and benchmarks." -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true [dependencies] roc_collections = { path = "../compiler/collections" } -roc_reporting = { path = "../reporting" } roc_load = { path = "../compiler/load" } roc_module = { path = "../compiler/module" } -roc_utils = { path = "../utils" } +roc_reporting = { path = "../reporting" } +roc_command_utils = { path = "../utils/command" } -bumpalo = { version = "3.8.0", features = ["collections"] } -criterion = { git = "https://github.com/Anton-4/criterion.rs"} -serde = { version = "1.0.130", features = ["derive"] } -serde-xml-rs = "0.5.1" -strip-ansi-escapes = "0.1.1" -tempfile = "3.2.0" +bumpalo.workspace = true +criterion.workspace = true +serde-xml-rs.workspace = true +serde.workspace = true +tempfile.workspace = true [target.'cfg(unix)'.dependencies] -rlimit = "0.6.2" +rlimit.workspace = true diff --git a/crates/cli_utils/src/helpers.rs b/crates/cli_utils/src/helpers.rs index a48ba3423e..e6a99a58b5 100644 --- a/crates/cli_utils/src/helpers.rs +++ b/crates/cli_utils/src/helpers.rs @@ -4,9 +4,7 @@ extern crate roc_load; extern crate roc_module; extern crate tempfile; -use roc_utils::cargo; -use roc_utils::pretty_command_string; -use roc_utils::root_dir; +use roc_command_utils::{cargo, pretty_command_string, root_dir}; use serde::Deserialize; use serde_xml_rs::from_str; use std::env; @@ -16,6 +14,7 @@ use std::io::Write; use std::path::Path; use std::path::PathBuf; use std::process::{Command, ExitStatus, Stdio}; +use std::sync::Mutex; use tempfile::NamedTempFile; #[derive(Debug)] @@ -41,49 +40,67 @@ pub fn build_roc_bin_cached() -> PathBuf { let roc_binary_path = path_to_roc_binary(); if !roc_binary_path.exists() { - // Remove the /target/release/roc part - let root_project_dir = roc_binary_path - .parent() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap(); - - // cargo build --bin roc - // (with --release iff the test is being built with --release) - let args = if cfg!(debug_assertions) { - vec!["build", "--bin", "roc"] - } else { - vec!["build", "--release", "--bin", "roc"] - }; - - let mut cargo_cmd = cargo(); - - cargo_cmd.current_dir(root_project_dir).args(&args); - - let cargo_cmd_str = format!("{:?}", cargo_cmd); - - let cargo_output = cargo_cmd.output().unwrap(); - - if !cargo_output.status.success() { - panic!( - "The following cargo command failed:\n\n {}\n\n stdout was:\n\n {}\n\n stderr was:\n\n {}\n", - cargo_cmd_str, - String::from_utf8(cargo_output.stdout).unwrap(), - String::from_utf8(cargo_output.stderr).unwrap() - ); - } + build_roc_bin(&[]); } roc_binary_path } +pub fn build_roc_bin(extra_args: &[&str]) -> PathBuf { + let roc_binary_path = path_to_roc_binary(); + + // Remove the /target/release/roc part + let root_project_dir = roc_binary_path + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap(); + + // cargo build --bin roc + // (with --release iff the test is being built with --release) + let mut args = if cfg!(debug_assertions) { + vec!["build", "--bin", "roc"] + } else { + vec!["build", "--release", "--bin", "roc"] + }; + + args.extend(extra_args); + + let mut cargo_cmd = cargo(); + + cargo_cmd.current_dir(root_project_dir).args(&args); + + let cargo_cmd_str = format!("{:?}", cargo_cmd); + + let cargo_output = cargo_cmd.output().unwrap(); + + if !cargo_output.status.success() { + panic!( + "The following cargo command failed:\n\n {}\n\n stdout was:\n\n {}\n\n stderr was:\n\n {}\n", + cargo_cmd_str, + String::from_utf8(cargo_output.stdout).unwrap(), + String::from_utf8(cargo_output.stderr).unwrap() + ); + } + + roc_binary_path +} + +// Since glue is always compiling the same plugin, it can not be run in parallel. +// That would lead to a race condition in writing the output shared library. +// Thus, all calls to glue in a test are made sequential. +// TODO: In the future, look into compiling the shared libary once and then caching it. +static GLUE_LOCK: Mutex<()> = Mutex::new(()); + pub fn run_glue(args: I) -> Out where I: IntoIterator, S: AsRef, { + let _guard = GLUE_LOCK.lock().unwrap(); + run_roc_with_stdin(&path_to_roc_binary(), args, &[]) } @@ -387,7 +404,7 @@ pub fn cli_testing_dir(dir_name: &str) -> PathBuf { // Descend into examples/{dir_name} path.push("crates"); path.push("cli_testing_examples"); - path.extend(dir_name.split("/")); // Make slashes cross-target + path.extend(dir_name.split('/')); // Make slashes cross-target path } @@ -396,7 +413,7 @@ pub fn cli_testing_dir(dir_name: &str) -> PathBuf { pub fn dir_path_from_root(dir_name: &str) -> PathBuf { let mut path = root_dir(); - path.extend(dir_name.split("/")); // Make slashes cross-target + path.extend(dir_name.split('/')); // Make slashes cross-target path } @@ -419,7 +436,7 @@ pub fn fixtures_dir(dir_name: &str) -> PathBuf { path.push("cli"); path.push("tests"); path.push("fixtures"); - path.extend(dir_name.split("/")); // Make slashes cross-target + path.extend(dir_name.split('/')); // Make slashes cross-target path } diff --git a/crates/code_markup/Cargo.toml b/crates/code_markup/Cargo.toml index d8d63f7a4a..4a784234b1 100644 --- a/crates/code_markup/Cargo.toml +++ b/crates/code_markup/Cargo.toml @@ -1,16 +1,19 @@ [package] name = "roc_code_markup" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Our own markup language for Roc code. Used by the editor and the docs." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_ast = { path = "../ast" } roc_module = { path = "../compiler/module" } -roc_utils = { path = "../utils" } -serde = { version = "1.0.144", features = ["derive"] } -palette = "0.6.1" -snafu = { version = "0.7.1", features = ["backtraces"] } -bumpalo = { version = "3.11.1", features = ["collections"] } +roc_error_utils = { path = "../utils/error" } + +palette.workspace = true + +bumpalo.workspace = true +serde.workspace = true +snafu.workspace = true diff --git a/crates/code_markup/src/markup/nodes.rs b/crates/code_markup/src/markup/nodes.rs index 9f1551d1e4..4ff045dea4 100644 --- a/crates/code_markup/src/markup/nodes.rs +++ b/crates/code_markup/src/markup/nodes.rs @@ -16,7 +16,7 @@ use roc_ast::{ lang::{core::ast::ASTNodeId, env::Env}, mem_pool::pool_str::PoolStr, }; -use roc_utils::{index_of, slice_get}; +use roc_error_utils::{index_of, slice_get}; use std::fmt; use std::fmt::Write; diff --git a/crates/code_markup/src/markup_error.rs b/crates/code_markup/src/markup_error.rs index 89ac7c45d7..5e888652d2 100644 --- a/crates/code_markup/src/markup_error.rs +++ b/crates/code_markup/src/markup_error.rs @@ -1,4 +1,4 @@ -use roc_utils::util_error::UtilError; +use roc_error_utils::UtilError; use snafu::{Backtrace, NoneError, ResultExt, Snafu}; use crate::slow_pool::MarkNodeId; diff --git a/crates/compiler/alias_analysis/Cargo.toml b/crates/compiler/alias_analysis/Cargo.toml index 3b2abd0000..3d6fd4987b 100644 --- a/crates/compiler/alias_analysis/Cargo.toml +++ b/crates/compiler/alias_analysis/Cargo.toml @@ -1,14 +1,17 @@ [package] -authors = ["The Roc Contributors"] -edition = "2021" -license = "UPL-1.0" name = "roc_alias_analysis" -version = "0.0.1" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dependencies] -morphic_lib = {path = "../../vendor/morphic_lib"} -roc_collections = {path = "../collections"} -roc_module = {path = "../module"} -roc_mono = {path = "../mono"} -roc_debug_flags = {path = "../debug_flags"} +morphic_lib = { path = "../../vendor/morphic_lib" } +roc_collections = { path = "../collections" } +roc_debug_flags = { path = "../debug_flags" } +roc_error_macros = { path = "../../error_macros" } +roc_module = { path = "../module" } +roc_mono = { path = "../mono" } + bumpalo.workspace = true diff --git a/crates/compiler/alias_analysis/src/lib.rs b/crates/compiler/alias_analysis/src/lib.rs index 0b2eae34d0..eab45fc8b6 100644 --- a/crates/compiler/alias_analysis/src/lib.rs +++ b/crates/compiler/alias_analysis/src/lib.rs @@ -9,6 +9,7 @@ use morphic_lib::{ TypeDefBuilder, TypeId, TypeName, UpdateModeVar, ValueId, }; use roc_collections::all::{MutMap, MutSet}; +use roc_error_macros::internal_error; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; @@ -187,20 +188,28 @@ where let func_name = FuncName(&bytes); if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts { - for (_, (symbol, top_level, layout)) in aliases { - match layout { + for (_, hels) in aliases { + match hels.raw_function_layout { RawFunctionLayout::Function(_, _, _) => { - let it = top_level.arguments.iter().copied(); - let bytes = - func_name_bytes_help(*symbol, it, Niche::NONE, top_level.result); + let it = hels.proc_layout.arguments.iter().copied(); + let bytes = func_name_bytes_help( + hels.symbol, + it, + Niche::NONE, + hels.proc_layout.result, + ); - host_exposed_functions.push((bytes, top_level.arguments)); + host_exposed_functions.push((bytes, hels.proc_layout.arguments)); } RawFunctionLayout::ZeroArgumentThunk(_) => { - let bytes = - func_name_bytes_help(*symbol, [], Niche::NONE, top_level.result); + let bytes = func_name_bytes_help( + hels.symbol, + [], + Niche::NONE, + hels.proc_layout.result, + ); - host_exposed_functions.push((bytes, top_level.arguments)); + host_exposed_functions.push((bytes, hels.proc_layout.arguments)); } } } @@ -365,13 +374,7 @@ fn build_entry_point<'a>( let block = builder.add_block(); // to the modelling language, the arguments appear out of thin air - let argument_type = build_tuple_type( - env, - &mut builder, - interner, - layout.arguments, - &WhenRecursive::Unreachable, - )?; + let argument_type = build_tuple_type(env, &mut builder, interner, layout.arguments)?; // does not make any assumptions about the input // let argument = builder.add_unknown_with(block, &[], argument_type)?; @@ -401,13 +404,7 @@ fn build_entry_point<'a>( let block = builder.add_block(); let struct_layout = interner.insert(Layout::struct_no_name_order(layouts)); - let type_id = layout_spec( - env, - &mut builder, - interner, - struct_layout, - &WhenRecursive::Unreachable, - )?; + let type_id = layout_spec(env, &mut builder, interner, struct_layout)?; let argument = builder.add_unknown_with(block, &[], type_id)?; @@ -466,20 +463,8 @@ fn proc_spec<'a>( let args_struct_layout = interner.insert(Layout::struct_no_name_order( argument_layouts.into_bump_slice(), )); - let arg_type_id = layout_spec( - &mut env, - &mut builder, - interner, - args_struct_layout, - &WhenRecursive::Unreachable, - )?; - let ret_type_id = layout_spec( - &mut env, - &mut builder, - interner, - proc.ret_layout, - &WhenRecursive::Unreachable, - )?; + let arg_type_id = layout_spec(&mut env, &mut builder, interner, args_struct_layout)?; + let ret_type_id = layout_spec(&mut env, &mut builder, interner, proc.ret_layout)?; let spec = builder.build(arg_type_id, ret_type_id, root)?; @@ -617,17 +602,10 @@ fn stmt_spec<'a>( let mut type_ids = Vec::new(); for p in parameters.iter() { - type_ids.push(layout_spec( - env, - builder, - interner, - p.layout, - &WhenRecursive::Unreachable, - )?); + type_ids.push(layout_spec(env, builder, interner, p.layout)?); } - let ret_type_id = - layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?; + let ret_type_id = layout_spec(env, builder, interner, layout)?; let jp_arg_type_id = builder.add_tuple_type(&type_ids)?; @@ -668,8 +646,7 @@ fn stmt_spec<'a>( builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id)) } Jump(id, symbols) => { - let ret_type_id = - layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?; + let ret_type_id = layout_spec(env, builder, interner, layout)?; let argument = build_tuple_value(builder, env, block, symbols)?; let jpid = env.join_points[id]; @@ -678,8 +655,7 @@ fn stmt_spec<'a>( Crash(msg, _) => { // Model this as a foreign call rather than TERMINATE because // we want ownership of the message. - let result_type = - layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?; + let result_type = layout_spec(env, builder, interner, layout)?; builder.add_unknown_with(block, &[env.symbols[msg]], result_type) } @@ -708,23 +684,16 @@ fn build_tuple_value( builder.add_make_tuple(block, &value_ids) } -#[derive(Clone, Debug, PartialEq)] -enum WhenRecursive<'a> { - Unreachable, - Loop(UnionLayout<'a>), -} - fn build_recursive_tuple_type<'a>( env: &mut Env<'a>, builder: &mut impl TypeContext, interner: &STLayoutInterner<'a>, layouts: &[InLayout<'a>], - when_recursive: &WhenRecursive, ) -> Result { let mut field_types = Vec::new(); for field in layouts.iter() { - let type_id = layout_spec_help(env, builder, interner, *field, when_recursive)?; + let type_id = layout_spec_help(env, builder, interner, *field)?; field_types.push(type_id); } @@ -736,12 +705,11 @@ fn build_tuple_type<'a>( builder: &mut impl TypeContext, interner: &STLayoutInterner<'a>, layouts: &[InLayout<'a>], - when_recursive: &WhenRecursive, ) -> Result { let mut field_types = Vec::new(); for field in layouts.iter() { - field_types.push(layout_spec(env, builder, interner, *field, when_recursive)?); + field_types.push(layout_spec(env, builder, interner, *field)?); } builder.add_tuple_type(&field_types) @@ -811,13 +779,7 @@ fn call_spec<'a>( .map(|symbol| env.symbols[symbol]) .collect(); - let result_type = layout_spec( - env, - builder, - interner, - *ret_layout, - &WhenRecursive::Unreachable, - )?; + let result_type = layout_spec(env, builder, interner, *ret_layout)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -888,23 +850,11 @@ fn call_spec<'a>( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec( - env, - builder, - interner, - *return_layout, - &WhenRecursive::Unreachable, - )?; + let output_element_type = layout_spec(env, builder, interner, *return_layout)?; let state_layout = interner.insert(Layout::Builtin(Builtin::List(*return_layout))); - let state_type = layout_spec( - env, - builder, - interner, - state_layout, - &WhenRecursive::Unreachable, - )?; + let state_type = layout_spec(env, builder, interner, state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -931,13 +881,7 @@ fn call_spec<'a>( let arg0_layout = argument_layouts[0]; let state_layout = interner.insert(Layout::Builtin(Builtin::List(arg0_layout))); - let state_type = layout_spec( - env, - builder, - interner, - state_layout, - &WhenRecursive::Unreachable, - )?; + let state_type = layout_spec(env, builder, interner, state_layout)?; let init_state = list; add_loop(builder, block, state_type, init_state, loop_body) @@ -961,23 +905,11 @@ fn call_spec<'a>( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec( - env, - builder, - interner, - *return_layout, - &WhenRecursive::Unreachable, - )?; + let output_element_type = layout_spec(env, builder, interner, *return_layout)?; let state_layout = interner.insert(Layout::Builtin(Builtin::List(*return_layout))); - let state_type = layout_spec( - env, - builder, - interner, - state_layout, - &WhenRecursive::Unreachable, - )?; + let state_type = layout_spec(env, builder, interner, state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -1007,23 +939,11 @@ fn call_spec<'a>( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec( - env, - builder, - interner, - *return_layout, - &WhenRecursive::Unreachable, - )?; + let output_element_type = layout_spec(env, builder, interner, *return_layout)?; let state_layout = interner.insert(Layout::Builtin(Builtin::List(*return_layout))); - let state_type = layout_spec( - env, - builder, - interner, - state_layout, - &WhenRecursive::Unreachable, - )?; + let state_type = layout_spec(env, builder, interner, state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -1059,23 +979,11 @@ fn call_spec<'a>( list_append(builder, block, update_mode_var, state, new_element) }; - let output_element_type = layout_spec( - env, - builder, - interner, - *return_layout, - &WhenRecursive::Unreachable, - )?; + let output_element_type = layout_spec(env, builder, interner, *return_layout)?; let state_layout = interner.insert(Layout::Builtin(Builtin::List(*return_layout))); - let state_type = layout_spec( - env, - builder, - interner, - state_layout, - &WhenRecursive::Unreachable, - )?; + let state_type = layout_spec(env, builder, interner, state_layout)?; let init_state = new_list(builder, block, output_element_type)?; @@ -1130,7 +1038,7 @@ fn lowlevel_spec<'a>( ) -> Result { use LowLevel::*; - let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?; + let type_id = layout_spec(env, builder, interner, layout)?; let mode = update_mode.to_bytes(); let update_mode_var = UpdateModeVar(&mode); @@ -1238,13 +1146,7 @@ fn lowlevel_spec<'a>( match interner.get(layout) { Layout::Builtin(Builtin::List(element_layout)) => { - let type_id = layout_spec( - env, - builder, - interner, - element_layout, - &WhenRecursive::Unreachable, - )?; + let type_id = layout_spec(env, builder, interner, element_layout)?; new_list(builder, block, type_id) } _ => unreachable!("empty array does not have a list layout"), @@ -1255,6 +1157,11 @@ fn lowlevel_spec<'a>( list_clone(builder, block, update_mode_var, list) } + ListReleaseExcessCapacity => { + let list = env.symbols[&arguments[0]]; + + list_clone(builder, block, update_mode_var, list) + } ListAppendUnsafe => { let list = env.symbols[&arguments[0]]; let to_insert = env.symbols[&arguments[1]]; @@ -1287,8 +1194,7 @@ fn lowlevel_spec<'a>( // TODO overly pessimstic let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect(); - let result_type = - layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?; + let result_type = layout_spec(env, builder, interner, layout)?; builder.add_unknown_with(block, &arguments, result_type) } @@ -1299,12 +1205,9 @@ fn recursive_tag_variant<'a>( env: &mut Env<'a>, builder: &mut impl TypeContext, interner: &STLayoutInterner<'a>, - union_layout: &UnionLayout, fields: &[InLayout<'a>], ) -> Result { - let when_recursive = WhenRecursive::Loop(*union_layout); - - build_recursive_tuple_type(env, builder, interner, fields, &when_recursive) + build_recursive_tuple_type(env, builder, interner, fields) } fn recursive_variant_types<'a>( @@ -1325,23 +1228,11 @@ fn recursive_variant_types<'a>( result = Vec::with_capacity(tags.len()); for tag in tags.iter() { - result.push(recursive_tag_variant( - env, - builder, - interner, - union_layout, - tag, - )?); + result.push(recursive_tag_variant(env, builder, interner, tag)?); } } NonNullableUnwrapped(fields) => { - result = vec![recursive_tag_variant( - env, - builder, - interner, - union_layout, - fields, - )?]; + result = vec![recursive_tag_variant(env, builder, interner, fields)?]; } NullableWrapped { nullable_id, @@ -1352,39 +1243,21 @@ fn recursive_variant_types<'a>( let cutoff = *nullable_id as usize; for tag in tags[..cutoff].iter() { - result.push(recursive_tag_variant( - env, - builder, - interner, - union_layout, - tag, - )?); + result.push(recursive_tag_variant(env, builder, interner, tag)?); } - result.push(recursive_tag_variant( - env, - builder, - interner, - union_layout, - &[], - )?); + result.push(recursive_tag_variant(env, builder, interner, &[])?); for tag in tags[cutoff..].iter() { - result.push(recursive_tag_variant( - env, - builder, - interner, - union_layout, - tag, - )?); + result.push(recursive_tag_variant(env, builder, interner, tag)?); } } NullableUnwrapped { nullable_id, other_fields: fields, } => { - let unit = recursive_tag_variant(env, builder, interner, union_layout, &[])?; - let other_type = recursive_tag_variant(env, builder, interner, union_layout, fields)?; + let unit = recursive_tag_variant(env, builder, interner, &[])?; + let other_type = recursive_tag_variant(env, builder, interner, fields)?; if *nullable_id { // nullable_id == 1 @@ -1432,13 +1305,7 @@ fn expr_spec<'a>( let value_id = match tag_layout { UnionLayout::NonRecursive(tags) => { - let variant_types = non_recursive_variant_types( - env, - builder, - interner, - tags, - &WhenRecursive::Unreachable, - )?; + let variant_types = non_recursive_variant_types(env, builder, interner, tags)?; let value_id = build_tuple_value(builder, env, block, arguments)?; return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); } @@ -1543,13 +1410,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( - env, - builder, - interner, - *elem_layout, - &WhenRecursive::Unreachable, - )?; + let type_id = layout_spec(env, builder, interner, *elem_layout)?; let list = new_list(builder, block, type_id)?; @@ -1576,13 +1437,7 @@ fn expr_spec<'a>( EmptyArray => match interner.get(layout) { Layout::Builtin(Builtin::List(element_layout)) => { - let type_id = layout_spec( - env, - builder, - interner, - element_layout, - &WhenRecursive::Unreachable, - )?; + let type_id = layout_spec(env, builder, interner, element_layout)?; new_list(builder, block, type_id) } _ => unreachable!("empty array does not have a list layout"), @@ -1615,7 +1470,7 @@ fn expr_spec<'a>( with_new_heap_cell(builder, block, union_data) } RuntimeErrorFunction(_) => { - let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?; + let type_id = layout_spec(env, builder, interner, layout)?; builder.add_terminate(block, type_id) } @@ -1647,9 +1502,8 @@ fn layout_spec<'a>( builder: &mut impl TypeContext, interner: &STLayoutInterner<'a>, layout: InLayout<'a>, - when_recursive: &WhenRecursive, ) -> Result { - layout_spec_help(env, builder, interner, layout, when_recursive) + layout_spec_help(env, builder, interner, layout) } fn non_recursive_variant_types<'a>( @@ -1657,19 +1511,11 @@ fn non_recursive_variant_types<'a>( builder: &mut impl TypeContext, interner: &STLayoutInterner<'a>, tags: &[&[InLayout<'a>]], - // 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( - env, - builder, - interner, - tag, - when_recursive, - )?); + result.push(build_tuple_type(env, builder, interner, tag)?); } Ok(result) @@ -1680,22 +1526,17 @@ fn layout_spec_help<'a>( builder: &mut impl TypeContext, interner: &STLayoutInterner<'a>, layout: InLayout<'a>, - when_recursive: &WhenRecursive, ) -> Result { use Layout::*; match interner.get(layout) { - Builtin(builtin) => builtin_spec(env, builder, interner, &builtin, when_recursive), + Builtin(builtin) => builtin_spec(env, builder, interner, &builtin), Struct { field_layouts, .. } => { - build_recursive_tuple_type(env, builder, interner, field_layouts, when_recursive) + build_recursive_tuple_type(env, builder, interner, field_layouts) + } + LambdaSet(lambda_set) => { + layout_spec_help(env, builder, interner, lambda_set.runtime_representation()) } - LambdaSet(lambda_set) => layout_spec_help( - env, - builder, - interner, - lambda_set.runtime_representation(), - when_recursive, - ), Union(union_layout) => { match union_layout { UnionLayout::NonRecursive(&[]) => { @@ -1705,8 +1546,7 @@ fn layout_spec_help<'a>( builder.add_tuple_type(&[]) } UnionLayout::NonRecursive(tags) => { - let variant_types = - non_recursive_variant_types(env, builder, interner, tags, when_recursive)?; + let variant_types = non_recursive_variant_types(env, builder, interner, tags)?; builder.add_union_type(&variant_types) } UnionLayout::Recursive(_) @@ -1724,29 +1564,21 @@ fn layout_spec_help<'a>( } Boxed(inner_layout) => { - let inner_type = - layout_spec_help(env, builder, interner, inner_layout, when_recursive)?; + let inner_type = layout_spec_help(env, builder, interner, inner_layout)?; let cell_type = builder.add_heap_cell_type(); builder.add_tuple_type(&[cell_type, inner_type]) } // TODO(recursive-layouts): update once we have recursive pointer loops - RecursivePointer(_) => match when_recursive { - WhenRecursive::Unreachable => { - unreachable!() - } - WhenRecursive::Loop(union_layout) => match union_layout { - UnionLayout::NonRecursive(_) => unreachable!(), - UnionLayout::Recursive(_) - | UnionLayout::NullableUnwrapped { .. } - | UnionLayout::NullableWrapped { .. } - | UnionLayout::NonNullableUnwrapped(_) => { - let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes(); - let type_name = TypeName(&type_name_bytes); + RecursivePointer(union_layout) => match interner.get(union_layout) { + Layout::Union(union_layout) => { + assert!(!matches!(union_layout, UnionLayout::NonRecursive(..))); + let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); - Ok(builder.add_named_type(MOD_APP, type_name)) - } - }, + Ok(builder.add_named_type(MOD_APP, type_name)) + } + _ => internal_error!("somehow, a non-recursive layout is under a recursive pointer"), }, } } @@ -1756,7 +1588,6 @@ fn builtin_spec<'a>( builder: &mut impl TypeContext, interner: &STLayoutInterner<'a>, builtin: &Builtin<'a>, - when_recursive: &WhenRecursive, ) -> Result { use Builtin::*; @@ -1765,8 +1596,7 @@ fn builtin_spec<'a>( Decimal | Float(_) => builder.add_tuple_type(&[]), Str => str_type(builder), List(element_layout) => { - let element_type = - layout_spec_help(env, builder, interner, *element_layout, when_recursive)?; + let element_type = layout_spec_help(env, builder, interner, *element_layout)?; let cell = builder.add_heap_cell_type(); let bag = builder.add_bag_type(element_type)?; diff --git a/crates/compiler/arena_pool/Cargo.toml b/crates/compiler/arena_pool/Cargo.toml index 1afb7e4eb3..ab35ee4d37 100644 --- a/crates/compiler/arena_pool/Cargo.toml +++ b/crates/compiler/arena_pool/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "arena-pool" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -repository = "https://github.com/roc-lang/roc" -edition = "2021" description = "An implementation of an arena allocator designed for the compiler's workloads." +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + [dependencies] roc_error_macros = { path = "../../error_macros" } diff --git a/crates/compiler/build/Cargo.toml b/crates/compiler/build/Cargo.toml index ac04bd5cef..2dae039b57 100644 --- a/crates/compiler/build/Cargo.toml +++ b/crates/compiler/build/Cargo.toml @@ -1,52 +1,55 @@ [package] name = "roc_build" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Responsible for coordinating building and linking of a Roc app with its host." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -roc_collections = { path = "../collections" } +roc_bitcode = { path = "../builtins/bitcode" } roc_can = { path = "../can" } -roc_parse = { path = "../parse" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_problem = { path = "../problem" } -roc_types = { path = "../types" } -roc_builtins = { path = "../builtins" } +roc_collections = { path = "../collections" } roc_constrain = { path = "../constrain" } -roc_unify = { path = "../unify" } -roc_solve_problem = { path = "../solve_problem" } -roc_mono = { path = "../mono" } -roc_load = { path = "../load" } -roc_target = { path = "../roc_target" } +roc_error_macros = { path = "../../error_macros" } +roc_gen_dev = { path = "../gen_dev", default-features = false } roc_gen_llvm = { path = "../gen_llvm" } roc_gen_wasm = { path = "../gen_wasm" } -roc_gen_dev = { path = "../gen_dev", default-features = false } +roc_linker = { path = "../../linker" } +roc_load = { path = "../load" } +roc_module = { path = "../module" } +roc_mono = { path = "../mono" } +roc_packaging = { path = "../../packaging" } +roc_parse = { path = "../parse" } +roc_problem = { path = "../problem" } +roc_region = { path = "../region" } roc_reporting = { path = "../../reporting" } -roc_error_macros = { path = "../../error_macros" } +roc_solve_problem = { path = "../solve_problem" } roc_std = { path = "../../roc_std" } -roc_utils = { path = "../../utils" } +roc_target = { path = "../roc_target" } +roc_types = { path = "../types" } +roc_unify = { path = "../unify" } +roc_command_utils = { path = "../../utils/command" } wasi_libc_sys = { path = "../../wasi-libc-sys" } -const_format.workspace = true bumpalo.workspace = true -libloading.workspace = true -tempfile.workspace = true -target-lexicon.workspace = true +indoc.workspace = true inkwell.workspace = true +libloading.workspace = true +target-lexicon.workspace = true +tempfile.workspace = true [target.'cfg(target_os = "macos")'.dependencies] -serde_json = "1.0.85" +serde_json.workspace = true [features] -target-arm = [] target-aarch64 = ["roc_gen_dev/target-aarch64"] +target-arm = [] +target-wasm32 = [] target-x86 = [] target-x86_64 = ["roc_gen_dev/target-x86_64"] -target-wasm32 = [] # This is used to enable fuzzing and sanitizers. # Example use is describe here: https://github.com/bhansconnect/roc-fuzz diff --git a/crates/compiler/build/src/link.rs b/crates/compiler/build/src/link.rs index cf9c11231c..6798dc65d6 100644 --- a/crates/compiler/build/src/link.rs +++ b/crates/compiler/build/src/link.rs @@ -1,26 +1,18 @@ use crate::target::{arch_str, target_zig_str}; -use const_format::concatcp; use libloading::{Error, Library}; -use roc_builtins::bitcode; +use roc_command_utils::{cargo, clang, get_lib_path, rustup, zig}; use roc_error_macros::internal_error; use roc_mono::ir::OptLevel; -use roc_utils::{cargo, clang, zig}; -use roc_utils::{get_lib_path, rustup}; use std::collections::HashMap; -use std::env; +use std::fs::DirEntry; use std::io; use std::path::{Path, PathBuf}; use std::process::{self, Child, Command}; +use std::{env, fs}; use target_lexicon::{Architecture, OperatingSystem, Triple}; use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH}; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum LinkType { - // These numbers correspond to the --lib and --no-link flags - Executable = 0, - Dylib = 1, - None = 2, -} +pub use roc_linker::LinkType; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum LinkingStrategy { @@ -60,134 +52,15 @@ pub fn link( } } -const PRECOMPILED_HOST_EXT: &str = "rh1"; // Short for "roc host version 1" (so we can change format in the future) - -const WASM_TARGET_STR: &str = "wasm32"; -const LINUX_X86_64_TARGET_STR: &str = "linux-x86_64"; -const LINUX_ARM64_TARGET_STR: &str = "linux-arm64"; -const MACOS_ARM64_TARGET_STR: &str = "macos-arm64"; -const MACOS_X86_64_TARGET_STR: &str = "macos-x86_64"; -const WINDOWS_X86_64_TARGET_STR: &str = "windows-x86_64"; -const WINDOWS_X86_32_TARGET_STR: &str = "windows-x86_32"; -const WIDNOWS_ARM64_TARGET_STR: &str = "windows-arm64"; - -pub const fn preprocessed_host_filename(target: &Triple) -> Option<&'static str> { - // Don't try to split this match off in a different function, it will not work with concatcp - match target { - Triple { - architecture: Architecture::Wasm32, - .. - } => Some(concatcp!(WASM_TARGET_STR, '.', PRECOMPILED_HOST_EXT)), - Triple { - operating_system: OperatingSystem::Linux, - architecture: Architecture::X86_64, - .. - } => Some(concatcp!( - LINUX_X86_64_TARGET_STR, - '.', - PRECOMPILED_HOST_EXT - )), - Triple { - operating_system: OperatingSystem::Linux, - architecture: Architecture::Aarch64(_), - .. - } => Some(concatcp!(LINUX_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)), - Triple { - operating_system: OperatingSystem::Darwin, - architecture: Architecture::Aarch64(_), - .. - } => Some(concatcp!(MACOS_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)), - Triple { - operating_system: OperatingSystem::Darwin, - architecture: Architecture::X86_64, - .. - } => Some(concatcp!( - MACOS_X86_64_TARGET_STR, - '.', - PRECOMPILED_HOST_EXT - )), - Triple { - operating_system: OperatingSystem::Windows, - architecture: Architecture::X86_64, - .. - } => Some(concatcp!( - WINDOWS_X86_64_TARGET_STR, - '.', - PRECOMPILED_HOST_EXT - )), - Triple { - operating_system: OperatingSystem::Windows, - architecture: Architecture::X86_32(_), - .. - } => Some(concatcp!( - WINDOWS_X86_32_TARGET_STR, - '.', - PRECOMPILED_HOST_EXT - )), - Triple { - operating_system: OperatingSystem::Windows, - architecture: Architecture::Aarch64(_), - .. - } => Some(concatcp!( - WIDNOWS_ARM64_TARGET_STR, - '.', - PRECOMPILED_HOST_EXT - )), - _ => None, - } -} - -pub fn get_target_triple_str(target: &Triple) -> Option<&'static str> { - match target { - Triple { - architecture: Architecture::Wasm32, - .. - } => Some(WASM_TARGET_STR), - Triple { - operating_system: OperatingSystem::Linux, - architecture: Architecture::X86_64, - .. - } => Some(LINUX_X86_64_TARGET_STR), - Triple { - operating_system: OperatingSystem::Linux, - architecture: Architecture::Aarch64(_), - .. - } => Some(LINUX_ARM64_TARGET_STR), - Triple { - operating_system: OperatingSystem::Darwin, - architecture: Architecture::Aarch64(_), - .. - } => Some(MACOS_ARM64_TARGET_STR), - Triple { - operating_system: OperatingSystem::Darwin, - architecture: Architecture::X86_64, - .. - } => Some(MACOS_X86_64_TARGET_STR), - Triple { - operating_system: OperatingSystem::Windows, - architecture: Architecture::X86_64, - .. - } => Some(WINDOWS_X86_64_TARGET_STR), - Triple { - operating_system: OperatingSystem::Windows, - architecture: Architecture::X86_32(_), - .. - } => Some(WINDOWS_X86_32_TARGET_STR), - Triple { - operating_system: OperatingSystem::Windows, - architecture: Architecture::Aarch64(_), - .. - } => Some(WIDNOWS_ARM64_TARGET_STR), - _ => None, - } -} - /// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj" pub fn legacy_host_filename(target: &Triple) -> Option { let os = roc_target::OperatingSystem::from(target.operating_system); let ext = os.object_file_ext(); - Some(preprocessed_host_filename(target)?.replace(PRECOMPILED_HOST_EXT, ext)) + Some( + roc_linker::preprocessed_host_filename(target)? + .replace(roc_linker::PRECOMPILED_HOST_EXT, ext), + ) } fn find_zig_str_path() -> PathBuf { @@ -681,7 +554,7 @@ pub fn rebuild_host( let env_cpath = env::var("CPATH").unwrap_or_else(|_| "".to_string()); let builtins_host_tempfile = - bitcode::host_tempfile().expect("failed to write host builtins object to tempfile"); + roc_bitcode::host_tempfile().expect("failed to write host builtins object to tempfile"); if zig_host_src.exists() { // Compile host.zig @@ -752,14 +625,6 @@ pub fn rebuild_host( // Compile and link Cargo.toml, if it exists let cargo_dir = platform_main_roc.parent().unwrap(); - let cargo_out_dir = cargo_dir.join("target").join( - if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) { - "release" - } else { - "debug" - }, - ); - let mut cargo_cmd = if cfg!(windows) { // on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols` // using `+nightly` only works when running cargo through rustup @@ -793,11 +658,20 @@ pub fn rebuild_host( run_build_command(cargo_cmd, source_file, 0); + let cargo_out_dir = find_used_target_sub_folder(opt_level, cargo_dir.join("target")); + if shared_lib_path.is_some() { // For surgical linking, just copy the dynamically linked rust app. let mut exe_path = cargo_out_dir.join("host"); exe_path.set_extension(executable_extension); - std::fs::copy(&exe_path, &host_dest).unwrap(); + if let Err(e) = std::fs::copy(&exe_path, &host_dest) { + panic!( + "unable to copy {} => {}: {:?}\n\nIs the file used by another invocation of roc?", + exe_path.display(), + host_dest.display(), + e, + ); + } } else { // Cargo hosts depend on a c wrapper for the api. Compile host.c as well. @@ -943,6 +817,63 @@ pub fn rebuild_host( host_dest } +// there can be multiple release folders, one in target and one in target/x86_64-unknown-linux-musl, +// we want the one that was most recently used +fn find_used_target_sub_folder(opt_level: OptLevel, target_folder: PathBuf) -> PathBuf { + let out_folder_name = if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) { + "release" + } else { + "debug" + }; + + let matching_folders = find_in_folder_or_subfolders(&target_folder, out_folder_name); + let mut matching_folders_iter = matching_folders.iter(); + + let mut out_folder = match matching_folders_iter.next() { + Some(dir_entry) => dir_entry, + None => panic!("I could not find a folder named {} in {:?}. This may be because the `cargo build` for the platform went wrong.", out_folder_name, target_folder) + }; + + let mut out_folder_last_change = out_folder.metadata().unwrap().modified().unwrap(); + + for dir_entry in matching_folders_iter { + let last_modified = dir_entry.metadata().unwrap().modified().unwrap(); + + if last_modified > out_folder_last_change { + out_folder_last_change = last_modified; + out_folder = dir_entry; + } + } + + out_folder.path().canonicalize().unwrap() +} + +fn find_in_folder_or_subfolders(path: &PathBuf, folder_to_find: &str) -> Vec { + let mut matching_dirs = vec![]; + + if let Ok(entries) = fs::read_dir(path) { + for entry in entries.flatten() { + if entry.file_type().unwrap().is_dir() { + let dir_name = entry + .file_name() + .into_string() + .unwrap_or_else(|_| "".to_string()); + + if dir_name == folder_to_find { + matching_dirs.push(entry) + } else { + let matched_in_sub_dir = + find_in_folder_or_subfolders(&entry.path(), folder_to_find); + + matching_dirs.extend(matched_in_sub_dir); + } + } + } + } + + matching_dirs +} + fn get_target_str(target: &Triple) -> &str { if target.operating_system == OperatingSystem::Windows && target.environment == target_lexicon::Environment::Gnu @@ -1502,8 +1433,8 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P (but seems to be an unofficial API) */ - let builtins_host_tempfile = - bitcode::host_wasm_tempfile().expect("failed to write host builtins object to tempfile"); + let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile() + .expect("failed to write host builtins object to tempfile"); let mut zig_cmd = zig(); let args = &[ diff --git a/crates/compiler/build/src/program.rs b/crates/compiler/build/src/program.rs index 2a3bbcecbc..cee336736f 100644 --- a/crates/compiler/build/src/program.rs +++ b/crates/compiler/build/src/program.rs @@ -1,17 +1,36 @@ +use crate::link::{ + legacy_host_filename, link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy, +}; +use bumpalo::Bump; use inkwell::memory_buffer::MemoryBuffer; use roc_error_macros::internal_error; use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode}; use roc_gen_llvm::llvm::externs::add_default_roc_externs; -use roc_load::{EntryPoint, ExpectMetadata, LoadedModule, MonomorphizedModule}; +use roc_load::{ + EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule, + LoadingProblem, MonomorphizedModule, Threading, +}; use roc_mono::ir::{OptLevel, SingleEntryPoint}; -use roc_reporting::cli::{report_problems, Problems}; +use roc_packaging::cache::RocCacheDir; +use roc_reporting::{ + cli::{report_problems, Problems}, + report::{RenderTarget, DEFAULT_PALETTE}, +}; +use roc_target::TargetInfo; +use std::ffi::OsStr; use std::ops::Deref; -use std::path::{Path, PathBuf}; -use std::time::{Duration, Instant}; +use std::{ + path::{Path, PathBuf}, + thread::JoinHandle, + time::{Duration, Instant}, +}; +use target_lexicon::Triple; #[cfg(feature = "target-wasm32")] use roc_collections::all::MutSet; +pub const DEFAULT_ROC_FILENAME: &str = "main.roc"; + #[derive(Debug, Clone, Copy, Default)] pub struct CodeGenTiming { pub code_gen: Duration, @@ -56,7 +75,7 @@ impl Deref for CodeObject { #[derive(Debug, Clone, Copy)] pub enum CodeGenBackend { Assembly, - Llvm, + Llvm(LlvmBackendMode), Wasm, } @@ -79,6 +98,10 @@ pub fn gen_from_mono_module<'a>( preprocessed_host_path: &Path, wasm_dev_stack_bytes: Option, ) -> GenFromMono<'a> { + let path = roc_file_path; + let debug = code_gen_options.emit_debug_info; + let opt = code_gen_options.opt_level; + match code_gen_options.backend { CodeGenBackend::Assembly => gen_from_mono_module_dev( arena, @@ -87,12 +110,18 @@ pub fn gen_from_mono_module<'a>( preprocessed_host_path, wasm_dev_stack_bytes, ), - CodeGenBackend::Llvm => { - gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options) + CodeGenBackend::Llvm(backend_mode) => { + gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug) } CodeGenBackend::Wasm => { // emit wasm via the llvm backend - gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options) + + let backend_mode = match code_gen_options.opt_level { + OptLevel::Development => LlvmBackendMode::BinaryDev, + OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary, + }; + + gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug) } } } @@ -105,7 +134,9 @@ fn gen_from_mono_module_llvm<'a>( mut loaded: MonomorphizedModule<'a>, roc_file_path: &Path, target: &target_lexicon::Triple, - code_gen_options: CodeGenOptions, + opt_level: OptLevel, + backend_mode: LlvmBackendMode, + emit_debug_info: bool, ) -> GenFromMono<'a> { use crate::target::{self, convert_opt_level}; use inkwell::attributes::{Attribute, AttributeLoc}; @@ -155,12 +186,6 @@ fn gen_from_mono_module_llvm<'a>( } } - let CodeGenOptions { - backend: _, - opt_level, - emit_debug_info, - } = code_gen_options; - let builder = context.create_builder(); let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module); let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level); @@ -175,12 +200,14 @@ fn gen_from_mono_module_llvm<'a>( interns: loaded.interns, module, target_info, - mode: match opt_level { - OptLevel::Development => LlvmBackendMode::BinaryDev, - OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary, - }, + mode: backend_mode, - exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(), + exposed_to_host: loaded + .exposed_to_host + .top_level_values + .keys() + .copied() + .collect(), }; // does not add any externs for this mode (we have a host) but cleans up some functions around @@ -208,6 +235,7 @@ fn gen_from_mono_module_llvm<'a>( loaded.procedures, entry_point, Some(&app_ll_file), + &loaded.glue_layouts, ); env.dibuilder.finalize(); @@ -484,7 +512,7 @@ fn gen_from_mono_module_dev_wasm32<'a>( let exposed_to_host = loaded .exposed_to_host - .values + .top_level_values .keys() .copied() .collect::>(); @@ -555,7 +583,7 @@ fn gen_from_mono_module_dev_assembly<'a>( let env = roc_gen_dev::Env { arena, module_id, - exposed_to_host: exposed_to_host.values.keys().copied().collect(), + exposed_to_host: exposed_to_host.top_level_values.keys().copied().collect(), lazy_literals, generate_allocators, }; @@ -579,3 +607,675 @@ fn gen_from_mono_module_dev_assembly<'a>( }, ) } + +fn report_timing(buf: &mut String, label: &str, duration: Duration) { + use std::fmt::Write; + + writeln!( + buf, + " {:9.3} ms {}", + duration.as_secs_f64() * 1000.0, + label, + ) + .unwrap() +} + +pub struct BuiltFile<'a> { + pub binary_path: PathBuf, + pub problems: Problems, + pub total_time: Duration, + pub expect_metadata: ExpectMetadata<'a>, +} + +pub enum BuildOrdering { + /// Run up through typechecking first; continue building iff that is successful. + BuildIfChecks, + /// Always build the Roc binary, even if there are type errors. + AlwaysBuild, +} + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum BuildFileError<'a> { + LoadingProblem(LoadingProblem<'a>), + ErrorModule { + module: LoadedModule, + total_time: Duration, + }, +} + +impl<'a> BuildFileError<'a> { + fn from_mono_error(error: LoadMonomorphizedError<'a>, compilation_start: Instant) -> Self { + match error { + LoadMonomorphizedError::LoadingProblem(problem) => { + BuildFileError::LoadingProblem(problem) + } + LoadMonomorphizedError::ErrorModule(module) => BuildFileError::ErrorModule { + module, + total_time: compilation_start.elapsed(), + }, + } + } +} + +pub fn handle_error_module( + mut module: roc_load::LoadedModule, + total_time: std::time::Duration, + filename: &OsStr, + print_run_anyway_hint: bool, +) -> std::io::Result { + debug_assert!(module.total_problems() > 0); + + let problems = report_problems_typechecked(&mut module); + + problems.print_to_stdout(total_time); + + if print_run_anyway_hint { + // If you're running "main.roc" then you can just do `roc run` + // to re-run the program. + print!(".\n\nYou can run the program anyway with \x1B[32mroc run"); + + if filename != DEFAULT_ROC_FILENAME { + print!(" {}", &filename.to_string_lossy()); + } + + println!("\x1B[39m"); + } + + Ok(problems.exit_code()) +} + +pub fn handle_loading_problem(problem: LoadingProblem) -> std::io::Result { + match problem { + LoadingProblem::FormattedReport(report) => { + print!("{}", report); + Ok(1) + } + _ => { + // TODO: tighten up the types here, we should always end up with a + // formatted report from load. + print!("Failed with error: {:?}", problem); + Ok(1) + } + } +} + +pub fn standard_load_config( + target: &Triple, + order: BuildOrdering, + threading: Threading, +) -> LoadConfig { + let target_info = TargetInfo::from(target); + + let exec_mode = match order { + BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck, + BuildOrdering::AlwaysBuild => ExecutionMode::Executable, + }; + + LoadConfig { + target_info, + render: RenderTarget::ColorTerminal, + palette: DEFAULT_PALETTE, + threading, + exec_mode, + } +} + +#[allow(clippy::too_many_arguments)] +pub fn build_file<'a>( + arena: &'a Bump, + target: &Triple, + app_module_path: PathBuf, + code_gen_options: CodeGenOptions, + emit_timings: bool, + link_type: LinkType, + linking_strategy: LinkingStrategy, + prebuilt_requested: bool, + wasm_dev_stack_bytes: Option, + roc_cache_dir: RocCacheDir<'_>, + load_config: LoadConfig, +) -> Result, BuildFileError<'a>> { + let compilation_start = Instant::now(); + + // Step 1: compile the app and generate the .o file + let loaded = + roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config) + .map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?; + + build_loaded_file( + arena, + target, + app_module_path, + code_gen_options, + emit_timings, + link_type, + linking_strategy, + prebuilt_requested, + wasm_dev_stack_bytes, + loaded, + compilation_start, + ) +} + +#[allow(clippy::too_many_arguments)] +fn build_loaded_file<'a>( + arena: &'a Bump, + target: &Triple, + app_module_path: PathBuf, + code_gen_options: CodeGenOptions, + emit_timings: bool, + link_type: LinkType, + linking_strategy: LinkingStrategy, + prebuilt_requested: bool, + wasm_dev_stack_bytes: Option, + loaded: roc_load::MonomorphizedModule<'a>, + compilation_start: Instant, +) -> Result, BuildFileError<'a>> { + let operating_system = roc_target::OperatingSystem::from(target.operating_system); + + let platform_main_roc = match &loaded.entry_point { + EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(), + _ => unreachable!(), + }; + + // the preprocessed host is stored beside the platform's main.roc + let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy { + if let roc_target::OperatingSystem::Wasi = operating_system { + // when compiling a wasm application, we implicitly assume here that the host is in zig + // and has a file called "host.zig" + platform_main_roc.with_file_name("host.zig") + } else { + platform_main_roc.with_file_name(legacy_host_filename(target).unwrap()) + } + } else { + platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target).unwrap()) + }; + + // For example, if we're loading the platform from a URL, it's automatically prebuilt + // even if the --prebuilt-platform=true CLI flag wasn't set. + let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform; + + let cwd = app_module_path.parent().unwrap(); + let mut output_exe_path = cwd.join(&*loaded.output_path); + + if let Some(extension) = operating_system.executable_file_ext() { + output_exe_path.set_extension(extension); + } + + // We don't need to spawn a rebuild thread when using a prebuilt host. + let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) { + None + } else if is_platform_prebuilt { + if !preprocessed_host_path.exists() { + invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path); + + std::process::exit(1); + } + + if linking_strategy == LinkingStrategy::Surgical { + // Copy preprocessed host to executable location. + // The surgical linker will modify that copy in-place. + std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap(); + } + + None + } else { + // TODO this should probably be moved before load_and_monomorphize. + // To do this we will need to preprocess files just for their exported symbols. + // Also, we should no longer need to do this once we have platforms on + // a package repository, as we can then get prebuilt platforms from there. + + let dll_stub_symbols = roc_linker::ExposedSymbols::from_exposed_to_host( + &loaded.interns, + &loaded.exposed_to_host, + ); + + let join_handle = spawn_rebuild_thread( + code_gen_options.opt_level, + linking_strategy, + platform_main_roc.clone(), + preprocessed_host_path.clone(), + output_exe_path.clone(), + target, + dll_stub_symbols, + ); + + Some(join_handle) + }; + + let buf = &mut String::with_capacity(1024); + + let mut it = loaded.timings.iter().peekable(); + while let Some((module_id, module_timing)) = it.next() { + let module_name = loaded.interns.module_name(*module_id); + + buf.push_str(" "); + + if module_name.is_empty() { + // the App module + buf.push_str("Application Module"); + } else { + buf.push_str(module_name); + } + + buf.push('\n'); + + use std::fmt::Write; + write!(buf, "{}", module_timing).unwrap(); + + if it.peek().is_some() { + buf.push('\n'); + } + } + + // This only needs to be mutable for report_problems. This can't be done + // inside a nested scope without causing a borrow error! + let mut loaded = loaded; + let problems = report_problems_monomorphized(&mut loaded); + let loaded = loaded; + + enum HostRebuildTiming { + BeforeApp(u128), + ConcurrentWithApp(JoinHandle), + } + + let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread { + if linking_strategy == LinkingStrategy::Additive { + let rebuild_duration = rebuild_thread + .join() + .expect("Failed to (re)build platform."); + + if emit_timings && !is_platform_prebuilt { + println!( + "Finished rebuilding the platform in {} ms\n", + rebuild_duration + ); + } + + Some(HostRebuildTiming::BeforeApp(rebuild_duration)) + } else { + Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread)) + } + } else { + None + }; + + let (roc_app_bytes, code_gen_timing, expect_metadata) = gen_from_mono_module( + arena, + loaded, + &app_module_path, + target, + code_gen_options, + &preprocessed_host_path, + wasm_dev_stack_bytes, + ); + + buf.push('\n'); + buf.push_str(" "); + buf.push_str("Code Generation"); + buf.push('\n'); + + report_timing( + buf, + "Generate Assembly from Mono IR", + code_gen_timing.code_gen, + ); + + let compilation_end = compilation_start.elapsed(); + let size = roc_app_bytes.len(); + + if emit_timings { + println!( + "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}", + buf + ); + + println!( + "Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n", + compilation_end.as_millis(), + size, + ); + } + + if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing { + let rebuild_duration = thread.join().expect("Failed to (re)build platform."); + + if emit_timings && !is_platform_prebuilt { + println!( + "Finished rebuilding the platform in {} ms\n", + rebuild_duration + ); + } + } + + // Step 2: link the prebuilt platform and compiled app + let link_start = Instant::now(); + + match (linking_strategy, link_type) { + (LinkingStrategy::Surgical, _) => { + roc_linker::link_preprocessed_host( + target, + &platform_main_roc, + &roc_app_bytes, + &output_exe_path, + ); + } + (LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => { + // Just copy the object file to the output folder. + output_exe_path.set_extension(operating_system.object_file_ext()); + std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap(); + } + (LinkingStrategy::Legacy, _) => { + let app_o_file = tempfile::Builder::new() + .prefix("roc_app") + .suffix(&format!(".{}", operating_system.object_file_ext())) + .tempfile() + .map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?; + let app_o_file = app_o_file.path(); + + std::fs::write(app_o_file, &*roc_app_bytes).unwrap(); + + let builtins_host_tempfile = roc_bitcode::host_tempfile() + .expect("failed to write host builtins object to tempfile"); + + let mut inputs = vec![app_o_file.to_str().unwrap()]; + + if !matches!(link_type, LinkType::Dylib | LinkType::None) { + // the host has been compiled into a .o or .obj file + inputs.push(preprocessed_host_path.as_path().to_str().unwrap()); + } + + if matches!(code_gen_options.backend, CodeGenBackend::Assembly) { + inputs.push(builtins_host_tempfile.path().to_str().unwrap()); + } + + let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type) + .map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?; + + let exit_status = child + .wait() + .map_err(|_| todo!("gracefully handle error after `ld` spawned"))?; + + // Extend the lifetime of the tempfile so it doesn't get dropped + // (and thus deleted) before the child process is done using it! + let _ = builtins_host_tempfile; + + if !exit_status.success() { + todo!( + "gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}", + exit_status.code() + ); + } + } + } + + let linking_time = link_start.elapsed(); + + if emit_timings { + println!("Finished linking in {} ms\n", linking_time.as_millis()); + } + + let total_time = compilation_start.elapsed(); + + Ok(BuiltFile { + binary_path: output_exe_path, + problems, + total_time, + expect_metadata, + }) +} + +fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) { + let prefix = match prebuilt_requested { + true => "Because I was run with --prebuilt-platform=true, ", + false => "", + }; + + let preprocessed_host_path_str = preprocessed_host_path.to_string_lossy(); + let extra_err_msg = if preprocessed_host_path_str.ends_with(".rh") { + "\n\n\tNote: If the platform does have an .rh1 file but no .rh file, it's because it's been built with an older version of roc. Contact the author to release a new build of the platform using a roc release newer than March 21 2023.\n" + } else { + "" + }; + + eprintln!( + indoc::indoc!( + r#" + {}I was expecting this file to exist: + + {} + + However, it was not there!{} + + If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false + "# + ), + prefix, + preprocessed_host_path.to_string_lossy(), + extra_err_msg + ); +} + +#[allow(clippy::too_many_arguments)] +fn spawn_rebuild_thread( + opt_level: OptLevel, + linking_strategy: LinkingStrategy, + platform_main_roc: PathBuf, + preprocessed_host_path: PathBuf, + output_exe_path: PathBuf, + target: &Triple, + dll_stub_symbols: Vec, +) -> std::thread::JoinHandle { + let thread_local_target = target.clone(); + std::thread::spawn(move || { + // Printing to stderr because we want stdout to contain only the output of the roc program. + // We are aware of the trade-offs. + // `cargo run` follows the same approach + eprintln!("🔨 Rebuilding platform..."); + + let rebuild_host_start = Instant::now(); + + match linking_strategy { + LinkingStrategy::Additive => { + let host_dest = rebuild_host( + opt_level, + &thread_local_target, + platform_main_roc.as_path(), + None, + ); + + preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path); + } + LinkingStrategy::Surgical => { + build_and_preprocess_host_lowlevel( + opt_level, + &thread_local_target, + platform_main_roc.as_path(), + preprocessed_host_path.as_path(), + &dll_stub_symbols, + ); + + // Copy preprocessed host to executable location. + // The surgical linker will modify that copy in-place. + std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap(); + } + LinkingStrategy::Legacy => { + rebuild_host( + opt_level, + &thread_local_target, + platform_main_roc.as_path(), + None, + ); + } + } + + rebuild_host_start.elapsed().as_millis() + }) +} + +pub fn build_and_preprocess_host( + opt_level: OptLevel, + target: &Triple, + platform_main_roc: &Path, + preprocessed_host_path: &Path, + exposed_symbols: roc_linker::ExposedSymbols, +) { + let stub_dll_symbols = exposed_symbols.stub_dll_symbols(); + + build_and_preprocess_host_lowlevel( + opt_level, + target, + platform_main_roc, + preprocessed_host_path, + &stub_dll_symbols, + ) +} + +fn build_and_preprocess_host_lowlevel( + opt_level: OptLevel, + target: &Triple, + platform_main_roc: &Path, + preprocessed_host_path: &Path, + stub_dll_symbols: &[String], +) { + let stub_lib = + roc_linker::generate_stub_lib_from_loaded(target, platform_main_roc, stub_dll_symbols); + + debug_assert!(stub_lib.exists()); + + rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib)); + + roc_linker::preprocess_host( + target, + platform_main_roc, + preprocessed_host_path, + &stub_lib, + stub_dll_symbols, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn check_file<'a>( + arena: &'a Bump, + roc_file_path: PathBuf, + emit_timings: bool, + roc_cache_dir: RocCacheDir<'_>, + threading: Threading, +) -> Result<(Problems, Duration), LoadingProblem<'a>> { + let compilation_start = Instant::now(); + + // only used for generating errors. We don't do code generation, so hardcoding should be fine + // we need monomorphization for when exhaustiveness checking + let target_info = TargetInfo::default_x86_64(); + + // Step 1: compile the app and generate the .o file + + let load_config = LoadConfig { + target_info, + // TODO: expose this from CLI? + render: RenderTarget::ColorTerminal, + palette: DEFAULT_PALETTE, + threading, + exec_mode: ExecutionMode::Check, + }; + let mut loaded = + roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?; + + let buf = &mut String::with_capacity(1024); + + let mut it = loaded.timings.iter().peekable(); + while let Some((module_id, module_timing)) = it.next() { + let module_name = loaded.interns.module_name(*module_id); + + buf.push_str(" "); + + if module_name.is_empty() { + // the App module + buf.push_str("Application Module"); + } else { + buf.push_str(module_name); + } + + buf.push('\n'); + + report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file); + report_timing(buf, "Parse header", module_timing.parse_header); + report_timing(buf, "Parse body", module_timing.parse_body); + report_timing(buf, "Canonicalize", module_timing.canonicalize); + report_timing(buf, "Constrain", module_timing.constrain); + report_timing(buf, "Solve", module_timing.solve); + report_timing(buf, "Other", module_timing.other()); + buf.push('\n'); + report_timing(buf, "Total", module_timing.total()); + + if it.peek().is_some() { + buf.push('\n'); + } + } + + let compilation_end = compilation_start.elapsed(); + + if emit_timings { + println!( + "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}", + buf + ); + + println!("Finished checking in {} ms\n", compilation_end.as_millis(),); + } + + Ok((report_problems_typechecked(&mut loaded), compilation_end)) +} + +pub fn build_str_test<'a>( + arena: &'a Bump, + app_module_path: &Path, + app_module_source: &'a str, + assume_prebuild: bool, +) -> Result, BuildFileError<'a>> { + let triple = target_lexicon::Triple::host(); + + let code_gen_options = CodeGenOptions { + backend: CodeGenBackend::Llvm(LlvmBackendMode::Binary), + opt_level: OptLevel::Normal, + emit_debug_info: false, + }; + + let emit_timings = false; + let link_type = LinkType::Executable; + let linking_strategy = LinkingStrategy::Surgical; + let wasm_dev_stack_bytes = None; + + let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed; + let build_ordering = BuildOrdering::AlwaysBuild; + let threading = Threading::AtMost(2); + + let load_config = standard_load_config(&triple, build_ordering, threading); + + let compilation_start = std::time::Instant::now(); + + // Step 1: compile the app and generate the .o file + let loaded = roc_load::load_and_monomorphize_from_str( + arena, + PathBuf::from("valgrind_test.roc"), + app_module_source, + app_module_path.to_path_buf(), + roc_cache_dir, + load_config, + ) + .map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?; + + build_loaded_file( + arena, + &triple, + app_module_path.to_path_buf(), + code_gen_options, + emit_timings, + link_type, + linking_strategy, + assume_prebuild, + wasm_dev_stack_bytes, + loaded, + compilation_start, + ) +} diff --git a/crates/compiler/builtins/Cargo.toml b/crates/compiler/builtins/Cargo.toml index ca3a5b38f8..852a085a9f 100644 --- a/crates/compiler/builtins/Cargo.toml +++ b/crates/compiler/builtins/Cargo.toml @@ -1,23 +1,17 @@ [package] name = "roc_builtins" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Provides the Roc functions and modules that are implicitly imported into every module." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_collections = { path = "../collections" } -roc_region = { path = "../region" } roc_module = { path = "../module" } +roc_region = { path = "../region" } roc_target = { path = "../roc_target" } -roc_utils = { path = "../../utils" } + tempfile.workspace = true -[build-dependencies] -# dunce can be removed once ziglang/zig#5109 is fixed -dunce = "1.0.3" -roc_utils = { path = "../../utils" } - -[target.'cfg(target_os = "macos")'.build-dependencies] -tempfile.workspace = true diff --git a/crates/compiler/builtins/bitcode/Cargo.toml b/crates/compiler/builtins/bitcode/Cargo.toml new file mode 100644 index 0000000000..46ef041619 --- /dev/null +++ b/crates/compiler/builtins/bitcode/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "roc_bitcode" +description = "Compiles the zig bitcode to `.o` for builtins" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] +tempfile.workspace = true + +[build-dependencies] +roc_command_utils = { path = "../../../utils/command" } +# dunce can be removed once ziglang/zig#5109 is fixed +dunce = "1.0.3" + +[target.'cfg(target_os = "macos")'.build-dependencies] +tempfile.workspace = true diff --git a/crates/compiler/builtins/bitcode/bc/Cargo.toml b/crates/compiler/builtins/bitcode/bc/Cargo.toml new file mode 100644 index 0000000000..d6b8113443 --- /dev/null +++ b/crates/compiler/builtins/bitcode/bc/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "roc_bitcode_bc" +description = "Compiles the zig bitcode to `.bc` for llvm" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[build-dependencies] +roc_command_utils = { path = "../../../../utils/command" } +# dunce can be removed once ziglang/zig#5109 is fixed +dunce = "1.0.3" + +[target.'cfg(target_os = "macos")'.build-dependencies] +tempfile.workspace = true diff --git a/crates/compiler/builtins/bitcode/bc/build.rs b/crates/compiler/builtins/bitcode/bc/build.rs new file mode 100644 index 0000000000..bda982ccd6 --- /dev/null +++ b/crates/compiler/builtins/bitcode/bc/build.rs @@ -0,0 +1,154 @@ +use roc_command_utils::{pretty_command_string, zig}; +use std::fs; +use std::io; +use std::path::Path; +use std::str; +use std::{env, path::PathBuf, process::Command}; + +#[cfg(target_os = "macos")] +use tempfile::tempdir; + +/// To debug the zig code with debug prints, we need to disable the wasm code gen +const DEBUG: bool = false; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + // "." is relative to where "build.rs" is + // dunce can be removed once ziglang/zig#5109 is fixed + let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap().join(".."); + + // workaround for github.com/ziglang/zig/issues/9711 + #[cfg(target_os = "macos")] + let zig_cache_dir = tempdir().expect("Failed to create temp directory for zig cache"); + #[cfg(target_os = "macos")] + std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str()); + + // LLVM .bc FILES + + generate_bc_file(&bitcode_path, "ir", "builtins-host"); + + if !DEBUG { + generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32"); + } + + generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386"); + generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64"); + generate_bc_file( + &bitcode_path, + "ir-windows-x86_64", + "builtins-windows-x86_64", + ); + + get_zig_files(bitcode_path.as_path(), &|path| { + let path: &Path = path; + println!( + "cargo:rerun-if-changed={}", + path.to_str().expect("Failed to convert path to str") + ); + }) + .unwrap(); + + #[cfg(target_os = "macos")] + zig_cache_dir + .close() + .expect("Failed to delete temp dir zig_cache_dir."); +} + +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); + + // workaround for github.com/ziglang/zig/issues/9711 + #[cfg(target_os = "macos")] + let _ = fs::remove_dir_all("./zig-cache"); + + let mut zig_cmd = zig(); + + zig_cmd + .current_dir(bitcode_path) + .args(["build", zig_object, "-Drelease=true"]); + + run_command(zig_cmd, 0); +} + +pub fn get_lib_dir() -> PathBuf { + // Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`. + // So we just need to add "/bitcode" to that. + let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + // create dir if it does not exist + fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir."); + + dir +} + +fn run_command(mut command: Command, flaky_fail_counter: usize) { + let command_str = pretty_command_string(&command); + let command_str = command_str.to_string_lossy(); + + let output_result = command.output(); + + match output_result { + Ok(output) => match output.status.success() { + true => (), + false => { + let error_str = match str::from_utf8(&output.stderr) { + Ok(stderr) => stderr.to_string(), + Err(_) => format!("Failed to run \"{}\"", command_str), + }; + + // Flaky test errors that only occur sometimes on MacOS ci server. + if error_str.contains("FileNotFound") + || error_str.contains("unable to save cached ZIR code") + || error_str.contains("LLVM failed to emit asm") + { + if flaky_fail_counter == 10 { + panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str); + } else { + run_command(command, flaky_fail_counter + 1) + } + } else if error_str + .contains("lld-link: error: failed to write the output file: Permission denied") + { + panic!("{} failed with:\n\n {}\n\nWorkaround:\n\n Re-run the cargo command that triggered this build.\n\n", command_str, error_str); + } else { + panic!("{} failed with:\n\n {}\n", command_str, error_str); + } + } + }, + Err(reason) => panic!("{} failed: {}", command_str, reason), + } +} + +fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> { + if dir.is_dir() { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path_buf = entry.path(); + if path_buf.is_dir() { + if !path_buf.ends_with("zig-cache") { + get_zig_files(&path_buf, cb).unwrap(); + } + } else { + let path = path_buf.as_path(); + + match path.extension() { + Some(osstr) if osstr == "zig" => { + cb(path); + } + _ => {} + } + } + } + } + Ok(()) +} diff --git a/crates/compiler/builtins/bitcode/bc/src/lib.rs b/crates/compiler/builtins/bitcode/bc/src/lib.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/compiler/builtins/bitcode/bc/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/compiler/builtins/build.rs b/crates/compiler/builtins/bitcode/build.rs similarity index 80% rename from crates/compiler/builtins/build.rs rename to crates/compiler/builtins/bitcode/build.rs index 0e10b07627..40ed60021c 100644 --- a/crates/compiler/builtins/build.rs +++ b/crates/compiler/builtins/bitcode/build.rs @@ -1,11 +1,9 @@ -use roc_utils::zig; -use std::env; +use roc_command_utils::{pretty_command_string, zig}; use std::fs; use std::io; use std::path::Path; -use std::path::PathBuf; -use std::process::Command; use std::str; +use std::{env, path::PathBuf, process::Command}; #[cfg(target_os = "macos")] use tempfile::tempdir; @@ -18,8 +16,7 @@ fn main() { // "." is relative to where "build.rs" is // dunce can be removed once ziglang/zig#5109 is fixed - let build_script_dir_path = dunce::canonicalize(Path::new(".")).unwrap(); - let bitcode_path = build_script_dir_path.join("bitcode"); + let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap(); // workaround for github.com/ziglang/zig/issues/9711 #[cfg(target_os = "macos")] @@ -27,22 +24,6 @@ fn main() { #[cfg(target_os = "macos")] std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str()); - // LLVM .bc FILES - - generate_bc_file(&bitcode_path, "ir", "builtins-host"); - - if !DEBUG { - generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32"); - } - - generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386"); - generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64"); - generate_bc_file( - &bitcode_path, - "ir-windows-x86_64", - "builtins-windows-x86_64", - ); - // OBJECT FILES #[cfg(windows)] const BUILTINS_HOST_FILE: &str = "builtins-host.obj"; @@ -107,38 +88,13 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name: } } -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); - - // workaround for github.com/ziglang/zig/issues/9711 - #[cfg(target_os = "macos")] - let _ = fs::remove_dir_all("./bitcode/zig-cache"); - - let mut zig_cmd = zig(); - - zig_cmd - .current_dir(bitcode_path) - .args(["build", zig_object, "-Drelease=true"]); - - run_command(zig_cmd, 0); -} - pub fn get_lib_dir() -> PathBuf { // Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`. // So we just need to add "/bitcode" to that. - let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("bitcode"); + let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); // create dir if it does not exist - fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/bitcode dir."); + fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir."); dir } @@ -192,7 +148,7 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> { } fn run_command(mut command: Command, flaky_fail_counter: usize) { - let command_str = roc_utils::pretty_command_string(&command); + let command_str = pretty_command_string(&command); let command_str = command_str.to_string_lossy(); let output_result = command.output(); diff --git a/crates/compiler/builtins/bitcode/src/dec.zig b/crates/compiler/builtins/bitcode/src/dec.zig index 3910d98a2d..a3f0171133 100644 --- a/crates/compiler/builtins/bitcode/src/dec.zig +++ b/crates/compiler/builtins/bitcode/src/dec.zig @@ -921,8 +921,8 @@ test "toStr: 123.1111111" { test "toStr: 123.1111111111111 (big str)" { var dec: RocDec = .{ .num = 123111111111111000000 }; var res_roc_str = dec.toStr(); - errdefer res_roc_str.deinit(); - defer res_roc_str.deinit(); + errdefer res_roc_str.decref(); + defer res_roc_str.decref(); const res_slice: []const u8 = "123.111111111111"[0..]; try expectEqualSlices(u8, res_slice, res_roc_str.asSlice()); @@ -931,8 +931,8 @@ test "toStr: 123.1111111111111 (big str)" { test "toStr: 123.111111111111444444 (max number of decimal places)" { var dec: RocDec = .{ .num = 123111111111111444444 }; var res_roc_str = dec.toStr(); - errdefer res_roc_str.deinit(); - defer res_roc_str.deinit(); + errdefer res_roc_str.decref(); + defer res_roc_str.decref(); const res_slice: []const u8 = "123.111111111111444444"[0..]; try expectEqualSlices(u8, res_slice, res_roc_str.asSlice()); @@ -941,8 +941,8 @@ test "toStr: 123.111111111111444444 (max number of decimal places)" { test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" { var dec: RocDec = .{ .num = 12345678912345678912111111111111111111 }; var res_roc_str = dec.toStr(); - errdefer res_roc_str.deinit(); - defer res_roc_str.deinit(); + errdefer res_roc_str.decref(); + defer res_roc_str.decref(); const res_slice: []const u8 = "12345678912345678912.111111111111111111"[0..]; try expectEqualSlices(u8, res_slice, res_roc_str.asSlice()); @@ -951,8 +951,8 @@ test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" { test "toStr: std.math.maxInt" { var dec: RocDec = .{ .num = std.math.maxInt(i128) }; var res_roc_str = dec.toStr(); - errdefer res_roc_str.deinit(); - defer res_roc_str.deinit(); + errdefer res_roc_str.decref(); + defer res_roc_str.decref(); const res_slice: []const u8 = "170141183460469231731.687303715884105727"[0..]; try expectEqualSlices(u8, res_slice, res_roc_str.asSlice()); @@ -961,8 +961,8 @@ test "toStr: std.math.maxInt" { test "toStr: std.math.minInt" { var dec: RocDec = .{ .num = std.math.minInt(i128) }; var res_roc_str = dec.toStr(); - errdefer res_roc_str.deinit(); - defer res_roc_str.deinit(); + errdefer res_roc_str.decref(); + defer res_roc_str.decref(); const res_slice: []const u8 = "-170141183460469231731.687303715884105728"[0..]; try expectEqualSlices(u8, res_slice, res_roc_str.asSlice()); @@ -1047,8 +1047,8 @@ test "div: 10 / 3" { var denom: RocDec = RocDec.fromU64(3); var roc_str = RocStr.init("3.333333333333333333", 20); - errdefer roc_str.deinit(); - defer roc_str.deinit(); + errdefer roc_str.decref(); + defer roc_str.decref(); var res: RocDec = RocDec.fromStr(roc_str).?; diff --git a/crates/compiler/builtins/bitcode/src/lib.rs b/crates/compiler/builtins/bitcode/src/lib.rs new file mode 100644 index 0000000000..58efea062e --- /dev/null +++ b/crates/compiler/builtins/bitcode/src/lib.rs @@ -0,0 +1,65 @@ +use tempfile::NamedTempFile; + +const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-wasm32.o")); +// TODO: in the future, we should use Zig's cross-compilation to generate and store these +// for all targets, so that we can do cross-compilation! +#[cfg(unix)] +const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-host.o")); +#[cfg(windows)] +const HOST_WINDOWS: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/builtins-windows-x86_64.obj")); + +pub fn host_wasm_tempfile() -> std::io::Result { + let tempfile = tempfile::Builder::new() + .prefix("host_bitcode") + .suffix(".wasm") + .rand_bytes(8) + .tempfile()?; + + std::fs::write(tempfile.path(), HOST_WASM)?; + + Ok(tempfile) +} + +#[cfg(unix)] +fn host_unix_tempfile() -> std::io::Result { + let tempfile = tempfile::Builder::new() + .prefix("host_bitcode") + .suffix(".o") + .rand_bytes(8) + .tempfile()?; + + std::fs::write(tempfile.path(), HOST_UNIX)?; + + Ok(tempfile) +} + +#[cfg(windows)] +fn host_windows_tempfile() -> std::io::Result { + let tempfile = tempfile::Builder::new() + .prefix("host_bitcode") + .suffix(".obj") + .rand_bytes(8) + .tempfile()?; + + std::fs::write(tempfile.path(), HOST_WINDOWS)?; + + Ok(tempfile) +} + +pub fn host_tempfile() -> std::io::Result { + #[cfg(unix)] + { + host_unix_tempfile() + } + + #[cfg(windows)] + { + host_windows_tempfile() + } + + #[cfg(not(any(windows, unix)))] + { + unreachable!() + } +} diff --git a/crates/compiler/builtins/bitcode/src/list.zig b/crates/compiler/builtins/bitcode/src/list.zig index 66d5fe9147..71968aa082 100644 --- a/crates/compiler/builtins/bitcode/src/list.zig +++ b/crates/compiler/builtins/bitcode/src/list.zig @@ -15,21 +15,45 @@ const IncN = fn (?[*]u8, usize) callconv(.C) void; const Dec = fn (?[*]u8) callconv(.C) void; const HasTagId = fn (u16, ?[*]u8) callconv(.C) extern struct { matched: bool, data: ?[*]u8 }; +const SEAMLESS_SLICE_BIT: usize = + @bitCast(usize, @as(isize, std.math.minInt(isize))); + pub const RocList = extern struct { bytes: ?[*]u8, length: usize, - capacity: usize, + // This technically points to directly after the refcount. + // This is an optimization that enables use one code path for regular lists and slices for geting the refcount ptr. + capacity_or_ref_ptr: usize, pub inline fn len(self: RocList) usize { return self.length; } + pub fn getCapacity(self: RocList) usize { + const list_capacity = self.capacity_or_ref_ptr; + const slice_capacity = self.length; + const slice_mask = self.seamlessSliceMask(); + const capacity = (list_capacity & ~slice_mask) | (slice_capacity & slice_mask); + return capacity; + } + + pub fn isSeamlessSlice(self: RocList) bool { + return @bitCast(isize, self.capacity_or_ref_ptr) < 0; + } + + // This returns all ones if the list is a seamless slice. + // Otherwise, it returns all zeros. + // This is done without branching for optimization purposes. + pub fn seamlessSliceMask(self: RocList) usize { + return @bitCast(usize, @bitCast(isize, self.capacity_or_ref_ptr) >> (@bitSizeOf(isize) - 1)); + } + pub fn isEmpty(self: RocList) bool { return self.len() == 0; } pub fn empty() RocList { - return RocList{ .bytes = null, .length = 0, .capacity = 0 }; + return RocList{ .bytes = null, .length = 0, .capacity_or_ref_ptr = 0 }; } pub fn eql(self: RocList, other: RocList) bool { @@ -75,8 +99,21 @@ pub const RocList = extern struct { return list; } - pub fn deinit(self: RocList, comptime T: type) void { - utils.decref(self.bytes, self.capacity, @alignOf(T)); + // returns a pointer to just after the refcount. + // It is just after the refcount as an optimization for other shared code paths. + // For regular list, it just returns their bytes pointer. + // For seamless slices, it returns the pointer stored in capacity_or_ref_ptr. + pub fn getRefcountPtr(self: RocList) ?[*]u8 { + const list_ref_ptr = @ptrToInt(self.bytes); + const slice_ref_ptr = self.capacity_or_ref_ptr << 1; + const slice_mask = self.seamlessSliceMask(); + const ref_ptr = (list_ref_ptr & ~slice_mask) | (slice_ref_ptr & slice_mask); + return @intToPtr(?[*]u8, ref_ptr); + } + + pub fn decref(self: RocList, alignment: u32) void { + // We use the raw capacity to ensure we always decrement the refcount of seamless slices. + utils.decref(self.getRefcountPtr(), self.capacity_or_ref_ptr, alignment); } pub fn elements(self: RocList, comptime T: type) ?[*]T { @@ -88,7 +125,7 @@ pub const RocList = extern struct { } fn refcountMachine(self: RocList) usize { - if (self.capacity == 0) { + if (self.getCapacity() == 0 and !self.isSeamlessSlice()) { // the zero-capacity is Clone, copying it will not leak memory return utils.REFCOUNT_ONE; } @@ -110,12 +147,15 @@ pub const RocList = extern struct { } pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList { - if (self.isEmpty()) { + if (self.isUnique()) { return self; } - if (self.isUnique()) { - return self; + if (self.isEmpty()) { + // Empty is not necessarily unique on it's own. + // The list could have capacity and be shared. + self.decref(alignment); + return RocList.empty(); } // unfortunately, we have to clone @@ -127,9 +167,8 @@ pub const RocList = extern struct { const number_of_bytes = self.len() * element_width; @memcpy(new_bytes, old_bytes, number_of_bytes); - // NOTE we fuse an increment of all keys/values with a decrement of the input dict - const data_bytes = self.len() * element_width; - utils.decref(self.bytes, data_bytes, alignment); + // NOTE we fuse an increment of all keys/values with a decrement of the input list. + self.decref(alignment); return new_list; } @@ -148,7 +187,24 @@ pub const RocList = extern struct { return RocList{ .bytes = utils.allocateWithRefcount(data_bytes, alignment), .length = length, - .capacity = capacity, + .capacity_or_ref_ptr = capacity, + }; + } + + pub fn allocateExact( + alignment: u32, + length: usize, + element_width: usize, + ) RocList { + if (length == 0) { + return empty(); + } + + const data_bytes = length * element_width; + return RocList{ + .bytes = utils.allocateWithRefcount(data_bytes, alignment), + .length = length, + .capacity_or_ref_ptr = length, }; } @@ -159,13 +215,14 @@ pub const RocList = extern struct { element_width: usize, ) RocList { if (self.bytes) |source_ptr| { - if (self.isUnique()) { - if (self.capacity >= new_length) { - return RocList{ .bytes = self.bytes, .length = new_length, .capacity = self.capacity }; + if (self.isUnique() and !self.isSeamlessSlice()) { + const capacity = self.capacity_or_ref_ptr; + if (capacity >= new_length) { + return RocList{ .bytes = self.bytes, .length = new_length, .capacity_or_ref_ptr = capacity }; } else { - const new_capacity = utils.calculateCapacity(self.capacity, new_length, element_width); - const new_source = utils.unsafeReallocate(source_ptr, alignment, self.len(), new_capacity, element_width); - return RocList{ .bytes = new_source, .length = new_length, .capacity = new_capacity }; + const new_capacity = utils.calculateCapacity(capacity, new_length, element_width); + const new_source = utils.unsafeReallocate(source_ptr, alignment, capacity, new_capacity, element_width); + return RocList{ .bytes = new_source, .length = new_length, .capacity_or_ref_ptr = new_capacity }; } } return self.reallocateFresh(alignment, new_length, element_width); @@ -193,7 +250,7 @@ pub const RocList = extern struct { @memset(dest_ptr + old_length * element_width, 0, delta_length * element_width); } - utils.decref(self.bytes, old_length * element_width, alignment); + self.decref(alignment); return result; } @@ -428,7 +485,7 @@ pub fn listReserve( update_mode: UpdateMode, ) callconv(.C) RocList { const old_length = list.len(); - if ((update_mode == .InPlace or list.isUnique()) and list.capacity >= list.len() + spare) { + if ((update_mode == .InPlace or list.isUnique()) and list.getCapacity() >= list.len() + spare) { return list; } else { var output = list.reallocate(alignment, old_length + spare, element_width); @@ -437,6 +494,31 @@ pub fn listReserve( } } +pub fn listReleaseExcessCapacity( + list: RocList, + alignment: u32, + element_width: usize, + update_mode: UpdateMode, +) callconv(.C) RocList { + const old_length = list.len(); + // We use the direct list.capacity_or_ref_ptr to make sure both that there is no extra capacity and that it isn't a seamless slice. + if ((update_mode == .InPlace or list.isUnique()) and list.capacity_or_ref_ptr == old_length) { + return list; + } else if (old_length == 0) { + list.decref(alignment); + return RocList.empty(); + } else { + var output = RocList.allocateExact(alignment, old_length, element_width); + if (list.bytes) |source_ptr| { + const dest_ptr = output.bytes orelse unreachable; + + @memcpy(dest_ptr, source_ptr, old_length * element_width); + } + list.decref(alignment); + return output; + } +} + pub fn listAppendUnsafe( list: RocList, element: Opaque, @@ -462,10 +544,12 @@ fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usi pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList { const old_length = list.len(); - var output = list.reallocate(alignment, old_length + 1, element_width); + // TODO: properly wire in update mode. + var with_capacity = listReserve(list, alignment, 1, element_width, .Immutable); + with_capacity.length += 1; // can't use one memcpy here because source and target overlap - if (output.bytes) |target| { + if (with_capacity.bytes) |target| { var i: usize = old_length; while (i > 0) { @@ -481,7 +565,7 @@ pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width } } - return output; + return with_capacity; } pub fn listSwap( @@ -522,19 +606,20 @@ pub fn listSublist( ) callconv(.C) RocList { const size = list.len(); if (len == 0 or start >= size) { - if (list.isUnique()) { - // Decrement the reference counts of all elements. - if (list.bytes) |source_ptr| { - var i: usize = 0; - while (i < size) : (i += 1) { - const element = source_ptr + i * element_width; - dec(element); - } - var output = list; - output.length = 0; - return output; + // Decrement the reference counts of all elements. + if (list.bytes) |source_ptr| { + var i: usize = 0; + while (i < size) : (i += 1) { + const element = source_ptr + i * element_width; + dec(element); } } + if (list.isUnique()) { + var output = list; + output.length = 0; + return output; + } + list.decref(alignment); return RocList.empty(); } @@ -557,26 +642,20 @@ pub fn listSublist( dec(element); } - if (list.isUnique()) { + if (start == 0 and list.isUnique()) { var output = list; output.length = keep_len; - if (start == 0) { - return output; - } else { - // We want memmove due to aliasing. Zig does not expose it directly. - // Instead use copy which can write to aliases as long as the dest is before the source. - mem.copy(u8, source_ptr[0 .. keep_len * element_width], source_ptr[start * element_width .. (start + keep_len) * element_width]); - return output; - } - } else { - const output = RocList.allocate(alignment, keep_len, element_width); - const target_ptr = output.bytes orelse unreachable; - - @memcpy(target_ptr, source_ptr + start * element_width, keep_len * element_width); - - utils.decref(list.bytes, size * element_width, alignment); - return output; + } else { + const list_ref_ptr = (@ptrToInt(source_ptr) >> 1) | SEAMLESS_SLICE_BIT; + const slice_ref_ptr = list.capacity_or_ref_ptr; + const slice_mask = list.seamlessSliceMask(); + const ref_ptr = (list_ref_ptr & ~slice_mask) | (slice_ref_ptr & slice_mask); + return RocList{ + .bytes = source_ptr + start * element_width, + .length = keep_len, + .capacity_or_ref_ptr = ref_ptr, + }; } } @@ -590,9 +669,17 @@ pub fn listDropAt( drop_index: usize, dec: Dec, ) callconv(.C) RocList { - if (list.bytes) |source_ptr| { - const size = list.len(); + const size = list.len(); + // If droping the first or last element, return a seamless slice. + // For simplicity, do this by calling listSublist. + // In the future, we can test if it is faster to manually inline the important parts here. + if (drop_index == 0) { + return listSublist(list, alignment, element_width, 1, size -| 1, dec); + } else if (drop_index == size -| 1) { + return listSublist(list, alignment, element_width, 0, size -| 1, dec); + } + if (list.bytes) |source_ptr| { if (drop_index >= size) { return list; } @@ -607,7 +694,7 @@ pub fn listDropAt( // because we rely on the pointer field being null if the list is empty // which also requires duplicating the utils.decref call to spend the RC token if (size < 2) { - utils.decref(list.bytes, size * element_width, alignment); + list.decref(alignment); return RocList.empty(); } @@ -637,7 +724,7 @@ pub fn listDropAt( const tail_size = (size - drop_index - 1) * element_width; @memcpy(tail_target, tail_source, tail_size); - utils.decref(list.bytes, size * element_width, alignment); + list.decref(alignment); return output; } else { @@ -747,11 +834,13 @@ fn swapElements(source_ptr: [*]u8, element_width: usize, index_1: usize, index_2 pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_width: usize) callconv(.C) RocList { // NOTE we always use list_a! because it is owned, we must consume it, and it may have unused capacity if (list_b.isEmpty()) { - if (list_a.capacity == 0) { + if (list_a.getCapacity() == 0) { + // a could be a seamless slice, so we still need to decref. + list_a.decref(alignment); return list_b; } else { // we must consume this list. Even though it has no elements, it could still have capacity - list_b.deinit(usize); + list_b.decref(alignment); return list_a; } @@ -766,7 +855,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt @memcpy(source_a + list_a.len() * element_width, source_b, list_b.len() * element_width); // decrement list b. - utils.decref(source_b, list_b.len(), alignment); + list_b.decref(alignment); return resized_list_a; } else if (list_b.isUnique()) { @@ -787,7 +876,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt @memcpy(source_b, source_a, byte_count_a); // decrement list a. - utils.decref(source_a, list_a.len(), alignment); + list_a.decref(alignment); return resized_list_b; } @@ -804,8 +893,8 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt @memcpy(target + list_a.len() * element_width, source_b, list_b.len() * element_width); // decrement list a and b. - utils.decref(source_a, list_a.len(), alignment); - utils.decref(source_b, list_b.len(), alignment); + list_a.decref(alignment); + list_b.decref(alignment); return output; } @@ -868,20 +957,32 @@ pub fn listIsUnique( return list.isEmpty() or list.isUnique(); } +pub fn listCapacity( + list: RocList, +) callconv(.C) usize { + return list.getCapacity(); +} + +pub fn listRefcountPtr( + list: RocList, +) callconv(.C) ?[*]u8 { + return list.getRefcountPtr(); +} + test "listConcat: non-unique with unique overlapping" { var nonUnique = RocList.fromSlice(u8, ([_]u8{1})[0..]); var bytes: [*]u8 = @ptrCast([*]u8, nonUnique.bytes); const ptr_width = @sizeOf(usize); const refcount_ptr = @ptrCast([*]isize, @alignCast(ptr_width, bytes) - ptr_width); utils.increfC(&refcount_ptr[0], 1); - defer nonUnique.deinit(u8); // listConcat will dec the other refcount + defer nonUnique.decref(@sizeOf(u8)); // listConcat will dec the other refcount var unique = RocList.fromSlice(u8, ([_]u8{ 2, 3, 4 })[0..]); - defer unique.deinit(u8); + defer unique.decref(@sizeOf(u8)); var concatted = listConcat(nonUnique, unique, 1, 1); var wanted = RocList.fromSlice(u8, ([_]u8{ 1, 2, 3, 4 })[0..]); - defer wanted.deinit(u8); + defer wanted.decref(@sizeOf(u8)); try expect(concatted.eql(wanted)); } diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 56a403a8c8..6f0b59468d 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -54,6 +54,9 @@ comptime { exportListFn(list.listReplaceInPlace, "replace_in_place"); exportListFn(list.listSwap, "swap"); exportListFn(list.listIsUnique, "is_unique"); + exportListFn(list.listCapacity, "capacity"); + exportListFn(list.listRefcountPtr, "refcount_ptr"); + exportListFn(list.listReleaseExcessCapacity, "release_excess_capacity"); } // Num Module @@ -67,6 +70,8 @@ const NUMBERS = INTEGERS ++ FLOATS; comptime { exportNumFn(num.bytesToU16C, "bytes_to_u16"); exportNumFn(num.bytesToU32C, "bytes_to_u32"); + exportNumFn(num.bytesToU64C, "bytes_to_u64"); + exportNumFn(num.bytesToU128C, "bytes_to_u128"); inline for (INTEGERS) |T, i| { num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int."); @@ -86,6 +91,10 @@ comptime { num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow."); num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic."); num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated."); + + num.exportCountLeadingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_leading_zero_bits."); + num.exportCountTrailingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_trailing_zero_bits."); + num.exportCountOneBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_one_bits."); } inline for (INTEGERS) |FROM| { @@ -138,7 +147,6 @@ comptime { exportStrFn(str.getScalarUnsafe, "get_scalar_unsafe"); exportStrFn(str.appendScalar, "append_scalar"); exportStrFn(str.strToUtf8C, "to_utf8"); - exportStrFn(str.fromUtf8C, "from_utf8"); exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); exportStrFn(str.repeat, "repeat"); exportStrFn(str.strTrim, "trim"); @@ -147,6 +155,8 @@ comptime { exportStrFn(str.strCloneTo, "clone_to"); exportStrFn(str.withCapacity, "with_capacity"); exportStrFn(str.strGraphemes, "graphemes"); + exportStrFn(str.strRefcountPtr, "refcount_ptr"); + exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity"); inline for (INTEGERS) |T| { str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int."); diff --git a/crates/compiler/builtins/bitcode/src/num.zig b/crates/compiler/builtins/bitcode/src/num.zig index cb8c2498f2..dbbf87c1cf 100644 --- a/crates/compiler/builtins/bitcode/src/num.zig +++ b/crates/compiler/builtins/bitcode/src/num.zig @@ -236,6 +236,24 @@ fn bytesToU32(arg: RocList, position: usize) u32 { return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] }); } +pub fn bytesToU64C(arg: RocList, position: usize) callconv(.C) u64 { + return @call(.{ .modifier = always_inline }, bytesToU64, .{ arg, position }); +} + +fn bytesToU64(arg: RocList, position: usize) u64 { + const bytes = @ptrCast([*]const u8, arg.bytes); + return @bitCast(u64, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7] }); +} + +pub fn bytesToU128C(arg: RocList, position: usize) callconv(.C) u128 { + return @call(.{ .modifier = always_inline }, bytesToU128, .{ arg, position }); +} + +fn bytesToU128(arg: RocList, position: usize) u128 { + const bytes = @ptrCast([*]const u8, arg.bytes); + return @bitCast(u128, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7], bytes[position + 8], bytes[position + 9], bytes[position + 10], bytes[position + 11], bytes[position + 12], bytes[position + 13], bytes[position + 14], bytes[position + 15] }); +} + fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) { switch (@typeInfo(T)) { .Int => { @@ -460,3 +478,30 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con }.func; @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } + +pub fn exportCountLeadingZeroBits(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(self: T) callconv(.C) usize { + return @as(usize, @clz(T, self)); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportCountTrailingZeroBits(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(self: T) callconv(.C) usize { + return @as(usize, @ctz(T, self)); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(self: T) callconv(.C) usize { + return @as(usize, @popCount(T, self)); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} diff --git a/crates/compiler/builtins/bitcode/src/str.zig b/crates/compiler/builtins/bitcode/src/str.zig index 9c72af30c8..ea57a934e1 100644 --- a/crates/compiler/builtins/bitcode/src/str.zig +++ b/crates/compiler/builtins/bitcode/src/str.zig @@ -18,6 +18,7 @@ const InPlace = enum(u8) { const MASK_ISIZE: isize = std.math.minInt(isize); const MASK: usize = @bitCast(usize, MASK_ISIZE); +const SEAMLESS_SLICE_BIT: usize = MASK; const SMALL_STR_MAX_LENGTH = SMALL_STRING_SIZE - 1; const SMALL_STRING_SIZE = @sizeOf(RocStr); @@ -57,6 +58,37 @@ pub const RocStr = extern struct { return result; } + // This requires that the list is non-null. + // It also requires that start and count define a slice that does not go outside the bounds of the list. + pub fn fromSubListUnsafe(list: RocList, start: usize, count: usize, update_mode: UpdateMode) RocStr { + const start_byte = @ptrCast([*]u8, list.bytes) + start; + if (list.isSeamlessSlice()) { + return RocStr{ + .str_bytes = start_byte, + .str_len = count | SEAMLESS_SLICE_BIT, + .str_capacity = list.capacity_or_ref_ptr & (~SEAMLESS_SLICE_BIT), + }; + } else if (start == 0 and (update_mode == .InPlace or list.isUnique())) { + // Rare case, we can take over the original list. + return RocStr{ + .str_bytes = start_byte, + .str_len = count, + .str_capacity = list.capacity_or_ref_ptr, // This is guaranteed to be a proper capacity. + }; + } else { + // Create seamless slice pointing to the list. + return RocStr{ + .str_bytes = start_byte, + .str_len = count | SEAMLESS_SLICE_BIT, + .str_capacity = @ptrToInt(list.bytes) >> 1, + }; + } + } + + pub fn isSeamlessSlice(self: RocStr) bool { + return !self.isSmallStr() and @bitCast(isize, self.str_len) < 0; + } + pub fn fromSlice(slice: []const u8) RocStr { return RocStr.init(slice.ptr, slice.len); } @@ -89,13 +121,55 @@ pub const RocStr = extern struct { } } - pub fn deinit(self: RocStr) void { - self.decref(); + // allocate space for a (big or small) RocStr, but put nothing in it yet. + // Will have the exact same capacity as length if it is not a small string. + pub fn allocateExact(length: usize) RocStr { + const result_is_big = length >= SMALL_STRING_SIZE; + + if (result_is_big) { + return RocStr.allocateBig(length, length); + } else { + var string = RocStr.empty(); + + string.asU8ptrMut()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000; + + return string; + } } - fn decref(self: RocStr) void { + // This returns all ones if the list is a seamless slice. + // Otherwise, it returns all zeros. + // This is done without branching for optimization purposes. + pub fn seamlessSliceMask(self: RocStr) usize { + return @bitCast(usize, @bitCast(isize, self.str_len) >> (@bitSizeOf(isize) - 1)); + } + + // returns a pointer to just after the refcount. + // It is just after the refcount as an optimization for other shared code paths. + // For regular list, it just returns their bytes pointer. + // For seamless slices, it returns the pointer stored in capacity_or_ref_ptr. + // This does not return a valid value if the input is a small string. + pub fn getRefcountPtr(self: RocStr) ?[*]u8 { + const str_ref_ptr = @ptrToInt(self.str_bytes); + const slice_ref_ptr = self.str_capacity << 1; + const slice_mask = self.seamlessSliceMask(); + const ref_ptr = (str_ref_ptr & ~slice_mask) | (slice_ref_ptr & slice_mask); + return @intToPtr(?[*]u8, ref_ptr); + } + + pub fn incref(self: RocStr, n: usize) void { if (!self.isSmallStr()) { - utils.decref(self.str_bytes, self.str_capacity, RocStr.alignment); + const ref_ptr = self.getRefcountPtr(); + if (ref_ptr != null) { + const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@alignOf(isize), ref_ptr)); + utils.increfC(@ptrCast(*isize, isizes - 1), @intCast(isize, n)); + } + } + } + + pub fn decref(self: RocStr) void { + if (!self.isSmallStr()) { + utils.decref(self.getRefcountPtr(), self.str_capacity, RocStr.alignment); } } @@ -116,20 +190,11 @@ pub const RocStr = extern struct { // Now we have to look at the string contents const self_bytes = self.asU8ptr(); const other_bytes = other.asU8ptr(); - - // It's faster to compare pointer-sized words rather than bytes, as far as possible - // The bytes are always pointer-size aligned due to the refcount - const self_words = @ptrCast([*]const usize, @alignCast(@alignOf(usize), self_bytes)); - const other_words = @ptrCast([*]const usize, @alignCast(@alignOf(usize), other_bytes)); - var w: usize = 0; - while (w < self_len / @sizeOf(usize)) : (w += 1) { - if (self_words[w] != other_words[w]) { - return false; - } - } - - // Compare the leftover bytes - var b = w * @sizeOf(usize); + // TODO: we can make an optimization like memcmp does in glibc. + // We can check the min shared alignment 1, 2, 4, or 8. + // Then do a copy at that alignment before falling back on one byte at a time. + // Currently we have to be unaligned because slices can be at any alignment. + var b: usize = 0; while (b < self_len) : (b += 1) { if (self_bytes[b] != other_bytes[b]) { return false; @@ -162,7 +227,7 @@ pub const RocStr = extern struct { const element_width = 1; const old_capacity = self.getCapacity(); - if (self.isSmallStr() or !self.isUnique()) { + if (self.isSmallStr() or self.isSeamlessSlice() or !self.isUnique()) { return self.reallocateFresh(new_length); } @@ -228,7 +293,7 @@ pub const RocStr = extern struct { if (self.isSmallStr()) { return self.asArray()[@sizeOf(RocStr) - 1] ^ 0b1000_0000; } else { - return self.str_len; + return self.str_len & (~SEAMLESS_SLICE_BIT); } } @@ -236,13 +301,15 @@ pub const RocStr = extern struct { if (self.isSmallStr()) { self.asU8ptrMut()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000; } else { - self.str_len = length; + self.str_len = length | (SEAMLESS_SLICE_BIT & self.str_len); } } pub fn getCapacity(self: RocStr) usize { if (self.isSmallStr()) { return SMALL_STR_MAX_LENGTH; + } else if (self.isSeamlessSlice()) { + return self.str_len & (~SEAMLESS_SLICE_BIT); } else { return self.str_capacity; } @@ -289,6 +356,9 @@ pub const RocStr = extern struct { // then the next byte is off the end of the struct; // in that case, we are also not null-terminated! return length != 0 and length != longest_small_str; + } else if (self.isSeamlessSlice()) { + // Seamless slices can not use the character past the end even if it is null. + return false; } else { // This is a big string, and it's not empty, so we can safely // dereference the pointer. @@ -324,7 +394,7 @@ pub const RocStr = extern struct { } fn refcountMachine(self: RocStr) usize { - if (self.getCapacity() == 0 or self.isSmallStr()) { + if ((self.getCapacity() == 0 and !self.isSeamlessSlice()) or self.isSmallStr()) { return utils.REFCOUNT_ONE; } @@ -388,8 +458,8 @@ pub const RocStr = extern struct { try expect(roc_str1.eq(roc_str2)); - roc_str1.deinit(); - roc_str2.deinit(); + roc_str1.decref(); + roc_str2.decref(); } test "RocStr.eq: small, not equal, different length" { @@ -404,8 +474,8 @@ pub const RocStr = extern struct { var roc_str2 = RocStr.init(str2_ptr, str2_len); defer { - roc_str1.deinit(); - roc_str2.deinit(); + roc_str1.decref(); + roc_str2.decref(); } try expect(!roc_str1.eq(roc_str2)); @@ -423,8 +493,8 @@ pub const RocStr = extern struct { var roc_str2 = RocStr.init(str2_ptr, str2_len); defer { - roc_str1.deinit(); - roc_str2.deinit(); + roc_str1.decref(); + roc_str2.decref(); } try expect(!roc_str1.eq(roc_str2)); @@ -436,8 +506,8 @@ pub const RocStr = extern struct { const roc_str2 = RocStr.init(content, content.len); defer { - roc_str1.deinit(); - roc_str2.deinit(); + roc_str1.decref(); + roc_str2.decref(); } try expect(roc_str1.eq(roc_str2)); @@ -450,8 +520,8 @@ pub const RocStr = extern struct { const roc_str2 = RocStr.init(content2, content2.len); defer { - roc_str1.deinit(); - roc_str2.deinit(); + roc_str1.decref(); + roc_str2.decref(); } try expect(!roc_str1.eq(roc_str2)); @@ -464,8 +534,8 @@ pub const RocStr = extern struct { const roc_str2 = RocStr.init(content2, content2.len); defer { - roc_str1.deinit(); - roc_str2.deinit(); + roc_str1.decref(); + roc_str2.decref(); } try expect(!roc_str1.eq(roc_str2)); @@ -484,8 +554,8 @@ pub const RocStr = extern struct { roc_str2.str_bytes.?[31] = '-'; defer { - roc_str1.deinit(); - roc_str2.deinit(); + roc_str1.decref(); + roc_str2.decref(); } try expect(roc_str1.eq(roc_str2)); @@ -619,95 +689,95 @@ inline fn writeNextScalar(non_empty_string: RocStr, src_index: usize, dest: [*]u test "strToScalars: empty string" { const str = RocStr.fromSlice(""); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected = RocList.empty(); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } test "strToScalars: One ASCII char" { const str = RocStr.fromSlice("R"); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected_array = [_]u32{82}; const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]); - defer RocList.deinit(expected, u32); + defer expected.decref(@sizeOf(u32)); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } test "strToScalars: Multiple ASCII chars" { const str = RocStr.fromSlice("Roc!"); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected_array = [_]u32{ 82, 111, 99, 33 }; const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]); - defer RocList.deinit(expected, u32); + defer expected.decref(@sizeOf(u32)); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } test "strToScalars: One 2-byte UTF-8 character" { const str = RocStr.fromSlice("é"); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected_array = [_]u32{233}; const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]); - defer RocList.deinit(expected, u32); + defer expected.decref(@sizeOf(u32)); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } test "strToScalars: Multiple 2-byte UTF-8 characters" { const str = RocStr.fromSlice("Cäfés"); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected_array = [_]u32{ 67, 228, 102, 233, 115 }; const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]); - defer RocList.deinit(expected, u32); + defer expected.decref(@sizeOf(u32)); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } test "strToScalars: One 3-byte UTF-8 character" { const str = RocStr.fromSlice("鹏"); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected_array = [_]u32{40527}; const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]); - defer RocList.deinit(expected, u32); + defer expected.decref(@sizeOf(u32)); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } test "strToScalars: Multiple 3-byte UTF-8 characters" { const str = RocStr.fromSlice("鹏很有趣"); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected_array = [_]u32{ 40527, 24456, 26377, 36259 }; const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]); - defer RocList.deinit(expected, u32); + defer expected.decref(@sizeOf(u32)); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } @@ -715,14 +785,14 @@ test "strToScalars: Multiple 3-byte UTF-8 characters" { test "strToScalars: One 4-byte UTF-8 character" { // from https://design215.com/toolbox/utf8-4byte-characters.php const str = RocStr.fromSlice("𒀀"); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected_array = [_]u32{73728}; const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]); - defer RocList.deinit(expected, u32); + defer expected.decref(@sizeOf(u32)); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } @@ -730,14 +800,14 @@ test "strToScalars: One 4-byte UTF-8 character" { test "strToScalars: Multiple 4-byte UTF-8 characters" { // from https://design215.com/toolbox/utf8-4byte-characters.php const str = RocStr.fromSlice("𒀀𒀁"); - defer RocStr.deinit(str); + defer RocStr.decref(str); const expected_array = [_]u32{ 73728, 73729 }; const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]); - defer RocList.deinit(expected, u32); + defer expected.decref(@sizeOf(u32)); const actual = strToScalars(str); - defer RocList.deinit(actual, u32); + defer actual.decref(@sizeOf(u32)); try expect(RocList.eql(actual, expected)); } @@ -801,6 +871,27 @@ pub fn strSplit(string: RocStr, delimiter: RocStr) callconv(.C) RocList { return list; } +fn initFromSmallStr(slice_bytes: [*]u8, len: usize, _: usize) RocStr { + return RocStr.init(slice_bytes, len); +} + +// The ref_ptr must already be shifted to be ready for storing in a seamless slice. +fn initFromBigStr(slice_bytes: [*]u8, len: usize, ref_ptr: usize) RocStr { + // Here we can make seamless slices instead of copying to a new small str. + return RocStr{ + .str_bytes = slice_bytes, + .str_len = len | SEAMLESS_SLICE_BIT, + .str_capacity = ref_ptr, + }; +} + +// TODO: relpace this with @qualCast or @constCast in future version of zig +fn constCast(ptr: [*]const u8) [*]u8 { + var result: [*]u8 = undefined; + @memcpy(@ptrCast([*]u8, &result), @ptrCast([*]const u8, &ptr), @sizeOf([*]u8)); + return result; +} + fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void { var ret_array_index: usize = 0; var slice_start_index: usize = 0; @@ -808,6 +899,11 @@ fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void { const str_bytes = string.asU8ptr(); const str_len = string.len(); + const ref_ptr = @ptrToInt(string.getRefcountPtr()) >> 1; + const init_fn = if (string.isSmallStr()) + initFromSmallStr + else + initFromBigStr; const delimiter_bytes_ptrs = delimiter.asU8ptr(); const delimiter_len = delimiter.len(); @@ -839,7 +935,7 @@ fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void { if (matches_delimiter) { const segment_len: usize = str_index - slice_start_index; - array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, segment_len); + array[ret_array_index] = init_fn(constCast(str_bytes) + slice_start_index, segment_len, ref_ptr); slice_start_index = str_index + delimiter_len; ret_array_index += 1; str_index += delimiter_len; @@ -849,7 +945,12 @@ fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void { } } - array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, str_len - slice_start_index); + array[ret_array_index] = init_fn(constCast(str_bytes) + slice_start_index, str_len - slice_start_index, ref_ptr); + + if (!string.isSmallStr()) { + // Correct refcount for all of the splits made. + string.incref(ret_array_index + 1); + } } test "strSplitHelp: empty delimiter" { @@ -871,15 +972,15 @@ test "strSplitHelp: empty delimiter" { defer { for (array) |roc_str| { - roc_str.deinit(); + roc_str.decref(); } for (expected) |roc_str| { - roc_str.deinit(); + roc_str.decref(); } - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } try expectEqual(array.len, expected.len); @@ -905,15 +1006,15 @@ test "strSplitHelp: no delimiter" { defer { for (array) |roc_str| { - roc_str.deinit(); + roc_str.decref(); } for (expected) |roc_str| { - roc_str.deinit(); + roc_str.decref(); } - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } try expectEqual(array.len, expected.len); @@ -944,15 +1045,15 @@ test "strSplitHelp: empty start" { defer { for (array) |rocStr| { - rocStr.deinit(); + rocStr.decref(); } for (expected) |rocStr| { - rocStr.deinit(); + rocStr.decref(); } - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } try expectEqual(array.len, expected.len); @@ -986,15 +1087,15 @@ test "strSplitHelp: empty end" { defer { for (array) |rocStr| { - rocStr.deinit(); + rocStr.decref(); } for (expected) |rocStr| { - rocStr.deinit(); + rocStr.decref(); } - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } try expectEqual(array.len, expected.len); @@ -1020,14 +1121,14 @@ test "strSplitHelp: string equals delimiter" { defer { for (array) |rocStr| { - rocStr.deinit(); + rocStr.decref(); } for (expected) |rocStr| { - rocStr.deinit(); + rocStr.decref(); } - str_delimiter.deinit(); + str_delimiter.decref(); } try expectEqual(array.len, expected.len); @@ -1060,15 +1161,15 @@ test "strSplitHelp: delimiter on sides" { defer { for (array) |rocStr| { - rocStr.deinit(); + rocStr.decref(); } for (expected) |rocStr| { - rocStr.deinit(); + rocStr.decref(); } - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } try expectEqual(array.len, expected.len); @@ -1101,15 +1202,15 @@ test "strSplitHelp: three pieces" { defer { for (array) |roc_str| { - roc_str.deinit(); + roc_str.decref(); } for (expected_array) |roc_str| { - roc_str.deinit(); + roc_str.decref(); } - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } try expectEqual(expected_array.len, array.len); @@ -1118,6 +1219,58 @@ test "strSplitHelp: three pieces" { try expect(array[2].eq(expected_array[2])); } +test "strSplitHelp: overlapping delimiter 1" { + // Str.split "aaa" "aa" == ["", "a"] + const str_arr = "aaa"; + const str = RocStr.init(str_arr, str_arr.len); + + const delimiter_arr = "aa"; + const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len); + + var array: [2]RocStr = undefined; + const array_ptr: [*]RocStr = &array; + + strSplitHelp(array_ptr, str, delimiter); + + var expected = [2]RocStr{ + RocStr.empty(), + RocStr.init("a", 1), + }; + + // strings are all small so we ignore freeing the memory + + try expectEqual(array.len, expected.len); + try expect(array[0].eq(expected[0])); + try expect(array[1].eq(expected[1])); +} + +test "strSplitHelp: overlapping delimiter 2" { + // Str.split "aaa" "aa" == ["", "a"] + const str_arr = "aaaa"; + const str = RocStr.init(str_arr, str_arr.len); + + const delimiter_arr = "aa"; + const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len); + + var array: [3]RocStr = undefined; + const array_ptr: [*]RocStr = &array; + + strSplitHelp(array_ptr, str, delimiter); + + var expected = [3]RocStr{ + RocStr.empty(), + RocStr.empty(), + RocStr.empty(), + }; + + // strings are all small so we ignore freeing the memory + + try expectEqual(array.len, expected.len); + try expect(array[0].eq(expected[0])); + try expect(array[1].eq(expected[1])); + try expect(array[2].eq(expected[2])); +} + // This is used for `Str.split : Str, Str -> Array Str // It is used to count how many segments the input `_str` // needs to be broken into, so that we can allocate a array @@ -1154,9 +1307,10 @@ pub fn countSegments(string: RocStr, delimiter: RocStr) callconv(.C) usize { if (matches_delimiter) { count += 1; + str_index += delimiter_len; + } else { + str_index += 1; } - - str_index += 1; } } @@ -1173,8 +1327,8 @@ test "countSegments: long delimiter" { const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len); defer { - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } const segments_count = countSegments(str, delimiter); @@ -1191,8 +1345,8 @@ test "countSegments: delimiter at start" { const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len); defer { - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } const segments_count = countSegments(str, delimiter); @@ -1210,8 +1364,8 @@ test "countSegments: delimiter interspered" { const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len); defer { - str.deinit(); - delimiter.deinit(); + str.decref(); + delimiter.decref(); } const segments_count = countSegments(str, delimiter); @@ -1226,7 +1380,7 @@ test "countSegments: string equals delimiter" { const str_delimiter = RocStr.init(str_delimiter_arr, str_delimiter_arr.len); defer { - str_delimiter.deinit(); + str_delimiter.decref(); } const segments_count = countSegments(str_delimiter, str_delimiter); @@ -1234,6 +1388,20 @@ test "countSegments: string equals delimiter" { try expectEqual(segments_count, 2); } +test "countSegments: overlapping delimiter 1" { + // Str.split "aaa" "aa" == ["", "a"] + const segments_count = countSegments(RocStr.init("aaa", 3), RocStr.init("aa", 2)); + + try expectEqual(segments_count, 2); +} + +test "countSegments: overlapping delimiter 2" { + // Str.split "aaa" "aa" == ["", "a"] + const segments_count = countSegments(RocStr.init("aaaa", 4), RocStr.init("aa", 2)); + + try expectEqual(segments_count, 3); +} + // Str.countGraphemeClusters pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize { if (string.isEmpty()) { @@ -1277,6 +1445,12 @@ pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList { var index: usize = 0; var last_codepoint_len: u8 = 0; + const ref_ptr = @ptrToInt(roc_str.getRefcountPtr()) >> 1; + const init_fn = if (roc_str.isSmallStr()) + initFromSmallStr + else + initFromBigStr; + var result = RocList.allocate(@alignOf(RocStr), countGraphemeClusters(roc_str), @sizeOf(RocStr)); const graphemes = result.elements(RocStr) orelse return result; var slice = roc_str.asSlice(); @@ -1287,7 +1461,7 @@ pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList { if (opt_last_codepoint) |last_codepoint| { var did_break = grapheme.isGraphemeBreak(last_codepoint, cur_codepoint, &break_state); if (did_break) { - graphemes[index] = RocStr.fromSlice(slice[0..last_codepoint_len]); + graphemes[index] = init_fn(constCast(slice.ptr), last_codepoint_len, ref_ptr); slice = slice[last_codepoint_len..]; index += 1; break_state = null; @@ -1298,19 +1472,24 @@ pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList { opt_last_codepoint = cur_codepoint; } // Append last grapheme - graphemes[index] = RocStr.fromSlice(slice); + graphemes[index] = init_fn(constCast(slice.ptr), slice.len, ref_ptr); + + if (!roc_str.isSmallStr()) { + // Correct refcount for all of the splits made. + roc_str.incref(index + 1); + } return result; } // these test both countGraphemeClusters() and strGraphemes() fn graphemesTest(input: []const u8, expected: []const []const u8) !void { const rocstr = RocStr.fromSlice(input); - defer rocstr.deinit(); + defer rocstr.decref(); const count = countGraphemeClusters(rocstr); try expectEqual(expected.len, count); const graphemes = strGraphemes(rocstr); - defer graphemes.deinit(u8); + defer graphemes.decref(@sizeOf(u8)); if (input.len == 0) return; // empty string const elems = graphemes.elements(RocStr) orelse unreachable; for (expected) |g, i| { @@ -1361,10 +1540,10 @@ pub fn getUnsafe(string: RocStr, index: usize) callconv(.C) u8 { test "substringUnsafe: start" { const str = RocStr.fromSlice("abcdef"); - defer str.deinit(); + defer str.decref(); const expected = RocStr.fromSlice("abc"); - defer expected.deinit(); + defer expected.decref(); const actual = substringUnsafe(str, 0, 3); @@ -1373,10 +1552,10 @@ test "substringUnsafe: start" { test "substringUnsafe: middle" { const str = RocStr.fromSlice("abcdef"); - defer str.deinit(); + defer str.decref(); const expected = RocStr.fromSlice("bcd"); - defer expected.deinit(); + defer expected.decref(); const actual = substringUnsafe(str, 1, 3); @@ -1385,10 +1564,10 @@ test "substringUnsafe: middle" { test "substringUnsafe: end" { const str = RocStr.fromSlice("a string so long it is heap-allocated"); - defer str.deinit(); + defer str.decref(); const expected = RocStr.fromSlice("heap-allocated"); - defer expected.deinit(); + defer expected.decref(); const actual = substringUnsafe(str, 23, 37 - 23); @@ -1480,15 +1659,15 @@ test "startsWith: foo starts with fo" { test "startsWith: 123456789123456789 starts with 123456789123456789" { const str = RocStr.fromSlice("123456789123456789"); - defer str.deinit(); + defer str.decref(); try expect(startsWith(str, str)); } test "startsWith: 12345678912345678910 starts with 123456789123456789" { const str = RocStr.fromSlice("12345678912345678910"); - defer str.deinit(); + defer str.decref(); const prefix = RocStr.fromSlice("123456789123456789"); - defer prefix.deinit(); + defer prefix.decref(); try expect(startsWith(str, prefix)); } @@ -1519,23 +1698,23 @@ pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool { test "endsWith: foo ends with oo" { const foo = RocStr.init("foo", 3); const oo = RocStr.init("oo", 2); - defer foo.deinit(); - defer oo.deinit(); + defer foo.decref(); + defer oo.decref(); try expect(endsWith(foo, oo)); } test "endsWith: 123456789123456789 ends with 123456789123456789" { const str = RocStr.init("123456789123456789", 18); - defer str.deinit(); + defer str.decref(); try expect(endsWith(str, str)); } test "endsWith: 12345678912345678910 ends with 345678912345678910" { const str = RocStr.init("12345678912345678910", 20); const suffix = RocStr.init("345678912345678910", 18); - defer str.deinit(); - defer suffix.deinit(); + defer str.decref(); + defer suffix.decref(); try expect(endsWith(str, suffix)); } @@ -1543,8 +1722,8 @@ test "endsWith: 12345678912345678910 ends with 345678912345678910" { test "endsWith: hello world ends with world" { const str = RocStr.init("hello world", 11); const suffix = RocStr.init("world", 5); - defer str.deinit(); - defer suffix.deinit(); + defer str.decref(); + defer suffix.decref(); try expect(endsWith(str, suffix)); } @@ -1587,14 +1766,14 @@ test "RocStr.concat: small concat small" { var roc_str3 = RocStr.init(str3_ptr, str3_len); defer { - roc_str1.deinit(); - roc_str2.deinit(); - roc_str3.deinit(); + roc_str1.decref(); + roc_str2.decref(); + roc_str3.decref(); } const result = strConcat(roc_str1, roc_str2); - defer result.deinit(); + defer result.decref(); try expect(roc_str3.eq(result)); } @@ -1602,7 +1781,7 @@ test "RocStr.concat: small concat small" { pub const RocListStr = extern struct { list_elements: ?[*]RocStr, list_length: usize, - list_capacity: usize, + list_capacity_or_ref_ptr: usize, }; // Str.joinWith @@ -1610,7 +1789,7 @@ pub fn strJoinWithC(list: RocList, separator: RocStr) callconv(.C) RocStr { const roc_list_str = RocListStr{ .list_elements = @ptrCast(?[*]RocStr, @alignCast(@alignOf(usize), list.bytes)), .list_length = list.length, - .list_capacity = list.capacity, + .list_capacity_or_ref_ptr = list.capacity_or_ref_ptr, }; return @call(.{ .modifier = always_inline }, strJoinWith, .{ roc_list_str, separator }); @@ -1672,19 +1851,19 @@ test "RocStr.joinWith: result is big" { var elements: [3]RocStr = .{ roc_elem, roc_elem, roc_elem }; const list = RocListStr{ .list_length = 3, - .list_capacity = 3, + .list_capacity_or_ref_ptr = 3, .list_elements = @ptrCast([*]RocStr, &elements), }; defer { - roc_sep.deinit(); - roc_elem.deinit(); - roc_result.deinit(); + roc_sep.decref(); + roc_elem.decref(); + roc_result.decref(); } const result = strJoinWith(list, roc_sep); - defer result.deinit(); + defer result.decref(); try expect(roc_result.eq(result)); } @@ -1703,9 +1882,10 @@ inline fn strToBytes(arg: RocStr) RocList { @memcpy(ptr, arg.asU8ptr(), length); - return RocList{ .length = length, .bytes = ptr, .capacity = length }; + return RocList{ .length = length, .bytes = ptr, .capacity_or_ref_ptr = length }; } else { - return RocList{ .length = length, .bytes = arg.str_bytes, .capacity = arg.str_capacity }; + const is_seamless_slice = arg.str_len & SEAMLESS_SLICE_BIT; + return RocList{ .length = length, .bytes = arg.str_bytes, .capacity_or_ref_ptr = arg.str_capacity | is_seamless_slice }; } } @@ -1721,61 +1901,6 @@ const CountAndStart = extern struct { start: usize, }; -pub fn fromUtf8C(output: *FromUtf8Result, arg: RocList, update_mode: UpdateMode) callconv(.C) void { - output.* = fromUtf8(arg, update_mode); -} - -inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result { - const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length]; - - if (unicode.utf8ValidateSlice(bytes)) { - // the output will be correct. Now we need to take ownership of the input - if (arg.len() <= SMALL_STR_MAX_LENGTH) { - // turn the bytes into a small string - const string = RocStr.init(@ptrCast([*]u8, arg.bytes), arg.len()); - - // then decrement the input list - const data_bytes = arg.len(); - utils.decref(arg.bytes, data_bytes, RocStr.alignment); - - return FromUtf8Result{ - .is_ok = true, - .string = string, - .byte_index = 0, - .problem_code = Utf8ByteProblem.InvalidStartByte, - }; - } else { - const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode); - - const string = RocStr{ - .str_bytes = byte_list.bytes, - .str_len = byte_list.length, - .str_capacity = byte_list.capacity, - }; - - return FromUtf8Result{ - .is_ok = true, - .string = string, - .byte_index = 0, - .problem_code = Utf8ByteProblem.InvalidStartByte, - }; - } - } else { - const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); - - // consume the input list - const data_bytes = arg.len(); - utils.decref(arg.bytes, data_bytes, RocStr.alignment); - - return FromUtf8Result{ - .is_ok = false, - .string = RocStr.empty(), - .byte_index = temp.index, - .problem_code = temp.problem, - }; - } -} - pub fn fromUtf8RangeC( output: *FromUtf8Result, list: RocList, @@ -1787,45 +1912,31 @@ pub fn fromUtf8RangeC( } pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: UpdateMode) FromUtf8Result { - const bytes = @ptrCast([*]const u8, arg.bytes)[start..count]; + if (arg.len() == 0 or count == 0) { + arg.decref(RocStr.alignment); + return FromUtf8Result{ + .is_ok = true, + .string = RocStr.empty(), + .byte_index = 0, + .problem_code = Utf8ByteProblem.InvalidStartByte, + }; + } + const bytes = @ptrCast([*]const u8, arg.bytes)[start .. start + count]; - if (unicode.utf8ValidateSlice(bytes)) { - // the output will be correct. Now we need to clone the input - - if (count == arg.len() and count > SMALL_STR_MAX_LENGTH) { - const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode); - - const string = RocStr{ - .str_bytes = byte_list.bytes, - .str_len = byte_list.length, - .str_capacity = byte_list.capacity, - }; - - return FromUtf8Result{ - .is_ok = true, - .string = string, - .byte_index = 0, - .problem_code = Utf8ByteProblem.InvalidStartByte, - }; - } else { - // turn the bytes into a small string - const string = RocStr.init(@ptrCast([*]const u8, bytes), count); - - // decref the list - utils.decref(arg.bytes, arg.len(), 1); - - return FromUtf8Result{ - .is_ok = true, - .string = string, - .byte_index = 0, - .problem_code = Utf8ByteProblem.InvalidStartByte, - }; - } + if (isValidUnicode(bytes)) { + // Make a seamless slice of the input. + const string = RocStr.fromSubListUnsafe(arg, start, count, update_mode); + return FromUtf8Result{ + .is_ok = true, + .string = string, + .byte_index = 0, + .problem_code = Utf8ByteProblem.InvalidStartByte, + }; } else { const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); // decref the list - utils.decref(arg.bytes, arg.len(), 1); + arg.decref(RocStr.alignment); return FromUtf8Result{ .is_ok = false, @@ -1858,9 +1969,48 @@ fn errorToProblem(bytes: [*]u8, length: usize) struct { index: usize, problem: U unreachable; } -pub fn isValidUnicode(ptr: [*]u8, len: usize) callconv(.C) bool { - const bytes: []u8 = ptr[0..len]; - return @call(.{ .modifier = always_inline }, unicode.utf8ValidateSlice, .{bytes}); +pub fn isValidUnicode(buf: []const u8) bool { + const size = @sizeOf(u64); + // TODO: we should test changing the step on other platforms. + // The general tradeoff is making extremely large strings potentially much faster + // at the cost of small strings being slightly slower. + const step = size; + var i: usize = 0; + while (i + step < buf.len) { + var bytes: u64 = undefined; + @memcpy(@ptrCast([*]u8, &bytes), @ptrCast([*]const u8, buf) + i, size); + const unicode_bytes = bytes & 0x8080_8080_8080_8080; + if (unicode_bytes == 0) { + i += step; + continue; + } + + while (buf[i] < 0b1000_0000) : (i += 1) {} + + while (buf[i] >= 0b1000_0000) { + // This forces prefetching, otherwise the loop can run at about half speed. + if (i + 4 >= buf.len) break; + var small_buf: [4]u8 = undefined; + @memcpy(&small_buf, @ptrCast([*]const u8, buf) + i, 4); + // TODO: Should we always inline these function calls below? + if (std.unicode.utf8ByteSequenceLength(small_buf[0])) |cp_len| { + if (std.meta.isError(std.unicode.utf8Decode(small_buf[0..cp_len]))) { + return false; + } + i += cp_len; + } else |_| { + return false; + } + } + } + + if (i == buf.len) return true; + while (buf[i] < 0b1000_0000) { + i += 1; + if (i == buf.len) return true; + } + + return @call(.{ .modifier = always_inline }, unicode.utf8ValidateSlice, .{buf[i..]}); } const Utf8DecodeError = error{ @@ -1897,11 +2047,11 @@ pub const Utf8ByteProblem = enum(u8) { }; fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result { - return fromUtf8(RocList{ .bytes = bytes, .length = length, .capacity = length }, .Immutable); + return fromUtf8Range(RocList{ .bytes = bytes, .length = length, .capacity_or_ref_ptr = length }, 0, length, .Immutable); } fn validateUtf8BytesX(str: RocList) FromUtf8Result { - return fromUtf8(str, .Immutable); + return fromUtf8Range(str, 0, str.len(), .Immutable); } fn expectOk(result: FromUtf8Result) !void { @@ -1930,7 +2080,9 @@ test "validateUtf8Bytes: ascii" { const ptr: [*]const u8 = @ptrCast([*]const u8, raw); const list = sliceHelp(ptr, raw.len); - try expectOk(validateUtf8BytesX(list)); + const str_result = validateUtf8BytesX(list); + defer str_result.string.decref(); + try expectOk(str_result); } test "validateUtf8Bytes: unicode œ" { @@ -1938,7 +2090,9 @@ test "validateUtf8Bytes: unicode œ" { const ptr: [*]const u8 = @ptrCast([*]const u8, raw); const list = sliceHelp(ptr, raw.len); - try expectOk(validateUtf8BytesX(list)); + const str_result = validateUtf8BytesX(list); + defer str_result.string.decref(); + try expectOk(str_result); } test "validateUtf8Bytes: unicode ∆" { @@ -1946,7 +2100,9 @@ test "validateUtf8Bytes: unicode ∆" { const ptr: [*]const u8 = @ptrCast([*]const u8, raw); const list = sliceHelp(ptr, raw.len); - try expectOk(validateUtf8BytesX(list)); + const str_result = validateUtf8BytesX(list); + defer str_result.string.decref(); + try expectOk(str_result); } test "validateUtf8Bytes: emoji" { @@ -1954,7 +2110,9 @@ test "validateUtf8Bytes: emoji" { const ptr: [*]const u8 = @ptrCast([*]const u8, raw); const list = sliceHelp(ptr, raw.len); - try expectOk(validateUtf8BytesX(list)); + const str_result = validateUtf8BytesX(list); + defer str_result.string.decref(); + try expectOk(str_result); } test "validateUtf8Bytes: unicode ∆ in middle of array" { @@ -1962,7 +2120,9 @@ test "validateUtf8Bytes: unicode ∆ in middle of array" { const ptr: [*]const u8 = @ptrCast([*]const u8, raw); const list = sliceHelp(ptr, raw.len); - try expectOk(validateUtf8BytesX(list)); + const str_result = validateUtf8BytesX(list); + defer str_result.string.decref(); + try expectOk(str_result); } fn expectErr(list: RocList, index: usize, err: Utf8DecodeError, problem: Utf8ByteProblem) !void { @@ -2089,134 +2249,149 @@ test "isWhitespace" { try expect(!isWhitespace('x')); } -pub fn strTrim(string: RocStr) callconv(.C) RocStr { - if (string.str_bytes) |bytes_ptr| { - const leading_bytes = countLeadingWhitespaceBytes(string); - const original_len = string.len(); +pub fn strTrim(input_string: RocStr) callconv(.C) RocStr { + var string = input_string; - if (original_len == leading_bytes) { - string.deinit(); - return RocStr.empty(); - } - - const trailing_bytes = countTrailingWhitespaceBytes(string); - const new_len = original_len - leading_bytes - trailing_bytes; - - const small_or_shared = new_len <= SMALL_STR_MAX_LENGTH or !string.isRefcountOne(); - if (small_or_shared) { - // consume the input string; this will not free the - // bytes because the string is small or shared - const result = RocStr.init(string.asU8ptr() + leading_bytes, new_len); - - string.decref(); - - return result; - } else { - // nonempty, large, and unique: shift everything over in-place if necessary. - // Note: must use memmove over memcpy, because the bytes definitely overlap! - if (leading_bytes > 0) { - // Zig doesn't seem to have `memmove` in the stdlib anymore; this is based on: - // https://github.com/ziglang/zig/blob/52ba2c3a43a88a4db30cff47f2f3eff8c3d5be19/lib/std/special/c.zig#L115 - // Copyright Andrew Kelley, MIT licensed. - const src = bytes_ptr + leading_bytes; - var index: usize = 0; - - while (index != new_len) : (index += 1) { - bytes_ptr[index] = src[index]; - } - } - - var new_string = string; - new_string.str_len = new_len; - - return new_string; - } + if (string.isEmpty()) { + string.decref(); + return RocStr.empty(); } - return RocStr.empty(); -} + const bytes_ptr = string.asU8ptrMut(); -pub fn strTrimLeft(string: RocStr) callconv(.C) RocStr { - if (string.str_bytes) |bytes_ptr| { - const leading_bytes = countLeadingWhitespaceBytes(string); - const original_len = string.len(); + const leading_bytes = countLeadingWhitespaceBytes(string); + const original_len = string.len(); - if (original_len == leading_bytes) { - string.deinit(); - return RocStr.empty(); - } - - const new_len = original_len - leading_bytes; - - if (string.isSmallStr() or !string.isRefcountOne()) { - // if the trimmed string fits in a small string, - // make the result a small string and decref the original string - const result = RocStr.init(string.asU8ptr() + leading_bytes, new_len); - - string.decref(); - - return result; - } else { - // nonempty, large, and unique: shift everything over in-place if necessary. - // Note: must use memmove over memcpy, because the bytes definitely overlap! - if (leading_bytes > 0) { - // Zig doesn't seem to have `memmove` in the stdlib anymore; this is based on: - // https://github.com/ziglang/zig/blob/52ba2c3a43a88a4db30cff47f2f3eff8c3d5be19/lib/std/special/c.zig#L115 - // Copyright Andrew Kelley, MIT licensed. - const src = bytes_ptr + leading_bytes; - var index: usize = 0; - - while (index != new_len) : (index += 1) { - bytes_ptr[index] = src[index]; - } - } - - var new_string = string; - new_string.str_len = new_len; - - return new_string; - } + if (original_len == leading_bytes) { + string.decref(); + return RocStr.empty(); } - return RocStr.empty(); -} - -pub fn strTrimRight(string: RocStr) callconv(.C) RocStr { - if (string.str_bytes) |bytes_ptr| { - const trailing_bytes = countTrailingWhitespaceBytes(string); - const original_len = string.len(); - - if (original_len == trailing_bytes) { - string.deinit(); - return RocStr.empty(); - } - - const new_len = original_len - trailing_bytes; - - if (string.isSmallStr() or !string.isRefcountOne()) { - const result = RocStr.init(string.asU8ptr(), new_len); - - string.decref(); - - return result; - } - - // nonempty, large, and unique: - - var i: usize = 0; - while (i < new_len) : (i += 1) { - const dest = bytes_ptr + i; - const source = dest; - @memcpy(dest, source, 1); - } + const trailing_bytes = countTrailingWhitespaceBytes(string); + const new_len = original_len - leading_bytes - trailing_bytes; + if (string.isSmallStr()) { + // Just create another small string of the correct bytes. + // No need to decref because it is a small string. + return RocStr.init(string.asU8ptr() + leading_bytes, new_len); + } else if (leading_bytes == 0 and string.isUnique()) { + // Big and unique with no leading bytes to remove. + // Just take ownership and shrink the length. var new_string = string; new_string.str_len = new_len; return new_string; + } else if (string.isSeamlessSlice()) { + // Already a seamless slice, just update the range. + return RocStr{ + .str_bytes = bytes_ptr + leading_bytes, + .str_len = new_len | SEAMLESS_SLICE_BIT, + .str_capacity = string.str_capacity, + }; + } else { + // Not unique or removing leading bytes, just make a slice. + return RocStr{ + .str_bytes = bytes_ptr + leading_bytes, + .str_len = new_len | SEAMLESS_SLICE_BIT, + .str_capacity = @ptrToInt(bytes_ptr) >> 1, + }; + } +} + +pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr { + var string = input_string; + + if (string.isEmpty()) { + string.decref(); + return RocStr.empty(); } - return RocStr.empty(); + const bytes_ptr = string.asU8ptrMut(); + + const leading_bytes = countLeadingWhitespaceBytes(string); + const original_len = string.len(); + + if (original_len == leading_bytes) { + string.decref(); + return RocStr.empty(); + } + + const new_len = original_len - leading_bytes; + + if (string.isSmallStr()) { + // Just create another small string of the correct bytes. + // No need to decref because it is a small string. + return RocStr.init(string.asU8ptr() + leading_bytes, new_len); + } else if (leading_bytes == 0 and string.isUnique()) { + // Big and unique with no leading bytes to remove. + // Just take ownership and shrink the length. + var new_string = string; + new_string.str_len = new_len; + + return new_string; + } else if (string.isSeamlessSlice()) { + // Already a seamless slice, just update the range. + return RocStr{ + .str_bytes = bytes_ptr + leading_bytes, + .str_len = new_len | SEAMLESS_SLICE_BIT, + .str_capacity = string.str_capacity, + }; + } else { + // Not unique or removing leading bytes, just make a slice. + return RocStr{ + .str_bytes = bytes_ptr + leading_bytes, + .str_len = new_len | SEAMLESS_SLICE_BIT, + .str_capacity = @ptrToInt(bytes_ptr) >> 1, + }; + } +} + +pub fn strTrimRight(input_string: RocStr) callconv(.C) RocStr { + var string = input_string; + + if (string.isEmpty()) { + string.decref(); + return RocStr.empty(); + } + + const bytes_ptr = string.asU8ptrMut(); + + const trailing_bytes = countTrailingWhitespaceBytes(string); + const original_len = string.len(); + + if (original_len == trailing_bytes) { + string.decref(); + return RocStr.empty(); + } + + const new_len = original_len - trailing_bytes; + + if (string.isSmallStr()) { + // Just create another small string of the correct bytes. + // No need to decref because it is a small string. + return RocStr.init(string.asU8ptr(), new_len); + } else if (string.isUnique()) { + // Big and unique with no leading bytes to remove. + // Just take ownership and shrink the length. + var new_string = string; + new_string.str_len = new_len; + + return new_string; + } else if (string.isSeamlessSlice()) { + // Already a seamless slice, just update the range. + return RocStr{ + .str_bytes = bytes_ptr, + .str_len = new_len | SEAMLESS_SLICE_BIT, + .str_capacity = string.str_capacity, + }; + } else { + // Not unique, just make a slice. + return RocStr{ + .str_bytes = bytes_ptr, + .str_len = new_len | SEAMLESS_SLICE_BIT, + .str_capacity = @ptrToInt(bytes_ptr) >> 1, + }; + } } fn countLeadingWhitespaceBytes(string: RocStr) usize { @@ -2318,12 +2493,31 @@ test "strTrim: empty" { try expect(trimmedEmpty.eq(RocStr.empty())); } +test "strTrim: null byte" { + const bytes = [_]u8{0}; + const original = RocStr.init(&bytes, 1); + + try expectEqual(@as(usize, 1), original.len()); + try expectEqual(@as(usize, SMALL_STR_MAX_LENGTH), original.getCapacity()); + + const original_with_capacity = reserve(original, 40); + defer original_with_capacity.decref(); + + try expectEqual(@as(usize, 1), original_with_capacity.len()); + try expectEqual(@as(usize, 64), original_with_capacity.getCapacity()); + + const trimmed = strTrim(original.clone()); + defer trimmed.decref(); + + try expect(original.eq(trimmed)); +} + test "strTrim: blank" { const original_bytes = " "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); const trimmed = strTrim(original); + defer trimmed.decref(); try expect(trimmed.eq(RocStr.empty())); } @@ -2331,22 +2525,22 @@ test "strTrim: blank" { test "strTrim: large to large" { const original_bytes = " hello even more giant world "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); try expect(!original.isSmallStr()); const expected_bytes = "hello even more giant world"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(!expected.isSmallStr()); const trimmed = strTrim(original); + defer trimmed.decref(); try expect(trimmed.eq(expected)); } -test "strTrim: large to small" { +test "strTrim: large to small sized slice" { const original_bytes = " hello "; const original = RocStr.init(original_bytes, original_bytes.len); @@ -2354,26 +2548,28 @@ test "strTrim: large to small" { const expected_bytes = "hello"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(expected.isSmallStr()); + try expect(original.isUnique()); const trimmed = strTrim(original); + defer trimmed.decref(); try expect(trimmed.eq(expected)); - try expect(trimmed.isSmallStr()); + try expect(!trimmed.isSmallStr()); } test "strTrim: small to small" { const original_bytes = " hello "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); + defer original.decref(); try expect(original.isSmallStr()); const expected_bytes = "hello"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(expected.isSmallStr()); @@ -2391,7 +2587,7 @@ test "strTrimLeft: empty" { test "strTrimLeft: blank" { const original_bytes = " "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); + defer original.decref(); const trimmed = strTrimLeft(original); @@ -2401,13 +2597,13 @@ test "strTrimLeft: blank" { test "strTrimLeft: large to large" { const original_bytes = " hello even more giant world "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); + defer original.decref(); try expect(!original.isSmallStr()); const expected_bytes = "hello even more giant world "; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(!expected.isSmallStr()); @@ -2425,12 +2621,12 @@ test "strTrimLeft: large to small" { const expected_bytes = "hello "; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(expected.isSmallStr()); const trimmed = strTrimLeft(original); - defer trimmed.deinit(); + defer trimmed.decref(); try expect(trimmed.eq(expected)); try expect(!trimmed.isSmallStr()); @@ -2439,13 +2635,13 @@ test "strTrimLeft: large to small" { test "strTrimLeft: small to small" { const original_bytes = " hello "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); + defer original.decref(); try expect(original.isSmallStr()); const expected_bytes = "hello "; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(expected.isSmallStr()); @@ -2463,7 +2659,7 @@ test "strTrimRight: empty" { test "strTrimRight: blank" { const original_bytes = " "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); + defer original.decref(); const trimmed = strTrimRight(original); @@ -2473,13 +2669,13 @@ test "strTrimRight: blank" { test "strTrimRight: large to large" { const original_bytes = " hello even more giant world "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); + defer original.decref(); try expect(!original.isSmallStr()); const expected_bytes = " hello even more giant world"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(!expected.isSmallStr()); @@ -2497,12 +2693,12 @@ test "strTrimRight: large to small" { const expected_bytes = " hello"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(expected.isSmallStr()); const trimmed = strTrimRight(original); - defer trimmed.deinit(); + defer trimmed.decref(); try expect(trimmed.eq(expected)); try expect(!trimmed.isSmallStr()); @@ -2511,13 +2707,13 @@ test "strTrimRight: large to small" { test "strTrimRight: small to small" { const original_bytes = " hello "; const original = RocStr.init(original_bytes, original_bytes.len); - defer original.deinit(); + defer original.decref(); try expect(original.isSmallStr()); const expected_bytes = " hello"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(expected.isSmallStr()); @@ -2551,7 +2747,7 @@ test "ReverseUtf8View: empty" { test "capacity: small string" { const data_bytes = "foobar"; var data = RocStr.init(data_bytes, data_bytes.len); - defer data.deinit(); + defer data.decref(); try expectEqual(data.getCapacity(), SMALL_STR_MAX_LENGTH); } @@ -2559,7 +2755,7 @@ test "capacity: small string" { test "capacity: big string" { const data_bytes = "a string so large that it must be heap-allocated"; var data = RocStr.init(data_bytes, data_bytes.len); - defer data.deinit(); + defer data.decref(); try expect(data.getCapacity() >= data_bytes.len); } @@ -2583,11 +2779,11 @@ test "appendScalar: small A" { var data = RocStr.init(data_bytes, data_bytes.len); const actual = appendScalar(data, A[0]); - defer actual.deinit(); + defer actual.decref(); const expected_bytes = "helloA"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(actual.eq(expected)); } @@ -2597,11 +2793,11 @@ test "appendScalar: small 😀" { var data = RocStr.init(data_bytes, data_bytes.len); const actual = appendScalar(data, 0x1F600); - defer actual.deinit(); + defer actual.decref(); const expected_bytes = "hello😀"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(actual.eq(expected)); } @@ -2613,11 +2809,11 @@ test "appendScalar: big A" { var data = RocStr.init(data_bytes, data_bytes.len); const actual = appendScalar(data, A[0]); - defer actual.deinit(); + defer actual.decref(); const expected_bytes = "a string so large that it must be heap-allocatedA"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(actual.eq(expected)); } @@ -2627,11 +2823,11 @@ test "appendScalar: big 😀" { var data = RocStr.init(data_bytes, data_bytes.len); const actual = appendScalar(data, 0x1F600); - defer actual.deinit(); + defer actual.decref(); const expected_bytes = "a string so large that it must be heap-allocated😀"; const expected = RocStr.init(expected_bytes, expected_bytes.len); - defer expected.deinit(); + defer expected.decref(); try expect(actual.eq(expected)); } @@ -2705,3 +2901,34 @@ pub fn strCloneTo( return extra_offset + slice.len; } } + +pub fn strRefcountPtr( + string: RocStr, +) callconv(.C) ?[*]u8 { + return string.getRefcountPtr(); +} + +pub fn strReleaseExcessCapacity( + string: RocStr, +) callconv(.C) RocStr { + const old_length = string.len(); + // We use the direct list.capacity_or_ref_ptr to make sure both that there is no extra capacity and that it isn't a seamless slice. + if (string.isSmallStr()) { + // SmallStr has no excess capacity. + return string; + } else if (string.isUnique() and !string.isSeamlessSlice() and string.getCapacity() == old_length) { + return string; + } else if (old_length == 0) { + string.decref(); + return RocStr.empty(); + } else { + var output = RocStr.allocateExact(old_length); + const source_ptr = string.asU8ptr(); + const dest_ptr = output.asU8ptrMut(); + + @memcpy(dest_ptr, source_ptr, old_length); + string.decref(); + + return output; + } +} diff --git a/crates/compiler/builtins/bitcode/tests/bitcode_test.rs b/crates/compiler/builtins/bitcode/tests/bitcode_test.rs deleted file mode 100644 index 515e2d309b..0000000000 --- a/crates/compiler/builtins/bitcode/tests/bitcode_test.rs +++ /dev/null @@ -1,64 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; - -#[cfg(test)] -mod bitcode { - use roc_builtins_bitcode::{count_segments_, str_split_}; - - #[test] - fn count_segments() { - assert_eq!( - count_segments_((&"hello there").as_bytes(), (&"hello").as_bytes()), - 2 - ); - assert_eq!( - count_segments_((&"a\nb\nc").as_bytes(), (&"\n").as_bytes()), - 3 - ); - assert_eq!( - count_segments_((&"str").as_bytes(), (&"delimiter").as_bytes()), - 1 - ); - } - - #[test] - fn str_split() { - fn splits_to(string: &str, delimiter: &str, expectation: &[&[u8]]) { - assert_eq!( - str_split_( - &mut [(&"").as_bytes()].repeat(expectation.len()), - &string.as_bytes(), - &delimiter.as_bytes() - ), - expectation - ); - } - - splits_to( - "a!b!c", - "!", - &[(&"a").as_bytes(), (&"b").as_bytes(), (&"c").as_bytes()], - ); - - splits_to( - "a!?b!?c!?", - "!?", - &[ - (&"a").as_bytes(), - (&"b").as_bytes(), - (&"c").as_bytes(), - (&"").as_bytes(), - ], - ); - - splits_to("abc", "!", &[(&"abc").as_bytes()]); - - splits_to( - "tttttghittttt", - "ttttt", - &[(&"").as_bytes(), (&"ghi").as_bytes(), (&"").as_bytes()], - ); - - splits_to("def", "!!!!!!", &[(&"def").as_bytes()]); - } -} diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc index bb95805069..d16e8b2494 100644 --- a/crates/compiler/builtins/roc/Bool.roc +++ b/crates/compiler/builtins/roc/Bool.roc @@ -32,6 +32,8 @@ Eq has ## cannot derive `isEq` for types that contain functions. isEq : a, a -> Bool | a has Eq +## Represents the boolean true and false using an opaque type. +## `Bool` implements the `Eq` ability. Bool := [True, False] has [Eq { isEq: boolIsEq }] boolIsEq = \@Bool b1, @Bool b2 -> structuralEq b1 b2 @@ -49,23 +51,27 @@ false = @Bool False ## gate. The infix operator `&&` can also be used as shorthand for ## `Bool.and`. ## -## expect (Bool.and Bool.true Bool.true) == Bool.true -## expect (Bool.true && Bool.true) == Bool.true -## expect (Bool.false && Bool.true) == Bool.false -## expect (Bool.true && Bool.false) == Bool.false -## expect (Bool.false && Bool.false) == Bool.false +## ``` +## expect (Bool.and Bool.true Bool.true) == Bool.true +## expect (Bool.true && Bool.true) == Bool.true +## expect (Bool.false && Bool.true) == Bool.false +## expect (Bool.true && Bool.false) == Bool.false +## expect (Bool.false && Bool.false) == Bool.false +## ``` ## -## **Performance Note** that in Roc the `&&` and `||` work the same way as any +## ## Performance Details +## +## In Roc the `&&` and `||` work the same way as any ## other function. However, in some languages `&&` and `||` are special-cased. ## In these languages the compiler will skip evaluating the expression after the ## first operator under certain circumstances. For example an expression like ## `enablePets && likesDogs user` would compile to. -## -## if enablePets then -## likesDogs user -## else -## Bool.false -## +## ``` +## if enablePets then +## likesDogs user +## else +## Bool.false +## ``` ## Roc does not do this because conditionals like `if` and `when` have a ## performance cost. Calling a function can sometimes be faster across the board ## than doing an `if` to decide whether to skip calling it. @@ -74,14 +80,17 @@ and : Bool, Bool -> Bool ## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to ## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate. ## The infix operator `||` can also be used as shorthand for `Bool.or`. +## ``` +## expect (Bool.or Bool.false Bool.true) == Bool.true +## expect (Bool.true || Bool.true) == Bool.true +## expect (Bool.false || Bool.true) == Bool.true +## expect (Bool.true || Bool.false) == Bool.true +## expect (Bool.false || Bool.false) == Bool.false +## ``` ## -## expect (Bool.or Bool.false Bool.true) == Bool.true -## expect (Bool.true || Bool.true) == Bool.true -## expect (Bool.false || Bool.true) == Bool.true -## expect (Bool.true || Bool.false) == Bool.true -## expect (Bool.false || Bool.false) == Bool.false +## ## Performance Details ## -## **Performance Note** that in Roc the `&&` and `||` work the same way as any +## In Roc the `&&` and `||` work the same way as any ## other functions. However, in some languages `&&` and `||` are special-cased. ## Refer to the note in `Bool.and` for more detail. or : Bool, Bool -> Bool @@ -89,9 +98,10 @@ or : Bool, Bool -> Bool ## Returns `Bool.false` when given `Bool.true`, and vice versa. This is ## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation) ## gate. The operator `!` can also be used as shorthand for `Bool.not`. -## -## expect (Bool.not Bool.false) == Bool.true -## expect (!Bool.false) == Bool.true +## ``` +## expect (Bool.not Bool.false) == Bool.true +## expect (!Bool.false) == Bool.true +## ``` not : Bool -> Bool ## This will call the function `Bool.isEq` on the inputs, and then `Bool.not` @@ -101,10 +111,11 @@ not : Bool -> Bool ## ## **Note** that `isNotEq` does not accept arguments whose types contain ## functions. -## -## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true -## expect (Bool.false != Bool.false) == Bool.false -## expect "Apples" != "Oranges" +## ``` +## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true +## expect (Bool.false != Bool.false) == Bool.false +## expect "Apples" != "Oranges" +## ``` isNotEq : a, a -> Bool | a has Eq isNotEq = \a, b -> structuralNotEq a b diff --git a/crates/compiler/builtins/roc/Box.roc b/crates/compiler/builtins/roc/Box.roc index f4e0158e85..5bebae160a 100644 --- a/crates/compiler/builtins/roc/Box.roc +++ b/crates/compiler/builtins/roc/Box.roc @@ -6,13 +6,15 @@ interface Box ## the value from the stack to the heap. This may provide a performance ## optimization for advanced use cases with large values. A platform may require ## that some values are boxed. -## -## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster" +## ``` +## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster" +## ``` box : a -> Box a ## Returns a boxed value. -## -## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster" +## ``` +## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster" +## ``` unbox : Box a -> a # # we'd need reset/reuse for box for this to be efficient diff --git a/crates/compiler/builtins/roc/Decode.roc b/crates/compiler/builtins/roc/Decode.roc index 95407be0d0..a6bfef53b7 100644 --- a/crates/compiler/builtins/roc/Decode.roc +++ b/crates/compiler/builtins/roc/Decode.roc @@ -23,6 +23,7 @@ interface Decode string, list, record, + tuple, custom, decodeWith, fromBytesPartial, @@ -43,6 +44,7 @@ interface Decode I32, I64, I128, + Nat, F32, F64, Dec, @@ -76,8 +78,24 @@ DecoderFormatting has bool : Decoder Bool fmt | fmt has DecoderFormatting string : Decoder Str fmt | fmt has DecoderFormatting list : Decoder elem fmt -> Decoder (List elem) fmt | fmt has DecoderFormatting + + ## `record state stepField finalizer` decodes a record field-by-field. + ## + ## `stepField` returns a decoder for the given field in the record, or + ## `Skip` if the field is not a part of the decoded record. + ## + ## `finalizer` should produce the record value from the decoded `state`. record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting + ## `tuple state stepElem finalizer` decodes a tuple element-by-element. + ## + ## `stepElem` returns a decoder for the nth index in the tuple, or + ## `TooLong` if the index is larger than the expected size of the tuple. The + ## index passed to `stepElem` is 0-indexed. + ## + ## `finalizer` should produce the tuple value from the decoded `state`. + tuple : state, (state, Nat -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting + custom : (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting custom = \decode -> @Decoder decode diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index dddf993397..0db8f601ca 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -34,7 +34,7 @@ interface Dict ## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you ## associate keys with values. ## -## ### Inserting +## ## Inserting ## ## The most basic way to use a dictionary is to start with an empty one and ## then: @@ -45,16 +45,16 @@ interface Dict ## ## 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 +## ``` +## 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. @@ -63,16 +63,16 @@ interface Dict ## order. This will be true if all you ever do is [Dict.insert] and [Dict.get] operations ## on the dictionary, but [Dict.remove] operations can change this order. ## -## ### Removing +## ## Removing ## ## We can remove an element from the dictionary, like so: -## -## populationByCity -## |> Dict.remove "Philadelphia" -## |> Dict.keys -## == -## ["London", "Amsterdam", "Shanghai", "Delhi"] -## +## ``` +## populationByCity +## |> Dict.remove "Philadelphia" +## |> Dict.keys +## == +## ["London", "Amsterdam", "Shanghai", "Delhi"] +## ``` ## Notice that the order has changed. Philadelphia was 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] @@ -100,6 +100,9 @@ Dict k v := { } | k has Hash & Eq ## Return an empty dictionary. +## ``` +## emptyDict = Dict.empty {} +## ``` empty : {} -> Dict k v | k has Hash & Eq empty = \{} -> @Dict { @@ -110,6 +113,13 @@ empty = \{} -> } ## Returns the max number of elements the dictionary can hold before requiring a rehash. +## ``` +## foodDict = +## Dict.empty {} +## |> Dict.insert "apple" "fruit" +## +## capacityOfDict = Dict.capacity foodDict +## ``` capacity : Dict k v -> Nat | k has Hash & Eq capacity = \@Dict { dataIndices } -> cap = List.len dataIndices @@ -125,41 +135,55 @@ withCapacity = \_ -> empty {} ## Returns a dictionary containing the key and value provided as input. -## -## expect -## Dict.single "A" "B" -## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B") +## ``` +## expect +## Dict.single "A" "B" +## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B") +## ``` single : k, v -> Dict k v | k has Hash & Eq single = \k, v -> insert (empty {}) k v ## Returns dictionary with the keys and values specified by the input [List]. -## -## expect -## Dict.single 1 "One" -## |> Dict.insert 2 "Two" -## |> Dict.insert 3 "Three" -## |> Dict.insert 4 "Four" -## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]) +## ``` +## expect +## Dict.single 1 "One" +## |> Dict.insert 2 "Two" +## |> Dict.insert 3 "Three" +## |> Dict.insert 4 "Four" +## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]) +## ``` fromList : List (T k v) -> Dict k v | k has Hash & Eq fromList = \data -> # TODO: make this efficient. Should just set data and then set all indicies in the hashmap. List.walk data (empty {}) (\dict, T k v -> insert dict k v) ## Returns the number of values in the dictionary. -## -## expect -## Dict.empty {} -## |> Dict.insert "One" "A Song" -## |> Dict.insert "Two" "Candy Canes" -## |> Dict.insert "Three" "Boughs of Holly" -## |> Dict.len -## |> Bool.isEq 3 +## ``` +## expect +## Dict.empty {} +## |> Dict.insert "One" "A Song" +## |> Dict.insert "Two" "Candy Canes" +## |> Dict.insert "Three" "Boughs of Holly" +## |> Dict.len +## |> Bool.isEq 3 +## ``` len : Dict k v -> Nat | k has Hash & Eq len = \@Dict { size } -> size ## Clears all elements from a dictionary keeping around the allocation if it isn't huge. +## ``` +## songs = +## Dict.empty {} +## |> Dict.insert "One" "A Song" +## |> Dict.insert "Two" "Candy Canes" +## |> Dict.insert "Three" "Boughs of Holly" +## +## clearSongs = Dict.clear songs +## +## expect Dict.len clearSongs == 0 +## ``` clear : Dict k v -> Dict k v | k has Hash & Eq clear = \@Dict { metadata, dataIndices, data } -> cap = List.len dataIndices @@ -180,13 +204,14 @@ clear = \@Dict { metadata, dataIndices, data } -> ## Iterate through the keys and values in the dictionary and call the provided ## function with signature `state, k, v -> state` for each value, with an ## initial `state` value provided for the first call. -## -## expect -## Dict.empty {} -## |> Dict.insert "Apples" 12 -## |> Dict.insert "Orange" 24 -## |> Dict.walk 0 (\count, _, qty -> count + qty) -## |> Bool.isEq 36 +## ``` +## expect +## Dict.empty {} +## |> Dict.insert "Apples" 12 +## |> Dict.insert "Orange" 24 +## |> Dict.walk 0 (\count, _, qty -> count + qty) +## |> Bool.isEq 36 +## ``` walk : Dict k v, state, (state, k, v -> state) -> state | k has Hash & Eq walk = \@Dict { data }, initialState, transform -> List.walk data initialState (\state, T k v -> transform state k v) @@ -202,20 +227,38 @@ walk = \@Dict { data }, initialState, transform -> ## ## As such, it is typically better for performance to use this over [Dict.walk] ## if returning `Break` earlier than the last element is expected to be common. +## ``` +## people = +## Dict.empty {} +## |> Dict.insert "Alice" 17 +## |> Dict.insert "Bob" 18 +## |> Dict.insert "Charlie" 19 +## +## isAdult = \_, _, age -> +## if age >= 18 then +## Break Bool.true +## else +## Continue Bool.false +## +## someoneIsAnAdult = Dict.walkUntil people Bool.false isAdult +## +## expect someoneIsAnAdult == Bool.true +## ``` walkUntil : Dict k v, state, (state, k, v -> [Continue state, Break state]) -> state | k has Hash & Eq walkUntil = \@Dict { data }, initialState, transform -> List.walkUntil data initialState (\state, T k v -> transform state k v) ## Get the value for a given key. If there is a value for the specified key it ## will return [Ok value], otherwise return [Err KeyNotFound]. +## ``` +## dictionary = +## Dict.empty {} +## |> Dict.insert 1 "Apple" +## |> Dict.insert 2 "Orange" ## -## dictionary = -## Dict.empty {} -## |> Dict.insert 1 "Apple" -## |> Dict.insert 2 "Orange" -## -## expect Dict.get dictionary 1 == Ok "Apple" -## expect Dict.get dictionary 2000 == Err KeyNotFound +## expect Dict.get dictionary 1 == Ok "Apple" +## expect Dict.get dictionary 2000 == Err KeyNotFound +## ``` get : Dict k v, k -> Result v [KeyNotFound] | k has Hash & Eq get = \@Dict { metadata, dataIndices, data }, key -> hashKey = @@ -237,12 +280,13 @@ get = \@Dict { metadata, dataIndices, data }, key -> Err KeyNotFound ## Check if the dictionary has a value for a specified key. -## -## expect -## Dict.empty {} -## |> Dict.insert 1234 "5678" -## |> Dict.contains 1234 -## |> Bool.isEq Bool.true +## ``` +## expect +## Dict.empty {} +## |> Dict.insert 1234 "5678" +## |> Dict.contains 1234 +## |> Bool.isEq Bool.true +## ``` contains : Dict k v, k -> Bool | k has Hash & Eq contains = \@Dict { metadata, dataIndices, data }, key -> hashKey = @@ -261,12 +305,13 @@ contains = \@Dict { metadata, dataIndices, data }, key -> Bool.false ## Insert a value into the dictionary at a specified key. -## -## expect -## Dict.empty {} -## |> Dict.insert "Apples" 12 -## |> Dict.get "Apples" -## |> Bool.isEq (Ok 12) +## ``` +## expect +## Dict.empty {} +## |> Dict.insert "Apples" 12 +## |> Dict.get "Apples" +## |> Bool.isEq (Ok 12) +## ``` insert : Dict k v, k, v -> Dict k v | k has Hash & Eq insert = \@Dict { metadata, dataIndices, data, size }, key, value -> hashKey = @@ -305,13 +350,14 @@ insert = \@Dict { metadata, dataIndices, data, size }, key, value -> insertNotFoundHelper rehashedDict key value h1Key h2Key ## Remove a value from the dictionary for a specified key. -## -## expect -## Dict.empty {} -## |> Dict.insert "Some" "Value" -## |> Dict.remove "Some" -## |> Dict.len -## |> Bool.isEq 0 +## ``` +## expect +## Dict.empty {} +## |> Dict.insert "Some" "Value" +## |> Dict.remove "Some" +## |> Dict.len +## |> Bool.isEq 0 +## ``` remove : Dict k v, k -> Dict k v | k has Hash & Eq remove = \@Dict { metadata, dataIndices, data, size }, key -> # TODO: change this from swap remove to tombstone and test is performance is still good. @@ -345,16 +391,17 @@ remove = \@Dict { metadata, dataIndices, data, size }, key -> ## performance optimisation for the use case of providing a default when a value ## is missing. This is more efficient than doing both a `Dict.get` and then a ## `Dict.insert` call, and supports being piped. +## ``` +## alterValue : [Present Bool, Missing] -> [Present Bool, Missing] +## alterValue = \possibleValue -> +## when possibleValue is +## Missing -> Present Bool.false +## Present value -> if value then Missing else Present Bool.true ## -## alterValue : [Present Bool, Missing] -> [Present Bool, Missing] -## alterValue = \possibleValue -> -## when possibleValue is -## Missing -> Present Bool.false -## Present value -> if value then Missing else Present Bool.true -## -## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false -## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true -## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {} +## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false +## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true +## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {} +## ``` update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v | k has Hash & Eq update = \dict, key, alter -> # TODO: look into optimizing by merging substeps and reducing lookups. @@ -369,42 +416,45 @@ update = \dict, key, alter -> ## Returns the keys and values of a dictionary as a [List]. ## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead. -## -## expect -## Dict.single 1 "One" -## |> Dict.insert 2 "Two" -## |> Dict.insert 3 "Three" -## |> Dict.insert 4 "Four" -## |> Dict.toList -## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"] +## ``` +## expect +## Dict.single 1 "One" +## |> Dict.insert 2 "Two" +## |> Dict.insert 3 "Three" +## |> Dict.insert 4 "Four" +## |> Dict.toList +## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"] +## ``` toList : Dict k v -> List (T k v) | k has Hash & Eq toList = \@Dict { data } -> data ## Returns the keys of a dictionary as a [List]. ## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead. -## -## expect -## Dict.single 1 "One" -## |> Dict.insert 2 "Two" -## |> Dict.insert 3 "Three" -## |> Dict.insert 4 "Four" -## |> Dict.keys -## |> Bool.isEq [1,2,3,4] +## ``` +## expect +## Dict.single 1 "One" +## |> Dict.insert 2 "Two" +## |> Dict.insert 3 "Three" +## |> Dict.insert 4 "Four" +## |> Dict.keys +## |> Bool.isEq [1,2,3,4] +## ``` keys : Dict k v -> List k | k has Hash & Eq keys = \@Dict { data } -> List.map data (\T k _ -> k) ## Returns the values of a dictionary as a [List]. ## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead. -## -## expect -## Dict.single 1 "One" -## |> Dict.insert 2 "Two" -## |> Dict.insert 3 "Three" -## |> Dict.insert 4 "Four" -## |> Dict.values -## |> Bool.isEq ["One","Two","Three","Four"] +## ``` +## expect +## Dict.single 1 "One" +## |> Dict.insert 2 "Two" +## |> Dict.insert 3 "Three" +## |> Dict.insert 4 "Four" +## |> Dict.values +## |> Bool.isEq ["One","Two","Three","Four"] +## ``` values : Dict k v -> List v | k has Hash & Eq values = \@Dict { data } -> List.map data (\T _ v -> v) @@ -414,24 +464,25 @@ values = \@Dict { data } -> ## both dictionaries will be combined. Note that where there are pairs ## with the same key, the value contained in the second input will be ## retained, and the value in the first input will be removed. +## ``` +## first = +## Dict.single 1 "Not Me" +## |> Dict.insert 2 "And Me" ## -## first = -## Dict.single 1 "Not Me" -## |> Dict.insert 2 "And Me" +## second = +## Dict.single 1 "Keep Me" +## |> Dict.insert 3 "Me Too" +## |> Dict.insert 4 "And Also Me" ## -## second = -## Dict.single 1 "Keep Me" -## |> Dict.insert 3 "Me Too" -## |> Dict.insert 4 "And Also Me" +## expected = +## Dict.single 1 "Keep Me" +## |> Dict.insert 2 "And Me" +## |> Dict.insert 3 "Me Too" +## |> Dict.insert 4 "And Also Me" ## -## expected = -## Dict.single 1 "Keep Me" -## |> Dict.insert 2 "And Me" -## |> Dict.insert 3 "Me Too" -## |> Dict.insert 4 "And Also Me" -## -## expect -## Dict.insertAll first second == expected +## expect +## Dict.insertAll first second == expected +## ``` insertAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq insertAll = \xs, ys -> walk ys xs insert @@ -441,18 +492,19 @@ insertAll = \xs, ys -> ## that are in both dictionaries. Note that where there are pairs with ## the same key, the value contained in the first input will be retained, ## and the value in the second input will be removed. +## ``` +## first = +## Dict.single 1 "Keep Me" +## |> Dict.insert 2 "And Me" ## -## first = -## Dict.single 1 "Keep Me" -## |> Dict.insert 2 "And Me" +## second = +## Dict.single 1 "Keep Me" +## |> Dict.insert 2 "And Me" +## |> Dict.insert 3 "But Not Me" +## |> Dict.insert 4 "Or Me" ## -## second = -## Dict.single 1 "Keep Me" -## |> Dict.insert 2 "And Me" -## |> Dict.insert 3 "But Not Me" -## |> Dict.insert 4 "Or Me" -## -## expect Dict.keepShared first second == first +## expect Dict.keepShared first second == first +## ``` keepShared : Dict k v, Dict k v -> Dict k v | k has Hash & Eq keepShared = \xs, ys -> walk @@ -469,21 +521,22 @@ keepShared = \xs, ys -> ## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement) ## of the values. This means that we will be left with only those pairs that ## are in the first dictionary and whose keys are not in the second. +## ``` +## first = +## Dict.single 1 "Keep Me" +## |> Dict.insert 2 "And Me" +## |> Dict.insert 3 "Remove Me" ## -## first = -## Dict.single 1 "Keep Me" -## |> Dict.insert 2 "And Me" -## |> Dict.insert 3 "Remove Me" +## second = +## Dict.single 3 "Remove Me" +## |> Dict.insert 4 "I do nothing..." ## -## second = -## Dict.single 3 "Remove Me" -## |> Dict.insert 4 "I do nothing..." +## expected = +## Dict.single 1 "Keep Me" +## |> Dict.insert 2 "And Me" ## -## expected = -## Dict.single 1 "Keep Me" -## |> Dict.insert 2 "And Me" -## -## expect Dict.removeAll first second == expected +## expect Dict.removeAll first second == expected +## ``` removeAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq removeAll = \xs, ys -> walk ys xs (\state, k, _ -> remove state k) @@ -851,7 +904,7 @@ LowLevelHasher := { originalSeed : U64, state : U64 } has [ # TODO hide behind an InternalList.roc module listGetUnsafe : List a, Nat -> a -createLowLevelHasher : { seed ?U64 } -> LowLevelHasher +createLowLevelHasher : { seed ? U64 } -> LowLevelHasher createLowLevelHasher = \{ seed ? 0x526F_6352_616E_643F } -> @LowLevelHasher { originalSeed: seed, state: seed } @@ -1220,3 +1273,25 @@ expect |> complete hash1 != hash2 + +expect + empty {} + |> len + |> Bool.isEq 0 + +expect + empty {} + |> insert "One" "A Song" + |> insert "Two" "Candy Canes" + |> insert "Three" "Boughs of Holly" + |> clear + |> len + |> Bool.isEq 0 + +expect + Dict.empty {} + |> Dict.insert "Alice" 17 + |> Dict.insert "Bob" 18 + |> Dict.insert "Charlie" 19 + |> Dict.walkUntil Bool.false (\_, _, age -> if age >= 18 then Break Bool.true else Continue Bool.false) + |> Bool.isEq Bool.true diff --git a/crates/compiler/builtins/roc/Encode.roc b/crates/compiler/builtins/roc/Encode.roc index ec0d10ef0a..2ac900e3e7 100644 --- a/crates/compiler/builtins/roc/Encode.roc +++ b/crates/compiler/builtins/roc/Encode.roc @@ -22,6 +22,7 @@ interface Encode list, record, tag, + tuple, custom, appendWith, append, @@ -69,6 +70,7 @@ EncoderFormatting has string : Str -> Encoder fmt | fmt has EncoderFormatting list : List elem, (elem -> Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting record : List { key : Str, value : Encoder fmt } -> Encoder fmt | fmt has EncoderFormatting + tuple : List (Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting tag : Str, List (Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting custom : (List U8, fmt -> List U8) -> Encoder fmt | fmt has EncoderFormatting diff --git a/crates/compiler/builtins/roc/Hash.roc b/crates/compiler/builtins/roc/Hash.roc index 4e5845becb..caed5814c8 100644 --- a/crates/compiler/builtins/roc/Hash.roc +++ b/crates/compiler/builtins/roc/Hash.roc @@ -9,6 +9,7 @@ interface Hash addU32, addU64, addU128, + hashBool, hashI8, hashI16, hashI32, @@ -20,7 +21,7 @@ interface Hash hashList, hashUnordered, ] imports [ - Bool.{ isEq }, + Bool.{ Bool, isEq }, List, Str, Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Nat }, @@ -70,6 +71,12 @@ hashList = \hasher, lst -> List.walk lst hasher \accumHasher, elem -> hash accumHasher elem +## Adds a single [Bool] to a hasher. +hashBool : a, Bool -> a | a has Hasher +hashBool = \hasher, b -> + asU8 = if b then 1 else 0 + addU8 hasher asU8 + ## Adds a single I8 to a hasher. hashI8 : a, I8 -> a | a has Hasher hashI8 = \hasher, n -> addU8 hasher (Num.toU8 n) diff --git a/crates/compiler/builtins/roc/Json.roc b/crates/compiler/builtins/roc/Json.roc index aa01c37c1c..09f7df875c 100644 --- a/crates/compiler/builtins/roc/Json.roc +++ b/crates/compiler/builtins/roc/Json.roc @@ -1,3 +1,40 @@ +## JSON is a data format that is easy for humans to read and write. It is +## commonly used to exhange data between two systems such as a server and a +## client (e.g. web browser). +## +## This module implements functionality to serialise and de-serialise Roc types +## to and from JSON data. Using the `Encode` and `Decode` builtins this process +## can be achieved without the need to write custom encoder and decoder functions +## to parse UTF-8 strings. +## +## Here is a basic example which shows how to parse a JSON record into a Roc +## type named `Language` which includes a `name` field. The JSON string is +## decoded and then the field is encoded back into a UTF-8 string. +## +## ``` +## Language : { +## name : Str, +## } +## +## jsonStr = Str.toUtf8 "{\"name\":\"Röc Lang\"}" +## +## result : Result Language _ +## result = +## jsonStr +## |> Decode.fromBytes fromUtf8 # returns `Ok {name : "Röc Lang"}` +## +## name = +## decodedValue <- Result.map result +## +## Encode.toBytes decodedValue.name toUtf8 +## +## expect name == Ok (Str.toUtf8 "\"Röc Lang\"") +## ``` +## +## **Note:** This module is likely to be moved out of the builtins in future. +## It is currently located here to facilitate development of the Abilities +## language feature and testing. You are welcome to use this module, just note +## that it will be moved into a package in a future update. interface Json exposes [ Json, @@ -7,6 +44,7 @@ interface Json imports [ List, Str, + Result.{ Result }, Encode, Encode.{ Encoder, @@ -37,6 +75,8 @@ interface Json Result, ] +## An opaque type with the `EncoderFormatting` and +## `DecoderFormatting` abilities. Json := {} has [ EncoderFormatting { u8: encodeU8, @@ -56,6 +96,7 @@ Json := {} has [ string: encodeString, list: encodeList, record: encodeRecord, + tuple: encodeTuple, tag: encodeTag, }, DecoderFormatting { @@ -76,11 +117,14 @@ Json := {} has [ string: decodeString, list: decodeList, record: decodeRecord, + tuple: decodeTuple, }, ] +## Returns a JSON `Decoder` toUtf8 = @Json {} +## Returns a JSON `Encoder` fromUtf8 = @Json {} numToBytes = \n -> @@ -165,6 +209,25 @@ encodeRecord = \fields -> List.append bytesWithRecord (Num.toU8 '}') +encodeTuple = \elems -> + Encode.custom \bytes, @Json {} -> + writeTuple = \{ buffer, elemsLeft }, elemEncoder -> + bufferWithElem = + appendWith buffer elemEncoder (@Json {}) + + bufferWithSuffix = + if elemsLeft > 1 then + List.append bufferWithElem (Num.toU8 ',') + else + bufferWithElem + + { buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 } + + bytesHead = List.append bytes (Num.toU8 '[') + { buffer: bytesWithRecord } = List.walk elems { buffer: bytesHead, elemsLeft: List.len elems } writeTuple + + List.append bytesWithRecord (Num.toU8 ']') + encodeTag = \name, payload -> Encode.custom \bytes, @Json {} -> # Idea: encode `A v1 v2` as `{"A": [v1, v2]}` @@ -191,16 +254,42 @@ encodeTag = \name, payload -> List.append bytesWithPayload (Num.toU8 ']') |> List.append (Num.toU8 '}') +isEscapeSequence : U8, U8 -> Bool +isEscapeSequence = \a, b -> + when P a b is + P '\\' 'b' -> Bool.true # Backspace + P '\\' 'f' -> Bool.true # Form feed + P '\\' 'n' -> Bool.true # Newline + P '\\' 'r' -> Bool.true # Carriage return + P '\\' 't' -> Bool.true # Tab + P '\\' '"' -> Bool.true # Double quote + P '\\' '\\' -> Bool.true # Backslash + _ -> Bool.false + takeWhile = \list, predicate -> helper = \{ taken, rest } -> - when List.first rest is - Ok elem -> - if predicate elem then - helper { taken: List.append taken elem, rest: List.split rest 1 |> .others } + when rest is + [a, b, ..] -> + if isEscapeSequence a b then + helper { + taken: taken |> List.append a |> List.append b, + rest: List.drop rest 2, + } + else if predicate a then + helper { + taken: List.append taken a, + rest: List.dropFirst rest, + } else { taken, rest } - Err _ -> { taken, rest } + [a, ..] if predicate a -> + helper { + taken: List.append taken a, + rest: List.dropFirst rest, + } + + _ -> { taken, rest } helper { taken: [], rest: list } @@ -341,7 +430,6 @@ jsonString = \bytes -> if before == ['"'] then - # TODO: handle escape sequences { taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != '"' when Str.fromUtf8 strSequence is @@ -358,42 +446,38 @@ decodeString = Decode.custom \bytes, @Json {} -> jsonString bytes decodeList = \decodeElem -> Decode.custom \bytes, @Json {} -> + decodeElems = \chunk, accum -> when Decode.decodeWith chunk decodeElem (@Json {}) is { result, rest } -> when result is - Ok val -> - # TODO: handle spaces before ',' - { before: afterElem, others } = List.split rest 1 - - if - afterElem == [','] - then - decodeElems others (List.append accum val) - else - Done (List.append accum val) rest - Err e -> Errored e rest + Ok val -> + restWithoutWhitespace = eatWhitespace rest + when restWithoutWhitespace is + [',', ..] -> decodeElems (eatWhitespace (List.dropFirst restWithoutWhitespace)) (List.append accum val) + _ -> Done (List.append accum val) restWithoutWhitespace - { before, others: afterStartingBrace } = List.split bytes 1 + when bytes is + ['[', ']'] -> { result: Ok [], rest: List.drop bytes 2 } + ['[', ..] -> + bytesWithoutWhitespace = eatWhitespace (List.dropFirst bytes) + when bytesWithoutWhitespace is + [']', ..] -> + { result: Ok [], rest: List.dropFirst bytesWithoutWhitespace } - if - before == ['['] - then - # TODO: empty lists - when decodeElems afterStartingBrace [] is - Errored e rest -> { result: Err e, rest } - Done vals rest -> - { before: maybeEndingBrace, others: afterEndingBrace } = List.split rest 1 + _ -> + when decodeElems bytesWithoutWhitespace [] is + Errored e rest -> + { result: Err e, rest } - if - maybeEndingBrace == [']'] - then - { result: Ok vals, rest: afterEndingBrace } - else - { result: Err TooShort, rest } - else - { result: Err TooShort, rest: bytes } + Done vals rest -> + when rest is + [']', ..] -> { result: Ok vals, rest: List.dropFirst rest } + _ -> { result: Err TooShort, rest } + + _ -> + { result: Err TooShort, rest: bytes } parseExactChar : List U8, U8 -> DecodeResult {} parseExactChar = \bytes, char -> @@ -414,6 +498,12 @@ openBrace = \bytes -> parseExactChar bytes '{' closingBrace : List U8 -> DecodeResult {} closingBrace = \bytes -> parseExactChar bytes '}' +openBracket : List U8 -> DecodeResult {} +openBracket = \bytes -> parseExactChar bytes '[' + +closingBracket : List U8 -> DecodeResult {} +closingBracket = \bytes -> parseExactChar bytes ']' + recordKey : List U8 -> DecodeResult Str recordKey = \bytes -> jsonString bytes @@ -463,3 +553,92 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso when finalizer endStateResult is Ok val -> { result: Ok val, rest: afterRecordBytes } Err e -> { result: Err e, rest: afterRecordBytes } + +decodeTuple = \initialState, stepElem, finalizer -> Decode.custom \initialBytes, @Json {} -> + # NB: the stepper function must be passed explicitly until #2894 is resolved. + decodeElems = \stepper, state, index, bytes -> + { val: newState, rest: beforeCommaOrBreak } <- tryDecode + ( + when stepper state index is + TooLong -> + { rest: beforeCommaOrBreak } <- bytes |> anything |> tryDecode + { result: Ok state, rest: beforeCommaOrBreak } + + Next decoder -> + Decode.decodeWith bytes decoder (@Json {}) + ) + + { result: commaResult, rest: nextBytes } = comma beforeCommaOrBreak + + when commaResult is + Ok {} -> decodeElems stepElem newState (index + 1) nextBytes + Err _ -> { result: Ok newState, rest: nextBytes } + + { rest: afterBracketBytes } <- initialBytes |> openBracket |> tryDecode + + { val: endStateResult, rest: beforeClosingBracketBytes } <- decodeElems stepElem initialState 0 afterBracketBytes |> tryDecode + + { rest: afterTupleBytes } <- beforeClosingBracketBytes |> closingBracket |> tryDecode + + when finalizer endStateResult is + Ok val -> { result: Ok val, rest: afterTupleBytes } + Err e -> { result: Err e, rest: afterTupleBytes } + +# Helper to eat leading Json whitespace characters +eatWhitespace = \input -> + when input is + [' ', ..] -> eatWhitespace (List.dropFirst input) + ['\n', ..] -> eatWhitespace (List.dropFirst input) + ['\r', ..] -> eatWhitespace (List.dropFirst input) + ['\t', ..] -> eatWhitespace (List.dropFirst input) + _ -> input + +# Test eating Json whitespace +expect + input = Str.toUtf8 " \n\r\tabc" + actual = eatWhitespace input + expected = Str.toUtf8 "abc" + + actual == expected + +# Test json string decoding with escapes +expect + input = Str.toUtf8 "\"a\r\nbc\\\"xz\"" + expected = Ok "a\r\nbc\\\"xz" + actual = Decode.fromBytes input fromUtf8 + + actual == expected + +# Test json string encoding with escapes +expect + input = "a\r\nbc\\\"xz" + expected = Str.toUtf8 "\"a\r\nbc\\\"xz\"" + actual = Encode.toBytes input toUtf8 + + actual == expected + +# Test json array decode empty list +expect + input = Str.toUtf8 "[ ]" + actual : Result (List U8) _ + actual = Decode.fromBytes input fromUtf8 + expected = Ok [] + + actual == expected + +# Test json array decoding into integers +expect + input = Str.toUtf8 "[ 1,\n2,\t3]" + actual : Result (List U8) _ + actual = Decode.fromBytes input fromUtf8 + expected = Ok [1, 2, 3] + + actual == expected + +# Test json array decoding into strings ignoring whitespace around values +expect + input = Str.toUtf8 "[\r\"one\" ,\t\"two\"\n,\n\"3\"\t]" + actual = Decode.fromBytes input fromUtf8 + expected = Ok ["one", "two", "3"] + + actual == expected diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc index 0090457e7b..a33206a398 100644 --- a/crates/compiler/builtins/roc/List.roc +++ b/crates/compiler/builtins/roc/List.roc @@ -62,6 +62,7 @@ interface List sortAsc, sortDesc, reserve, + releaseExcessCapacity, walkBackwardsUntil, countIf, ] @@ -73,11 +74,11 @@ interface List ## 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 -## +## ``` +## [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) @@ -105,11 +106,11 @@ interface List ## will be immediately freed. ## ## Let's look at an example. +## ``` +## ratings = [5, 4, 3] ## -## ratings = [5, 4, 3] -## -## { foo: ratings, bar: ratings } -## +## { 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. ## @@ -118,14 +119,14 @@ interface List ## refcount getting incremented from 1 to 3. ## ## Let's turn this example into a function. +## ``` +## getRatings = \first -> +## ratings = [first, 4, 3] ## -## getRatings = \first -> -## ratings = [first, 4, 3] -## -## { foo: ratings, bar: ratings } -## -## getRatings 5 +## { 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 @@ -140,23 +141,23 @@ interface List ## list, and that list has a refcount of 2. ## ## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`: +## ``` +## getRatings = \first -> +## ratings = [first, 4, 3] ## -## getRatings = \first -> -## ratings = [first, 4, 3] -## -## { foo: ratings, bar: ratings } -## -## (getRatings 5).bar +## { 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 -## +## ``` +## 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 @@ -167,26 +168,26 @@ interface List ## 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] ## -## nums = [1, 2, 3, 4, 5, 6, 7] -## -## first = List.first nums -## last = List.last nums -## -## first +## 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]] ## -## lists = [[1], [2, 3], [], [4, 5, 6, 7]] +## first = List.first lists +## last = List.last lists ## -## 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, +## 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 @@ -206,10 +207,11 @@ interface List ## * 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 [1, 2, 3] -## -## >>> List.isEmpty [] +## List.isEmpty [] +## ``` isEmpty : List a -> Bool isEmpty = \list -> List.len list == 0 @@ -237,9 +239,9 @@ replace = \list, index, newValue -> { list, value: newValue } ## Replaces the element at the given index with a replacement. -## -## >>> List.set ["a", "b", "c"] 1 "B" -## +## ``` +## List.set ["a", "b", "c"] 1 "B" +## ``` ## If the given index is outside the bounds of the list, returns the original ## list unmodified. ## @@ -249,11 +251,12 @@ 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 ## -## >>> List.append [1, 2, 3] 4 -## -## >>> [0, 1, 2] -## >>> |> List.append 3 +## [0, 1, 2] +## |> List.append 3 +## ``` append : List a, a -> List a append = \list, element -> list @@ -268,11 +271,12 @@ append = \list, element -> appendUnsafe : List a, a -> List a ## Add a single element to the beginning of a list. +## ``` +## List.prepend [1, 2, 3] 0 ## -## >>> List.prepend [1, 2, 3] 0 -## -## >>> [2, 3, 4] -## >>> |> List.prepend 1 +## [2, 3, 4] +## |> List.prepend 1 +## ``` prepend : List a, a -> List a ## Returns the length of the list - the number of elements it contains. @@ -288,12 +292,17 @@ withCapacity : Nat -> List a ## Enlarge the list for at least capacity additional elements reserve : List a, Nat -> List a +## Shrink the memory footprint of a list such that it's capacity and length are equal. +## Note: This will also convert seamless slices to regular lists. +releaseExcessCapacity : List a -> List a + ## Put two lists together. +## ``` +## List.concat [1, 2, 3] [4, 5] ## -## >>> List.concat [1, 2, 3] [4, 5] -## -## >>> [0, 1, 2] -## >>> |> List.concat [3, 4] +## [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. @@ -306,17 +315,15 @@ last = \list -> ## A list with a single element in it. ## ## This is useful in pipelines, like so: -## -## websites = -## Str.concat domain ".com" -## |> List.single -## +## ``` +## websites = +## Str.concat domain ".com" +## |> List.single +## ``` single : a -> List a single = \x -> [x] ## Returns a list with the given length, where every element is the given value. -## -## repeat : a, Nat -> List a repeat = \value, count -> repeatHelp value count (List.withCapacity count) @@ -329,8 +336,9 @@ repeatHelp = \value, count, accum -> accum ## Returns the list with its elements reversed. -## -## >>> List.reverse [1, 2, 3] +## ``` +## List.reverse [1, 2, 3] +## ``` reverse : List a -> List a reverse = \list -> reverseHelp list 0 (Num.subSaturated (List.len list) 1) @@ -342,12 +350,11 @@ reverseHelp = \list, left, right -> list ## Join the given lists together into one list. -## -## >>> List.join [[1, 2, 3], [4, 5], [], [6, 7]] -## -## >>> List.join [[], []] -## -## >>> List.join [] +## ``` +## List.join [[1, 2, 3], [4, 5], [], [6, 7]] +## List.join [[], []] +## List.join [] +## ``` join : List (List a) -> List a join = \lists -> totalLength = @@ -366,10 +373,10 @@ contains = \list, needle -> ## which updates the `state`. It returns the final `state` at the end. ## ## You can use it in a pipeline: -## -## [2, 4, 8] -## |> List.walk 0 Num.add -## +## ``` +## [2, 4, 8] +## |> List.walk 0 Num.add +## ``` ## This returns 14 because: ## * `state` starts at 0 ## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`. @@ -385,10 +392,10 @@ contains = \list, needle -> ## 6 | 8 | 14 ## ## The following returns -6: -## -## [1, 2, 3] -## |> List.walk 0 Num.sub -## +## ``` +## [1, 2, 3] +## |> List.walk 0 Num.sub +## ``` ## Note that in other languages, `walk` is sometimes called `reduce`, ## `fold`, `foldLeft`, or `foldl`. walk : List elem, state, (state, elem -> state) -> state @@ -494,9 +501,9 @@ all = \list, predicate -> ## Run the given function on each element of a list, and return all the ## elements for which the function returned `Bool.true`. -## -## >>> List.keepIf [1, 2, 3, 4] (\num -> num > 2) -## +## ``` +## List.keepIf [1, 2, 3, 4] (\num -> num > 2) +## ``` ## ## Performance Details ## ## [List.keepIf] always returns a list that takes up exactly the same amount @@ -531,9 +538,9 @@ keepIfHelp = \list, predicate, kept, index, length -> ## Run the given function on each element of a list, and return all the ## elements for which the function returned `Bool.false`. -## -## >>> List.dropIf [1, 2, 3, 4] (\num -> num > 2) -## +## ``` +## List.dropIf [1, 2, 3, 4] (\num -> num > 2) +## ``` ## ## Performance Details ## ## `List.dropIf` has the same performance characteristics as [List.keepIf]. @@ -556,12 +563,13 @@ countIf = \list, predicate -> ## 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 ## -## >>> List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last +## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) ## -## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) -## >>> -## >>> List.keepOks ["", "a", "bc", "", "d", "ef", ""] +## List.keepOks ["", "a", "bc", "", "d", "ef", ""] +## ``` keepOks : List before, (before -> Result after *) -> List after keepOks = \list, toResult -> walker = \accum, element -> @@ -573,12 +581,13 @@ keepOks = \list, toResult -> ## 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 ## -## >>> List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last +## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) ## -## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) -## >>> -## >>> List.keepErrs ["", "a", "bc", "", "d", "ef", ""] +## List.keepErrs ["", "a", "bc", "", "d", "ef", ""] +## ``` keepErrs : List before, (before -> Result * after) -> List after keepErrs = \list, toResult -> walker = \accum, element -> @@ -590,10 +599,11 @@ keepErrs = \list, toResult -> ## 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 [1, 2, 3] (\num -> num + 1) -## -## > List.map ["", "a", "bc"] Str.isEmpty +## List.map ["", "a", "bc"] Str.isEmpty +## ``` map : List a, (a -> b) -> List b ## Run a transformation function on the first element of each list, @@ -602,8 +612,9 @@ map : List a, (a -> b) -> List b ## ## 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 +## ``` +## 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, @@ -640,92 +651,110 @@ mapWithIndexHelp = \src, dest, func, index, length -> ## Returns a list of all the integers between `start` and `end`. ## ## To include the `start` and `end` integers themselves, use `At` like so: -## -## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5] -## +## ``` +## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5] +## ``` ## To exclude them, use `After` and `Before`, like so: -## -## List.range { start: After 2, end: Before 5 } # returns [3, 4] -## +## ``` +## List.range { start: After 2, end: Before 5 } # returns [3, 4] +## ``` ## You can have the list end at a certain length rather than a certain integer: -## -## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9] -## +## ``` +## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9] +## ``` ## If `step` is specified, each integer increases by that much. (`step: 1` is the default.) -## -## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6] -## +## ``` +## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6] +## ``` +## List.range will also generate a reversed list if step is negative or end comes before start: +## ``` +## List.range { start: At 5, end: At 2 } # returns [5, 4, 3, 2] +## ``` ## All of these options are compatible with the others. For example, you can use `At` or `After` ## with `start` regardless of what `end` and `step` are set to. range : _ range = \{ start, end, step ? 0 } -> - { incByStep, stepIsPositive } = + { calcNext, stepIsPositive } = if step == 0 then when T start end is T (At x) (At y) | T (At x) (Before y) | T (After x) (At y) | T (After x) (Before y) -> if x < y then { - incByStep: \i -> i + 1, + calcNext: \i -> Num.addChecked i 1, stepIsPositive: Bool.true, } else { - incByStep: \i -> i - 1, + calcNext: \i -> Num.subChecked i 1, stepIsPositive: Bool.false, } T (At _) (Length _) | T (After _) (Length _) -> { - incByStep: \i -> i + 1, + calcNext: \i -> Num.addChecked i 1, stepIsPositive: Bool.true, } else { - incByStep: \i -> i + step, + calcNext: \i -> Num.addChecked i step, stepIsPositive: step > 0, } inclusiveStart = when start is - At x -> x - After x -> incByStep x + At x -> Ok x + After x -> calcNext x when end is At at -> - isComplete = + isValid = if stepIsPositive then - \i -> i > at + \i -> i <= at else - \i -> i < at + \i -> i >= at # TODO: switch to List.withCapacity - rangeHelp [] inclusiveStart incByStep isComplete + rangeHelp [] inclusiveStart calcNext isValid Before before -> - isComplete = + isValid = if stepIsPositive then - \i -> i >= before + \i -> i < before else - \i -> i <= before + \i -> i > before # TODO: switch to List.withCapacity - rangeHelp [] inclusiveStart incByStep isComplete + rangeHelp [] inclusiveStart calcNext isValid Length l -> - rangeLengthHelp (List.withCapacity l) inclusiveStart l incByStep + rangeLengthHelp (List.withCapacity l) inclusiveStart l calcNext -rangeHelp = \accum, i, incByStep, isComplete -> - if isComplete i then - accum - else - # TODO: change this to List.appendUnsafe once capacity is set correctly - rangeHelp (List.append accum i) (incByStep i) incByStep isComplete +rangeHelp = \accum, i, calcNext, isValid -> + when i is + Ok val -> + if isValid val then + # TODO: change this to List.appendUnsafe once capacity is set correctly + rangeHelp (List.append accum val) (calcNext val) calcNext isValid + else + accum -rangeLengthHelp = \accum, i, remaining, incByStep -> + Err _ -> + # We went past the end of the numeric range and there is no next. + # return the generated list. + accum + +rangeLengthHelp = \accum, i, remaining, calcNext -> if remaining == 0 then accum else - rangeLengthHelp (List.appendUnsafe accum i) (incByStep i) (remaining - 1) incByStep + when i is + Ok val -> + rangeLengthHelp (List.appendUnsafe accum val) (calcNext val) (remaining - 1) calcNext + + Err _ -> + # We went past the end of the numeric range and there is no next. + # The list is not the correct length yet, so we must crash. + crash "List.range: failed to generate enough elements to fill the range before overflowing the numeric type" expect List.range { start: At 0, end: At 4 } == [0, 1, 2, 3, 4] @@ -754,6 +783,18 @@ expect expect List.range { start: At 4, end: Length 5, step: -3 } == [4, 1, -2, -5, -8] +expect + List.range { start: After 250u8, end: At 255 } == [251, 252, 253, 254, 255] + +expect + List.range { start: After 250u8, end: At 255, step: 10 } == [] + +expect + List.range { start: After 250u8, end: At 245, step: 10 } == [] + +expect + List.range { start: At 4, end: At 0 } == [4, 3, 2, 1, 0] + ## Sort with a custom comparison function sortWith : List a, (a, a -> [LT, EQ, GT]) -> List a @@ -795,14 +836,14 @@ dropLast = \list -> List.dropAt list (Num.subSaturated (List.len list) 1) ## Returns the given number of elements from the beginning of the list. -## -## >>> List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4 -## +## ``` +## List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4 +## ``` ## If there are fewer elements in the list than the requested number, ## returns the entire list. -## -## >>> List.takeFirst [1, 2] 5 -## +## ``` +## List.takeFirst [1, 2] 5 +## ``` ## To *remove* elements from the beginning of the list, use `List.takeLast`. ## ## To remove elements from both the beginning and end of the list, @@ -810,28 +851,19 @@ dropLast = \list -> ## ## 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 list 1` 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 takeFirst = \list, outputLength -> List.sublist list { start: 0, len: outputLength } ## Returns the given number of elements from the end of the list. -## -## >>> List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4 -## +## ``` +## List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4 +## ``` ## If there are fewer elements in the list than the requested number, ## returns the entire list. -## -## >>> List.takeLast [1, 2] 5 -## +## ``` +## List.takeLast [1, 2] 5 +## ``` ## To *remove* elements from the end of the list, use `List.takeFirst`. ## ## To remove elements from both the beginning and end of the list, @@ -839,16 +871,6 @@ takeFirst = \list, outputLength -> ## ## 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 list 1` 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 takeLast = \list, outputLength -> List.sublist list { start: Num.subSaturated (List.len list) outputLength, len: outputLength } @@ -971,13 +993,13 @@ findLastIndex = \list, matches -> ## including a total of `len` elements. ## ## If `start` is outside the bounds of the given list, returns the empty list. -## -## >>> List.sublist [1, 2, 3] { start: 4, len: 0 } -## +## ``` +## List.sublist [1, 2, 3] { start: 4, len: 0 } +## ``` ## If more elements are requested than exist in the list, returns as many as it can. -## -## >>> List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 } -## +## ``` +## List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 } +## ``` ## > 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. ## @@ -993,7 +1015,9 @@ sublist = \list, config -> sublistLowlevel : List elem, Nat, Nat -> List elem ## Intersperses `sep` between the elements of `list` -## >>> List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3] +## ``` +## List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3] +## ``` intersperse : List elem, elem -> List elem intersperse = \list, sep -> capacity = 2 * List.len list @@ -1051,8 +1075,9 @@ split = \elements, userSplitIndex -> ## Returns the elements before the first occurrence of a delimiter, as well as the ## remaining elements after that occurrence. If the delimiter is not found, returns `Err`. -## -## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] } +## ``` +## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Z, Baz] } +## ``` splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq splitFirst = \list, delimiter -> when List.findFirstIndex list (\elem -> elem == delimiter) is @@ -1066,8 +1091,9 @@ splitFirst = \list, delimiter -> ## Returns the elements before the last occurrence of a delimiter, as well as the ## remaining elements after that occurrence. If the delimiter is not found, returns `Err`. -## -## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] } +## ``` +## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Z, Bar], after: [Baz] } +## ``` splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq splitLast = \list, delimiter -> when List.findLastIndex list (\elem -> elem == delimiter) is diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index eef9cf4741..cc616af3d6 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -68,6 +68,9 @@ interface Num compare, pow, powInt, + countLeadingZeroBits, + countTrailingZeroBits, + countOneBits, addWrap, addChecked, addSaturated, @@ -86,6 +89,8 @@ interface Num intCast, bytesToU16, bytesToU32, + bytesToU64, + bytesToU128, divCeil, divCeilChecked, divTrunc, @@ -151,9 +156,9 @@ interface Num ## 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 -## +## ``` +## 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 *)`. ## @@ -191,9 +196,9 @@ interface Num ## ## 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 -## +## ``` +## 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: @@ -234,7 +239,7 @@ Num range := range ## ## This pattern continues up to [U128] and [I128]. ## -## ## Performance notes +## ## Performance Details ## ## 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 @@ -259,15 +264,11 @@ Num range := range ## ## 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 -## +## ``` +## 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. @@ -342,16 +343,16 @@ Int range : Num (Integer range) ## ## 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 -## +## ``` +## wasItPrecise = 0.1 + 0.2 == 0.3 +## ``` ## The value of `wasItPrecise` here will be `Bool.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 -## +## ``` +## wasItPrecise = 0.1f64 + 0.2 == 0.3 +## ``` ## Here, `wasItPrecise` will be `Bool.false` because the entire calculation will have ## been done in a base-2 floating point calculation, which causes noticeable ## precision loss in this case. @@ -380,7 +381,7 @@ Int range : Num (Integer range) ## Whenever a function in this module could return one of these values, that ## possibility is noted in the function's documentation. ## -## ## Performance Notes +## ## Performance Details ## ## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32] ## for addition and subtraction. For example, [F32] and [F64] do addition using @@ -480,7 +481,7 @@ F32 : Num (FloatingPoint Binary32) ## 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 +## ## Performance Details ## ## [Dec] typically takes slightly less time than [F64] to perform addition and ## subtraction, but 10-20 times longer to perform multiplication and division. @@ -492,15 +493,14 @@ Dec : Num (FloatingPoint Decimal) ## ## This is the same as calling `Num.format {}` - so for more details on ## exact formatting, see `Num.format`. -## -## >>> Num.toStr 42 -## +## ``` +## 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 -## +## ``` +## 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 `"-∞"`. ## @@ -510,6 +510,8 @@ intCast : Int a -> Int b bytesToU16Lowlevel : List U8, Nat -> U16 bytesToU32Lowlevel : List U8, Nat -> U32 +bytesToU64Lowlevel : List U8, Nat -> U64 +bytesToU128Lowlevel : List U8, Nat -> U128 bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds] bytesToU16 = \bytes, index -> @@ -531,6 +533,26 @@ bytesToU32 = \bytes, index -> else Err OutOfBounds +bytesToU64 : List U8, Nat -> Result U64 [OutOfBounds] +bytesToU64 = \bytes, index -> + # we need at least 7 more bytes + offset = 7 + + if index + offset < List.len bytes then + Ok (bytesToU64Lowlevel bytes index) + else + Err OutOfBounds + +bytesToU128 : List U8, Nat -> Result U128 [OutOfBounds] +bytesToU128 = \bytes, index -> + # we need at least 15 more bytes + offset = 15 + + if index + offset < List.len bytes then + Ok (bytesToU128Lowlevel bytes index) + else + Err OutOfBounds + compare : Num a, Num a -> [LT, EQ, GT] ## Returns `Bool.true` if the first number is less than the second. @@ -539,9 +561,10 @@ compare : Num a, Num a -> [LT, EQ, GT] ## ## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN* ## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -## -## >>> 5 -## >>> |> Num.isLt 6 +## ``` +## 5 +## |> Num.isLt 6 +## ``` isLt : Num a, Num a -> Bool ## Returns `Bool.true` if the first number is greater than the second. @@ -550,9 +573,10 @@ isLt : Num a, Num a -> Bool ## ## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN* ## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -## -## >>> 6 -## >>> |> Num.isGt 5 +## ``` +## 6 +## |> Num.isGt 5 +## ``` isGt : Num a, Num a -> Bool ## Returns `Bool.true` if the first number is less than or equal to the second. @@ -601,15 +625,15 @@ toFrac : Num * -> Frac * ## * 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 4 +## Num.abs -2.5 ## -## >>> Num.abs -2.5 -## -## >>> Num.abs 0 -## -## >>> Num.abs 0.0 +## 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. @@ -620,15 +644,15 @@ toFrac : Num * -> Frac * abs : Num a -> Num a ## Return a negative number when given a positive one, and vice versa. +## ``` +## Num.neg 5 ## -## >>> Num.neg 5 +## Num.neg -2.5 ## -## >>> Num.neg -2.5 -## -## >>> Num.neg 0 -## -## >>> Num.neg 0.0 +## 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. @@ -645,16 +669,16 @@ neg : Num a -> Num a ## (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 ## -## >>> 5 + 7 -## -## >>> Num.add 5 7 -## +## Num.add 5 7 +## ``` ## `Num.add` can be convenient in pipelines. -## -## >>> Frac.pi -## >>> |> Num.add 1.0 -## +## ``` +## 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 @@ -666,16 +690,16 @@ add : Num a, Num a -> Num a ## (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 ## -## >>> 7 - 5 -## -## >>> Num.sub 7 5 -## +## Num.sub 7 5 +## ``` ## `Num.sub` can be convenient in pipelines. -## -## >>> Frac.pi -## >>> |> Num.sub 2.0 -## +## ``` +## 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 @@ -687,16 +711,18 @@ sub : Num a, Num a -> Num a ## (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 ## -## >>> 5 * 7 -## -## >>> Num.mul 5 7 +## Num.mul 5 7 +## ``` ## ## `Num.mul` can be convenient in pipelines. ## -## >>> Frac.pi -## >>> |> Num.mul 2.0 -## +## ``` +## 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 @@ -731,14 +757,15 @@ atan : Frac a -> Frac a ## > 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 4.0 +## Num.sqrt 1.5 ## -## >>> Num.sqrt 1.5 +## Num.sqrt 0.0 ## -## >>> Num.sqrt 0.0 -## -## >>> Num.sqrt -4.0f64 +## Num.sqrt -4.0f64 +## ``` sqrt : Frac a -> Frac a sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative] @@ -748,6 +775,7 @@ sqrtChecked = \x -> else Ok (Num.sqrt x) +## Natural logarithm log : Frac a -> Frac a logChecked : Frac a -> Result (Frac a) [LogNeedsPositive] @@ -778,15 +806,16 @@ logChecked = \x -> ## ## 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 ## -## >>> 5.0 / 7.0 -## -## >>> Num.div 5 7 -## +## Num.div 5 7 +## ``` ## `Num.div` can be convenient in pipelines. -## -## >>> Num.pi -## >>> |> Num.div 2.0 +## ``` +## Num.pi +## |> Num.div 2.0 +## ``` div : Frac a, Frac a -> Frac a divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero] @@ -812,15 +841,15 @@ divCeilChecked = \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 ## -## >>> 5 // 7 +## Num.divTrunc 5 7 ## -## >>> Num.divTrunc 5 7 -## -## >>> 8 // -3 -## -## >>> Num.divTrunc 8 -3 +## 8 // -3 ## +## Num.divTrunc 8 -3 +## ``` divTrunc : Int a, Int a -> Int a divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero] @@ -833,14 +862,15 @@ divTruncChecked = \a, b -> ## Obtain the remainder (truncating modulo) from the division of two integers. ## ## `a % b` is shorthand for `Num.rem a b`. +## ``` +## 5 % 7 ## -## >>> 5 % 7 +## Num.rem 5 7 ## -## >>> Num.rem 5 7 +## -8 % -3 ## -## >>> -8 % -3 -## -## >>> Num.rem -8 -3 +## Num.rem -8 -3 +## ``` rem : Int a, Int a -> Int a remChecked : Int a, Int a -> Result (Int a) [DivByZero] @@ -860,24 +890,24 @@ bitwiseOr : Int a, Int a -> Int a ## ## The least significant bits always become 0. This means that shifting left is ## like multiplying by factors of two for unsigned integers. +## ``` +## shiftLeftBy 0b0000_0011 2 == 0b0000_1100 ## -## >>> shiftLeftBy 0b0000_0011 2 == 0b0000_1100 -## -## >>> 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100 -## +## 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100 +## ``` ## In some languages `shiftLeftBy` is implemented as a binary operator `<<`. shiftLeftBy : Int a, U8 -> Int a ## Bitwise arithmetic shift of a number by another ## ## The most significant bits are copied from the current. +## ``` +## shiftRightBy 0b0000_0011 2 == 0b0000_1100 ## -## >>> shiftRightBy 0b0000_0011 2 == 0b0000_1100 -## -## >>> 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101 -## -## >>> 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100 +## 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101 ## +## 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100 +## ``` ## In some languages `shiftRightBy` is implemented as a binary operator `>>>`. shiftRightBy : Int a, U8 -> Int a @@ -885,13 +915,13 @@ shiftRightBy : Int a, U8 -> Int a ## ## The most significant bits always become 0. This means that shifting left is ## like dividing by factors of two for unsigned integers. +## ``` +## shiftRightBy 0b0010_1000 2 == 0b0000_1010 ## -## >>> shiftRightBy 0b0010_1000 2 == 0b0000_1010 -## -## >>> 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010 -## -## >>> 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100 +## 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010 ## +## 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100 +## ``` ## In some languages `shiftRightBy` is implemented as a binary operator `>>`. shiftRightZfBy : Int a, U8 -> Int a @@ -912,21 +942,60 @@ pow : Frac a, Frac a -> Frac a ## ## For a [Frac] alternative to this function, which supports negative exponents, ## see #Num.exp. +## ``` +## Num.exp 5 0 ## -## >>> Num.exp 5 0 +## Num.exp 5 1 ## -## >>> Num.exp 5 1 +## Num.exp 5 2 ## -## >>> Num.exp 5 2 -## -## >>> Num.exp 5 6 -## -## ## Performance Notes +## Num.exp 5 6 +## ``` +## ## Performance Details ## ## 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 +## Counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer. +## +## ``` +## Num.countLeadingZeroBits 0b0001_1100u8 +## +## 3 +## +## Num.countLeadingZeroBits 0b0000_0000u8 +## +## 8 +## ``` +countLeadingZeroBits : Int a -> Nat + +## Counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer. +## +## ``` +## Num.countTrailingZeroBits 0b0001_1100u8 +## +## 2 +## +## Num.countTrailingZeroBits 0b0000_0000u8 +## +## 8 +## ``` +countTrailingZeroBits : Int a -> Nat + +## Counts the number of set bits in an integer. +## +## ``` +## Num.countOneBits 0b0001_1100u8 +## +## 3 +## +## Num.countOneBits 0b0000_0000u8 +## +## 0 +## ``` +countOneBits : Int a -> Nat + addWrap : Int range, Int range -> Int range ## Add two numbers, clamping on the maximum representable number rather than @@ -1261,164 +1330,3 @@ 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 `Bool.false` if that value is -## [*NaN*](Num.isNaN), ∞ or -∞, and `Bool.true` otherwise. -## -## Always returns `Bool.true` when given a [Dec]. -## -## This is the opposite of #isInfinite, except when given [*NaN*](Num.isNaN). Both -## #isFinite and #isInfinite return `Bool.false` for [*NaN*](Num.isNaN). -# isFinite : Frac * -> Bool -## When given a [F64] or [F32] value, returns `Bool.true` if that value is either -## ∞ or -∞, and `Bool.false` otherwise. -## -## Always returns `Bool.false` when given a [Dec]. -## -## This is the opposite of #isFinite, except when given [*NaN*](Num.isNaN). Both -## #isFinite and #isInfinite return `Bool.false` for [*NaN*](Num.isNaN). -# isInfinite : Frac * -> Bool -## When given a [F64] or [F32] value, returns `Bool.true` if that value is -## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `Bool.false` otherwise. -## -## Always returns `Bool.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 `Bool.false` if either argument is *NaN*. -## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `Bool.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.insert] and [Dict.insert] for details. -# isNaN : Frac * -> Bool -## Returns the higher of two numbers. -## -## If either argument is [*NaN*](Num.isNaN), returns `Bool.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 `Bool.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/crates/compiler/builtins/roc/Result.roc b/crates/compiler/builtins/roc/Result.roc index 967c901567..596a3e78da 100644 --- a/crates/compiler/builtins/roc/Result.roc +++ b/crates/compiler/builtins/roc/Result.roc @@ -7,8 +7,9 @@ interface Result Result ok err : [Ok ok, Err err] ## Return `Bool.true` if the result indicates a success, else return `Bool.false` -## -## >>> Result.isOk (Ok 5) +## ``` +## Result.isOk (Ok 5) +## ``` isOk : Result ok err -> Bool isOk = \result -> when result is @@ -16,8 +17,9 @@ isOk = \result -> Err _ -> Bool.false ## Return `Bool.true` if the result indicates a failure, else return `Bool.false` -## -## >>> Result.isErr (Err "uh oh") +## ``` +## Result.isErr (Err "uh oh") +## ``` isErr : Result ok err -> Bool isErr = \result -> when result is @@ -26,10 +28,10 @@ isErr = \result -> ## 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 +## ``` +## Result.withDefault (Ok 7) 42 +## Result.withDefault (Err "uh oh") 42 +## ``` withDefault : Result ok err, ok -> ok withDefault = \result, default -> when result is @@ -37,16 +39,15 @@ withDefault = \result, default -> 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. +## 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 +## ``` ## -## (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`. +## Functions like `map` are common in Roc; 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 @@ -54,13 +55,12 @@ map = \result, transform -> 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 +## 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 @@ -68,13 +68,12 @@ mapErr = \result, transform -> 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 `onErr` to transform an `Err`.) -## -## >>> Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num -## -## >>> Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num +## function on the value the `Ok` holds. Then return that new result. If the +## result is `Err`, this has no effect. Use `onErr` to transform an `Err`. +## ``` +## Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num +## Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num +## ``` try : Result a err, (a -> Result b err) -> Result b err try = \result, transform -> when result is @@ -82,13 +81,12 @@ try = \result, transform -> Err e -> Err e ## If the result is `Err`, transform the entire result by running a conversion -## function on the value the `Err` holds. Then return that new result. -## -## (If the result is `Ok`, this has no effect. Use `try` to transform an `Ok`.) -## -## >>> Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum -## -## >>> Result.onErr (Err "42") \errorNum -> Str.toNat errorNum +## function on the value the `Err` holds. Then return that new result. If the +## result is `Ok`, this has no effect. Use `try` to transform an `Ok`. +## ``` +## Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum +## Result.onErr (Err "42") \errorNum -> Str.toNat errorNum +## ``` onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr onErr = \result, transform -> when result is diff --git a/crates/compiler/builtins/roc/Set.roc b/crates/compiler/builtins/roc/Set.roc index c8ead19fcc..ba6a09069b 100644 --- a/crates/compiler/builtins/roc/Set.roc +++ b/crates/compiler/builtins/roc/Set.roc @@ -4,6 +4,7 @@ interface Set empty, single, walk, + walkUntil, insert, len, remove, @@ -25,6 +26,8 @@ interface Set # We should have this line above the next has. # It causes the formatter to fail currently. # | k has Hash & Eq +## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) +## type which stores a collection of unique values, without any ordering Set k := Dict.Dict k {} has [ Eq { @@ -43,14 +46,39 @@ isEq = \xs, ys -> else Break Bool.false -## Creates a new empty set. +## Creates a new empty `Set`. +## ``` +## emptySet = Set.empty {} +## countValues = Set.len emptySet +## +## expect countValues == 0 +## ``` empty : {} -> Set k | k has Hash & Eq empty = \{} -> @Set (Dict.empty {}) +## Creates a new `Set` with a single value. +## ``` +## singleItemSet = Set.single "Apple" +## countValues = Set.len singleItemSet +## +## expect countValues == 1 +## ``` single : k -> Set k | k has Hash & Eq single = \key -> Dict.single key {} |> @Set +## Insert a value into a `Set`. +## ``` +## fewItemSet = +## Set.empty {} +## |> Set.insert "Apple" +## |> Set.insert "Pear" +## |> Set.insert "Banana" +## +## countValues = Set.len fewItemSet +## +## expect countValues == 3 +## ``` insert : Set k, k -> Set k | k has Hash & Eq insert = \@Set dict, key -> Dict.insert dict key {} |> @Set @@ -72,6 +100,18 @@ expect expected == actual +## Counts the number of values in a given `Set`. +## ``` +## fewItemSet = +## Set.empty {} +## |> Set.insert "Apple" +## |> Set.insert "Pear" +## |> Set.insert "Banana" +## +## countValues = Set.len fewItemSet +## +## expect countValues == 3 +## ``` len : Set k -> Nat | k has Hash & Eq len = \@Set dict -> Dict.len dict @@ -88,41 +128,151 @@ expect actual == 3 -## Drops the given element from the set. +## Removes the value from the given `Set`. +## ``` +## numbers = +## Set.empty {} +## |> Set.insert 10 +## |> Set.insert 20 +## |> Set.remove 10 +## +## has10 = Set.contains numbers 10 +## has20 = Set.contains numbers 20 +## +## expect has10 == Bool.false +## expect has20 == Bool.true +## ``` remove : Set k, k -> Set k | k has Hash & Eq remove = \@Set dict, key -> Dict.remove dict key |> @Set +## Test if a value is in the `Set`. +## ``` +## Fruit : [Apple, Pear, Banana] +## +## fruit : Set Fruit +## fruit = +## Set.single Apple +## |> Set.insert Pear +## +## hasApple = Set.contains fruit Apple +## hasBanana = Set.contains fruit Banana +## +## expect hasApple == Bool.true +## expect hasBanana == Bool.false +## ``` contains : Set k, k -> Bool | k has Hash & Eq contains = \@Set dict, key -> Dict.contains dict key +## Retrieve the values in a `Set` as a `List`. +## ``` +## numbers : Set U64 +## numbers = Set.fromList [1,2,3,4,5] +## +## values = [1,2,3,4,5] +## +## expect Set.toList numbers == values +## ``` toList : Set k -> List k | k has Hash & Eq toList = \@Set dict -> Dict.keys dict +## Create a `Set` from a `List` of values. +## ``` +## values = +## Set.empty {} +## |> Set.insert Banana +## |> Set.insert Apple +## |> Set.insert Pear +## +## expect Set.fromList [Pear, Apple, Banana] == values +## ``` fromList : List k -> Set k | k has Hash & Eq fromList = \list -> initial = @Set (Dict.withCapacity (List.len list)) List.walk list initial insert +## Combine two `Set` collection by keeping the +## [union](https://en.wikipedia.org/wiki/Union_(set_theory)) +## of all the values pairs. This means that all of the values in both `Set`s +## will be combined. +## ``` +## set1 = Set.single Left +## set2 = Set.single Right +## +## expect Set.union set1 set2 == Set.fromList [Left, Right] +## ``` union : Set k, Set k -> Set k | k has Hash & Eq union = \@Set dict1, @Set dict2 -> Dict.insertAll dict1 dict2 |> @Set +## Combine two `Set`s by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory)) +## of all the values pairs. This means that we keep only those values that are +## in both `Set`s. +## ``` +## set1 = Set.fromList [Left, Other] +## set2 = Set.fromList [Left, Right] +## +## expect Set.intersection set1 set2 == Set.single Left +## ``` intersection : Set k, Set k -> Set k | k has Hash & Eq intersection = \@Set dict1, @Set dict2 -> Dict.keepShared dict1 dict2 |> @Set +## Remove the values in the first `Set` that are also in the second `Set` +## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement) +## of the values. This means that we will be left with only those values that +## are in the first and not in the second. +## ``` +## first = Set.fromList [Left, Right, Up, Down] +## second = Set.fromList [Left, Right] +## +## expect Set.difference first second == Set.fromList [Up, Down] +## ``` difference : Set k, Set k -> Set k | k has Hash & Eq difference = \@Set dict1, @Set dict2 -> Dict.removeAll dict1 dict2 |> @Set +## Iterate through the values of a given `Set` and build a value. +## ``` +## values = Set.fromList ["March", "April", "May"] +## +## startsWithLetterM = \month -> +## when Str.toUtf8 month is +## ['M', ..] -> Bool.true +## _ -> Bool.false +## +## reduce = \state, k -> +## if startsWithLetterM k then +## state + 1 +## else +## state +## +## result = Set.walk values 0 reduce +## +## expect result == 2 +## ``` walk : Set k, state, (state, k -> state) -> state | k has Hash & Eq walk = \@Set dict, state, step -> Dict.walk dict state (\s, k, _ -> step s k) +## Iterate through the values of a given `Set` and build a value, can stop +## iterating part way through the collection. +## ``` +## numbers = Set.fromList [1,2,3,4,5,6,42,7,8,9,10] +## +## find42 = \state, k -> +## if k == 42 then +## Break FoundTheAnswer +## else +## Continue state +## +## result = Set.walkUntil numbers NotFound find42 +## +## expect result == FoundTheAnswer +## ``` walkUntil : Set k, state, (state, k -> [Continue state, Break state]) -> state | k has Hash & Eq walkUntil = \@Set dict, state, step -> Dict.walkUntil dict state (\s, k, _ -> step s k) diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index 7d5456e307..4744dc245f 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -1,14 +1,13 @@ -## Working with Unicode strings in Roc. ## -## ### Unicode +## ## Working with Unicode strings in Roc ## ## 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 "👩‍👩‍👦‍👦". @@ -17,11 +16,11 @@ ## 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 "🕊" -## +## ``` +## 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`. @@ -31,23 +30,23 @@ ## 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." -## +## ``` +## "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`. +## Another way you could insert a newlines is by writing `\u(0A)` 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. +## `\u` escape sequences are always followed by a hexadecimal number 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 +## As another example, `"R\u(6F)c"` is the same string as `"Roc"`, because +## `"\u(6F)"` 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"\. +## you can write `"R\u(F6)c"` as an alternative way to get the string `"Röc"\. ## ## Roc strings also support these escape sequences: ## @@ -58,12 +57,11 @@ ## * `\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)." -## +## ``` +## 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 @@ -111,6 +109,7 @@ interface Str splitLast, walkUtf8WithIndex, reserve, + releaseExcessCapacity, appendScalar, walkScalars, walkScalarsUntil, @@ -125,7 +124,6 @@ interface Str Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec }, ] -## Test Utf8ByteProblem : [ InvalidStartByte, UnexpectedEndOfSequence, @@ -138,16 +136,18 @@ Utf8ByteProblem : [ Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem } ## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise. -## -## expect Str.isEmpty "hi!" == Bool.false -## expect Str.isEmpty "" == Bool.true +## ``` +## expect Str.isEmpty "hi!" == Bool.false +## expect Str.isEmpty "" == Bool.true +## ``` isEmpty : Str -> Bool ## Concatenates two strings together. -## -## expect Str.concat "ab" "cd" == "abcd" -## expect Str.concat "hello" "" == "hello" -## expect Str.concat "" "" == "" +## ``` +## expect Str.concat "ab" "cd" == "abcd" +## expect Str.concat "hello" "" == "hello" +## expect Str.concat "" "" == "" +## ``` concat : Str, Str -> Str ## Returns a string of the specified capacity without any content. @@ -155,9 +155,10 @@ withCapacity : Nat -> Str ## Combines a [List] of strings into a single string, with a separator ## string in between each. -## -## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three" -## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4" +## ``` +## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three" +## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4" +## ``` joinWith : List Str, Str -> Str ## Split a string around a separator. @@ -165,20 +166,22 @@ joinWith : List Str, Str -> Str ## Passing `""` for the separator is not useful; ## it returns the original string wrapped in a [List]. To split a string ## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes` -## -## expect Str.split "1,2,3" "," == ["1","2","3"] -## expect Str.split "1,2,3" "" == ["1,2,3"] +## ``` +## expect Str.split "1,2,3" "," == ["1","2","3"] +## expect Str.split "1,2,3" "" == ["1,2,3"] +## ``` split : Str, Str -> List Str ## Repeats a string the given number of times. -## -## expect Str.repeat "z" 3 == "zzz" -## expect Str.repeat "na" 8 == "nananananananana" -## +## ``` +## expect Str.repeat "z" 3 == "zzz" +## expect Str.repeat "na" 8 == "nananananananana" +## ``` ## Returns `""` when given `""` for the string or `0` for the count. -## -## expect Str.repeat "" 10 == "" -## expect Str.repeat "anything" 0 == "" +## ``` +## expect Str.repeat "" 10 == "" +## expect Str.repeat "anything" 0 == "" +## ``` repeat : Str, Nat -> Str ## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster) @@ -186,11 +189,11 @@ repeat : Str, Nat -> Str ## ## Note that the number of extended grapheme clusters can be different from the number ## of visual glyphs rendered! Consider the following examples: -## -## expect Str.countGraphemes "Roc" == 3 -## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4 -## expect Str.countGraphemes "🕊" == 1 -## +## ``` +## expect Str.countGraphemes "Roc" == 3 +## expect Str.countGraphemes "👩‍👩‍👦‍👦" == 4 +## expect Str.countGraphemes "🕊" == 1 +## ``` ## Note that "👩‍👩‍👦‍👦" takes up 4 graphemes (even though visually it appears as a single ## glyph) because under the hood it's represented using an emoji modifier sequence. ## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented @@ -205,12 +208,15 @@ graphemes : Str -> List Str ## ## If the given string is empty, or if the given [U32] is not a valid ## code point, returns [Bool.false]. +## ``` +## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527 +## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9 +## expect !Str.startsWithScalar "" 40527 +## ``` ## -## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527 -## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9 -## expect !Str.startsWithScalar "" 40527 +## ## Performance Details ## -## **Performance Note:** This runs slightly faster than [Str.startsWith], so +## 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.startsWithScalar '鹏'` ## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.) @@ -225,36 +231,39 @@ startsWithScalar : Str, U32 -> Bool ## ## (Roc strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point), ## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).) -## -## expect Str.toScalars "Roc" == [82, 111, 99] -## expect Str.toScalars "鹏" == [40527] -## expect Str.toScalars "சி" == [2970, 3007] -## expect Str.toScalars "🐦" == [128038] -## expect Str.toScalars "👩‍👩‍👦‍👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102] -## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99] -## expect Str.toScalars "" == [] +## ``` +## expect Str.toScalars "Roc" == [82, 111, 99] +## expect Str.toScalars "鹏" == [40527] +## expect Str.toScalars "சி" == [2970, 3007] +## expect Str.toScalars "🐦" == [128038] +## expect Str.toScalars "👩‍👩‍👦‍👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102] +## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99] +## expect Str.toScalars "" == [] +## ``` toScalars : Str -> List U32 ## Returns 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].) -## -## expect Str.toUtf8 "Roc" == [82, 111, 99] -## expect Str.toUtf8 "鹏" == [233, 185, 143] -## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191] -## expect Str.toUtf8 "🐦" == [240, 159, 144, 166] +## ``` +## expect Str.toUtf8 "Roc" == [82, 111, 99] +## expect Str.toUtf8 "鹏" == [233, 185, 143] +## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191] +## expect Str.toUtf8 "🐦" == [240, 159, 144, 166] +## ``` toUtf8 : Str -> List U8 ## Converts a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) to a string. ## ## Returns `Err` if the given bytes are invalid UTF-8, and returns `Ok ""` when given `[]`. -## -## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc" -## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏" -## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி" -## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦" -## expect Str.fromUtf8 [] == Ok "" -## expect Str.fromUtf8 [255] |> Result.isErr +## ``` +## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc" +## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏" +## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி" +## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦" +## expect Str.fromUtf8 [] == Ok "" +## expect Str.fromUtf8 [255] |> Result.isErr +## ``` fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat] fromUtf8 = \bytes -> result = fromUtf8RangeLowlevel bytes 0 (List.len bytes) @@ -266,8 +275,9 @@ fromUtf8 = \bytes -> ## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) ## into a [Str] -## -## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi" +## ``` +## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi" +## ``` fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds] fromUtf8Range = \bytes, config -> if config.start + config.count <= List.len bytes then @@ -290,57 +300,65 @@ FromUtf8Result : { fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result ## Check if the given [Str] starts with a value. -## -## expect Str.startsWith "ABC" "A" == Bool.true -## expect Str.startsWith "ABC" "X" == Bool.false +## ``` +## expect Str.startsWith "ABC" "A" == Bool.true +## expect Str.startsWith "ABC" "X" == Bool.false +## ``` startsWith : Str, Str -> Bool ## Check if the given [Str] ends with a value. -## -## expect Str.endsWith "ABC" "C" == Bool.true -## expect Str.endsWith "ABC" "X" == Bool.false +## ``` +## expect Str.endsWith "ABC" "C" == Bool.true +## expect Str.endsWith "ABC" "X" == Bool.false +## ``` endsWith : Str, Str -> Bool ## Return the [Str] with all whitespace removed from both the beginning ## as well as the end. -## -## expect Str.trim " Hello \n\n" == "Hello" +## ``` +## expect Str.trim " Hello \n\n" == "Hello" +## ``` trim : Str -> Str ## Return the [Str] with all whitespace removed from the beginning. -## -## expect Str.trimLeft " Hello \n\n" == "Hello \n\n" +## ``` +## expect Str.trimLeft " Hello \n\n" == "Hello \n\n" +## ``` trimLeft : Str -> Str ## Return the [Str] with all whitespace removed from the end. -## -## expect Str.trimRight " Hello \n\n" == " Hello" +## ``` +## expect Str.trimRight " Hello \n\n" == " Hello" +## ``` trimRight : Str -> Str ## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal ## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic). -## -## expect Str.toDec "10" == Ok 10dec -## expect Str.toDec "-0.25" == Ok -0.25dec -## expect Str.toDec "not a number" == Err InvalidNumStr +## ``` +## expect Str.toDec "10" == Ok 10dec +## expect Str.toDec "-0.25" == Ok -0.25dec +## expect Str.toDec "not a number" == Err InvalidNumStr +## ``` toDec : Str -> Result Dec [InvalidNumStr] toDec = \string -> strToNumHelp string ## Encode a [Str] to a [F64]. A [F64] value is a 64-bit ## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be ## specified with a `f64` suffix. -## -## expect Str.toF64 "0.10" == Ok 0.10f64 -## expect Str.toF64 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toF64 "0.10" == Ok 0.10f64 +## expect Str.toF64 "not a number" == Err InvalidNumStr +## ``` toF64 : Str -> Result F64 [InvalidNumStr] toF64 = \string -> strToNumHelp string ## Encode a [Str] to a [F32].A [F32] value is a 32-bit ## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be ## specified with a `f32` suffix. -## -## expect Str.toF32 "0.10" == Ok 0.10f32 -## expect Str.toF32 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toF32 "0.10" == Ok 0.10f32 +## expect Str.toF32 "not a number" == Err InvalidNumStr +## ``` toF32 : Str -> Result F32 [InvalidNumStr] toF32 = \string -> strToNumHelp string @@ -356,20 +374,22 @@ toF32 = \string -> strToNumHelp string ## Calling `Str.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 smaller than `Num.maxU64`. -## -## expect Str.toNat "9_000_000_000" == Ok 9000000000 -## expect Str.toNat "not a number" == Err InvalidNumStr +## ``` +## expect Str.toNat "9_000_000_000" == Ok 9000000000 +## expect Str.toNat "not a number" == Err InvalidNumStr +## ``` toNat : Str -> Result Nat [InvalidNumStr] toNat = \string -> strToNumHelp string ## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers ## from `0` to `340_282_366_920_938_463_463_374_607_431_768_211_455` (over ## 340 undecillion). It can be specified with a u128 suffix. -## -## expect Str.toU128 "1500" == Ok 1500u128 -## expect Str.toU128 "0.1" == Err InvalidNumStr -## expect Str.toU128 "-1" == Err InvalidNumStr -## expect Str.toU128 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toU128 "1500" == Ok 1500u128 +## expect Str.toU128 "0.1" == Err InvalidNumStr +## expect Str.toU128 "-1" == Err InvalidNumStr +## expect Str.toU128 "not a number" == Err InvalidNumStr +## ``` toU128 : Str -> Result U128 [InvalidNumStr] toU128 = \string -> strToNumHelp string @@ -377,96 +397,105 @@ toU128 = \string -> strToNumHelp string ## from `-170_141_183_460_469_231_731_687_303_715_884_105_728` to ## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified ## with a i128 suffix. -## -## expect Str.toI128 "1500" == Ok 1500i128 -## expect Str.toI128 "-1" == Ok -1i128 -## expect Str.toI128 "0.1" == Err InvalidNumStr -## expect Str.toI128 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toI128 "1500" == Ok 1500i128 +## expect Str.toI128 "-1" == Ok -1i128 +## expect Str.toI128 "0.1" == Err InvalidNumStr +## expect Str.toI128 "not a number" == Err InvalidNumStr +## ``` toI128 : Str -> Result I128 [InvalidNumStr] toI128 = \string -> strToNumHelp string ## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers ## from `0` to `18_446_744_073_709_551_615` (over 18 quintillion). It ## can be specified with a u64 suffix. -## -## expect Str.toU64 "1500" == Ok 1500u64 -## expect Str.toU64 "0.1" == Err InvalidNumStr -## expect Str.toU64 "-1" == Err InvalidNumStr -## expect Str.toU64 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toU64 "1500" == Ok 1500u64 +## expect Str.toU64 "0.1" == Err InvalidNumStr +## expect Str.toU64 "-1" == Err InvalidNumStr +## expect Str.toU64 "not a number" == Err InvalidNumStr +## ``` toU64 : Str -> Result U64 [InvalidNumStr] toU64 = \string -> strToNumHelp string ## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers ## from `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807`. It can be ## specified with a i64 suffix. -## -## expect Str.toI64 "1500" == Ok 1500i64 -## expect Str.toI64 "-1" == Ok -1i64 -## expect Str.toI64 "0.1" == Err InvalidNumStr -## expect Str.toI64 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toI64 "1500" == Ok 1500i64 +## expect Str.toI64 "-1" == Ok -1i64 +## expect Str.toI64 "0.1" == Err InvalidNumStr +## expect Str.toI64 "not a number" == Err InvalidNumStr +## ``` toI64 : Str -> Result I64 [InvalidNumStr] toI64 = \string -> strToNumHelp string ## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers ## from `0` to `4_294_967_295` (over 4 billion). It can be specified with ## a u32 suffix. -## -## expect Str.toU32 "1500" == Ok 1500u32 -## expect Str.toU32 "0.1" == Err InvalidNumStr -## expect Str.toU32 "-1" == Err InvalidNumStr -## expect Str.toU32 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toU32 "1500" == Ok 1500u32 +## expect Str.toU32 "0.1" == Err InvalidNumStr +## expect Str.toU32 "-1" == Err InvalidNumStr +## expect Str.toU32 "not a number" == Err InvalidNumStr +## ``` toU32 : Str -> Result U32 [InvalidNumStr] toU32 = \string -> strToNumHelp string ## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers ## from `-2_147_483_648` to `2_147_483_647`. It can be ## specified with a i32 suffix. -## -## expect Str.toI32 "1500" == Ok 1500i32 -## expect Str.toI32 "-1" == Ok -1i32 -## expect Str.toI32 "0.1" == Err InvalidNumStr -## expect Str.toI32 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toI32 "1500" == Ok 1500i32 +## expect Str.toI32 "-1" == Ok -1i32 +## expect Str.toI32 "0.1" == Err InvalidNumStr +## expect Str.toI32 "not a number" == Err InvalidNumStr +## ``` toI32 : Str -> Result I32 [InvalidNumStr] toI32 = \string -> strToNumHelp string ## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers ## from `0` to `65_535`. It can be specified with a u16 suffix. -## -## expect Str.toU16 "1500" == Ok 1500u16 -## expect Str.toU16 "0.1" == Err InvalidNumStr -## expect Str.toU16 "-1" == Err InvalidNumStr -## expect Str.toU16 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toU16 "1500" == Ok 1500u16 +## expect Str.toU16 "0.1" == Err InvalidNumStr +## expect Str.toU16 "-1" == Err InvalidNumStr +## expect Str.toU16 "not a number" == Err InvalidNumStr +## ``` toU16 : Str -> Result U16 [InvalidNumStr] toU16 = \string -> strToNumHelp string ## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers ## from `-32_768` to `32_767`. It can be ## specified with a i16 suffix. -## -## expect Str.toI16 "1500" == Ok 1500i16 -## expect Str.toI16 "-1" == Ok -1i16 -## expect Str.toI16 "0.1" == Err InvalidNumStr -## expect Str.toI16 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toI16 "1500" == Ok 1500i16 +## expect Str.toI16 "-1" == Ok -1i16 +## expect Str.toI16 "0.1" == Err InvalidNumStr +## expect Str.toI16 "not a number" == Err InvalidNumStr +## ``` toI16 : Str -> Result I16 [InvalidNumStr] toI16 = \string -> strToNumHelp string ## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers ## from `0` to `255`. It can be specified with a u8 suffix. -## -## expect Str.toU8 "250" == Ok 250u8 -## expect Str.toU8 "-0.1" == Err InvalidNumStr -## expect Str.toU8 "not a number" == Err InvalidNumStr -## expect Str.toU8 "1500" == Err InvalidNumStr +## ``` +## expect Str.toU8 "250" == Ok 250u8 +## expect Str.toU8 "-0.1" == Err InvalidNumStr +## expect Str.toU8 "not a number" == Err InvalidNumStr +## expect Str.toU8 "1500" == Err InvalidNumStr +## ``` toU8 : Str -> Result U8 [InvalidNumStr] toU8 = \string -> strToNumHelp string ## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers ## from `-128` to `127`. It can be ## specified with a i8 suffix. -## -## expect Str.toI8 "-15" == Ok -15i8 -## expect Str.toI8 "150.00" == Err InvalidNumStr -## expect Str.toI8 "not a number" == Err InvalidNumStr +## ``` +## expect Str.toI8 "-15" == Ok -15i8 +## expect Str.toI8 "150.00" == Err InvalidNumStr +## expect Str.toI8 "not a number" == Err InvalidNumStr +## ``` toI8 : Str -> Result I8 [InvalidNumStr] toI8 = \string -> strToNumHelp string @@ -474,8 +503,9 @@ toI8 = \string -> strToNumHelp string getUnsafe : Str, Nat -> U8 ## Gives the number of bytes in a [Str] value. -## -## expect Str.countUtf8Bytes "Hello World" == 11 +## ``` +## expect Str.countUtf8Bytes "Hello World" == 11 +## ``` countUtf8Bytes : Str -> Nat ## string slice that does not do bounds checking or utf-8 verification @@ -483,9 +513,10 @@ substringUnsafe : Str, Nat, Nat -> Str ## Returns the given [Str] with each occurrence of a substring replaced. ## Returns [Err NotFound] if the substring is not found. -## -## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz" -## expect Str.replaceEach "not here" "/" "_" == Err NotFound +## ``` +## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz" +## expect Str.replaceEach "not here" "/" "_" == Err NotFound +## ``` replaceEach : Str, Str, Str -> Result Str [NotFound] replaceEach = \haystack, needle, flower -> when splitFirst haystack needle is @@ -515,9 +546,10 @@ expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi" ## Returns the given [Str] with the first occurrence of a substring replaced. ## Returns [Err NotFound] if the substring is not found. -## -## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz" -## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound +## ``` +## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz" +## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound +## ``` replaceFirst : Str, Str, Str -> Result Str [NotFound] replaceFirst = \haystack, needle, flower -> when splitFirst haystack needle is @@ -530,9 +562,10 @@ expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi" ## Returns the given [Str] with the last occurrence of a substring replaced. ## Returns [Err NotFound] if the substring is not found. -## -## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz" -## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound +## ``` +## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz" +## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound +## ``` replaceLast : Str, Str, Str -> Result Str [NotFound] replaceLast = \haystack, needle, flower -> when splitLast haystack needle is @@ -546,9 +579,10 @@ expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi" ## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well ## as the rest of the string after that occurrence. ## Returns [ Err NotFound] if the delimiter is not found. -## -## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" } -## expect Str.splitFirst "no slashes here" "/" == Err NotFound +## ``` +## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" } +## expect Str.splitFirst "no slashes here" "/" == Err NotFound +## ``` splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound] splitFirst = \haystack, needle -> when firstMatch haystack needle is @@ -599,9 +633,10 @@ firstMatchHelp = \haystack, needle, index, lastPossible -> ## Returns the given [Str] before the last occurrence of a delimiter, as well as ## the rest of the string after that occurrence. ## Returns [Err NotFound] if the delimiter is not found. -## -## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" } -## expect Str.splitLast "no slashes here" "/" == Err NotFound +## ``` +## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" } +## expect Str.splitLast "no slashes here" "/" == Err NotFound +## ``` splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound] splitLast = \haystack, needle -> when lastMatch haystack needle is @@ -690,10 +725,11 @@ matchesAtHelp = \state -> ## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update ## state for each byte. The index for that byte in the string is provided ## to the update function. -## -## f : List U8, U8, Nat -> List U8 -## f = \state, byte, _ -> List.append state byte -## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67] +## ``` +## f : List U8, U8, Nat -> List U8 +## f = \state, byte, _ -> List.append state byte +## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67] +## ``` walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state walkUtf8WithIndex = \string, state, step -> walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string) @@ -711,14 +747,19 @@ walkUtf8WithIndexHelp = \string, state, step, index, length -> ## Enlarge a string for at least the given number additional bytes. reserve : Str, Nat -> Str +## Shrink the memory footprint of a str such that it's capacity and length are equal. +## Note: This will also convert seamless slices to regular lists. +releaseExcessCapacity : Str -> Str + ## is UB when the scalar is invalid appendScalarUnsafe : Str, U32 -> Str ## Append a [U32] scalar to the given string. If the given scalar is not a valid ## unicode value, it returns [Err InvalidScalar]. -## -## expect Str.appendScalar "H" 105 == Ok "Hi" -## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar +## ``` +## expect Str.appendScalar "H" 105 == Ok "Hi" +## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar +## ``` appendScalar : Str, U32 -> Result Str [InvalidScalar] appendScalar = \string, scalar -> if isValidScalar scalar then @@ -734,10 +775,11 @@ getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat } ## Walks over the unicode [U32] values for the given [Str] and calls a function ## to update state for each. -## -## f : List U32, U32 -> List U32 -## f = \state, scalar -> List.append state scalar -## expect Str.walkScalars "ABC" [] f == [65, 66, 67] +## ``` +## f : List U32, U32 -> List U32 +## f = \state, scalar -> List.append state scalar +## expect Str.walkScalars "ABC" [] f == [65, 66, 67] +## ``` walkScalars : Str, state, (state, U32 -> state) -> state walkScalars = \string, init, step -> walkScalarsHelp string init step 0 (Str.countUtf8Bytes string) @@ -754,16 +796,17 @@ walkScalarsHelp = \string, state, step, index, length -> ## Walks over the unicode [U32] values for the given [Str] and calls a function ## to update state for each. -## -## f : List U32, U32 -> [Break (List U32), Continue (List U32)] -## f = \state, scalar -> -## check = 66 -## if scalar == check then -## Break [check] -## else -## Continue (List.append state scalar) -## expect Str.walkScalarsUntil "ABC" [] f == [66] -## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67] +## ``` +## f : List U32, U32 -> [Break (List U32), Continue (List U32)] +## f = \state, scalar -> +## check = 66 +## if scalar == check then +## Break [check] +## else +## Continue (List.append state scalar) +## expect Str.walkScalarsUntil "ABC" [] f == [66] +## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67] +## ``` walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state walkScalarsUntil = \string, init, step -> walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string) @@ -795,7 +838,8 @@ strToNumHelp = \string -> Err InvalidNumStr ## Adds a prefix to the given [Str]. -## -## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome" +## ``` +## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome" +## ``` withPrefix : Str, Str -> Str withPrefix = \str, prefix -> Str.concat prefix str diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 99b19dfddd..d96496eae9 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -1,73 +1,6 @@ use roc_module::symbol::Symbol; use roc_target::TargetInfo; use std::ops::Index; -use tempfile::NamedTempFile; - -pub const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-wasm32.o")); -// TODO: in the future, we should use Zig's cross-compilation to generate and store these -// for all targets, so that we can do cross-compilation! -#[cfg(unix)] -pub const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-host.o")); -#[cfg(windows)] -pub const HOST_WINDOWS: &[u8] = include_bytes!(concat!( - env!("OUT_DIR"), - "/bitcode/builtins-windows-x86_64.obj" -)); - -pub fn host_wasm_tempfile() -> std::io::Result { - let tempfile = tempfile::Builder::new() - .prefix("host_bitcode") - .suffix(".wasm") - .rand_bytes(8) - .tempfile()?; - - std::fs::write(tempfile.path(), HOST_WASM)?; - - Ok(tempfile) -} - -#[cfg(unix)] -fn host_unix_tempfile() -> std::io::Result { - let tempfile = tempfile::Builder::new() - .prefix("host_bitcode") - .suffix(".o") - .rand_bytes(8) - .tempfile()?; - - std::fs::write(tempfile.path(), HOST_UNIX)?; - - Ok(tempfile) -} - -#[cfg(windows)] -fn host_windows_tempfile() -> std::io::Result { - let tempfile = tempfile::Builder::new() - .prefix("host_bitcode") - .suffix(".obj") - .rand_bytes(8) - .tempfile()?; - - std::fs::write(tempfile.path(), HOST_WINDOWS)?; - - Ok(tempfile) -} - -pub fn host_tempfile() -> std::io::Result { - #[cfg(unix)] - { - host_unix_tempfile() - } - - #[cfg(windows)] - { - host_windows_tempfile() - } - - #[cfg(not(any(windows, unix)))] - { - unreachable!() - } -} #[derive(Debug, Default, Copy, Clone)] pub struct IntrinsicName { @@ -356,8 +289,16 @@ pub const NUM_MUL_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num. pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.num.mul_with_overflow"); +pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName = + int_intrinsic!("roc_builtins.num.count_leading_zero_bits"); +pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName = + int_intrinsic!("roc_builtins.num.count_trailing_zero_bits"); +pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_one_bits"); + 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"; +pub const NUM_BYTES_TO_U64: &str = "roc_builtins.num.bytes_to_u64"; +pub const NUM_BYTES_TO_U128: &str = "roc_builtins.num.bytes_to_u128"; pub const STR_INIT: &str = "roc_builtins.str.init"; pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments"; @@ -392,6 +333,8 @@ pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe"; pub const STR_CLONE_TO: &str = "roc_builtins.str.clone_to"; pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity"; pub const STR_GRAPHEMES: &str = "roc_builtins.str.graphemes"; +pub const STR_REFCOUNT_PTR: &str = "roc_builtins.str.refcount_ptr"; +pub const STR_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.str.release_excess_capacity"; pub const LIST_MAP: &str = "roc_builtins.list.map"; pub const LIST_MAP2: &str = "roc_builtins.list.map2"; @@ -409,6 +352,9 @@ pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique"; pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_APPEND_UNSAFE: &str = "roc_builtins.list.append_unsafe"; pub const LIST_RESERVE: &str = "roc_builtins.list.reserve"; +pub const LIST_CAPACITY: &str = "roc_builtins.list.capacity"; +pub const LIST_REFCOUNT_PTR: &str = "roc_builtins.list.refcount_ptr"; +pub const LIST_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.list.release_excess_capacity"; pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str"; pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str"; diff --git a/crates/compiler/can/Cargo.toml b/crates/compiler/can/Cargo.toml index 16efe76ab7..986c5fd732 100644 --- a/crates/compiler/can/Cargo.toml +++ b/crates/compiler/can/Cargo.toml @@ -1,28 +1,29 @@ [package] name = "roc_can" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Canonicalize a roc abstract syntax tree, resolving symbols, re-ordering definitions, and preparing a module for type inference." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [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_region = { path = "../region" } roc_serialize = { path = "../serialize" } - -bumpalo.workspace = true -static_assertions.workspace = true -bitvec.workspace = true +roc_types = { path = "../types" } ven_pretty = { path = "../../vendor/pretty" } +bitvec.workspace = true +bumpalo.workspace = true +static_assertions.workspace = true + [dev-dependencies] -pretty_assertions.workspace = true indoc.workspace = true +pretty_assertions.workspace = true diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs index fbfd6fc3cc..e028b750b8 100644 --- a/crates/compiler/can/src/annotation.rs +++ b/crates/compiler/can/src/annotation.rs @@ -448,7 +448,7 @@ pub fn find_type_def_symbols( As(actual, _, _) => { stack.push(&actual.value); } - Tuple { fields: _, ext: _ } => { + Tuple { elems: _, ext: _ } => { todo!("find_type_def_symbols: Tuple"); } Record { fields, ext } => { @@ -872,8 +872,41 @@ fn can_annotation_help( } } - Tuple { fields: _, ext: _ } => { - todo!("tuple"); + Tuple { elems, ext } => { + let (ext_type, is_implicit_openness) = can_extension_type( + env, + pol, + scope, + var_store, + introduced_variables, + local_aliases, + references, + ext, + roc_problem::can::ExtensionTypeKind::Record, + ); + + debug_assert!( + matches!(is_implicit_openness, ExtImplicitOpenness::No), + "tuples should never be implicitly inferred open" + ); + + debug_assert!(!elems.is_empty()); // We don't allow empty tuples + + let elem_types = can_assigned_tuple_elems( + env, + pol, + &elems.items, + scope, + var_store, + introduced_variables, + local_aliases, + references, + ); + + Type::Tuple( + elem_types, + TypeExtension::from_type(ext_type, is_implicit_openness), + ) } Record { fields, ext } => { let (ext_type, is_implicit_openness) = can_extension_type( @@ -1440,6 +1473,39 @@ fn can_assigned_fields<'a>( field_types } +// TODO trim down these arguments! +#[allow(clippy::too_many_arguments)] +fn can_assigned_tuple_elems<'a>( + env: &mut Env, + pol: CanPolarity, + elems: &&[Loc>], + scope: &mut Scope, + var_store: &mut VarStore, + introduced_variables: &mut IntroducedVariables, + local_aliases: &mut VecMap, + references: &mut VecSet, +) -> VecMap { + let mut elem_types = VecMap::with_capacity(elems.len()); + + for (index, loc_elem) in elems.iter().enumerate() { + let elem_type = can_annotation_help( + env, + pol, + &loc_elem.value, + loc_elem.region, + scope, + var_store, + introduced_variables, + local_aliases, + references, + ); + + elem_types.insert(index, elem_type); + } + + elem_types +} + // TODO trim down these arguments! #[allow(clippy::too_many_arguments)] fn can_tags<'a>( diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index 4e5b9f95b2..aafbff85a7 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -126,6 +126,7 @@ map_symbol_to_lowlevel_and_arity! { StrGetCapacity; STR_CAPACITY; 1, StrWithCapacity; STR_WITH_CAPACITY; 1, StrGraphemes; STR_GRAPHEMES; 1, + StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1, ListLen; LIST_LEN; 1, ListWithCapacity; LIST_WITH_CAPACITY; 1, @@ -145,6 +146,7 @@ map_symbol_to_lowlevel_and_arity! { ListDropAt; LIST_DROP_AT; 2, ListSwap; LIST_SWAP; 3, ListGetCapacity; LIST_CAPACITY; 1, + ListReleaseExcessCapacity; LIST_RELEASE_EXCESS_CAPACITY; 1, ListGetUnsafe; DICT_LIST_GET_UNSAFE; 2, @@ -187,6 +189,8 @@ map_symbol_to_lowlevel_and_arity! { NumAsin; NUM_ASIN; 1, NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2, NumBytesToU32; NUM_BYTES_TO_U32_LOWLEVEL; 2, + NumBytesToU64; NUM_BYTES_TO_U64_LOWLEVEL; 2, + NumBytesToU128; NUM_BYTES_TO_U128_LOWLEVEL; 2, NumBitwiseAnd; NUM_BITWISE_AND; 2, NumBitwiseXor; NUM_BITWISE_XOR; 2, NumBitwiseOr; NUM_BITWISE_OR; 2, @@ -194,6 +198,9 @@ map_symbol_to_lowlevel_and_arity! { NumShiftRightBy; NUM_SHIFT_RIGHT; 2, NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2, NumToStr; NUM_TO_STR; 1, + NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1, + NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1, + NumCountOneBits; NUM_COUNT_ONE_BITS; 1, Eq; BOOL_STRUCTURAL_EQ; 2, NotEq; BOOL_STRUCTURAL_NOT_EQ; 2, diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs index 307c435425..7da4816a45 100644 --- a/crates/compiler/can/src/copy.rs +++ b/crates/compiler/can/src/copy.rs @@ -1,10 +1,9 @@ use crate::{ def::Def, expr::{ - ClosureData, Expr, Field, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData, - WhenBranchPattern, + ClosureData, Expr, Field, OpaqueWrapFunctionData, StructAccessorData, WhenBranchPattern, }, - pattern::{DestructType, ListPatterns, Pattern, RecordDestruct}, + pattern::{DestructType, ListPatterns, Pattern, RecordDestruct, TupleDestruct}, }; use roc_module::{ ident::{Lowercase, TagName}, @@ -513,7 +512,7 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr field: field.clone(), }, - RecordAccessor(RecordAccessorData { + RecordAccessor(StructAccessorData { name, function_var, record_var, @@ -521,7 +520,7 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr ext_var, field_var, field, - }) => RecordAccessor(RecordAccessorData { + }) => RecordAccessor(StructAccessorData { name: *name, function_var: sub!(*function_var), record_var: sub!(*record_var), @@ -545,24 +544,6 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr index: *index, }, - TupleAccessor(TupleAccessorData { - name, - function_var, - tuple_var: record_var, - closure_var, - ext_var, - elem_var: field_var, - index, - }) => TupleAccessor(TupleAccessorData { - name: *name, - function_var: sub!(*function_var), - tuple_var: sub!(*record_var), - closure_var: sub!(*closure_var), - ext_var: sub!(*ext_var), - elem_var: sub!(*field_var), - index: *index, - }), - RecordUpdate { record_var, ext_var, @@ -794,6 +775,30 @@ fn deep_copy_pattern_help( }) .collect(), }, + TupleDestructure { + whole_var, + ext_var, + destructs, + } => TupleDestructure { + whole_var: sub!(*whole_var), + ext_var: sub!(*ext_var), + destructs: destructs + .iter() + .map(|lrd| { + lrd.map( + |TupleDestruct { + destruct_index: index, + var, + typ: (tyvar, pat), + }: &crate::pattern::TupleDestruct| TupleDestruct { + destruct_index: *index, + var: sub!(*var), + typ: (sub!(*tyvar), pat.map(|p| go_help!(p))), + }, + ) + }) + .collect(), + }, List { list_var, elem_var, diff --git a/crates/compiler/can/src/debug/pretty_print.rs b/crates/compiler/can/src/debug/pretty_print.rs index 0f8de5070d..9450e71dfa 100644 --- a/crates/compiler/can/src/debug/pretty_print.rs +++ b/crates/compiler/can/src/debug/pretty_print.rs @@ -5,7 +5,7 @@ use crate::expr::Expr::{self, *}; use crate::expr::{ ClosureData, DeclarationTag, Declarations, FunctionDef, OpaqueWrapFunctionData, WhenBranch, }; -use crate::pattern::{Pattern, RecordDestruct}; +use crate::pattern::{Pattern, RecordDestruct, TupleDestruct}; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -35,7 +35,10 @@ pub fn pretty_print_declarations(c: &Ctx, declarations: &Declarations) -> String DeclarationTag::Expectation => todo!(), DeclarationTag::ExpectationFx => todo!(), DeclarationTag::Destructure(_) => todo!(), - DeclarationTag::MutualRecursion { .. } => todo!(), + DeclarationTag::MutualRecursion { .. } => { + // the defs will be printed next + continue; + } }; defs.push(def); @@ -123,9 +126,10 @@ fn toplevel_function<'a>( .append(f.line()) .append(f.text("\\")) .append(f.intersperse(args, f.text(", "))) - .append(f.text("->")) + .append(f.text(" ->")) + .group() .append(f.line()) - .append(expr(c, EPrec::Free, f, body)) + .append(expr(c, EPrec::Free, f, body).group()) .nest(2) .group() } @@ -330,12 +334,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, } => expr(c, AppArg, f, &loc_expr.value) .append(f.text(format!(".{}", field.as_str()))) .group(), - TupleAccess { .. } => todo!(), + TupleAccess { + loc_expr, index, .. + } => expr(c, AppArg, f, &loc_expr.value) + .append(f.text(format!(".{index}"))) + .group(), OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => { f.text(format!("@{}", opaque_name.as_str(c.interns))) } RecordAccessor(_) => todo!(), - TupleAccessor(_) => todo!(), RecordUpdate { symbol, updates, .. } => f @@ -386,7 +393,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, ), Crash { .. } => todo!(), ZeroArgumentTag { .. } => todo!(), - OpaqueRef { .. } => todo!(), + OpaqueRef { name, argument, .. } => maybe_paren!( + Free, + p, + || true, + pp_sym(c, f, *name) + .append(f.space()) + .append(expr(c, AppArg, f, &argument.1.value)) + .group() + ), Dbg { .. } => todo!(), Expect { .. } => todo!(), ExpectFx { .. } => todo!(), @@ -505,6 +520,19 @@ fn pattern<'a>( ) .append(f.text("}")) .group(), + TupleDestructure { destructs, .. } => f + .text("(") + .append( + f.intersperse( + destructs + .iter() + .map(|l| &l.value) + .map(|TupleDestruct { typ: (_, p), .. }| pattern(c, Free, f, &p.value)), + f.text(", "), + ), + ) + .append(f.text(")")) + .group(), List { .. } => todo!(), NumLiteral(_, n, _, _) | IntLiteral(_, _, n, _, _) | FloatLiteral(_, _, n, _, _) => { f.text(&**n) diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index 5e4e95e3bc..2910e2498a 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -15,7 +15,7 @@ use crate::expr::AnnotatedMark; use crate::expr::ClosureData; use crate::expr::Declarations; use crate::expr::Expr::{self, *}; -use crate::expr::RecordAccessorData; +use crate::expr::StructAccessorData; use crate::expr::{canonicalize_expr, Output, Recursive}; use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern}; use crate::procedure::References; @@ -36,6 +36,7 @@ use roc_parse::ast::AssignedField; use roc_parse::ast::Defs; use roc_parse::ast::ExtractSpaces; use roc_parse::ast::TypeHeader; +use roc_parse::ident::Accessor; use roc_parse::pattern::PatternType; use roc_problem::can::ShadowKind; use roc_problem::can::{CycleEntry, Problem, RuntimeError}; @@ -45,6 +46,7 @@ use roc_types::subs::{VarStore, Variable}; use roc_types::types::AliasCommon; use roc_types::types::AliasKind; use roc_types::types::AliasVar; +use roc_types::types::IndexOrField; use roc_types::types::LambdaSet; use roc_types::types::MemberImpl; use roc_types::types::OptAbleType; @@ -1995,6 +1997,16 @@ fn pattern_to_vars_by_symbol( vars_by_symbol.insert(*opaque, expr_var); } + TupleDestructure { destructs, .. } => { + for destruct in destructs { + pattern_to_vars_by_symbol( + vars_by_symbol, + &destruct.value.typ.1.value, + destruct.value.typ.0, + ); + } + } + RecordDestructure { destructs, .. } => { for destruct in destructs { vars_by_symbol.insert(destruct.value.symbol, destruct.value.var); @@ -2316,19 +2328,23 @@ fn canonicalize_pending_body<'a>( ident: defined_symbol, .. }, - ast::Expr::RecordAccessorFunction(field), + ast::Expr::AccessorFunction(field), ) => { + let field = match field { + Accessor::RecordField(field) => IndexOrField::Field((*field).into()), + Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()), + }; let (loc_can_expr, can_output) = ( Loc::at( loc_expr.region, - RecordAccessor(RecordAccessorData { + RecordAccessor(StructAccessorData { name: *defined_symbol, function_var: var_store.fresh(), record_var: var_store.fresh(), ext_var: var_store.fresh(), closure_var: var_store.fresh(), field_var: var_store.fresh(), - field: (*field).into(), + field, }), ), Output::default(), diff --git a/crates/compiler/can/src/exhaustive.rs b/crates/compiler/can/src/exhaustive.rs index b810d554a7..1960ee2aab 100644 --- a/crates/compiler/can/src/exhaustive.rs +++ b/crates/compiler/can/src/exhaustive.rs @@ -10,9 +10,10 @@ use roc_module::ident::{Lowercase, TagIdIntType, TagName}; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::{ - Content, FlatType, GetSubsSlice, RedundantMark, Subs, SubsFmtContent, Variable, + Content, FlatType, GetSubsSlice, RedundantMark, SortedTagsIterator, Subs, SubsFmtContent, + Variable, }; -use roc_types::types::AliasKind; +use roc_types::types::{gather_tags_unsorted_iter, AliasKind}; pub use roc_exhaustive::Context as ExhaustiveContext; @@ -80,6 +81,8 @@ enum IndexCtor<'a> { Opaque, /// Index a record type. The arguments are the types of the record fields. Record(&'a [Lowercase]), + /// Index a tuple type. + Tuple, /// Index a guard constructor. The arguments are a faux guard pattern, and then the real /// pattern being guarded. E.g. `A B if g` becomes Guard { [True, (A B)] }. Guard, @@ -112,6 +115,7 @@ impl<'a> IndexCtor<'a> { } RenderAs::Opaque => Self::Opaque, RenderAs::Record(fields) => Self::Record(fields), + RenderAs::Tuple => Self::Tuple, RenderAs::Guard => Self::Guard, } } @@ -145,9 +149,7 @@ fn index_var( var = *structure; } Content::Structure(structure) => match structure { - FlatType::Func(_, _, _) | FlatType::FunctionOrTagUnion(_, _, _) => { - return Err(TypeError) - } + FlatType::Func(_, _, _) => return Err(TypeError), FlatType::Apply(Symbol::LIST_LIST, args) => { match (subs.get_subs_slice(*args), ctor) { ([elem_var], IndexCtor::List) => { @@ -208,6 +210,19 @@ fn index_var( let vars = opt_vars.expect("constructor must be known in the indexable type if we are exhautiveness checking"); return Ok(vars); } + FlatType::FunctionOrTagUnion(tags, _, _) => { + let tag_ctor = match ctor { + IndexCtor::Tag(name) => name, + _ => { + internal_error!("constructor in a tag union must be tag") + } + }; + + let tags = subs.get_subs_slice(*tags); + debug_assert!(tags.contains(tag_ctor), "constructor must be known in the indexable type if we are exhautiveness checking"); + + return Ok(vec![]); + } FlatType::EmptyRecord => { debug_assert!(matches!(ctor, IndexCtor::Record(..))); // If there are optional record fields we don't unify them, but we need to @@ -354,6 +369,30 @@ fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern { SP::KnownCtor(union, tag_id, patterns) } + TupleDestructure { destructs, .. } => { + let tag_id = TagId(0); + let mut patterns = std::vec::Vec::with_capacity(destructs.len()); + + for Loc { + value: destruct, + region: _, + } in destructs + { + patterns.push(sketch_pattern(&destruct.typ.1.value)); + } + + let union = Union { + render_as: RenderAs::Tuple, + alternatives: vec![Ctor { + name: CtorName::Tag(TagName("#Record".into())), + tag_id, + arity: destructs.len(), + }], + }; + + SP::KnownCtor(union, tag_id, patterns) + } + List { patterns, list_var: _, @@ -616,64 +655,80 @@ fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union, use {Content::*, FlatType::*}; - match dealias_tag(subs, content) { + let (sorted_tags, ext) = 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.var()) { - FlexVar(_) | RigidVar(_) => { - let openness_tag = TagName(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()); - - let mut index = 0; - for (tag, args) in alternatives_iter { - let is_inhabited = args.iter().all(|v| subs.is_inhabited(*v)); - if !is_inhabited { - // This constructor is not material; we don't need to match over it! - continue; - } - - let tag_id = TagId(index as TagIdIntType); - index += 1; - - if this_tag == &tag { - my_tag_id = tag_id; - } - alternatives.push(Ctor { - name: CtorName::Tag(tag), - tag_id, - arity: args.len(), + (sorted_tags, ext) + } + Structure(FunctionOrTagUnion(tags, _, ext)) => { + let (ext_tags, ext) = gather_tags_unsorted_iter(subs, Default::default(), *ext) + .unwrap_or_else(|_| { + internal_error!("Content is not a tag union: {:?}", subs.dbg(whole_var)) }); + let mut all_tags: Vec<(TagName, &[Variable])> = Vec::with_capacity(tags.len()); + for tag in subs.get_subs_slice(*tags) { + all_tags.push((tag.clone(), &[])); } - - let union = Union { - alternatives, - render_as: RenderAs::Tag, - }; - - (union, my_tag_id) + for (tag, vars) in ext_tags { + debug_assert!(vars.is_empty()); + all_tags.push((tag.clone(), &[])); + } + (Box::new(all_tags.into_iter()) as SortedTagsIterator, ext) } _ => internal_error!( "Content is not a tag union: {:?}", SubsFmtContent(content, subs) ), + }; + + 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.var()) { + FlexVar(_) | RigidVar(_) => { + let openness_tag = TagName(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()); + + let mut index = 0; + for (tag, args) in alternatives_iter { + let is_inhabited = args.iter().all(|v| subs.is_inhabited(*v)); + if !is_inhabited { + // This constructor is not material; we don't need to match over it! + continue; + } + + let tag_id = TagId(index as TagIdIntType); + index += 1; + + 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) } pub fn dealias_tag<'a>(subs: &'a Subs, content: &'a Content) -> &'a Content { diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index 1a8ceb303b..888d0222b6 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -19,12 +19,13 @@ use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_parse::ast::{self, Defs, StrLiteral}; +use roc_parse::ident::Accessor; use roc_parse::pattern::PatternType::*; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::num::SingleQuoteBound; use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable}; -use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type}; +use roc_types::types::{Alias, Category, IndexOrField, LambdaSet, OptAbleVar, Type}; use std::fmt::{Debug, Display}; use std::{char, u32}; @@ -186,8 +187,8 @@ pub enum Expr { field: Lowercase, }, - /// field accessor as a function, e.g. (.foo) expr - RecordAccessor(RecordAccessorData), + /// tuple or field accessor as a function, e.g. (.foo) expr or (.1) expr + RecordAccessor(StructAccessorData), TupleAccess { tuple_var: Variable, @@ -197,9 +198,6 @@ pub enum Expr { index: usize, }, - /// tuple accessor as a function, e.g. (.1) expr - TupleAccessor(TupleAccessorData), - RecordUpdate { record_var: Variable, ext_var: Variable, @@ -315,9 +313,8 @@ impl Expr { Self::Record { .. } => Category::Record, Self::EmptyRecord => Category::Record, Self::RecordAccess { field, .. } => Category::RecordAccess(field.clone()), - Self::RecordAccessor(data) => Category::RecordAccessor(data.field.clone()), + Self::RecordAccessor(data) => Category::Accessor(data.field.clone()), Self::TupleAccess { index, .. } => Category::TupleAccess(*index), - Self::TupleAccessor(data) => Category::TupleAccessor(data.index), Self::RecordUpdate { .. } => Category::Record, Self::Tag { name, arguments, .. @@ -383,43 +380,30 @@ pub struct ClosureData { pub loc_body: Box>, } -/// A tuple accessor like `.2`, which is equivalent to `\x -> x.2` -/// TupleAccessors are desugared to closures; they need to have a name +/// A record or tuple accessor like `.foo` or `.0`, which is equivalent to `\r -> r.foo` +/// Struct accessors are desugared to closures; they need to have a name /// so the closure can have a correct lambda set. /// /// We distinguish them from closures so we can have better error messages /// during constraint generation. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct TupleAccessorData { - pub name: Symbol, - pub function_var: Variable, - pub tuple_var: Variable, - pub closure_var: Variable, - pub ext_var: Variable, - pub elem_var: Variable, - pub index: usize, -} - -/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo` -/// RecordAccessors are desugared to closures; they need to have a name -/// so the closure can have a correct lambda set. -/// -/// We distinguish them from closures so we can have better error messages -/// during constraint generation. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RecordAccessorData { +pub struct StructAccessorData { pub name: Symbol, pub function_var: Variable, pub record_var: Variable, pub closure_var: Variable, pub ext_var: Variable, pub field_var: Variable, - pub field: Lowercase, + + /// Note that the `field` field is an `IndexOrField` in order to represent both + /// record and tuple accessors. This is different from `TupleAccess` and + /// `RecordAccess` (and RecordFields/TupleElems), which share less of their implementation. + pub field: IndexOrField, } -impl RecordAccessorData { +impl StructAccessorData { pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData { - let RecordAccessorData { + let StructAccessorData { name, function_var, record_var, @@ -436,12 +420,21 @@ impl RecordAccessorData { // into // // (\r -> r.foo) - let body = Expr::RecordAccess { - record_var, - ext_var, - field_var, - loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))), - field, + let body = match field { + IndexOrField::Index(index) => Expr::TupleAccess { + tuple_var: record_var, + ext_var, + elem_var: field_var, + loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))), + index, + }, + IndexOrField::Field(field) => Expr::RecordAccess { + record_var, + ext_var, + field_var, + loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))), + field, + }, }; let loc_body = Loc::at_zero(body); @@ -1080,15 +1073,18 @@ pub fn canonicalize_expr<'a>( output, ) } - ast::Expr::RecordAccessorFunction(field) => ( - RecordAccessor(RecordAccessorData { + ast::Expr::AccessorFunction(field) => ( + RecordAccessor(StructAccessorData { name: scope.gen_unique_symbol(), function_var: var_store.fresh(), record_var: var_store.fresh(), ext_var: var_store.fresh(), closure_var: var_store.fresh(), field_var: var_store.fresh(), - field: (*field).into(), + field: match field { + Accessor::RecordField(field) => IndexOrField::Field((*field).into()), + Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()), + }, }), Output::default(), ), @@ -1106,18 +1102,6 @@ pub fn canonicalize_expr<'a>( output, ) } - ast::Expr::TupleAccessorFunction(index) => ( - TupleAccessor(TupleAccessorData { - name: scope.gen_unique_symbol(), - function_var: var_store.fresh(), - tuple_var: var_store.fresh(), - ext_var: var_store.fresh(), - closure_var: var_store.fresh(), - elem_var: var_store.fresh(), - index: index.parse().unwrap(), - }), - Output::default(), - ), ast::Expr::Tag(tag) => { let variant_var = var_store.fresh(); let ext_var = var_store.fresh(); @@ -1874,7 +1858,6 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr { | other @ RuntimeError(_) | other @ EmptyRecord | other @ RecordAccessor { .. } - | other @ TupleAccessor { .. } | other @ RecordUpdate { .. } | other @ Var(..) | other @ AbilityMember(..) @@ -3004,7 +2987,6 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec { | Expr::Str(_) | Expr::ZeroArgumentTag { .. } | Expr::RecordAccessor(_) - | Expr::TupleAccessor(_) | Expr::SingleQuote(..) | Expr::EmptyRecord | Expr::TypedHole(_) diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index 07958d457e..05073411c6 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -837,21 +837,10 @@ fn fix_values_captured_in_closure_defs( no_capture_symbols: &mut VecSet, closure_captures: &mut VecMap>, ) { - // recursive defs cannot capture each other - for def in defs.iter() { - no_capture_symbols.extend( - crate::traverse::symbols_introduced_from_pattern(&def.loc_pattern).map(|ls| ls.value), - ); - } - - for def in defs.iter_mut() { - fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures); - } - // Mutually recursive functions should both capture the union of all their capture sets // // Really unfortunate we make a lot of clones here, can this be done more efficiently? - let mut total_capture_set = Vec::default(); + let mut total_capture_set = VecMap::default(); for def in defs.iter_mut() { if let Expr::Closure(ClosureData { captured_symbols, .. @@ -860,8 +849,16 @@ fn fix_values_captured_in_closure_defs( total_capture_set.extend(captured_symbols.iter().copied()); } } + for def in defs.iter() { + for symbol in + crate::traverse::symbols_introduced_from_pattern(&def.loc_pattern).map(|ls| ls.value) + { + total_capture_set.remove(&symbol); + } + } + + let mut total_capture_set: Vec<_> = total_capture_set.into_iter().collect(); total_capture_set.sort_by_key(|(sym, _)| *sym); - total_capture_set.dedup_by_key(|(sym, _)| *sym); for def in defs.iter_mut() { if let Expr::Closure(ClosureData { captured_symbols, .. @@ -870,6 +867,10 @@ fn fix_values_captured_in_closure_defs( *captured_symbols = total_capture_set.clone(); } } + + for def in defs.iter_mut() { + fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures); + } } fn fix_values_captured_in_closure_pattern( @@ -918,6 +919,15 @@ fn fix_values_captured_in_closure_pattern( } } } + TupleDestructure { destructs, .. } => { + for loc_destruct in destructs.iter_mut() { + fix_values_captured_in_closure_pattern( + &mut loc_destruct.value.typ.1.value, + no_capture_symbols, + closure_captures, + ) + } + } List { patterns, .. } => { for loc_pat in patterns.patterns.iter_mut() { fix_values_captured_in_closure_pattern( @@ -1023,9 +1033,9 @@ fn fix_values_captured_in_closure_expr( captured_symbols.retain(|(s, _)| s != name); let original_captures_len = captured_symbols.len(); - let mut num_visited = 0; let mut i = 0; - while num_visited < original_captures_len { + let mut added_captures = false; + while i < original_captures_len { // If we've captured a capturing closure, replace the captured closure symbol with // the symbols of its captures. That way, we can construct the closure with the // captures it needs inside our body. @@ -1039,19 +1049,21 @@ fn fix_values_captured_in_closure_expr( let (captured_symbol, _) = captured_symbols[i]; if let Some(captures) = closure_captures.get(&captured_symbol) { debug_assert!(!captures.is_empty()); - captured_symbols.swap_remove(i); captured_symbols.extend(captures); + captured_symbols.swap_remove(i); // Jump two, because the next element is now one of the newly-added captures, // which we don't need to check. i += 2; + + added_captures = true; } else { i += 1; } - num_visited += 1; } - if captured_symbols.len() > original_captures_len { + if added_captures { // Re-sort, since we've added new captures. captured_symbols.sort_by_key(|(sym, _)| *sym); + captured_symbols.dedup_by_key(|(sym, _)| *sym); } if captured_symbols.is_empty() { @@ -1087,8 +1099,7 @@ fn fix_values_captured_in_closure_expr( | TypedHole { .. } | RuntimeError(_) | ZeroArgumentTag { .. } - | RecordAccessor { .. } - | TupleAccessor { .. } => {} + | RecordAccessor { .. } => {} List { loc_elems, .. } => { for elem in loc_elems.iter_mut() { diff --git a/crates/compiler/can/src/operator.rs b/crates/compiler/can/src/operator.rs index bbcc84f89c..013a87d6d8 100644 --- a/crates/compiler/can/src/operator.rs +++ b/crates/compiler/can/src/operator.rs @@ -130,8 +130,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc | NonBase10Int { .. } | Str(_) | SingleQuote(_) - | RecordAccessorFunction(_) - | TupleAccessorFunction(_) + | AccessorFunction(_) | Var { .. } | Underscore { .. } | MalformedIdent(_, _) diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index 9d6a366cf9..fbbee0ab0e 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -58,6 +58,11 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, + TupleDestructure { + whole_var: Variable, + ext_var: Variable, + destructs: Vec>, + }, List { list_var: Variable, elem_var: Variable, @@ -100,6 +105,7 @@ impl Pattern { AppliedTag { whole_var, .. } => Some(*whole_var), UnwrappedOpaque { whole_var, .. } => Some(*whole_var), RecordDestructure { whole_var, .. } => Some(*whole_var), + TupleDestructure { whole_var, .. } => Some(*whole_var), List { list_var: whole_var, .. @@ -130,7 +136,21 @@ impl Pattern { | UnsupportedPattern(..) | MalformedPattern(..) | AbilityMemberSpecialization { .. } => true, - RecordDestructure { destructs, .. } => destructs.is_empty(), + + RecordDestructure { destructs, .. } => { + // If all destructs are surely exhaustive, then this is surely exhaustive. + destructs.iter().all(|d| match &d.value.typ { + DestructType::Required | DestructType::Optional(_, _) => false, + DestructType::Guard(_, pat) => pat.value.surely_exhaustive(), + }) + } + TupleDestructure { destructs, .. } => { + // If all destructs are surely exhaustive, then this is surely exhaustive. + destructs + .iter() + .all(|d| d.value.typ.1.value.surely_exhaustive()) + } + As(pattern, _identifier) => pattern.value.surely_exhaustive(), List { patterns, .. } => patterns.surely_exhaustive(), AppliedTag { .. } @@ -160,6 +180,7 @@ impl Pattern { UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque), RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord, RecordDestructure { .. } => C::Record, + TupleDestructure { .. } => C::Tuple, List { .. } => C::List, NumLiteral(..) => C::Num, IntLiteral(..) => C::Int, @@ -215,6 +236,13 @@ pub struct RecordDestruct { pub typ: DestructType, } +#[derive(Clone, Debug)] +pub struct TupleDestruct { + pub var: Variable, + pub destruct_index: usize, + pub typ: (Variable, Loc), +} + #[derive(Clone, Debug)] pub enum DestructType { Required, @@ -554,8 +582,38 @@ pub fn canonicalize_pattern<'a>( ) } - Tuple(_patterns) => { - todo!("canonicalize_pattern: Tuple") + Tuple(patterns) => { + let ext_var = var_store.fresh(); + let whole_var = var_store.fresh(); + let mut destructs = Vec::with_capacity(patterns.len()); + + for (i, loc_pattern) in patterns.iter().enumerate() { + let can_guard = canonicalize_pattern( + env, + var_store, + scope, + output, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + permit_shadows, + ); + + destructs.push(Loc { + region: loc_pattern.region, + value: TupleDestruct { + destruct_index: i, + var: var_store.fresh(), + typ: (var_store.fresh(), can_guard), + }, + }); + } + + Pattern::TupleDestructure { + whole_var, + ext_var, + destructs, + } } RecordDestructure(patterns) => { @@ -861,7 +919,8 @@ pub enum BindingsFromPattern<'a> { pub enum BindingsFromPatternWork<'a> { Pattern(&'a Loc), - Destruct(&'a Loc), + RecordDestruct(&'a Loc), + TupleDestruct(&'a Loc), } impl<'a> BindingsFromPattern<'a> { @@ -911,8 +970,12 @@ impl<'a> BindingsFromPattern<'a> { let (_, loc_arg) = &**argument; stack.push(Pattern(loc_arg)); } + TupleDestructure { destructs, .. } => { + let it = destructs.iter().rev().map(TupleDestruct); + stack.extend(it); + } RecordDestructure { destructs, .. } => { - let it = destructs.iter().rev().map(Destruct); + let it = destructs.iter().rev().map(RecordDestruct); stack.extend(it); } NumLiteral(..) @@ -930,7 +993,7 @@ impl<'a> BindingsFromPattern<'a> { } } } - BindingsFromPatternWork::Destruct(loc_destruct) => { + BindingsFromPatternWork::RecordDestruct(loc_destruct) => { match &loc_destruct.value.typ { DestructType::Required | DestructType::Optional(_, _) => { return Some((loc_destruct.value.symbol, loc_destruct.region)); @@ -941,6 +1004,10 @@ impl<'a> BindingsFromPattern<'a> { } } } + BindingsFromPatternWork::TupleDestruct(loc_destruct) => { + let inner = &loc_destruct.value.typ.1; + stack.push(BindingsFromPatternWork::Pattern(inner)) + } } } diff --git a/crates/compiler/can/src/traverse.rs b/crates/compiler/can/src/traverse.rs index ceaa46fd6a..c8b31ec715 100644 --- a/crates/compiler/can/src/traverse.rs +++ b/crates/compiler/can/src/traverse.rs @@ -9,9 +9,9 @@ use crate::{ def::{Annotation, Declaration, Def}, expr::{ self, AnnotatedMark, ClosureData, Declarations, Expr, Field, OpaqueWrapFunctionData, - RecordAccessorData, TupleAccessorData, + StructAccessorData, }, - pattern::{DestructType, Pattern, RecordDestruct}, + pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct}, }; macro_rules! visit_list { @@ -242,7 +242,7 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) { record_var: _, ext_var: _, } => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var), - Expr::RecordAccessor(RecordAccessorData { .. }) => { /* terminal */ } + Expr::RecordAccessor(StructAccessorData { .. }) => { /* terminal */ } Expr::TupleAccess { elem_var, loc_expr, @@ -250,7 +250,6 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) { tuple_var: _, ext_var: _, } => visitor.visit_expr(&loc_expr.value, loc_expr.region, *elem_var), - Expr::TupleAccessor(TupleAccessorData { .. }) => { /* terminal */ } Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ } Expr::RecordUpdate { record_var: _, @@ -483,6 +482,16 @@ pub trait Visitor: Sized { walk_record_destruct(self, destruct); } } + + fn visit_tuple_destruct(&mut self, destruct: &TupleDestruct, region: Region) { + if self.should_visit(region) { + self.visit_pattern( + &destruct.typ.1.value, + destruct.typ.1.region, + Some(destruct.typ.0), + ) + } + } } pub fn walk_pattern(visitor: &mut V, pattern: &Pattern) { @@ -503,6 +512,9 @@ pub fn walk_pattern(visitor: &mut V, pattern: &Pattern) { RecordDestructure { destructs, .. } => destructs .iter() .for_each(|d| visitor.visit_record_destruct(&d.value, d.region)), + TupleDestructure { destructs, .. } => destructs + .iter() + .for_each(|d| visitor.visit_tuple_destruct(&d.value, d.region)), List { patterns, elem_var, .. } => patterns diff --git a/crates/compiler/collections/Cargo.toml b/crates/compiler/collections/Cargo.toml index 241bb44c04..5b2d493461 100644 --- a/crates/compiler/collections/Cargo.toml +++ b/crates/compiler/collections/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "roc_collections" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Domain-specific collections created for the needs of the compiler." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -fnv.workspace = true -im.workspace = true -im-rc.workspace = true -wyhash.workspace = true -bumpalo.workspace = true -hashbrown.workspace = true bitvec.workspace = true +bumpalo.workspace = true +fnv.workspace = true +hashbrown.workspace = true +im-rc.workspace = true +im.workspace = true +wyhash.workspace = true diff --git a/crates/compiler/constrain/Cargo.toml b/crates/compiler/constrain/Cargo.toml index a830ac3f37..01d9414867 100644 --- a/crates/compiler/constrain/Cargo.toml +++ b/crates/compiler/constrain/Cargo.toml @@ -1,18 +1,20 @@ [package] name = "roc_constrain" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Responsible for building the set of constraints that are used during type inference of a program, and for gathering context needed for pleasant error messages when a type error occurs." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] +roc_can = { path = "../can" } roc_collections = { path = "../collections" } roc_error_macros = { path = "../../error_macros" } -roc_region = { path = "../region" } roc_module = { path = "../module" } roc_parse = { path = "../parse" } roc_problem = { path = "../problem" } +roc_region = { path = "../region" } roc_types = { path = "../types" } -roc_can = { path = "../can" } -arrayvec = "0.7.2" + +arrayvec.workspace = true diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index 659c202e65..96baf89b22 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -17,7 +17,7 @@ use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; use roc_can::expr::{ AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, ExpectLookup, Field, - FunctionDef, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData, WhenBranch, + FunctionDef, OpaqueWrapFunctionData, StructAccessorData, WhenBranch, }; use roc_can::pattern::Pattern; use roc_can::traverse::symbols_introduced_from_pattern; @@ -30,7 +30,7 @@ use roc_region::all::{Loc, Region}; use roc_types::subs::{IllegalCycleMark, Variable}; use roc_types::types::Type::{self, *}; use roc_types::types::{ - AliasKind, AnnotationSource, Category, OptAbleType, PReason, Reason, RecordField, + AliasKind, AnnotationSource, Category, IndexOrField, OptAbleType, PReason, Reason, RecordField, TypeExtension, TypeTag, Types, }; @@ -1162,7 +1162,7 @@ pub fn constrain_expr( [constraint, eq, record_con], ) } - RecordAccessor(RecordAccessorData { + RecordAccessor(StructAccessorData { name: closure_name, function_var, field, @@ -1176,19 +1176,32 @@ pub fn constrain_expr( let field_var = *field_var; let field_type = Variable(field_var); - let mut field_types = SendMap::default(); - let label = field.clone(); - field_types.insert(label, RecordField::Demanded(field_type.clone())); - let record_type = Type::Record( - field_types, - TypeExtension::from_non_annotation_type(ext_type), - ); + let record_type = match field { + IndexOrField::Field(field) => { + let mut field_types = SendMap::default(); + let label = field.clone(); + field_types.insert(label, RecordField::Demanded(field_type.clone())); + Type::Record( + field_types, + TypeExtension::from_non_annotation_type(ext_type), + ) + } + IndexOrField::Index(index) => { + let mut field_types = VecMap::with_capacity(1); + field_types.insert(*index, field_type.clone()); + Type::Tuple( + field_types, + TypeExtension::from_non_annotation_type(ext_type), + ) + } + }; + let record_type_index = { let typ = types.from_old_type(&record_type); constraints.push_type(types, typ) }; - let category = Category::RecordAccessor(field.clone()); + let category = Category::Accessor(field.clone()); let record_expected = constraints.push_expected_type(NoExpectation(record_type_index)); let record_con = @@ -1288,88 +1301,6 @@ pub fn constrain_expr( let eq = constraints.equal_types_var(elem_var, expected, category, region); constraints.exists_many([*tuple_var, elem_var, ext_var], [constraint, eq, tuple_con]) } - TupleAccessor(TupleAccessorData { - name: closure_name, - function_var, - tuple_var, - closure_var, - ext_var, - elem_var, - index, - }) => { - let ext_var = *ext_var; - let ext_type = Variable(ext_var); - let elem_var = *elem_var; - let elem_type = Variable(elem_var); - - let mut elem_types = VecMap::with_capacity(1); - elem_types.insert(*index, elem_type.clone()); - - let record_type = Type::Tuple( - elem_types, - TypeExtension::from_non_annotation_type(ext_type), - ); - let record_type_index = { - let typ = types.from_old_type(&record_type); - constraints.push_type(types, typ) - }; - - let category = Category::TupleAccessor(*index); - - let record_expected = constraints.push_expected_type(NoExpectation(record_type_index)); - let record_con = - constraints.equal_types_var(*tuple_var, record_expected, category.clone(), region); - - let expected_lambda_set = { - let lambda_set_ty = { - let typ = types.from_old_type(&Type::ClosureTag { - name: *closure_name, - captures: vec![], - ambient_function: *function_var, - }); - constraints.push_type(types, typ) - }; - constraints.push_expected_type(NoExpectation(lambda_set_ty)) - }; - - let closure_type = Type::Variable(*closure_var); - - let function_type_index = { - let typ = types.from_old_type(&Type::Function( - vec![record_type], - Box::new(closure_type), - Box::new(elem_type), - )); - constraints.push_type(types, typ) - }; - - let cons = [ - constraints.equal_types_var( - *closure_var, - expected_lambda_set, - category.clone(), - region, - ), - constraints.equal_types(function_type_index, expected, category.clone(), region), - { - let store_fn_var_index = constraints.push_variable(*function_var); - let store_fn_var_expected = - constraints.push_expected_type(NoExpectation(store_fn_var_index)); - constraints.equal_types( - function_type_index, - store_fn_var_expected, - category, - region, - ) - }, - record_con, - ]; - - constraints.exists_many( - [*tuple_var, *function_var, *closure_var, elem_var, ext_var], - cons, - ) - } LetRec(defs, loc_ret, cycle_mark) => { let body_con = constrain_expr( types, @@ -4001,10 +3932,6 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool { // RecordAccessor functions `.field` are equivalent to closures `\r -> r.field`, no need to weaken them. return true; } - TupleAccessor(_) => { - // TupleAccessor functions `.0` are equivalent to closures `\r -> r.0`, no need to weaken them. - return true; - } OpaqueWrapFunction(_) => { // Opaque wrapper functions `@Q` are equivalent to closures `\x -> @Q x`, no need to weaken them. return true; diff --git a/crates/compiler/constrain/src/pattern.rs b/crates/compiler/constrain/src/pattern.rs index d67c0c95a4..f05c218125 100644 --- a/crates/compiler/constrain/src/pattern.rs +++ b/crates/compiler/constrain/src/pattern.rs @@ -3,7 +3,7 @@ use crate::expr::{constrain_expr, Env}; use roc_can::constraint::{Constraint, Constraints, PExpectedTypeIndex, TypeOrVar}; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; -use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct}; +use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct, TupleDestruct}; use roc_collections::all::{HumanIndex, SendMap}; use roc_collections::VecMap; use roc_module::ident::Lowercase; @@ -125,6 +125,10 @@ fn headers_from_annotation_help( _ => false, }, + TupleDestructure { destructs: _, .. } => { + todo!(); + } + List { patterns, .. } => { if let Some((_, Some(rest))) = patterns.opt_rest { let annotation_index = { @@ -465,6 +469,96 @@ pub fn constrain_pattern( )); } + TupleDestructure { + whole_var, + ext_var, + destructs, + } => { + state.vars.push(*whole_var); + state.vars.push(*ext_var); + let ext_type = Type::Variable(*ext_var); + + let mut elem_types: VecMap = VecMap::default(); + + for Loc { + value: + TupleDestruct { + destruct_index: index, + var, + typ, + }, + .. + } in destructs.iter() + { + let pat_type = Type::Variable(*var); + let pat_type_index = constraints.push_variable(*var); + let expected = + constraints.push_pat_expected_type(PExpected::NoExpectation(pat_type_index)); + + let (guard_var, loc_guard) = typ; + let elem_type = { + let guard_type = constraints.push_variable(*guard_var); + let expected_pat = constraints.push_pat_expected_type(PExpected::ForReason( + PReason::PatternGuard, + pat_type_index, + loc_guard.region, + )); + + state.constraints.push(constraints.pattern_presence( + guard_type, + expected_pat, + PatternCategory::PatternGuard, + region, + )); + state.vars.push(*guard_var); + + constrain_pattern( + types, + constraints, + env, + &loc_guard.value, + loc_guard.region, + expected, + state, + ); + + pat_type + }; + + elem_types.insert(*index, elem_type); + + state.vars.push(*var); + } + + let tuple_type = { + let typ = types.from_old_type(&Type::Tuple( + elem_types, + TypeExtension::from_non_annotation_type(ext_type), + )); + constraints.push_type(types, typ) + }; + + let whole_var_index = constraints.push_variable(*whole_var); + let expected_record = + constraints.push_expected_type(Expected::NoExpectation(tuple_type)); + let whole_con = constraints.equal_types( + whole_var_index, + expected_record, + Category::Storage(std::file!(), std::line!()), + region, + ); + + let record_con = constraints.pattern_presence( + whole_var_index, + expected, + PatternCategory::Record, + region, + ); + + state.constraints.push(whole_con); + state.constraints.push(record_con); + } + RecordDestructure { whole_var, ext_var, diff --git a/crates/compiler/debug_flags/Cargo.toml b/crates/compiler/debug_flags/Cargo.toml index 9e299039a4..b3b9b4bc15 100644 --- a/crates/compiler/debug_flags/Cargo.toml +++ b/crates/compiler/debug_flags/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "roc_debug_flags" -version = "0.0.1" -edition = "2021" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "Environment variables that can be toggled to aid debugging of the compiler itself." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] diff --git a/crates/compiler/derive/Cargo.toml b/crates/compiler/derive/Cargo.toml index 05d4dbdbbb..9aba2f51c7 100644 --- a/crates/compiler/derive/Cargo.toml +++ b/crates/compiler/derive/Cargo.toml @@ -1,25 +1,27 @@ [package] name = "roc_derive" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Provides auto-derivers for builtin abilities like `Hash` and `Decode`." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -roc_collections = { path = "../collections" } -roc_error_macros = { path = "../../error_macros" } -roc_derive_key = { path = "../derive_key" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_types = { path = "../types" } roc_can = { path = "../can" } +roc_collections = { path = "../collections" } +roc_derive_key = { path = "../derive_key" } +roc_error_macros = { path = "../../error_macros" } +roc_module = { path = "../module" } +roc_region = { path = "../region" } +roc_types = { path = "../types" } roc_unify = { path = "../unify" } + bumpalo.workspace = true [features] -default = [] debug-derived-symbols = ["roc_module/debug-symbols"] +default = [] # Enables open extension variables for constructed records and tag unions. # This is not necessary for code generation, but may be necessary if you are # constraining and solving generated derived bodies. diff --git a/crates/compiler/derive/src/decoding.rs b/crates/compiler/derive/src/decoding.rs index 77bc98c1ad..2ba0b8cff8 100644 --- a/crates/compiler/derive/src/decoding.rs +++ b/crates/compiler/derive/src/decoding.rs @@ -1,33 +1,32 @@ //! Derivers for the `Decoding` ability. -use roc_can::expr::{ - AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch, WhenBranchPattern, -}; +use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Recursive}; use roc_can::pattern::Pattern; -use roc_collections::SendMap; -use roc_derive_key::decoding::FlatDecodableKey; -use roc_error_macros::internal_error; -use roc_module::called_via::CalledVia; -use roc_module::ident::Lowercase; -use roc_module::symbol::Symbol; -use roc_region::all::{Loc, Region}; -use roc_types::subs::{ - Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields, - RedundantMark, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, -}; -use roc_types::types::{AliasKind, RecordField}; -use crate::util::{Env, ExtensionKind}; +use roc_derive_key::decoding::FlatDecodableKey; +use roc_module::called_via::CalledVia; +use roc_module::symbol::Symbol; +use roc_region::all::Loc; +use roc_types::subs::{ + Content, FlatType, LambdaSet, OptVariable, SubsSlice, UnionLambdas, Variable, +}; + +use crate::util::Env; use crate::{synth_var, DerivedBody}; +mod list; +mod record; +mod tuple; + pub(crate) fn derive_decoder( env: &mut Env<'_>, key: FlatDecodableKey, def_symbol: Symbol, ) -> DerivedBody { let (body, body_type) = match key { - FlatDecodableKey::List() => decoder_list(env, def_symbol), - FlatDecodableKey::Record(fields) => decoder_record(env, def_symbol, fields), + FlatDecodableKey::List() => list::decoder(env, def_symbol), + FlatDecodableKey::Record(fields) => record::decoder(env, def_symbol, fields), + FlatDecodableKey::Tuple(arity) => tuple::decoder(env, def_symbol, arity), }; let specialization_lambda_sets = @@ -40,1062 +39,9 @@ pub(crate) fn derive_decoder( } } -// Implements decoding of a record. For example, for -// -// {first: a, second: b} -// -// we'd like to generate an impl like -// -// decoder : Decoder {first: a, second: b} fmt | a has Decoding, b has Decoding, fmt has DecoderFormatting -// decoder = -// initialState : {f0: Result a [NoField], f1: Result b [NoField]} -// initialState = {f0: Err NoField, f1: Err NoField} -// -// stepField = \state, field -> -// when field is -// "first" -> -// Keep (Decode.custom \bytes, fmt -> -// when Decode.decodeWith bytes Decode.decoder fmt is -// {result, rest} -> -// {result: Result.map result \val -> {state & f0: Ok val}, rest}) -// "second" -> -// Keep (Decode.custom \bytes, fmt -> -// when Decode.decodeWith bytes Decode.decoder fmt is -// {result, rest} -> -// {result: Result.map result \val -> {state & f1: Ok val}, rest}) -// _ -> Skip -// -// finalizer = \{f0, f1} -> -// when f0 is -// Ok first -> -// when f1 is -// Ok second -> Ok {first, second} -// Err NoField -> Err TooShort -// Err NoField -> Err TooShort -// -// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.record initialState stepField finalizer) fmt -fn decoder_record(env: &mut Env, _def_symbol: Symbol, fields: Vec) -> (Expr, Variable) { - // The decoded type of each field in the record, e.g. {first: a, second: b}. - let mut field_vars = Vec::with_capacity(fields.len()); - // The type of each field in the decoding state, e.g. {first: Result a [NoField], second: Result b [NoField]} - let mut result_field_vars = Vec::with_capacity(fields.len()); - - // initialState = ... - let (initial_state_var, initial_state) = - decoder_record_initial_state(env, &fields, &mut field_vars, &mut result_field_vars); - - // finalizer = ... - let (finalizer, finalizer_var, decode_err_var) = decoder_record_finalizer( - env, - initial_state_var, - &fields, - &field_vars, - &result_field_vars, - ); - - // stepField = ... - let (step_field, step_var) = decoder_record_step_field( - env, - fields, - &field_vars, - &result_field_vars, - initial_state_var, - decode_err_var, - ); - - // Build up the type of `Decode.record` we expect - let record_decoder_var = env.subs.fresh_unnamed_flex_var(); - let decode_record_lambda_set = env.subs.fresh_unnamed_flex_var(); - let decode_record_var = env.import_builtin_symbol_var(Symbol::DECODE_RECORD); - let this_decode_record_var = { - let flat_type = FlatType::Func( - SubsSlice::insert_into_subs(env.subs, [initial_state_var, step_var, finalizer_var]), - decode_record_lambda_set, - record_decoder_var, - ); - - synth_var(env.subs, Content::Structure(flat_type)) - }; - - env.unify(decode_record_var, this_decode_record_var); - - // Decode.record initialState stepField finalizer - let call_decode_record = Expr::Call( - Box::new(( - this_decode_record_var, - Loc::at_zero(Expr::AbilityMember( - Symbol::DECODE_RECORD, - None, - this_decode_record_var, - )), - decode_record_lambda_set, - record_decoder_var, - )), - vec![ - (initial_state_var, Loc::at_zero(initial_state)), - (step_var, Loc::at_zero(step_field)), - (finalizer_var, Loc::at_zero(finalizer)), - ], - CalledVia::Space, - ); - - let (call_decode_custom, decode_custom_ret_var) = { - let bytes_sym = env.new_symbol("bytes"); - let fmt_sym = env.new_symbol("fmt"); - let fmt_var = env.subs.fresh_unnamed_flex_var(); - - let (decode_custom, decode_custom_var) = wrap_in_decode_custom_decode_with( - env, - bytes_sym, - (fmt_sym, fmt_var), - vec![], - (call_decode_record, record_decoder_var), - ); - - (decode_custom, decode_custom_var) - }; - - (call_decode_custom, decode_custom_ret_var) -} - -// Example: -// stepField = \state, field -> -// when field is -// "first" -> -// Keep (Decode.custom \bytes, fmt -> -// # Uses a single-branch `when` because `let` is more expensive to monomorphize -// # due to checks for polymorphic expressions, and `rec` would be polymorphic. -// when Decode.decodeWith bytes Decode.decoder fmt is -// rec -> -// { -// rest: rec.rest, -// result: when rec.result is -// Ok val -> Ok {state & first: Ok val}, -// Err err -> Err err -// }) -// -// "second" -> -// Keep (Decode.custom \bytes, fmt -> -// when Decode.decodeWith bytes Decode.decoder fmt is -// rec -> -// { -// rest: rec.rest, -// result: when rec.result is -// Ok val -> Ok {state & second: Ok val}, -// Err err -> Err err -// }) -// -// _ -> Skip -fn decoder_record_step_field( - env: &mut Env, - fields: Vec, - field_vars: &[Variable], - result_field_vars: &[Variable], - state_record_var: Variable, - decode_err_var: Variable, -) -> (Expr, Variable) { - let state_arg_symbol = env.new_symbol("stateRecord"); - let field_arg_symbol = env.new_symbol("field"); - - // +1 because of the default branch. - let mut branches = Vec::with_capacity(fields.len() + 1); - let keep_payload_var = env.subs.fresh_unnamed_flex_var(); - let keep_or_skip_var = { - let keep_payload_subs_slice = SubsSlice::insert_into_subs(env.subs, [keep_payload_var]); - let flat_type = FlatType::TagUnion( - UnionTags::insert_slices_into_subs( - env.subs, - [ - ("Keep".into(), keep_payload_subs_slice), - ("Skip".into(), Default::default()), - ], - ), - TagExt::Any(Variable::EMPTY_TAG_UNION), - ); - - synth_var(env.subs, Content::Structure(flat_type)) - }; - - for ((field_name, &field_var), &result_field_var) in fields - .into_iter() - .zip(field_vars.iter()) - .zip(result_field_vars.iter()) - { - // Example: - // "first" -> - // Keep (Decode.custom \bytes, fmt -> - // # Uses a single-branch `when` because `let` is more expensive to monomorphize - // # due to checks for polymorphic expressions, and `rec` would be polymorphic. - // when Decode.decodeWith bytes Decode.decoder fmt is - // rec -> - // { - // rest: rec.rest, - // result: when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - // } - // ) - - let this_custom_callback_var; - let custom_callback_ret_var; - let custom_callback = { - // \bytes, fmt -> - // when Decode.decodeWith bytes Decode.decoder fmt is - // rec -> - // { - // rest: rec.rest, - // result: when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - // } - let bytes_arg_symbol = env.new_symbol("bytes"); - let fmt_arg_symbol = env.new_symbol("fmt"); - let bytes_arg_var = env.subs.fresh_unnamed_flex_var(); - let fmt_arg_var = env.subs.fresh_unnamed_flex_var(); - - // rec.result : [Ok field_var, Err DecodeError] - let rec_dot_result = { - let tag_union = FlatType::TagUnion( - UnionTags::for_result(env.subs, field_var, decode_err_var), - TagExt::Any(Variable::EMPTY_TAG_UNION), - ); - - synth_var(env.subs, Content::Structure(tag_union)) - }; - - // rec : { rest: List U8, result: (typeof rec.result) } - let rec_var = { - let fields = RecordFields::insert_into_subs( - env.subs, - [ - ("rest".into(), RecordField::Required(Variable::LIST_U8)), - ("result".into(), RecordField::Required(rec_dot_result)), - ], - ); - let record = FlatType::Record(fields, Variable::EMPTY_RECORD); - - synth_var(env.subs, Content::Structure(record)) - }; - - // `Decode.decoder` for the field's value - let decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER); - let decode_with_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH); - let lambda_set_var = env.subs.fresh_unnamed_flex_var(); - let this_decode_with_var = { - let subs_slice = SubsSlice::insert_into_subs( - env.subs, - [bytes_arg_var, decoder_var, fmt_arg_var], - ); - let this_decode_with_var = synth_var( - env.subs, - Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)), - ); - - env.unify(decode_with_var, this_decode_with_var); - - this_decode_with_var - }; - - // The result of decoding this field's value - either the updated state, or a decoding error. - let when_expr_var = { - let flat_type = FlatType::TagUnion( - UnionTags::for_result(env.subs, state_record_var, decode_err_var), - TagExt::Any(Variable::EMPTY_TAG_UNION), - ); - - synth_var(env.subs, Content::Structure(flat_type)) - }; - - // What our decoder passed to `Decode.custom` returns - the result of decoding the - // field's value, and the remaining bytes. - custom_callback_ret_var = { - let rest_field = RecordField::Required(Variable::LIST_U8); - let result_field = RecordField::Required(when_expr_var); - let flat_type = FlatType::Record( - RecordFields::insert_into_subs( - env.subs, - [("rest".into(), rest_field), ("result".into(), result_field)], - ), - Variable::EMPTY_RECORD, - ); - - synth_var(env.subs, Content::Structure(flat_type)) - }; - - let custom_callback_body = { - let rec_symbol = env.new_symbol("rec"); - - // # Uses a single-branch `when` because `let` is more expensive to monomorphize - // # due to checks for polymorphic expressions, and `rec` would be polymorphic. - // when Decode.decodeWith bytes Decode.decoder fmt is - // rec -> - // { - // rest: rec.rest, - // result: when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - // } - let branch_body = { - let result_val = { - // result: when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - let ok_val_symbol = env.new_symbol("val"); - let err_val_symbol = env.new_symbol("err"); - let ok_branch_expr = { - // Ok {state & first: Ok val}, - let mut updates = SendMap::default(); - - updates.insert( - field_name.clone(), - Field { - var: result_field_var, - region: Region::zero(), - loc_expr: Box::new(Loc::at_zero(Expr::Tag { - tag_union_var: result_field_var, - ext_var: env.new_ext_var(ExtensionKind::TagUnion), - name: "Ok".into(), - arguments: vec![( - field_var, - Loc::at_zero(Expr::Var(ok_val_symbol, field_var)), - )], - })), - }, - ); - - let updated_record = Expr::RecordUpdate { - record_var: state_record_var, - ext_var: env.new_ext_var(ExtensionKind::Record), - symbol: state_arg_symbol, - updates, - }; - - Expr::Tag { - tag_union_var: when_expr_var, - ext_var: env.new_ext_var(ExtensionKind::TagUnion), - name: "Ok".into(), - arguments: vec![(state_record_var, Loc::at_zero(updated_record))], - } - }; - - let branches = vec![ - // Ok val -> Ok {state & first: Ok val}, - WhenBranch { - patterns: vec![WhenBranchPattern { - pattern: Loc::at_zero(Pattern::AppliedTag { - whole_var: rec_dot_result, - ext_var: Variable::EMPTY_TAG_UNION, - tag_name: "Ok".into(), - arguments: vec![( - field_var, - Loc::at_zero(Pattern::Identifier(ok_val_symbol)), - )], - }), - degenerate: false, - }], - value: Loc::at_zero(ok_branch_expr), - guard: None, - redundant: RedundantMark::known_non_redundant(), - }, - // Err err -> Err err - WhenBranch { - patterns: vec![WhenBranchPattern { - pattern: Loc::at_zero(Pattern::AppliedTag { - whole_var: rec_dot_result, - ext_var: Variable::EMPTY_TAG_UNION, - tag_name: "Err".into(), - arguments: vec![( - decode_err_var, - Loc::at_zero(Pattern::Identifier(err_val_symbol)), - )], - }), - degenerate: false, - }], - value: Loc::at_zero(Expr::Tag { - tag_union_var: when_expr_var, - ext_var: env.new_ext_var(ExtensionKind::TagUnion), - name: "Err".into(), - arguments: vec![( - decode_err_var, - Loc::at_zero(Expr::Var(err_val_symbol, decode_err_var)), - )], - }), - guard: None, - redundant: RedundantMark::known_non_redundant(), - }, - ]; - - // when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - Expr::When { - loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess { - record_var: rec_var, - ext_var: env.new_ext_var(ExtensionKind::Record), - field_var: rec_dot_result, - loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))), - field: "result".into(), - })), - cond_var: rec_dot_result, - expr_var: when_expr_var, - region: Region::zero(), - branches, - branches_cond_var: rec_dot_result, - exhaustive: ExhaustiveMark::known_exhaustive(), - } - }; - - // { - // rest: rec.rest, - // result: when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - // } - let mut fields_map = SendMap::default(); - - fields_map.insert( - "rest".into(), - Field { - var: Variable::LIST_U8, - region: Region::zero(), - loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess { - record_var: rec_var, - ext_var: env.new_ext_var(ExtensionKind::Record), - field_var: Variable::LIST_U8, - loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))), - field: "rest".into(), - })), - }, - ); - - // result: when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - fields_map.insert( - "result".into(), - Field { - var: when_expr_var, - region: Region::zero(), - loc_expr: Box::new(Loc::at_zero(result_val)), - }, - ); - - Expr::Record { - record_var: custom_callback_ret_var, - fields: fields_map, - } - }; - - let branch = WhenBranch { - patterns: vec![WhenBranchPattern { - pattern: Loc::at_zero(Pattern::Identifier(rec_symbol)), - degenerate: false, - }], - value: Loc::at_zero(branch_body), - guard: None, - redundant: RedundantMark::known_non_redundant(), - }; - - let condition_expr = Expr::Call( - Box::new(( - this_decode_with_var, - Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)), - lambda_set_var, - rec_var, - )), - vec![ - ( - Variable::LIST_U8, - Loc::at_zero(Expr::Var(bytes_arg_symbol, Variable::LIST_U8)), - ), - ( - decoder_var, - Loc::at_zero(Expr::AbilityMember( - Symbol::DECODE_DECODER, - None, - decoder_var, - )), - ), - ( - fmt_arg_var, - Loc::at_zero(Expr::Var(fmt_arg_symbol, fmt_arg_var)), - ), - ], - CalledVia::Space, - ); - - // when Decode.decodeWith bytes Decode.decoder fmt is - Expr::When { - loc_cond: Box::new(Loc::at_zero(condition_expr)), - cond_var: rec_var, - expr_var: custom_callback_ret_var, - region: Region::zero(), - branches: vec![branch], - branches_cond_var: rec_var, - exhaustive: ExhaustiveMark::known_exhaustive(), - } - }; - - let custom_closure_symbol = env.new_symbol("customCallback"); - this_custom_callback_var = env.subs.fresh_unnamed_flex_var(); - let custom_callback_lambda_set_var = { - let content = Content::LambdaSet(LambdaSet { - solved: UnionLambdas::insert_into_subs( - env.subs, - [(custom_closure_symbol, [state_record_var])], - ), - recursion_var: OptVariable::NONE, - unspecialized: Default::default(), - ambient_function: this_custom_callback_var, - }); - let custom_callback_lambda_set_var = synth_var(env.subs, content); - let subs_slice = - SubsSlice::insert_into_subs(env.subs, [bytes_arg_var, fmt_arg_var]); - - env.subs.set_content( - this_custom_callback_var, - Content::Structure(FlatType::Func( - subs_slice, - custom_callback_lambda_set_var, - custom_callback_ret_var, - )), - ); - - custom_callback_lambda_set_var - }; - - // \bytes, fmt -> … - Expr::Closure(ClosureData { - function_type: this_custom_callback_var, - closure_type: custom_callback_lambda_set_var, - return_type: custom_callback_ret_var, - name: custom_closure_symbol, - captured_symbols: vec![(state_arg_symbol, state_record_var)], - recursive: Recursive::NotRecursive, - arguments: vec![ - ( - bytes_arg_var, - AnnotatedMark::known_exhaustive(), - Loc::at_zero(Pattern::Identifier(bytes_arg_symbol)), - ), - ( - fmt_arg_var, - AnnotatedMark::known_exhaustive(), - Loc::at_zero(Pattern::Identifier(fmt_arg_symbol)), - ), - ], - loc_body: Box::new(Loc::at_zero(custom_callback_body)), - }) - }; - - let decode_custom_ret_var = env.subs.fresh_unnamed_flex_var(); - let decode_custom = { - let decode_custom_var = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM); - let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var(); - let this_decode_custom_var = { - let subs_slice = SubsSlice::insert_into_subs(env.subs, [this_custom_callback_var]); - let flat_type = - FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var); - - synth_var(env.subs, Content::Structure(flat_type)) - }; - - env.unify(decode_custom_var, this_decode_custom_var); - - // Decode.custom \bytes, fmt -> … - Expr::Call( - Box::new(( - this_decode_custom_var, - Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)), - decode_custom_closure_var, - decode_custom_ret_var, - )), - vec![(this_custom_callback_var, Loc::at_zero(custom_callback))], - CalledVia::Space, - ) - }; - - env.unify(keep_payload_var, decode_custom_ret_var); - - let keep = { - // Keep (Decode.custom \bytes, fmt -> - // when Decode.decodeWith bytes Decode.decoder fmt is - // rec -> - // { - // rest: rec.rest, - // result: when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - // } - // ) - Expr::Tag { - tag_union_var: keep_or_skip_var, - ext_var: env.new_ext_var(ExtensionKind::TagUnion), - name: "Keep".into(), - arguments: vec![(decode_custom_ret_var, Loc::at_zero(decode_custom))], - } - }; - - let branch = { - // "first" -> - // Keep (Decode.custom \bytes, fmt -> - // when Decode.decodeWith bytes Decode.decoder fmt is - // rec -> - // { - // rest: rec.rest, - // result: when rec.result is - // Ok val -> Ok {state & first: Ok val}, - // Err err -> Err err - // } - // ) - WhenBranch { - patterns: vec![WhenBranchPattern { - pattern: Loc::at_zero(Pattern::StrLiteral(field_name.into())), - degenerate: false, - }], - value: Loc::at_zero(keep), - guard: None, - redundant: RedundantMark::known_non_redundant(), - } - }; - - branches.push(branch); - } - - // Example: `_ -> Skip` - let default_branch = WhenBranch { - patterns: vec![WhenBranchPattern { - pattern: Loc::at_zero(Pattern::Underscore), - degenerate: false, - }], - value: Loc::at_zero(Expr::Tag { - tag_union_var: keep_or_skip_var, - ext_var: env.new_ext_var(ExtensionKind::TagUnion), - name: "Skip".into(), - arguments: Vec::new(), - }), - guard: None, - redundant: RedundantMark::known_non_redundant(), - }; - - branches.push(default_branch); - - // when field is - let body = Expr::When { - loc_cond: Box::new(Loc::at_zero(Expr::Var(field_arg_symbol, Variable::STR))), - cond_var: Variable::STR, - expr_var: keep_or_skip_var, - region: Region::zero(), - branches, - branches_cond_var: Variable::STR, - exhaustive: ExhaustiveMark::known_exhaustive(), - }; - - let step_field_closure = env.new_symbol("stepField"); - let function_type = env.subs.fresh_unnamed_flex_var(); - let closure_type = { - let lambda_set = LambdaSet { - solved: UnionLambdas::tag_without_arguments(env.subs, step_field_closure), - recursion_var: OptVariable::NONE, - unspecialized: Default::default(), - ambient_function: function_type, - }; - - synth_var(env.subs, Content::LambdaSet(lambda_set)) - }; - - { - let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::STR]); - - env.subs.set_content( - function_type, - Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)), - ) - }; - - let expr = Expr::Closure(ClosureData { - function_type, - closure_type, - return_type: keep_or_skip_var, - name: step_field_closure, - captured_symbols: Vec::new(), - recursive: Recursive::NotRecursive, - arguments: vec![ - ( - state_record_var, - AnnotatedMark::known_exhaustive(), - Loc::at_zero(Pattern::Identifier(state_arg_symbol)), - ), - ( - Variable::STR, - AnnotatedMark::known_exhaustive(), - Loc::at_zero(Pattern::Identifier(field_arg_symbol)), - ), - ], - loc_body: Box::new(Loc::at_zero(body)), - }); - - (expr, function_type) -} - -// Example: -// finalizer = \rec -> -// when rec.first is -// Ok first -> -// when rec.second is -// Ok second -> Ok {first, second} -// Err NoField -> Err TooShort -// Err NoField -> Err TooShort -fn decoder_record_finalizer( - env: &mut Env, - state_record_var: Variable, - fields: &[Lowercase], - field_vars: &[Variable], - result_field_vars: &[Variable], -) -> (Expr, Variable, Variable) { - let state_arg_symbol = env.new_symbol("stateRecord"); - let mut fields_map = SendMap::default(); - let mut pattern_symbols = Vec::with_capacity(fields.len()); - let decode_err_var = { - let flat_type = FlatType::TagUnion( - UnionTags::tag_without_arguments(env.subs, "TooShort".into()), - TagExt::Any(Variable::EMPTY_TAG_UNION), - ); - - synth_var(env.subs, Content::Structure(flat_type)) - }; - - for (field_name, &field_var) in fields.iter().zip(field_vars.iter()) { - let symbol = env.new_symbol(field_name.as_str()); - - pattern_symbols.push(symbol); - - let field_expr = Expr::Var(symbol, field_var); - let field = Field { - var: field_var, - region: Region::zero(), - loc_expr: Box::new(Loc::at_zero(field_expr)), - }; - - fields_map.insert(field_name.clone(), field); - } - - // The bottom of the happy path - return the decoded record {first: a, second: b} wrapped with - // "Ok". - let return_type_var; - let mut body = { - let subs = &mut env.subs; - let record_field_iter = fields - .iter() - .zip(field_vars.iter()) - .map(|(field_name, &field_var)| (field_name.clone(), RecordField::Required(field_var))); - let flat_type = FlatType::Record( - RecordFields::insert_into_subs(subs, record_field_iter), - Variable::EMPTY_RECORD, - ); - let done_record_var = synth_var(subs, Content::Structure(flat_type)); - let done_record = Expr::Record { - record_var: done_record_var, - fields: fields_map, - }; - - return_type_var = { - let flat_type = FlatType::TagUnion( - UnionTags::for_result(subs, done_record_var, decode_err_var), - TagExt::Any(Variable::EMPTY_TAG_UNION), - ); - - synth_var(subs, Content::Structure(flat_type)) - }; - - Expr::Tag { - tag_union_var: return_type_var, - ext_var: env.new_ext_var(ExtensionKind::TagUnion), - name: "Ok".into(), - arguments: vec![(done_record_var, Loc::at_zero(done_record))], - } - }; - - // Unwrap each result in the decoded state - // - // when rec.first is - // Ok first -> ...happy path... - // Err NoField -> Err TooShort - for (((symbol, field_name), &field_var), &result_field_var) in pattern_symbols - .iter() - .rev() - .zip(fields.iter().rev()) - .zip(field_vars.iter().rev()) - .zip(result_field_vars.iter().rev()) - { - // when rec.first is - let cond_expr = Expr::RecordAccess { - record_var: state_record_var, - ext_var: env.new_ext_var(ExtensionKind::Record), - field_var: result_field_var, - loc_expr: Box::new(Loc::at_zero(Expr::Var(state_arg_symbol, state_record_var))), - field: field_name.clone(), - }; - - // Example: `Ok x -> expr` - let ok_branch = WhenBranch { - patterns: vec![WhenBranchPattern { - pattern: Loc::at_zero(Pattern::AppliedTag { - whole_var: result_field_var, - ext_var: Variable::EMPTY_TAG_UNION, - tag_name: "Ok".into(), - arguments: vec![(field_var, Loc::at_zero(Pattern::Identifier(*symbol)))], - }), - degenerate: false, - }], - value: Loc::at_zero(body), - guard: None, - redundant: RedundantMark::known_non_redundant(), - }; - - // Example: `_ -> Err TooShort` - let err_branch = WhenBranch { - patterns: vec![WhenBranchPattern { - pattern: Loc::at_zero(Pattern::Underscore), - degenerate: false, - }], - value: Loc::at_zero(Expr::Tag { - tag_union_var: return_type_var, - ext_var: env.new_ext_var(ExtensionKind::TagUnion), - name: "Err".into(), - arguments: vec![( - decode_err_var, - Loc::at_zero(Expr::Tag { - tag_union_var: decode_err_var, - ext_var: Variable::EMPTY_TAG_UNION, - name: "TooShort".into(), - arguments: Vec::new(), - }), - )], - }), - guard: None, - redundant: RedundantMark::known_non_redundant(), - }; - - body = Expr::When { - loc_cond: Box::new(Loc::at_zero(cond_expr)), - cond_var: result_field_var, - expr_var: return_type_var, - region: Region::zero(), - branches: vec![ok_branch, err_branch], - branches_cond_var: result_field_var, - exhaustive: ExhaustiveMark::known_exhaustive(), - }; - } - - let function_var = synth_var(env.subs, Content::Error); // We'll fix this up in subs later. - let function_symbol = env.new_symbol("finalizer"); - let lambda_set = LambdaSet { - solved: UnionLambdas::tag_without_arguments(env.subs, function_symbol), - recursion_var: OptVariable::NONE, - unspecialized: Default::default(), - ambient_function: function_var, - }; - let closure_type = synth_var(env.subs, Content::LambdaSet(lambda_set)); - let flat_type = FlatType::Func( - SubsSlice::insert_into_subs(env.subs, [state_record_var]), - closure_type, - return_type_var, - ); - - // Fix up function_var so it's not Content::Error anymore - env.subs - .set_content(function_var, Content::Structure(flat_type)); - - let finalizer = Expr::Closure(ClosureData { - function_type: function_var, - closure_type, - return_type: return_type_var, - name: function_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::NotRecursive, - arguments: vec![( - state_record_var, - AnnotatedMark::known_exhaustive(), - Loc::at_zero(Pattern::Identifier(state_arg_symbol)), - )], - loc_body: Box::new(Loc::at_zero(body)), - }); - - (finalizer, function_var, decode_err_var) -} - -// Example: -// initialState : {first: Result a [NoField], second: Result b [NoField]} -// initialState = {first: Err NoField, second: Err NoField} -fn decoder_record_initial_state( - env: &mut Env<'_>, - field_names: &[Lowercase], - field_vars: &mut Vec, - result_field_vars: &mut Vec, -) -> (Variable, Expr) { - let mut initial_state_fields = SendMap::default(); - - for field_name in field_names { - let subs = &mut env.subs; - let field_var = subs.fresh_unnamed_flex_var(); - - field_vars.push(field_var); - - let no_field_label = "NoField"; - let union_tags = UnionTags::tag_without_arguments(subs, no_field_label.into()); - let no_field_var = synth_var( - subs, - Content::Structure(FlatType::TagUnion( - union_tags, - TagExt::Any(Variable::EMPTY_TAG_UNION), - )), - ); - let no_field = Expr::Tag { - tag_union_var: no_field_var, - ext_var: Variable::EMPTY_TAG_UNION, - name: no_field_label.into(), - arguments: Vec::new(), - }; - let err_label = "Err"; - let union_tags = UnionTags::for_result(subs, field_var, no_field_var); - let result_var = synth_var( - subs, - Content::Structure(FlatType::TagUnion( - union_tags, - TagExt::Any(Variable::EMPTY_TAG_UNION), - )), - ); - let field_expr = Expr::Tag { - tag_union_var: result_var, - ext_var: env.new_ext_var(ExtensionKind::TagUnion), - name: err_label.into(), - arguments: vec![(no_field_var, Loc::at_zero(no_field))], - }; - result_field_vars.push(result_var); - let field = Field { - var: result_var, - region: Region::zero(), - loc_expr: Box::new(Loc::at_zero(field_expr)), - }; - - initial_state_fields.insert(field_name.clone(), field); - } - - let subs = &mut env.subs; - let record_field_iter = field_names - .iter() - .zip(result_field_vars.iter()) - .map(|(field_name, &var)| (field_name.clone(), RecordField::Required(var))); - let flat_type = FlatType::Record( - RecordFields::insert_into_subs(subs, record_field_iter), - Variable::EMPTY_RECORD, - ); - - let state_record_var = synth_var(subs, Content::Structure(flat_type)); - - ( - state_record_var, - Expr::Record { - record_var: state_record_var, - fields: initial_state_fields, - }, - ) -} - -fn decoder_list(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable) { - // Build - // - // def_symbol : Decoder (List elem) fmt | elem has Decoding, fmt has DecoderFormatting - // def_symbol = Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt - // - // TODO try to reduce to `Decode.list Decode.decoder` - - use Expr::*; - - // Decode.list Decode.decoder : Decoder (List elem) fmt - let (decode_list_call, this_decode_list_ret_var) = { - // List elem - let elem_var = env.subs.fresh_unnamed_flex_var(); - - // Decode.decoder : Decoder elem fmt | elem has Decoding, fmt has EncoderFormatting - let (elem_decoder, elem_decoder_var) = { - // build `Decode.decoder : Decoder elem fmt` type - // Decoder val fmt | val has Decoding, fmt has EncoderFormatting - let elem_decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER); - - // set val ~ elem - let val_var = match env.subs.get_content_without_compacting(elem_decoder_var) { - Content::Alias(Symbol::DECODE_DECODER_OPAQUE, vars, _, AliasKind::Opaque) - if vars.type_variables_len == 2 => - { - env.subs.get_subs_slice(vars.type_variables())[0] - } - _ => internal_error!("Decode.decode not an opaque type"), - }; - - env.unify(val_var, elem_var); - - ( - AbilityMember(Symbol::DECODE_DECODER, None, elem_decoder_var), - elem_decoder_var, - ) - }; - - // Build `Decode.list Decode.decoder` type - // Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting - let decode_list_fn_var = env.import_builtin_symbol_var(Symbol::DECODE_LIST); - - // Decoder elem fmt -a-> b - let elem_decoder_var_slice = SubsSlice::insert_into_subs(env.subs, [elem_decoder_var]); - let this_decode_list_clos_var = env.subs.fresh_unnamed_flex_var(); - let this_decode_list_ret_var = env.subs.fresh_unnamed_flex_var(); - let this_decode_list_fn_var = synth_var( - env.subs, - Content::Structure(FlatType::Func( - elem_decoder_var_slice, - this_decode_list_clos_var, - this_decode_list_ret_var, - )), - ); - - // Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting - // ~ Decoder elem fmt -a -> b - env.unify(decode_list_fn_var, this_decode_list_fn_var); - - let decode_list_member = AbilityMember(Symbol::DECODE_LIST, None, this_decode_list_fn_var); - let decode_list_fn = Box::new(( - decode_list_fn_var, - Loc::at_zero(decode_list_member), - this_decode_list_clos_var, - this_decode_list_ret_var, - )); - - let decode_list_call = Call( - decode_list_fn, - vec![(elem_decoder_var, Loc::at_zero(elem_decoder))], - CalledVia::Space, - ); - - (decode_list_call, this_decode_list_ret_var) - }; - - let bytes_sym = env.new_symbol("bytes"); - let fmt_sym = env.new_symbol("fmt"); - let fmt_var = env.subs.fresh_unnamed_flex_var(); - let captures = vec![]; - - wrap_in_decode_custom_decode_with( - env, - bytes_sym, - (fmt_sym, fmt_var), - captures, - (decode_list_call, this_decode_list_ret_var), - ) -} - // Wraps `myDecoder` in `Decode.custom \bytes, fmt -> Decode.decodeWith bytes myDecoder fmt`. -// I think most can be removed when https://github.com/roc-lang/roc/issues/3724 is resolved. +// +// Needed to work around the Higher-Region Restriction. See https://github.com/roc-lang/roc/issues/3724. fn wrap_in_decode_custom_decode_with( env: &mut Env, bytes: Symbol, diff --git a/crates/compiler/derive/src/decoding/list.rs b/crates/compiler/derive/src/decoding/list.rs new file mode 100644 index 0000000000..53c89fd1bd --- /dev/null +++ b/crates/compiler/derive/src/decoding/list.rs @@ -0,0 +1,104 @@ +use roc_can::expr::Expr; + +use roc_error_macros::internal_error; +use roc_module::called_via::CalledVia; + +use roc_module::symbol::Symbol; +use roc_region::all::Loc; +use roc_types::subs::{Content, FlatType, GetSubsSlice, SubsSlice, Variable}; +use roc_types::types::AliasKind; + +use crate::decoding::wrap_in_decode_custom_decode_with; +use crate::synth_var; +use crate::util::Env; + +pub(crate) fn decoder(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable) { + // Build + // + // def_symbol : Decoder (List elem) fmt | elem has Decoding, fmt has DecoderFormatting + // def_symbol = Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt + // + // NB: reduction to `Decode.list Decode.decoder` is not possible to the HRR. + + use Expr::*; + + // Decode.list Decode.decoder : Decoder (List elem) fmt + let (decode_list_call, this_decode_list_ret_var) = { + // List elem + let elem_var = env.subs.fresh_unnamed_flex_var(); + + // Decode.decoder : Decoder elem fmt | elem has Decoding, fmt has EncoderFormatting + let (elem_decoder, elem_decoder_var) = { + // build `Decode.decoder : Decoder elem fmt` type + // Decoder val fmt | val has Decoding, fmt has EncoderFormatting + let elem_decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER); + + // set val ~ elem + let val_var = match env.subs.get_content_without_compacting(elem_decoder_var) { + Content::Alias(Symbol::DECODE_DECODER_OPAQUE, vars, _, AliasKind::Opaque) + if vars.type_variables_len == 2 => + { + env.subs.get_subs_slice(vars.type_variables())[0] + } + _ => internal_error!("Decode.decode not an opaque type"), + }; + + env.unify(val_var, elem_var); + + ( + AbilityMember(Symbol::DECODE_DECODER, None, elem_decoder_var), + elem_decoder_var, + ) + }; + + // Build `Decode.list Decode.decoder` type + // Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting + let decode_list_fn_var = env.import_builtin_symbol_var(Symbol::DECODE_LIST); + + // Decoder elem fmt -a-> b + let elem_decoder_var_slice = SubsSlice::insert_into_subs(env.subs, [elem_decoder_var]); + let this_decode_list_clos_var = env.subs.fresh_unnamed_flex_var(); + let this_decode_list_ret_var = env.subs.fresh_unnamed_flex_var(); + let this_decode_list_fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + elem_decoder_var_slice, + this_decode_list_clos_var, + this_decode_list_ret_var, + )), + ); + + // Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting + // ~ Decoder elem fmt -a -> b + env.unify(decode_list_fn_var, this_decode_list_fn_var); + + let decode_list_member = AbilityMember(Symbol::DECODE_LIST, None, this_decode_list_fn_var); + let decode_list_fn = Box::new(( + decode_list_fn_var, + Loc::at_zero(decode_list_member), + this_decode_list_clos_var, + this_decode_list_ret_var, + )); + + let decode_list_call = Call( + decode_list_fn, + vec![(elem_decoder_var, Loc::at_zero(elem_decoder))], + CalledVia::Space, + ); + + (decode_list_call, this_decode_list_ret_var) + }; + + let bytes_sym = env.new_symbol("bytes"); + let fmt_sym = env.new_symbol("fmt"); + let fmt_var = env.subs.fresh_unnamed_flex_var(); + let captures = vec![]; + + wrap_in_decode_custom_decode_with( + env, + bytes_sym, + (fmt_sym, fmt_var), + captures, + (decode_list_call, this_decode_list_ret_var), + ) +} diff --git a/crates/compiler/derive/src/decoding/record.rs b/crates/compiler/derive/src/decoding/record.rs new file mode 100644 index 0000000000..d62ffe2f90 --- /dev/null +++ b/crates/compiler/derive/src/decoding/record.rs @@ -0,0 +1,990 @@ +use roc_can::expr::{ + AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch, WhenBranchPattern, +}; +use roc_can::pattern::Pattern; +use roc_collections::SendMap; +use roc_module::called_via::CalledVia; +use roc_module::ident::Lowercase; +use roc_module::symbol::Symbol; +use roc_region::all::{Loc, Region}; +use roc_types::subs::{ + Content, ExhaustiveMark, FlatType, LambdaSet, OptVariable, RecordFields, RedundantMark, + SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, +}; +use roc_types::types::RecordField; + +use crate::synth_var; +use crate::util::{Env, ExtensionKind}; + +use super::wrap_in_decode_custom_decode_with; + +/// Implements decoding of a record. For example, for +/// +/// ```text +/// {first: a, second: b} +/// ``` +/// +/// we'd like to generate an impl like +/// +/// ```roc +/// decoder : Decoder {first: a, second: b} fmt | a has Decoding, b has Decoding, fmt has DecoderFormatting +/// decoder = +/// initialState : {f0: Result a [NoField], f1: Result b [NoField]} +/// initialState = {f0: Err NoField, f1: Err NoField} +/// +/// stepField = \state, field -> +/// when field is +/// "first" -> +/// Keep (Decode.custom \bytes, fmt -> +/// when Decode.decodeWith bytes Decode.decoder fmt is +/// {result, rest} -> +/// {result: Result.map result \val -> {state & f0: Ok val}, rest}) +/// "second" -> +/// Keep (Decode.custom \bytes, fmt -> +/// when Decode.decodeWith bytes Decode.decoder fmt is +/// {result, rest} -> +/// {result: Result.map result \val -> {state & f1: Ok val}, rest}) +/// _ -> Skip +/// +/// finalizer = \{f0, f1} -> +/// when f0 is +/// Ok first -> +/// when f1 is +/// Ok second -> Ok {first, second} +/// Err NoField -> Err TooShort +/// Err NoField -> Err TooShort +/// +/// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.record initialState stepField finalizer) fmt +/// ``` +pub(crate) fn decoder( + env: &mut Env, + _def_symbol: Symbol, + fields: Vec, +) -> (Expr, Variable) { + // The decoded type of each field in the record, e.g. {first: a, second: b}. + let mut field_vars = Vec::with_capacity(fields.len()); + // The type of each field in the decoding state, e.g. {first: Result a [NoField], second: Result b [NoField]} + let mut result_field_vars = Vec::with_capacity(fields.len()); + + // initialState = ... + let (initial_state_var, initial_state) = + initial_state(env, &fields, &mut field_vars, &mut result_field_vars); + + // finalizer = ... + let (finalizer, finalizer_var, decode_err_var) = finalizer( + env, + initial_state_var, + &fields, + &field_vars, + &result_field_vars, + ); + + // stepField = ... + let (step_field, step_var) = step_field( + env, + fields, + &field_vars, + &result_field_vars, + initial_state_var, + decode_err_var, + ); + + // Build up the type of `Decode.record` we expect + let record_decoder_var = env.subs.fresh_unnamed_flex_var(); + let decode_record_lambda_set = env.subs.fresh_unnamed_flex_var(); + let decode_record_var = env.import_builtin_symbol_var(Symbol::DECODE_RECORD); + let this_decode_record_var = { + let flat_type = FlatType::Func( + SubsSlice::insert_into_subs(env.subs, [initial_state_var, step_var, finalizer_var]), + decode_record_lambda_set, + record_decoder_var, + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + env.unify(decode_record_var, this_decode_record_var); + + // Decode.record initialState stepField finalizer + let call_decode_record = Expr::Call( + Box::new(( + this_decode_record_var, + Loc::at_zero(Expr::AbilityMember( + Symbol::DECODE_RECORD, + None, + this_decode_record_var, + )), + decode_record_lambda_set, + record_decoder_var, + )), + vec![ + (initial_state_var, Loc::at_zero(initial_state)), + (step_var, Loc::at_zero(step_field)), + (finalizer_var, Loc::at_zero(finalizer)), + ], + CalledVia::Space, + ); + + let (call_decode_custom, decode_custom_ret_var) = { + let bytes_sym = env.new_symbol("bytes"); + let fmt_sym = env.new_symbol("fmt"); + let fmt_var = env.subs.fresh_unnamed_flex_var(); + + let (decode_custom, decode_custom_var) = wrap_in_decode_custom_decode_with( + env, + bytes_sym, + (fmt_sym, fmt_var), + vec![], + (call_decode_record, record_decoder_var), + ); + + (decode_custom, decode_custom_var) + }; + + (call_decode_custom, decode_custom_ret_var) +} + +// Example: +// stepField = \state, field -> +// when field is +// "first" -> +// Keep (Decode.custom \bytes, fmt -> +// # Uses a single-branch `when` because `let` is more expensive to monomorphize +// # due to checks for polymorphic expressions, and `rec` would be polymorphic. +// when Decode.decodeWith bytes Decode.decoder fmt is +// rec -> +// { +// rest: rec.rest, +// result: when rec.result is +// Ok val -> Ok {state & first: Ok val}, +// Err err -> Err err +// }) +// +// "second" -> +// Keep (Decode.custom \bytes, fmt -> +// when Decode.decodeWith bytes Decode.decoder fmt is +// rec -> +// { +// rest: rec.rest, +// result: when rec.result is +// Ok val -> Ok {state & second: Ok val}, +// Err err -> Err err +// }) +// +// _ -> Skip +fn step_field( + env: &mut Env, + fields: Vec, + field_vars: &[Variable], + result_field_vars: &[Variable], + state_record_var: Variable, + decode_err_var: Variable, +) -> (Expr, Variable) { + let state_arg_symbol = env.new_symbol("stateRecord"); + let field_arg_symbol = env.new_symbol("field"); + + // +1 because of the default branch. + let mut branches = Vec::with_capacity(fields.len() + 1); + let keep_payload_var = env.subs.fresh_unnamed_flex_var(); + let keep_or_skip_var = { + let keep_payload_subs_slice = SubsSlice::insert_into_subs(env.subs, [keep_payload_var]); + let flat_type = FlatType::TagUnion( + UnionTags::insert_slices_into_subs( + env.subs, + [ + ("Keep".into(), keep_payload_subs_slice), + ("Skip".into(), Default::default()), + ], + ), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + for ((field_name, &field_var), &result_field_var) in fields + .into_iter() + .zip(field_vars.iter()) + .zip(result_field_vars.iter()) + { + // Example: + // "first" -> + // Keep (Decode.custom \bytes, fmt -> + // # Uses a single-branch `when` because `let` is more expensive to monomorphize + // # due to checks for polymorphic expressions, and `rec` would be polymorphic. + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + // } + // ) + + let this_custom_callback_var; + let custom_callback_ret_var; + let custom_callback = { + // \bytes, fmt -> + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + // } + let bytes_arg_symbol = env.new_symbol("bytes"); + let fmt_arg_symbol = env.new_symbol("fmt"); + let bytes_arg_var = env.subs.fresh_unnamed_flex_var(); + let fmt_arg_var = env.subs.fresh_unnamed_flex_var(); + + // rec.result : [Ok field_var, Err DecodeError] + let rec_dot_result = { + let tag_union = FlatType::TagUnion( + UnionTags::for_result(env.subs, field_var, decode_err_var), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(env.subs, Content::Structure(tag_union)) + }; + + // rec : { rest: List U8, result: (typeof rec.result) } + let rec_var = { + let fields = RecordFields::insert_into_subs( + env.subs, + [ + ("rest".into(), RecordField::Required(Variable::LIST_U8)), + ("result".into(), RecordField::Required(rec_dot_result)), + ], + ); + let record = FlatType::Record(fields, Variable::EMPTY_RECORD); + + synth_var(env.subs, Content::Structure(record)) + }; + + // `Decode.decoder` for the field's value + let decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER); + let decode_with_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH); + let lambda_set_var = env.subs.fresh_unnamed_flex_var(); + let this_decode_with_var = { + let subs_slice = SubsSlice::insert_into_subs( + env.subs, + [bytes_arg_var, decoder_var, fmt_arg_var], + ); + let this_decode_with_var = synth_var( + env.subs, + Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)), + ); + + env.unify(decode_with_var, this_decode_with_var); + + this_decode_with_var + }; + + // The result of decoding this field's value - either the updated state, or a decoding error. + let when_expr_var = { + let flat_type = FlatType::TagUnion( + UnionTags::for_result(env.subs, state_record_var, decode_err_var), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + // What our decoder passed to `Decode.custom` returns - the result of decoding the + // field's value, and the remaining bytes. + custom_callback_ret_var = { + let rest_field = RecordField::Required(Variable::LIST_U8); + let result_field = RecordField::Required(when_expr_var); + let flat_type = FlatType::Record( + RecordFields::insert_into_subs( + env.subs, + [("rest".into(), rest_field), ("result".into(), result_field)], + ), + Variable::EMPTY_RECORD, + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + let custom_callback_body = { + let rec_symbol = env.new_symbol("rec"); + + // # Uses a single-branch `when` because `let` is more expensive to monomorphize + // # due to checks for polymorphic expressions, and `rec` would be polymorphic. + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + // } + let branch_body = { + let result_val = { + // result: when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + let ok_val_symbol = env.new_symbol("val"); + let err_val_symbol = env.new_symbol("err"); + let ok_branch_expr = { + // Ok {state & first: Ok val}, + let mut updates = SendMap::default(); + + updates.insert( + field_name.clone(), + Field { + var: result_field_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(Expr::Tag { + tag_union_var: result_field_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Ok".into(), + arguments: vec![( + field_var, + Loc::at_zero(Expr::Var(ok_val_symbol, field_var)), + )], + })), + }, + ); + + let updated_record = Expr::RecordUpdate { + record_var: state_record_var, + ext_var: env.new_ext_var(ExtensionKind::Record), + symbol: state_arg_symbol, + updates, + }; + + Expr::Tag { + tag_union_var: when_expr_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Ok".into(), + arguments: vec![(state_record_var, Loc::at_zero(updated_record))], + } + }; + + let branches = vec![ + // Ok val -> Ok {state & first: Ok val}, + WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::AppliedTag { + whole_var: rec_dot_result, + ext_var: Variable::EMPTY_TAG_UNION, + tag_name: "Ok".into(), + arguments: vec![( + field_var, + Loc::at_zero(Pattern::Identifier(ok_val_symbol)), + )], + }), + degenerate: false, + }], + value: Loc::at_zero(ok_branch_expr), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }, + // Err err -> Err err + WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::AppliedTag { + whole_var: rec_dot_result, + ext_var: Variable::EMPTY_TAG_UNION, + tag_name: "Err".into(), + arguments: vec![( + decode_err_var, + Loc::at_zero(Pattern::Identifier(err_val_symbol)), + )], + }), + degenerate: false, + }], + value: Loc::at_zero(Expr::Tag { + tag_union_var: when_expr_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Err".into(), + arguments: vec![( + decode_err_var, + Loc::at_zero(Expr::Var(err_val_symbol, decode_err_var)), + )], + }), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }, + ]; + + // when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + Expr::When { + loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess { + record_var: rec_var, + ext_var: env.new_ext_var(ExtensionKind::Record), + field_var: rec_dot_result, + loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))), + field: "result".into(), + })), + cond_var: rec_dot_result, + expr_var: when_expr_var, + region: Region::zero(), + branches, + branches_cond_var: rec_dot_result, + exhaustive: ExhaustiveMark::known_exhaustive(), + } + }; + + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + // } + let mut fields_map = SendMap::default(); + + fields_map.insert( + "rest".into(), + Field { + var: Variable::LIST_U8, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess { + record_var: rec_var, + ext_var: env.new_ext_var(ExtensionKind::Record), + field_var: Variable::LIST_U8, + loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))), + field: "rest".into(), + })), + }, + ); + + // result: when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + fields_map.insert( + "result".into(), + Field { + var: when_expr_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(result_val)), + }, + ); + + Expr::Record { + record_var: custom_callback_ret_var, + fields: fields_map, + } + }; + + let branch = WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::Identifier(rec_symbol)), + degenerate: false, + }], + value: Loc::at_zero(branch_body), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }; + + let condition_expr = Expr::Call( + Box::new(( + this_decode_with_var, + Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)), + lambda_set_var, + rec_var, + )), + vec![ + ( + Variable::LIST_U8, + Loc::at_zero(Expr::Var(bytes_arg_symbol, Variable::LIST_U8)), + ), + ( + decoder_var, + Loc::at_zero(Expr::AbilityMember( + Symbol::DECODE_DECODER, + None, + decoder_var, + )), + ), + ( + fmt_arg_var, + Loc::at_zero(Expr::Var(fmt_arg_symbol, fmt_arg_var)), + ), + ], + CalledVia::Space, + ); + + // when Decode.decodeWith bytes Decode.decoder fmt is + Expr::When { + loc_cond: Box::new(Loc::at_zero(condition_expr)), + cond_var: rec_var, + expr_var: custom_callback_ret_var, + region: Region::zero(), + branches: vec![branch], + branches_cond_var: rec_var, + exhaustive: ExhaustiveMark::known_exhaustive(), + } + }; + + let custom_closure_symbol = env.new_symbol("customCallback"); + this_custom_callback_var = env.subs.fresh_unnamed_flex_var(); + let custom_callback_lambda_set_var = { + let content = Content::LambdaSet(LambdaSet { + solved: UnionLambdas::insert_into_subs( + env.subs, + [(custom_closure_symbol, [state_record_var])], + ), + recursion_var: OptVariable::NONE, + unspecialized: Default::default(), + ambient_function: this_custom_callback_var, + }); + let custom_callback_lambda_set_var = synth_var(env.subs, content); + let subs_slice = + SubsSlice::insert_into_subs(env.subs, [bytes_arg_var, fmt_arg_var]); + + env.subs.set_content( + this_custom_callback_var, + Content::Structure(FlatType::Func( + subs_slice, + custom_callback_lambda_set_var, + custom_callback_ret_var, + )), + ); + + custom_callback_lambda_set_var + }; + + // \bytes, fmt -> … + Expr::Closure(ClosureData { + function_type: this_custom_callback_var, + closure_type: custom_callback_lambda_set_var, + return_type: custom_callback_ret_var, + name: custom_closure_symbol, + captured_symbols: vec![(state_arg_symbol, state_record_var)], + recursive: Recursive::NotRecursive, + arguments: vec![ + ( + bytes_arg_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(bytes_arg_symbol)), + ), + ( + fmt_arg_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(fmt_arg_symbol)), + ), + ], + loc_body: Box::new(Loc::at_zero(custom_callback_body)), + }) + }; + + let decode_custom_ret_var = env.subs.fresh_unnamed_flex_var(); + let decode_custom = { + let decode_custom_var = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM); + let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var(); + let this_decode_custom_var = { + let subs_slice = SubsSlice::insert_into_subs(env.subs, [this_custom_callback_var]); + let flat_type = + FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + env.unify(decode_custom_var, this_decode_custom_var); + + // Decode.custom \bytes, fmt -> … + Expr::Call( + Box::new(( + this_decode_custom_var, + Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)), + decode_custom_closure_var, + decode_custom_ret_var, + )), + vec![(this_custom_callback_var, Loc::at_zero(custom_callback))], + CalledVia::Space, + ) + }; + + env.unify(keep_payload_var, decode_custom_ret_var); + + let keep = { + // Keep (Decode.custom \bytes, fmt -> + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + // } + // ) + Expr::Tag { + tag_union_var: keep_or_skip_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Keep".into(), + arguments: vec![(decode_custom_ret_var, Loc::at_zero(decode_custom))], + } + }; + + let branch = { + // "first" -> + // Keep (Decode.custom \bytes, fmt -> + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & first: Ok val}, + // Err err -> Err err + // } + // ) + WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::StrLiteral(field_name.into())), + degenerate: false, + }], + value: Loc::at_zero(keep), + guard: None, + redundant: RedundantMark::known_non_redundant(), + } + }; + + branches.push(branch); + } + + // Example: `_ -> Skip` + let default_branch = WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::Underscore), + degenerate: false, + }], + value: Loc::at_zero(Expr::Tag { + tag_union_var: keep_or_skip_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Skip".into(), + arguments: Vec::new(), + }), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }; + + branches.push(default_branch); + + // when field is + let body = Expr::When { + loc_cond: Box::new(Loc::at_zero(Expr::Var(field_arg_symbol, Variable::STR))), + cond_var: Variable::STR, + expr_var: keep_or_skip_var, + region: Region::zero(), + branches, + branches_cond_var: Variable::STR, + exhaustive: ExhaustiveMark::known_exhaustive(), + }; + + let step_field_closure = env.new_symbol("stepField"); + let function_type = env.subs.fresh_unnamed_flex_var(); + let closure_type = { + let lambda_set = LambdaSet { + solved: UnionLambdas::tag_without_arguments(env.subs, step_field_closure), + recursion_var: OptVariable::NONE, + unspecialized: Default::default(), + ambient_function: function_type, + }; + + synth_var(env.subs, Content::LambdaSet(lambda_set)) + }; + + { + let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::STR]); + + env.subs.set_content( + function_type, + Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)), + ) + }; + + let expr = Expr::Closure(ClosureData { + function_type, + closure_type, + return_type: keep_or_skip_var, + name: step_field_closure, + captured_symbols: Vec::new(), + recursive: Recursive::NotRecursive, + arguments: vec![ + ( + state_record_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(state_arg_symbol)), + ), + ( + Variable::STR, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(field_arg_symbol)), + ), + ], + loc_body: Box::new(Loc::at_zero(body)), + }); + + (expr, function_type) +} + +// Example: +// finalizer = \rec -> +// when rec.first is +// Ok first -> +// when rec.second is +// Ok second -> Ok {first, second} +// Err NoField -> Err TooShort +// Err NoField -> Err TooShort +fn finalizer( + env: &mut Env, + state_record_var: Variable, + fields: &[Lowercase], + field_vars: &[Variable], + result_field_vars: &[Variable], +) -> (Expr, Variable, Variable) { + let state_arg_symbol = env.new_symbol("stateRecord"); + let mut fields_map = SendMap::default(); + let mut pattern_symbols = Vec::with_capacity(fields.len()); + let decode_err_var = { + let flat_type = FlatType::TagUnion( + UnionTags::tag_without_arguments(env.subs, "TooShort".into()), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + for (field_name, &field_var) in fields.iter().zip(field_vars.iter()) { + let symbol = env.new_symbol(field_name.as_str()); + + pattern_symbols.push(symbol); + + let field_expr = Expr::Var(symbol, field_var); + let field = Field { + var: field_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(field_expr)), + }; + + fields_map.insert(field_name.clone(), field); + } + + // The bottom of the happy path - return the decoded record {first: a, second: b} wrapped with + // "Ok". + let return_type_var; + let mut body = { + let subs = &mut env.subs; + let record_field_iter = fields + .iter() + .zip(field_vars.iter()) + .map(|(field_name, &field_var)| (field_name.clone(), RecordField::Required(field_var))); + let flat_type = FlatType::Record( + RecordFields::insert_into_subs(subs, record_field_iter), + Variable::EMPTY_RECORD, + ); + let done_record_var = synth_var(subs, Content::Structure(flat_type)); + let done_record = Expr::Record { + record_var: done_record_var, + fields: fields_map, + }; + + return_type_var = { + let flat_type = FlatType::TagUnion( + UnionTags::for_result(subs, done_record_var, decode_err_var), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(subs, Content::Structure(flat_type)) + }; + + Expr::Tag { + tag_union_var: return_type_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Ok".into(), + arguments: vec![(done_record_var, Loc::at_zero(done_record))], + } + }; + + // Unwrap each result in the decoded state + // + // when rec.first is + // Ok first -> ...happy path... + // Err NoField -> Err TooShort + for (((symbol, field_name), &field_var), &result_field_var) in pattern_symbols + .iter() + .rev() + .zip(fields.iter().rev()) + .zip(field_vars.iter().rev()) + .zip(result_field_vars.iter().rev()) + { + // when rec.first is + let cond_expr = Expr::RecordAccess { + record_var: state_record_var, + ext_var: env.new_ext_var(ExtensionKind::Record), + field_var: result_field_var, + loc_expr: Box::new(Loc::at_zero(Expr::Var(state_arg_symbol, state_record_var))), + field: field_name.clone(), + }; + + // Example: `Ok x -> expr` + let ok_branch = WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::AppliedTag { + whole_var: result_field_var, + ext_var: Variable::EMPTY_TAG_UNION, + tag_name: "Ok".into(), + arguments: vec![(field_var, Loc::at_zero(Pattern::Identifier(*symbol)))], + }), + degenerate: false, + }], + value: Loc::at_zero(body), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }; + + // Example: `_ -> Err TooShort` + let err_branch = WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::Underscore), + degenerate: false, + }], + value: Loc::at_zero(Expr::Tag { + tag_union_var: return_type_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Err".into(), + arguments: vec![( + decode_err_var, + Loc::at_zero(Expr::Tag { + tag_union_var: decode_err_var, + ext_var: Variable::EMPTY_TAG_UNION, + name: "TooShort".into(), + arguments: Vec::new(), + }), + )], + }), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }; + + body = Expr::When { + loc_cond: Box::new(Loc::at_zero(cond_expr)), + cond_var: result_field_var, + expr_var: return_type_var, + region: Region::zero(), + branches: vec![ok_branch, err_branch], + branches_cond_var: result_field_var, + exhaustive: ExhaustiveMark::known_exhaustive(), + }; + } + + let function_var = synth_var(env.subs, Content::Error); // We'll fix this up in subs later. + let function_symbol = env.new_symbol("finalizer"); + let lambda_set = LambdaSet { + solved: UnionLambdas::tag_without_arguments(env.subs, function_symbol), + recursion_var: OptVariable::NONE, + unspecialized: Default::default(), + ambient_function: function_var, + }; + let closure_type = synth_var(env.subs, Content::LambdaSet(lambda_set)); + let flat_type = FlatType::Func( + SubsSlice::insert_into_subs(env.subs, [state_record_var]), + closure_type, + return_type_var, + ); + + // Fix up function_var so it's not Content::Error anymore + env.subs + .set_content(function_var, Content::Structure(flat_type)); + + let finalizer = Expr::Closure(ClosureData { + function_type: function_var, + closure_type, + return_type: return_type_var, + name: function_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::NotRecursive, + arguments: vec![( + state_record_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(state_arg_symbol)), + )], + loc_body: Box::new(Loc::at_zero(body)), + }); + + (finalizer, function_var, decode_err_var) +} + +// Example: +// initialState : {first: Result a [NoField], second: Result b [NoField]} +// initialState = {first: Err NoField, second: Err NoField} +fn initial_state( + env: &mut Env<'_>, + field_names: &[Lowercase], + field_vars: &mut Vec, + result_field_vars: &mut Vec, +) -> (Variable, Expr) { + let mut initial_state_fields = SendMap::default(); + + for field_name in field_names { + let subs = &mut env.subs; + let field_var = subs.fresh_unnamed_flex_var(); + + field_vars.push(field_var); + + let no_field_label = "NoField"; + let union_tags = UnionTags::tag_without_arguments(subs, no_field_label.into()); + let no_field_var = synth_var( + subs, + Content::Structure(FlatType::TagUnion( + union_tags, + TagExt::Any(Variable::EMPTY_TAG_UNION), + )), + ); + let no_field = Expr::Tag { + tag_union_var: no_field_var, + ext_var: Variable::EMPTY_TAG_UNION, + name: no_field_label.into(), + arguments: Vec::new(), + }; + let err_label = "Err"; + let union_tags = UnionTags::for_result(subs, field_var, no_field_var); + let result_var = synth_var( + subs, + Content::Structure(FlatType::TagUnion( + union_tags, + TagExt::Any(Variable::EMPTY_TAG_UNION), + )), + ); + let field_expr = Expr::Tag { + tag_union_var: result_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: err_label.into(), + arguments: vec![(no_field_var, Loc::at_zero(no_field))], + }; + result_field_vars.push(result_var); + let field = Field { + var: result_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(field_expr)), + }; + + initial_state_fields.insert(field_name.clone(), field); + } + + let subs = &mut env.subs; + let record_field_iter = field_names + .iter() + .zip(result_field_vars.iter()) + .map(|(field_name, &var)| (field_name.clone(), RecordField::Required(var))); + let flat_type = FlatType::Record( + RecordFields::insert_into_subs(subs, record_field_iter), + Variable::EMPTY_RECORD, + ); + + let state_record_var = synth_var(subs, Content::Structure(flat_type)); + + ( + state_record_var, + Expr::Record { + record_var: state_record_var, + fields: initial_state_fields, + }, + ) +} diff --git a/crates/compiler/derive/src/decoding/tuple.rs b/crates/compiler/derive/src/decoding/tuple.rs new file mode 100644 index 0000000000..b6ce201691 --- /dev/null +++ b/crates/compiler/derive/src/decoding/tuple.rs @@ -0,0 +1,994 @@ +use roc_can::expr::{ + AnnotatedMark, ClosureData, Expr, Field, IntValue, Recursive, WhenBranch, WhenBranchPattern, +}; +use roc_can::num::{IntBound, IntLitWidth}; +use roc_can::pattern::Pattern; +use roc_collections::SendMap; +use roc_module::called_via::CalledVia; +use roc_module::ident::Lowercase; +use roc_module::symbol::Symbol; +use roc_region::all::{Loc, Region}; +use roc_types::subs::{ + Content, ExhaustiveMark, FlatType, LambdaSet, OptVariable, RecordFields, RedundantMark, + SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags, Variable, +}; +use roc_types::types::RecordField; + +use crate::synth_var; +use crate::util::{Env, ExtensionKind}; + +use super::wrap_in_decode_custom_decode_with; + +/// Implements decoding of a tuple. For example, for +/// +/// ```text +/// (a, b) +/// ``` +/// +/// we'd like to generate an impl like +/// +/// ```roc +/// decoder : Decoder (a, b) fmt | a has Decoding, b has Decoding, fmt has DecoderFormatting +/// decoder = +/// initialState : {e0: Result a [NoElem], e1: Result b [NoElem]} +/// initialState = {e0: Err NoElem, e1: Err NoElem} +/// +/// stepElem = \state, index -> +/// when index is +/// 0 -> +/// Next (Decode.custom \bytes, fmt -> +/// when Decode.decodeWith bytes Decode.decoder fmt is +/// {result, rest} -> +/// {result: Result.map result \val -> {state & e0: Ok val}, rest}) +/// 1 -> +/// Next (Decode.custom \bytes, fmt -> +/// when Decode.decodeWith bytes Decode.decoder fmt is +/// {result, rest} -> +/// {result: Result.map result \val -> {state & e1: Ok val}, rest}) +/// _ -> TooLong +/// +/// finalizer = \st -> +/// when st.e0 is +/// Ok e0 -> +/// when st.e1 is +/// Ok e1 -> Ok (e0, e1) +/// Err NoElem -> Err TooShort +/// Err NoElem -> Err TooShort +/// +/// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.tuple initialState stepElem finalizer) fmt +/// ``` +pub(crate) fn decoder(env: &mut Env, _def_symbol: Symbol, arity: u32) -> (Expr, Variable) { + // The decoded type of each index in the tuple, e.g. (a, b). + let mut index_vars = Vec::with_capacity(arity as _); + // The type of each index in the decoding state, e.g. {e0: Result a [NoElem], e1: Result b [NoElem]} + let mut state_fields = Vec::with_capacity(arity as _); + let mut state_field_vars = Vec::with_capacity(arity as _); + + // initialState = ... + let (state_var, initial_state) = initial_state( + env, + arity, + &mut index_vars, + &mut state_fields, + &mut state_field_vars, + ); + + // finalizer = ... + let (finalizer, finalizer_var, decode_err_var) = finalizer( + env, + &index_vars, + state_var, + &state_fields, + &state_field_vars, + ); + + // stepElem = ... + let (step_elem, step_var) = step_elem( + env, + &index_vars, + state_var, + &state_fields, + &state_field_vars, + decode_err_var, + ); + + // Build up the type of `Decode.tuple` we expect + let tuple_decoder_var = env.subs.fresh_unnamed_flex_var(); + let decode_record_lambda_set = env.subs.fresh_unnamed_flex_var(); + let decode_record_var = env.import_builtin_symbol_var(Symbol::DECODE_TUPLE); + let this_decode_record_var = { + let flat_type = FlatType::Func( + SubsSlice::insert_into_subs(env.subs, [state_var, step_var, finalizer_var]), + decode_record_lambda_set, + tuple_decoder_var, + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + env.unify(decode_record_var, this_decode_record_var); + + // Decode.tuple initialState stepElem finalizer + let call_decode_record = Expr::Call( + Box::new(( + this_decode_record_var, + Loc::at_zero(Expr::AbilityMember( + Symbol::DECODE_TUPLE, + None, + this_decode_record_var, + )), + decode_record_lambda_set, + tuple_decoder_var, + )), + vec![ + (state_var, Loc::at_zero(initial_state)), + (step_var, Loc::at_zero(step_elem)), + (finalizer_var, Loc::at_zero(finalizer)), + ], + CalledVia::Space, + ); + + let (call_decode_custom, decode_custom_ret_var) = { + let bytes_sym = env.new_symbol("bytes"); + let fmt_sym = env.new_symbol("fmt"); + let fmt_var = env.subs.fresh_unnamed_flex_var(); + + let (decode_custom, decode_custom_var) = wrap_in_decode_custom_decode_with( + env, + bytes_sym, + (fmt_sym, fmt_var), + vec![], + (call_decode_record, tuple_decoder_var), + ); + + (decode_custom, decode_custom_var) + }; + + (call_decode_custom, decode_custom_ret_var) +} + +// Example: +// stepElem = \state, index -> +// when index is +// 0 -> +// Next (Decode.custom \bytes, fmt -> +// # Uses a single-branch `when` because `let` is more expensive to monomorphize +// # due to checks for polymorphic expressions, and `rec` would be polymorphic. +// when Decode.decodeWith bytes Decode.decoder fmt is +// rec -> +// { +// rest: rec.rest, +// result: when rec.result is +// Ok val -> Ok {state & e0: Ok val}, +// Err err -> Err err +// }) +// +// "e1" -> +// Next (Decode.custom \bytes, fmt -> +// when Decode.decodeWith bytes Decode.decoder fmt is +// rec -> +// { +// rest: rec.rest, +// result: when rec.result is +// Ok val -> Ok {state & e1: Ok val}, +// Err err -> Err err +// }) +// +// _ -> TooLong +fn step_elem( + env: &mut Env, + index_vars: &[Variable], + state_record_var: Variable, + state_fields: &[Lowercase], + state_field_vars: &[Variable], + decode_err_var: Variable, +) -> (Expr, Variable) { + let state_arg_symbol = env.new_symbol("stateRecord"); + let index_arg_symbol = env.new_symbol("index"); + + // +1 because of the default branch. + let mut branches = Vec::with_capacity(index_vars.len() + 1); + let keep_payload_var = env.subs.fresh_unnamed_flex_var(); + let keep_or_skip_var = { + let keep_payload_subs_slice = SubsSlice::insert_into_subs(env.subs, [keep_payload_var]); + let flat_type = FlatType::TagUnion( + UnionTags::insert_slices_into_subs( + env.subs, + [ + ("Next".into(), keep_payload_subs_slice), + ("TooLong".into(), Default::default()), + ], + ), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + for (((index, state_field), &index_var), &result_index_var) in state_fields + .iter() + .enumerate() + .zip(index_vars) + .zip(state_field_vars) + { + // Example: + // 0 -> + // Next (Decode.custom \bytes, fmt -> + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + // } + // ) + + let this_custom_callback_var; + let custom_callback_ret_var; + let custom_callback = { + // \bytes, fmt -> + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + // } + let bytes_arg_symbol = env.new_symbol("bytes"); + let fmt_arg_symbol = env.new_symbol("fmt"); + let bytes_arg_var = env.subs.fresh_unnamed_flex_var(); + let fmt_arg_var = env.subs.fresh_unnamed_flex_var(); + + // rec.result : [Ok index_var, Err DecodeError] + let rec_dot_result = { + let tag_union = FlatType::TagUnion( + UnionTags::for_result(env.subs, index_var, decode_err_var), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(env.subs, Content::Structure(tag_union)) + }; + + // rec : { rest: List U8, result: (typeof rec.result) } + let rec_var = { + let indexs = RecordFields::insert_into_subs( + env.subs, + [ + ("rest".into(), RecordField::Required(Variable::LIST_U8)), + ("result".into(), RecordField::Required(rec_dot_result)), + ], + ); + let record = FlatType::Record(indexs, Variable::EMPTY_RECORD); + + synth_var(env.subs, Content::Structure(record)) + }; + + // `Decode.decoder` for the index's value + let decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER); + let decode_with_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH); + let lambda_set_var = env.subs.fresh_unnamed_flex_var(); + let this_decode_with_var = { + let subs_slice = SubsSlice::insert_into_subs( + env.subs, + [bytes_arg_var, decoder_var, fmt_arg_var], + ); + let this_decode_with_var = synth_var( + env.subs, + Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)), + ); + + env.unify(decode_with_var, this_decode_with_var); + + this_decode_with_var + }; + + // The result of decoding this index's value - either the updated state, or a decoding error. + let when_expr_var = { + let flat_type = FlatType::TagUnion( + UnionTags::for_result(env.subs, state_record_var, decode_err_var), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + // What our decoder passed to `Decode.custom` returns - the result of decoding the + // index's value, and the remaining bytes. + custom_callback_ret_var = { + let rest_index = RecordField::Required(Variable::LIST_U8); + let result_index = RecordField::Required(when_expr_var); + let flat_type = FlatType::Record( + RecordFields::insert_into_subs( + env.subs, + [("rest".into(), rest_index), ("result".into(), result_index)], + ), + Variable::EMPTY_RECORD, + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + let custom_callback_body = { + let rec_symbol = env.new_symbol("rec"); + + // # Uses a single-branch `when` because `let` is more expensive to monomorphize + // # due to checks for polymorphic expressions, and `rec` would be polymorphic. + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + // } + let branch_body = { + let result_val = { + // result: when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + let ok_val_symbol = env.new_symbol("val"); + let err_val_symbol = env.new_symbol("err"); + let ok_branch_expr = { + // Ok {state & e0: Ok val}, + let mut updates = SendMap::default(); + + updates.insert( + state_field.clone(), + Field { + var: result_index_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(Expr::Tag { + tag_union_var: result_index_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Ok".into(), + arguments: vec![( + index_var, + Loc::at_zero(Expr::Var(ok_val_symbol, index_var)), + )], + })), + }, + ); + + let updated_record = Expr::RecordUpdate { + record_var: state_record_var, + ext_var: env.new_ext_var(ExtensionKind::Record), + symbol: state_arg_symbol, + updates, + }; + + Expr::Tag { + tag_union_var: when_expr_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Ok".into(), + arguments: vec![(state_record_var, Loc::at_zero(updated_record))], + } + }; + + let branches = vec![ + // Ok val -> Ok {state & e0: Ok val}, + WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::AppliedTag { + whole_var: rec_dot_result, + ext_var: Variable::EMPTY_TAG_UNION, + tag_name: "Ok".into(), + arguments: vec![( + index_var, + Loc::at_zero(Pattern::Identifier(ok_val_symbol)), + )], + }), + degenerate: false, + }], + value: Loc::at_zero(ok_branch_expr), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }, + // Err err -> Err err + WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::AppliedTag { + whole_var: rec_dot_result, + ext_var: Variable::EMPTY_TAG_UNION, + tag_name: "Err".into(), + arguments: vec![( + decode_err_var, + Loc::at_zero(Pattern::Identifier(err_val_symbol)), + )], + }), + degenerate: false, + }], + value: Loc::at_zero(Expr::Tag { + tag_union_var: when_expr_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Err".into(), + arguments: vec![( + decode_err_var, + Loc::at_zero(Expr::Var(err_val_symbol, decode_err_var)), + )], + }), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }, + ]; + + // when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + Expr::When { + loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess { + record_var: rec_var, + ext_var: env.new_ext_var(ExtensionKind::Record), + field_var: rec_dot_result, + loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))), + field: "result".into(), + })), + cond_var: rec_dot_result, + expr_var: when_expr_var, + region: Region::zero(), + branches, + branches_cond_var: rec_dot_result, + exhaustive: ExhaustiveMark::known_exhaustive(), + } + }; + + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + // } + let mut fields_map = SendMap::default(); + + fields_map.insert( + "rest".into(), + Field { + var: Variable::LIST_U8, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess { + record_var: rec_var, + ext_var: env.new_ext_var(ExtensionKind::Record), + field_var: Variable::LIST_U8, + loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))), + field: "rest".into(), + })), + }, + ); + + // result: when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + fields_map.insert( + "result".into(), + Field { + var: when_expr_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(result_val)), + }, + ); + + Expr::Record { + record_var: custom_callback_ret_var, + fields: fields_map, + } + }; + + let branch = WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::Identifier(rec_symbol)), + degenerate: false, + }], + value: Loc::at_zero(branch_body), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }; + + let condition_expr = Expr::Call( + Box::new(( + this_decode_with_var, + Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)), + lambda_set_var, + rec_var, + )), + vec![ + ( + Variable::LIST_U8, + Loc::at_zero(Expr::Var(bytes_arg_symbol, Variable::LIST_U8)), + ), + ( + decoder_var, + Loc::at_zero(Expr::AbilityMember( + Symbol::DECODE_DECODER, + None, + decoder_var, + )), + ), + ( + fmt_arg_var, + Loc::at_zero(Expr::Var(fmt_arg_symbol, fmt_arg_var)), + ), + ], + CalledVia::Space, + ); + + // when Decode.decodeWith bytes Decode.decoder fmt is + Expr::When { + loc_cond: Box::new(Loc::at_zero(condition_expr)), + cond_var: rec_var, + expr_var: custom_callback_ret_var, + region: Region::zero(), + branches: vec![branch], + branches_cond_var: rec_var, + exhaustive: ExhaustiveMark::known_exhaustive(), + } + }; + + let custom_closure_symbol = env.new_symbol("customCallback"); + this_custom_callback_var = env.subs.fresh_unnamed_flex_var(); + let custom_callback_lambda_set_var = { + let content = Content::LambdaSet(LambdaSet { + solved: UnionLambdas::insert_into_subs( + env.subs, + [(custom_closure_symbol, [state_record_var])], + ), + recursion_var: OptVariable::NONE, + unspecialized: Default::default(), + ambient_function: this_custom_callback_var, + }); + let custom_callback_lambda_set_var = synth_var(env.subs, content); + let subs_slice = + SubsSlice::insert_into_subs(env.subs, [bytes_arg_var, fmt_arg_var]); + + env.subs.set_content( + this_custom_callback_var, + Content::Structure(FlatType::Func( + subs_slice, + custom_callback_lambda_set_var, + custom_callback_ret_var, + )), + ); + + custom_callback_lambda_set_var + }; + + // \bytes, fmt -> … + Expr::Closure(ClosureData { + function_type: this_custom_callback_var, + closure_type: custom_callback_lambda_set_var, + return_type: custom_callback_ret_var, + name: custom_closure_symbol, + captured_symbols: vec![(state_arg_symbol, state_record_var)], + recursive: Recursive::NotRecursive, + arguments: vec![ + ( + bytes_arg_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(bytes_arg_symbol)), + ), + ( + fmt_arg_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(fmt_arg_symbol)), + ), + ], + loc_body: Box::new(Loc::at_zero(custom_callback_body)), + }) + }; + + let decode_custom_ret_var = env.subs.fresh_unnamed_flex_var(); + let decode_custom = { + let decode_custom_var = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM); + let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var(); + let this_decode_custom_var = { + let subs_slice = SubsSlice::insert_into_subs(env.subs, [this_custom_callback_var]); + let flat_type = + FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + env.unify(decode_custom_var, this_decode_custom_var); + + // Decode.custom \bytes, fmt -> … + Expr::Call( + Box::new(( + this_decode_custom_var, + Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)), + decode_custom_closure_var, + decode_custom_ret_var, + )), + vec![(this_custom_callback_var, Loc::at_zero(custom_callback))], + CalledVia::Space, + ) + }; + + env.unify(keep_payload_var, decode_custom_ret_var); + + let keep = { + // Next (Decode.custom \bytes, fmt -> + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + // } + // ) + Expr::Tag { + tag_union_var: keep_or_skip_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Next".into(), + arguments: vec![(decode_custom_ret_var, Loc::at_zero(decode_custom))], + } + }; + + let branch = { + // 0 -> + // Next (Decode.custom \bytes, fmt -> + // when Decode.decodeWith bytes Decode.decoder fmt is + // rec -> + // { + // rest: rec.rest, + // result: when rec.result is + // Ok val -> Ok {state & e0: Ok val}, + // Err err -> Err err + // } + // ) + WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::IntLiteral( + Variable::NAT, + Variable::NATURAL, + index.to_string().into_boxed_str(), + IntValue::I128((index as i128).to_ne_bytes()), + IntBound::Exact(IntLitWidth::Nat), + )), + degenerate: false, + }], + value: Loc::at_zero(keep), + guard: None, + redundant: RedundantMark::known_non_redundant(), + } + }; + + branches.push(branch); + } + + // Example: `_ -> TooLong` + let default_branch = WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::Underscore), + degenerate: false, + }], + value: Loc::at_zero(Expr::Tag { + tag_union_var: keep_or_skip_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "TooLong".into(), + arguments: Vec::new(), + }), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }; + + branches.push(default_branch); + + // when index is + let body = Expr::When { + loc_cond: Box::new(Loc::at_zero(Expr::Var(index_arg_symbol, Variable::NAT))), + cond_var: Variable::NAT, + expr_var: keep_or_skip_var, + region: Region::zero(), + branches, + branches_cond_var: Variable::NAT, + exhaustive: ExhaustiveMark::known_exhaustive(), + }; + + let step_elem_closure = env.new_symbol("stepElem"); + let function_type = env.subs.fresh_unnamed_flex_var(); + let closure_type = { + let lambda_set = LambdaSet { + solved: UnionLambdas::tag_without_arguments(env.subs, step_elem_closure), + recursion_var: OptVariable::NONE, + unspecialized: Default::default(), + ambient_function: function_type, + }; + + synth_var(env.subs, Content::LambdaSet(lambda_set)) + }; + + { + let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::NAT]); + + env.subs.set_content( + function_type, + Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)), + ) + }; + + let expr = Expr::Closure(ClosureData { + function_type, + closure_type, + return_type: keep_or_skip_var, + name: step_elem_closure, + captured_symbols: Vec::new(), + recursive: Recursive::NotRecursive, + arguments: vec![ + ( + state_record_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(state_arg_symbol)), + ), + ( + Variable::NAT, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(index_arg_symbol)), + ), + ], + loc_body: Box::new(Loc::at_zero(body)), + }); + + (expr, function_type) +} + +// Example: +// finalizer = \rec -> +// when rec.e0 is +// Ok e0 -> +// when rec.e1 is +// Ok e1 -> Ok (e0, e1) +// Err NoElem -> Err TooShort +// Err NoElem -> Err TooShort +fn finalizer( + env: &mut Env, + index_vars: &[Variable], + state_record_var: Variable, + state_fields: &[Lowercase], + state_field_vars: &[Variable], +) -> (Expr, Variable, Variable) { + let state_arg_symbol = env.new_symbol("stateRecord"); + let mut tuple_elems = Vec::with_capacity(index_vars.len()); + let mut pattern_symbols = Vec::with_capacity(index_vars.len()); + let decode_err_var = { + let flat_type = FlatType::TagUnion( + UnionTags::tag_without_arguments(env.subs, "TooShort".into()), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(env.subs, Content::Structure(flat_type)) + }; + + for (i, &index_var) in index_vars.iter().enumerate() { + let symbol = env.new_symbol(i); + + pattern_symbols.push(symbol); + + let index_expr = Expr::Var(symbol, index_var); + + tuple_elems.push((index_var, Box::new(Loc::at_zero(index_expr)))); + } + + // The bottom of the happy path - return the decoded tuple (a, b) wrapped with + // "Ok". + let return_type_var; + let mut body = { + let subs = &mut env.subs; + let tuple_indices_iter = index_vars.iter().copied().enumerate(); + let flat_type = FlatType::Tuple( + TupleElems::insert_into_subs(subs, tuple_indices_iter), + Variable::EMPTY_TUPLE, + ); + let done_tuple_var = synth_var(subs, Content::Structure(flat_type)); + let done_record = Expr::Tuple { + tuple_var: done_tuple_var, + elems: tuple_elems, + }; + + return_type_var = { + let flat_type = FlatType::TagUnion( + UnionTags::for_result(subs, done_tuple_var, decode_err_var), + TagExt::Any(Variable::EMPTY_TAG_UNION), + ); + + synth_var(subs, Content::Structure(flat_type)) + }; + + Expr::Tag { + tag_union_var: return_type_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Ok".into(), + arguments: vec![(done_tuple_var, Loc::at_zero(done_record))], + } + }; + + // Unwrap each result in the decoded state + // + // when rec.e0 is + // Ok e0 -> ...happy path... + // Err NoElem -> Err TooShort + for (((symbol, field), &index_var), &result_index_var) in pattern_symbols + .iter() + .zip(state_fields) + .zip(index_vars) + .zip(state_field_vars) + .rev() + { + // when rec.e0 is + let cond_expr = Expr::RecordAccess { + record_var: state_record_var, + ext_var: env.new_ext_var(ExtensionKind::Record), + field_var: result_index_var, + loc_expr: Box::new(Loc::at_zero(Expr::Var(state_arg_symbol, state_record_var))), + field: field.clone(), + }; + + // Example: `Ok x -> expr` + let ok_branch = WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::AppliedTag { + whole_var: result_index_var, + ext_var: Variable::EMPTY_TAG_UNION, + tag_name: "Ok".into(), + arguments: vec![(index_var, Loc::at_zero(Pattern::Identifier(*symbol)))], + }), + degenerate: false, + }], + value: Loc::at_zero(body), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }; + + // Example: `_ -> Err TooShort` + let err_branch = WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: Loc::at_zero(Pattern::Underscore), + degenerate: false, + }], + value: Loc::at_zero(Expr::Tag { + tag_union_var: return_type_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: "Err".into(), + arguments: vec![( + decode_err_var, + Loc::at_zero(Expr::Tag { + tag_union_var: decode_err_var, + ext_var: Variable::EMPTY_TAG_UNION, + name: "TooShort".into(), + arguments: Vec::new(), + }), + )], + }), + guard: None, + redundant: RedundantMark::known_non_redundant(), + }; + + body = Expr::When { + loc_cond: Box::new(Loc::at_zero(cond_expr)), + cond_var: result_index_var, + expr_var: return_type_var, + region: Region::zero(), + branches: vec![ok_branch, err_branch], + branches_cond_var: result_index_var, + exhaustive: ExhaustiveMark::known_exhaustive(), + }; + } + + let function_var = synth_var(env.subs, Content::Error); // We'll fix this up in subs later. + let function_symbol = env.new_symbol("finalizer"); + let lambda_set = LambdaSet { + solved: UnionLambdas::tag_without_arguments(env.subs, function_symbol), + recursion_var: OptVariable::NONE, + unspecialized: Default::default(), + ambient_function: function_var, + }; + let closure_type = synth_var(env.subs, Content::LambdaSet(lambda_set)); + let flat_type = FlatType::Func( + SubsSlice::insert_into_subs(env.subs, [state_record_var]), + closure_type, + return_type_var, + ); + + // Fix up function_var so it's not Content::Error anymore + env.subs + .set_content(function_var, Content::Structure(flat_type)); + + let finalizer = Expr::Closure(ClosureData { + function_type: function_var, + closure_type, + return_type: return_type_var, + name: function_symbol, + captured_symbols: Vec::new(), + recursive: Recursive::NotRecursive, + arguments: vec![( + state_record_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(state_arg_symbol)), + )], + loc_body: Box::new(Loc::at_zero(body)), + }); + + (finalizer, function_var, decode_err_var) +} + +// Example: +// initialState : {e0: Result a [NoElem], e1: Result b [NoElem]} +// initialState = {e0: Err NoElem, e1: Err NoElem} +fn initial_state( + env: &mut Env<'_>, + arity: u32, + index_vars: &mut Vec, + state_fields: &mut Vec, + state_field_vars: &mut Vec, +) -> (Variable, Expr) { + let mut initial_state_fields = SendMap::default(); + + for i in 0..arity { + let subs = &mut env.subs; + let index_var = subs.fresh_unnamed_flex_var(); + + index_vars.push(index_var); + + let state_field = Lowercase::from(format!("e{i}")); + state_fields.push(state_field.clone()); + + let no_index_label = "NoElem"; + let union_tags = UnionTags::tag_without_arguments(subs, no_index_label.into()); + let no_index_var = synth_var( + subs, + Content::Structure(FlatType::TagUnion( + union_tags, + TagExt::Any(Variable::EMPTY_TAG_UNION), + )), + ); + let no_index = Expr::Tag { + tag_union_var: no_index_var, + ext_var: Variable::EMPTY_TAG_UNION, + name: no_index_label.into(), + arguments: Vec::new(), + }; + let err_label = "Err"; + let union_tags = UnionTags::for_result(subs, index_var, no_index_var); + let result_var = synth_var( + subs, + Content::Structure(FlatType::TagUnion( + union_tags, + TagExt::Any(Variable::EMPTY_TAG_UNION), + )), + ); + let index_expr = Expr::Tag { + tag_union_var: result_var, + ext_var: env.new_ext_var(ExtensionKind::TagUnion), + name: err_label.into(), + arguments: vec![(no_index_var, Loc::at_zero(no_index))], + }; + state_field_vars.push(result_var); + let index = Field { + var: result_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(index_expr)), + }; + + initial_state_fields.insert(state_field, index); + } + + let subs = &mut env.subs; + let record_index_iter = state_fields + .iter() + .zip(state_field_vars.iter()) + .map(|(index_name, &var)| (index_name.clone(), RecordField::Required(var))); + let flat_type = FlatType::Record( + RecordFields::insert_into_subs(subs, record_index_iter), + Variable::EMPTY_RECORD, + ); + + let state_record_var = synth_var(subs, Content::Structure(flat_type)); + + ( + state_record_var, + Expr::Record { + record_var: state_record_var, + fields: initial_state_fields, + }, + ) +} diff --git a/crates/compiler/derive/src/encoding.rs b/crates/compiler/derive/src/encoding.rs index f51bf545e3..183d489db1 100644 --- a/crates/compiler/derive/src/encoding.rs +++ b/crates/compiler/derive/src/encoding.rs @@ -14,7 +14,8 @@ use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; use roc_types::subs::{ Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields, - RedundantMark, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, VariableSubsSlice, + RedundantMark, SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags, Variable, + VariableSubsSlice, }; use roc_types::types::RecordField; @@ -50,6 +51,21 @@ pub(crate) fn derive_to_encoder( to_encoder_record(env, record_var, fields, def_symbol) } + FlatEncodableKey::Tuple(arity) => { + // Generalized tuple var so we can reuse this impl between many tuples: + // if arity = n, this is (t1, ..., tn) for fresh t1, ..., tn. + let flex_elems = (0..arity) + .into_iter() + .map(|idx| (idx as usize, env.subs.fresh_unnamed_flex_var())) + .collect::>(); + let elems = TupleElems::insert_into_subs(env.subs, flex_elems); + let tuple_var = synth_var( + env.subs, + Content::Structure(FlatType::Tuple(elems, Variable::EMPTY_TUPLE)), + ); + + to_encoder_tuple(env, tuple_var, elems, def_symbol) + } FlatEncodableKey::TagUnion(tags) => { // Generalized tag union var so we can reuse this impl between many unions: // if tags = [ A arity=2, B arity=1 ], this is [ A t1 t2, B t3 ] for fresh t1, t2, t3 @@ -490,6 +506,189 @@ fn to_encoder_record( (clos, fn_var) } +fn to_encoder_tuple( + env: &mut Env<'_>, + tuple_var: Variable, + elems: TupleElems, + fn_name: Symbol, +) -> (Expr, Variable) { + // Suppose tup = (t1, t2). Build + // + // \tup -> Encode.tuple [ + // Encode.toEncoder tup.0, + // Encode.toEncoder tup.1, + // ] + + let tup_sym = env.new_symbol("tup"); + let whole_encoder_in_list_var = env.subs.fresh_unnamed_flex_var(); // type of the encoder in the list + + use Expr::*; + + let elem_encoders_list = elems + .iter_all() + .map(|(elem_index, elem_var_index)| { + let index = env.subs[elem_index]; + let elem_var = env.subs[elem_var_index]; + let elem_var_slice = VariableSubsSlice::new(elem_var_index.index, 1); + + // tup.0 + let tuple_access = TupleAccess { + tuple_var, + ext_var: env.subs.fresh_unnamed_flex_var(), + elem_var, + loc_expr: Box::new(Loc::at_zero(Var( + tup_sym, + env.subs.fresh_unnamed_flex_var(), + ))), + index, + }; + + // build `toEncoder tup.0` type + // val -[uls]-> Encoder fmt | fmt has EncoderFormatting + let to_encoder_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER); + + // (typeof tup.0) -[clos]-> t1 + let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos + let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1 + let this_to_encoder_fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + elem_var_slice, + to_encoder_clos_var, + encoder_var, + )), + ); + + // val -[uls]-> Encoder fmt | fmt has EncoderFormatting + // ~ (typeof tup.0) -[clos]-> t1 + env.unify(to_encoder_fn_var, this_to_encoder_fn_var); + + // toEncoder : (typeof tup.0) -[clos]-> Encoder fmt | fmt has EncoderFormatting + let to_encoder_var = AbilityMember(Symbol::ENCODE_TO_ENCODER, None, to_encoder_fn_var); + let to_encoder_fn = Box::new(( + to_encoder_fn_var, + Loc::at_zero(to_encoder_var), + to_encoder_clos_var, + encoder_var, + )); + + // toEncoder tup.0 + let to_encoder_call = Call( + to_encoder_fn, + vec![(elem_var, Loc::at_zero(tuple_access))], + CalledVia::Space, + ); + + // NOTE: must be done to unify the lambda sets under `encoder_var` + env.unify(encoder_var, whole_encoder_in_list_var); + + Loc::at_zero(to_encoder_call) + }) + .collect::>(); + + // typeof [ toEncoder tup.0, toEncoder tup.1 ] + let whole_encoder_in_list_var_slice = + VariableSubsSlice::insert_into_subs(env.subs, once(whole_encoder_in_list_var)); + let elem_encoders_list_var = synth_var( + env.subs, + Content::Structure(FlatType::Apply( + Symbol::LIST_LIST, + whole_encoder_in_list_var_slice, + )), + ); + + // [ toEncoder tup.0, toEncoder tup.1 ] + let elem_encoders_list = List { + elem_var: whole_encoder_in_list_var, + loc_elems: elem_encoders_list, + }; + + // build `Encode.tuple [ toEncoder tup.0, toEncoder tup.1 ]` type + // List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting + let encode_tuple_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TUPLE); + + // elem_encoders_list_var -[clos]-> t1 + let elem_encoders_list_var_slice = + VariableSubsSlice::insert_into_subs(env.subs, once(elem_encoders_list_var)); + let encode_tuple_clos_var = env.subs.fresh_unnamed_flex_var(); // clos + let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1 + let this_encode_tuple_fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + elem_encoders_list_var_slice, + encode_tuple_clos_var, + encoder_var, + )), + ); + + // List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting + // ~ elem_encoders_list_var -[clos]-> t1 + env.unify(encode_tuple_fn_var, this_encode_tuple_fn_var); + + // Encode.tuple : elem_encoders_list_var -[clos]-> Encoder fmt | fmt has EncoderFormatting + let encode_tuple_var = AbilityMember(Symbol::ENCODE_TUPLE, None, encode_tuple_fn_var); + let encode_tuple_fn = Box::new(( + encode_tuple_fn_var, + Loc::at_zero(encode_tuple_var), + encode_tuple_clos_var, + encoder_var, + )); + + // Encode.tuple [ { key: .., value: .. }, .. ] + let encode_tuple_call = Call( + encode_tuple_fn, + vec![(elem_encoders_list_var, Loc::at_zero(elem_encoders_list))], + CalledVia::Space, + ); + + // Encode.custom \bytes, fmt -> Encode.appendWith bytes (Encode.tuple_var ..) fmt + let (body, this_encoder_var) = + wrap_in_encode_custom(env, encode_tuple_call, encoder_var, tup_sym, tuple_var); + + // Create fn_var for ambient capture; we fix it up below. + let fn_var = synth_var(env.subs, Content::Error); + + // -[fn_name]-> + let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![]))); + let fn_clos_var = synth_var( + env.subs, + Content::LambdaSet(LambdaSet { + solved: fn_name_labels, + recursion_var: OptVariable::NONE, + unspecialized: SubsSlice::default(), + ambient_function: fn_var, + }), + ); + // typeof tup -[fn_name]-> (typeof Encode.tuple [ .. ] = Encoder fmt) + let tuple_var_slice = SubsSlice::insert_into_subs(env.subs, once(tuple_var)); + env.subs.set_content( + fn_var, + Content::Structure(FlatType::Func( + tuple_var_slice, + fn_clos_var, + this_encoder_var, + )), + ); + + // \tup -[fn_name]-> Encode.tuple [ { key: .., value: .. }, .. ] + let clos = Closure(ClosureData { + function_type: fn_var, + closure_type: fn_clos_var, + return_type: this_encoder_var, + name: fn_name, + captured_symbols: vec![], + recursive: Recursive::NotRecursive, + arguments: vec![( + tuple_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(tup_sym)), + )], + loc_body: Box::new(Loc::at_zero(body)), + }); + + (clos, fn_var) +} + fn to_encoder_tag_union( env: &mut Env<'_>, tag_union_var: Variable, diff --git a/crates/compiler/derive/src/hash.rs b/crates/compiler/derive/src/hash.rs index 6a786dfb66..1f597f2dda 100644 --- a/crates/compiler/derive/src/hash.rs +++ b/crates/compiler/derive/src/hash.rs @@ -19,8 +19,8 @@ use roc_types::{ num::int_lit_width_to_variable, subs::{ Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields, - RedundantMark, Subs, SubsIndex, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, - VariableSubsSlice, + RedundantMark, Subs, SubsIndex, SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags, + Variable, VariableSubsSlice, }, types::RecordField, }; @@ -30,6 +30,7 @@ use crate::{synth_var, util::Env, DerivedBody}; pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody { let (body_type, body) = match key { FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields), + FlatHashKey::Tuple(arity) => hash_tuple(env, def_symbol, arity), FlatHashKey::TagUnion(tags) => { if tags.len() == 1 { hash_newtype_tag_union(env, def_symbol, tags.into_iter().next().unwrap()) @@ -122,6 +123,76 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec) -> (V ) } +fn hash_tuple(env: &mut Env<'_>, fn_name: Symbol, arity: u32) -> (Variable, Expr) { + // Suppose tup = (v1, ..., vn). + // Build a generalized type t_tup = (t1, ..., tn), with fresh t1, ..., tn, + // so that we can re-use the derived impl for many tuples of the same arity. + let (tuple_var, tuple_elems) = { + // TODO: avoid an allocation here by pre-allocating the indices and variables `TupleElems` + // will be instantiated with. + let flex_elems: Vec<_> = (0..arity) + .into_iter() + .map(|i| (i as usize, env.subs.fresh_unnamed_flex_var())) + .collect(); + let elems = TupleElems::insert_into_subs(env.subs, flex_elems); + let tuple_var = synth_var( + env.subs, + Content::Structure(FlatType::Tuple(elems, Variable::EMPTY_TUPLE)), + ); + + (tuple_var, elems) + }; + + // Now, a hasher for this tuple is + // + // hash_tup : hasher, (t1, ..., tn) -> hasher | hasher has Hasher + // hash_tup = \hasher, tup -> + // Hash.hash ( + // Hash.hash + // ... + // (Hash.hash hasher tup.0) + // ... + // tup.n1) + // tup.n + // + // So, just a build a fold travelling up the elements. + let tup_sym = env.new_symbol("tup"); + + let hasher_sym = env.new_symbol("hasher"); + let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Subs::AB_HASHER)); + + let (body_var, body) = tuple_elems.iter_all().fold( + (hasher_var, Expr::Var(hasher_sym, hasher_var)), + |total_hasher, (elem_idx, elem_var)| { + let index = env.subs[elem_idx]; + let elem_var = env.subs[elem_var]; + + let elem_access = Expr::TupleAccess { + tuple_var, + elem_var, + ext_var: env.subs.fresh_unnamed_flex_var(), + loc_expr: Box::new(Loc::at_zero(Expr::Var( + tup_sym, + env.subs.fresh_unnamed_flex_var(), + ))), + index, + }; + + call_hash_hash(env, total_hasher, (elem_var, elem_access)) + }, + ); + + // Finally, build the closure + // \hasher, rcd -> body + build_outer_derived_closure( + env, + fn_name, + (hasher_var, hasher_sym), + (tuple_var, Pattern::Identifier(tup_sym)), + (body_var, body), + ) +} + /// Build a `hash` implementation for a non-singleton tag union. fn hash_tag_union( env: &mut Env<'_>, diff --git a/crates/compiler/derive/src/util.rs b/crates/compiler/derive/src/util.rs index 7dab0d1606..2319dd2e12 100644 --- a/crates/compiler/derive/src/util.rs +++ b/crates/compiler/derive/src/util.rs @@ -18,19 +18,20 @@ pub(crate) struct Env<'a> { } impl Env<'_> { - pub fn new_symbol(&mut self, name_hint: &str) -> Symbol { + pub fn new_symbol(&mut self, name_hint: impl std::string::ToString) -> Symbol { if cfg!(any( debug_assertions, test, feature = "debug-derived-symbols" )) { let mut i = 0; + let hint = name_hint.to_string(); let debug_name = loop { i += 1; let name = if i == 1 { - name_hint.to_owned() + hint.clone() } else { - format!("{}{}", name_hint, i) + format!("{}{}", hint, i) }; if self.derived_ident_ids.get_id(&name).is_none() { break name; diff --git a/crates/compiler/derive_key/Cargo.toml b/crates/compiler/derive_key/Cargo.toml index a69689fd32..a8f9a5a826 100644 --- a/crates/compiler/derive_key/Cargo.toml +++ b/crates/compiler/derive_key/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "roc_derive_key" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dependencies] roc_collections = { path = "../collections" } roc_error_macros = { path = "../../error_macros" } -roc_region = { path = "../region" } roc_module = { path = "../module" } +roc_region = { path = "../region" } roc_types = { path = "../types" } -roc_can = { path = "../can" } diff --git a/crates/compiler/derive_key/src/decoding.rs b/crates/compiler/derive_key/src/decoding.rs index ec0d732363..dc52745f85 100644 --- a/crates/compiler/derive_key/src/decoding.rs +++ b/crates/compiler/derive_key/src/decoding.rs @@ -2,7 +2,7 @@ use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_types::subs::{Content, FlatType, Subs, Variable}; use crate::{ - util::{check_derivable_ext_var, debug_name_record}, + util::{check_derivable_ext_var, debug_name_record, debug_name_tuple}, DeriveError, }; @@ -18,6 +18,7 @@ pub enum FlatDecodableKey { // Unfortunate that we must allocate here, c'est la vie Record(Vec), + Tuple(u32), } impl FlatDecodableKey { @@ -25,6 +26,7 @@ impl FlatDecodableKey { match self { FlatDecodableKey::List() => "list".to_string(), FlatDecodableKey::Record(fields) => debug_name_record(fields), + FlatDecodableKey::Tuple(arity) => debug_name_tuple(*arity), } } } @@ -61,8 +63,14 @@ impl FlatDecodable { Ok(Key(FlatDecodableKey::Record(field_names))) } - FlatType::Tuple(_elems, _ext) => { - todo!() + FlatType::Tuple(elems, ext) => { + let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext); + + check_derivable_ext_var(subs, ext, |ext| { + matches!(ext, Content::Structure(FlatType::EmptyTuple)) + })?; + + Ok(Key(FlatDecodableKey::Tuple(elems_iter.count() as _))) } FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => { Err(Underivable) // yet @@ -78,27 +86,18 @@ impl FlatDecodable { // FlatType::Func(..) => Err(Underivable), }, - Content::Alias(sym, _, real_var, _) => match sym { - Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Ok(Immediate(Symbol::DECODE_U8)), - Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Ok(Immediate(Symbol::DECODE_U16)), - Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Ok(Immediate(Symbol::DECODE_U32)), - Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Ok(Immediate(Symbol::DECODE_U64)), - Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Ok(Immediate(Symbol::DECODE_U128)), - Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Ok(Immediate(Symbol::DECODE_I8)), - Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Ok(Immediate(Symbol::DECODE_I16)), - Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Ok(Immediate(Symbol::DECODE_I32)), - Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Ok(Immediate(Symbol::DECODE_I64)), - Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Ok(Immediate(Symbol::DECODE_I128)), - Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Ok(Immediate(Symbol::DECODE_DEC)), - Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Ok(Immediate(Symbol::DECODE_F32)), - Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Ok(Immediate(Symbol::DECODE_F64)), + Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) { + Some(lambda) => lambda, // NB: I believe it is okay to unwrap opaques here because derivers are only used // by the backend, and the backend treats opaques like structural aliases. - _ => Self::from_var(subs, real_var), + None => Self::from_var(subs, real_var), }, - Content::RangedNumber(_) => Err(Underivable), + Content::RangedNumber(range) => { + Self::from_var(subs, range.default_compilation_variable()) + } + // + Content::RecursionVar { structure, .. } => Self::from_var(subs, structure), // - Content::RecursionVar { .. } => Err(Underivable), Content::Error => Err(Underivable), Content::FlexVar(_) | Content::RigidVar(_) @@ -107,4 +106,30 @@ impl FlatDecodable { Content::LambdaSet(_) => Err(Underivable), } } + + pub(crate) fn from_builtin_symbol(symbol: Symbol) -> Result { + from_builtin_symbol(symbol).unwrap_or(Err(DeriveError::Underivable)) + } +} + +const fn from_builtin_symbol(symbol: Symbol) -> Option> { + use FlatDecodable::*; + match symbol { + Symbol::BOOL_BOOL => Some(Ok(Immediate(Symbol::DECODE_BOOL))), + Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(Ok(Immediate(Symbol::DECODE_U8))), + Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(Ok(Immediate(Symbol::DECODE_U16))), + Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(Ok(Immediate(Symbol::DECODE_U32))), + Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(Ok(Immediate(Symbol::DECODE_U64))), + Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(Ok(Immediate(Symbol::DECODE_U128))), + Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(Ok(Immediate(Symbol::DECODE_I8))), + Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(Ok(Immediate(Symbol::DECODE_I16))), + Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(Ok(Immediate(Symbol::DECODE_I32))), + Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(Ok(Immediate(Symbol::DECODE_I64))), + Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(Ok(Immediate(Symbol::DECODE_I128))), + Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Ok(Immediate(Symbol::DECODE_DEC))), + Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Ok(Immediate(Symbol::DECODE_F32))), + Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Ok(Immediate(Symbol::DECODE_F64))), + Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Err(DeriveError::Underivable)), + _ => None, + } } diff --git a/crates/compiler/derive_key/src/encoding.rs b/crates/compiler/derive_key/src/encoding.rs index 521cc27744..ec6140cbe1 100644 --- a/crates/compiler/derive_key/src/encoding.rs +++ b/crates/compiler/derive_key/src/encoding.rs @@ -5,7 +5,7 @@ use roc_module::{ use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable}; use crate::{ - util::{check_derivable_ext_var, debug_name_record, debug_name_tag}, + util::{check_derivable_ext_var, debug_name_record, debug_name_tag, debug_name_tuple}, DeriveError, }; @@ -22,6 +22,7 @@ pub enum FlatEncodableKey { Dict(/* takes two variables */), // Unfortunate that we must allocate here, c'est la vie Record(Vec), + Tuple(u32), TagUnion(Vec<(TagName, u16)>), } @@ -32,6 +33,7 @@ impl FlatEncodableKey { FlatEncodableKey::Set() => "set".to_string(), FlatEncodableKey::Dict() => "dict".to_string(), FlatEncodableKey::Record(fields) => debug_name_record(fields), + FlatEncodableKey::Tuple(arity) => debug_name_tuple(*arity), FlatEncodableKey::TagUnion(tags) => debug_name_tag(tags), } } @@ -66,8 +68,14 @@ impl FlatEncodable { Ok(Key(FlatEncodableKey::Record(field_names))) } - FlatType::Tuple(_elems, _ext) => { - todo!() + FlatType::Tuple(elems, ext) => { + let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext); + + check_derivable_ext_var(subs, ext, |ext| { + matches!(ext, Content::Structure(FlatType::EmptyTuple)) + })?; + + Ok(Key(FlatEncodableKey::Tuple(elems_iter.count() as _))) } FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => { // The recursion var doesn't matter, because the derived implementation will only @@ -112,27 +120,18 @@ impl FlatEncodable { // FlatType::Func(..) => Err(Underivable), }, - Content::Alias(sym, _, real_var, _) => match sym { - Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Ok(Immediate(Symbol::ENCODE_U8)), - Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Ok(Immediate(Symbol::ENCODE_U16)), - Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Ok(Immediate(Symbol::ENCODE_U32)), - Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Ok(Immediate(Symbol::ENCODE_U64)), - Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Ok(Immediate(Symbol::ENCODE_U128)), - Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Ok(Immediate(Symbol::ENCODE_I8)), - Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Ok(Immediate(Symbol::ENCODE_I16)), - Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Ok(Immediate(Symbol::ENCODE_I32)), - Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Ok(Immediate(Symbol::ENCODE_I64)), - Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Ok(Immediate(Symbol::ENCODE_I128)), - Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Ok(Immediate(Symbol::ENCODE_DEC)), - Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Ok(Immediate(Symbol::ENCODE_F32)), - Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Ok(Immediate(Symbol::ENCODE_F64)), + Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) { + Some(lambda) => lambda, // TODO: I believe it is okay to unwrap opaques here because derivers are only used // by the backend, and the backend treats opaques like structural aliases. _ => Self::from_var(subs, real_var), }, - Content::RangedNumber(_) => Err(Underivable), + Content::RangedNumber(range) => { + Self::from_var(subs, range.default_compilation_variable()) + } + // + Content::RecursionVar { structure, .. } => Self::from_var(subs, structure), // - Content::RecursionVar { .. } => Err(Underivable), Content::Error => Err(Underivable), Content::FlexVar(_) | Content::RigidVar(_) @@ -141,4 +140,30 @@ impl FlatEncodable { Content::LambdaSet(_) => Err(Underivable), } } + + pub(crate) fn from_builtin_symbol(symbol: Symbol) -> Result { + from_builtin_symbol(symbol).unwrap_or(Err(DeriveError::Underivable)) + } +} + +const fn from_builtin_symbol(symbol: Symbol) -> Option> { + use FlatEncodable::*; + match symbol { + Symbol::BOOL_BOOL => Some(Ok(Immediate(Symbol::ENCODE_BOOL))), + Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(Ok(Immediate(Symbol::ENCODE_U8))), + Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(Ok(Immediate(Symbol::ENCODE_U16))), + Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(Ok(Immediate(Symbol::ENCODE_U32))), + Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(Ok(Immediate(Symbol::ENCODE_U64))), + Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(Ok(Immediate(Symbol::ENCODE_U128))), + Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(Ok(Immediate(Symbol::ENCODE_I8))), + Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(Ok(Immediate(Symbol::ENCODE_I16))), + Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(Ok(Immediate(Symbol::ENCODE_I32))), + Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(Ok(Immediate(Symbol::ENCODE_I64))), + Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(Ok(Immediate(Symbol::ENCODE_I128))), + Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Ok(Immediate(Symbol::ENCODE_DEC))), + Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Ok(Immediate(Symbol::ENCODE_F32))), + Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Ok(Immediate(Symbol::ENCODE_F64))), + Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Err(DeriveError::Underivable)), + _ => None, + } } diff --git a/crates/compiler/derive_key/src/hash.rs b/crates/compiler/derive_key/src/hash.rs index 00e765974b..6ca876988c 100644 --- a/crates/compiler/derive_key/src/hash.rs +++ b/crates/compiler/derive_key/src/hash.rs @@ -5,7 +5,7 @@ use roc_module::{ use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable}; use crate::{ - util::{check_derivable_ext_var, debug_name_record, debug_name_tag}, + util::{check_derivable_ext_var, debug_name_record, debug_name_tag, debug_name_tuple}, DeriveError, }; @@ -21,6 +21,7 @@ pub enum FlatHash { pub enum FlatHashKey { // Unfortunate that we must allocate here, c'est la vie Record(Vec), + Tuple(u32), TagUnion(Vec<(TagName, u16)>), } @@ -28,6 +29,7 @@ impl FlatHashKey { pub(crate) fn debug_name(&self) -> String { match self { FlatHashKey::Record(fields) => debug_name_record(fields), + FlatHashKey::Tuple(arity) => debug_name_tuple(*arity), FlatHashKey::TagUnion(tags) => debug_name_tag(tags), } } @@ -65,8 +67,14 @@ impl FlatHash { Ok(Key(FlatHashKey::Record(field_names))) } - FlatType::Tuple(_elems, _ext) => { - todo!(); + FlatType::Tuple(elems, ext) => { + let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext); + + check_derivable_ext_var(subs, ext, |ext| { + matches!(ext, Content::Structure(FlatType::EmptyTuple)) + })?; + + Ok(Key(FlatHashKey::Tuple(elems_iter.count() as _))) } FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => { // The recursion var doesn't matter, because the derived implementation will only @@ -109,7 +117,7 @@ impl FlatHash { // FlatType::Func(..) => Err(Underivable), }, - Content::Alias(sym, _, real_var, _) => match num_symbol_to_hash_lambda(sym) { + Content::Alias(sym, _, real_var, _) => match builtin_symbol_to_hash_lambda(sym) { Some(lambda) => Ok(lambda), // NB: I believe it is okay to unwrap opaques here because derivers are only used // by the backend, and the backend treats opaques like structural aliases. @@ -129,7 +137,7 @@ impl FlatHash { // during monomorphization, at which point we always choose a default layout // for ranged numbers, without concern for reification to a ground type. let chosen_width = range.default_compilation_width(); - let lambda = num_symbol_to_hash_lambda(chosen_width.symbol()).unwrap(); + let lambda = builtin_symbol_to_hash_lambda(chosen_width.symbol()).unwrap(); Ok(lambda) } // @@ -143,11 +151,16 @@ impl FlatHash { Content::LambdaSet(_) => Err(Underivable), } } + + pub fn from_builtin_symbol(symbol: Symbol) -> Result { + builtin_symbol_to_hash_lambda(symbol).ok_or(DeriveError::Underivable) + } } -const fn num_symbol_to_hash_lambda(symbol: Symbol) -> Option { +const fn builtin_symbol_to_hash_lambda(symbol: Symbol) -> Option { use FlatHash::*; match symbol { + Symbol::BOOL_BOOL => Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_BOOL)), Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => { Some(SingleLambdaSetImmediate(Symbol::HASH_ADD_U8)) } diff --git a/crates/compiler/derive_key/src/lib.rs b/crates/compiler/derive_key/src/lib.rs index c9b6e3731f..8d122c2661 100644 --- a/crates/compiler/derive_key/src/lib.rs +++ b/crates/compiler/derive_key/src/lib.rs @@ -52,7 +52,7 @@ impl DeriveKey { } } -#[derive(Hash, PartialEq, Eq, Debug)] +#[derive(Hash, Clone, PartialEq, Eq, Debug)] pub enum Derived { /// If a derived implementation name is well-known ahead-of-time, we can inline the symbol /// directly rather than associating a key for an implementation to be made later on. @@ -123,4 +123,34 @@ impl Derived { } } } + + pub fn builtin_with_builtin_symbol( + builtin: DeriveBuiltin, + symbol: Symbol, + ) -> Result { + match builtin { + DeriveBuiltin::ToEncoder => match encoding::FlatEncodable::from_builtin_symbol(symbol)? + { + FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)), + FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))), + }, + DeriveBuiltin::Decoder => match decoding::FlatDecodable::from_builtin_symbol(symbol)? { + FlatDecodable::Immediate(imm) => Ok(Derived::Immediate(imm)), + FlatDecodable::Key(repr) => Ok(Derived::Key(DeriveKey::Decoder(repr))), + }, + DeriveBuiltin::Hash => match hash::FlatHash::from_builtin_symbol(symbol)? { + FlatHash::SingleLambdaSetImmediate(imm) => { + Ok(Derived::SingleLambdaSetImmediate(imm)) + } + FlatHash::Key(repr) => Ok(Derived::Key(DeriveKey::Hash(repr))), + }, + DeriveBuiltin::IsEq => { + // If obligation checking passes, we always lower derived implementations of `isEq` + // to the `Eq` low-level, to be fulfilled by the backends. + Ok(Derived::SingleLambdaSetImmediate( + Symbol::BOOL_STRUCTURAL_EQ, + )) + } + } + } } diff --git a/crates/compiler/derive_key/src/util.rs b/crates/compiler/derive_key/src/util.rs index a834dcb2d0..8fbab82957 100644 --- a/crates/compiler/derive_key/src/util.rs +++ b/crates/compiler/derive_key/src/util.rs @@ -43,6 +43,10 @@ pub(crate) fn debug_name_record(fields: &[Lowercase]) -> String { str } +pub(crate) fn debug_name_tuple(arity: u32) -> String { + format!("(arity:{arity})") +} + pub(crate) fn debug_name_tag(tags: &[(TagName, u16)]) -> String { let mut str = String::from('['); tags.iter().enumerate().for_each(|(i, (tag, arity))| { diff --git a/crates/compiler/exhaustive/Cargo.toml b/crates/compiler/exhaustive/Cargo.toml index 045e477d50..17d96fcd40 100644 --- a/crates/compiler/exhaustive/Cargo.toml +++ b/crates/compiler/exhaustive/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "roc_exhaustive" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Provides exhaustiveness checking for Roc." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_collections = { path = "../collections" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } roc_error_macros = { path = "../../error_macros" } +roc_module = { path = "../module" } roc_problem = { path = "../problem" } +roc_region = { path = "../region" } diff --git a/crates/compiler/exhaustive/src/lib.rs b/crates/compiler/exhaustive/src/lib.rs index 561564f7fd..2b71f3e6cf 100644 --- a/crates/compiler/exhaustive/src/lib.rs +++ b/crates/compiler/exhaustive/src/lib.rs @@ -38,6 +38,7 @@ pub enum RenderAs { Tag, Opaque, Record(Vec), + Tuple, Guard, } diff --git a/crates/compiler/fmt/Cargo.toml b/crates/compiler/fmt/Cargo.toml index 4d0afff245..d72ce03678 100644 --- a/crates/compiler/fmt/Cargo.toml +++ b/crates/compiler/fmt/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "roc_fmt" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "The roc code formatter." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_collections = { path = "../collections" } -roc_region = { path = "../region" } roc_module = { path = "../module" } roc_parse = { path = "../parse" } -bumpalo.workspace = true +roc_region = { path = "../region" } + +bumpalo.workspace = true diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs index 73ed08a605..1091b81458 100644 --- a/crates/compiler/fmt/src/annotation.rs +++ b/crates/compiler/fmt/src/annotation.rs @@ -177,7 +177,7 @@ impl<'a> Formattable for TypeAnnotation<'a> { annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline()) } - Tuple { fields, ext } => { + Tuple { elems: fields, ext } => { match ext { Some(ann) if ann.value.is_multiline() => return true, _ => {} @@ -343,7 +343,7 @@ impl<'a> Formattable for TypeAnnotation<'a> { } } - Tuple { fields, ext } => { + Tuple { elems: fields, ext } => { fmt_collection(buf, indent, Braces::Round, *fields, newlines); if let Some(loc_ext_ann) = *ext { @@ -509,6 +509,7 @@ fn format_assigned_field_help<'a, 'buf, T>( buf.spaces(separator_spaces); buf.push('?'); + buf.spaces(1); ann.value.format(buf, indent); } LabelOnly(name) => { diff --git a/crates/compiler/fmt/src/collection.rs b/crates/compiler/fmt/src/collection.rs index 643928fcb1..3d241dafca 100644 --- a/crates/compiler/fmt/src/collection.rs +++ b/crates/compiler/fmt/src/collection.rs @@ -38,7 +38,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>( let braces_indent = indent; let item_indent = braces_indent + INDENT; if newline == Newlines::Yes { - buf.newline(); + buf.ensure_ends_with_newline(); } buf.indent(braces_indent); buf.push(start); diff --git a/crates/compiler/fmt/src/def.rs b/crates/compiler/fmt/src/def.rs index 930f8e9351..d45e37b733 100644 --- a/crates/compiler/fmt/src/def.rs +++ b/crates/compiler/fmt/src/def.rs @@ -61,7 +61,7 @@ impl<'a> Formattable for TypeDef<'a> { &self, buf: &mut Buf<'buf>, _parens: Parens, - _newlines: Newlines, + newlines: Newlines, indent: u16, ) { use roc_parse::ast::TypeDef::*; @@ -76,8 +76,19 @@ impl<'a> Formattable for TypeDef<'a> { for var in *vars { buf.spaces(1); + + let need_parens = matches!(var.value, Pattern::Apply(..)); + + if need_parens { + buf.push_str("("); + } + fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); buf.indent(indent); + + if need_parens { + buf.push_str(")"); + } } buf.push_str(" :"); @@ -86,22 +97,10 @@ impl<'a> Formattable for TypeDef<'a> { ann.format(buf, indent) } Opaque { - header: TypeHeader { name, vars }, + header, typ: ann, derived: has_abilities, } => { - buf.indent(indent); - buf.push_str(name.value); - - for var in *vars { - buf.spaces(1); - fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); - buf.indent(indent); - } - - buf.push_str(" :="); - buf.spaces(1); - let ann_is_where_clause = matches!(ann.extract_spaces().item, TypeAnnotation::Where(..)); @@ -115,7 +114,7 @@ impl<'a> Formattable for TypeDef<'a> { let make_multiline = ann.is_multiline() || has_abilities_multiline; - ann.format(buf, indent); + fmt_general_def(header, buf, indent, ":=", &ann.value, newlines); if let Some(has_abilities) = has_abilities { buf.spaces(1); @@ -167,6 +166,29 @@ impl<'a> Formattable for TypeDef<'a> { } } +impl<'a> Formattable for TypeHeader<'a> { + fn is_multiline(&self) -> bool { + self.vars.iter().any(|v| v.is_multiline()) + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + indent: u16, + ) { + buf.indent(indent); + buf.push_str(self.name.value); + + for var in self.vars.iter() { + buf.spaces(1); + fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); + buf.indent(indent); + } + } +} + impl<'a> Formattable for ValueDef<'a> { fn is_multiline(&self) -> bool { use roc_parse::ast::ValueDef::*; @@ -193,63 +215,14 @@ impl<'a> Formattable for ValueDef<'a> { use roc_parse::ast::ValueDef::*; match self { Annotation(loc_pattern, loc_annotation) => { - loc_pattern.format(buf, indent); - buf.indent(indent); - - if loc_annotation.is_multiline() { - buf.push_str(" :"); - buf.spaces(1); - - let should_outdent = match loc_annotation.value { - TypeAnnotation::SpaceBefore(sub_def, spaces) => match sub_def { - TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => { - let is_only_newlines = spaces.iter().all(|s| s.is_newline()); - is_only_newlines && sub_def.is_multiline() - } - _ => false, - }, - TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => true, - _ => false, - }; - - if should_outdent { - match loc_annotation.value { - TypeAnnotation::SpaceBefore(sub_def, _) => { - sub_def.format_with_options( - buf, - Parens::NotNeeded, - Newlines::No, - indent, - ); - } - _ => { - loc_annotation.format_with_options( - buf, - Parens::NotNeeded, - Newlines::No, - indent, - ); - } - } - } else { - loc_annotation.format_with_options( - buf, - Parens::NotNeeded, - newlines, - indent + INDENT, - ); - } - } else { - buf.spaces(1); - buf.push(':'); - buf.spaces(1); - loc_annotation.format_with_options( - buf, - Parens::NotNeeded, - Newlines::No, - indent, - ); - } + fmt_general_def( + loc_pattern, + buf, + indent, + ":", + &loc_annotation.value, + newlines, + ); } Body(loc_pattern, loc_expr) => { fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent); @@ -266,34 +239,7 @@ impl<'a> Formattable for ValueDef<'a> { body_pattern, body_expr, } => { - let is_type_multiline = ann_type.is_multiline(); - let is_type_function = matches!( - ann_type.value, - TypeAnnotation::Function(..) - | TypeAnnotation::SpaceBefore(TypeAnnotation::Function(..), ..) - | TypeAnnotation::SpaceAfter(TypeAnnotation::Function(..), ..) - ); - - let next_indent = if is_type_multiline { - indent + INDENT - } else { - indent - }; - - ann_pattern.format(buf, indent); - buf.push_str(" :"); - - if is_type_multiline && is_type_function { - ann_type.format_with_options( - buf, - Parens::NotNeeded, - Newlines::Yes, - next_indent, - ); - } else { - buf.spaces(1); - ann_type.format(buf, indent); - } + fmt_general_def(ann_pattern, buf, indent, ":", &ann_type.value, newlines); if let Some(comment_str) = comment { buf.push_str(" #"); @@ -308,6 +254,66 @@ impl<'a> Formattable for ValueDef<'a> { } } +fn fmt_general_def( + lhs: L, + buf: &mut Buf, + indent: u16, + sep: &str, + rhs: &TypeAnnotation, + newlines: Newlines, +) { + lhs.format(buf, indent); + buf.indent(indent); + + if rhs.is_multiline() { + buf.spaces(1); + buf.push_str(sep); + buf.spaces(1); + + let should_outdent = should_outdent(rhs); + + if should_outdent { + match rhs { + TypeAnnotation::SpaceBefore(sub_def, _) => { + sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + } + _ => { + rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + } + } + } else { + rhs.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT); + } + } else { + buf.spaces(1); + buf.push_str(sep); + buf.spaces(1); + rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + } +} + +fn should_outdent(mut rhs: &TypeAnnotation) -> bool { + loop { + match rhs { + TypeAnnotation::SpaceBefore(sub_def, spaces) => { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + if !is_only_newlines || !sub_def.is_multiline() { + return false; + } + rhs = sub_def; + } + TypeAnnotation::Where(ann, _clauses) => { + if !ann.is_multiline() { + return false; + } + rhs = &ann.value; + } + TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => return true, + _ => return false, + } + } +} + fn fmt_dbg_in_def<'a, 'buf>( buf: &mut Buf<'buf>, condition: &'a Loc>, diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs index 1fc7a90032..8e716e1772 100644 --- a/crates/compiler/fmt/src/expr.rs +++ b/crates/compiler/fmt/src/expr.rs @@ -1,4 +1,4 @@ -use crate::annotation::{except_last, Formattable, Newlines, Parens}; +use crate::annotation::{except_last, is_collection_multiline, Formattable, Newlines, Parens}; use crate::collection::{fmt_collection, Braces}; use crate::def::fmt_defs; use crate::pattern::fmt_pattern; @@ -12,6 +12,7 @@ use roc_parse::ast::{ AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch, }; use roc_parse::ast::{StrLiteral, StrSegment}; +use roc_parse::ident::Accessor; use roc_region::all::Loc; impl<'a> Formattable for Expr<'a> { @@ -35,9 +36,8 @@ impl<'a> Formattable for Expr<'a> { | NonBase10Int { .. } | SingleQuote(_) | RecordAccess(_, _) - | RecordAccessorFunction(_) + | AccessorFunction(_) | TupleAccess(_, _) - | TupleAccessorFunction(_) | Var { .. } | Underscore { .. } | MalformedIdent(_, _) @@ -49,27 +49,9 @@ impl<'a> Formattable for Expr<'a> { // These expressions always have newlines Defs(_, _) | When(_, _) => true, - List(items) => items.iter().any(|loc_expr| loc_expr.is_multiline()), + List(items) => is_collection_multiline(items), - Str(literal) => { - use roc_parse::ast::StrLiteral::*; - - match literal { - PlainLine(string) => { - // When a PlainLine contains '\n' or '"', format as a block string - string.contains('"') || string.contains('\n') - } - Line(_) => { - // If this had any newlines, it'd have parsed as Block. - false - } - Block(_) => { - // Block strings are always formatted on multiple lines, - // even if the string is only a single line. - true - } - } - } + Str(literal) => is_str_multiline(literal), Apply(loc_expr, args, _) => { loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline()) } @@ -114,9 +96,9 @@ impl<'a> Formattable for Expr<'a> { .any(|loc_pattern| loc_pattern.is_multiline()) } - Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()), - Tuple(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()), - RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()), + Record(fields) => is_collection_multiline(fields), + Tuple(fields) => is_collection_multiline(fields), + RecordUpdate { fields, .. } => is_collection_multiline(fields), } } @@ -271,8 +253,21 @@ impl<'a> Formattable for Expr<'a> { indent }; + let expr_needs_parens = + matches!(loc_expr.value.extract_spaces().item, Expr::Closure(..)) + && !loc_args.is_empty(); + + if expr_needs_parens { + buf.push('('); + } + loc_expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); + if expr_needs_parens { + buf.indent(indent); + buf.push(')'); + } + for loc_arg in loc_args.iter() { if should_reflow_outdentable { buf.spaces(1); @@ -432,23 +427,45 @@ impl<'a> Formattable for Expr<'a> { } } - sub_expr.format_with_options(buf, Parens::InApply, newlines, indent); + let needs_newline = match &sub_expr.value { + SpaceBefore(..) => true, + Str(text) => is_str_multiline(text), + _ => false, + }; + let needs_parens = + needs_newline && matches!(unary_op.value, called_via::UnaryOp::Negate); + + if needs_parens { + // Unary negation can't be followed by whitespace (which is what a newline is) - so + // we need to wrap the negated value in parens. + buf.push('('); + } + + let inner_indent = if needs_parens { + indent + INDENT + } else { + indent + }; + + sub_expr.format_with_options(buf, Parens::InApply, newlines, inner_indent); + + if needs_parens { + buf.push(')'); + } } - RecordAccessorFunction(key) => { + AccessorFunction(key) => { buf.indent(indent); buf.push('.'); - buf.push_str(key); + match key { + Accessor::RecordField(key) => buf.push_str(key), + Accessor::TupleIndex(key) => buf.push_str(key), + } } RecordAccess(expr, key) => { expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); buf.push('.'); buf.push_str(key); } - TupleAccessorFunction(key) => { - buf.indent(indent); - buf.push('.'); - buf.push_str(key); - } TupleAccess(expr, key) => { expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); buf.push('.'); @@ -464,6 +481,26 @@ impl<'a> Formattable for Expr<'a> { } } +fn is_str_multiline(literal: &StrLiteral) -> bool { + use roc_parse::ast::StrLiteral::*; + + match literal { + PlainLine(string) => { + // When a PlainLine contains '\n' or '"', format as a block string + string.contains('"') || string.contains('\n') + } + Line(_) => { + // If this had any newlines, it'd have parsed as Block. + false + } + Block(_) => { + // Block strings are always formatted on multiple lines, + // even if the string is only a single line. + true + } + } +} + fn needs_unicode_escape(ch: char) -> bool { matches!(ch, '\u{0000}'..='\u{001f}' | '\u{007f}'..='\u{009f}') } @@ -585,11 +622,11 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u buf.ensure_ends_with_newline(); buf.indent(indent); buf.push_str("\"\"\""); - buf.newline(); + buf.push_newline_literal(); for line in string.split('\n') { buf.indent(indent); buf.push_str_allow_spaces(line); - buf.newline(); + buf.push_newline_literal(); } buf.indent(indent); buf.push_str("\"\"\""); @@ -613,7 +650,7 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u buf.ensure_ends_with_newline(); buf.indent(indent); buf.push_str("\"\"\""); - buf.newline(); + buf.push_newline_literal(); for segments in lines.iter() { for seg in segments.iter() { @@ -622,11 +659,11 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u buf.indent(indent); format_str_segment(seg, buf, indent); } else { - buf.newline(); + buf.push_newline_literal(); } } - buf.newline(); + buf.push_newline_literal(); } buf.indent(indent); buf.push_str("\"\"\""); @@ -1282,7 +1319,7 @@ fn fmt_record<'a, 'buf>( let loc_fields = fields.items; let final_comments = fields.final_comments(); buf.indent(indent); - if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) { + if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) && update.is_none() { buf.push_str("{}"); } else { buf.push('{'); diff --git a/crates/compiler/fmt/src/lib.rs b/crates/compiler/fmt/src/lib.rs index 95eca1d978..88a4974bf0 100644 --- a/crates/compiler/fmt/src/lib.rs +++ b/crates/compiler/fmt/src/lib.rs @@ -106,12 +106,20 @@ impl<'a> Buf<'a> { self.spaces_to_flush += count; } - pub fn newline(&mut self) { + /// Only for use in emitting newlines in block strings, which don't follow the rule of + /// having at most two newlines in a row. + pub fn push_newline_literal(&mut self) { self.spaces_to_flush = 0; self.newlines_to_flush += 1; self.beginning_of_line = true; } + pub fn newline(&mut self) { + self.spaces_to_flush = 0; + self.newlines_to_flush = std::cmp::min(self.newlines_to_flush + 1, 2); + self.beginning_of_line = true; + } + /// Ensures the current buffer ends in a newline, if it didn't already. /// Doesn't add a newline if the buffer already ends in one. pub fn ensure_ends_with_newline(&mut self) { diff --git a/crates/compiler/fmt/src/spaces.rs b/crates/compiler/fmt/src/spaces.rs index b91fcad716..6cefc289a5 100644 --- a/crates/compiler/fmt/src/spaces.rs +++ b/crates/compiler/fmt/src/spaces.rs @@ -656,9 +656,8 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> { }, Expr::Str(a) => Expr::Str(a.remove_spaces(arena)), Expr::RecordAccess(a, b) => Expr::RecordAccess(arena.alloc(a.remove_spaces(arena)), b), - Expr::RecordAccessorFunction(a) => Expr::RecordAccessorFunction(a), + Expr::AccessorFunction(a) => Expr::AccessorFunction(a), Expr::TupleAccess(a, b) => Expr::TupleAccess(arena.alloc(a.remove_spaces(arena)), b), - Expr::TupleAccessorFunction(a) => Expr::TupleAccessorFunction(a), Expr::List(a) => Expr::List(a.remove_spaces(arena)), Expr::RecordUpdate { update, fields } => Expr::RecordUpdate { update: arena.alloc(update.remove_spaces(arena)), @@ -745,6 +744,7 @@ fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent { BadIdent::WeirdDotQualified(_) => BadIdent::WeirdDotQualified(Position::zero()), BadIdent::StrayDot(_) => BadIdent::StrayDot(Position::zero()), BadIdent::BadOpaqueRef(_) => BadIdent::BadOpaqueRef(Position::zero()), + BadIdent::QualifiedTupleAccessor(_) => BadIdent::QualifiedTupleAccessor(Position::zero()), } } @@ -813,8 +813,8 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> { vars: vars.remove_spaces(arena), }, ), - TypeAnnotation::Tuple { fields, ext } => TypeAnnotation::Tuple { - fields: fields.remove_spaces(arena), + TypeAnnotation::Tuple { elems: fields, ext } => TypeAnnotation::Tuple { + elems: fields.remove_spaces(arena), ext: ext.remove_spaces(arena), }, TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record { diff --git a/crates/compiler/gen_dev/Cargo.toml b/crates/compiler/gen_dev/Cargo.toml index 5225d9f3c5..12680ffdb3 100644 --- a/crates/compiler/gen_dev/Cargo.toml +++ b/crates/compiler/gen_dev/Cargo.toml @@ -1,28 +1,29 @@ [package] name = "roc_gen_dev" description = "The development backend for the Roc compiler" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dependencies] -roc_collections = { path = "../collections" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_problem = { path = "../problem" } -roc_types = { path = "../types" } roc_builtins = { path = "../builtins" } -roc_unify = { path = "../unify" } -roc_solve = { path = "../solve" } -roc_mono = { path = "../mono" } -roc_target = { path = "../roc_target" } +roc_collections = { path = "../collections" } roc_error_macros = { path = "../../error_macros" } +roc_module = { path = "../module" } +roc_mono = { path = "../mono" } +roc_problem = { path = "../problem" } +roc_region = { path = "../region" } +roc_solve = { path = "../solve" } +roc_target = { path = "../roc_target" } +roc_types = { path = "../types" } +roc_unify = { path = "../unify" } bumpalo.workspace = true -target-lexicon.workspace = true object.workspace = true packed_struct.workspace = true +target-lexicon.workspace = true [dev-dependencies] roc_can = { path = "../can" } diff --git a/crates/compiler/gen_dev/README.md b/crates/compiler/gen_dev/README.md index 15cffb0bc4..b2ab191534 100644 --- a/crates/compiler/gen_dev/README.md +++ b/crates/compiler/gen_dev/README.md @@ -86,6 +86,57 @@ This is the general procedure I follow with some helpful links: 1. If things aren't working, reach out on zulip. Get advice, maybe even pair. 1. Make a PR. +## Debugging x86_64 backend output + +While working on the x86_64 backend it may be useful to inspect the assembly output of a given piece of Roc code. With the right tools, you can do this rather easily. You'll need `objdump` to follow along. + +We'll try to explore the x86 assembly output of some lines of Roc code: + +```elixir +app "dbg" + provides [main] to "." + +main = + (List.len [1]) + 41 +``` + +If this file exists somewhere in the repo as `dbg.roc`, we'll be able to compile an object file by issuing the following command: + +```console +# `cargo run --` can be replaces with calling the compiled `roc` cli binary. +$ cargo run -- build --dev main.roc --no-link +``` + +Which will produce a minimal `dbg.o` object file containing the output assembly code. This object file can be inspected by using `objdump` in the following way: + +```console +$ objdump -M intel -dS dbg.o +dbg.o: file format elf64-x86-64 + +Disassembly of section .text.700000006: + +0000000000000000 : + 0: 55 push rbp + 1: 48 89 e5 mov rbp,rsp + 4: 48 8b 85 18 00 00 00 mov rax,QWORD PTR [rbp+0x18] + b: 5d pop rbp + c: c3 ret + +Disassembly of section .text.400000013: + +0000000000000000 : + 0: 55 push rbp +# .. more output .. + +Disassembly of section .text.1000000000: + +0000000000000000 : + 0: 55 push rbp +# .. more output .. +``` + +The output lines contain the hexadecimal representation of the x86 opcodes and fields followed by the `intel` assembly syntax. This setup is very useful for figuring out the causes of invalid pointer references (or equivalent) when running the resulting x86 assembly. + ## Helpful Resources - [Compiler Explorer](https://godbolt.org/) - @@ -105,10 +156,10 @@ This is the general procedure I follow with some helpful links: Also, sometimes it doesn't seem to generate things quite as you expect. - [Alternative Online Assembler](http://shell-storm.org/online/Online-Assembler-and-Disassembler/) - Like previous but with more architecture options. -- [x86 and amd64 instruction reference](https://www.felixcloutier.com/x86/) - +- [x86 and amd64 instruction reference](https://web.archive.org/web/20230221053750/https://www.felixcloutier.com/x86/) - Great for looking up x86_64 instructions and there bytes. Definitely missing information if you aren't used to reading it. -- [Intel 64 ISA Reference](https://software.intel.com/content/dam/develop/public/us/en/documents/325383-sdm-vol-2abcd.pdf) - +- [Intel 64 ISA Reference](https://community.intel.com/legacyfs/online/drupal_files/managed/a4/60/325383-sdm-vol-2abcd.pdf) - Super dense manual. Contains everything you would need to know for x86_64. Also is like 2000 pages. diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 8f049e4f2c..ae3842c4a4 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -2,10 +2,13 @@ use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::Relocation; use bumpalo::collections::Vec; use packed_struct::prelude::*; +use roc_builtins::bitcode::FloatWidth; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{InLayout, STLayoutInterner}; +use super::CompareOperation; + #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[allow(dead_code)] pub enum AArch64GeneralReg { @@ -609,9 +612,31 @@ impl Assembler for AArch64Assembler { } } #[inline(always)] + fn mov_reg32_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) { + todo!() + } + #[inline(always)] + fn mov_reg16_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) { + todo!() + } + #[inline(always)] + fn mov_reg8_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) { + todo!() + } + #[inline(always)] fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) { todo!("saving floating point reg to base offset for AArch64"); } + #[inline(always)] + fn movesd_mem64_offset32_freg64( + _buf: &mut Vec<'_, u8>, + _ptr: AArch64GeneralReg, + _offset: i32, + _src: AArch64FloatReg, + ) { + todo!() + } + #[inline(always)] fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) { if offset < 0 { @@ -624,6 +649,19 @@ impl Assembler for AArch64Assembler { } } + #[inline(always)] + fn mov_base32_reg32(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) { + todo!() + } + #[inline(always)] + fn mov_base32_reg16(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) { + todo!() + } + #[inline(always)] + fn mov_base32_reg8(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) { + todo!() + } + #[inline(always)] fn mov_reg64_mem64_offset32( buf: &mut Vec<'_, u8>, @@ -640,6 +678,41 @@ impl Assembler for AArch64Assembler { todo!("mem offsets over 32k for AArch64"); } } + #[inline(always)] + fn mov_reg32_mem32_offset32( + buf: &mut Vec<'_, u8>, + dst: AArch64GeneralReg, + src: AArch64GeneralReg, + offset: i32, + ) { + if offset < 0 { + todo!("negative mem offsets for AArch64"); + } else if offset < (0xFFF << 8) { + debug_assert!(offset % 8 == 0); + ldr_reg64_reg64_imm12(buf, dst, src, (offset as u16) >> 3); + } else { + todo!("mem offsets over 32k for AArch64"); + } + } + #[inline(always)] + fn mov_reg16_mem16_offset32( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src: AArch64GeneralReg, + _offset: i32, + ) { + todo!() + } + #[inline(always)] + fn mov_reg8_mem8_offset32( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src: AArch64GeneralReg, + _offset: i32, + ) { + todo!() + } + #[inline(always)] fn mov_mem64_offset32_reg64( buf: &mut Vec<'_, u8>, @@ -657,6 +730,36 @@ impl Assembler for AArch64Assembler { } } + #[inline(always)] + fn mov_mem32_offset32_reg32( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _offset: i32, + _src: AArch64GeneralReg, + ) { + todo!() + } + + #[inline(always)] + fn mov_mem16_offset32_reg16( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _offset: i32, + _src: AArch64GeneralReg, + ) { + todo!() + } + + #[inline(always)] + fn mov_mem8_offset32_reg8( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _offset: i32, + _src: AArch64GeneralReg, + ) { + todo!() + } + #[inline(always)] fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) { debug_assert!(size <= 8); @@ -740,12 +843,12 @@ impl Assembler for AArch64Assembler { } #[inline(always)] fn sub_reg64_reg64_reg64( - _buf: &mut Vec<'_, u8>, - _dst: AArch64GeneralReg, - _src1: AArch64GeneralReg, - _src2: AArch64GeneralReg, + buf: &mut Vec<'_, u8>, + dst: AArch64GeneralReg, + src1: AArch64GeneralReg, + src2: AArch64GeneralReg, ) { - todo!("registers subtractions for AArch64"); + sub_reg64_reg64_reg64(buf, dst, src1, src2); } #[inline(always)] @@ -788,6 +891,18 @@ impl Assembler for AArch64Assembler { todo!("registers unsigned less than for AArch64"); } + #[inline(always)] + fn cmp_freg_freg_reg64( + _buf: &mut Vec<'_, u8>, + _dst: AArch64GeneralReg, + _src1: AArch64FloatReg, + _src2: AArch64FloatReg, + _width: FloatWidth, + _operation: CompareOperation, + ) { + todo!("registers float comparison for AArch64"); + } + #[inline(always)] fn igt_reg64_reg64_reg64( _buf: &mut Vec<'_, u8>, @@ -899,6 +1014,53 @@ impl Assembler for AArch64Assembler { ) { todo!("bitwise xor for AArch64") } + + fn shl_reg64_reg64_reg64<'a, 'r, ASM, CC>( + _buf: &mut Vec<'a, u8>, + _storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + todo!("shl for AArch64") + } + + fn shr_reg64_reg64_reg64<'a, 'r, ASM, CC>( + _buf: &mut Vec<'a, u8>, + _storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + todo!("shr for AArch64") + } + + fn sar_reg64_reg64_reg64<'a, 'r, ASM, CC>( + _buf: &mut Vec<'a, u8>, + _storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + todo!("sar for AArch64") + } + + fn sqrt_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) { + todo!("sqrt") + } + + fn sqrt_freg32_freg32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) { + todo!("sqrt") + } } impl AArch64Assembler {} @@ -1315,6 +1477,19 @@ fn sub_reg64_reg64_imm12( buf.extend(inst.bytes()); } +/// `SUB Xd, Xm, Xn` -> Subtract Xm and Xn and place the result into Xd. +#[inline(always)] +fn sub_reg64_reg64_reg64( + buf: &mut Vec<'_, u8>, + dst: AArch64GeneralReg, + src1: AArch64GeneralReg, + src2: AArch64GeneralReg, +) { + let inst = ArithmeticShifted::new(true, false, ShiftType::LSL, 0, src2, src1, dst); + + buf.extend(inst.bytes()); +} + /// `RET Xn` -> Return to the address stored in Xn. #[inline(always)] fn ret_reg64(buf: &mut Vec<'_, u8>, xn: AArch64GeneralReg) { @@ -1535,6 +1710,34 @@ mod tests { ); } + #[test] + fn test_sub_reg64_reg64_reg64() { + disassembler_test!( + sub_reg64_reg64_reg64, + |reg1: AArch64GeneralReg, reg2: AArch64GeneralReg, reg3: AArch64GeneralReg| { + if reg2 == AArch64GeneralReg::ZRSP { + // When the second register is ZR, it gets disassembled as neg, + // which is an alias for sub. + format!( + "neg {}, {}", + reg1.capstone_string(UsesZR), + reg3.capstone_string(UsesZR) + ) + } else { + format!( + "sub {}, {}, {}", + reg1.capstone_string(UsesZR), + reg2.capstone_string(UsesZR), + reg3.capstone_string(UsesZR) + ) + } + }, + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + ALL_GENERAL_REGS + ); + } + #[test] fn test_ret_reg64() { disassembler_test!( diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index c8c4ea209d..81e8f6bc7a 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2,7 +2,7 @@ use crate::{ single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env, Relocation, }; -use bumpalo::collections::Vec; +use bumpalo::collections::{CollectIn, Vec}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_collections::all::MutMap; use roc_error_macros::internal_error; @@ -25,7 +25,6 @@ pub(crate) mod x86_64; use storage::{RegStorage, StorageManager}; -const REFCOUNT_ONE: u64 = i64::MIN as u64; // TODO: on all number functions double check and deal with over/underflow. pub trait CallConv>: @@ -114,6 +113,13 @@ pub trait CallConv: Sized + Copy { src2: GeneralReg, ); + fn shl_reg64_reg64_reg64<'a, 'r, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ) where + ASM: Assembler, + CC: CallConv; + + fn shr_reg64_reg64_reg64<'a, 'r, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ) where + ASM: Assembler, + CC: CallConv; + + fn sar_reg64_reg64_reg64<'a, 'r, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ) where + ASM: Assembler, + CC: CallConv; + fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String); /// Jumps by an offset of offset bytes unconditionally. @@ -207,22 +243,67 @@ pub trait Assembler: Sized + Copy { // base32 is similar to stack based instructions but they reference the base/frame pointer. fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); - fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); - fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); - fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); + fn mov_reg32_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); + fn mov_reg16_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); + fn mov_reg8_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); + + fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); + + fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn mov_base32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn mov_base32_reg16(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn mov_base32_reg8(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + + // move from memory (a pointer) to register fn mov_reg64_mem64_offset32( buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg, offset: i32, ); + fn mov_reg32_mem32_offset32( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src: GeneralReg, + offset: i32, + ); + fn mov_reg16_mem16_offset32( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src: GeneralReg, + offset: i32, + ); + fn mov_reg8_mem8_offset32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg, offset: i32); + + // move from register to memory fn mov_mem64_offset32_reg64( buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, src: GeneralReg, ); + fn mov_mem32_offset32_reg32( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + offset: i32, + src: GeneralReg, + ); + fn mov_mem16_offset32_reg16( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + offset: i32, + src: GeneralReg, + ); + fn mov_mem8_offset32_reg8(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, src: GeneralReg); + + fn movesd_mem64_offset32_freg64( + buf: &mut Vec<'_, u8>, + ptr: GeneralReg, + offset: i32, + src: FloatReg, + ); /// Sign extends the data at `offset` with `size` as it copies it to `dst` /// size must be less than or equal to 8. @@ -236,6 +317,9 @@ pub trait Assembler: Sized + Copy { fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + fn sqrt_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); + fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); + fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); fn mul_freg32_freg32_freg32( buf: &mut Vec<'_, u8>, @@ -332,6 +416,15 @@ pub trait Assembler: Sized + Copy { src2: GeneralReg, ); + fn cmp_freg_freg_reg64( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src1: FloatReg, + src2: FloatReg, + width: FloatWidth, + operation: CompareOperation, + ); + fn igt_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, dst: GeneralReg, @@ -686,14 +779,23 @@ impl< let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); } - _ => { - CC::load_returned_complex_symbol( - &mut self.buf, - &mut self.storage_manager, - self.layout_interner, - dst, - ret_layout, - ); + other => { + // + match self.layout_interner.get(other) { + Layout::Boxed(_) => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]); + } + _ => { + CC::load_returned_complex_symbol( + &mut self.buf, + &mut self.storage_manager, + self.layout_interner, + dst, + ret_layout, + ); + } + } } } } @@ -920,6 +1022,30 @@ impl< } } + fn build_num_sub_checked( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + num_layout: &InLayout<'a>, + return_layout: &InLayout<'a>, + ) { + let function_name = match self.interner().get(*num_layout) { + Layout::Builtin(Builtin::Int(width)) => &bitcode::NUM_SUB_CHECKED_INT[width], + Layout::Builtin(Builtin::Float(width)) => &bitcode::NUM_SUB_CHECKED_FLOAT[width], + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_SUB_WITH_OVERFLOW, + x => internal_error!("NumSubChecked is not defined for {:?}", x), + }; + + self.build_fn_call( + dst, + function_name.to_string(), + &[*src1, *src2], + &[*num_layout, *num_layout], + return_layout, + ) + } + fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) { use Builtin::Int; @@ -1035,19 +1161,9 @@ impl< } fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) { - match self.layout_interner.get(*layout) { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - let src1_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, src1); - let src2_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, src2); - ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); - } - x => todo!("NumSub: layout, {:?}", x), - } + // for the time being, `num_sub` is implemented as wrapping subtraction. In roc, the normal + // `sub` should panic on overflow, but we just don't do that yet + self.build_num_sub_wrap(dst, src1, src2, layout) } fn build_num_sub_wrap( @@ -1058,7 +1174,7 @@ impl< layout: &InLayout<'a>, ) { match self.layout_interner.get(*layout) { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + Layout::Builtin(Builtin::Int(quadword_and_smaller!())) => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let src1_reg = self .storage_manager @@ -1074,7 +1190,7 @@ impl< fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) { match *arg_layout { - single_register_int_builtins!() => { + single_register_int_builtins!() | Layout::BOOL => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let src1_reg = self .storage_manager @@ -1084,13 +1200,23 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::STR => { + // use a zig call + self.build_fn_call( + dst, + bitcode::STR_EQUAL.to_string(), + &[*src1, *src2], + &[Layout::STR, Layout::STR], + &Layout::BOOL, + ) + } x => todo!("NumEq: layout, {:?}", x), } } fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) { - match self.layout_interner.get(*arg_layout) { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + match *arg_layout { + single_register_int_builtins!() | Layout::BOOL => { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let src1_reg = self .storage_manager @@ -1100,10 +1226,44 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::STR => { + self.build_fn_call( + dst, + bitcode::STR_EQUAL.to_string(), + &[*src1, *src2], + &[Layout::STR, Layout::STR], + &Layout::BOOL, + ); + + // negate the result + let tmp = &Symbol::DEV_TMP; + let tmp_reg = self.storage_manager.claim_general_reg(&mut self.buf, tmp); + ASM::mov_reg64_imm64(&mut self.buf, tmp_reg, 164); + + let dst_reg = self.storage_manager.load_to_general_reg(&mut self.buf, dst); + ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, dst_reg, tmp_reg); + } x => todo!("NumNeq: layout, {:?}", x), } } + fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>) { + match *arg_layout { + Layout::BOOL => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); + + // Not would usually be implemented as `xor src, -1` followed by `and src, 1` + // but since our booleans are represented as `0x101010101010101` currently, we can simply XOR with that + let bool_val = [true as u8; 8]; + ASM::mov_reg64_imm64(&mut self.buf, dst_reg, i64::from_ne_bytes(bool_val)); + ASM::xor_reg64_reg64_reg64(&mut self.buf, src_reg, src_reg, dst_reg); + ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg); + } + x => todo!("Not: layout, {:?}", x), + } + } + fn build_num_lt( &mut self, dst: &Symbol, @@ -1132,6 +1292,20 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::ult_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::Builtin(Builtin::Float(width)) => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); + + ASM::cmp_freg_freg_reg64( + &mut self.buf, + dst_reg, + src1_reg, + src2_reg, + width, + CompareOperation::LessThan, + ); + } x => todo!("NumLt: layout, {:?}", x), } } @@ -1164,6 +1338,20 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::ugt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::Builtin(Builtin::Float(width)) => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); + + ASM::cmp_freg_freg_reg64( + &mut self.buf, + dst_reg, + src1_reg, + src2_reg, + width, + CompareOperation::GreaterThan, + ); + } x => todo!("NumGt: layout, {:?}", x), } } @@ -1244,6 +1432,26 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::F64 | Layout::F32 => { + let width = if *arg_layout == Layout::F64 { + FloatWidth::F64 + } else { + FloatWidth::F32 + }; + + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); + + ASM::cmp_freg_freg_reg64( + &mut self.buf, + dst_reg, + src1_reg, + src2_reg, + width, + CompareOperation::LessThanOrEqual, + ); + } x => todo!("NumLte: layout, {:?}", x), } } @@ -1266,6 +1474,26 @@ impl< .load_to_general_reg(&mut self.buf, src2); ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); } + Layout::F64 | Layout::F32 => { + let width = if *arg_layout == Layout::F64 { + FloatWidth::F64 + } else { + FloatWidth::F32 + }; + + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); + let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2); + + ASM::cmp_freg_freg_reg64( + &mut self.buf, + dst_reg, + src1_reg, + src2_reg, + width, + CompareOperation::GreaterThanOrEqual, + ); + } x => todo!("NumGte: layout, {:?}", x), } } @@ -1279,25 +1507,14 @@ impl< dst: &Symbol, capacity: Symbol, capacity_layout: InLayout<'a>, + elem_layout: InLayout<'a>, ret_layout: &InLayout<'a>, ) { // List alignment argument (u32). - let u32_layout = Layout::U32; - let list_alignment = self.layout_interner.alignment_bytes(*ret_layout); - self.load_literal( - &Symbol::DEV_TMP, - &u32_layout, - &Literal::Int((list_alignment as i128).to_ne_bytes()), - ); + self.load_layout_alignment(*ret_layout, Symbol::DEV_TMP); // Load element_width argument (usize). - let u64_layout = Layout::U64; - let element_width = self.layout_interner.stack_size(*ret_layout); - self.load_literal( - &Symbol::DEV_TMP2, - &u64_layout, - &Literal::Int((element_width as i128).to_ne_bytes()), - ); + self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2); // Setup the return location. let base_offset = self @@ -1312,12 +1529,7 @@ impl< // element_width Symbol::DEV_TMP2, ]; - let lowlevel_arg_layouts = bumpalo::vec![ - in self.env.arena; - capacity_layout, - u32_layout, - u64_layout, - ]; + let lowlevel_arg_layouts = [capacity_layout, Layout::U32, Layout::U64]; self.build_fn_call( &Symbol::DEV_TMP3, @@ -1341,6 +1553,144 @@ impl< self.free_symbol(&Symbol::DEV_TMP3); } + fn build_list_reserve( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[InLayout<'a>], + ret_layout: &InLayout<'a>, + ) { + let list = args[0]; + let list_layout = arg_layouts[0]; + let spare = args[1]; + let spare_layout = arg_layouts[1]; + + // Load list alignment argument (u32). + self.load_layout_alignment(list_layout, Symbol::DEV_TMP); + + // Load element_width argument (usize). + self.load_layout_stack_size(*ret_layout, Symbol::DEV_TMP2); + + // Load UpdateMode.Immutable argument (0u8) + let u8_layout = Layout::U8; + let update_mode = 0u8; + self.load_literal( + &Symbol::DEV_TMP3, + &u8_layout, + &Literal::Int((update_mode as i128).to_ne_bytes()), + ); + + // Setup the return location. + let base_offset = self + .storage_manager + .claim_stack_area(dst, self.layout_interner.stack_size(*ret_layout)); + + let lowlevel_args = bumpalo::vec![ + in self.env.arena; + list, + // alignment + Symbol::DEV_TMP, + spare, + // element_width + Symbol::DEV_TMP2, + // update_mode + Symbol::DEV_TMP3, + + ]; + let lowlevel_arg_layouts = [ + list_layout, + Layout::U32, + spare_layout, + Layout::U64, + u8_layout, + ]; + + self.build_fn_call( + &Symbol::DEV_TMP4, + bitcode::LIST_RESERVE.to_string(), + &lowlevel_args, + &lowlevel_arg_layouts, + ret_layout, + ); + self.free_symbol(&Symbol::DEV_TMP); + self.free_symbol(&Symbol::DEV_TMP2); + self.free_symbol(&Symbol::DEV_TMP3); + + // Return list value from fn call + self.storage_manager.copy_symbol_to_stack_offset( + self.layout_interner, + &mut self.buf, + base_offset, + &Symbol::DEV_TMP4, + ret_layout, + ); + + self.free_symbol(&Symbol::DEV_TMP4); + } + + fn build_list_append_unsafe( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[InLayout<'a>], + ret_layout: &InLayout<'a>, + ) { + let list = args[0]; + let list_layout = arg_layouts[0]; + let elem = args[1]; + let elem_layout = arg_layouts[1]; + + // Have to pass the input element by pointer, so put it on the stack and load it's address. + self.storage_manager + .ensure_symbol_on_stack(&mut self.buf, &elem); + let (new_elem_offset, _) = self.storage_manager.stack_offset_and_size(&elem); + + // Load address of output element into register. + let reg = self + .storage_manager + .claim_general_reg(&mut self.buf, &Symbol::DEV_TMP); + ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset); + + // Load element_witdh argument (usize). + self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2); + + // Setup the return location. + let base_offset = self + .storage_manager + .claim_stack_area(dst, self.layout_interner.stack_size(*ret_layout)); + + let lowlevel_args = bumpalo::vec![ + in self.env.arena; + list, + // element + Symbol::DEV_TMP, + // element_width + Symbol::DEV_TMP2 + ]; + let lowlevel_arg_layouts = [list_layout, Layout::U64, Layout::U64]; + + self.build_fn_call( + &Symbol::DEV_TMP3, + bitcode::LIST_APPEND_UNSAFE.to_string(), + &lowlevel_args, + &lowlevel_arg_layouts, + ret_layout, + ); + self.free_symbol(&Symbol::DEV_TMP); + self.free_symbol(&Symbol::DEV_TMP2); + + // Return list value from fn call + self.storage_manager.copy_symbol_to_stack_offset( + self.layout_interner, + &mut self.buf, + base_offset, + &Symbol::DEV_TMP3, + ret_layout, + ); + + self.free_symbol(&Symbol::DEV_TMP3); + } + fn build_list_get_unsafe( &mut self, dst: &Symbol, @@ -1360,16 +1710,22 @@ impl< |storage_manager, buf, list_ptr| { ASM::mov_reg64_base32(buf, list_ptr, base_offset as i32); storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp| { + // calculate `element_width * index` ASM::mov_reg64_imm64(buf, tmp, ret_stack_size as i64); ASM::imul_reg64_reg64_reg64(buf, tmp, tmp, index_reg); + + // add the offset to the list pointer, store in `tmp` ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr); - match *ret_layout { - single_register_integers!() if ret_stack_size == 8 => { - let dst_reg = storage_manager.claim_general_reg(buf, dst); - ASM::mov_reg64_mem64_offset32(buf, dst_reg, tmp, 0); - } - x => internal_error!("Loading list element with layout: {:?}", x), - } + let element_ptr = tmp; + + Self::ptr_read( + buf, + storage_manager, + self.layout_interner, + element_ptr, + *ret_layout, + *dst, + ); }); }, ); @@ -1393,13 +1749,8 @@ impl< let elem = args[2]; let elem_layout = arg_layouts[2]; - let u32_layout = Layout::U32; - let list_alignment = self.layout_interner.alignment_bytes(list_layout); - self.load_literal( - &Symbol::DEV_TMP, - &u32_layout, - &Literal::Int((list_alignment as i128).to_ne_bytes()), - ); + // Load list alignment argument (u32). + self.load_layout_alignment(list_layout, Symbol::DEV_TMP); // Have to pass the input element by pointer, so put it on the stack and load it's address. self.storage_manager @@ -1413,12 +1764,7 @@ impl< ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset); // Load the elements size. - let elem_stack_size = self.layout_interner.stack_size(elem_layout); - self.load_literal( - &Symbol::DEV_TMP3, - &u64_layout, - &Literal::Int((elem_stack_size as i128).to_ne_bytes()), - ); + self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP3); // Setup the return location. let base_offset = self @@ -1465,14 +1811,13 @@ impl< Symbol::DEV_TMP3, Symbol::DEV_TMP4, ]; - let lowlevel_arg_layouts = bumpalo::vec![ - in self.env.arena; - list_layout, - u32_layout, - index_layout, - u64_layout, - u64_layout, - u64_layout, + let lowlevel_arg_layouts = [ + list_layout, + Layout::U32, + index_layout, + u64_layout, + u64_layout, + u64_layout, ]; self.build_fn_call( @@ -1499,6 +1844,133 @@ impl< self.free_symbol(&Symbol::DEV_TMP5); } + fn build_list_concat( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[InLayout<'a>], + elem_layout: InLayout<'a>, + ret_layout: &InLayout<'a>, + ) { + let list_a = args[0]; + let list_a_layout = arg_layouts[0]; + let list_b = args[1]; + let list_b_layout = arg_layouts[1]; + + // Load list alignment argument (u32). + self.load_layout_alignment(*ret_layout, Symbol::DEV_TMP); + + // Load element_width argument (usize). + self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2); + + // Setup the return location. + let base_offset = self + .storage_manager + .claim_stack_area(dst, self.layout_interner.stack_size(*ret_layout)); + + let lowlevel_args = bumpalo::vec![ + in self.env.arena; + list_a, + list_b, + // alignment + Symbol::DEV_TMP, + // element_width + Symbol::DEV_TMP2, + ]; + let lowlevel_arg_layouts = [list_a_layout, list_b_layout, Layout::U32, Layout::U64]; + + self.build_fn_call( + &Symbol::DEV_TMP3, + bitcode::LIST_CONCAT.to_string(), + &lowlevel_args, + &lowlevel_arg_layouts, + ret_layout, + ); + + self.free_symbol(&Symbol::DEV_TMP); + self.free_symbol(&Symbol::DEV_TMP2); + + // Return list value from fn call + self.storage_manager.copy_symbol_to_stack_offset( + self.layout_interner, + &mut self.buf, + base_offset, + &Symbol::DEV_TMP3, + ret_layout, + ); + + self.free_symbol(&Symbol::DEV_TMP3); + } + + fn build_list_prepend( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[InLayout<'a>], + ret_layout: &InLayout<'a>, + ) { + let list = args[0]; + let list_layout = arg_layouts[0]; + let elem = args[1]; + let elem_layout = arg_layouts[1]; + + // List alignment argument (u32). + self.load_layout_alignment(*ret_layout, Symbol::DEV_TMP); + + // Have to pass the input element by pointer, so put it on the stack and load it's address. + self.storage_manager + .ensure_symbol_on_stack(&mut self.buf, &elem); + let (new_elem_offset, _) = self.storage_manager.stack_offset_and_size(&elem); + + // Load address of input element into register. + let reg = self + .storage_manager + .claim_general_reg(&mut self.buf, &Symbol::DEV_TMP2); + ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset); + + // Load element_witdh argument (usize). + self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP3); + + // Setup the return location. + let base_offset = self + .storage_manager + .claim_stack_area(dst, self.layout_interner.stack_size(*ret_layout)); + + let lowlevel_args = bumpalo::vec![ + in self.env.arena; + list, + // alignment + Symbol::DEV_TMP, + // element + Symbol::DEV_TMP2, + // element_width + Symbol::DEV_TMP3, + ]; + let lowlevel_arg_layouts = [list_layout, Layout::U32, Layout::U64, Layout::U64]; + + self.build_fn_call( + &Symbol::DEV_TMP4, + bitcode::LIST_PREPEND.to_string(), + &lowlevel_args, + &lowlevel_arg_layouts, + ret_layout, + ); + self.free_symbol(&Symbol::DEV_TMP); + self.free_symbol(&Symbol::DEV_TMP2); + self.free_symbol(&Symbol::DEV_TMP3); + + // Return list value from fn call + self.storage_manager.copy_symbol_to_stack_offset( + self.layout_interner, + &mut self.buf, + base_offset, + &Symbol::DEV_TMP4, + ret_layout, + ); + + self.free_symbol(&Symbol::DEV_TMP4); + } + fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) { let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); self.storage_manager @@ -1521,102 +1993,68 @@ impl< fn create_array( &mut self, sym: &Symbol, - elem_layout: &InLayout<'a>, - elems: &'a [ListLiteralElement<'a>], + element_in_layout: &InLayout<'a>, + elements: &[ListLiteralElement<'a>], ) { - // Allocate - // This requires at least 8 for the refcount alignment. - let allocation_alignment = std::cmp::max( - 8, - self.layout_interner - .allocation_alignment_bytes(*elem_layout) as u64, - ); + let element_layout = self.layout_interner.get(*element_in_layout); + let element_width = self.layout_interner.stack_size(*element_in_layout) as u64; - let elem_size = self.layout_interner.stack_size(*elem_layout) as u64; - let allocation_size = elem_size * elems.len() as u64 + allocation_alignment /* add space for refcount */; - let u64_layout = Layout::U64; + // load the total size of the data we want to store (excludes refcount) + let data_bytes_symbol = Symbol::DEV_TMP; + let data_bytes = element_width * elements.len() as u64; self.load_literal( - &Symbol::DEV_TMP, - &u64_layout, - &Literal::Int((allocation_size as i128).to_ne_bytes()), - ); - let u32_layout = Layout::U32; - self.load_literal( - &Symbol::DEV_TMP2, - &u32_layout, - &Literal::Int((allocation_alignment as i128).to_ne_bytes()), + &data_bytes_symbol, + &Layout::U64, + &Literal::Int((data_bytes as i128).to_ne_bytes()), ); - self.build_fn_call( - &Symbol::DEV_TMP3, - "roc_alloc".to_string(), - &[Symbol::DEV_TMP, Symbol::DEV_TMP2], - &[u64_layout, u32_layout], - &u64_layout, - ); - self.free_symbol(&Symbol::DEV_TMP); - self.free_symbol(&Symbol::DEV_TMP2); + // Load allocation alignment (u32) + let element_alignment_symbol = Symbol::DEV_TMP2; + self.load_layout_alignment(Layout::U32, element_alignment_symbol); - // Fill pointer with elems + self.allocate_with_refcount( + Symbol::DEV_TMP3, + data_bytes_symbol, + element_alignment_symbol, + ); + + self.free_symbol(&data_bytes_symbol); + self.free_symbol(&element_alignment_symbol); + + // The pointer already points to the first element let ptr_reg = self .storage_manager .load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3); - // Point to first element of array. - ASM::add_reg64_reg64_imm32(&mut self.buf, ptr_reg, ptr_reg, allocation_alignment as i32); - - // fill refcount at -8. - self.storage_manager.with_tmp_general_reg( - &mut self.buf, - |_storage_manager, buf, tmp_reg| { - ASM::mov_reg64_imm64(buf, tmp_reg, REFCOUNT_ONE as i64); - ASM::mov_mem64_offset32_reg64(buf, ptr_reg, -8, tmp_reg); - }, - ); // Copy everything into output array. - let mut elem_offset = 0; - for elem in elems { + let mut element_offset = 0; + for elem in elements { // TODO: this could be a lot faster when loading large lists // if we move matching on the element layout to outside this loop. // It also greatly bloats the code here. // Refactor this and switch to one external match. // We also could make loadining indivitual literals much faster - let elem_sym = match elem { - ListLiteralElement::Symbol(sym) => sym, + let element_symbol = match elem { + ListLiteralElement::Symbol(sym) => *sym, ListLiteralElement::Literal(lit) => { - self.load_literal(&Symbol::DEV_TMP, elem_layout, lit); - &Symbol::DEV_TMP + self.load_literal(&Symbol::DEV_TMP, element_in_layout, lit); + Symbol::DEV_TMP } }; - // TODO: Expand to all types. - match self.layout_interner.get(*elem_layout) { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { - let sym_reg = self - .storage_manager - .load_to_general_reg(&mut self.buf, elem_sym); - ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, elem_offset, sym_reg); - } - _ if elem_size == 0 => {} - _ if elem_size > 8 => { - let (from_offset, size) = self.storage_manager.stack_offset_and_size(elem_sym); - debug_assert!(from_offset % 8 == 0); - debug_assert!(size % 8 == 0); - debug_assert_eq!(size as u64, elem_size); - self.storage_manager.with_tmp_general_reg( - &mut self.buf, - |_storage_manager, buf, tmp_reg| { - for i in (0..size as i32).step_by(8) { - ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i); - ASM::mov_mem64_offset32_reg64(buf, ptr_reg, elem_offset, tmp_reg); - } - }, - ); - } - x => todo!("copying data to list with layout, {:?}", x), - } - elem_offset += elem_size as i32; - if elem_sym == &Symbol::DEV_TMP { - self.free_symbol(elem_sym); + + Self::ptr_write( + &mut self.buf, + &mut self.storage_manager, + ptr_reg, + element_offset, + element_width, + element_layout, + element_symbol, + ); + + element_offset += element_width as i32; + if element_symbol == Symbol::DEV_TMP { + self.free_symbol(&element_symbol); } } @@ -1627,7 +2065,7 @@ impl< let base_offset = storage_manager.claim_stack_area(sym, 24); ASM::mov_base32_reg64(buf, base_offset, ptr_reg); - ASM::mov_reg64_imm64(buf, tmp_reg, elems.len() as i64); + ASM::mov_reg64_imm64(buf, tmp_reg, elements.len() as i64); ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg); ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg); }, @@ -1679,10 +2117,77 @@ impl< tag_layouts[tag_id as usize], ); } - x => todo!("loading from union type: {:?}", x), + _ => { + let union_in_layout = self.layout_interner.insert(Layout::Union(*union_layout)); + todo!( + "loading from union type: {:?}", + self.layout_interner.dbg(union_in_layout) + ) + } } } + fn expr_box(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>) { + let element_width_symbol = Symbol::DEV_TMP; + self.load_layout_stack_size(element_layout, element_width_symbol); + + // Load allocation alignment (u32) + let element_alignment_symbol = Symbol::DEV_TMP2; + self.load_layout_alignment(Layout::U32, element_alignment_symbol); + + self.allocate_with_refcount( + Symbol::DEV_TMP3, + element_width_symbol, + element_alignment_symbol, + ); + + self.free_symbol(&element_width_symbol); + self.free_symbol(&element_alignment_symbol); + + // Fill pointer with the value + let ptr_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3); + + let element_width = self.layout_interner.stack_size(element_layout) as u64; + let element_offset = 0; + + Self::ptr_write( + &mut self.buf, + &mut self.storage_manager, + ptr_reg, + element_offset, + element_width, + self.layout_interner.get(element_layout), + value, + ); + + if value == Symbol::DEV_TMP { + self.free_symbol(&value); + } + + // box is just a pointer on the stack + let base_offset = self.storage_manager.claim_stack_area(&sym, 8); + ASM::mov_base32_reg64(&mut self.buf, base_offset, ptr_reg); + + self.free_symbol(&Symbol::DEV_TMP3); + } + + fn expr_unbox(&mut self, dst: Symbol, ptr: Symbol, element_layout: InLayout<'a>) { + let ptr_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, &ptr); + + Self::ptr_read( + &mut self.buf, + &mut self.storage_manager, + self.layout_interner, + ptr_reg, + element_layout, + dst, + ); + } + fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) { self.storage_manager.load_union_tag_id( self.layout_interner, @@ -1729,6 +2234,33 @@ impl< let val = *x; ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64); } + ( + Literal::Int(bytes), + Layout::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128)), + ) => { + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, reg| { + let base_offset = storage_manager.claim_stack_area(sym, 16); + + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset, reg); + + num_bytes.copy_from_slice(&bytes[8..16]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset + 8, reg); + }, + ); + } + (Literal::Byte(x), Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8))) => { + let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym); + let val = *x; + ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64); + } (Literal::Bool(x), Layout::Builtin(Builtin::Bool)) => { let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym); let val = [*x as u8; 16]; @@ -1744,15 +2276,11 @@ impl< let val = *x as f32; ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val); } - (Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 24 => { - // Load small string. + (Literal::Decimal(bytes), Layout::Builtin(Builtin::Decimal)) => { self.storage_manager.with_tmp_general_reg( &mut self.buf, |storage_manager, buf, reg| { - let base_offset = storage_manager.claim_stack_area(sym, 24); - let mut bytes = [0; 24]; - bytes[..x.len()].copy_from_slice(x.as_bytes()); - bytes[23] = (x.len() as u8) | 0b1000_0000; + let base_offset = storage_manager.claim_stack_area(sym, 16); let mut num_bytes = [0; 8]; num_bytes.copy_from_slice(&bytes[..8]); @@ -1764,14 +2292,49 @@ impl< let num = i64::from_ne_bytes(num_bytes); ASM::mov_reg64_imm64(buf, reg, num); ASM::mov_base32_reg64(buf, base_offset + 8, reg); - - num_bytes.copy_from_slice(&bytes[16..]); - let num = i64::from_ne_bytes(num_bytes); - ASM::mov_reg64_imm64(buf, reg, num); - ASM::mov_base32_reg64(buf, base_offset + 16, reg); }, ); } + (Literal::Str(x), Layout::Builtin(Builtin::Str)) => { + if x.len() < 24 { + // Load small string. + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, reg| { + let base_offset = storage_manager.claim_stack_area(sym, 24); + let mut bytes = [0; 24]; + bytes[..x.len()].copy_from_slice(x.as_bytes()); + bytes[23] = (x.len() as u8) | 0b1000_0000; + + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset, reg); + + num_bytes.copy_from_slice(&bytes[8..16]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset + 8, reg); + + num_bytes.copy_from_slice(&bytes[16..]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(buf, reg, num); + ASM::mov_base32_reg64(buf, base_offset + 16, reg); + }, + ); + } else { + // load large string (pretend it's a `List U8`). We should move this data into + // the binary eventually because our RC algorithm won't free this value + let elements: Vec<_> = x + .as_bytes() + .iter() + .map(|b| ListLiteralElement::Literal(Literal::Byte(*b))) + .collect_in(self.storage_manager.env.arena); + + self.create_array(sym, &Layout::U8, elements.into_bump_slice()) + } + } x => todo!("loading literal, {:?}", x), } } @@ -1799,9 +2362,19 @@ impl< CC::FLOAT_RETURN_REGS[0], ); } - _ => { - internal_error!("All primitive valuse should fit in a single register"); - } + other => match self.layout_interner.get(other) { + Layout::Boxed(_) => { + // treat like a 64-bit integer + self.storage_manager.load_to_specified_general_reg( + &mut self.buf, + sym, + CC::GENERAL_RETURN_REGS[0], + ); + } + _ => { + internal_error!("All primitive values should fit in a single register"); + } + }, } } else { CC::return_complex_symbol( @@ -1880,6 +2453,139 @@ impl< } } } + + fn build_int_shift_left( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + int_width: IntWidth, + ) { + let buf = &mut self.buf; + + match int_width { + IntWidth::U128 | IntWidth::I128 => todo!(), + _ => { + let dst_reg = self.storage_manager.claim_general_reg(buf, dst); + let src1_reg = self.storage_manager.load_to_general_reg(buf, src1); + let src2_reg = self.storage_manager.load_to_general_reg(buf, src2); + + ASM::shl_reg64_reg64_reg64( + buf, + &mut self.storage_manager, + dst_reg, + src1_reg, + src2_reg, + ); + } + } + } + + fn build_int_shift_right( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + int_width: IntWidth, + ) { + let buf = &mut self.buf; + + match int_width { + IntWidth::U128 | IntWidth::I128 => todo!(), + _ => { + let dst_reg = self.storage_manager.claim_general_reg(buf, dst); + let src1_reg = self.storage_manager.load_to_general_reg(buf, src1); + let src2_reg = self.storage_manager.load_to_general_reg(buf, src2); + + // to get sign extension "for free", we move our bits to the left + // so the integers sign bit is stored in the register's sign bit. + // Then we arithmetic shift right, getting the correct sign extension behavior, + // then shift logical right to get the bits back into the position they should + // be for our particular integer width + let sign_extend_shift_amount = 64 - (int_width.stack_size() as i64 * 8); + + if sign_extend_shift_amount > 0 { + self.storage_manager.with_tmp_general_reg( + buf, + |storage_manager, buf, tmp_reg| { + ASM::mov_reg64_imm64(buf, tmp_reg, sign_extend_shift_amount); + ASM::shl_reg64_reg64_reg64( + buf, + storage_manager, + src1_reg, + src1_reg, + tmp_reg, + ); + }, + ) + } + + ASM::sar_reg64_reg64_reg64( + buf, + &mut self.storage_manager, + dst_reg, + src1_reg, + src2_reg, + ); + + if sign_extend_shift_amount > 0 { + // shift back if needed + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, tmp_reg| { + ASM::mov_reg64_imm64(buf, tmp_reg, sign_extend_shift_amount); + ASM::shr_reg64_reg64_reg64( + buf, + storage_manager, + dst_reg, + dst_reg, + tmp_reg, + ); + }, + ) + } + } + } + } + + fn build_int_shift_right_zero_fill( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + int_width: IntWidth, + ) { + let buf = &mut self.buf; + + match int_width { + IntWidth::U128 | IntWidth::I128 => todo!(), + _ => { + let dst_reg = self.storage_manager.claim_general_reg(buf, dst); + let src1_reg = self.storage_manager.load_to_general_reg(buf, src1); + let src2_reg = self.storage_manager.load_to_general_reg(buf, src2); + + ASM::shr_reg64_reg64_reg64( + buf, + &mut self.storage_manager, + dst_reg, + src1_reg, + src2_reg, + ); + } + } + } + + fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth) { + let buf = &mut self.buf; + + let dst_reg = self.storage_manager.claim_float_reg(buf, &dst); + let src_reg = self.storage_manager.load_to_float_reg(buf, &src); + + match float_width { + FloatWidth::F32 => ASM::sqrt_freg32_freg32(buf, dst_reg, src_reg), + FloatWidth::F64 => ASM::sqrt_freg64_freg64(buf, dst_reg, src_reg), + } + } } /// This impl block is for ir related instructions that need backend specific information. @@ -1893,6 +2599,149 @@ impl< CC: CallConv, > Backend64Bit<'a, 'r, GeneralReg, FloatReg, ASM, CC> { + fn allocate_with_refcount( + &mut self, + dst: Symbol, + data_bytes: Symbol, + element_alignment: Symbol, + ) { + self.build_fn_call( + &dst, + bitcode::UTILS_ALLOCATE_WITH_REFCOUNT.to_string(), + &[data_bytes, element_alignment], + &[Layout::U64, Layout::U32], + &Layout::U64, + ); + } + + fn unbox_str_or_list( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, + dst: Symbol, + ptr_reg: GeneralReg, + tmp_reg: GeneralReg, + ) { + let base_offset = storage_manager.claim_stack_area(&dst, 24); + + ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 0); + ASM::mov_base32_reg64(buf, base_offset, tmp_reg); + + ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 8); + ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg); + + ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 16); + ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg); + } + + fn ptr_read( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, + layout_interner: &STLayoutInterner<'a>, + ptr_reg: GeneralReg, + element_in_layout: InLayout<'a>, + dst: Symbol, + ) { + match layout_interner.get(element_in_layout) { + Layout::Builtin(builtin) => match builtin { + Builtin::Int(int_width) => match int_width { + IntWidth::I128 | IntWidth::U128 => { + // can we treat this as 2 u64's? + todo!() + } + IntWidth::I64 | IntWidth::U64 => { + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0); + } + IntWidth::I32 | IntWidth::U32 => { + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg32_mem32_offset32(buf, dst_reg, ptr_reg, 0); + } + IntWidth::I16 | IntWidth::U16 => { + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg16_mem16_offset32(buf, dst_reg, ptr_reg, 0); + } + IntWidth::I8 | IntWidth::U8 => { + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0); + } + }, + Builtin::Float(_) => { + let dst_reg = storage_manager.claim_float_reg(buf, &dst); + ASM::mov_freg64_freg64(buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); + } + Builtin::Bool => { + // the same as an 8-bit integer + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0); + } + Builtin::Decimal => { + // same as 128-bit integer + } + Builtin::Str | Builtin::List(_) => { + storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| { + Self::unbox_str_or_list(buf, storage_manager, dst, ptr_reg, tmp_reg); + }); + } + }, + + Layout::Boxed(_) => { + // the same as 64-bit integer (for 64-bit targets) + let dst_reg = storage_manager.claim_general_reg(buf, &dst); + ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0); + } + + _ => todo!("unboxing of {:?}", layout_interner.dbg(element_in_layout)), + } + } + + fn ptr_write( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>, + ptr_reg: GeneralReg, + element_offset: i32, + element_width: u64, + element_layout: Layout<'a>, + value: Symbol, + ) { + match element_layout { + Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + let sym_reg = storage_manager.load_to_general_reg(buf, &value); + ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset, sym_reg); + } + Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::U32)) => { + let sym_reg = storage_manager.load_to_general_reg(buf, &value); + ASM::mov_mem32_offset32_reg32(buf, ptr_reg, element_offset, sym_reg); + } + Layout::Builtin(Builtin::Int(IntWidth::I16 | IntWidth::U16)) => { + let sym_reg = storage_manager.load_to_general_reg(buf, &value); + ASM::mov_mem16_offset32_reg16(buf, ptr_reg, element_offset, sym_reg); + } + Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8) | Builtin::Bool) => { + let sym_reg = storage_manager.load_to_general_reg(buf, &value); + ASM::mov_mem8_offset32_reg8(buf, ptr_reg, element_offset, sym_reg); + } + Layout::Builtin(Builtin::Float(FloatWidth::F64 | FloatWidth::F32)) => { + let sym_reg = storage_manager.load_to_float_reg(buf, &value); + ASM::movesd_mem64_offset32_freg64(buf, ptr_reg, element_offset, sym_reg); + } + _ if element_width == 0 => {} + _ if element_width > 8 => { + let (from_offset, size) = storage_manager.stack_offset_and_size(&value); + debug_assert!(from_offset % 8 == 0); + debug_assert!(size % 8 == 0); + debug_assert_eq!(size as u64, element_width); + storage_manager.with_tmp_general_reg(buf, |_storage_manager, buf, tmp_reg| { + // a crude memcpy + for i in (0..size as i32).step_by(8) { + ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i); + ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset + i, tmp_reg); + } + }); + } + x => todo!("copying data to list with layout, {:?}", x), + } + } + /// Updates a jump instruction to a new offset and returns the number of bytes written. fn update_jmp_imm32_offset( &mut self, @@ -1908,6 +2757,24 @@ impl< self.buf[jmp_location as usize + i] = *byte; } } + + /// Loads the alignment bytes of `layout` into the given `symbol` + fn load_layout_alignment(&mut self, layout: InLayout<'a>, symbol: Symbol) { + let u32_layout = Layout::U32; + let alignment = self.layout_interner.alignment_bytes(layout); + let alignment_literal = Literal::Int((alignment as i128).to_ne_bytes()); + + self.load_literal(&symbol, &u32_layout, &alignment_literal); + } + + /// Loads the stack size of `layout` into the given `symbol` + fn load_layout_stack_size(&mut self, layout: InLayout<'a>, symbol: Symbol) { + let u64_layout = Layout::U64; + let width = self.layout_interner.stack_size(layout); + let width_literal = Literal::Int((width as i128).to_ne_bytes()); + + self.load_literal(&symbol, &u64_layout, &width_literal); + } } #[macro_export] diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 4a3090b0dd..7e2eb7d4b4 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -9,7 +9,6 @@ use roc_collections::all::{MutMap, MutSet}; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::{ - borrow::Ownership, ir::{JoinPointId, Param}, layout::{ Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout, @@ -315,7 +314,7 @@ impl< reg: Some(Float(_)), .. }) => { - internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) + internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}") } Stack(Primitive { reg: None, @@ -350,8 +349,10 @@ impl< self.free_reference(sym); reg } - Stack(Complex { .. }) => { - internal_error!("Cannot load large values into general registers: {}", sym) + Stack(Complex { size, .. }) => { + internal_error!( + "Cannot load large values (size {size}) into general registers: {sym:?}", + ) } NoData => { internal_error!("Cannot load no data into general registers: {}", sym) @@ -448,7 +449,7 @@ impl< reg: Some(Float(_)), .. }) => { - internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym) + internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}",) } Stack(Primitive { reg: None, @@ -458,19 +459,25 @@ impl< ASM::mov_reg64_base32(buf, reg, *base_offset); } Stack(ReferencedPrimitive { - base_offset, size, .. - }) if base_offset % 8 == 0 && *size == 8 => { - // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. - ASM::mov_reg64_base32(buf, reg, *base_offset); + base_offset, + size, + sign_extend, + }) => { + debug_assert!(*size <= 8); + + if *sign_extend { + ASM::movsx_reg64_base32(buf, reg, *base_offset, *size as u8) + } else { + ASM::movzx_reg64_base32(buf, reg, *base_offset, *size as u8) + } } - Stack(ReferencedPrimitive { .. }) => { - todo!("loading referenced primitives") - } - Stack(Complex { .. }) => { - internal_error!("Cannot load large values into general registers: {}", sym) + Stack(Complex { size, .. }) => { + internal_error!( + "Cannot load large values (size {size}) into general registers: {sym:?}", + ) } NoData => { - internal_error!("Cannot load no data into general registers: {}", sym) + internal_error!("Cannot load no data into general registers: {:?}", sym) } } } @@ -553,7 +560,7 @@ impl< self.allocation_map.insert(*sym, owned_data); self.symbol_storage_map.insert( *sym, - Stack(if is_primitive(layout) { + Stack(if is_primitive(layout_interner, layout) { ReferencedPrimitive { base_offset: data_offset, size, @@ -739,15 +746,73 @@ impl< layout: &InLayout<'a>, ) { match layout_interner.get(*layout) { - Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + Layout::Builtin(builtin) => match builtin { + Builtin::Int(int_width) => match int_width { + IntWidth::I128 | IntWidth::U128 => { + let (from_offset, size) = self.stack_offset_and_size(sym); + debug_assert_eq!(from_offset % 8, 0); + debug_assert_eq!(size % 8, 0); + debug_assert_eq!(size, layout_interner.stack_size(*layout)); + self.copy_to_stack_offset(buf, size, from_offset, to_offset) + } + IntWidth::I64 | IntWidth::U64 => { + debug_assert_eq!(to_offset % 8, 0); + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg64(buf, to_offset, reg); + } + IntWidth::I32 | IntWidth::U32 => { + debug_assert_eq!(to_offset % 4, 0); + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg32(buf, to_offset, reg); + } + IntWidth::I16 | IntWidth::U16 => { + debug_assert_eq!(to_offset % 2, 0); + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg16(buf, to_offset, reg); + } + IntWidth::I8 | IntWidth::U8 => { + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg8(buf, to_offset, reg); + } + }, + + Builtin::Float(float_width) => match float_width { + FloatWidth::F64 => { + debug_assert_eq!(to_offset % 8, 0); + let reg = self.load_to_float_reg(buf, sym); + ASM::mov_base32_freg64(buf, to_offset, reg); + } + FloatWidth::F32 => todo!(), + }, + Builtin::Bool => { + // same as 8-bit integer + let reg = self.load_to_general_reg(buf, sym); + ASM::mov_base32_reg8(buf, to_offset, reg); + } + Builtin::Decimal => todo!(), + Builtin::Str | Builtin::List(_) => { + let (from_offset, size) = self.stack_offset_and_size(sym); + debug_assert_eq!(from_offset % 8, 0); + debug_assert_eq!(size % 8, 0); + debug_assert_eq!(size, layout_interner.stack_size(*layout)); + self.copy_to_stack_offset(buf, size, from_offset, to_offset) + } + }, + Layout::Boxed(_) => { + // like a 64-bit integer debug_assert_eq!(to_offset % 8, 0); let reg = self.load_to_general_reg(buf, sym); ASM::mov_base32_reg64(buf, to_offset, reg); } - Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { - debug_assert_eq!(to_offset % 8, 0); - let reg = self.load_to_float_reg(buf, sym); - ASM::mov_base32_freg64(buf, to_offset, reg); + Layout::LambdaSet(lambda_set) => { + // like its runtime representation + self.copy_symbol_to_stack_offset( + layout_interner, + buf, + to_offset, + sym, + &lambda_set.runtime_representation(), + ) } _ if layout_interner.stack_size(*layout) == 0 => {} // TODO: Verify this is always true. @@ -756,20 +821,64 @@ impl< // Later, it will be reloaded and stored in refcounted as needed. _ if layout_interner.stack_size(*layout) > 8 => { let (from_offset, size) = self.stack_offset_and_size(sym); - debug_assert!(from_offset % 8 == 0); - debug_assert!(size % 8 == 0); + debug_assert_eq!(from_offset % 8, 0); + debug_assert_eq!(size % 8, 0); debug_assert_eq!(size, layout_interner.stack_size(*layout)); - self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { - for i in (0..size as i32).step_by(8) { - ASM::mov_reg64_base32(buf, reg, from_offset + i); - ASM::mov_base32_reg64(buf, to_offset + i, reg); - } - }); + self.copy_to_stack_offset(buf, size, from_offset, to_offset) } x => todo!("copying data to the stack with layout, {:?}", x), } } + pub fn copy_to_stack_offset( + &mut self, + buf: &mut Vec<'a, u8>, + size: u32, + from_offset: i32, + to_offset: i32, + ) { + let mut copied = 0; + let size = size as i32; + + self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { + if size - copied >= 8 { + for _ in (0..(size - copied)).step_by(8) { + ASM::mov_reg64_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg64(buf, to_offset + copied, reg); + + copied += 8; + } + } + + if size - copied >= 4 { + for _ in (0..(size - copied)).step_by(4) { + ASM::mov_reg32_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg32(buf, to_offset + copied, reg); + + copied += 4; + } + } + + if size - copied >= 2 { + for _ in (0..(size - copied)).step_by(2) { + ASM::mov_reg16_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg16(buf, to_offset + copied, reg); + + copied += 2; + } + } + + if size - copied >= 1 { + for _ in (0..(size - copied)).step_by(1) { + ASM::mov_reg8_base32(buf, reg, from_offset + copied); + ASM::mov_base32_reg8(buf, to_offset + copied, reg); + + copied += 1; + } + } + }); + } + #[allow(dead_code)] /// Ensures that a register is free. If it is not free, data will be moved to make it free. pub fn ensure_reg_free( @@ -928,7 +1037,7 @@ impl< ) => (*base_offset, *size), storage => { internal_error!( - "Data not on the stack for sym ({}) with storage ({:?})", + "Data not on the stack for sym {:?} with storage {:?}", sym, storage ) @@ -1008,15 +1117,10 @@ impl< param_storage.reserve(params.len()); for Param { symbol, - ownership, + ownership: _, layout, } in params { - if *ownership == Ownership::Borrowed { - // These probably need to be passed by pointer/reference? - // Otherwise, we probably need to copy back to the param at the end of the joinpoint. - todo!("joinpoints with borrowed parameters"); - } // Claim a location for every join point parameter to be loaded at. // Put everything on the stack for simplicity. match *layout { @@ -1331,6 +1435,15 @@ impl< } } -fn is_primitive(layout: InLayout<'_>) -> bool { - matches!(layout, single_register_layouts!()) +fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool { + match layout { + single_register_layouts!() => true, + _ => match layout_interner.get(layout) { + Layout::Boxed(_) => true, + Layout::LambdaSet(lambda_set) => { + is_primitive(layout_interner, lambda_set.runtime_representation()) + } + _ => false, + }, + } } diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index c52d567dec..0f3902e951 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -4,10 +4,13 @@ use crate::{ single_register_layouts, Relocation, }; use bumpalo::collections::Vec; +use roc_builtins::bitcode::FloatWidth; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{InLayout, Layout, LayoutInterner, STLayoutInterner}; +use super::CompareOperation; + // Not sure exactly how I want to represent registers. // If we want max speed, we would likely make them structs that impl the same trait to avoid ifs. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] @@ -261,46 +264,22 @@ impl CallConv for X86_64Syste args: &'a [(InLayout<'a>, Symbol)], ret_layout: &InLayout<'a>, ) { - let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer. - let mut general_i = 0; - let mut float_i = 0; - if X86_64SystemV::returns_via_arg_pointer(layout_interner, ret_layout) { - storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]); - general_i += 1; + let returns_via_pointer = + X86_64SystemV::returns_via_arg_pointer(layout_interner, ret_layout); + + let mut state = X64_64SystemVLoadArgs { + general_i: usize::from(returns_via_pointer), + float_i: 0, + // 16 is the size of the pushed return address and base pointer. + argument_offset: X86_64SystemV::SHADOW_SPACE_SIZE as i32 + 16, + }; + + if returns_via_pointer { + storage_manager.ret_pointer_arg(X86_64SystemV::GENERAL_PARAM_REGS[0]); } - for (layout, sym) in args.iter() { - let stack_size = layout_interner.stack_size(*layout); - match *layout { - single_register_integers!() => { - if general_i < Self::GENERAL_PARAM_REGS.len() { - storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[general_i]); - general_i += 1; - } else { - storage_manager.primitive_stack_arg(sym, arg_offset); - arg_offset += 8; - } - } - single_register_floats!() => { - if float_i < Self::FLOAT_PARAM_REGS.len() { - storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[float_i]); - float_i += 1; - } else { - storage_manager.primitive_stack_arg(sym, arg_offset); - arg_offset += 8; - } - } - _ if stack_size == 0 => { - storage_manager.no_data_arg(sym); - } - _ if stack_size > 16 => { - // TODO: Double check this. - storage_manager.complex_stack_arg(sym, arg_offset, stack_size); - arg_offset += stack_size as i32; - } - x => { - todo!("Loading args with layout {:?}", x); - } - } + + for (in_layout, sym) in args.iter() { + state.load_arg(storage_manager, layout_interner, *sym, *in_layout); } } @@ -321,9 +300,8 @@ impl CallConv for X86_64Syste arg_layouts: &[InLayout<'a>], ret_layout: &InLayout<'a>, ) { - let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut general_i = 0; - let mut float_i = 0; + if Self::returns_via_arg_pointer(layout_interner, ret_layout) { // Save space on the stack for the result we will be return. let base_offset = @@ -338,81 +316,18 @@ impl CallConv for X86_64Syste base_offset, ); } - for (sym, layout) in args.iter().zip(arg_layouts.iter()) { - match *layout { - single_register_integers!() => { - if general_i < Self::GENERAL_PARAM_REGS.len() { - storage_manager.load_to_specified_general_reg( - buf, - sym, - Self::GENERAL_PARAM_REGS[general_i], - ); - general_i += 1; - } else { - // Copy to stack using return reg as buffer. - storage_manager.load_to_specified_general_reg( - buf, - sym, - Self::GENERAL_RETURN_REGS[0], - ); - X86_64Assembler::mov_stack32_reg64( - buf, - tmp_stack_offset, - Self::GENERAL_RETURN_REGS[0], - ); - tmp_stack_offset += 8; - } - } - single_register_floats!() => { - if float_i < Self::FLOAT_PARAM_REGS.len() { - storage_manager.load_to_specified_float_reg( - buf, - sym, - Self::FLOAT_PARAM_REGS[float_i], - ); - float_i += 1; - } else { - // Copy to stack using return reg as buffer. - storage_manager.load_to_specified_float_reg( - buf, - sym, - Self::FLOAT_RETURN_REGS[0], - ); - X86_64Assembler::mov_stack32_freg64( - buf, - tmp_stack_offset, - Self::FLOAT_RETURN_REGS[0], - ); - tmp_stack_offset += 8; - } - } - x if layout_interner.stack_size(x) == 0 => {} - x if layout_interner.stack_size(x) > 16 => { - // TODO: Double check this. - // Just copy onto the stack. - // Use return reg as buffer because it will be empty right now. - let (base_offset, size) = storage_manager.stack_offset_and_size(sym); - debug_assert_eq!(base_offset % 8, 0); - for i in (0..size as i32).step_by(8) { - X86_64Assembler::mov_reg64_base32( - buf, - Self::GENERAL_RETURN_REGS[0], - base_offset + i, - ); - X86_64Assembler::mov_stack32_reg64( - buf, - tmp_stack_offset + i, - Self::GENERAL_RETURN_REGS[0], - ); - } - tmp_stack_offset += size as i32; - } - x => { - todo!("calling with arg type, {:?}", x); - } - } + + let mut state = X64_64SystemVStoreArgs { + general_i, + float_i: 0, + tmp_stack_offset: Self::SHADOW_SPACE_SIZE as i32, + }; + + for (sym, in_layout) in args.iter().zip(arg_layouts.iter()) { + state.store_arg(buf, storage_manager, layout_interner, *sym, *in_layout); } - storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32); + + storage_manager.update_fn_call_stack_size(state.tmp_stack_offset as u32); } fn return_complex_symbol<'a, 'r>( @@ -519,6 +434,225 @@ impl CallConv for X86_64Syste } } +struct X64_64SystemVStoreArgs { + general_i: usize, + float_i: usize, + tmp_stack_offset: i32, +} + +impl X64_64SystemVStoreArgs { + const GENERAL_PARAM_REGS: &'static [X86_64GeneralReg] = X86_64SystemV::GENERAL_PARAM_REGS; + const GENERAL_RETURN_REGS: &'static [X86_64GeneralReg] = X86_64SystemV::GENERAL_RETURN_REGS; + + const FLOAT_PARAM_REGS: &'static [X86_64FloatReg] = X86_64SystemV::FLOAT_PARAM_REGS; + const FLOAT_RETURN_REGS: &'static [X86_64FloatReg] = X86_64SystemV::FLOAT_RETURN_REGS; + + fn store_arg<'a, 'r>( + &mut self, + buf: &mut Vec<'a, u8>, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + layout_interner: &mut STLayoutInterner<'a>, + sym: Symbol, + in_layout: InLayout<'a>, + ) { + match in_layout { + single_register_integers!() => self.store_arg_general(buf, storage_manager, sym), + single_register_floats!() => self.store_arg_float(buf, storage_manager, sym), + x if layout_interner.stack_size(x) == 0 => {} + x if layout_interner.stack_size(x) > 16 => { + // TODO: Double check this. + // Just copy onto the stack. + // Use return reg as buffer because it will be empty right now. + let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); + debug_assert_eq!(base_offset % 8, 0); + for i in (0..size as i32).step_by(8) { + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_RETURN_REGS[0], + base_offset + i, + ); + X86_64Assembler::mov_stack32_reg64( + buf, + self.tmp_stack_offset + i, + Self::GENERAL_RETURN_REGS[0], + ); + } + self.tmp_stack_offset += size as i32; + } + other => { + // look at the layout in more detail + match layout_interner.get(other) { + Layout::Boxed(_) => { + // treat boxed like a 64-bit integer + self.store_arg_general(buf, storage_manager, sym) + } + Layout::LambdaSet(lambda_set) => self.store_arg( + buf, + storage_manager, + layout_interner, + sym, + lambda_set.runtime_representation(), + ), + Layout::Struct { .. } => { + // for now, just also store this on the stack + let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); + debug_assert_eq!(base_offset % 8, 0); + for i in (0..size as i32).step_by(8) { + X86_64Assembler::mov_reg64_base32( + buf, + Self::GENERAL_RETURN_REGS[0], + base_offset + i, + ); + X86_64Assembler::mov_stack32_reg64( + buf, + self.tmp_stack_offset + i, + Self::GENERAL_RETURN_REGS[0], + ); + } + self.tmp_stack_offset += size as i32; + } + _ => { + todo!("calling with arg type, {:?}", layout_interner.dbg(other)); + } + } + } + } + } + + fn store_arg_general<'a, 'r>( + &mut self, + buf: &mut Vec<'a, u8>, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + sym: Symbol, + ) { + if self.general_i < Self::GENERAL_PARAM_REGS.len() { + storage_manager.load_to_specified_general_reg( + buf, + &sym, + Self::GENERAL_PARAM_REGS[self.general_i], + ); + self.general_i += 1; + } else { + // Copy to stack using return reg as buffer. + storage_manager.load_to_specified_general_reg(buf, &sym, Self::GENERAL_RETURN_REGS[0]); + X86_64Assembler::mov_stack32_reg64( + buf, + self.tmp_stack_offset, + Self::GENERAL_RETURN_REGS[0], + ); + self.tmp_stack_offset += 8; + } + } + + fn store_arg_float<'a, 'r>( + &mut self, + buf: &mut Vec<'a, u8>, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + sym: Symbol, + ) { + if self.float_i < Self::FLOAT_PARAM_REGS.len() { + storage_manager.load_to_specified_float_reg( + buf, + &sym, + Self::FLOAT_PARAM_REGS[self.float_i], + ); + self.float_i += 1; + } else { + // Copy to stack using return reg as buffer. + storage_manager.load_to_specified_float_reg(buf, &sym, Self::FLOAT_RETURN_REGS[0]); + X86_64Assembler::mov_stack32_freg64( + buf, + self.tmp_stack_offset, + Self::FLOAT_RETURN_REGS[0], + ); + self.tmp_stack_offset += 8; + } + } +} + +struct X64_64SystemVLoadArgs { + general_i: usize, + float_i: usize, + argument_offset: i32, +} + +type X86_64StorageManager<'a, 'r, CallConv> = + StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, X86_64Assembler, CallConv>; + +impl X64_64SystemVLoadArgs { + fn load_arg<'a, 'r>( + &mut self, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + layout_interner: &mut STLayoutInterner<'a>, + sym: Symbol, + in_layout: InLayout<'a>, + ) { + let stack_size = layout_interner.stack_size(in_layout); + match in_layout { + single_register_integers!() => self.load_arg_general(storage_manager, sym), + single_register_floats!() => self.load_arg_float(storage_manager, sym), + _ if stack_size == 0 => { + storage_manager.no_data_arg(&sym); + } + _ if stack_size > 16 => { + // TODO: Double check this. + storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); + self.argument_offset += stack_size as i32; + } + other => match layout_interner.get(other) { + Layout::Boxed(_) => { + // boxed layouts are pointers, which we treat as 64-bit integers + self.load_arg_general(storage_manager, sym) + } + Layout::LambdaSet(lambda_set) => self.load_arg( + storage_manager, + layout_interner, + sym, + lambda_set.runtime_representation(), + ), + Layout::Struct { .. } => { + // for now, just also store this on the stack + storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); + self.argument_offset += stack_size as i32; + } + _ => { + todo!("Loading args with layout {:?}", layout_interner.dbg(other)); + } + }, + } + } + + fn load_arg_general<'a, 'r>( + &mut self, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + sym: Symbol, + ) { + if self.general_i < X86_64SystemV::GENERAL_PARAM_REGS.len() { + let reg = X86_64SystemV::GENERAL_PARAM_REGS[self.general_i]; + storage_manager.general_reg_arg(&sym, reg); + self.general_i += 1; + } else { + storage_manager.primitive_stack_arg(&sym, self.argument_offset); + self.argument_offset += 8; + } + } + + fn load_arg_float<'a, 'r>( + &mut self, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>, + sym: Symbol, + ) { + if self.general_i < X86_64SystemV::GENERAL_PARAM_REGS.len() { + let reg = X86_64SystemV::FLOAT_PARAM_REGS[self.general_i]; + storage_manager.float_reg_arg(&sym, reg); + self.float_i += 1; + } else { + storage_manager.primitive_stack_arg(&sym, self.argument_offset); + self.argument_offset += 8; + } + } +} + impl X86_64SystemV { fn returns_via_arg_pointer<'a>( interner: &STLayoutInterner<'a>, @@ -662,14 +796,7 @@ impl CallConv for X86_64Windo #[inline(always)] fn load_args<'a, 'r>( _buf: &mut Vec<'a, u8>, - storage_manager: &mut StorageManager< - 'a, - 'r, - X86_64GeneralReg, - X86_64FloatReg, - X86_64Assembler, - X86_64WindowsFastcall, - >, + storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64WindowsFastcall>, layout_interner: &mut STLayoutInterner<'a>, args: &'a [(InLayout<'a>, Symbol)], ret_layout: &InLayout<'a>, @@ -1228,18 +1355,55 @@ impl Assembler for X86_64Assembler { fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) { movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset) } + #[inline(always)] fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) { mov_reg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset) } + #[inline(always)] + fn mov_reg32_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) { + mov_reg32_base32_offset32(buf, dst, X86_64GeneralReg::RBP, offset) + } + #[inline(always)] + fn mov_reg16_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) { + mov_reg16_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset) + } + #[inline(always)] + fn mov_reg8_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) { + mov_reg8_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset) + } + #[inline(always)] fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64FloatReg) { movsd_base64_offset32_freg64(buf, X86_64GeneralReg::RBP, offset, src) } + + #[inline(always)] + fn movesd_mem64_offset32_freg64( + buf: &mut Vec<'_, u8>, + ptr: X86_64GeneralReg, + offset: i32, + src: X86_64FloatReg, + ) { + movsd_base64_offset32_freg64(buf, ptr, offset, src) + } + #[inline(always)] fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src) } + #[inline(always)] + fn mov_base32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { + mov_base32_offset32_reg32(buf, X86_64GeneralReg::RBP, offset, src) + } + #[inline(always)] + fn mov_base32_reg16(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { + mov_base16_offset32_reg16(buf, X86_64GeneralReg::RBP, offset, src) + } + #[inline(always)] + fn mov_base32_reg8(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { + mov_base8_offset32_reg8(buf, X86_64GeneralReg::RBP, offset, src) + } #[inline(always)] fn mov_reg64_mem64_offset32( @@ -1250,6 +1414,32 @@ impl Assembler for X86_64Assembler { ) { mov_reg64_base64_offset32(buf, dst, src, offset) } + #[inline(always)] + fn mov_reg32_mem32_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + offset: i32, + ) { + mov_reg32_base32_offset32(buf, dst, src, offset) + } + fn mov_reg16_mem16_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + offset: i32, + ) { + mov_reg16_base16_offset32(buf, dst, src, offset) + } + fn mov_reg8_mem8_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + offset: i32, + ) { + mov_reg8_base8_offset32(buf, dst, src, offset) + } + #[inline(always)] fn mov_mem64_offset32_reg64( buf: &mut Vec<'_, u8>, @@ -1260,12 +1450,44 @@ impl Assembler for X86_64Assembler { mov_base64_offset32_reg64(buf, dst, offset, src) } + #[inline(always)] + fn mov_mem32_offset32_reg32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, + ) { + mov_base32_offset32_reg32(buf, dst, offset, src) + } + + #[inline(always)] + fn mov_mem16_offset32_reg16( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, + ) { + mov_base16_offset32_reg16(buf, dst, offset, src) + } + + #[inline(always)] + fn mov_mem8_offset32_reg8( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, + ) { + mov_base8_offset32_reg8(buf, dst, offset, src) + } + #[inline(always)] fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) { debug_assert!(size <= 8); match size { 8 => Self::mov_reg64_base32(buf, dst, offset), - 4 | 2 | 1 => todo!("sign extending {size} byte values"), + 4 => movsx_reg64_base32_offset32(buf, dst, X86_64GeneralReg::RBP, offset), + 2 => movsx_reg64_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset), + 1 => movsx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset), _ => internal_error!("Invalid size for sign extension: {size}"), } } @@ -1274,7 +1496,12 @@ impl Assembler for X86_64Assembler { debug_assert!(size <= 8); match size { 8 => Self::mov_reg64_base32(buf, dst, offset), - 4 | 2 => todo!("zero extending {size} byte values"), + 4 => { + // The Intel documentation (3.4.1.1 General-Purpose Registers in 64-Bit Mode in manual Basic Architecture)) + // 32-bit operands generate a 32-bit result, zero-extended to a 64-bit result in the destination general-purpose register. + Self::mov_reg64_base32(buf, dst, offset) + } + 2 => movzx_reg64_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset), 1 => movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset), _ => internal_error!("Invalid size for zero extension: {size}"), } @@ -1368,6 +1595,33 @@ impl Assembler for X86_64Assembler { setb_reg64(buf, dst); } + #[inline(always)] + fn cmp_freg_freg_reg64( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + src1: X86_64FloatReg, + src2: X86_64FloatReg, + width: FloatWidth, + operation: CompareOperation, + ) { + use CompareOperation::*; + + let (arg1, arg2) = match operation { + LessThan | LessThanOrEqual => (src1, src2), + GreaterThan | GreaterThanOrEqual => (src2, src1), + }; + + match width { + FloatWidth::F32 => cmp_freg32_freg32(buf, arg2, arg1), + FloatWidth::F64 => cmp_freg64_freg64(buf, arg2, arg1), + } + + match operation { + LessThan | GreaterThan => seta_reg64(buf, dst), + LessThanOrEqual | GreaterThanOrEqual => setae_reg64(buf, dst), + }; + } + #[inline(always)] fn igt_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, @@ -1452,6 +1706,91 @@ impl Assembler for X86_64Assembler { fn xor_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) { binop_move_src_to_dst_reg64(buf, xor_reg64_reg64, dst, src1, src2) } + + fn shl_reg64_reg64_reg64<'a, 'r, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, ASM, CC>, + dst: X86_64GeneralReg, + src1: X86_64GeneralReg, + src2: X86_64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + shift_reg64_reg64_reg64(buf, storage_manager, shl_reg64_reg64, dst, src1, src2) + } + + fn shr_reg64_reg64_reg64<'a, 'r, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, ASM, CC>, + dst: X86_64GeneralReg, + src1: X86_64GeneralReg, + src2: X86_64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + shift_reg64_reg64_reg64(buf, storage_manager, shr_reg64_reg64, dst, src1, src2) + } + + fn sar_reg64_reg64_reg64<'a, 'r, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, ASM, CC>, + dst: X86_64GeneralReg, + src1: X86_64GeneralReg, + src2: X86_64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + shift_reg64_reg64_reg64(buf, storage_manager, sar_reg64_reg64, dst, src1, src2) + } + + fn sqrt_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + sqrtsd_freg64_freg64(buf, dst, src) + } + + fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + sqrtss_freg32_freg32(buf, dst, src) + } +} + +fn shift_reg64_reg64_reg64<'a, 'r, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, ASM, CC>, + shift_function: fn(buf: &mut Vec<'_, u8>, X86_64GeneralReg), + dst: X86_64GeneralReg, + src1: X86_64GeneralReg, + src2: X86_64GeneralReg, +) where + ASM: Assembler, + CC: CallConv, +{ + macro_rules! helper { + ($buf:expr, $dst:expr, $src1:expr, $src2:expr) => {{ + mov_reg64_reg64($buf, $dst, $src1); + mov_reg64_reg64($buf, X86_64GeneralReg::RCX, $src2); + + shift_function($buf, $dst) + }}; + } + + // if RCX is one of our input registers, we need to move some stuff around + if let X86_64GeneralReg::RCX = dst { + storage_manager.with_tmp_general_reg(buf, |_, buf, tmp| { + helper!(buf, tmp, src1, src2); + + mov_reg64_reg64(buf, dst, tmp); + }) + } else if let X86_64GeneralReg::RCX = src2 { + storage_manager.with_tmp_general_reg(buf, |_, buf, tmp| { + mov_reg64_reg64(buf, tmp, src2); + + helper!(buf, dst, src1, tmp); + }) + } else { + helper!(buf, dst, src1, src2) + } } impl X86_64Assembler { @@ -1465,12 +1804,16 @@ impl X86_64Assembler { push_reg64(buf, reg); } } + +const GRP_4: u8 = 0x66; + const REX: u8 = 0x40; // see https://wiki.osdev.org/X86-64_Instruction_Encoding#Encoding /// If set, 64-bit operand size is used const REX_PREFIX_W: u8 = 0b1000; /// Extension to the MODRM.reg +/// Permits access to additional registers const REX_PREFIX_R: u8 = 0b0100; #[allow(unused)] /// Extension to the SIB.index field @@ -1478,7 +1821,7 @@ const REX_PREFIX_X: u8 = 0b0010; /// Extension to the MODRM.rm const REX_PREFIX_B: u8 = 0b0001; -/// Wide REX +/// Wide REX (64-bit) const REX_W: u8 = REX | REX_PREFIX_W; #[inline(always)] @@ -1576,6 +1919,36 @@ fn xor_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene binop_reg64_reg64(0x33, buf, src, dst); } +/// `SHL r/m64, CL` -> Multiply r/m64 by 2, CL times. +#[inline(always)] +fn shl_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) { + let rex = add_rm_extension(dst, REX_W); + let rex = add_reg_extension(dst, rex); + + let dst_mod = dst as u8 % 8; + buf.extend([rex, 0xD3, 0xC0 | (4 << 3) | dst_mod]); +} + +/// `SHR r/m64, CL` -> Unsigned divide r/m64 by 2, CL times. +#[inline(always)] +fn shr_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) { + let rex = add_rm_extension(dst, REX_W); + let rex = add_reg_extension(dst, rex); + + let dst_mod = dst as u8 % 8; + buf.extend([rex, 0xD3, 0xC0 | (5 << 3) | dst_mod]); +} + +/// `SAR r/m64, CL` -> Signed divide r/m64 by 2, CL times. +#[inline(always)] +fn sar_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) { + let rex = add_rm_extension(dst, REX_W); + let rex = add_reg_extension(dst, rex); + + let dst_mod = dst as u8 % 8; + buf.extend([rex, 0xD3, 0xC0 | (7 << 3) | dst_mod]); +} + /// `ADDSD xmm1,xmm2/m64` -> Add the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1. #[inline(always)] fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { @@ -1750,6 +2123,90 @@ fn cmp_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene binop_reg64_reg64(0x39, buf, dst, src); } +#[inline(always)] +fn cmp_freg64_freg64(buf: &mut Vec<'_, u8>, src1: X86_64FloatReg, src2: X86_64FloatReg) { + let src1_high = src1 as u8 > 7; + let src1_mod = src1 as u8 % 8; + + let src2_high = src2 as u8 > 7; + let src2_mod = src2 as u8 % 8; + + if src1_high || src2_high { + buf.extend([ + 0x66, + 0x40 | ((src1_high as u8) << 2) | (src2_high as u8), + 0x0F, + 0x2E, + 0xC0 | (src1_mod << 3) | (src2_mod), + ]) + } else { + buf.extend([0x66, 0x0F, 0x2E, 0xC0 | (src1_mod << 3) | (src2_mod)]) + } +} + +#[inline(always)] +fn cmp_freg32_freg32(buf: &mut Vec<'_, u8>, src1: X86_64FloatReg, src2: X86_64FloatReg) { + let src1_high = src1 as u8 > 7; + let src1_mod = src1 as u8 % 8; + + let src2_high = src2 as u8 > 7; + let src2_mod = src2 as u8 % 8; + + if src1_high || src2_high { + buf.extend([ + 0x65, + 0x40 | ((src1_high as u8) << 2) | (src2_high as u8), + 0x0F, + 0x2E, + 0xC0 | (src1_mod << 3) | (src2_mod), + ]) + } else { + buf.extend([0x65, 0x0F, 0x2E, 0xC0 | (src1_mod << 3) | (src2_mod)]) + } +} + +#[inline(always)] +fn sqrtsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + let dst_high = dst as u8 > 7; + let dst_mod = dst as u8 % 8; + + let src_high = src as u8 > 7; + let src_mod = src as u8 % 8; + + if dst_high || src_high { + buf.extend([ + 0xF2, + 0x40 | ((dst_high as u8) << 2) | (src_high as u8), + 0x0F, + 0x51, + 0xC0 | (dst_mod << 3) | (src_mod), + ]) + } else { + buf.extend([0xF2, 0x0F, 0x51, 0xC0 | (dst_mod << 3) | (src_mod)]) + } +} + +#[inline(always)] +fn sqrtss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + let dst_high = dst as u8 > 7; + let dst_mod = dst as u8 % 8; + + let src_high = src as u8 > 7; + let src_mod = src as u8 % 8; + + if dst_high || src_high { + buf.extend([ + 0xF3, + 0x40 | ((dst_high as u8) << 2) | (src_high as u8), + 0x0F, + 0x51, + 0xC0 | (dst_mod << 3) | (src_mod), + ]) + } else { + buf.extend([0xF3, 0x0F, 0x51, 0xC0 | (dst_mod << 3) | (src_mod)]) + } +} + /// `TEST r/m64,r64` -> AND r64 with r/m64; set SF, ZF, PF according to result. #[allow(dead_code)] #[inline(always)] @@ -1899,6 +2356,117 @@ fn mov_base64_offset32_reg64( buf.extend(offset.to_le_bytes()); } +/// `MOV r/m32,r32` -> Move r32 to r/m32, where m32 references a base + offset. +#[inline(always)] +fn mov_base32_offset32_reg32( + buf: &mut Vec<'_, u8>, + base: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(src, rex); + let src_mod = (src as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + buf.extend([rex, 0x89, 0x80 | src_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + +/// `MOV r/m16,r16` -> Move r16 to r/m16, where m16 references a base + offset. +#[inline(always)] +fn mov_base16_offset32_reg16( + buf: &mut Vec<'_, u8>, + base: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(src, rex); + let src_mod = (src as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + buf.extend([GRP_4, rex, 0x89, 0x80 | src_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + +/// `MOV r/m8,r8` -> Move r8 to r/m8, where m8 references a base + offset. +#[inline(always)] +fn mov_base8_offset32_reg8( + buf: &mut Vec<'_, u8>, + base: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, +) { + let rex = add_rm_extension(base, REX); + let rex = add_reg_extension(src, rex); + let src_mod = (src as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(8); + buf.extend([rex, 0x88, 0x80 | src_mod | base_mod]); + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + +enum RegisterWidth { + W8, + W16, + W32, + W64, +} + +#[inline(always)] +fn mov_reg_base_offset32( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + use RegisterWidth::*; + + let rex = match register_width { + W64 => REX_W, + _ => REX, + }; + + let rex = add_rm_extension(base, rex); + let rex = add_reg_extension(dst, rex); + + let dst_mod = (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + let operands = 0x80 | dst_mod | base_mod; + + buf.reserve(8); + + let instruction = match register_width { + W8 => 0x8A, + W16 | W32 | W64 => 0x8B, + }; + + match register_width { + W16 => buf.extend([GRP_4, rex, instruction, operands]), + _ => buf.extend([rex, instruction, operands]), + }; + + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + /// `MOV r64,r/m64` -> Move r/m64 to r64, where m64 references a base + offset. #[inline(always)] fn mov_reg64_base64_offset32( @@ -1906,13 +2474,116 @@ fn mov_reg64_base64_offset32( dst: X86_64GeneralReg, base: X86_64GeneralReg, offset: i32, +) { + mov_reg_base_offset32(buf, RegisterWidth::W64, dst, base, offset) +} + +/// `MOV r/m32,r32` -> Move r32 to r/m32. +#[inline(always)] +fn mov_reg32_base32_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + mov_reg_base_offset32(buf, RegisterWidth::W32, dst, base, offset) +} + +/// `MOV r/m16,r16` -> Move r16 to r/m16. +#[inline(always)] +fn mov_reg16_base16_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + mov_reg_base_offset32(buf, RegisterWidth::W16, dst, base, offset) +} + +/// `MOV r/m8,r8` -> Move r8 to r/m8. +#[inline(always)] +fn mov_reg8_base8_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + mov_reg_base_offset32(buf, RegisterWidth::W8, dst, base, offset) +} + +#[inline(always)] +fn movsx_reg64_base_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, + opcode: &[u8], ) { let rex = add_rm_extension(base, REX_W); let rex = add_reg_extension(dst, rex); let dst_mod = (dst as u8 % 8) << 3; let base_mod = base as u8 % 8; - buf.reserve(8); - buf.extend([rex, 0x8B, 0x80 | dst_mod | base_mod]); + buf.reserve(9); + + // our output is a 64-bit value, so rex is always needed + buf.push(rex); + buf.extend(opcode); + buf.push(0x80 | dst_mod | base_mod); + + // Using RSP or R12 requires a secondary index byte. + if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { + buf.push(0x24); + } + buf.extend(offset.to_le_bytes()); +} + +/// `MOVSX r64,r/m32` -> Move r/m32 with sign extention to r64, where m32 references a base + offset. +#[inline(always)] +fn movsx_reg64_base32_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + movsx_reg64_base_offset32(buf, dst, base, offset, &[0x63]) +} + +/// `MOVSX r64,r/m16` -> Move r/m16 with sign extention to r64, where m16 references a base + offset. +#[inline(always)] +fn movsx_reg64_base16_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + movsx_reg64_base_offset32(buf, dst, base, offset, &[0x0F, 0xBF]) +} + +/// `MOVSX r64,r/m8` -> Move r/m8 with sign extention to r64, where m8 references a base + offset. +#[inline(always)] +fn movsx_reg64_base8_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + movsx_reg64_base_offset32(buf, dst, base, offset, &[0x0F, 0xBE]) +} + +#[inline(always)] +fn movzx_reg64_base_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, + opcode: u8, +) { + let rex = add_rm_extension(base, REX_W); + let rex = add_reg_extension(dst, rex); + let dst_mod = (dst as u8 % 8) << 3; + let base_mod = base as u8 % 8; + buf.reserve(9); + buf.extend([rex, 0x0F, opcode, 0x80 | dst_mod | base_mod]); // Using RSP or R12 requires a secondary index byte. if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { buf.push(0x24); @@ -1928,17 +2599,18 @@ fn movzx_reg64_base8_offset32( base: X86_64GeneralReg, offset: i32, ) { - let rex = add_rm_extension(base, REX_W); - let rex = add_reg_extension(dst, rex); - let dst_mod = (dst as u8 % 8) << 3; - let base_mod = base as u8 % 8; - buf.reserve(9); - buf.extend([rex, 0x0F, 0xB6, 0x80 | dst_mod | base_mod]); - // Using RSP or R12 requires a secondary index byte. - if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 { - buf.push(0x24); - } - buf.extend(offset.to_le_bytes()); + movzx_reg64_base_offset32(buf, dst, base, offset, 0xB6) +} + +/// `MOVZX r64,r/m16` -> Move r/m16 with zero extention to r64, where m16 references a base + offset. +#[inline(always)] +fn movzx_reg64_base16_offset32( + buf: &mut Vec<'_, u8>, + dst: X86_64GeneralReg, + base: X86_64GeneralReg, + offset: i32, +) { + movzx_reg64_base_offset32(buf, dst, base, offset, 0xB7) } /// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register. @@ -2207,6 +2879,12 @@ fn seta_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { set_reg64_help(0x97, buf, reg); } +/// `SETAE r/m64` -> Set byte if above or equal (CF=0). +#[inline(always)] +fn setae_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { + set_reg64_help(0x93, buf, reg); +} + /// `SETLE r/m64` -> Set byte if less or equal (ZF=1 or SF≠ OF). #[inline(always)] fn setle_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { @@ -2281,6 +2959,50 @@ mod tests { use capstone::prelude::*; impl X86_64GeneralReg { + #[allow(dead_code)] + fn low_32bits_string(&self) -> &str { + match self { + X86_64GeneralReg::RAX => "eax", + X86_64GeneralReg::RBX => "ebx", + X86_64GeneralReg::RCX => "ecx", + X86_64GeneralReg::RDX => "edx", + X86_64GeneralReg::RBP => "ebp", + X86_64GeneralReg::RSP => "esp", + X86_64GeneralReg::RDI => "edi", + X86_64GeneralReg::RSI => "esi", + X86_64GeneralReg::R8 => "r8d", + X86_64GeneralReg::R9 => "r9d", + X86_64GeneralReg::R10 => "r10d", + X86_64GeneralReg::R11 => "r11d", + X86_64GeneralReg::R12 => "r12d", + X86_64GeneralReg::R13 => "r13d", + X86_64GeneralReg::R14 => "r14d", + X86_64GeneralReg::R15 => "r15d", + } + } + + #[allow(dead_code)] + fn low_16bits_string(&self) -> &str { + match self { + X86_64GeneralReg::RAX => "ax", + X86_64GeneralReg::RBX => "bx", + X86_64GeneralReg::RCX => "cx", + X86_64GeneralReg::RDX => "dx", + X86_64GeneralReg::RBP => "bp", + X86_64GeneralReg::RSP => "sp", + X86_64GeneralReg::RDI => "di", + X86_64GeneralReg::RSI => "si", + X86_64GeneralReg::R8 => "r8w", + X86_64GeneralReg::R9 => "r9w", + X86_64GeneralReg::R10 => "r10w", + X86_64GeneralReg::R11 => "r11w", + X86_64GeneralReg::R12 => "r12w", + X86_64GeneralReg::R13 => "r13w", + X86_64GeneralReg::R14 => "r14w", + X86_64GeneralReg::R15 => "r15w", + } + } + #[allow(dead_code)] fn low_8bits_string(&self) -> &str { match self { @@ -2447,6 +3169,33 @@ mod tests { ); } + #[test] + fn test_shl_reg64_reg64() { + disassembler_test!( + shl_reg64_reg64, + |reg| format!("shl {reg}, cl"), + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_shr_reg64_reg64() { + disassembler_test!( + shr_reg64_reg64, + |reg| format!("shr {reg}, cl"), + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_sar_reg64_reg64() { + disassembler_test!( + sar_reg64_reg64, + |reg| format!("sar {reg}, cl"), + ALL_GENERAL_REGS + ); + } + #[test] fn test_cmovl_reg64_reg64() { disassembler_test!( @@ -2633,6 +3382,54 @@ mod tests { ); } + #[test] + fn test_mov_reg32_base32_offset32() { + disassembler_test!( + mov_reg32_base32_offset32, + |reg1, reg2, imm| format!( + "mov {}, dword ptr [{} + 0x{:x}]", + X86_64GeneralReg::low_32bits_string(®1), + reg2, + imm + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_mov_reg16_base16_offset32() { + disassembler_test!( + mov_reg16_base16_offset32, + |reg1, reg2, imm| format!( + "mov {}, word ptr [{} + 0x{:x}]", + X86_64GeneralReg::low_16bits_string(®1), + reg2, + imm + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_mov_reg8_base8_offset32() { + disassembler_test!( + mov_reg8_base8_offset32, + |reg1, reg2, imm| format!( + "mov {}, byte ptr [{} + 0x{:x}]", + X86_64GeneralReg::low_8bits_string(®1), + reg2, + imm + ), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + #[test] fn test_mov_base64_offset32_reg64() { disassembler_test!( @@ -2644,6 +3441,98 @@ mod tests { ); } + #[test] + fn test_mov_base32_offset32_reg32() { + disassembler_test!( + mov_base32_offset32_reg32, + |reg1, imm, reg2| format!( + "mov dword ptr [{} + 0x{:x}], {}", + reg1, + imm, + X86_64GeneralReg::low_32bits_string(®2), + ), + ALL_GENERAL_REGS, + [TEST_I32], + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_mov_base16_offset32_reg16() { + disassembler_test!( + mov_base16_offset32_reg16, + |reg1, imm, reg2| format!( + "mov word ptr [{} + 0x{:x}], {}", + reg1, + imm, + X86_64GeneralReg::low_16bits_string(®2), + ), + ALL_GENERAL_REGS, + [TEST_I32], + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_mov_base8_offset32_reg8() { + disassembler_test!( + mov_base8_offset32_reg8, + |reg1, imm, reg2| format!( + "mov byte ptr [{} + 0x{:x}], {}", + reg1, + imm, + X86_64GeneralReg::low_8bits_string(®2), + ), + ALL_GENERAL_REGS, + [TEST_I32], + ALL_GENERAL_REGS + ); + } + + #[test] + fn test_movsx_reg64_base32_offset32() { + disassembler_test!( + movsx_reg64_base32_offset32, + |reg1, reg2, imm| format!("movsxd {}, dword ptr [{} + 0x{:x}]", reg1, reg2, imm), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_movsx_reg64_base16_offset32() { + disassembler_test!( + movsx_reg64_base16_offset32, + |reg1, reg2, imm| format!("movsx {}, word ptr [{} + 0x{:x}]", reg1, reg2, imm), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_movsx_reg64_base8_offset32() { + disassembler_test!( + movsx_reg64_base8_offset32, + |reg1, reg2, imm| format!("movsx {}, byte ptr [{} + 0x{:x}]", reg1, reg2, imm), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + + #[test] + fn test_movzx_reg64_base16_offset32() { + disassembler_test!( + movzx_reg64_base16_offset32, + |reg1, reg2, imm| format!("movzx {}, word ptr [{} + 0x{:x}]", reg1, reg2, imm), + ALL_GENERAL_REGS, + ALL_GENERAL_REGS, + [TEST_I32] + ); + } + #[test] fn test_movzx_reg64_base8_offset32() { disassembler_test!( @@ -2762,4 +3651,24 @@ mod tests { fn test_push_reg64() { disassembler_test!(push_reg64, |reg| format!("push {}", reg), ALL_GENERAL_REGS); } + + #[test] + fn test_sqrt_freg64_freg64() { + disassembler_test!( + sqrtsd_freg64_freg64, + |dst, src| format!("sqrtsd {dst}, {src}"), + ALL_FLOAT_REGS, + ALL_FLOAT_REGS + ); + } + + #[test] + fn test_sqrt_freg32_freg32() { + disassembler_test!( + sqrtss_freg32_freg32, + |dst, src| format!("sqrtss {dst}, {src}"), + ALL_FLOAT_REGS, + ALL_FLOAT_REGS + ); + } } diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 6c15df866b..1aba223c90 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -21,6 +21,7 @@ use roc_mono::layout::{ Builtin, InLayout, Layout, LayoutId, LayoutIds, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout, }; +use roc_mono::list_element_layout; mod generic64; mod object_builder; @@ -283,28 +284,31 @@ trait Backend<'a> { if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = LowLevelWrapperType::from_symbol(func_sym.name()) { - self.build_run_low_level( + return self.build_run_low_level( sym, &lowlevel, arguments, arg_layouts, ret_layout, - ) - } else if self.defined_in_app_module(func_sym.name()) { - let layout_id = LayoutIds::default().get(func_sym.name(), layout); - let fn_name = self.symbol_to_string(func_sym.name(), layout_id); - // Now that the arguments are needed, load them if they are literals. - self.load_literal_symbols(arguments); - self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout) - } else { - self.build_builtin( + ); + } else if sym.is_builtin() { + // These builtins can be built through `build_fn_call` as well, but the + // implementation in `build_builtin` inlines some of the symbols. + return self.build_builtin( sym, func_sym.name(), arguments, arg_layouts, ret_layout, - ) + ); } + + let layout_id = LayoutIds::default().get(func_sym.name(), layout); + let fn_name = self.symbol_to_string(func_sym.name(), layout_id); + + // Now that the arguments are needed, load them if they are literals. + self.load_literal_symbols(arguments); + self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout) } CallType::LowLevel { op: lowlevel, .. } => { @@ -380,6 +384,21 @@ trait Backend<'a> { self.load_literal_symbols(arguments); self.tag(sym, arguments, tag_layout, *tag_id); } + Expr::ExprBox { symbol: value } => { + let element_layout = match self.interner().get(*layout) { + Layout::Boxed(boxed) => boxed, + _ => unreachable!("{:?}", self.interner().dbg(*layout)), + }; + + self.load_literal_symbols([*value].as_slice()); + self.expr_box(*sym, *value, element_layout) + } + Expr::ExprUnbox { symbol: ptr } => { + let element_layout = *layout; + + self.load_literal_symbols([*ptr].as_slice()); + self.expr_unbox(*sym, *ptr, element_layout) + } x => todo!("the expression, {:?}", x), } } @@ -428,6 +447,9 @@ trait Backend<'a> { LowLevel::NumAddChecked => { self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout) } + LowLevel::NumSubChecked => { + self.build_num_sub_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout) + } LowLevel::NumAcos => self.build_fn_call( sym, bitcode::NUM_ACOS[FloatWidth::F64].to_string(), @@ -532,6 +554,27 @@ trait Backend<'a> { ); self.build_num_sub_wrap(sym, &args[0], &args[1], ret_layout) } + LowLevel::NumSubSaturated => match self.interner().get(*ret_layout) { + Layout::Builtin(Builtin::Int(int_width)) => self.build_fn_call( + sym, + bitcode::NUM_SUB_SATURATED_INT[int_width].to_string(), + args, + arg_layouts, + ret_layout, + ), + Layout::Builtin(Builtin::Float(FloatWidth::F32)) => { + self.build_num_sub(sym, &args[0], &args[1], ret_layout) + } + Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + // saturated sub is just normal sub + self.build_num_sub(sym, &args[0], &args[1], ret_layout) + } + Layout::Builtin(Builtin::Decimal) => { + // self.load_args_and_call_zig(backend, bitcode::DEC_SUB_SATURATED) + todo!() + } + _ => internal_error!("invalid return type"), + }, LowLevel::NumBitwiseAnd => { if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) { self.build_int_bitwise_and(sym, &args[0], &args[1], int_width) @@ -553,6 +596,41 @@ trait Backend<'a> { internal_error!("bitwise xor on a non-integer") } } + LowLevel::And => { + if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) { + self.build_int_bitwise_and(sym, &args[0], &args[1], IntWidth::U8) + } else { + internal_error!("bitwise and on a non-integer") + } + } + LowLevel::Or => { + if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) { + self.build_int_bitwise_or(sym, &args[0], &args[1], IntWidth::U8) + } else { + internal_error!("bitwise or on a non-integer") + } + } + LowLevel::NumShiftLeftBy => { + if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) { + self.build_int_shift_left(sym, &args[0], &args[1], int_width) + } else { + internal_error!("shift left on a non-integer") + } + } + LowLevel::NumShiftRightBy => { + if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) { + self.build_int_shift_right(sym, &args[0], &args[1], int_width) + } else { + internal_error!("shift right on a non-integer") + } + } + LowLevel::NumShiftRightZfBy => { + if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) { + self.build_int_shift_right_zero_fill(sym, &args[0], &args[1], int_width) + } else { + internal_error!("shift right zero-fill on a non-integer") + } + } LowLevel::Eq => { debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument"); debug_assert_eq!( @@ -583,6 +661,15 @@ trait Backend<'a> { ); self.build_neq(sym, &args[0], &args[1], &arg_layouts[0]) } + LowLevel::Not => { + debug_assert_eq!(1, args.len(), "Not: expected to have exactly one argument"); + debug_assert_eq!( + Layout::BOOL, + *ret_layout, + "Not: expected to have return layout of type Bool" + ); + self.build_not(sym, &args[0], &arg_layouts[0]) + } LowLevel::NumLt => { debug_assert_eq!( 2, @@ -664,6 +751,30 @@ trait Backend<'a> { ); self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0]) } + LowLevel::NumLogUnchecked => { + let float_width = match arg_layouts[0] { + Layout::F64 => FloatWidth::F64, + Layout::F32 => FloatWidth::F32, + _ => unreachable!("invalid layout for sqrt"), + }; + + self.build_fn_call( + sym, + bitcode::NUM_LOG[float_width].to_string(), + args, + arg_layouts, + ret_layout, + ) + } + LowLevel::NumSqrtUnchecked => { + let float_width = match arg_layouts[0] { + Layout::F64 => FloatWidth::F64, + Layout::F32 => FloatWidth::F32, + _ => unreachable!("invalid layout for sqrt"), + }; + + self.build_num_sqrt(*sym, args[0], float_width); + } LowLevel::NumRound => self.build_fn_call( sym, bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(), @@ -685,7 +796,24 @@ trait Backend<'a> { args.len(), "ListWithCapacity: expected to have exactly one argument" ); - self.build_list_with_capacity(sym, args[0], arg_layouts[0], ret_layout) + let elem_layout = list_element_layout!(self.interner(), *ret_layout); + self.build_list_with_capacity(sym, args[0], arg_layouts[0], elem_layout, ret_layout) + } + LowLevel::ListReserve => { + debug_assert_eq!( + 2, + args.len(), + "ListReserve: expected to have exactly two arguments" + ); + self.build_list_reserve(sym, args, arg_layouts, ret_layout) + } + LowLevel::ListAppendUnsafe => { + debug_assert_eq!( + 2, + args.len(), + "ListAppendUnsafe: expected to have exactly two arguments" + ); + self.build_list_append_unsafe(sym, args, arg_layouts, ret_layout) } LowLevel::ListGetUnsafe => { debug_assert_eq!( @@ -703,6 +831,23 @@ trait Backend<'a> { ); self.build_list_replace_unsafe(sym, args, arg_layouts, ret_layout) } + LowLevel::ListConcat => { + debug_assert_eq!( + 2, + args.len(), + "ListConcat: expected to have exactly two arguments" + ); + let elem_layout = list_element_layout!(self.interner(), *ret_layout); + self.build_list_concat(sym, args, arg_layouts, elem_layout, ret_layout) + } + LowLevel::ListPrepend => { + debug_assert_eq!( + 2, + args.len(), + "ListPrepend: expected to have exactly two arguments" + ); + self.build_list_prepend(sym, args, arg_layouts, ret_layout) + } LowLevel::StrConcat => self.build_fn_call( sym, bitcode::STR_CONCAT.to_string(), @@ -710,6 +855,171 @@ trait Backend<'a> { arg_layouts, ret_layout, ), + LowLevel::StrJoinWith => self.build_fn_call( + sym, + bitcode::STR_JOIN_WITH.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrSplit => self.build_fn_call( + sym, + bitcode::STR_SPLIT.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrStartsWith => self.build_fn_call( + sym, + bitcode::STR_STARTS_WITH.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrStartsWithScalar => self.build_fn_call( + sym, + bitcode::STR_STARTS_WITH_SCALAR.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrAppendScalar => self.build_fn_call( + sym, + bitcode::STR_APPEND_SCALAR.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrEndsWith => self.build_fn_call( + sym, + bitcode::STR_ENDS_WITH.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrCountGraphemes => self.build_fn_call( + sym, + bitcode::STR_COUNT_GRAPEHEME_CLUSTERS.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrSubstringUnsafe => self.build_fn_call( + sym, + bitcode::STR_SUBSTRING_UNSAFE.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrToUtf8 => self.build_fn_call( + sym, + bitcode::STR_TO_UTF8.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrCountUtf8Bytes => self.build_fn_call( + sym, + bitcode::STR_COUNT_UTF8_BYTES.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrFromUtf8Range => self.build_fn_call( + sym, + bitcode::STR_FROM_UTF8_RANGE.to_string(), + args, + arg_layouts, + ret_layout, + ), + // LowLevel::StrToUtf8 => self.build_fn_call( + // sym, + // bitcode::STR_TO_UTF8.to_string(), + // args, + // arg_layouts, + // ret_layout, + // ), + LowLevel::StrRepeat => self.build_fn_call( + sym, + bitcode::STR_REPEAT.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrTrim => self.build_fn_call( + sym, + bitcode::STR_TRIM.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrTrimLeft => self.build_fn_call( + sym, + bitcode::STR_TRIM_LEFT.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrTrimRight => self.build_fn_call( + sym, + bitcode::STR_TRIM_RIGHT.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrReserve => self.build_fn_call( + sym, + bitcode::STR_RESERVE.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrWithCapacity => self.build_fn_call( + sym, + bitcode::STR_WITH_CAPACITY.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrToScalars => self.build_fn_call( + sym, + bitcode::STR_TO_SCALARS.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrGetUnsafe => self.build_fn_call( + sym, + bitcode::STR_GET_UNSAFE.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrGetScalarUnsafe => self.build_fn_call( + sym, + bitcode::STR_GET_SCALAR_UNSAFE.to_string(), + args, + arg_layouts, + ret_layout, + ), + LowLevel::StrToNum => { + let number_layout = match self.interner().get(*ret_layout) { + Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct? + _ => unreachable!(), + }; + + // match on the return layout to figure out which zig builtin we need + let intrinsic = match self.interner().get(number_layout) { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => { + &bitcode::STR_TO_FLOAT[float_width] + } + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + _ => unreachable!(), + }; + + self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout) + } LowLevel::PtrCast => { debug_assert_eq!( 1, @@ -746,7 +1056,6 @@ trait Backend<'a> { arg_layouts: &[InLayout<'a>], ret_layout: &InLayout<'a>, ) { - self.load_literal_symbols(args); match func_sym { Symbol::NUM_IS_ZERO => { debug_assert_eq!( @@ -760,6 +1069,7 @@ trait Backend<'a> { "NumIsZero: expected to have return layout of type Bool" ); + self.load_literal_symbols(args); self.load_literal( &Symbol::DEV_TMP, &arg_layouts[0], @@ -768,7 +1078,7 @@ trait Backend<'a> { self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]); self.free_symbol(&Symbol::DEV_TMP) } - Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE => { + Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE | Symbol::LIST_APPEND => { // TODO: This is probably simple enough to be worth inlining. let layout_id = LayoutIds::default().get(func_sym, ret_layout); let fn_name = self.symbol_to_string(func_sym, layout_id); @@ -776,24 +1086,36 @@ trait Backend<'a> { self.load_literal_symbols(args); self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) } - Symbol::NUM_ADD_CHECKED => { - let layout_id = LayoutIds::default().get(func_sym, ret_layout); - let fn_name = self.symbol_to_string(func_sym, layout_id); - // Now that the arguments are needed, load them if they are literals. - self.load_literal_symbols(args); - self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) - } Symbol::BOOL_TRUE => { let bool_layout = Layout::BOOL; self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(true)); self.return_symbol(&Symbol::DEV_TMP, &bool_layout); + self.free_symbol(&Symbol::DEV_TMP) } Symbol::BOOL_FALSE => { let bool_layout = Layout::BOOL; self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(false)); self.return_symbol(&Symbol::DEV_TMP, &bool_layout); + self.free_symbol(&Symbol::DEV_TMP) + } + Symbol::STR_IS_VALID_SCALAR => { + // just call the function + let layout_id = LayoutIds::default().get(func_sym, ret_layout); + let fn_name = self.symbol_to_string(func_sym, layout_id); + // Now that the arguments are needed, load them if they are literals. + self.load_literal_symbols(args); + self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) + } + other => { + eprintln!("maybe {other:?} should have a custom implementation?"); + + // just call the function + let layout_id = LayoutIds::default().get(func_sym, ret_layout); + let fn_name = self.symbol_to_string(func_sym, layout_id); + // Now that the arguments are needed, load them if they are literals. + self.load_literal_symbols(args); + self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) } - _ => todo!("the function, {:?}", func_sym), } } @@ -824,6 +1146,16 @@ trait Backend<'a> { return_layout: &InLayout<'a>, ); + /// build_num_sub_checked stores the sum of src1 and src2 into dst. + fn build_num_sub_checked( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + num_layout: &InLayout<'a>, + return_layout: &InLayout<'a>, + ); + /// build_num_mul stores `src1 * src2` into dst. fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>); @@ -872,12 +1204,42 @@ trait Backend<'a> { int_width: IntWidth, ); + /// stores the `Num.shiftLeftBy src1 src2` into dst. + fn build_int_shift_left( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + int_width: IntWidth, + ); + + /// stores the `Num.shiftRightBy src1 src2` into dst. + fn build_int_shift_right( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + int_width: IntWidth, + ); + + /// stores the `Num.shiftRightZfBy src1 src2` into dst. + fn build_int_shift_right_zero_fill( + &mut self, + dst: &Symbol, + src1: &Symbol, + src2: &Symbol, + int_width: IntWidth, + ); + /// build_eq stores the result of `src1 == src2` into dst. fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>); /// build_neq stores the result of `src1 != src2` into dst. fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>); + /// build_not stores the result of `!src` into dst. + fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>); + /// build_num_lt stores the result of `src1 < src2` into dst. fn build_num_lt( &mut self, @@ -923,6 +1285,9 @@ trait Backend<'a> { arg_layout: &InLayout<'a>, ); + /// build_sqrt stores the result of `sqrt(src)` into dst. + fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth); + /// build_list_len returns the length of a list. fn build_list_len(&mut self, dst: &Symbol, list: &Symbol); @@ -932,6 +1297,25 @@ trait Backend<'a> { dst: &Symbol, capacity: Symbol, capacity_layout: InLayout<'a>, + elem_layout: InLayout<'a>, + ret_layout: &InLayout<'a>, + ); + + /// build_list_reserve enlarges a list to at least accommodate the given capacity. + fn build_list_reserve( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[InLayout<'a>], + ret_layout: &InLayout<'a>, + ); + + /// build_list_append_unsafe returns a new list with a given element appended. + fn build_list_append_unsafe( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[InLayout<'a>], ret_layout: &InLayout<'a>, ); @@ -953,6 +1337,25 @@ trait Backend<'a> { ret_layout: &InLayout<'a>, ); + /// build_list_concat returns a new list containing the two argument lists concatenated. + fn build_list_concat( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[InLayout<'a>], + elem_layout: InLayout<'a>, + ret_layout: &InLayout<'a>, + ); + + /// build_list_prepend returns a new list with a given element prepended. + fn build_list_prepend( + &mut self, + dst: &Symbol, + args: &'a [Symbol], + arg_layouts: &[InLayout<'a>], + ret_layout: &InLayout<'a>, + ); + /// build_refcount_getptr loads the pointer to the reference count of src into dst. fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol); @@ -1021,6 +1424,12 @@ trait Backend<'a> { tag_id: TagIdIntType, ); + /// load a value from a pointer + fn expr_unbox(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>); + + /// store a refcounted value on the heap + fn expr_box(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>); + /// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function. fn return_symbol(&mut self, sym: &Symbol, layout: &InLayout<'a>); diff --git a/crates/compiler/gen_dev/src/run_roc.rs b/crates/compiler/gen_dev/src/run_roc.rs index 981a60f8ae..a0d20c0d12 100644 --- a/crates/compiler/gen_dev/src/run_roc.rs +++ b/crates/compiler/gen_dev/src/run_roc.rs @@ -18,12 +18,16 @@ macro_rules! run_jit_function_raw { let result = main(); - assert_eq!( - $errors, - std::vec::Vec::new(), - "Encountered errors: {:?}", - $errors - ); + if !$errors.is_empty() { + dbg!(&$errors); + + assert_eq!( + $errors, + std::vec::Vec::new(), + "Encountered errors: {:?}", + $errors + ); + } $transform(result) } diff --git a/crates/compiler/gen_llvm/Cargo.toml b/crates/compiler/gen_llvm/Cargo.toml index af3bf91a16..031c418bfc 100644 --- a/crates/compiler/gen_llvm/Cargo.toml +++ b/crates/compiler/gen_llvm/Cargo.toml @@ -1,24 +1,26 @@ [package] name = "roc_gen_llvm" description = "The LLVM backend for the Roc compiler" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dependencies] -roc_alias_analysis = { path = "../alias_analysis" } -roc_collections = { path = "../collections" } -roc_module = { path = "../module" } -roc_builtins = { path = "../builtins" } -roc_error_macros = { path = "../../error_macros" } -roc_mono = { path = "../mono" } -roc_target = { path = "../roc_target" } -roc_std = { path = "../../roc_std" } -roc_debug_flags = { path = "../debug_flags" } -roc_region = { path = "../region" } morphic_lib = { path = "../../vendor/morphic_lib" } +roc_alias_analysis = { path = "../alias_analysis" } +roc_bitcode_bc = { path = "../builtins/bitcode/bc" } +roc_builtins = { path = "../builtins" } +roc_collections = { path = "../collections" } +roc_debug_flags = { path = "../debug_flags" } +roc_error_macros = { path = "../../error_macros" } +roc_module = { path = "../module" } +roc_mono = { path = "../mono" } +roc_region = { path = "../region" } +roc_std = { path = "../../roc_std" } +roc_target = { path = "../roc_target" } bumpalo.workspace = true +inkwell.workspace = true target-lexicon.workspace = true -inkwell.workspace = true diff --git a/crates/compiler/gen_llvm/src/llvm/bitcode.rs b/crates/compiler/gen_llvm/src/llvm/bitcode.rs index 29812f73e6..96c6d9290e 100644 --- a/crates/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/crates/compiler/gen_llvm/src/llvm/bitcode.rs @@ -11,8 +11,8 @@ use crate::llvm::refcounting::{ use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::types::{BasicType, BasicTypeEnum, StructType}; use inkwell::values::{ - BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, - PointerValue, StructValue, + BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, PointerValue, + StructValue, }; use inkwell::AddressSpace; use roc_error_macros::internal_error; @@ -206,7 +206,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( let block = env.builder.get_insert_block().expect("to be in a function"); let di_location = env.builder.get_current_debug_location().unwrap(); - let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic); + let arg_type = env.context.i8_type().ptr_type(AddressSpace::default()); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -244,7 +244,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) { let basic_type = - basic_type_from_layout(env, layout_interner, *layout).ptr_type(AddressSpace::Generic); + basic_type_from_layout(env, layout_interner, *layout).ptr_type(AddressSpace::default()); let cast_ptr = env.builder.build_pointer_cast( argument_ptr.into_pointer_value(), @@ -274,7 +274,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( } (true, layout) => { let closure_type = basic_type_from_layout(env, layout_interner, layout) - .ptr_type(AddressSpace::Generic); + .ptr_type(AddressSpace::default()); let closure_cast = env.builder @@ -310,8 +310,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>( env.builder.build_return(None); env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function_value } @@ -375,7 +374,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>( let function_value = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic); + let arg_type = env.context.i8_type().ptr_type(AddressSpace::default()); let function_value = match rc_operation { Mode::Inc | Mode::Dec => crate::llvm::refcounting::build_header_help( @@ -411,7 +410,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>( generic_value_ptr.set_name(Symbol::ARG_1.as_str(&env.interns)); let value_type = basic_type_from_layout(env, layout_interner, layout); - let value_ptr_type = value_type.ptr_type(AddressSpace::Generic); + let value_ptr_type = value_type.ptr_type(AddressSpace::default()); let value_ptr = env.builder .build_pointer_cast(generic_value_ptr, value_ptr_type, "load_opaque"); @@ -449,8 +448,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function_value } @@ -472,7 +470,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>( let function_value = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, None => { - let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic); + let arg_type = env.context.i8_type().ptr_type(AddressSpace::default()); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -502,7 +500,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>( value_ptr2.set_name(Symbol::ARG_2.as_str(&env.interns)); let value_type = basic_type_from_layout(env, layout_interner, layout) - .ptr_type(AddressSpace::Generic); + .ptr_type(AddressSpace::default()); let value_cast1 = env .builder @@ -533,8 +531,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function_value } @@ -558,7 +555,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>( let function_value = match env.module.get_function(fn_name) { Some(function_value) => function_value, None => { - let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic); + let arg_type = env.context.i8_type().ptr_type(AddressSpace::default()); let function_value = crate::llvm::refcounting::build_header_help( env, @@ -593,7 +590,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>( value_ptr2.set_name(Symbol::ARG_3.as_str(&env.interns)); let value_type = basic_type_from_layout(env, layout_interner, layout); - let value_ptr_type = value_type.ptr_type(AddressSpace::Generic); + let value_ptr_type = value_type.ptr_type(AddressSpace::default()); let value_cast1 = env.builder @@ -623,7 +620,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>( _ => { let closure_type = basic_type_from_layout(env, layout_interner, closure_data_repr); - let closure_ptr_type = closure_type.ptr_type(AddressSpace::Generic); + let closure_ptr_type = closure_type.ptr_type(AddressSpace::default()); let closure_cast = env.builder.build_pointer_cast( closure_ptr, @@ -659,8 +656,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function_value } @@ -798,7 +794,7 @@ fn ptr_len_cap<'a, 'ctx, 'env>( let ptr = env.builder.build_int_to_ptr( lower_word, - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i8_type().ptr_type(AddressSpace::default()), "list_ptr", ); diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index 8d051a7779..b9c888858f 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -24,8 +24,8 @@ use inkwell::types::{ }; use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{ - BasicMetadataValueEnum, BasicValue, CallSiteValue, FunctionValue, InstructionValue, IntValue, - PhiValue, PointerValue, StructValue, + BasicMetadataValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, PhiValue, + PointerValue, StructValue, }; use inkwell::OptimizationLevel; use inkwell::{AddressSpace, IntPredicate}; @@ -37,11 +37,10 @@ use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION; -use roc_error_macros::internal_error; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::{ - BranchInfo, CallType, CrashTag, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc, - OptLevel, ProcLayout, SingleEntryPoint, + BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet, JoinPointId, + ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint, }; use roc_mono::layout::{ Builtin, InLayout, LambdaName, LambdaSet, Layout, LayoutIds, LayoutInterner, Niche, @@ -157,7 +156,7 @@ macro_rules! debug_info_init { /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - $env.builder.set_current_debug_location(&$env.context, loc); + $env.builder.set_current_debug_location(loc); }}; } @@ -201,6 +200,7 @@ pub enum LlvmBackendMode { BinaryDev, /// Creates a test wrapper around the main roc function to catch and report panics. /// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc) + BinaryGlue, GenTest, WasmGenTest, CliTest, @@ -211,6 +211,7 @@ impl LlvmBackendMode { match self { LlvmBackendMode::Binary => true, LlvmBackendMode::BinaryDev => true, + LlvmBackendMode::BinaryGlue => false, LlvmBackendMode::GenTest => false, LlvmBackendMode::WasmGenTest => true, LlvmBackendMode::CliTest => false, @@ -222,6 +223,7 @@ impl LlvmBackendMode { match self { LlvmBackendMode::Binary => false, LlvmBackendMode::BinaryDev => false, + LlvmBackendMode::BinaryGlue => false, LlvmBackendMode::GenTest => true, LlvmBackendMode::WasmGenTest => true, LlvmBackendMode::CliTest => true, @@ -232,6 +234,7 @@ impl LlvmBackendMode { match self { LlvmBackendMode::Binary => false, LlvmBackendMode::BinaryDev => true, + LlvmBackendMode::BinaryGlue => false, LlvmBackendMode::GenTest => false, LlvmBackendMode::WasmGenTest => false, LlvmBackendMode::CliTest => true, @@ -686,7 +689,7 @@ fn promote_to_wasm_test_wrapper<'a, 'ctx, 'env>( let output_type = match roc_main_fn.get_type().get_return_type() { Some(return_type) => { - let output_type = return_type.ptr_type(AddressSpace::Generic); + let output_type = return_type.ptr_type(AddressSpace::default()); output_type.into() } None => { @@ -880,7 +883,7 @@ fn small_str_ptr_width_8<'a, 'ctx, 'env>( let len = env.ptr_int().const_int(word2, false); let cap = env.ptr_int().const_int(word3, false); - let address_space = AddressSpace::Generic; + let address_space = AddressSpace::default(); let ptr_type = env.context.i8_type().ptr_type(address_space); let ptr = env.builder.build_int_to_ptr(ptr, ptr_type, "to_u8_ptr"); @@ -907,7 +910,7 @@ fn small_str_ptr_width_4<'a, 'ctx, 'env>( let len = env.ptr_int().const_int(word2 as u64, false); let cap = env.ptr_int().const_int(word3 as u64, false); - let address_space = AddressSpace::Generic; + let address_space = AddressSpace::default(); let ptr_type = env.context.i8_type().ptr_type(address_space); let ptr = env.builder.build_int_to_ptr(ptr, ptr_type, "to_u8_ptr"); @@ -1049,7 +1052,7 @@ fn struct_pointer_from_fields<'a, 'ctx, 'env, I>( .builder .build_bitcast( input_pointer, - struct_type.ptr_type(AddressSpace::Generic), + struct_type.ptr_type(AddressSpace::default()), "struct_ptr", ) .into_pointer_value(); @@ -1310,7 +1313,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( let data_ptr = env.builder.build_pointer_cast( opaque_data_ptr, - struct_type.ptr_type(AddressSpace::Generic), + struct_type.ptr_type(AddressSpace::default()), "to_data_pointer", ); @@ -1338,14 +1341,15 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( let field_layouts = tag_layouts[*tag_id as usize]; let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); + let target_loaded_type = basic_type_from_layout(env, layout_interner, layout); lookup_at_index_ptr2( env, layout_interner, - union_layout, field_layouts, *index as usize, ptr, + target_loaded_type, ) } UnionLayout::NonNullableUnwrapped(field_layouts) => { @@ -1353,15 +1357,16 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( layout_interner.insert(Layout::struct_no_name_order(field_layouts)); let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); + let target_loaded_type = basic_type_from_layout(env, layout_interner, layout); lookup_at_index_ptr( env, layout_interner, - union_layout, field_layouts, *index as usize, argument.into_pointer_value(), struct_type.into_struct_type(), + target_loaded_type, ) } UnionLayout::NullableWrapped { @@ -1380,13 +1385,15 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( let field_layouts = other_tags[tag_index as usize]; let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value()); + let target_loaded_type = basic_type_from_layout(env, layout_interner, layout); + lookup_at_index_ptr2( env, layout_interner, - union_layout, field_layouts, *index as usize, ptr, + target_loaded_type, ) } UnionLayout::NullableUnwrapped { @@ -1401,16 +1408,17 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( layout_interner.insert(Layout::struct_no_name_order(field_layouts)); let struct_type = basic_type_from_layout(env, layout_interner, struct_layout); + let target_loaded_type = basic_type_from_layout(env, layout_interner, layout); lookup_at_index_ptr( env, layout_interner, - union_layout, field_layouts, // the tag id is not stored *index as usize, argument.into_pointer_value(), struct_type.into_struct_type(), + target_loaded_type, ) } } @@ -1535,7 +1543,7 @@ fn build_tag_field_value<'a, 'ctx, 'env>( env.builder .build_pointer_cast( value.into_pointer_value(), - env.context.i64_type().ptr_type(AddressSpace::Generic), + env.context.i64_type().ptr_type(AddressSpace::default()), "cast_recursive_pointer", ) .into() @@ -1751,7 +1759,7 @@ fn build_tag<'a, 'ctx, 'env>( ); if tag_id == *nullable_id as _ { - let output_type = roc_union.struct_type().ptr_type(AddressSpace::Generic); + let output_type = roc_union.struct_type().ptr_type(AddressSpace::default()); return output_type.const_null().into(); } @@ -1993,17 +2001,17 @@ pub fn get_tag_id<'a, 'ctx, 'env>( fn lookup_at_index_ptr<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, - union_layout: &UnionLayout<'a>, field_layouts: &[InLayout<'a>], index: usize, value: PointerValue<'ctx>, struct_type: StructType<'ctx>, + target_loaded_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ptr = env.builder.build_pointer_cast( value, - struct_type.ptr_type(AddressSpace::Generic), + struct_type.ptr_type(AddressSpace::default()), "cast_lookup_at_index_ptr", ); @@ -2020,35 +2028,18 @@ fn lookup_at_index_ptr<'a, 'ctx, 'env>( "load_at_index_ptr_old", ); - if let Some(Layout::RecursivePointer(_)) = field_layouts - .get(index as usize) - .map(|l| layout_interner.get(*l)) - { - // a recursive field is stored as a `i64*`, to use it we must cast it to - // a pointer to the block of memory representation - let union_layout = layout_interner.insert(Layout::Union(*union_layout)); - let actual_type = basic_type_from_layout(env, layout_interner, union_layout); - debug_assert!(actual_type.is_pointer_type()); - - builder - .build_pointer_cast( - result.into_pointer_value(), - actual_type.into_pointer_type(), - "cast_rec_pointer_lookup_at_index_ptr_old", - ) - .into() - } else { - result - } + // A recursive pointer in the loaded structure is stored as a `i64*`, but the loaded layout + // might want a more precise structure. As such, cast it to the refined type if needed. + cast_if_necessary_for_opaque_recursive_pointers(env.builder, result, target_loaded_type) } fn lookup_at_index_ptr2<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, - union_layout: &UnionLayout<'a>, field_layouts: &'a [InLayout<'a>], index: usize, value: PointerValue<'ctx>, + target_loaded_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; @@ -2058,7 +2049,7 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>( let data_ptr = env.builder.build_pointer_cast( value, - struct_type.ptr_type(AddressSpace::Generic), + struct_type.ptr_type(AddressSpace::default()), "cast_lookup_at_index_ptr", ); @@ -2080,27 +2071,9 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>( "load_at_index_ptr", ); - if let Some(Layout::RecursivePointer(_)) = field_layouts - .get(index as usize) - .map(|l| layout_interner.get(*l)) - { - // a recursive field is stored as a `i64*`, to use it we must cast it to - // a pointer to the block of memory representation - - let union_layout = layout_interner.insert(Layout::Union(*union_layout)); - let actual_type = basic_type_from_layout(env, layout_interner, union_layout); - debug_assert!(actual_type.is_pointer_type()); - - builder - .build_pointer_cast( - result.into_pointer_value(), - actual_type.into_pointer_type(), - "cast_rec_pointer_lookup_at_index_ptr_new", - ) - .into() - } else { - result - } + // A recursive pointer in the loaded structure is stored as a `i64*`, but the loaded layout + // might want a more precise structure. As such, cast it to the refined type if needed. + cast_if_necessary_for_opaque_recursive_pointers(env.builder, result, target_loaded_type) } pub fn reserve_with_refcount<'a, 'ctx, 'env>( @@ -2181,7 +2154,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>( ) .into_pointer_value(); - let ptr_type = value_type.ptr_type(AddressSpace::Generic); + let ptr_type = value_type.ptr_type(AddressSpace::default()); env.builder .build_pointer_cast(ptr, ptr_type, "alloc_cast_to_desired") @@ -2406,7 +2379,7 @@ pub fn store_roc_value_opaque<'a, 'ctx, 'env>( value: BasicValueEnum<'ctx>, ) { let target_type = - basic_type_from_layout(env, layout_interner, layout).ptr_type(AddressSpace::Generic); + basic_type_from_layout(env, layout_interner, layout).ptr_type(AddressSpace::default()); let destination = env.builder .build_pointer_cast(opaque_destination, target_type, "store_roc_value_opaque"); @@ -2659,7 +2632,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( let basic_type = basic_type_from_layout(env, layout_interner, param.layout); let phi_type = if layout_interner.is_passed_by_reference(param.layout) { - basic_type.ptr_type(AddressSpace::Generic).into() + basic_type.ptr_type(AddressSpace::default()).into() } else { basic_type }; @@ -3043,6 +3016,24 @@ pub(crate) fn load_symbol_and_layout<'a, 'ctx, 'b>( } } +fn equivalent_type_constructors(t1: &BasicTypeEnum, t2: &BasicTypeEnum) -> bool { + use BasicTypeEnum::*; + match (t1, t2) { + (ArrayType(_), ArrayType(_)) => true, + (ArrayType(_), _) => false, + (FloatType(_), FloatType(_)) => true, + (FloatType(_), _) => false, + (IntType(_), IntType(_)) => true, + (IntType(_), _) => false, + (PointerType(_), PointerType(_)) => true, + (PointerType(_), _) => false, + (StructType(_), StructType(_)) => true, + (StructType(_), _) => false, + (VectorType(_), VectorType(_)) => true, + (VectorType(_), _) => false, + } +} + /// Cast a value to another value of the same size, but only if their types are not equivalent. /// This is needed to allow us to interoperate between recursive pointers in unions that are /// opaque, and well-typed. @@ -3054,7 +3045,10 @@ pub fn cast_if_necessary_for_opaque_recursive_pointers<'ctx>( from_value: BasicValueEnum<'ctx>, to_type: BasicTypeEnum<'ctx>, ) -> BasicValueEnum<'ctx> { - if from_value.get_type() != to_type { + if from_value.get_type() != to_type + // Only perform the cast if the target types are transumatble. + && equivalent_type_constructors(&from_value.get_type(), &to_type) + { complex_bitcast( builder, from_value, @@ -3202,7 +3196,7 @@ fn complex_bitcast_from_bigger_than_to<'ctx>( // then read it back as a different type let to_type_pointer = builder.build_pointer_cast( argument_pointer, - to_type.ptr_type(inkwell::AddressSpace::Generic), + to_type.ptr_type(inkwell::AddressSpace::default()), name, ); @@ -3225,7 +3219,7 @@ fn complex_bitcast_to_bigger_than_from<'ctx>( storage, from_value .get_type() - .ptr_type(inkwell::AddressSpace::Generic), + .ptr_type(inkwell::AddressSpace::default()), name, ); @@ -3586,7 +3580,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( argument_types.insert(0, output_type); } Some(return_type) => { - let output_type = return_type.ptr_type(AddressSpace::Generic); + let output_type = return_type.ptr_type(AddressSpace::default()); argument_types.insert(0, output_type.into()); } } @@ -3638,7 +3632,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( // bitcast the ptr let fastcc_ptr = env.builder.build_pointer_cast( arg.into_pointer_value(), - fastcc_type.ptr_type(AddressSpace::Generic), + fastcc_type.ptr_type(AddressSpace::default()), "bitcast_arg", ); @@ -3733,7 +3727,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( let return_type = wrapper_return_type; let c_function_spec = { - let output_type = return_type.ptr_type(AddressSpace::Generic); + let output_type = return_type.ptr_type(AddressSpace::default()); argument_types.push(output_type.into()); FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types) }; @@ -3842,6 +3836,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( Some(env.context.i64_type().as_basic_type_enum()), &[], ); + let size_function_name: String = format!("roc__{}_size", ident_string); let size_function = add_func( @@ -3897,7 +3892,10 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>( let c_abi_roc_str_type = env.context.struct_type( &[ - env.context.i8_type().ptr_type(AddressSpace::Generic).into(), + env.context + .i8_type() + .ptr_type(AddressSpace::default()) + .into(), env.ptr_int().into(), env.ptr_int().into(), ], @@ -3955,20 +3953,40 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>( (RocReturn::ByPointer, CCReturn::Return) => { // Roc currently puts the return pointer at the end of the argument list. // As such, we drop the last element here instead of the first. - (¶ms[..], ¶m_types[..param_types.len() - 1]) + ( + ¶ms[..], + ¶m_types[..param_types.len().saturating_sub(1)], + ) } // Drop the return pointer the other way, if the C function returns by pointer but Roc // doesn't (RocReturn::Return, CCReturn::ByPointer) => (¶ms[1..], ¶m_types[..]), (RocReturn::ByPointer, CCReturn::ByPointer) => { // Both return by pointer but Roc puts it at the end and C puts it at the beginning - (¶ms[1..], ¶m_types[..param_types.len() - 1]) + ( + ¶ms[1..], + ¶m_types[..param_types.len().saturating_sub(1)], + ) + } + (RocReturn::Return, CCReturn::Void) => { + // the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`. + // In C, this is modelled as a function returning void + (¶ms[..], ¶m_types[..]) + } + (RocReturn::ByPointer, CCReturn::Void) => { + // the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`. + // In C, this is modelled as a function returning void + ( + ¶ms[..], + ¶m_types[..param_types.len().saturating_sub(1)], + ) } _ => (¶ms[..], ¶m_types[..]), }; - debug_assert!( - params.len() == param_types.len(), + debug_assert_eq!( + params.len(), + param_types.len(), "when exposing a function to the host, params.len() was {}, but param_types.len() was {}", params.len(), param_types.len() @@ -4016,7 +4034,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>( // bitcast the ptr let fastcc_ptr = env.builder.build_pointer_cast( arg.into_pointer_value(), - fastcc_type.ptr_type(AddressSpace::Generic), + fastcc_type.ptr_type(AddressSpace::default()), "bitcast_arg", ); @@ -4102,7 +4120,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( ) } - LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {} + LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {} } // a generic version that writes the result into a passed *u8 pointer @@ -4131,7 +4149,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( Some(env.context.i64_type().as_basic_type_enum()), &[], ); - let size_function_name: String = format!("roc__{}_size", ident_string); + let size_function_name: String = format!("{}_size", c_function_name); let size_function = add_func( env.context, @@ -4155,7 +4173,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into() } - LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => { + LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => { basic_type_from_layout(env, layout_interner, return_layout) } }; @@ -4193,7 +4211,7 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu env.builder.build_pointer_cast( global.as_pointer_value(), - env.context.i32_type().ptr_type(AddressSpace::Generic), + env.context.i32_type().ptr_type(AddressSpace::default()), "cast_sjlj_buffer", ) } @@ -4210,12 +4228,12 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu let buf_type = env .context .i8_type() - .ptr_type(AddressSpace::Generic) + .ptr_type(AddressSpace::default()) .array_type(5); let jmp_buf_i8p_arr = env.builder.build_pointer_cast( jmp_buf, - buf_type.ptr_type(AddressSpace::Generic), + buf_type.ptr_type(AddressSpace::default()), "jmp_buf [5 x i8*]", ); @@ -4256,7 +4274,7 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu .builder .build_pointer_cast( jmp_buf, - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i8_type().ptr_type(AddressSpace::default()), "jmp_buf i8*", ) .into(); @@ -4413,7 +4431,7 @@ fn roc_call_result_type<'a, 'ctx, 'env>( env.context.struct_type( &[ env.context.i64_type().into(), - zig_str_type(env).ptr_type(AddressSpace::Generic).into(), + zig_str_type(env).ptr_type(AddressSpace::default()).into(), return_type, ], false, @@ -4489,7 +4507,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( basic_type_from_layout(env, layout_interner, return_layout), ); - // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into()); + // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::default()).into()); // let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false); let wrapper_function_spec = FunctionSpec::cconv( @@ -4591,8 +4609,9 @@ pub fn build_procedures<'a, 'ctx, 'env>( procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, entry_point: EntryPoint<'a>, debug_output_file: Option<&Path>, + glue_layouts: &GlueLayouts<'a>, ) { - build_procedures_help( + let mod_solutions = build_procedures_help( env, layout_interner, opt_level, @@ -4600,6 +4619,43 @@ pub fn build_procedures<'a, 'ctx, 'env>( entry_point, debug_output_file, ); + + let niche = Niche::NONE; + + for (symbol, top_level) in glue_layouts.getters.iter().copied() { + let it = top_level.arguments.iter().copied(); + let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, niche, top_level.result); + let func_name = FuncName(&bytes); + let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); + + let mut it = func_solutions.specs(); + let Some(func_spec) = it.next() else { + // TODO this means a function was not considered host-exposed in mono + continue; + }; + debug_assert!( + it.next().is_none(), + "we expect only one specialization of this symbol" + ); + + // NOTE fake layout; it is only used for debug prints + let getter_fn = + function_value_by_func_spec(env, *func_spec, symbol, &[], niche, Layout::UNIT); + + let name = getter_fn.get_name().to_str().unwrap(); + let getter_name = symbol.as_str(&env.interns); + + // Add the getter function to the module. + let _ = expose_function_to_host_help_c_abi( + env, + layout_interner, + name, + getter_fn, + top_level.arguments, + top_level.result, + getter_name, + ); + } } pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>( @@ -4919,28 +4975,23 @@ fn expose_alias_to_host<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, mod_solutions: &'a ModSolutions, - proc_name: LambdaName, + fn_name: &str, alias_symbol: Symbol, - exposed_function_symbol: Symbol, - top_level: ProcLayout<'a>, - layout: RawFunctionLayout<'a>, + hels: &HostExposedLambdaSet<'a>, ) { - let ident_string = proc_name.name().as_str(&env.interns); - let fn_name: String = format!("{}_1", ident_string); - - match layout { + match hels.raw_function_layout { RawFunctionLayout::Function(arguments, closure, result) => { // define closure size and return value size, e.g. // // * roc__mainForHost_1_Update_size() -> i64 // * roc__mainForHost_1_Update_result_size() -> i64 - let it = top_level.arguments.iter().copied(); + let it = hels.proc_layout.arguments.iter().copied(); let bytes = roc_alias_analysis::func_name_bytes_help( - exposed_function_symbol, + hels.symbol, it, Niche::NONE, - top_level.result, + hels.proc_layout.result, ); let func_name = FuncName(&bytes); let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); @@ -4956,24 +5007,24 @@ fn expose_alias_to_host<'a, 'ctx, 'env>( function_value_by_func_spec( env, *func_spec, - exposed_function_symbol, - top_level.arguments, + hels.symbol, + hels.proc_layout.arguments, Niche::NONE, - top_level.result, + hels.proc_layout.result, ) } None => { // morphic did not generate a specialization for this function, // therefore it must actually be unused. // An example is our closure callers - panic!("morphic did not specialize {:?}", exposed_function_symbol); + panic!("morphic did not specialize {:?}", hels.symbol); } }; build_closure_caller( env, layout_interner, - &fn_name, + fn_name, evaluator, alias_symbol, arguments, @@ -4992,7 +5043,7 @@ fn expose_alias_to_host<'a, 'ctx, 'env>( build_host_exposed_alias_size_help( env, - &fn_name, + fn_name, alias_symbol, Some("result"), result_type, @@ -5016,7 +5067,7 @@ fn build_closure_caller<'a, 'ctx, 'env>( for layout in arguments { let arg_type = basic_type_from_layout(env, layout_interner, *layout); - let arg_ptr_type = arg_type.ptr_type(AddressSpace::Generic); + let arg_ptr_type = arg_type.ptr_type(AddressSpace::default()); argument_types.push(arg_ptr_type.into()); } @@ -5025,7 +5076,7 @@ fn build_closure_caller<'a, 'ctx, 'env>( let basic_type = basic_type_from_layout(env, layout_interner, lambda_set.runtime_representation()); - basic_type.ptr_type(AddressSpace::Generic) + basic_type.ptr_type(AddressSpace::default()) }; argument_types.push(closure_argument_type.into()); @@ -5034,18 +5085,13 @@ fn build_closure_caller<'a, 'ctx, 'env>( let result_type = basic_type_from_layout(env, layout_interner, result); - let output_type = { result_type.ptr_type(AddressSpace::Generic) }; + let output_type = { result_type.ptr_type(AddressSpace::default()) }; argument_types.push(output_type.into()); // STEP 1: build function header - // e.g. `roc__main_1_Fx_caller` - let function_name = format!( - "roc__{}_{}_{}_caller", - def_name, - alias_symbol.module_string(&env.interns), - alias_symbol.as_str(&env.interns) - ); + // e.g. `roc__mainForHost_0_caller` (def_name is `mainForHost_0`) + let function_name = format!("roc__{}_caller", def_name); let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types); @@ -5156,7 +5202,7 @@ fn build_host_exposed_alias_size<'a, 'r, 'ctx, 'env>( fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( env: &'a Env<'a, 'ctx, 'env>, def_name: &str, - alias_symbol: Symbol, + _alias_symbol: Symbol, opt_label: Option<&str>, basic_type: BasicTypeEnum<'ctx>, ) { @@ -5166,20 +5212,9 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( 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", - def_name, - alias_symbol.module_string(&env.interns), - alias_symbol.as_str(&env.interns), - label - ) + format!("roc__{}_{}_size", def_name, label) } else { - format!( - "roc__{}_{}_{}_size", - def_name, - alias_symbol.module_string(&env.interns), - alias_symbol.as_str(&env.interns) - ) + format!("roc__{}_size", def_name,) }; let size_function = add_func( @@ -5198,7 +5233,7 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( builder.build_return(Some(&size)); } -pub fn build_proc<'a, 'ctx, 'env>( +fn build_proc<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, mod_solutions: &'a ModSolutions, @@ -5219,17 +5254,18 @@ pub fn build_proc<'a, 'ctx, 'env>( GenTest | WasmGenTest | CliTest => { /* no host, or exposing types is not supported */ } - Binary | BinaryDev => { - for (alias_name, (generated_function, top_level, layout)) in aliases.iter() { + Binary | BinaryDev | BinaryGlue => { + for (alias_name, hels) in aliases.iter() { + let ident_string = proc.name.name().as_str(&env.interns); + let fn_name: String = format!("{}_{}", ident_string, hels.id.0); + expose_alias_to_host( env, layout_interner, mod_solutions, - proc.name, + &fn_name, *alias_name, - *generated_function, - *top_level, - *layout, + hels, ) } } @@ -5550,7 +5586,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>( basic_type_from_builtin(env, builtin) } Builtin::Str | Builtin::List(_) => { - let address_space = AddressSpace::Generic; + let address_space = AddressSpace::default(); let field_types: [BasicTypeEnum; 3] = [ env.context.i8_type().ptr_type(address_space).into(), env.ptr_int().into(), @@ -5665,7 +5701,7 @@ impl<'ctx> 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.unwrap().ptr_type(AddressSpace::Generic); + let output_type = return_type.unwrap().ptr_type(AddressSpace::default()); let mut arguments: Vec<'_, BasicTypeEnum> = bumpalo::vec![in env.arena; output_type.into()]; @@ -5683,6 +5719,8 @@ impl<'ctx> FunctionSpec<'ctx> { (return_type.unwrap().fn_type(&arguments, false), None) } CCReturn::Void => { + // NOTE: there may be a valid return type, but it is zero-sized. + // for instance just `{}` or something more complex like `{ { {}, {} }, {} }` let arguments = function_arguments(env, argument_types); (env.context.void_type().fn_type(&arguments, false), None) } @@ -5707,7 +5745,7 @@ impl<'ctx> FunctionSpec<'ctx> { return_type.fn_type(&function_arguments(env, &argument_types), false) } RocReturn::ByPointer => { - argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + argument_types.push(return_type.ptr_type(AddressSpace::default()).into()); env.context .void_type() .fn_type(&function_arguments(env, &argument_types), false) @@ -5955,7 +5993,7 @@ fn define_global_str_literal_ptr<'a, 'ctx, 'env>( let ptr = env.builder.build_pointer_cast( global.as_pointer_value(), - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i8_type().ptr_type(AddressSpace::default()), "to_opaque", ); @@ -6090,7 +6128,7 @@ pub fn add_func<'ctx>( ) -> FunctionValue<'ctx> { if cfg!(debug_assertions) { if let Some(func) = module.get_function(name) { - panic!("Attempting to redefine LLVM function {}, which was already defined in this module as:\n\n{:?}", name, func); + panic!("Attempting to redefine LLVM function {}, which was already defined in this module as:\n\n{:#?}", name, func); } } @@ -6100,23 +6138,3 @@ pub fn add_func<'ctx>( fn_val } - -#[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) enum WhenRecursive<'a> { - Unreachable, - Loop(UnionLayout<'a>), -} - -impl<'a> WhenRecursive<'a> { - pub fn unwrap_recursive_pointer(&self, layout: Layout<'a>) -> Layout<'a> { - match layout { - Layout::RecursivePointer(_) => match self { - WhenRecursive::Loop(lay) => Layout::Union(*lay), - WhenRecursive::Unreachable => { - internal_error!("cannot compare recursive pointers outside of a structure") - } - }, - _ => layout, - } - } -} diff --git a/crates/compiler/gen_llvm/src/llvm/build_list.rs b/crates/compiler/gen_llvm/src/llvm/build_list.rs index 33eb9e0c47..fd8ebfd0fe 100644 --- a/crates/compiler/gen_llvm/src/llvm/build_list.rs +++ b/crates/compiler/gen_llvm/src/llvm/build_list.rs @@ -74,7 +74,7 @@ fn pass_element_as_opaque<'a, 'ctx, 'env>( env.builder .build_pointer_cast( element_ptr, - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i8_type().ptr_type(AddressSpace::default()), "pass_element_as_opaque", ) .into() @@ -97,7 +97,7 @@ pub(crate) fn pass_as_opaque<'a, 'ctx, 'env>( env.builder .build_pointer_cast( ptr, - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i8_type().ptr_type(AddressSpace::default()), "pass_as_opaque", ) .into() @@ -133,7 +133,7 @@ pub(crate) fn list_get_unsafe<'a, 'ctx, 'env>( let builder = env.builder; let elem_type = basic_type_from_layout(env, layout_interner, element_layout); - let ptr_type = elem_type.ptr_type(AddressSpace::Generic); + let ptr_type = elem_type.ptr_type(AddressSpace::default()); // Load the pointer to the array data let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type); @@ -183,6 +183,26 @@ pub(crate) fn list_reserve<'a, 'ctx, 'env>( ) } +/// List.releaseExcessCapacity : List elem -> List elem +pub(crate) fn list_release_excess_capacity<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_interner: &mut STLayoutInterner<'a>, + list: BasicValueEnum<'ctx>, + element_layout: InLayout<'a>, + update_mode: UpdateMode, +) -> BasicValueEnum<'ctx> { + call_list_bitcode_fn_1( + env, + list.into_struct_value(), + &[ + env.alignment_intvalue(layout_interner, element_layout), + layout_width(env, layout_interner, element_layout), + pass_update_mode(env, update_mode), + ], + bitcode::LIST_RELEASE_EXCESS_CAPACITY, + ) +} + /// List.appendUnsafe : List elem, elem -> List elem pub(crate) fn list_append_unsafe<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -393,17 +413,36 @@ pub(crate) fn list_len<'ctx>( .into_int_value() } -/// List.capacity : List * -> Nat -pub(crate) fn list_capacity<'ctx>( +pub(crate) fn list_capacity_or_ref_ptr<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, ) -> IntValue<'ctx> { builder - .build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "list_capacity") + .build_extract_value( + wrapper_struct, + Builtin::WRAPPER_CAPACITY, + "list_capacity_or_ref_ptr", + ) .unwrap() .into_int_value() } +// Gets a pointer to just after the refcount for a list or seamless slice. +// The value is just after the refcount so that normal lists and seamless slices can share code paths easily. +pub(crate) fn list_refcount_ptr<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + wrapper_struct: StructValue<'ctx>, +) -> PointerValue<'ctx> { + call_list_bitcode_fn( + env, + &[wrapper_struct], + &[], + BitcodeReturns::Basic, + bitcode::LIST_REFCOUNT_PTR, + ) + .into_pointer_value() +} + pub(crate) fn destructure<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, @@ -801,11 +840,7 @@ pub(crate) fn decref<'a, 'ctx, 'env>( wrapper_struct: StructValue<'ctx>, alignment: u32, ) { - let (_, pointer) = load_list( - env.builder, - wrapper_struct, - env.context.i8_type().ptr_type(AddressSpace::Generic), - ); + let refcount_ptr = list_refcount_ptr(env, wrapper_struct); - crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment); + crate::llvm::refcounting::decref_pointer_check_null(env, refcount_ptr, alignment); } diff --git a/crates/compiler/gen_llvm/src/llvm/build_str.rs b/crates/compiler/gen_llvm/src/llvm/build_str.rs index faebc9cac0..ab824c8b14 100644 --- a/crates/compiler/gen_llvm/src/llvm/build_str.rs +++ b/crates/compiler/gen_llvm/src/llvm/build_str.rs @@ -32,7 +32,7 @@ pub(crate) fn decode_from_utf8_result<'a, 'ctx, 'env>( PtrWidth::Bytes4 | PtrWidth::Bytes8 => { let result_ptr_cast = env.builder.build_pointer_cast( pointer, - record_type.ptr_type(AddressSpace::Generic), + record_type.ptr_type(AddressSpace::default()), "to_unnamed", ); @@ -63,3 +63,19 @@ pub(crate) fn str_equal<'a, 'ctx, 'env>( bitcode::STR_EQUAL, ) } + +// Gets a pointer to just after the refcount for a list or seamless slice. +// The value is just after the refcount so that normal lists and seamless slices can share code paths easily. +pub(crate) fn str_refcount_ptr<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + value: BasicValueEnum<'ctx>, +) -> PointerValue<'ctx> { + call_str_bitcode_fn( + env, + &[value], + &[], + BitcodeReturns::Basic, + bitcode::STR_REFCOUNT_PTR, + ) + .into_pointer_value() +} diff --git a/crates/compiler/gen_llvm/src/llvm/compare.rs b/crates/compiler/gen_llvm/src/llvm/compare.rs index b284386fa7..77847130b3 100644 --- a/crates/compiler/gen_llvm/src/llvm/compare.rs +++ b/crates/compiler/gen_llvm/src/llvm/compare.rs @@ -1,17 +1,14 @@ -use crate::llvm::build::{ - get_tag_id, tag_pointer_clear_tag_id, Env, WhenRecursive, FAST_CALL_CONV, -}; +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; use crate::llvm::convert::basic_type_from_layout; use bumpalo::collections::Vec; use inkwell::types::BasicType; -use inkwell::values::{ - BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, -}; +use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::{AddressSpace, FloatPredicate, IntPredicate}; use roc_builtins::bitcode; use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::layout::{ Builtin, InLayout, Layout, LayoutIds, LayoutInterner, STLayoutInterner, UnionLayout, @@ -38,7 +35,6 @@ pub fn generic_eq<'a, 'ctx, 'env>( rhs_val, lhs_layout, rhs_layout, - WhenRecursive::Unreachable, ) } @@ -59,7 +55,6 @@ pub fn generic_neq<'a, 'ctx, 'env>( rhs_val, lhs_layout, rhs_layout, - WhenRecursive::Unreachable, ) } @@ -69,8 +64,8 @@ fn build_eq_builtin<'a, 'ctx, 'env>( layout_ids: &mut LayoutIds<'a>, lhs_val: BasicValueEnum<'ctx>, rhs_val: BasicValueEnum<'ctx>, + builtin_layout: InLayout<'a>, builtin: &Builtin<'a>, - when_recursive: WhenRecursive<'a>, ) -> BasicValueEnum<'ctx> { let int_cmp = |pred, label| { let int_val = env.builder.build_int_compare( @@ -129,19 +124,15 @@ fn build_eq_builtin<'a, 'ctx, 'env>( 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) => { - let list_layout = layout_interner.insert(Layout::Builtin(*builtin)); - build_list_eq( - env, - layout_interner, - layout_ids, - list_layout, - *elem, - lhs_val.into_struct_value(), - rhs_val.into_struct_value(), - when_recursive, - ) - } + Builtin::List(elem) => build_list_eq( + env, + layout_interner, + layout_ids, + builtin_layout, + *elem, + lhs_val.into_struct_value(), + rhs_val.into_struct_value(), + ), } } @@ -153,7 +144,6 @@ fn build_eq<'a, 'ctx, 'env>( rhs_val: BasicValueEnum<'ctx>, lhs_layout: InLayout<'a>, rhs_layout: InLayout<'a>, - when_recursive: WhenRecursive<'a>, ) -> BasicValueEnum<'ctx> { let lhs_layout = &layout_interner.runtime_representation_in(lhs_layout); let rhs_layout = &layout_interner.runtime_representation_in(rhs_layout); @@ -171,16 +161,16 @@ fn build_eq<'a, 'ctx, 'env>( layout_ids, lhs_val, rhs_val, + *lhs_layout, &builtin, - when_recursive, ), Layout::Struct { field_layouts, .. } => build_struct_eq( env, layout_interner, layout_ids, + *lhs_layout, field_layouts, - when_recursive, lhs_val.into_struct_value(), rhs_val.into_struct_value(), ), @@ -191,7 +181,7 @@ fn build_eq<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, + *lhs_layout, &union_layout, lhs_val, rhs_val, @@ -201,47 +191,48 @@ fn build_eq<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, *lhs_layout, inner_layout, lhs_val, rhs_val, ), - Layout::RecursivePointer(_) => match when_recursive { - WhenRecursive::Unreachable => { - unreachable!("recursion pointers should never be compared directly") - } + Layout::RecursivePointer(rec_layout) => { + let layout = rec_layout; - WhenRecursive::Loop(union_layout) => { - let layout = layout_interner.insert(Layout::Union(union_layout)); + let bt = basic_type_from_layout(env, layout_interner, layout); - let bt = basic_type_from_layout(env, layout_interner, layout); + // cast the i64 pointer to a pointer to block of memory + let field1_cast = env.builder.build_pointer_cast( + lhs_val.into_pointer_value(), + bt.into_pointer_type(), + "i64_to_opaque", + ); - // cast the i64 pointer to a pointer to block of memory - let field1_cast = env.builder.build_pointer_cast( - lhs_val.into_pointer_value(), - bt.into_pointer_type(), - "i64_to_opaque", - ); + let field2_cast = env.builder.build_pointer_cast( + rhs_val.into_pointer_value(), + bt.into_pointer_type(), + "i64_to_opaque", + ); - let field2_cast = env.builder.build_pointer_cast( - rhs_val.into_pointer_value(), - bt.into_pointer_type(), - "i64_to_opaque", - ); + let union_layout = match layout_interner.get(rec_layout) { + Layout::Union(union_layout) => { + debug_assert!(!matches!(union_layout, UnionLayout::NonRecursive(..))); + union_layout + } + _ => internal_error!(), + }; - build_tag_eq( - env, - layout_interner, - layout_ids, - WhenRecursive::Loop(union_layout), - &union_layout, - field1_cast.into(), - field2_cast.into(), - ) - } - }, + build_tag_eq( + env, + layout_interner, + layout_ids, + rec_layout, + &union_layout, + field1_cast.into(), + field2_cast.into(), + ) + } } } @@ -251,8 +242,8 @@ fn build_neq_builtin<'a, 'ctx, 'env>( layout_ids: &mut LayoutIds<'a>, lhs_val: BasicValueEnum<'ctx>, rhs_val: BasicValueEnum<'ctx>, + builtin_layout: InLayout<'a>, builtin: &Builtin<'a>, - when_recursive: WhenRecursive<'a>, ) -> BasicValueEnum<'ctx> { let int_cmp = |pred, label| { let int_val = env.builder.build_int_compare( @@ -317,7 +308,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>( result.into() } Builtin::List(elem) => { - let builtin_layout = layout_interner.insert(Layout::Builtin(*builtin)); let is_equal = build_list_eq( env, layout_interner, @@ -326,7 +316,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>( *elem, lhs_val.into_struct_value(), rhs_val.into_struct_value(), - when_recursive, ) .into_int_value(); @@ -345,7 +334,6 @@ fn build_neq<'a, 'ctx, 'env>( rhs_val: BasicValueEnum<'ctx>, lhs_layout: InLayout<'a>, rhs_layout: InLayout<'a>, - when_recursive: WhenRecursive<'a>, ) -> BasicValueEnum<'ctx> { if lhs_layout != rhs_layout { panic!( @@ -361,8 +349,8 @@ fn build_neq<'a, 'ctx, 'env>( layout_ids, lhs_val, rhs_val, + lhs_layout, &builtin, - when_recursive, ), Layout::Struct { field_layouts, .. } => { @@ -370,8 +358,8 @@ fn build_neq<'a, 'ctx, 'env>( env, layout_interner, layout_ids, + lhs_layout, field_layouts, - when_recursive, lhs_val.into_struct_value(), rhs_val.into_struct_value(), ) @@ -387,7 +375,7 @@ fn build_neq<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, + lhs_layout, &union_layout, lhs_val, rhs_val, @@ -404,7 +392,6 @@ fn build_neq<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, lhs_layout, inner_layout, lhs_val, @@ -432,15 +419,17 @@ fn build_list_eq<'a, 'ctx, 'env>( element_layout: InLayout<'a>, list1: StructValue<'ctx>, list2: StructValue<'ctx>, - when_recursive: WhenRecursive<'a>, ) -> BasicValueEnum<'ctx> { let block = env.builder.get_insert_block().expect("to be in a function"); let di_location = env.builder.get_current_debug_location().unwrap(); let symbol = Symbol::LIST_EQ; - let element_layout = layout_interner.get(element_layout); - let element_layout = when_recursive.unwrap_recursive_pointer(element_layout); - let element_layout = layout_interner.insert(element_layout); + let element_layout = if let Layout::RecursivePointer(rec) = layout_interner.get(element_layout) + { + rec + } else { + element_layout + }; let fn_name = layout_ids .get(symbol, &element_layout) .to_symbol_string(symbol, &env.interns); @@ -461,7 +450,6 @@ fn build_list_eq<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, function_value, element_layout, ); @@ -471,8 +459,7 @@ fn build_list_eq<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); let call = env .builder .build_call(function, &[list1.into(), list2.into()], "list_eq"); @@ -486,7 +473,6 @@ fn build_list_eq_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, - when_recursive: WhenRecursive<'a>, parent: FunctionValue<'ctx>, element_layout: InLayout<'a>, ) { @@ -511,7 +497,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(ctx, loc); + builder.set_current_debug_location(loc); } // Add args to scope @@ -548,7 +534,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>( let builder = env.builder; let element_type = basic_type_from_layout(env, layout_interner, element_layout); - let ptr_type = element_type.ptr_type(AddressSpace::Generic); + let ptr_type = element_type.ptr_type(AddressSpace::default()); let ptr1 = load_list_ptr(env.builder, list1, ptr_type); let ptr2 = load_list_ptr(env.builder, list2, ptr_type); @@ -605,7 +591,6 @@ fn build_list_eq_help<'a, 'ctx, 'env>( elem2, element_layout, element_layout, - when_recursive, ) .into_int_value(); @@ -646,16 +631,14 @@ fn build_struct_eq<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, + struct_layout: InLayout<'a>, field_layouts: &'a [InLayout<'a>], - when_recursive: WhenRecursive<'a>, struct1: StructValue<'ctx>, struct2: StructValue<'ctx>, ) -> BasicValueEnum<'ctx> { let block = env.builder.get_insert_block().expect("to be in a function"); let di_location = env.builder.get_current_debug_location().unwrap(); - let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts)); - let symbol = Symbol::GENERIC_EQ; let fn_name = layout_ids .get(symbol, &struct_layout) @@ -678,7 +661,6 @@ fn build_struct_eq<'a, 'ctx, 'env>( layout_interner, layout_ids, function_value, - when_recursive, field_layouts, ); @@ -687,8 +669,7 @@ fn build_struct_eq<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); let call = env .builder .build_call(function, &[struct1.into(), struct2.into()], "struct_eq"); @@ -703,7 +684,6 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, parent: FunctionValue<'ctx>, - when_recursive: WhenRecursive<'a>, field_layouts: &[InLayout<'a>], ) { let ctx = env.context; @@ -727,7 +707,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(ctx, loc); + builder.set_current_debug_location(loc); } // Add args to scope @@ -761,42 +741,40 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( .build_extract_value(struct2, index as u32, "eq_field") .unwrap(); - let are_equal = if let Layout::RecursivePointer(_) = layout_interner.get(*field_layout) { - match &when_recursive { - WhenRecursive::Unreachable => { - unreachable!("The current layout should not be recursive, but is") - } - WhenRecursive::Loop(union_layout) => { - let field_layout = layout_interner.insert(Layout::Union(*union_layout)); + let are_equal = if let Layout::RecursivePointer(rec_layout) = + layout_interner.get(*field_layout) + { + debug_assert!( + matches!(layout_interner.get(rec_layout), Layout::Union(union_layout) if !matches!(union_layout, UnionLayout::NonRecursive(..))) + ); - let bt = basic_type_from_layout(env, layout_interner, field_layout); + let field_layout = rec_layout; - // cast the i64 pointer to a pointer to block of memory - let field1_cast = env.builder.build_pointer_cast( - field1.into_pointer_value(), - bt.into_pointer_type(), - "i64_to_opaque", - ); + let bt = basic_type_from_layout(env, layout_interner, field_layout); - let field2_cast = env.builder.build_pointer_cast( - field2.into_pointer_value(), - bt.into_pointer_type(), - "i64_to_opaque", - ); + // cast the i64 pointer to a pointer to block of memory + let field1_cast = env.builder.build_pointer_cast( + field1.into_pointer_value(), + bt.into_pointer_type(), + "i64_to_opaque", + ); - build_eq( - env, - layout_interner, - layout_ids, - field1_cast.into(), - field2_cast.into(), - field_layout, - field_layout, - WhenRecursive::Loop(*union_layout), - ) - .into_int_value() - } - } + let field2_cast = env.builder.build_pointer_cast( + field2.into_pointer_value(), + bt.into_pointer_type(), + "i64_to_opaque", + ); + + build_eq( + env, + layout_interner, + layout_ids, + field1_cast.into(), + field2_cast.into(), + field_layout, + field_layout, + ) + .into_int_value() } else { let lhs = use_roc_value(env, layout_interner, *field_layout, field1, "field1"); let rhs = use_roc_value(env, layout_interner, *field_layout, field2, "field2"); @@ -808,7 +786,6 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( rhs, *field_layout, *field_layout, - when_recursive, ) .into_int_value() }; @@ -839,7 +816,7 @@ fn build_tag_eq<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, - when_recursive: WhenRecursive<'a>, + tag_layout: InLayout<'a>, union_layout: &UnionLayout<'a>, tag1: BasicValueEnum<'ctx>, tag2: BasicValueEnum<'ctx>, @@ -847,7 +824,6 @@ fn build_tag_eq<'a, 'ctx, 'env>( let block = env.builder.get_insert_block().expect("to be in a function"); let di_location = env.builder.get_current_debug_location().unwrap(); - let tag_layout = layout_interner.insert(Layout::Union(*union_layout)); let symbol = Symbol::GENERIC_EQ; let fn_name = layout_ids .get(symbol, &tag_layout) @@ -869,7 +845,6 @@ fn build_tag_eq<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, function_value, union_layout, ); @@ -879,8 +854,7 @@ fn build_tag_eq<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); let call = env .builder .build_call(function, &[tag1.into(), tag2.into()], "tag_eq"); @@ -894,7 +868,6 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, - when_recursive: WhenRecursive<'a>, parent: FunctionValue<'ctx>, union_layout: &UnionLayout<'a>, ) { @@ -919,7 +892,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(ctx, loc); + builder.set_current_debug_location(loc); } // Add args to scope @@ -999,12 +972,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); + let struct_layout = + layout_interner.insert(Layout::struct_no_name_order(field_layouts)); + let answer = eq_ptr_to_struct( env, layout_interner, layout_ids, - union_layout, - Some(when_recursive), + struct_layout, field_layouts, tag1, tag2, @@ -1070,12 +1045,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); + let struct_layout = + layout_interner.insert(Layout::struct_no_name_order(field_layouts)); + let answer = eq_ptr_to_struct( env, layout_interner, layout_ids, - union_layout, - None, + struct_layout, field_layouts, tag1, tag2, @@ -1131,12 +1108,13 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( env.builder.position_at_end(compare_other); + let struct_layout = layout_interner.insert(Layout::struct_no_name_order(other_fields)); + let answer = eq_ptr_to_struct( env, layout_interner, layout_ids, - union_layout, - None, + struct_layout, other_fields, tag1.into_pointer_value(), tag2.into_pointer_value(), @@ -1229,12 +1207,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); + let struct_layout = + layout_interner.insert(Layout::struct_no_name_order(field_layouts)); + let answer = eq_ptr_to_struct( env, layout_interner, layout_ids, - union_layout, - None, + struct_layout, field_layouts, tag1, tag2, @@ -1268,12 +1248,13 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( env.builder.position_at_end(compare_fields); + let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts)); + let answer = eq_ptr_to_struct( env, layout_interner, layout_ids, - union_layout, - None, + struct_layout, field_layouts, tag1.into_pointer_value(), tag2.into_pointer_value(), @@ -1288,27 +1269,24 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, - union_layout: &UnionLayout<'a>, - opt_when_recursive: Option>, + struct_layout: InLayout<'a>, field_layouts: &'a [InLayout<'a>], tag1: PointerValue<'ctx>, tag2: PointerValue<'ctx>, ) -> IntValue<'ctx> { - let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts)); - let wrapper_type = basic_type_from_layout(env, layout_interner, struct_layout); debug_assert!(wrapper_type.is_struct_type()); // cast the opaque pointer to a pointer of the correct shape let struct1_ptr = env.builder.build_pointer_cast( tag1, - wrapper_type.ptr_type(AddressSpace::Generic), + wrapper_type.ptr_type(AddressSpace::default()), "opaque_to_correct", ); let struct2_ptr = env.builder.build_pointer_cast( tag2, - wrapper_type.ptr_type(AddressSpace::Generic), + wrapper_type.ptr_type(AddressSpace::default()), "opaque_to_correct", ); @@ -1326,8 +1304,8 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>( env, layout_interner, layout_ids, + struct_layout, field_layouts, - opt_when_recursive.unwrap_or(WhenRecursive::Loop(*union_layout)), struct1, struct2, ) @@ -1340,7 +1318,6 @@ fn build_box_eq<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, - when_recursive: WhenRecursive<'a>, box_layout: InLayout<'a>, inner_layout: InLayout<'a>, tag1: BasicValueEnum<'ctx>, @@ -1370,7 +1347,6 @@ fn build_box_eq<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, function_value, inner_layout, ); @@ -1380,8 +1356,7 @@ fn build_box_eq<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); let call = env .builder .build_call(function, &[tag1.into(), tag2.into()], "tag_eq"); @@ -1395,7 +1370,6 @@ fn build_box_eq_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, - when_recursive: WhenRecursive<'a>, parent: FunctionValue<'ctx>, inner_layout: InLayout<'a>, ) { @@ -1420,7 +1394,7 @@ fn build_box_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(ctx, loc); + builder.set_current_debug_location(loc); } // Add args to scope @@ -1472,7 +1446,6 @@ fn build_box_eq_help<'a, 'ctx, 'env>( value2, inner_layout, inner_layout, - when_recursive, ); env.builder.build_return(Some(&is_equal)); diff --git a/crates/compiler/gen_llvm/src/llvm/convert.rs b/crates/compiler/gen_llvm/src/llvm/convert.rs index 7e9df11233..150b5bc8e8 100644 --- a/crates/compiler/gen_llvm/src/llvm/convert.rs +++ b/crates/compiler/gen_llvm/src/llvm/convert.rs @@ -18,7 +18,9 @@ fn basic_type_from_record<'a, 'ctx, 'env>( let mut field_types = Vec::with_capacity_in(fields.len(), env.arena); for field_layout in fields.iter() { - field_types.push(basic_type_from_layout(env, layout_interner, *field_layout)); + let typ = basic_type_from_layout(env, layout_interner, *field_layout); + + field_types.push(typ); } env.context @@ -44,13 +46,13 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( Boxed(inner_layout) => { let inner_type = basic_type_from_layout(env, layout_interner, inner_layout); - inner_type.ptr_type(AddressSpace::Generic).into() + inner_type.ptr_type(AddressSpace::default()).into() } Union(union_layout) => basic_type_from_union_layout(env, layout_interner, &union_layout), RecursivePointer(_) => env .context .i64_type() - .ptr_type(AddressSpace::Generic) + .ptr_type(AddressSpace::default()) .as_basic_type_enum(), Builtin(builtin) => basic_type_from_builtin(env, &builtin), @@ -65,6 +67,7 @@ pub fn struct_type_from_union_layout<'a, 'ctx, 'env>( use UnionLayout::*; match union_layout { + NonRecursive([]) => env.context.struct_type(&[], false), NonRecursive(tags) => { RocUnion::tagged_from_slices(layout_interner, env.context, tags, env.target_info) .struct_type() @@ -109,7 +112,7 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>( Recursive(_) | NonNullableUnwrapped(_) | NullableWrapped { .. } - | NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::Generic).into(), + | NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::default()).into(), } } @@ -158,7 +161,7 @@ pub fn argument_type_from_layout<'a, 'ctx, 'env>( let base = basic_type_from_layout(env, layout_interner, layout); if layout_interner.is_passed_by_reference(layout) { - base.ptr_type(AddressSpace::Generic).into() + base.ptr_type(AddressSpace::default()).into() } else { base } @@ -176,7 +179,7 @@ pub fn argument_type_from_union_layout<'a, 'ctx, 'env>( let heap_type = basic_type_from_union_layout(env, layout_interner, union_layout); if let UnionLayout::NonRecursive(_) = union_layout { - heap_type.ptr_type(AddressSpace::Generic).into() + heap_type.ptr_type(AddressSpace::default()).into() } else { heap_type } @@ -300,7 +303,8 @@ impl<'ctx> RocUnion<'ctx> { target_info: TargetInfo, ) -> Self { let tag_type = match layouts.len() { - 0..=255 => TagType::I8, + 0 => unreachable!("zero-element tag union is not represented as a RocUnion"), + 1..=255 => TagType::I8, _ => TagType::I16, }; @@ -322,6 +326,10 @@ impl<'ctx> RocUnion<'ctx> { Self::new(context, target_info, data_align, data_width, None) } + pub fn data_width(&self) -> u32 { + self.data_width + } + pub fn tag_alignment(&self) -> u32 { let tag_id_alignment = match self.tag_type { None => 0, @@ -375,7 +383,7 @@ impl<'ctx> RocUnion<'ctx> { let cast_pointer = env.builder.build_pointer_cast( data_buffer, - data.get_type().ptr_type(AddressSpace::Generic), + data.get_type().ptr_type(AddressSpace::default()), "to_data_ptr", ); @@ -436,7 +444,7 @@ pub fn zig_dec_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> { - let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::Generic); + let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::default()); env.context .struct_type(&[env.context.bool_type().into(), u8_ptr_t.into()], false) diff --git a/crates/compiler/gen_llvm/src/llvm/expect.rs b/crates/compiler/gen_llvm/src/llvm/expect.rs index 28d6a343d6..29ff21942d 100644 --- a/crates/compiler/gen_llvm/src/llvm/expect.rs +++ b/crates/compiler/gen_llvm/src/llvm/expect.rs @@ -9,6 +9,7 @@ use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue}; use inkwell::AddressSpace; use roc_builtins::bitcode; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_mono::ir::LookupType; use roc_mono::layout::{ @@ -19,7 +20,7 @@ use roc_region::all::Region; use super::build::BuilderExt; use super::build::{ add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, LlvmBackendMode, - Scope, WhenRecursive, + Scope, }; use super::convert::struct_type_from_union_layout; @@ -98,7 +99,7 @@ fn read_state<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, ptr: PointerValue<'ctx>, ) -> (IntValue<'ctx>, IntValue<'ctx>) { - let ptr_type = env.ptr_int().ptr_type(AddressSpace::Generic); + let ptr_type = env.ptr_int().ptr_type(AddressSpace::default()); let ptr = env.builder.build_pointer_cast(ptr, ptr_type, ""); let one = env.ptr_int().const_int(1, false); @@ -118,7 +119,7 @@ fn write_state<'a, 'ctx, 'env>( count: IntValue<'ctx>, offset: IntValue<'ctx>, ) { - let ptr_type = env.ptr_int().ptr_type(AddressSpace::Generic); + let ptr_type = env.ptr_int().ptr_type(AddressSpace::default()); let ptr = env.builder.build_pointer_cast(ptr, ptr_type, ""); let one = env.ptr_int().const_int(1, false); @@ -128,6 +129,15 @@ fn write_state<'a, 'ctx, 'env>( env.builder.build_store(offset_ptr, offset); } +fn offset_add<'ctx>( + builder: &Builder<'ctx>, + current: IntValue<'ctx>, + extra: u32, +) -> IntValue<'ctx> { + let intval = current.get_type().const_int(extra as _, false); + builder.build_int_add(current, intval, "offset_add") +} + pub(crate) fn notify_parent_expect(env: &Env, shared_memory: &SharedMemoryPointer) { let func = env .module @@ -220,7 +230,6 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>( cursors, value, layout, - WhenRecursive::Unreachable, ); offset = extra_offset; @@ -252,7 +261,7 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>( ) }; - let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::Generic); + let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::default()); let ptr = env .builder .build_pointer_cast(ptr, u32_ptr, "cast_ptr_type"); @@ -286,7 +295,6 @@ fn build_clone<'a, 'ctx, 'env>( cursors: Cursors<'ctx>, value: BasicValueEnum<'ctx>, layout: InLayout<'a>, - when_recursive: WhenRecursive<'a>, ) -> IntValue<'ctx> { match layout_interner.get(layout) { Layout::Builtin(builtin) => build_clone_builtin( @@ -297,7 +305,6 @@ fn build_clone<'a, 'ctx, 'env>( cursors, value, builtin, - when_recursive, ), Layout::Struct { field_layouts, .. } => build_clone_struct( @@ -308,7 +315,6 @@ fn build_clone<'a, 'ctx, 'env>( cursors, value, field_layouts, - when_recursive, ), // Since we will never actually display functions (and hence lambda sets) @@ -326,7 +332,7 @@ fn build_clone<'a, 'ctx, 'env>( ) }; - let ptr_type = value.get_type().ptr_type(AddressSpace::Generic); + let ptr_type = value.get_type().ptr_type(AddressSpace::default()); let ptr = env .builder .build_pointer_cast(ptr, ptr_type, "cast_ptr_type"); @@ -343,7 +349,6 @@ fn build_clone<'a, 'ctx, 'env>( cursors, value, union_layout, - WhenRecursive::Loop(union_layout), ) } } @@ -376,39 +381,39 @@ fn build_clone<'a, 'ctx, 'env>( cursors, value, inner_layout, - when_recursive, ) } - Layout::RecursivePointer(_) => match when_recursive { - WhenRecursive::Unreachable => { - unreachable!("recursion pointers should never be compared directly") - } + Layout::RecursivePointer(rec_layout) => { + let layout = rec_layout; - WhenRecursive::Loop(union_layout) => { - let layout = layout_interner.insert(Layout::Union(union_layout)); + let bt = basic_type_from_layout(env, layout_interner, layout); - let bt = basic_type_from_layout(env, layout_interner, layout); + // cast the i64 pointer to a pointer to block of memory + let field1_cast = env.builder.build_pointer_cast( + value.into_pointer_value(), + bt.into_pointer_type(), + "i64_to_opaque", + ); - // cast the i64 pointer to a pointer to block of memory - let field1_cast = env.builder.build_pointer_cast( - value.into_pointer_value(), - bt.into_pointer_type(), - "i64_to_opaque", - ); + let union_layout = match layout_interner.get(rec_layout) { + Layout::Union(union_layout) => { + debug_assert!(!matches!(union_layout, UnionLayout::NonRecursive(..))); + union_layout + } + _ => internal_error!(), + }; - build_clone_tag( - env, - layout_interner, - layout_ids, - ptr, - cursors, - field1_cast.into(), - union_layout, - WhenRecursive::Loop(union_layout), - ) - } - }, + build_clone_tag( + env, + layout_interner, + layout_ids, + ptr, + cursors, + field1_cast.into(), + union_layout, + ) + } } } @@ -420,7 +425,6 @@ fn build_clone_struct<'a, 'ctx, 'env>( cursors: Cursors<'ctx>, value: BasicValueEnum<'ctx>, field_layouts: &[InLayout<'a>], - when_recursive: WhenRecursive<'a>, ) -> IntValue<'ctx> { let layout = Layout::struct_no_name_order(field_layouts); @@ -447,7 +451,6 @@ fn build_clone_struct<'a, 'ctx, 'env>( cursors, field, *field_layout, - when_recursive, ); let field_width = env @@ -472,7 +475,6 @@ fn build_clone_tag<'a, 'ctx, 'env>( cursors: Cursors<'ctx>, value: BasicValueEnum<'ctx>, union_layout: UnionLayout<'a>, - when_recursive: WhenRecursive<'a>, ) -> IntValue<'ctx> { let layout = layout_interner.insert(Layout::Union(union_layout)); let layout_id = layout_ids.get(Symbol::CLONE, &layout); @@ -486,7 +488,10 @@ fn build_clone_tag<'a, 'ctx, 'env>( let function_type = env.ptr_int().fn_type( &[ - env.context.i8_type().ptr_type(AddressSpace::Generic).into(), + env.context + .i8_type() + .ptr_type(AddressSpace::default()) + .into(), env.ptr_int().into(), env.ptr_int().into(), BasicMetadataTypeEnum::from(value.get_type()), @@ -512,13 +517,11 @@ fn build_clone_tag<'a, 'ctx, 'env>( layout_interner, layout_ids, union_layout, - when_recursive, function_value, ); env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function_value } @@ -563,19 +566,68 @@ fn load_tag_data<'a, 'ctx, 'env>( let data_ptr = env.builder.build_pointer_cast( raw_data_ptr, - tag_type.ptr_type(AddressSpace::Generic), + tag_type.ptr_type(AddressSpace::default()), "data_ptr", ); env.builder.new_build_load(tag_type, data_ptr, "load_data") } +fn clone_tag_payload_and_id<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_interner: &mut STLayoutInterner<'a>, + layout_ids: &mut LayoutIds<'a>, + ptr: PointerValue<'ctx>, + cursors: Cursors<'ctx>, + roc_union: RocUnion<'ctx>, + tag_id: usize, + payload_in_layout: InLayout<'a>, + opaque_payload_ptr: PointerValue<'ctx>, +) -> IntValue<'ctx> { + let payload_type = basic_type_from_layout(env, layout_interner, payload_in_layout); + + let payload_ptr = env.builder.build_pointer_cast( + opaque_payload_ptr, + payload_type.ptr_type(AddressSpace::default()), + "cast_payload_ptr", + ); + + let payload = env + .builder + .new_build_load(payload_type, payload_ptr, "payload"); + + // NOTE: `answer` includes any extra_offset that the tag payload may have needed + // (e.g. because it includes a list). That is what we want to return, but not what + // we need to write the padding and offset of this tag + let answer = build_clone( + env, + layout_interner, + layout_ids, + ptr, + cursors, + payload, + payload_in_layout, + ); + + // include padding between data and tag id + let tag_id_internal_offset = roc_union.data_width(); + + let tag_id_offset = offset_add(env.builder, cursors.offset, tag_id_internal_offset); + + // write the tag id + let value = env.context.i8_type().const_int(tag_id as _, false); + build_copy(env, ptr, tag_id_offset, value.into()); + + // NOTE: padding after tag id (is taken care of by the cursor) + + answer +} + fn build_clone_tag_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, union_layout: UnionLayout<'a>, - when_recursive: WhenRecursive<'a>, fn_val: FunctionValue<'ctx>, ) { use bumpalo::collections::Vec; @@ -629,29 +681,37 @@ fn build_clone_tag_help<'a, 'ctx, 'env>( let block = env.context.append_basic_block(parent, "tag_id_modify"); env.builder.position_at_end(block); - let layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts)); - let layout = layout_interner.insert(Layout::struct_no_name_order( - env.arena.alloc([layout, union_layout.tag_id_layout()]), - )); - - let basic_type = basic_type_from_layout(env, layout_interner, layout); - let data = load_tag_data( - env, + let roc_union = RocUnion::tagged_from_slices( layout_interner, - union_layout, - tag_value.into_pointer_value(), - basic_type, + env.context, + tags, + env.target_info, ); - let answer = build_clone( + // load the tag payload (if any) + let payload_layout = Layout::struct_no_name_order(field_layouts); + let payload_in_layout = layout_interner.insert(payload_layout); + + let opaque_payload_ptr = env + .builder + .new_build_struct_gep( + roc_union.struct_type(), + tag_value.into_pointer_value(), + RocUnion::TAG_DATA_INDEX, + "data_buffer", + ) + .unwrap(); + + let answer = clone_tag_payload_and_id( env, layout_interner, layout_ids, ptr, cursors, - data, - layout, - when_recursive, + roc_union, + tag_id, + payload_in_layout, + opaque_payload_ptr, ); env.builder.build_return(Some(&answer)); @@ -712,17 +772,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>( ), }; - let when_recursive = WhenRecursive::Loop(union_layout); - let answer = build_clone( - env, - layout_interner, - layout_ids, - ptr, - cursors, - data, - layout, - when_recursive, - ); + let answer = + build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout); env.builder.build_return(Some(&answer)); @@ -762,17 +813,7 @@ fn build_clone_tag_help<'a, 'ctx, 'env>( let data = load_tag_data(env, layout_interner, union_layout, tag_value, basic_type); - let when_recursive = WhenRecursive::Loop(union_layout); - let answer = build_clone( - env, - layout_interner, - layout_ids, - ptr, - cursors, - data, - layout, - when_recursive, - ); + let answer = build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout); env.builder.build_return(Some(&answer)); } @@ -831,17 +872,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>( let data = load_tag_data(env, layout_interner, union_layout, tag_value, basic_type); - let when_recursive = WhenRecursive::Loop(union_layout); - let answer = build_clone( - env, - layout_interner, - layout_ids, - ptr, - cursors, - data, - layout, - when_recursive, - ); + let answer = + build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout); env.builder.build_return(Some(&answer)); @@ -917,17 +949,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>( basic_type, ); - let when_recursive = WhenRecursive::Loop(union_layout); - let answer = build_clone( - env, - layout_interner, - layout_ids, - ptr, - cursors, - data, - layout, - when_recursive, - ); + let answer = + build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout); env.builder.build_return(Some(&answer)); } @@ -978,7 +1001,7 @@ fn build_copy<'a, 'ctx, 'env>( ) }; - let ptr_type = value.get_type().ptr_type(AddressSpace::Generic); + let ptr_type = value.get_type().ptr_type(AddressSpace::default()); let ptr = env .builder .build_pointer_cast(ptr, ptr_type, "cast_ptr_type"); @@ -997,7 +1020,6 @@ fn build_clone_builtin<'a, 'ctx, 'env>( cursors: Cursors<'ctx>, value: BasicValueEnum<'ctx>, builtin: Builtin<'a>, - when_recursive: WhenRecursive<'a>, ) -> IntValue<'ctx> { use Builtin::*; @@ -1051,7 +1073,7 @@ fn build_clone_builtin<'a, 'ctx, 'env>( let dest = pointer_at_offset(bd, env.context.i8_type(), ptr, elements_start_offset); let src = bd.build_pointer_cast( elements, - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i8_type().ptr_type(AddressSpace::default()), "to_bytes_pointer", ); bd.build_memcpy(dest, 1, src, 1, elements_width).unwrap(); @@ -1061,7 +1083,7 @@ fn build_clone_builtin<'a, 'ctx, 'env>( let element_type = basic_type_from_layout(env, layout_interner, elem); let elements = bd.build_pointer_cast( elements, - element_type.ptr_type(AddressSpace::Generic), + element_type.ptr_type(AddressSpace::default()), "elements", ); @@ -1102,7 +1124,6 @@ fn build_clone_builtin<'a, 'ctx, 'env>( cursors, element, elem, - when_recursive, ); bd.build_store(rest_offset, new_offset); diff --git a/crates/compiler/gen_llvm/src/llvm/externs.rs b/crates/compiler/gen_llvm/src/llvm/externs.rs index 0659461b9c..6bf96f83d3 100644 --- a/crates/compiler/gen_llvm/src/llvm/externs.rs +++ b/crates/compiler/gen_llvm/src/llvm/externs.rs @@ -4,7 +4,6 @@ use crate::llvm::build::{CCReturn, Env, FunctionSpec}; use crate::llvm::convert::zig_str_type; use inkwell::module::Linkage; use inkwell::types::BasicType; -use inkwell::values::BasicValue; use inkwell::AddressSpace; use roc_builtins::bitcode; @@ -19,7 +18,7 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) { let builder = env.builder; let usize_type = env.ptr_int(); - let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic); + let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::default()); match env.mode { super::build::LlvmBackendMode::CliTest => { @@ -198,6 +197,11 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) { let mut params = fn_val.get_param_iter(); let roc_str_arg = params.next().unwrap(); + // normally, roc_panic is marked as external so it can be provided by the host. But when we + // define it here in LLVM IR, we never want it to be linked by the host (that would + // overwrite which implementation is used. + fn_val.set_linkage(Linkage::Internal); + let tag_id_arg = params.next().unwrap(); debug_assert!(params.next().is_none()); @@ -271,7 +275,7 @@ pub fn build_longjmp_call(env: &Env) { // Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)` let jmp_buf_i8p = env.builder.build_pointer_cast( jmp_buf, - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i8_type().ptr_type(AddressSpace::default()), "jmp_buf i8*", ); let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p.into()]); diff --git a/crates/compiler/gen_llvm/src/llvm/intrinsics.rs b/crates/compiler/gen_llvm/src/llvm/intrinsics.rs index 94de9dfc5d..6c68f29889 100644 --- a/crates/compiler/gen_llvm/src/llvm/intrinsics.rs +++ b/crates/compiler/gen_llvm/src/llvm/intrinsics.rs @@ -75,7 +75,7 @@ pub(crate) fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { // https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics let i1_type = ctx.bool_type(); let i8_type = ctx.i8_type(); - let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic); + let i8_ptr_type = i8_type.ptr_type(AddressSpace::default()); let i32_type = ctx.i32_type(); let void_type = ctx.void_type(); diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs index c71b07554f..e7c115e003 100644 --- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs +++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs @@ -13,6 +13,7 @@ use roc_module::{low_level::LowLevel, symbol::Symbol}; use roc_mono::{ ir::HigherOrderLowLevel, layout::{Builtin, InLayout, LambdaSet, Layout, LayoutIds, LayoutInterner, STLayoutInterner}, + list_element_layout, }; use roc_target::PtrWidth; @@ -27,10 +28,10 @@ use crate::llvm::{ load_roc_value, roc_function_call, BuilderExt, RocReturn, }, build_list::{ - list_append_unsafe, list_capacity, list_concat, list_drop_at, list_get_unsafe, list_len, - list_map, list_map2, list_map3, list_map4, list_prepend, list_replace_unsafe, list_reserve, - list_sort_with, list_sublist, list_swap, list_symbol_to_c_abi, list_with_capacity, - pass_update_mode, + list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map, + list_map2, list_map3, list_map4, list_prepend, list_release_excess_capacity, + list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap, + list_symbol_to_c_abi, list_with_capacity, pass_update_mode, }, compare::{generic_eq, generic_neq}, convert::{ @@ -49,15 +50,6 @@ use super::{ convert::zig_dec_type, }; -macro_rules! list_element_layout { - ($interner:expr, $list_layout:expr) => { - match $interner.get($list_layout) { - Layout::Builtin(Builtin::List(list_layout)) => list_layout, - _ => unreachable!("invalid list layout"), - } - }; -} - pub(crate) fn run_low_level<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, @@ -256,7 +248,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( let roc_return_type = basic_type_from_layout(env, layout_interner, layout) - .ptr_type(AddressSpace::Generic); + .ptr_type(AddressSpace::default()); let roc_return_alloca = env.builder.build_pointer_cast( zig_return_alloca, @@ -497,7 +489,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( let return_type = basic_type_from_layout(env, layout_interner, layout); let cast_result = env.builder.build_pointer_cast( result, - return_type.ptr_type(AddressSpace::Generic), + return_type.ptr_type(AddressSpace::default()), "cast", ); @@ -567,6 +559,18 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( bitcode::STR_RESERVE, ) } + StrReleaseExcessCapacity => { + // Str.releaseExcessCapacity: Str -> Str + arguments!(string); + + call_str_bitcode_fn( + env, + &[string], + &[], + BitcodeReturns::Str, + bitcode::STR_RELEASE_EXCESS_CAPACITY, + ) + } StrAppendScalar => { // Str.appendScalar : Str, U32 -> Str arguments!(string, capacity); @@ -640,10 +644,16 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( list_len(env.builder, list.into_struct_value()).into() } ListGetCapacity => { - // List.capacity : List * -> Nat + // List.capacity: List a -> Nat arguments!(list); - list_capacity(env.builder, list.into_struct_value()).into() + call_list_bitcode_fn( + env, + &[list.into_struct_value()], + &[], + BitcodeReturns::Basic, + bitcode::LIST_CAPACITY, + ) } ListWithCapacity => { // List.withCapacity : Nat -> List a @@ -709,6 +719,15 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( update_mode, ) } + ListReleaseExcessCapacity => { + // List.releaseExcessCapacity: List elem -> List elem + debug_assert_eq!(args.len(), 1); + + let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); + let element_layout = list_element_layout!(layout_interner, list_layout); + + list_release_excess_capacity(env, layout_interner, list, element_layout, update_mode) + } ListSwap => { // List.swap : List elem, Nat, Nat -> List elem debug_assert_eq!(args.len(), 3); @@ -856,9 +875,24 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( _ => unreachable!(), } } - NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos - | NumCeiling | NumFloor | NumToFrac | NumIsFinite | NumAtan | NumAcos | NumAsin - | NumToIntChecked => { + NumAbs + | NumNeg + | NumRound + | NumSqrtUnchecked + | NumLogUnchecked + | NumSin + | NumCos + | NumCeiling + | NumFloor + | NumToFrac + | NumIsFinite + | NumAtan + | NumAcos + | NumAsin + | NumToIntChecked + | NumCountLeadingZeroBits + | NumCountTrailingZeroBits + | NumCountOneBits => { arguments_with_layouts!((arg, arg_layout)); match layout_interner.get(arg_layout) { @@ -922,6 +956,28 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>( bitcode::NUM_BYTES_TO_U32, ) } + NumBytesToU64 => { + arguments!(list, position); + + call_list_bitcode_fn( + env, + &[list.into_struct_value()], + &[position], + BitcodeReturns::Basic, + bitcode::NUM_BYTES_TO_U64, + ) + } + NumBytesToU128 => { + arguments!(list, position); + + call_list_bitcode_fn( + env, + &[list.into_struct_value()], + &[position], + BitcodeReturns::Basic, + bitcode::NUM_BYTES_TO_U128, + ) + } NumCompare => { arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout)); @@ -1663,7 +1719,7 @@ fn dec_alloca<'a, 'ctx, 'env>( let ptr = env.builder.build_pointer_cast( alloca, - value.get_type().ptr_type(AddressSpace::Generic), + value.get_type().ptr_type(AddressSpace::default()), "cast_to_i128_ptr", ); @@ -2013,7 +2069,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>( let roc_return_type = basic_type_from_layout(env, layout_interner, return_layout) - .ptr_type(AddressSpace::Generic); + .ptr_type(AddressSpace::default()); let roc_return_alloca = env.builder.build_pointer_cast( zig_return_alloca, @@ -2053,6 +2109,19 @@ fn build_int_unary_op<'a, 'ctx, 'env>( complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked") } } + NumCountLeadingZeroBits => call_bitcode_fn( + env, + &[arg.into()], + &bitcode::NUM_COUNT_LEADING_ZERO_BITS[arg_width], + ), + NumCountTrailingZeroBits => call_bitcode_fn( + env, + &[arg.into()], + &bitcode::NUM_COUNT_TRAILING_ZERO_BITS[arg_width], + ), + NumCountOneBits => { + call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COUNT_ONE_BITS[arg_width]) + } _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } diff --git a/crates/compiler/gen_llvm/src/llvm/refcounting.rs b/crates/compiler/gen_llvm/src/llvm/refcounting.rs index 51b1ad8a9f..2fc10ef52f 100644 --- a/crates/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/crates/compiler/gen_llvm/src/llvm/refcounting.rs @@ -3,17 +3,18 @@ use crate::llvm::bitcode::call_void_bitcode_fn; use crate::llvm::build::BuilderExt; use crate::llvm::build::{ add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env, - WhenRecursive, FAST_CALL_CONV, + FAST_CALL_CONV, }; -use crate::llvm::build_list::{incrementing_elem_loop, list_capacity, load_list}; +use crate::llvm::build_list::{ + incrementing_elem_loop, list_capacity_or_ref_ptr, list_refcount_ptr, load_list, +}; +use crate::llvm::build_str::str_refcount_ptr; use crate::llvm::convert::{basic_type_from_layout, zig_str_type, RocUnion}; use bumpalo::collections::Vec; use inkwell::basic_block::BasicBlock; use inkwell::module::Linkage; use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum}; -use inkwell::values::{ - BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, -}; +use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue}; use inkwell::{AddressSpace, IntPredicate}; use roc_module::symbol::Interns; use roc_module::symbol::Symbol; @@ -40,7 +41,7 @@ impl<'ctx> PointerToRefcount<'ctx> { let value = env.builder.build_pointer_cast( ptr, - refcount_type.ptr_type(AddressSpace::Generic), + refcount_type.ptr_type(AddressSpace::default()), "to_refcount_ptr", ); @@ -54,7 +55,7 @@ impl<'ctx> PointerToRefcount<'ctx> { let builder = env.builder; // pointer to usize let refcount_type = env.ptr_int(); - let refcount_ptr_type = refcount_type.ptr_type(AddressSpace::Generic); + let refcount_ptr_type = refcount_type.ptr_type(AddressSpace::default()); let ptr_as_usize_ptr = builder.build_pointer_cast(data_ptr, refcount_ptr_type, "as_usize_ptr"); @@ -75,16 +76,6 @@ impl<'ctx> PointerToRefcount<'ctx> { } } - fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self { - let data_ptr = env - .builder - .build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr") - .unwrap() - .into_pointer_value(); - - Self::from_ptr_to_data(env, data_ptr) - } - pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> { let current = self.get_refcount(env); let one = match env.target_info.ptr_width() { @@ -148,7 +139,7 @@ impl<'ctx> PointerToRefcount<'ctx> { None => { // inc and dec return void let fn_type = context.void_type().fn_type( - &[env.ptr_int().ptr_type(AddressSpace::Generic).into()], + &[env.ptr_int().ptr_type(AddressSpace::default()).into()], false, ); @@ -172,8 +163,7 @@ impl<'ctx> PointerToRefcount<'ctx> { let refcount_ptr = self.value; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); let call = env .builder @@ -216,7 +206,7 @@ fn incref_pointer<'a, 'ctx, 'env>( env.builder .build_pointer_cast( pointer, - env.ptr_int().ptr_type(AddressSpace::Generic), + env.ptr_int().ptr_type(AddressSpace::default()), "to_isize_ptr", ) .into(), @@ -238,7 +228,7 @@ fn decref_pointer<'a, 'ctx, 'env>( env.builder .build_pointer_cast( pointer, - env.ptr_int().ptr_type(AddressSpace::Generic), + env.ptr_int().ptr_type(AddressSpace::default()), "to_isize_ptr", ) .into(), @@ -261,7 +251,7 @@ pub fn decref_pointer_check_null<'a, 'ctx, 'env>( env.builder .build_pointer_cast( pointer, - env.context.i8_type().ptr_type(AddressSpace::Generic), + env.context.i8_type().ptr_type(AddressSpace::default()), "to_i8_ptr", ) .into(), @@ -277,7 +267,6 @@ fn modify_refcount_struct<'a, 'ctx, 'env>( layout_ids: &mut LayoutIds<'a>, layouts: &'a [InLayout<'a>], mode: Mode, - when_recursive: &WhenRecursive<'a>, ) -> FunctionValue<'ctx> { let block = env.builder.get_insert_block().expect("to be in a function"); let di_location = env.builder.get_current_debug_location().unwrap(); @@ -304,7 +293,6 @@ fn modify_refcount_struct<'a, 'ctx, 'env>( layout_interner, layout_ids, mode, - when_recursive, layouts, function_value, ); @@ -314,8 +302,7 @@ fn modify_refcount_struct<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function } @@ -326,7 +313,6 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, layouts: &[InLayout<'a>], fn_val: FunctionValue<'ctx>, ) { @@ -368,7 +354,6 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>( layout_interner, layout_ids, mode.to_call_mode(fn_val), - when_recursive, field_value, *field_layout, ); @@ -430,7 +415,6 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, layout: InLayout<'a>, builtin: &Builtin<'a>, ) -> Option> { @@ -438,14 +422,8 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>( match builtin { List(element_layout) => { - let function = modify_refcount_list( - env, - layout_interner, - layout_ids, - mode, - when_recursive, - *element_layout, - ); + let function = + modify_refcount_list(env, layout_interner, layout_ids, mode, *element_layout); Some(function) } @@ -473,15 +451,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>( value: BasicValueEnum<'ctx>, layout: InLayout<'a>, ) { - modify_refcount_layout_help( - env, - layout_interner, - layout_ids, - call_mode, - &WhenRecursive::Unreachable, - value, - layout, - ); + modify_refcount_layout_help(env, layout_interner, layout_ids, call_mode, value, layout); } fn modify_refcount_layout_help<'a, 'ctx, 'env>( @@ -489,7 +459,6 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, call_mode: CallMode<'ctx>, - when_recursive: &WhenRecursive<'a>, value: BasicValueEnum<'ctx>, layout: InLayout<'a>, ) { @@ -498,38 +467,28 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>( CallMode::Dec => Mode::Dec, }; - let function = match modify_refcount_layout_build_function( - env, - layout_interner, - layout_ids, - mode, - when_recursive, - layout, - ) { - Some(f) => f, - None => return, - }; + let function = + match modify_refcount_layout_build_function(env, layout_interner, layout_ids, mode, layout) + { + Some(f) => f, + None => return, + }; match layout_interner.get(layout) { - Layout::RecursivePointer(_) => match when_recursive { - WhenRecursive::Unreachable => { - unreachable!("recursion pointers should never be hashed directly") - } - WhenRecursive::Loop(union_layout) => { - let layout = layout_interner.insert(Layout::Union(*union_layout)); + Layout::RecursivePointer(rec_layout) => { + let layout = rec_layout; - let bt = basic_type_from_layout(env, layout_interner, layout); + let bt = basic_type_from_layout(env, layout_interner, layout); - // cast the i64 pointer to a pointer to block of memory - let field_cast = env.builder.build_pointer_cast( - value.into_pointer_value(), - bt.into_pointer_type(), - "i64_to_opaque", - ); + // cast the i64 pointer to a pointer to block of memory + let field_cast = env.builder.build_pointer_cast( + value.into_pointer_value(), + bt.into_pointer_type(), + "i64_to_opaque", + ); - call_help(env, function, call_mode, field_cast.into()); - } - }, + call_help(env, function, call_mode, field_cast.into()); + } _ => { call_help(env, function, call_mode, value); } @@ -568,21 +527,14 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, layout: InLayout<'a>, ) -> Option> { use Layout::*; match layout_interner.get(layout) { - Builtin(builtin) => modify_refcount_builtin( - env, - layout_interner, - layout_ids, - mode, - when_recursive, - layout, - &builtin, - ), + Builtin(builtin) => { + modify_refcount_builtin(env, layout_interner, layout_ids, mode, layout, &builtin) + } Boxed(inner) => { let function = modify_refcount_boxed(env, layout_interner, layout_ids, mode, inner); @@ -600,27 +552,14 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>( } NonRecursive(tags) => { - let function = modify_refcount_nonrecursive( - env, - layout_interner, - layout_ids, - mode, - when_recursive, - tags, - ); + let function = + modify_refcount_nonrecursive(env, layout_interner, layout_ids, mode, tags); Some(function) } _ => { - let function = build_rec_union( - env, - layout_interner, - layout_ids, - mode, - &WhenRecursive::Loop(variant), - variant, - ); + let function = build_rec_union(env, layout_interner, layout_ids, mode, variant); Some(function) } @@ -628,43 +567,30 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>( } Struct { field_layouts, .. } => { - let function = modify_refcount_struct( - env, - layout_interner, - layout_ids, - field_layouts, - mode, - when_recursive, - ); + let function = + modify_refcount_struct(env, layout_interner, layout_ids, field_layouts, mode); Some(function) } - Layout::RecursivePointer(_) => match when_recursive { - WhenRecursive::Unreachable => { - unreachable!("recursion pointers cannot be in/decremented directly") - } - WhenRecursive::Loop(union_layout) => { - let layout = layout_interner.insert(Layout::Union(*union_layout)); + Layout::RecursivePointer(rec_layout) => { + let layout = rec_layout; - let function = modify_refcount_layout_build_function( - env, - layout_interner, - layout_ids, - mode, - when_recursive, - layout, - )?; + let function = modify_refcount_layout_build_function( + env, + layout_interner, + layout_ids, + mode, + layout, + )?; - Some(function) - } - }, + Some(function) + } LambdaSet(lambda_set) => modify_refcount_layout_build_function( env, layout_interner, layout_ids, mode, - when_recursive, lambda_set.runtime_representation(), ), } @@ -675,15 +601,18 @@ fn modify_refcount_list<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, element_layout: InLayout<'a>, ) -> FunctionValue<'ctx> { let block = env.builder.get_insert_block().expect("to be in a function"); let di_location = env.builder.get_current_debug_location().unwrap(); - let element_layout = layout_interner.get(element_layout); - let element_layout = when_recursive.unwrap_recursive_pointer(element_layout); - let element_layout = layout_interner.insert(element_layout); + let element_layout = if let Layout::RecursivePointer(rec) = layout_interner.get(element_layout) + { + rec + } else { + element_layout + }; + let list_layout = layout_interner.insert(Layout::Builtin(Builtin::List(element_layout))); let (_, fn_name) = function_name_from_mode( layout_ids, @@ -705,7 +634,6 @@ fn modify_refcount_list<'a, 'ctx, 'env>( layout_interner, layout_ids, mode, - when_recursive, list_layout, element_layout, function_value, @@ -716,8 +644,7 @@ fn modify_refcount_list<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function } @@ -734,7 +661,6 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, layout: InLayout<'a>, element_layout: InLayout<'a>, fn_val: FunctionValue<'ctx>, @@ -758,26 +684,27 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( let parent = fn_val; let original_wrapper = arg_val.into_struct_value(); - let len = list_capacity(builder, original_wrapper); + // We use the raw capacity to ensure we always decrement the refcount of seamless slices. + let capacity = list_capacity_or_ref_ptr(builder, original_wrapper); let is_non_empty = builder.build_int_compare( IntPredicate::UGT, - len, + capacity, env.ptr_int().const_zero(), - "len > 0", + "cap > 0", ); // build blocks - let modification_block = ctx.append_basic_block(parent, "modification_block"); + let modification_list_block = ctx.append_basic_block(parent, "modification_list_block"); let cont_block = ctx.append_basic_block(parent, "modify_rc_list_cont"); - builder.build_conditional_branch(is_non_empty, modification_block, cont_block); + builder.build_conditional_branch(is_non_empty, modification_list_block, cont_block); - builder.position_at_end(modification_block); + builder.position_at_end(modification_list_block); if layout_interner.contains_refcounted(element_layout) { let ptr_type = basic_type_from_layout(env, layout_interner, element_layout) - .ptr_type(AddressSpace::Generic); + .ptr_type(AddressSpace::default()); let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type); @@ -787,7 +714,6 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( layout_interner, layout_ids, mode.to_call_mode(fn_val), - when_recursive, element, element_layout, ); @@ -805,7 +731,8 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( ); } - let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper); + let refcount_ptr = + PointerToRefcount::from_ptr_to_data(env, list_refcount_ptr(env, original_wrapper)); let call_mode = mode_to_call_mode(fn_val, mode); refcount_ptr.modify(call_mode, layout, env, layout_interner); @@ -849,8 +776,7 @@ fn modify_refcount_str<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function } @@ -880,9 +806,9 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>( let parent = fn_val; - let arg_val = + let str_type = zig_str_type(env); + let str_wrapper = if Layout::Builtin(Builtin::Str).is_passed_by_reference(layout_interner, env.target_info) { - let str_type = zig_str_type(env); env.builder .new_build_load(str_type, arg_val.into_pointer_value(), "load_str_to_stack") } else { @@ -890,7 +816,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>( debug_assert!(arg_val.is_struct_value()); arg_val }; - let str_wrapper = arg_val.into_struct_value(); + let str_wrapper = str_wrapper.into_struct_value(); let capacity = builder .build_extract_value(str_wrapper, Builtin::WRAPPER_CAPACITY, "read_str_capacity") @@ -913,7 +839,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>( builder.build_conditional_branch(is_big_and_non_empty, modification_block, cont_block); builder.position_at_end(modification_block); - let refcount_ptr = PointerToRefcount::from_list_wrapper(env, str_wrapper); + let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, str_refcount_ptr(env, arg_val)); let call_mode = mode_to_call_mode(fn_val, mode); refcount_ptr.modify(call_mode, layout, env, layout_interner); @@ -959,8 +885,7 @@ fn modify_refcount_boxed<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function } @@ -1093,7 +1018,6 @@ fn build_rec_union<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, union_layout: UnionLayout<'a>, ) -> FunctionValue<'ctx> { let layout = layout_interner.insert(Layout::Union(union_layout)); @@ -1121,14 +1045,12 @@ fn build_rec_union<'a, 'ctx, 'env>( layout_interner, layout_ids, mode, - when_recursive, union_layout, function_value, ); env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function_value } @@ -1143,7 +1065,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, union_layout: UnionLayout<'a>, fn_val: FunctionValue<'ctx>, ) { @@ -1235,7 +1156,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, parent, fn_val, union_layout, @@ -1268,7 +1188,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, - when_recursive: &WhenRecursive<'a>, parent: FunctionValue<'ctx>, decrement_fn: FunctionValue<'ctx>, union_layout: UnionLayout<'a>, @@ -1316,7 +1235,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( // cast the opaque pointer to a pointer of the correct shape let struct_ptr = env.builder.build_pointer_cast( value_ptr, - wrapper_type.ptr_type(AddressSpace::Generic), + wrapper_type.ptr_type(AddressSpace::default()), "opaque_to_correct_recursive_decrement", ); @@ -1339,7 +1258,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( .unwrap(); let ptr_as_i64_ptr = env.builder.new_build_load( - env.context.i64_type().ptr_type(AddressSpace::Generic), + env.context.i64_type().ptr_type(AddressSpace::default()), elem_pointer, "load_recursive_pointer", ); @@ -1396,7 +1315,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( layout_interner, layout_ids, mode.to_call_mode(decrement_fn), - when_recursive, field, *field_layout, ); @@ -1422,11 +1340,16 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( union_layout, UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. } ) { - debug_assert_eq!(cases.len(), 1); + debug_assert!(cases.len() <= 1, "{cases:?}"); - // in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id - let (_, only_branch) = cases.pop().unwrap(); - env.builder.build_unconditional_branch(only_branch); + if cases.is_empty() { + // The only other layout doesn't need refcounting. Pass through. + builder.build_return(None); + } else { + // in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id + let (_, only_branch) = cases.pop().unwrap(); + env.builder.build_unconditional_branch(only_branch); + } } else { let default_block = env.context.append_basic_block(parent, "switch_default"); @@ -1449,6 +1372,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( } } +#[derive(Debug)] struct UnionLayoutTags<'a> { nullable_id: Option, tags: &'a [&'a [InLayout<'a>]], @@ -1503,15 +1427,7 @@ pub fn build_reset<'a, 'ctx, 'env>( let fn_name = layout_id.to_symbol_string(Symbol::DEC, &env.interns); let fn_name = format!("{}_reset", fn_name); - let when_recursive = WhenRecursive::Loop(union_layout); - let dec_function = build_rec_union( - env, - layout_interner, - layout_ids, - Mode::Dec, - &when_recursive, - union_layout, - ); + let dec_function = build_rec_union(env, layout_interner, layout_ids, Mode::Dec, union_layout); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, @@ -1526,15 +1442,13 @@ pub fn build_reset<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - &when_recursive, union_layout, function_value, dec_function, ); env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function_value } @@ -1548,7 +1462,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, - when_recursive: &WhenRecursive<'a>, union_layout: UnionLayout<'a>, reset_function: FunctionValue<'ctx>, dec_function: FunctionValue<'ctx>, @@ -1626,7 +1539,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>( env, layout_interner, layout_ids, - when_recursive, parent, dec_function, union_layout, @@ -1665,7 +1577,6 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, fields: &'a [&'a [InLayout<'a>]], ) -> FunctionValue<'ctx> { let union_layout = UnionLayout::NonRecursive(fields); @@ -1694,7 +1605,6 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>( layout_interner, layout_ids, mode, - when_recursive, fields, function_value, ); @@ -1704,8 +1614,7 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>( }; env.builder.position_at_end(block); - env.builder - .set_current_debug_location(env.context, di_location); + env.builder.set_current_debug_location(di_location); function } @@ -1715,7 +1624,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>( layout_interner: &mut STLayoutInterner<'a>, layout_ids: &mut LayoutIds<'a>, mode: Mode, - when_recursive: &WhenRecursive<'a>, tags: &'a [&'a [InLayout<'a>]], fn_val: FunctionValue<'ctx>, ) { @@ -1805,19 +1713,12 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>( let cast_tag_data_pointer = env.builder.build_pointer_cast( opaque_tag_data_ptr, - data_struct_type.ptr_type(AddressSpace::Generic), + data_struct_type.ptr_type(AddressSpace::default()), "cast_to_concrete_tag", ); for (i, field_layout) in field_layouts.iter().enumerate() { - if let Layout::RecursivePointer(_) = layout_interner.get(*field_layout) { - 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, - }; - + if let Layout::RecursivePointer(union_layout) = layout_interner.get(*field_layout) { // This field is a pointer to the recursive pointer. let field_ptr = env .builder @@ -1831,7 +1732,7 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>( // This is the actual pointer to the recursive data. let field_value = env.builder.new_build_load( - env.context.i64_type().ptr_type(AddressSpace::Generic), + env.context.i64_type().ptr_type(AddressSpace::default()), field_ptr, "load_recursive_pointer", ); @@ -1839,7 +1740,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>( debug_assert!(field_value.is_pointer_value()); // therefore we must cast it to our desired type - let union_layout = layout_interner.insert(Layout::Union(*recursive_union_layout)); let union_type = basic_type_from_layout(env, layout_interner, union_layout); let recursive_ptr_field_value = cast_basic_basic(env.builder, field_value, union_type); @@ -1849,7 +1749,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>( layout_interner, layout_ids, mode.to_call_mode(fn_val), - when_recursive, recursive_ptr_field_value, *field_layout, ) @@ -1879,7 +1778,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>( layout_interner, layout_ids, mode.to_call_mode(fn_val), - when_recursive, field_value, *field_layout, ); diff --git a/crates/compiler/gen_wasm/Cargo.toml b/crates/compiler/gen_wasm/Cargo.toml index b96de60b50..a25ccf0e23 100644 --- a/crates/compiler/gen_wasm/Cargo.toml +++ b/crates/compiler/gen_wasm/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "roc_gen_wasm" -version = "0.0.1" -edition = "2021" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "Provides the WASM backend to generate Roc binaries." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_builtins = { path = "../builtins" } roc_collections = { path = "../collections" } +roc_error_macros = { path = "../../error_macros" } roc_module = { path = "../module" } roc_mono = { path = "../mono" } -roc_target = { path = "../roc_target" } roc_std = { path = "../../roc_std" } -roc_error_macros = { path = "../../error_macros" } +roc_target = { path = "../roc_target" } roc_wasm_module = { path = "../../wasm_module" } bitvec.workspace = true diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index 5b3a099edd..6cfd49bea3 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -285,6 +285,9 @@ impl<'a> LowLevelCall<'a> { StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT), StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8), StrReserve => self.load_args_and_call_zig(backend, bitcode::STR_RESERVE), + StrReleaseExcessCapacity => { + self.load_args_and_call_zig(backend, bitcode::STR_RELEASE_EXCESS_CAPACITY) + } StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT), StrAppendScalar => self.load_args_and_call_zig(backend, bitcode::STR_APPEND_SCALAR), StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM), @@ -318,25 +321,7 @@ impl<'a> LowLevelCall<'a> { _ => internal_error!("invalid storage for List"), }, - ListGetCapacity => match backend.storage.get(&self.arguments[0]) { - StoredValue::StackMemory { location, .. } => { - let (local_id, offset) = - location.local_and_offset(backend.storage.stack_frame_pointer); - backend.code_builder.get_local(local_id); - // List is stored as (pointer, length, capacity), - // with each of those fields being 4 bytes on wasm. - // So the capacity is 8 bytes after the start of the struct. - // - // WRAPPER_CAPACITY represents the index of the capacity field - // (which is 2 as of the writing of this comment). If the field order - // ever changes, WRAPPER_CAPACITY should be updated and this logic should - // continue to work even though this comment may become inaccurate. - backend - .code_builder - .i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_CAPACITY)); - } - _ => internal_error!("invalid storage for List"), - }, + ListGetCapacity => self.load_args_and_call_zig(backend, bitcode::LIST_CAPACITY), ListIsUnique => self.load_args_and_call_zig(backend, bitcode::LIST_IS_UNIQUE), @@ -573,6 +558,46 @@ impl<'a> LowLevelCall<'a> { backend.call_host_fn_after_loading_args(bitcode::LIST_RESERVE, 7, false); } + ListReleaseExcessCapacity => { + // List.releaseExcessCapacity : List elem -> List elem + + let list: Symbol = self.arguments[0]; + + let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw); + let elem_layout = backend.layout_interner.get(elem_layout); + let (elem_width, elem_align) = + elem_layout.stack_size_and_alignment(backend.layout_interner, TARGET_INFO); + + // Zig arguments Wasm types + // (return pointer) i32 + // list: RocList i64, i32 + // alignment: u32 i32 + // element_width: usize i32 + // update_mode: UpdateMode i32 + + // return pointer and list + backend.storage.load_symbols_for_call( + backend.env.arena, + &mut backend.code_builder, + &[list], + self.ret_symbol, + &WasmLayout::new(backend.layout_interner, self.ret_layout), + CallConv::Zig, + ); + + backend.code_builder.i32_const(elem_align as i32); + + backend.code_builder.i32_const(elem_width as i32); + + backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE); + + backend.call_host_fn_after_loading_args( + bitcode::LIST_RELEASE_EXCESS_CAPACITY, + 6, + false, + ); + } + ListAppendUnsafe => { // List.append : List elem, elem -> List elem @@ -1489,6 +1514,40 @@ impl<'a> LowLevelCall<'a> { } _ => panic_ret_type(), }, + + NumCountLeadingZeroBits => match backend + .layout_interner + .get(backend.storage.symbol_layouts[&self.arguments[0]]) + { + Layout::Builtin(Builtin::Int(width)) => { + self.load_args_and_call_zig( + backend, + &bitcode::NUM_COUNT_LEADING_ZERO_BITS[width], + ); + } + _ => panic_ret_type(), + }, + NumCountTrailingZeroBits => match backend + .layout_interner + .get(backend.storage.symbol_layouts[&self.arguments[0]]) + { + Layout::Builtin(Builtin::Int(width)) => { + self.load_args_and_call_zig( + backend, + &bitcode::NUM_COUNT_TRAILING_ZERO_BITS[width], + ); + } + _ => panic_ret_type(), + }, + NumCountOneBits => match backend + .layout_interner + .get(backend.storage.symbol_layouts[&self.arguments[0]]) + { + Layout::Builtin(Builtin::Int(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_COUNT_ONE_BITS[width]); + } + _ => panic_ret_type(), + }, NumRound => { self.load_args(backend); let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); @@ -1577,6 +1636,8 @@ impl<'a> LowLevelCall<'a> { }, NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16), NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32), + NumBytesToU64 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U64), + NumBytesToU128 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U128), NumBitwiseAnd => { self.load_args(backend); match CodeGenNumType::from(self.ret_layout) { diff --git a/crates/compiler/gen_wasm/src/wasm32_result.rs b/crates/compiler/gen_wasm/src/wasm32_result.rs index 04c8fa0480..fc4cde114b 100644 --- a/crates/compiler/gen_wasm/src/wasm32_result.rs +++ b/crates/compiler/gen_wasm/src/wasm32_result.rs @@ -8,7 +8,7 @@ use bumpalo::{collections::Vec, Bump}; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_mono::layout::{Builtin, InLayout, Layout, LayoutInterner, UnionLayout}; -use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128}; +use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128}; use roc_wasm_module::{ linking::SymInfo, linking::WasmObjectSymbol, Align, Export, ExportType, LocalId, Signature, ValueType, WasmModule, @@ -203,6 +203,13 @@ impl Wasm32Result for RocList { } } +impl Wasm32Result for RocBox { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + // treat box as if it's just a isize value + ::build_wrapper_body(code_builder, main_function_index) + } +} + impl Wasm32Result for RocResult { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( diff --git a/crates/compiler/gen_wasm/src/wasm32_sized.rs b/crates/compiler/gen_wasm/src/wasm32_sized.rs index 81c3b83bc0..05ac6d95ec 100644 --- a/crates/compiler/gen_wasm/src/wasm32_sized.rs +++ b/crates/compiler/gen_wasm/src/wasm32_sized.rs @@ -1,4 +1,4 @@ -use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128}; +use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128}; pub trait Wasm32Sized: Sized { const SIZE_OF_WASM: usize; @@ -45,6 +45,11 @@ impl Wasm32Sized for RocList { const ALIGN_OF_WASM: usize = 4; } +impl Wasm32Sized for RocBox { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; +} + impl Wasm32Sized for RocResult { const ALIGN_OF_WASM: usize = max(&[T::ALIGN_OF_WASM, E::ALIGN_OF_WASM]); const SIZE_OF_WASM: usize = max(&[T::ACTUAL_WIDTH, E::ACTUAL_WIDTH]) + 1; diff --git a/crates/compiler/ident/Cargo.toml b/crates/compiler/ident/Cargo.toml index febd0b050c..0c53fa90a9 100644 --- a/crates/compiler/ident/Cargo.toml +++ b/crates/compiler/ident/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "roc_ident" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Implements data structures used for efficiently representing small strings, like identifiers." + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true diff --git a/crates/compiler/late_solve/Cargo.toml b/crates/compiler/late_solve/Cargo.toml index f4b11fa9c7..cd9af7f6cf 100644 --- a/crates/compiler/late_solve/Cargo.toml +++ b/crates/compiler/late_solve/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "roc_late_solve" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Provides type unification and solving primitives from the perspective of the compiler backend." -[dependencies] -roc_types = { path = "../types" } -roc_can = { path = "../can" } -roc_derive = { path = "../derive" } -roc_module = { path = "../module" } -roc_unify = { path = "../unify" } -roc_solve = { path = "../solve" } -roc_collections = { path = "../collections" } -roc_error_macros = { path = "../../error_macros" } +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true -bumpalo.workspace = true +[dependencies] +roc_can = { path = "../can" } +roc_collections = { path = "../collections" } +roc_derive = { path = "../derive" } +roc_error_macros = { path = "../../error_macros" } +roc_module = { path = "../module" } +roc_solve = { path = "../solve" } +roc_types = { path = "../types" } +roc_unify = { path = "../unify" } + +bumpalo.workspace = true diff --git a/crates/compiler/late_solve/src/lib.rs b/crates/compiler/late_solve/src/lib.rs index c3d3eb4ee1..15098bda7a 100644 --- a/crates/compiler/late_solve/src/lib.rs +++ b/crates/compiler/late_solve/src/lib.rs @@ -20,7 +20,7 @@ use roc_types::types::Polarity; use roc_unify::unify::MetaCollector; use roc_unify::unify::{Env, Mode, Unified}; -pub use roc_solve::ability::Resolved; +pub use roc_solve::ability::{ResolveError, Resolved}; pub use roc_types::subs::instantiate_rigids; pub mod storage; @@ -161,7 +161,7 @@ pub fn resolve_ability_specialization( abilities: &AbilitiesView, ability_member: Symbol, specialization_var: Variable, -) -> Option { +) -> Result { let late_resolver = LateResolver { home, abilities }; roc_solve::ability::resolve_ability_specialization( subs, diff --git a/crates/compiler/load/Cargo.toml b/crates/compiler/load/Cargo.toml index fa190af36f..537093b6a6 100644 --- a/crates/compiler/load/Cargo.toml +++ b/crates/compiler/load/Cargo.toml @@ -1,30 +1,31 @@ [package] name = "roc_load" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Used to load a .roc file and coordinate the compiler pipeline, including parsing, type checking, and code generation." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -roc_load_internal = { path = "../load_internal" } -roc_target = { path = "../roc_target" } roc_can = { path = "../can" } -roc_types = { path = "../types" } -roc_module = { path = "../module" } roc_collections = { path = "../collections" } +roc_load_internal = { path = "../load_internal" } +roc_module = { path = "../module" } roc_packaging = { path = "../../packaging" } roc_reporting = { path = "../../reporting" } +roc_target = { path = "../roc_target" } +roc_types = { path = "../types" } bumpalo.workspace = true [build-dependencies] roc_builtins = { path = "../builtins" } +roc_can = { path = "../can" } roc_module = { path = "../module" } roc_packaging = { path = "../../packaging" } roc_reporting = { path = "../../reporting" } roc_target = { path = "../roc_target" } -roc_can = { path = "../can" } bumpalo.workspace = true diff --git a/crates/compiler/load/build.rs b/crates/compiler/load/build.rs index d71559a59c..f53a9f75f1 100644 --- a/crates/compiler/load/build.rs +++ b/crates/compiler/load/build.rs @@ -3,7 +3,6 @@ use std::path::{Path, PathBuf}; #[cfg(not(windows))] use bumpalo::Bump; use roc_module::symbol::ModuleId; -use roc_packaging::cache::RocCacheDir; const SKIP_SUBS_CACHE: bool = { match option_env!("ROC_SKIP_SUBS_CACHE") { @@ -70,6 +69,7 @@ fn write_types_for_module_dummy(output_path: &Path) { fn write_types_for_module_real(module_id: ModuleId, filename: &str, output_path: &Path) { use roc_can::module::TypeState; use roc_load_internal::file::{LoadingProblem, Threading}; + use roc_packaging::cache::RocCacheDir; use roc_reporting::cli::report_problems; let arena = Bump::new(); diff --git a/crates/compiler/load/src/lib.rs b/crates/compiler/load/src/lib.rs index e564c9d119..3f66ff742a 100644 --- a/crates/compiler/load/src/lib.rs +++ b/crates/compiler/load/src/lib.rs @@ -18,8 +18,8 @@ const SKIP_SUBS_CACHE: bool = { pub use roc_load_internal::docs; pub use roc_load_internal::file::{ - EntryPoint, ExecutionMode, ExpectMetadata, Expectations, LoadConfig, LoadResult, LoadStart, - LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading, + EntryPoint, ExecutionMode, ExpectMetadata, Expectations, ExposedToHost, LoadConfig, LoadResult, + LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading, }; #[allow(clippy::too_many_arguments)] diff --git a/crates/compiler/load_internal/Cargo.toml b/crates/compiler/load_internal/Cargo.toml index a52d997f93..e53090ac0b 100644 --- a/crates/compiler/load_internal/Cargo.toml +++ b/crates/compiler/load_internal/Cargo.toml @@ -1,45 +1,46 @@ [package] name = "roc_load_internal" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "The internal implementation of roc_load, separate from roc_load to support caching." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -roc_collections = { path = "../collections" } -roc_error_macros = { path = "../../error_macros" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_types = { path = "../types" } -roc_can = { path = "../can" } -roc_constrain = { path = "../constrain" } -roc_derive_key = { path = "../derive_key" } -roc_derive = { path = "../derive" } roc_builtins = { path = "../builtins" } -roc_problem = { path = "../problem" } -roc_unify = { path = "../unify" } +roc_can = { path = "../can" } +roc_collections = { path = "../collections" } +roc_constrain = { path = "../constrain" } +roc_debug_flags = { path = "../debug_flags" } +roc_derive = { path = "../derive" } +roc_derive_key = { path = "../derive_key" } +roc_error_macros = { path = "../../error_macros" } +roc_late_solve = { path = "../late_solve" } +roc_module = { path = "../module" } +roc_mono = { path = "../mono" } +roc_packaging = { path = "../../packaging" } roc_parse = { path = "../parse" } +roc_problem = { path = "../problem" } +roc_region = { path = "../region" } +roc_reporting = { path = "../../reporting" } roc_solve = { path = "../solve" } roc_solve_problem = { path = "../solve_problem" } -roc_late_solve = { path = "../late_solve" } -roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_tracing = { path = "../../tracing" } -roc_packaging = { path = "../../packaging" } -roc_reporting = { path = "../../reporting" } -roc_debug_flags = { path = "../debug_flags" } +roc_types = { path = "../types" } +roc_unify = { path = "../unify" } ven_pretty = { path = "../../vendor/pretty" } bumpalo.workspace = true -parking_lot.workspace = true crossbeam.workspace = true +parking_lot.workspace = true tempfile.workspace = true [dev-dependencies] roc_test_utils = { path = "../../test_utils" } -pretty_assertions.workspace = true indoc.workspace = true maplit.workspace = true +pretty_assertions.workspace = true diff --git a/crates/compiler/load_internal/src/docs.rs b/crates/compiler/load_internal/src/docs.rs index eb8b7ed514..27dee8c8a0 100644 --- a/crates/compiler/load_internal/src/docs.rs +++ b/crates/compiler/load_internal/src/docs.rs @@ -403,7 +403,7 @@ fn contains_unexposed_type( false } - Tuple { fields, ext } => { + Tuple { elems: fields, ext } => { if let Some(loc_ext) = ext { if contains_unexposed_type(&loc_ext.value, exposed_module_ids, module_ids) { return true; diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index ddc044d154..9aee848673 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -1,7 +1,7 @@ #![allow(clippy::too_many_arguments)] use crate::docs::ModuleDocumentation; -use bumpalo::Bump; +use bumpalo::{collections::CollectIn, Bump}; use crossbeam::channel::{bounded, Sender}; use crossbeam::deque::{Injector, Stealer, Worker}; use crossbeam::thread; @@ -32,9 +32,10 @@ use roc_module::symbol::{ }; use roc_mono::fl_reuse; use roc_mono::ir::{ - CapturedSymbols, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase, - UpdateModeIds, + CapturedSymbols, ExternalSpecializations, GlueLayouts, LambdaSetId, PartialProc, Proc, + ProcLayout, Procs, ProcsBase, UpdateModeIds, }; +use roc_mono::layout::LayoutInterner; use roc_mono::layout::{ GlobalLayoutInterner, LambdaName, Layout, LayoutCache, LayoutProblem, Niche, STLayoutInterner, }; @@ -118,11 +119,11 @@ pub enum ExecutionMode { impl ExecutionMode { fn goal_phase(&self) -> Phase { + use ExecutionMode::*; + match self { - ExecutionMode::Executable => Phase::MakeSpecializations, - ExecutionMode::Check | ExecutionMode::ExecutableIfCheck | ExecutionMode::Test => { - Phase::SolveTypes - } + Executable => Phase::MakeSpecializations, + Check | ExecutableIfCheck | Test => Phase::SolveTypes, } } @@ -767,6 +768,7 @@ pub struct MonomorphizedModule<'a> { pub timings: MutMap, pub expectations: VecMap, pub uses_prebuilt_platform: bool, + pub glue_layouts: GlueLayouts<'a>, } /// Values used to render expect output @@ -797,9 +799,12 @@ pub struct Expectations { #[derive(Clone, Debug, Default)] pub struct ExposedToHost { /// usually `mainForHost` - pub values: MutMap, + pub top_level_values: MutMap, /// exposed closure types, typically `Fx` pub closure_types: Vec, + /// lambda_sets + pub lambda_sets: Vec<(Symbol, LambdaSetId)>, + pub getters: Vec, } impl<'a> MonomorphizedModule<'a> { @@ -2777,7 +2782,7 @@ fn update<'a>( !matches!(state.exec_mode, ExecutionMode::Test); if add_to_host_exposed { - state.exposed_to_host.values.extend( + state.exposed_to_host.top_level_values.extend( solved_module .exposed_vars_by_symbol .iter() @@ -3104,7 +3109,7 @@ fn update<'a>( // debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_RESET_REUSE); // let host_exposed_procs = bumpalo::collections::Vec::from_iter_in( - // state.exposed_to_host.values.keys().copied(), + // state.exposed_to_host.top_level_values.keys().copied(), // arena, // ); @@ -3294,8 +3299,8 @@ fn finish_specialization<'a>( arena: &'a Bump, state: State<'a>, subs: Subs, - layout_interner: STLayoutInterner<'a>, - exposed_to_host: ExposedToHost, + mut layout_interner: STLayoutInterner<'a>, + mut exposed_to_host: ExposedToHost, module_expectations: VecMap, ) -> Result, LoadingProblem<'a>> { if false { @@ -3325,38 +3330,16 @@ fn finish_specialization<'a>( all_ident_ids, }; - let State { - toplevel_expects, - procedures, - module_cache, - output_path, - platform_path, - platform_data, - exec_mode, - .. - } = state; - - let ModuleCache { - type_problems, - can_problems, - sources, - .. - } = module_cache; - - let sources: MutMap)> = sources - .into_iter() - .map(|(id, (path, src))| (id, (path, src.into()))) - .collect(); - let entry_point = { - match exec_mode { - ExecutionMode::Test => EntryPoint::Test, + let interns: &mut Interns = &mut interns; + match state.exec_mode { + ExecutionMode::Test => Ok(EntryPoint::Test), ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => { use PlatformPath::*; - let platform_path = match platform_path { + let platform_path = match &state.platform_path { Valid(To::ExistingPackage(shorthand)) => { - match (*state.arc_shorthands).lock().get(shorthand) { + match state.arc_shorthands.lock().get(shorthand) { Some(shorthand_path) => shorthand_path.root_module().to_path_buf(), None => unreachable!(), } @@ -3368,13 +3351,14 @@ fn finish_specialization<'a>( } }; - let exposed_symbols_and_layouts = match platform_data { + let exposed_symbols_and_layouts = match state.platform_data { None => { - let src = &exposed_to_host.values; + let src = &state.exposed_to_host.top_level_values; let mut buf = bumpalo::collections::Vec::with_capacity_in(src.len(), arena); for &symbol in src.keys() { - let proc_layout = proc_layout_for(procedures.keys().copied(), symbol); + let proc_layout = + proc_layout_for(state.procedures.keys().copied(), symbol); buf.push((symbol, proc_layout)); } @@ -3393,7 +3377,8 @@ fn finish_specialization<'a>( for (loc_name, _loc_typed_ident) in provides { let ident_id = ident_ids.get_or_insert(loc_name.value.as_str()); let symbol = Symbol::new(module_id, ident_id); - let proc_layout = proc_layout_for(procedures.keys().copied(), symbol); + let proc_layout = + proc_layout_for(state.procedures.keys().copied(), symbol); buf.push((symbol, proc_layout)); } @@ -3402,14 +3387,84 @@ fn finish_specialization<'a>( } }; - EntryPoint::Executable { + Ok(EntryPoint::Executable { exposed_to_host: exposed_symbols_and_layouts, platform_path, - } + }) } ExecutionMode::Check => unreachable!(), } - }; + }?; + + let State { + toplevel_expects, + procedures, + module_cache, + output_path, + platform_data, + .. + } = state; + + let ModuleCache { + type_problems, + can_problems, + sources, + .. + } = module_cache; + + let sources: MutMap)> = sources + .into_iter() + .map(|(id, (path, src))| (id, (path, src.into()))) + .collect(); + + let module_id = state.root_id; + let mut glue_getters = Vec::new(); + + // the REPL does not have any platform data + if let ( + EntryPoint::Executable { + exposed_to_host: exposed_top_levels, + .. + }, + Some(platform_data), + ) = (&entry_point, platform_data.as_ref()) + { + // Expose glue for the platform, not for the app module! + let module_id = platform_data.module_id; + + for (_name, proc_layout) in exposed_top_levels.iter() { + let ret = &proc_layout.result; + for in_layout in proc_layout.arguments.iter().chain([ret]) { + let layout = layout_interner.get(*in_layout); + let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap(); + let all_glue_procs = roc_mono::ir::generate_glue_procs( + module_id, + ident_ids, + arena, + &mut layout_interner, + arena.alloc(layout), + ); + + let lambda_set_names = all_glue_procs + .extern_names + .iter() + .map(|(lambda_set_id, _)| (*_name, *lambda_set_id)); + exposed_to_host.lambda_sets.extend(lambda_set_names); + + let getter_names = all_glue_procs + .getters + .iter() + .flat_map(|(_, glue_procs)| glue_procs.iter().map(|glue_proc| glue_proc.name)); + exposed_to_host.getters.extend(getter_names); + + glue_getters.extend(all_glue_procs.getters.iter().flat_map(|(_, glue_procs)| { + glue_procs + .iter() + .map(|glue_proc| (glue_proc.name, glue_proc.proc_layout)) + })); + } + } + } let output_path = match output_path { Some(path_str) => Path::new(path_str).into(), @@ -3429,7 +3484,7 @@ fn finish_specialization<'a>( output_path, expectations: module_expectations, exposed_to_host, - module_id: state.root_id, + module_id, subs, interns, layout_interner, @@ -3438,6 +3493,9 @@ fn finish_specialization<'a>( sources, timings: state.timings, toplevel_expects, + glue_layouts: GlueLayouts { + getters: glue_getters, + }, uses_prebuilt_platform, }) } @@ -3460,6 +3518,7 @@ fn proc_layout_for<'a>( } } +#[allow(clippy::too_many_arguments)] fn finish( mut state: State, solved: Solved, @@ -5659,10 +5718,14 @@ fn make_specializations<'a>( let mut procs = Procs::new_in(arena); + let host_exposed_symbols: bumpalo::collections::Vec<_> = + procs_base.get_host_exposed_symbols().collect_in(arena); + for (symbol, partial_proc) in procs_base.partial_procs.into_iter() { procs.partial_procs.insert(symbol, partial_proc); } + procs.host_exposed_symbols = host_exposed_symbols.into_bump_slice(); procs.module_thunks = procs_base.module_thunks; procs.runtime_errors = procs_base.runtime_errors; procs.imported_module_thunks = procs_base.imported_module_thunks; @@ -5753,6 +5816,8 @@ fn build_pending_specializations<'a>( derived_module: &derived_module, }; + let layout_cache_snapshot = layout_cache.snapshot(); + // Add modules' decls to Procs for index in 0..declarations.len() { use roc_can::expr::DeclarationTag::*; @@ -5760,7 +5825,7 @@ fn build_pending_specializations<'a>( let symbol = declarations.symbols[index].value; let expr_var = declarations.variables[index]; - let is_host_exposed = exposed_to_host.values.contains_key(&symbol); + let is_host_exposed = exposed_to_host.top_level_values.contains_key(&symbol); // TODO remove clones (with drain) let annotation = declarations.annotations[index].clone(); @@ -6130,6 +6195,8 @@ fn build_pending_specializations<'a>( } } + layout_cache.rollback_to(layout_cache_snapshot); + procs_base.module_thunks = module_thunks.into_bump_slice(); let find_specializations_end = Instant::now(); @@ -6665,7 +6732,7 @@ fn to_parse_problem_report<'a>( buf } -fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> String { +fn to_missing_platform_report(module_id: ModuleId, other: &PlatformPath) -> String { use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; use ven_pretty::DocAllocator; use PlatformPath::*; diff --git a/crates/compiler/module/Cargo.toml b/crates/compiler/module/Cargo.toml index 938ad44b83..6be6b4e716 100644 --- a/crates/compiler/module/Cargo.toml +++ b/crates/compiler/module/Cargo.toml @@ -1,20 +1,22 @@ [package] name = "roc_module" -version = "0.0.1" -authors = ["The Roc Contributors"] -edition = "2021" -license = "UPL-1.0" description = "Implements data structures used for efficiently representing unique modules and identifiers in Roc programs." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -roc_region = { path = "../region" } -roc_ident = { path = "../ident" } roc_collections = { path = "../collections" } -roc_error_macros = {path = "../../error_macros"} +roc_error_macros = { path = "../../error_macros" } +roc_ident = { path = "../ident" } +roc_region = { path = "../region" } + bumpalo.workspace = true -static_assertions.workspace = true snafu.workspace = true +static_assertions.workspace = true [features] -default = [] debug-symbols = [] +default = [] diff --git a/crates/compiler/module/src/ident.rs b/crates/compiler/module/src/ident.rs index bcf7d49cb0..5cbe019736 100644 --- a/crates/compiler/module/src/ident.rs +++ b/crates/compiler/module/src/ident.rs @@ -1,5 +1,5 @@ pub use roc_ident::IdentStr; -use std::fmt; +use std::fmt::{self, Debug}; use crate::symbol::PQModuleName; @@ -74,7 +74,7 @@ pub type TagIdIntType = u16; /// 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.) -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TagName(pub Uppercase); roc_error_macros::assert_sizeof_non_wasm!(TagName, 16); @@ -86,6 +86,12 @@ impl TagName { } } +impl Debug for TagName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.0, f) + } +} + impl From<&str> for TagName { fn from(string: &str) -> Self { Self(string.into()) @@ -161,7 +167,7 @@ impl From for Box { impl fmt::Display for ModuleName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + fmt::Display::fmt(&self.0, f) } } @@ -298,7 +304,7 @@ impl From for Box { impl fmt::Display for Ident { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + fmt::Display::fmt(&self.0, f) } } @@ -317,7 +323,7 @@ impl fmt::Debug for Lowercase { impl fmt::Display for Lowercase { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + fmt::Display::fmt(&self.0, f) } } @@ -336,6 +342,6 @@ impl fmt::Debug for Uppercase { impl fmt::Display for Uppercase { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) + fmt::Display::fmt(&self.0, f) } } diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index 4f3c343bc7..7c72a0ec3d 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -32,9 +32,11 @@ pub enum LowLevel { StrGetCapacity, StrWithCapacity, StrGraphemes, + StrReleaseExcessCapacity, ListLen, ListWithCapacity, ListReserve, + ListReleaseExcessCapacity, ListAppendUnsafe, ListGetUnsafe, ListReplaceUnsafe, @@ -90,6 +92,8 @@ pub enum LowLevel { NumAsin, NumBytesToU16, NumBytesToU32, + NumBytesToU64, + NumBytesToU128, NumBitwiseAnd, NumBitwiseXor, NumBitwiseOr, @@ -101,6 +105,9 @@ pub enum LowLevel { NumToIntChecked, NumToFloatChecked, NumToStr, + NumCountLeadingZeroBits, + NumCountTrailingZeroBits, + NumCountOneBits, Eq, NotEq, And, @@ -253,10 +260,12 @@ map_symbol_to_lowlevel! { StrGetCapacity <= STR_CAPACITY, StrWithCapacity <= STR_WITH_CAPACITY, StrGraphemes <= STR_GRAPHEMES, + StrReleaseExcessCapacity <= STR_RELEASE_EXCESS_CAPACITY, ListLen <= LIST_LEN, ListGetCapacity <= LIST_CAPACITY, ListWithCapacity <= LIST_WITH_CAPACITY, ListReserve <= LIST_RESERVE, + ListReleaseExcessCapacity <= LIST_RELEASE_EXCESS_CAPACITY, ListIsUnique <= LIST_IS_UNIQUE, ListAppendUnsafe <= LIST_APPEND_UNSAFE, ListPrepend <= LIST_PREPEND, @@ -305,6 +314,8 @@ map_symbol_to_lowlevel! { NumAsin <= NUM_ASIN, NumBytesToU16 <= NUM_BYTES_TO_U16_LOWLEVEL, NumBytesToU32 <= NUM_BYTES_TO_U32_LOWLEVEL, + NumBytesToU64 <= NUM_BYTES_TO_U64_LOWLEVEL, + NumBytesToU128 <= NUM_BYTES_TO_U128_LOWLEVEL, NumBitwiseAnd <= NUM_BITWISE_AND, NumBitwiseXor <= NUM_BITWISE_XOR, NumBitwiseOr <= NUM_BITWISE_OR, @@ -312,6 +323,9 @@ map_symbol_to_lowlevel! { NumShiftRightBy <= NUM_SHIFT_RIGHT, NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL, NumToStr <= NUM_TO_STR, + NumCountLeadingZeroBits <= NUM_COUNT_LEADING_ZERO_BITS, + NumCountTrailingZeroBits <= NUM_COUNT_TRAILING_ZERO_BITS, + NumCountOneBits <= NUM_COUNT_ONE_BITS, Eq <= BOOL_STRUCTURAL_EQ, NotEq <= BOOL_STRUCTURAL_NOT_EQ, And <= BOOL_AND, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 4ab92ec50c..4edc38e50d 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1189,63 +1189,70 @@ define_builtins! { 88 NUM_DEC: "Dec" exposed_type=true // the Num.Dectype alias 89 NUM_BYTES_TO_U16: "bytesToU16" 90 NUM_BYTES_TO_U32: "bytesToU32" - 91 NUM_CAST_TO_NAT: "#castToNat" - 92 NUM_DIV_CEIL: "divCeil" - 93 NUM_DIV_CEIL_CHECKED: "divCeilChecked" - 94 NUM_TO_STR: "toStr" - 95 NUM_MIN_I8: "minI8" - 96 NUM_MAX_I8: "maxI8" - 97 NUM_MIN_U8: "minU8" - 98 NUM_MAX_U8: "maxU8" - 99 NUM_MIN_I16: "minI16" - 100 NUM_MAX_I16: "maxI16" - 101 NUM_MIN_U16: "minU16" - 102 NUM_MAX_U16: "maxU16" - 103 NUM_MIN_I32: "minI32" - 104 NUM_MAX_I32: "maxI32" - 105 NUM_MIN_U32: "minU32" - 106 NUM_MAX_U32: "maxU32" - 107 NUM_MIN_I64: "minI64" - 108 NUM_MAX_I64: "maxI64" - 109 NUM_MIN_U64: "minU64" - 110 NUM_MAX_U64: "maxU64" - 111 NUM_MIN_I128: "minI128" - 112 NUM_MAX_I128: "maxI128" - 113 NUM_MIN_U128: "minU128" - 114 NUM_MAX_U128: "maxU128" - 115 NUM_TO_I8: "toI8" - 116 NUM_TO_I8_CHECKED: "toI8Checked" - 117 NUM_TO_I16: "toI16" - 118 NUM_TO_I16_CHECKED: "toI16Checked" - 119 NUM_TO_I32: "toI32" - 120 NUM_TO_I32_CHECKED: "toI32Checked" - 121 NUM_TO_I64: "toI64" - 122 NUM_TO_I64_CHECKED: "toI64Checked" - 123 NUM_TO_I128: "toI128" - 124 NUM_TO_I128_CHECKED: "toI128Checked" - 125 NUM_TO_U8: "toU8" - 126 NUM_TO_U8_CHECKED: "toU8Checked" - 127 NUM_TO_U16: "toU16" - 128 NUM_TO_U16_CHECKED: "toU16Checked" - 129 NUM_TO_U32: "toU32" - 130 NUM_TO_U32_CHECKED: "toU32Checked" - 131 NUM_TO_U64: "toU64" - 132 NUM_TO_U64_CHECKED: "toU64Checked" - 133 NUM_TO_U128: "toU128" - 134 NUM_TO_U128_CHECKED: "toU128Checked" - 135 NUM_TO_NAT: "toNat" - 136 NUM_TO_NAT_CHECKED: "toNatChecked" - 137 NUM_TO_F32: "toF32" - 138 NUM_TO_F32_CHECKED: "toF32Checked" - 139 NUM_TO_F64: "toF64" - 140 NUM_TO_F64_CHECKED: "toF64Checked" - 141 NUM_MAX_F64: "maxF64" - 142 NUM_MIN_F64: "minF64" - 143 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel" - 144 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel" - 145 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel" - 146 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel" - 147 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel" + 91 NUM_BYTES_TO_U64: "bytesToU64" + 92 NUM_BYTES_TO_U128: "bytesToU128" + 93 NUM_CAST_TO_NAT: "#castToNat" + 94 NUM_DIV_CEIL: "divCeil" + 95 NUM_DIV_CEIL_CHECKED: "divCeilChecked" + 96 NUM_TO_STR: "toStr" + 97 NUM_MIN_I8: "minI8" + 98 NUM_MAX_I8: "maxI8" + 99 NUM_MIN_U8: "minU8" + 100 NUM_MAX_U8: "maxU8" + 101 NUM_MIN_I16: "minI16" + 102 NUM_MAX_I16: "maxI16" + 103 NUM_MIN_U16: "minU16" + 104 NUM_MAX_U16: "maxU16" + 105 NUM_MIN_I32: "minI32" + 106 NUM_MAX_I32: "maxI32" + 107 NUM_MIN_U32: "minU32" + 108 NUM_MAX_U32: "maxU32" + 109 NUM_MIN_I64: "minI64" + 110 NUM_MAX_I64: "maxI64" + 111 NUM_MIN_U64: "minU64" + 112 NUM_MAX_U64: "maxU64" + 113 NUM_MIN_I128: "minI128" + 114 NUM_MAX_I128: "maxI128" + 115 NUM_MIN_U128: "minU128" + 116 NUM_MAX_U128: "maxU128" + 117 NUM_TO_I8: "toI8" + 118 NUM_TO_I8_CHECKED: "toI8Checked" + 119 NUM_TO_I16: "toI16" + 120 NUM_TO_I16_CHECKED: "toI16Checked" + 121 NUM_TO_I32: "toI32" + 122 NUM_TO_I32_CHECKED: "toI32Checked" + 123 NUM_TO_I64: "toI64" + 124 NUM_TO_I64_CHECKED: "toI64Checked" + 125 NUM_TO_I128: "toI128" + 126 NUM_TO_I128_CHECKED: "toI128Checked" + 127 NUM_TO_U8: "toU8" + 128 NUM_TO_U8_CHECKED: "toU8Checked" + 129 NUM_TO_U16: "toU16" + 130 NUM_TO_U16_CHECKED: "toU16Checked" + 131 NUM_TO_U32: "toU32" + 132 NUM_TO_U32_CHECKED: "toU32Checked" + 133 NUM_TO_U64: "toU64" + 134 NUM_TO_U64_CHECKED: "toU64Checked" + 135 NUM_TO_U128: "toU128" + 136 NUM_TO_U128_CHECKED: "toU128Checked" + 137 NUM_TO_NAT: "toNat" + 138 NUM_TO_NAT_CHECKED: "toNatChecked" + 139 NUM_TO_F32: "toF32" + 140 NUM_TO_F32_CHECKED: "toF32Checked" + 141 NUM_TO_F64: "toF64" + 142 NUM_TO_F64_CHECKED: "toF64Checked" + 143 NUM_MAX_F64: "maxF64" + 144 NUM_MIN_F64: "minF64" + 145 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel" + 146 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel" + 147 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel" + 148 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel" + 149 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel" + 150 NUM_BYTES_TO_U64_LOWLEVEL: "bytesToU64Lowlevel" + 151 NUM_BYTES_TO_U128_LOWLEVEL: "bytesToU128Lowlevel" + 152 NUM_COUNT_LEADING_ZERO_BITS: "countLeadingZeroBits" + 153 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits" + 154 NUM_COUNT_ONE_BITS: "countOneBits" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias @@ -1319,6 +1326,8 @@ define_builtins! { 53 STR_WITH_CAPACITY: "withCapacity" 54 STR_WITH_PREFIX: "withPrefix" 55 STR_GRAPHEMES: "graphemes" + 56 STR_IS_VALID_SCALAR: "isValidScalar" + 57 STR_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity" } 6 LIST: "List" => { 0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias @@ -1401,6 +1410,8 @@ define_builtins! { 77 LIST_COUNT_IF: "countIf" 78 LIST_WALK_FROM: "walkFrom" 79 LIST_WALK_FROM_UNTIL: "walkFromUntil" + 80 LIST_ITER_HELP: "iterHelp" + 81 LIST_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity" } 7 RESULT: "Result" => { 0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias @@ -1489,11 +1500,12 @@ define_builtins! { 18 ENCODE_STRING: "string" 19 ENCODE_LIST: "list" 20 ENCODE_RECORD: "record" - 21 ENCODE_TAG: "tag" - 22 ENCODE_CUSTOM: "custom" - 23 ENCODE_APPEND_WITH: "appendWith" - 24 ENCODE_APPEND: "append" - 25 ENCODE_TO_BYTES: "toBytes" + 21 ENCODE_TUPLE: "tuple" + 22 ENCODE_TAG: "tag" + 23 ENCODE_CUSTOM: "custom" + 24 ENCODE_APPEND_WITH: "appendWith" + 25 ENCODE_APPEND: "append" + 26 ENCODE_TO_BYTES: "toBytes" } 12 DECODE: "Decode" => { 0 DECODE_DECODE_ERROR: "DecodeError" exposed_type=true @@ -1519,11 +1531,12 @@ define_builtins! { 20 DECODE_STRING: "string" 21 DECODE_LIST: "list" 22 DECODE_RECORD: "record" - 23 DECODE_CUSTOM: "custom" - 24 DECODE_DECODE_WITH: "decodeWith" - 25 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial" - 26 DECODE_FROM_BYTES: "fromBytes" - 27 DECODE_MAP_RESULT: "mapResult" + 23 DECODE_TUPLE: "tuple" + 24 DECODE_CUSTOM: "custom" + 25 DECODE_DECODE_WITH: "decodeWith" + 26 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial" + 27 DECODE_FROM_BYTES: "fromBytes" + 28 DECODE_MAP_RESULT: "mapResult" } 13 HASH: "Hash" => { 0 HASH_HASH_ABILITY: "Hash" exposed_type=true @@ -1535,16 +1548,17 @@ define_builtins! { 6 HASH_ADD_U32: "addU32" 7 HASH_ADD_U64: "addU64" 8 HASH_ADD_U128: "addU128" - 9 HASH_HASH_I8: "hashI8" - 10 HASH_HASH_I16: "hashI16" - 11 HASH_HASH_I32: "hashI32" - 12 HASH_HASH_I64: "hashI64" - 13 HASH_HASH_I128: "hashI128" - 14 HASH_HASH_NAT: "hashNat" - 15 HASH_COMPLETE: "complete" - 16 HASH_HASH_STR_BYTES: "hashStrBytes" - 17 HASH_HASH_LIST: "hashList" - 18 HASH_HASH_UNORDERED: "hashUnordered" + 9 HASH_HASH_BOOL: "hashBool" + 10 HASH_HASH_I8: "hashI8" + 11 HASH_HASH_I16: "hashI16" + 12 HASH_HASH_I32: "hashI32" + 13 HASH_HASH_I64: "hashI64" + 14 HASH_HASH_I128: "hashI128" + 15 HASH_HASH_NAT: "hashNat" + 16 HASH_COMPLETE: "complete" + 17 HASH_HASH_STR_BYTES: "hashStrBytes" + 18 HASH_HASH_LIST: "hashList" + 19 HASH_HASH_UNORDERED: "hashUnordered" } 14 JSON: "Json" => { 0 JSON_JSON: "Json" diff --git a/crates/compiler/mono/Cargo.toml b/crates/compiler/mono/Cargo.toml index 73ce78d7a0..44887a5b8f 100644 --- a/crates/compiler/mono/Cargo.toml +++ b/crates/compiler/mono/Cargo.toml @@ -1,32 +1,33 @@ [package] name = "roc_mono" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Roc's main intermediate representation (IR), which is responsible for monomorphization, defunctionalization, inserting ref-count instructions, and transforming a Roc program into a form that is easy to consume by a backend." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -roc_collections = { path = "../collections" } -roc_exhaustive = { path = "../exhaustive" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_types = { path = "../types" } -roc_can = { path = "../can" } -roc_derive_key = { path = "../derive_key" } -roc_derive = { path = "../derive" } -roc_late_solve = { path = "../late_solve" } -roc_std = { path = "../../roc_std" } -roc_problem = { path = "../problem" } roc_builtins = { path = "../builtins" } +roc_can = { path = "../can" } +roc_collections = { path = "../collections" } +roc_debug_flags = { path = "../debug_flags" } +roc_derive = { path = "../derive" } +roc_derive_key = { path = "../derive_key" } +roc_error_macros = { path = "../../error_macros" } +roc_exhaustive = { path = "../exhaustive" } +roc_late_solve = { path = "../late_solve" } +roc_module = { path = "../module" } +roc_problem = { path = "../problem" } +roc_region = { path = "../region" } +roc_std = { path = "../../roc_std" } roc_target = { path = "../roc_target" } -roc_error_macros = {path="../../error_macros"} -roc_debug_flags = {path="../debug_flags"} roc_tracing = { path = "../../tracing" } +roc_types = { path = "../types" } ven_pretty = { path = "../../vendor/pretty" } +bitvec.workspace = true bumpalo.workspace = true hashbrown.workspace = true -static_assertions.workspace = true -bitvec.workspace = true parking_lot.workspace = true +static_assertions.workspace = true diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index f6e19109b1..f53e354175 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -100,8 +100,14 @@ pub fn infer_borrow<'a>( // host-exposed functions must always own their arguments. let is_host_exposed = host_exposed_procs.contains(&key.0); - let param_offset = param_map.get_param_offset(key.0, key.1); - env.collect_proc(&mut param_map, proc, param_offset, is_host_exposed); + let param_offset = param_map.get_param_offset(interner, key.0, key.1); + env.collect_proc( + interner, + &mut param_map, + proc, + param_offset, + is_host_exposed, + ); } if !env.modified { @@ -167,6 +173,7 @@ impl<'a> DeclarationToIndex<'a> { fn get_param_offset( &self, + interner: &STLayoutInterner<'a>, needle_symbol: Symbol, needle_layout: ProcLayout<'a>, ) -> ParamOffset { @@ -181,12 +188,14 @@ impl<'a> DeclarationToIndex<'a> { .elements .iter() .filter_map(|(Declaration { symbol, layout }, _)| { - (*symbol == needle_symbol).then_some(layout) + (*symbol == needle_symbol) + .then_some(layout) + .map(|l| l.dbg_deep(interner)) }) .collect::>(); unreachable!( "symbol/layout {:?} {:#?} combo must be in DeclarationToIndex\nHowever {} similar layouts were found:\n{:#?}", - needle_symbol, needle_layout, similar.len(), similar + needle_symbol, needle_layout.dbg_deep(interner), similar.len(), similar, ) } } @@ -206,13 +215,24 @@ pub struct ParamMap<'a> { } impl<'a> ParamMap<'a> { - pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset { - self.declaration_to_index.get_param_offset(symbol, layout) + pub fn get_param_offset( + &self, + interner: &STLayoutInterner<'a>, + symbol: Symbol, + layout: ProcLayout<'a>, + ) -> ParamOffset { + self.declaration_to_index + .get_param_offset(interner, symbol, layout) } - pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&[Param<'a>]> { + pub fn get_symbol( + &self, + interner: &STLayoutInterner<'a>, + symbol: Symbol, + layout: ProcLayout<'a>, + ) -> Option<&[Param<'a>]> { // let index: usize = self.declaration_to_index[&(symbol, layout)].into(); - let index: usize = self.get_param_offset(symbol, layout).into(); + let index: usize = self.get_param_offset(interner, symbol, layout).into(); self.declarations.get(index..index + layout.arguments.len()) } @@ -292,7 +312,7 @@ impl<'a> ParamMap<'a> { // return; // } - let index: usize = self.get_param_offset(key.0, key.1).into(); + let index: usize = self.get_param_offset(interner, key.0, key.1).into(); for (i, param) in Self::init_borrow_args(arena, interner, proc.args) .iter() @@ -312,7 +332,7 @@ impl<'a> ParamMap<'a> { proc: &Proc<'a>, key: (Symbol, ProcLayout<'a>), ) { - let index: usize = self.get_param_offset(key.0, key.1).into(); + let index: usize = self.get_param_offset(interner, key.0, key.1).into(); for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args) .iter() @@ -534,7 +554,13 @@ impl<'a> BorrowInfState<'a> { /// /// and determines whether z and which of the symbols used in e /// must be taken as owned parameters - fn collect_call(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &crate::ir::Call<'a>) { + fn collect_call( + &mut self, + interner: &STLayoutInterner<'a>, + param_map: &mut ParamMap<'a>, + z: Symbol, + e: &crate::ir::Call<'a>, + ) { use crate::ir::CallType::*; let crate::ir::Call { @@ -553,7 +579,7 @@ impl<'a> BorrowInfState<'a> { // get the borrow signature of the applied function let ps = param_map - .get_symbol(name.name(), top_level) + .get_symbol(interner, name.name(), top_level) .expect("function is defined"); // the return value will be owned @@ -595,11 +621,14 @@ impl<'a> BorrowInfState<'a> { niche: passed_function.name.niche(), }; - let function_ps = - match param_map.get_symbol(passed_function.name.name(), closure_layout) { - Some(function_ps) => function_ps, - None => unreachable!(), - }; + let function_ps = match param_map.get_symbol( + interner, + passed_function.name.name(), + closure_layout, + ) { + Some(function_ps) => function_ps, + None => unreachable!(), + }; match op { ListMap { xs } => { @@ -671,7 +700,13 @@ impl<'a> BorrowInfState<'a> { } } - fn collect_expr(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &Expr<'a>) { + fn collect_expr( + &mut self, + interner: &STLayoutInterner<'a>, + param_map: &mut ParamMap<'a>, + z: Symbol, + e: &Expr<'a>, + ) { use Expr::*; match e { @@ -724,7 +759,7 @@ impl<'a> BorrowInfState<'a> { self.own_var(z); } - Call(call) => self.collect_call(param_map, z, call), + Call(call) => self.collect_call(interner, param_map, z, call), Literal(_) | RuntimeErrorFunction(_) => {} @@ -757,6 +792,7 @@ impl<'a> BorrowInfState<'a> { #[allow(clippy::many_single_char_names)] fn preserve_tail_call( &mut self, + interner: &STLayoutInterner<'a>, param_map: &mut ParamMap<'a>, x: Symbol, v: &Expr<'a>, @@ -782,7 +818,7 @@ impl<'a> BorrowInfState<'a> { if self.current_proc == g.name() && x == *z { // anonymous functions (for which the ps may not be known) // can never be tail-recursive, so this is fine - if let Some(ps) = param_map.get_symbol(g.name(), top_level) { + if let Some(ps) = param_map.get_symbol(interner, g.name(), top_level) { self.own_params_using_args(ys, ps) } } @@ -801,7 +837,12 @@ impl<'a> BorrowInfState<'a> { } } - fn collect_stmt(&mut self, param_map: &mut ParamMap<'a>, stmt: &Stmt<'a>) { + fn collect_stmt( + &mut self, + interner: &STLayoutInterner<'a>, + param_map: &mut ParamMap<'a>, + stmt: &Stmt<'a>, + ) { use Stmt::*; match stmt { @@ -813,11 +854,11 @@ impl<'a> BorrowInfState<'a> { } => { let old = self.param_set.clone(); self.update_param_set(ys); - self.collect_stmt(param_map, v); + self.collect_stmt(interner, param_map, v); self.param_set = old; self.update_param_map_join_point(param_map, *j); - self.collect_stmt(param_map, b); + self.collect_stmt(interner, param_map, b); } Let(x, v, _, mut b) => { @@ -830,17 +871,17 @@ impl<'a> BorrowInfState<'a> { stack.push((*symbol, expr)); } - self.collect_stmt(param_map, b); + self.collect_stmt(interner, param_map, b); let mut it = stack.into_iter().rev(); // collect the final expr, and see if we need to preserve a tail call let (x, v) = it.next().unwrap(); - self.collect_expr(param_map, x, v); - self.preserve_tail_call(param_map, x, v, b); + self.collect_expr(interner, param_map, x, v); + self.preserve_tail_call(interner, param_map, x, v, b); for (x, v) in it { - self.collect_expr(param_map, x, v); + self.collect_expr(interner, param_map, x, v); } } @@ -859,21 +900,21 @@ impl<'a> BorrowInfState<'a> { .. } => { for (_, _, b) in branches.iter() { - self.collect_stmt(param_map, b); + self.collect_stmt(interner, param_map, b); } - self.collect_stmt(param_map, default_branch.1); + self.collect_stmt(interner, param_map, default_branch.1); } Dbg { remainder, .. } => { - self.collect_stmt(param_map, remainder); + self.collect_stmt(interner, param_map, remainder); } Expect { remainder, .. } => { - self.collect_stmt(param_map, remainder); + self.collect_stmt(interner, param_map, remainder); } ExpectFx { remainder, .. } => { - self.collect_stmt(param_map, remainder); + self.collect_stmt(interner, param_map, remainder); } Refcounting(_, _) => unreachable!("these have not been introduced yet"), @@ -891,6 +932,7 @@ impl<'a> BorrowInfState<'a> { fn collect_proc( &mut self, + interner: &STLayoutInterner<'a>, param_map: &mut ParamMap<'a>, proc: &Proc<'a>, param_offset: ParamOffset, @@ -912,7 +954,7 @@ impl<'a> BorrowInfState<'a> { owned_entry.extend(params.iter().map(|p| p.symbol)); } - self.collect_stmt(param_map, &proc.body); + self.collect_stmt(interner, param_map, &proc.body); self.update_param_map_declaration(param_map, param_offset, proc.args.len()); self.param_set = old; @@ -974,6 +1016,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListSublist => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), + ListReleaseExcessCapacity => arena.alloc_slice_copy(&[owned]), + StrReleaseExcessCapacity => arena.alloc_slice_copy(&[owned]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), @@ -984,13 +1028,33 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { | NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), - NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked - | NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos - | NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => { - arena.alloc_slice_copy(&[irrelevant]) - } + NumToStr + | NumAbs + | NumNeg + | NumSin + | NumCos + | NumSqrtUnchecked + | NumLogUnchecked + | NumRound + | NumCeiling + | NumFloor + | NumToFrac + | Not + | NumIsFinite + | NumAtan + | NumAcos + | NumAsin + | NumIntCast + | NumToIntChecked + | NumToFloatCast + | NumToFloatChecked + | NumCountLeadingZeroBits + | NumCountTrailingZeroBits + | NumCountOneBits => arena.alloc_slice_copy(&[irrelevant]), NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), + NumBytesToU64 => arena.alloc_slice_copy(&[borrowed, irrelevant]), + NumBytesToU128 => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]), StrStartsWithScalar => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromUtf8Range => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), diff --git a/crates/compiler/mono/src/code_gen_help/mod.rs b/crates/compiler/mono/src/code_gen_help/mod.rs index e7170174c3..676d368932 100644 --- a/crates/compiler/mono/src/code_gen_help/mod.rs +++ b/crates/compiler/mono/src/code_gen_help/mod.rs @@ -125,16 +125,6 @@ impl<'a> CodeGenHelp<'a> { modify: &ModifyRc, following: &'a Stmt<'a>, ) -> (&'a Stmt<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) { - if !refcount::is_rc_implemented_yet(layout_interner, layout) { - // Just a warning, so we can decouple backend development from refcounting development. - // When we are closer to completion, we can change it to a panic. - println!( - "WARNING! MEMORY LEAK! Refcounting not yet implemented for Layout {:?}", - layout - ); - return (following, Vec::new_in(self.arena)); - } - let op = match modify { ModifyRc::Inc(..) => HelperOp::Inc, ModifyRc::Dec(_) => HelperOp::Dec, diff --git a/crates/compiler/mono/src/code_gen_help/refcount.rs b/crates/compiler/mono/src/code_gen_help/refcount.rs index 3f5bbcea99..e18fcee734 100644 --- a/crates/compiler/mono/src/code_gen_help/refcount.rs +++ b/crates/compiler/mono/src/code_gen_help/refcount.rs @@ -74,7 +74,7 @@ pub fn refcount_stmt<'a>( ModifyRc::DecRef(structure) => { match layout_interner.get(layout) { - // Str has no children, so we might as well do what we normally do and call the helper. + // Str has no children, so Dec is the same as DecRef. Layout::Builtin(Builtin::Str) => { ctx.op = HelperOp::Dec; refcount_stmt( @@ -127,8 +127,6 @@ pub fn refcount_generic<'a>( layout: InLayout<'a>, structure: Symbol, ) -> Stmt<'a> { - debug_assert!(is_rc_implemented_yet(layout_interner, layout)); - match layout_interner.get(layout) { Layout::Builtin(Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal) => { // Generate a dummy function that immediately returns Unit @@ -141,7 +139,6 @@ pub fn refcount_generic<'a>( ident_ids, ctx, layout_interner, - layout, elem_layout, structure, ), @@ -416,44 +413,6 @@ pub fn refcount_reset_proc_body<'a>( rc_ptr_stmt } -// Check if refcounting is implemented yet. In the long term, this will be deleted. -// In the short term, it helps us to skip refcounting and let it leak, so we can make -// progress incrementally. Kept in sync with generate_procs using assertions. -pub fn is_rc_implemented_yet<'a, I>(interner: &I, layout: InLayout<'a>) -> bool -where - I: LayoutInterner<'a>, -{ - use UnionLayout::*; - - match interner.get(layout) { - Layout::Builtin(Builtin::List(elem_layout)) => is_rc_implemented_yet(interner, elem_layout), - Layout::Builtin(_) => true, - Layout::Struct { field_layouts, .. } => field_layouts - .iter() - .all(|l| is_rc_implemented_yet(interner, *l)), - Layout::Union(union_layout) => match union_layout { - NonRecursive(tags) => tags - .iter() - .all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))), - Recursive(tags) => tags - .iter() - .all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))), - NonNullableUnwrapped(fields) => { - fields.iter().all(|l| is_rc_implemented_yet(interner, *l)) - } - NullableWrapped { other_tags, .. } => other_tags - .iter() - .all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))), - NullableUnwrapped { other_fields, .. } => other_fields - .iter() - .all(|l| is_rc_implemented_yet(interner, *l)), - }, - Layout::LambdaSet(lambda_set) => is_rc_implemented_yet(interner, lambda_set.representation), - Layout::RecursivePointer(_) => true, - Layout::Boxed(_) => true, - } -} - fn rc_return_stmt<'a>( root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds, @@ -765,7 +724,6 @@ fn refcount_list<'a>( ident_ids: &mut IdentIds, ctx: &mut Context<'a>, layout_interner: &mut STLayoutInterner<'a>, - layout: InLayout, elem_layout: InLayout<'a>, structure: Symbol, ) -> Stmt<'a> { @@ -773,8 +731,7 @@ fn refcount_list<'a>( let arena = root.arena; // A "Box" layout (heap pointer to a single list element) - let box_union_layout = UnionLayout::NonNullableUnwrapped(arena.alloc([elem_layout])); - let box_layout = layout_interner.insert(Layout::Union(box_union_layout)); + let box_layout = layout_interner.insert(Layout::Boxed(elem_layout)); // // Check if the list is empty @@ -814,7 +771,7 @@ fn refcount_list<'a>( // let rc_ptr = root.create_symbol(ident_ids, "rc_ptr"); - let alignment = layout_interner.alignment_bytes(layout); + let elem_alignment = layout_interner.alignment_bytes(elem_layout); let ret_stmt = rc_return_stmt(root, ident_ids, ctx); let modify_list = modify_refcount( @@ -822,7 +779,7 @@ fn refcount_list<'a>( ident_ids, ctx, rc_ptr, - alignment, + elem_alignment, arena.alloc(ret_stmt), ); @@ -845,7 +802,7 @@ fn refcount_list<'a>( layout_interner, elem_layout, LAYOUT_UNIT, - box_union_layout, + box_layout, len, elements, get_rc_and_modify_list, @@ -895,7 +852,7 @@ fn refcount_list_elems<'a>( layout_interner: &mut STLayoutInterner<'a>, elem_layout: InLayout<'a>, ret_layout: InLayout<'a>, - box_union_layout: UnionLayout<'a>, + box_layout: InLayout<'a>, length: Symbol, elements: Symbol, following: Stmt<'a>, @@ -955,17 +912,11 @@ fn refcount_list_elems<'a>( // Cast integer to box pointer let box_ptr = root.create_symbol(ident_ids, "box"); - let box_layout = layout_interner.insert(Layout::Union(box_union_layout)); let box_stmt = |next| let_lowlevel(arena, box_layout, box_ptr, PtrCast, &[addr], next); // Dereference the box pointer to get the current element let elem = root.create_symbol(ident_ids, "elem"); - let elem_expr = Expr::UnionAtIndex { - structure: box_ptr, - union_layout: box_union_layout, - tag_id: 0, - index: 0, - }; + let elem_expr = Expr::ExprUnbox { symbol: box_ptr }; let elem_stmt = |next| Stmt::Let(elem, elem_expr, elem_layout, next); // diff --git a/crates/compiler/mono/src/debug/checker.rs b/crates/compiler/mono/src/debug/checker.rs index d540b8a2c0..94114322ba 100644 --- a/crates/compiler/mono/src/debug/checker.rs +++ b/crates/compiler/mono/src/debug/checker.rs @@ -10,8 +10,7 @@ use crate::{ ModifyRc, Param, Proc, ProcLayout, Stmt, }, layout::{ - Builtin, InLayout, LambdaSet, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, - UnionLayout, + Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout, }, }; @@ -87,7 +86,7 @@ pub enum ProblemKind<'a> { structure: Symbol, def_line: usize, tag_id: u16, - union_layout: UnionLayout<'a>, + union_layout: InLayout<'a>, }, TagUnionStructIndexOOB { structure: Symbol, @@ -100,7 +99,7 @@ pub enum ProblemKind<'a> { structure: Symbol, def_line: usize, tag_id: u16, - union_layout: UnionLayout<'a>, + union_layout: InLayout<'a>, }, UnboxNotABox { symbol: Symbol, @@ -108,7 +107,7 @@ pub enum ProblemKind<'a> { }, CreatingTagIdNotInUnion { tag_id: u16, - union_layout: UnionLayout<'a>, + union_layout: InLayout<'a>, }, CreateTagPayloadMismatch { num_needed: usize, @@ -193,11 +192,12 @@ impl<'a, 'r> Ctx<'a, 'r> { r } - fn resolve(&mut self, mut layout: InLayout<'a>) -> InLayout<'a> { + fn resolve(&self, mut layout: InLayout<'a>) -> InLayout<'a> { // Note that we are more aggressive than the usual `runtime_representation` // here because we need strict equality, and so cannot unwrap lambda sets // lazily. loop { + layout = self.interner.chase_recursive_in(layout); match self.interner.get(layout) { Layout::LambdaSet(ls) => layout = ls.representation, _ => return layout, @@ -205,6 +205,12 @@ impl<'a, 'r> Ctx<'a, 'r> { } } + fn not_equiv(&mut self, layout1: InLayout<'a>, layout2: InLayout<'a>) -> bool { + !self + .interner + .equiv(self.resolve(layout1), self.resolve(layout2)) + } + fn insert(&mut self, symbol: Symbol, layout: InLayout<'a>) { if let Some((old_line, _)) = self.venv.insert(symbol, (self.line, layout)) { self.problem(ProblemKind::RedefinedSymbol { symbol, old_line }) @@ -237,7 +243,7 @@ impl<'a, 'r> Ctx<'a, 'r> { use_kind: UseKind, ) { if let Some(&(def_line, layout)) = self.venv.get(&symbol) { - if self.resolve(layout) != self.resolve(expected_layout) { + if self.not_equiv(layout, expected_layout) { self.problem(ProblemKind::SymbolUseMismatch { symbol, def_layout: layout, @@ -265,7 +271,7 @@ impl<'a, 'r> Ctx<'a, 'r> { match body { Stmt::Let(x, e, x_layout, rest) => { if let Some(e_layout) = self.check_expr(e) { - if self.resolve(e_layout) != self.resolve(*x_layout) { + if self.not_equiv(e_layout, *x_layout) { self.problem(ProblemKind::SymbolDefMismatch { symbol: *x, def_layout: *x_layout, @@ -390,8 +396,9 @@ impl<'a, 'r> Ctx<'a, 'r> { tag_id, arguments, } => { - self.check_tag_expr(tag_layout, tag_id, arguments); - Some(self.interner.insert(Layout::Union(tag_layout))) + let interned_layout = self.interner.insert(Layout::Union(tag_layout)); + self.check_tag_expr(interned_layout, tag_layout, tag_id, arguments); + Some(interned_layout) } Expr::Struct(syms) => { for sym in syms.iter() { @@ -415,7 +422,9 @@ impl<'a, 'r> Ctx<'a, 'r> { tag_id, union_layout, index, - } => self.check_union_at_index(structure, union_layout, tag_id, index), + } => self.with_sym_layout(structure, |ctx, _def_line, layout| { + ctx.check_union_at_index(structure, layout, union_layout, tag_id, index) + }), Expr::Array { elem_layout, elems } => { for elem in elems.iter() { match elem { @@ -503,6 +512,7 @@ impl<'a, 'r> Ctx<'a, 'r> { fn check_union_at_index( &mut self, structure: Symbol, + interned_union_layout: InLayout<'a>, union_layout: UnionLayout<'a>, tag_id: u16, index: u64, @@ -517,7 +527,7 @@ impl<'a, 'r> Ctx<'a, 'r> { structure, def_line, tag_id, - union_layout, + union_layout: interned_union_layout, }); None } @@ -532,12 +542,7 @@ impl<'a, 'r> Ctx<'a, 'r> { }); return None; } - let layout = resolve_recursive_layout( - ctx.arena, - ctx.interner, - payloads[index as usize], - union_layout, - ); + let layout = payloads[index as usize]; Some(layout) } } @@ -605,12 +610,18 @@ impl<'a, 'r> Ctx<'a, 'r> { } } - fn check_tag_expr(&mut self, union_layout: UnionLayout<'a>, tag_id: u16, arguments: &[Symbol]) { + fn check_tag_expr( + &mut self, + interned_union_layout: InLayout<'a>, + union_layout: UnionLayout<'a>, + tag_id: u16, + arguments: &[Symbol], + ) { match get_tag_id_payloads(union_layout, tag_id) { TagPayloads::IdNotInUnion => { self.problem(ProblemKind::CreatingTagIdNotInUnion { tag_id, - union_layout, + union_layout: interned_union_layout, }); } TagPayloads::Payloads(payloads) => { @@ -621,13 +632,7 @@ impl<'a, 'r> Ctx<'a, 'r> { }); } for (arg, wanted_layout) in arguments.iter().zip(payloads.iter()) { - let wanted_layout = resolve_recursive_layout( - self.arena, - self.interner, - *wanted_layout, - union_layout, - ); - self.check_sym_layout(*arg, wanted_layout, UseKind::TagPayloadArg); + self.check_sym_layout(*arg, *wanted_layout, UseKind::TagPayloadArg); } } } @@ -643,94 +648,6 @@ impl<'a, 'r> Ctx<'a, 'r> { } } -fn resolve_recursive_layout<'a>( - arena: &'a Bump, - interner: &mut STLayoutInterner<'a>, - layout: InLayout<'a>, - when_recursive: UnionLayout<'a>, -) -> InLayout<'a> { - macro_rules! go { - ($lay:expr) => { - resolve_recursive_layout(arena, interner, $lay, when_recursive) - }; - } - - // TODO check if recursive pointer not in recursive union - let layout = match interner.get(layout) { - Layout::RecursivePointer(_) => Layout::Union(when_recursive), - Layout::Union(union_layout) => match union_layout { - UnionLayout::NonRecursive(payloads) => { - let payloads = payloads.iter().map(|args| { - let args = args.iter().map(|lay| go!(*lay)); - &*arena.alloc_slice_fill_iter(args) - }); - let payloads = arena.alloc_slice_fill_iter(payloads); - Layout::Union(UnionLayout::NonRecursive(payloads)) - } - UnionLayout::Recursive(_) - | UnionLayout::NonNullableUnwrapped(_) - | UnionLayout::NullableWrapped { .. } - | UnionLayout::NullableUnwrapped { .. } => { - // This is the recursive layout. - // TODO will need fixing to be modified once we support multiple - // recursive pointers in one structure. - return layout; - } - }, - Layout::Boxed(inner) => { - let inner = go!(inner); - Layout::Boxed(inner) - } - Layout::Struct { - field_order_hash, - field_layouts, - } => { - let field_layouts = field_layouts - .iter() - .map(|lay| resolve_recursive_layout(arena, interner, *lay, when_recursive)); - let field_layouts = arena.alloc_slice_fill_iter(field_layouts); - Layout::Struct { - field_order_hash, - field_layouts, - } - } - Layout::Builtin(builtin) => match builtin { - Builtin::List(inner) => { - let inner = resolve_recursive_layout(arena, interner, inner, when_recursive); - Layout::Builtin(Builtin::List(inner)) - } - Builtin::Int(_) - | Builtin::Float(_) - | Builtin::Bool - | Builtin::Decimal - | Builtin::Str => return layout, - }, - Layout::LambdaSet(LambdaSet { - args, - ret, - set, - representation, - full_layout, - }) => { - let set = set.iter().map(|(symbol, captures)| { - let captures = captures.iter().map(|lay_in| go!(*lay_in)); - let captures = &*arena.alloc_slice_fill_iter(captures); - (*symbol, captures) - }); - let set = arena.alloc_slice_fill_iter(set); - Layout::LambdaSet(LambdaSet { - args, - ret, - set: arena.alloc(&*set), - representation, - full_layout, - }) - } - }; - - interner.insert(layout) -} - enum TagPayloads<'a> { IdNotInUnion, Payloads(&'a [InLayout<'a>]), diff --git a/crates/compiler/mono/src/debug/report.rs b/crates/compiler/mono/src/debug/report.rs index 177ccab0db..48576d3df1 100644 --- a/crates/compiler/mono/src/debug/report.rs +++ b/crates/compiler/mono/src/debug/report.rs @@ -5,7 +5,7 @@ use ven_pretty::{Arena, DocAllocator, DocBuilder}; use crate::{ ir::{Parens, ProcLayout}, - layout::{Layout, LayoutInterner}, + layout::LayoutInterner, }; use super::{ @@ -157,7 +157,7 @@ where f.concat([ format_symbol(f, interns, symbol), f.reflow(" defined here with layout "), - interner.to_doc(def_layout, f, Parens::NotNeeded), + interner.to_doc_top(def_layout, f), ]), )]; f.concat([ @@ -165,7 +165,7 @@ where f.reflow(" used as a "), f.reflow(format_use_kind(use_kind)), f.reflow(" here with layout "), - interner.to_doc(use_layout, f, Parens::NotNeeded), + interner.to_doc_top(use_layout, f), ]) } ProblemKind::SymbolDefMismatch { @@ -178,9 +178,9 @@ where f.concat([ format_symbol(f, interns, symbol), f.reflow(" is defined as "), - interner.to_doc(def_layout, f, Parens::NotNeeded), + interner.to_doc_top(def_layout, f), f.reflow(" but its initializer is "), - interner.to_doc(expr_layout, f, Parens::NotNeeded), + interner.to_doc_top(expr_layout, f), ]) } ProblemKind::BadSwitchConditionLayout { found_layout } => { @@ -188,7 +188,7 @@ where docs_before = vec![]; f.concat([ f.reflow("This switch condition is a "), - interner.to_doc(found_layout, f, Parens::NotNeeded), + interner.to_doc_top(found_layout, f), ]) } ProblemKind::DuplicateSwitchBranch {} => { @@ -324,7 +324,7 @@ where f.reflow("The union "), format_symbol(f, interns, structure), f.reflow(" defined here has layout "), - Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded), + interner.to_doc_top(union_layout, f), ]), )]; f.concat([f.reflow("which has no tag of id "), f.as_string(tag_id)]) @@ -367,7 +367,7 @@ where f.reflow("The union "), format_symbol(f, interns, structure), f.reflow(" defined here has layout "), - Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded), + interner.to_doc_top(union_layout, f), ]), )]; f.concat([ @@ -394,7 +394,7 @@ where f.reflow("The variant "), f.as_string(tag_id), f.reflow(" is outside the target union layout "), - Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded), + interner.to_doc_top(union_layout, f), ]) } ProblemKind::CreateTagPayloadMismatch { @@ -469,16 +469,16 @@ where let args = f.intersperse( arguments .iter() - .map(|a| interner.to_doc(*a, f, Parens::InFunction)), + .map(|a| interner.to_doc(*a, f, &mut Default::default(), Parens::InFunction)), f.reflow(", "), ); let fun = f.concat([ f.concat([f.reflow("("), args, f.reflow(")")]), f.reflow(" -> "), - interner.to_doc(result, f, Parens::NotNeeded), + interner.to_doc_top(result, f), ]); let niche = (f.text("(")) - .append(captures_niche.to_doc(f, interner)) + .append(captures_niche.to_doc(f, interner, &mut Default::default())) .append(f.text(")")); f.concat([fun, f.space(), niche]) } diff --git a/crates/compiler/mono/src/inc_dec.rs b/crates/compiler/mono/src/inc_dec.rs index eab6ec8acf..56b543f612 100644 --- a/crates/compiler/mono/src/inc_dec.rs +++ b/crates/compiler/mono/src/inc_dec.rs @@ -605,7 +605,7 @@ impl<'a, 'i> Context<'a, 'i> { // get the borrow signature let ps = self .param_map - .get_symbol(name.name(), top_level) + .get_symbol(self.layout_interner, name.name(), top_level) .expect("function is defined"); let v = Expr::Call(crate::ir::Call { @@ -653,10 +653,11 @@ impl<'a, 'i> Context<'a, 'i> { niche: passed_function.name.niche(), }; - let function_ps = match self - .param_map - .get_symbol(passed_function.name.name(), function_layout) - { + let function_ps = match self.param_map.get_symbol( + self.layout_interner, + passed_function.name.name(), + function_layout, + ) { Some(function_ps) => function_ps, None => unreachable!(), }; @@ -671,14 +672,14 @@ impl<'a, 'i> Context<'a, 'i> { match ownership { DataOwnedFunctionOwns | DataBorrowedFunctionOwns => { // elements have been consumed, must still consume the list itself - let rest = self.arena.alloc($stmt); + 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 rest = self.arena.alloc(stmt); let rc = Stmt::Refcounting(ModifyRc::Dec(argument), rest); stmt = self.arena.alloc(rc); @@ -1510,19 +1511,28 @@ pub fn visit_procs<'a, 'i>( }; for (key, proc) in procs.iter_mut() { - visit_proc(arena, &mut codegen, param_map, &ctx, proc, key.1); + visit_proc( + arena, + layout_interner, + &mut codegen, + param_map, + &ctx, + proc, + key.1, + ); } } fn visit_proc<'a, 'i>( arena: &'a Bump, + interner: &STLayoutInterner<'a>, codegen: &mut CodegenTools<'i>, param_map: &'a ParamMap<'a>, ctx: &Context<'a, 'i>, proc: &mut Proc<'a>, layout: ProcLayout<'a>, ) { - let params = match param_map.get_symbol(proc.name.name(), layout) { + let params = match param_map.get_symbol(interner, proc.name.name(), layout) { Some(slice) => slice, None => Vec::from_iter_in( proc.args.iter().cloned().map(|(layout, symbol)| Param { diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 0539bd5e05..d1ac1e6ab4 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -1,6 +1,7 @@ #![allow(clippy::manual_map)] use crate::borrow::Ownership; +use crate::ir::literal::{make_num_literal, IntOrFloatValue}; use crate::layout::{ self, Builtin, ClosureCallOptions, ClosureRepresentation, EnumDispatch, InLayout, LambdaName, LambdaSet, Layout, LayoutCache, LayoutInterner, LayoutProblem, Niche, RawFunctionLayout, @@ -8,9 +9,8 @@ use crate::layout::{ }; use bumpalo::collections::{CollectIn, Vec}; use bumpalo::Bump; -use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_can::abilities::SpecializationId; -use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup, IntValue}; +use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup}; use roc_can::module::ExposedByModule; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_collections::VecMap; @@ -22,7 +22,6 @@ use roc_debug_flags::{ }; use roc_derive::SharedDerivedModule; use roc_error_macros::{internal_error, todo_abilities}; -use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId}; use roc_late_solve::storage::{ExternalModuleStorage, ExternalModuleStorageSnapshot}; use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; @@ -39,6 +38,14 @@ use roc_types::subs::{ use std::collections::HashMap; use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; +use pattern::{from_can_pattern, store_pattern, Pattern}; + +pub use literal::{ListLiteralElement, Literal}; + +mod decision_tree; +mod literal; +mod pattern; + #[inline(always)] pub fn pretty_print_ir_symbols() -> bool { dbg_do!(ROC_PRINT_IR_AFTER_SPECIALIZATION, { @@ -305,12 +312,21 @@ pub struct Proc<'a> { pub host_exposed_layouts: HostExposedLayouts<'a>, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HostExposedLambdaSet<'a> { + pub id: LambdaSetId, + /// Symbol of the exposed function + pub symbol: Symbol, + pub proc_layout: ProcLayout<'a>, + pub raw_function_layout: RawFunctionLayout<'a>, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum HostExposedLayouts<'a> { NotHostExposed, HostExposed { rigids: BumpMap>, - aliases: BumpMap, RawFunctionLayout<'a>)>, + aliases: BumpMap>, }, } @@ -344,11 +360,9 @@ impl<'a> Proc<'a> { let args_doc = self.args.iter().map(|(layout, symbol)| { let arg_doc = symbol_to_doc(alloc, *symbol, pretty); if pretty_print_ir_symbols() { - arg_doc.append(alloc.reflow(": ")).append(interner.to_doc( - *layout, - alloc, - Parens::NotNeeded, - )) + arg_doc + .append(alloc.reflow(": ")) + .append(interner.to_doc_top(*layout, alloc)) } else { arg_doc } @@ -359,7 +373,7 @@ impl<'a> Proc<'a> { .text("procedure : ") .append(symbol_to_doc(alloc, self.name.name(), pretty)) .append(" ") - .append(interner.to_doc(self.ret_layout, alloc, Parens::NotNeeded)) + .append(interner.to_doc_top(self.ret_layout, alloc)) .append(alloc.hardline()) .append(alloc.text("procedure = ")) .append(symbol_to_doc(alloc, self.name.name(), pretty)) @@ -937,6 +951,16 @@ pub struct ProcsBase<'a> { pub imported_module_thunks: &'a [Symbol], } +impl<'a> ProcsBase<'a> { + pub fn get_host_exposed_symbols(&self) -> impl Iterator + '_ { + self.host_specializations + .symbol_or_lambdas + .iter() + .copied() + .map(|n| n.name()) + } +} + /// The current set of functions under specialization. They form a stack where the latest /// specialization to be seen is at the head of the stack. #[derive(Clone, Debug)] @@ -955,14 +979,16 @@ impl<'a> SpecializationStack<'a> { pub struct Procs<'a> { pub partial_procs: PartialProcs<'a>, 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>, specialization_stack: SpecializationStack<'a>, + + pub imported_module_thunks: &'a [Symbol], + pub module_thunks: &'a [Symbol], + pub host_exposed_symbols: &'a [Symbol], } impl<'a> Procs<'a> { @@ -970,14 +996,16 @@ impl<'a> Procs<'a> { Self { partial_procs: PartialProcs::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(), specialization_stack: SpecializationStack(Vec::with_capacity_in(16, arena)), + + imported_module_thunks: &[], + module_thunks: &[], + host_exposed_symbols: &[], } } @@ -1106,6 +1134,7 @@ impl<'a> Procs<'a> { let needs_suspended_specialization = self.symbol_needs_suspended_specialization(name.name()); + match ( &mut self.pending_specializations, needs_suspended_specialization, @@ -1178,7 +1207,6 @@ impl<'a> Procs<'a> { name, layout_cache, annotation, - &[], partial_proc_id, ) { Ok((proc, layout)) => { @@ -1271,7 +1299,6 @@ impl<'a> Procs<'a> { proc_name, layout_cache, fn_var, - Default::default(), partial_proc_id, ) { Ok((proc, raw_layout)) => { @@ -1481,7 +1508,7 @@ impl<'a, 'i> Env<'a, 'i> { right, )?; - layout_cache.invalidate(changed_variables.iter().copied()); + layout_cache.invalidate(self.subs, changed_variables.iter().copied()); external_specializations .into_iter() .for_each(|e| e.invalidate_cache(&changed_variables)); @@ -1689,43 +1716,6 @@ impl ModifyRc { } } -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Literal<'a> { - // Literals - /// stored as raw bytes rather than a number to avoid an alignment bump - Int([u8; 16]), - /// stored as raw bytes rather than a number to avoid an alignment bump - U128([u8; 16]), - Float(f64), - /// stored as raw bytes rather than a number to avoid an alignment bump - Decimal([u8; 16]), - Str(&'a str), - /// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool, - /// so they can (at least potentially) be emitted as 1-bit machine bools. - /// - /// So [True, False] compiles to this, and so do [A, B] and [Foo, Bar]. - /// However, a union like [True, False, Other Int] would not. - Bool(bool), - /// Closed tag unions containing between 3 and 256 tags (all of 0 arity) - /// compile to bytes, e.g. [Blue, Black, Red, Green, White] - Byte(u8), -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ListLiteralElement<'a> { - Literal(Literal<'a>), - Symbol(Symbol), -} - -impl<'a> ListLiteralElement<'a> { - pub fn to_symbol(&self) -> Option { - match self { - Self::Symbol(s) => Some(*s), - _ => None, - } - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct Call<'a> { pub call_type: CallType<'a>, @@ -2152,7 +2142,7 @@ impl<'a> Stmt<'a> { .text("let ") .append(symbol_to_doc(alloc, *symbol, pretty)) .append(" : ") - .append(interner.to_doc(*layout, alloc, Parens::NotNeeded)) + .append(interner.to_doc_top(*layout, alloc)) .append(" = ") .append(expr.to_doc(alloc, pretty)) .append(";") @@ -2311,15 +2301,6 @@ impl<'a> Stmt<'a> { String::from_utf8(w).unwrap() } - pub fn is_terminal(&self) -> bool { - use Stmt::*; - - matches!( - self, - Switch { .. } | Ret(_) | Jump(_, _) // TODO for Switch; is this the reason Lean only looks at the outermost `when`? - ) - } - pub fn if_then_else( arena: &'a Bump, condition_symbol: Symbol, @@ -2815,7 +2796,10 @@ fn pattern_to_when<'a>( (env.unique_symbol(), Loc::at_zero(RuntimeError(error))) } - AppliedTag { .. } | RecordDestructure { .. } | UnwrappedOpaque { .. } => { + AppliedTag { .. } + | RecordDestructure { .. } + | TupleDestructure { .. } + | UnwrappedOpaque { .. } => { let symbol = env.unique_symbol(); let wrapped_body = When { @@ -2909,7 +2893,7 @@ fn specialize_suspended<'a>( } }; - match specialize_variable(env, procs, name, layout_cache, var, &[], partial_proc) { + match specialize_variable(env, procs, name, layout_cache, var, partial_proc) { Ok((proc, raw_layout)) => { let proc_layout = ProcLayout::from_raw_named(env.arena, name, raw_layout); procs @@ -3002,15 +2986,8 @@ fn specialize_host_specializations<'a>( let offset_variable = StorageSubs::merge_into(store, env.subs); - for (symbol, variable, host_exposed_aliases) in it { - specialize_external_help( - env, - procs, - layout_cache, - symbol, - offset_variable(variable), - &host_exposed_aliases, - ) + for (symbol, variable, _host_exposed_aliases) in it { + specialize_external_help(env, procs, layout_cache, symbol, offset_variable(variable)) } } @@ -3035,7 +3012,7 @@ fn specialize_external_specializations<'a>( // duplicate specializations, and the insertion into a hash map // below will deduplicate them. - specialize_external_help(env, procs, layout_cache, symbol, imported_variable, &[]) + specialize_external_help(env, procs, layout_cache, symbol, imported_variable) } } } @@ -3046,7 +3023,6 @@ fn specialize_external_help<'a>( layout_cache: &mut LayoutCache<'a>, name: LambdaName<'a>, variable: Variable, - host_exposed_aliases: &[(Symbol, Variable)], ) { let partial_proc_id = match procs.partial_procs.symbol_to_id(name.name()) { Some(v) => v, @@ -3055,24 +3031,110 @@ fn specialize_external_help<'a>( } }; - let specialization_result = specialize_variable( - env, - procs, - name, - layout_cache, - variable, - host_exposed_aliases, - partial_proc_id, - ); + let specialization_result = + specialize_variable(env, procs, name, layout_cache, variable, partial_proc_id); match specialization_result { - Ok((proc, layout)) => { + Ok((mut proc, layout)) => { let top_level = ProcLayout::from_raw_named(env.arena, name, layout); if procs.is_module_thunk(name.name()) { debug_assert!(top_level.arguments.is_empty()); } + if procs.host_exposed_symbols.contains(&proc.name.name()) { + // layouts that are (transitively) used in the type of `mainForHost`. + let mut host_exposed_layouts: Vec<_> = top_level + .arguments + .iter() + .copied() + .chain([top_level.result]) + .collect_in(env.arena); + + // it is very likely we see the same types across functions, or in multiple arguments + host_exposed_layouts.sort(); + host_exposed_layouts.dedup(); + + for in_layout in host_exposed_layouts { + let layout = layout_cache.interner.get(in_layout); + + let all_glue_procs = generate_glue_procs( + env.home, + env.ident_ids, + env.arena, + &mut layout_cache.interner, + env.arena.alloc(layout), + ); + + // for now, getters are not processed here + let GlueProcs { + getters, + extern_names, + } = all_glue_procs; + + for (_layout, glue_procs) in getters { + for glue_proc in glue_procs { + procs.specialized.insert_specialized( + glue_proc.proc.name.name(), + glue_proc.proc_layout, + glue_proc.proc, + ); + } + } + + let mut aliases = BumpMap::default(); + + for (id, mut raw_function_layout) in extern_names { + let symbol = env.unique_symbol(); + let lambda_name = LambdaName::no_niche(symbol); + + // fix the recursion in the rocLovesRust example + if false { + raw_function_layout = match raw_function_layout { + RawFunctionLayout::Function(a, mut lambda_set, _) => { + lambda_set.ret = in_layout; + RawFunctionLayout::Function(a, lambda_set, in_layout) + } + RawFunctionLayout::ZeroArgumentThunk(x) => { + RawFunctionLayout::ZeroArgumentThunk(x) + } + }; + } + + let (key, (top_level, proc)) = generate_host_exposed_function( + env, + procs, + layout_cache, + lambda_name, + raw_function_layout, + ); + + procs + .specialized + .insert_specialized(symbol, top_level, proc); + + let hels = HostExposedLambdaSet { + id, + symbol, + proc_layout: top_level, + raw_function_layout, + }; + + aliases.insert(key, hels); + } + + match &mut proc.host_exposed_layouts { + HostExposedLayouts::HostExposed { aliases: old, .. } => old.extend(aliases), + hep @ HostExposedLayouts::NotHostExposed => { + *hep = HostExposedLayouts::HostExposed { + aliases, + rigids: Default::default(), + }; + } + } + } + } + procs .specialized .insert_specialized(name.name(), top_level, proc); @@ -3197,6 +3259,119 @@ fn rollback_typestate( } } +fn generate_host_exposed_function<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + lambda_name: LambdaName<'a>, + layout: RawFunctionLayout<'a>, +) -> (Symbol, (ProcLayout<'a>, Proc<'a>)) { + let function_name = lambda_name.name(); + + match layout { + RawFunctionLayout::Function(_, lambda_set, _) => { + let (proc, top_level) = generate_host_exposed_lambda_set( + env, + procs, + layout_cache, + function_name, + lambda_set, + ); + + (function_name, (top_level, proc)) + } + RawFunctionLayout::ZeroArgumentThunk(result) => { + let assigned = env.unique_symbol(); + let hole = env.arena.alloc(Stmt::Ret(assigned)); + let forced = force_thunk(env, function_name, result, assigned, hole); + + let lambda_name = LambdaName::no_niche(function_name); + let proc = Proc { + name: lambda_name, + args: &[], + body: forced, + closure_data_layout: None, + ret_layout: result, + is_self_recursive: SelfRecursive::NotSelfRecursive, + must_own_arguments: false, + host_exposed_layouts: HostExposedLayouts::NotHostExposed, + }; + + let top_level = ProcLayout::from_raw_named(env.arena, lambda_name, layout); + + (function_name, (top_level, proc)) + } + } +} + +fn generate_host_exposed_lambda_set<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + name: Symbol, + lambda_set: LambdaSet<'a>, +) -> (Proc<'a>, ProcLayout<'a>) { + let assigned = env.unique_symbol(); + + let argument_layouts = *lambda_set.args; + let return_layout = lambda_set.ret; + + let mut argument_symbols = Vec::with_capacity_in(argument_layouts.len(), env.arena); + let mut proc_arguments = Vec::with_capacity_in(argument_layouts.len() + 1, env.arena); + let mut top_level_arguments = Vec::with_capacity_in(argument_layouts.len() + 1, env.arena); + + for layout in *lambda_set.args { + let symbol = env.unique_symbol(); + + proc_arguments.push((*layout, symbol)); + + argument_symbols.push(symbol); + top_level_arguments.push(*layout); + } + + // the proc needs to take an extra closure argument + let lambda_set_layout = lambda_set.full_layout; + proc_arguments.push((lambda_set_layout, Symbol::ARG_CLOSURE)); + + // this should also be reflected in the TopLevel signature + top_level_arguments.push(lambda_set_layout); + + let hole = env.arena.alloc(Stmt::Ret(assigned)); + + let body = match_on_lambda_set( + env, + layout_cache, + procs, + lambda_set, + Symbol::ARG_CLOSURE, + argument_symbols.into_bump_slice(), + argument_layouts, + return_layout, + assigned, + hole, + ); + + let proc = Proc { + name: LambdaName::no_niche(name), + args: proc_arguments.into_bump_slice(), + body, + closure_data_layout: None, + ret_layout: return_layout, + is_self_recursive: SelfRecursive::NotSelfRecursive, + must_own_arguments: false, + host_exposed_layouts: HostExposedLayouts::NotHostExposed, + }; + + let top_level = ProcLayout::new( + env.arena, + top_level_arguments.into_bump_slice(), + Niche::NONE, + return_layout, + ); + + (proc, top_level) +} + /// Specialize a single proc. /// /// The caller should snapshot and rollback the type state before and after calling this function, @@ -3207,7 +3382,6 @@ fn specialize_proc_help<'a>( lambda_name: LambdaName<'a>, layout_cache: &mut LayoutCache<'a>, fn_var: Variable, - host_exposed_variables: &[(Symbol, Variable)], partial_proc_id: PartialProcId, ) -> Result, LayoutProblem> { let partial_proc = procs.partial_procs.get_id(partial_proc_id); @@ -3248,121 +3422,8 @@ fn specialize_proc_help<'a>( let body = partial_proc.body.clone(); let body_var = partial_proc.body_var; - // determine the layout of aliases/rigids exposed to the host - let host_exposed_layouts = if host_exposed_variables.is_empty() { - HostExposedLayouts::NotHostExposed - } else { - let mut aliases = BumpMap::new_in(env.arena); - - for (symbol, variable) in host_exposed_variables { - let layout = layout_cache - .raw_from_var(env.arena, *variable, env.subs) - .unwrap(); - - let name = env.unique_symbol(); - - match layout { - RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => { - let assigned = env.unique_symbol(); - - let mut argument_symbols = - Vec::with_capacity_in(argument_layouts.len(), env.arena); - let mut proc_arguments = - Vec::with_capacity_in(argument_layouts.len() + 1, env.arena); - let mut top_level_arguments = - Vec::with_capacity_in(argument_layouts.len() + 1, env.arena); - - for layout in argument_layouts { - let symbol = env.unique_symbol(); - - proc_arguments.push((*layout, symbol)); - - argument_symbols.push(symbol); - top_level_arguments.push(*layout); - } - - // the proc needs to take an extra closure argument - let lambda_set_layout = lambda_set.full_layout; - proc_arguments.push((lambda_set_layout, Symbol::ARG_CLOSURE)); - - // this should also be reflected in the TopLevel signature - top_level_arguments.push(lambda_set_layout); - - let hole = env.arena.alloc(Stmt::Ret(assigned)); - - let body = match_on_lambda_set( - env, - layout_cache, - procs, - lambda_set, - Symbol::ARG_CLOSURE, - argument_symbols.into_bump_slice(), - argument_layouts, - return_layout, - assigned, - hole, - ); - - let proc = Proc { - name: LambdaName::no_niche(name), - args: proc_arguments.into_bump_slice(), - body, - closure_data_layout: None, - ret_layout: return_layout, - is_self_recursive: SelfRecursive::NotSelfRecursive, - // must_own_arguments: false, - host_exposed_layouts: HostExposedLayouts::NotHostExposed, - }; - - let top_level = ProcLayout::new( - env.arena, - top_level_arguments.into_bump_slice(), - Niche::NONE, - return_layout, - ); - - procs.specialized.insert_specialized(name, top_level, proc); - - aliases.insert(*symbol, (name, top_level, layout)); - } - RawFunctionLayout::ZeroArgumentThunk(result) => { - let assigned = env.unique_symbol(); - let hole = env.arena.alloc(Stmt::Ret(assigned)); - let forced = force_thunk(env, lambda_name.name(), result, assigned, hole); - - let lambda_name = LambdaName::no_niche(name); - let proc = Proc { - name: lambda_name, - args: &[], - body: forced, - closure_data_layout: None, - ret_layout: result, - is_self_recursive: SelfRecursive::NotSelfRecursive, - // must_own_arguments: false, - host_exposed_layouts: HostExposedLayouts::NotHostExposed, - }; - - let top_level = ProcLayout::from_raw_named(env.arena, lambda_name, layout); - - procs.specialized.insert_specialized(name, top_level, proc); - - aliases.insert( - *symbol, - ( - name, - ProcLayout::new(env.arena, &[], Niche::NONE, result), - layout, - ), - ); - } - } - } - - HostExposedLayouts::HostExposed { - rigids: BumpMap::new_in(env.arena), - aliases, - } - }; + // host-exposed functions are tagged on later + let host_exposed_layouts = HostExposedLayouts::NotHostExposed; let mut specialized_body = from_can(env, body_var, body, procs, layout_cache); @@ -3445,6 +3506,7 @@ fn specialize_proc_help<'a>( UnionLayout::NonRecursive(_) | UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. } + | UnionLayout::NullableWrapped { .. } )); debug_assert_eq!(field_layouts.len(), captured.len()); @@ -3809,7 +3871,6 @@ fn specialize_variable<'a>( proc_name: LambdaName<'a>, layout_cache: &mut LayoutCache<'a>, fn_var: Variable, - host_exposed_variables: &[(Symbol, Variable)], partial_proc_id: PartialProcId, ) -> Result, SpecializeFailure<'a>> { let snapshot = snapshot_typestate(env.subs, procs, layout_cache); @@ -3839,15 +3900,8 @@ fn specialize_variable<'a>( procs.push_active_specialization(proc_name.name()); roc_tracing::debug!(?proc_name, ?fn_var, fn_content = ?roc_types::subs::SubsFmtContent(env.subs.get_content_without_compacting(fn_var), env.subs), "specialization start"); - let specialized = specialize_proc_help( - env, - procs, - proc_name, - layout_cache, - fn_var, - host_exposed_variables, - partial_proc_id, - ); + let specialized = + specialize_proc_help(env, procs, proc_name, layout_cache, fn_var, partial_proc_id); roc_tracing::debug!( ?proc_name, @@ -3931,6 +3985,33 @@ impl<'a> ProcLayout<'a> { } } } + + pub fn dbg_deep<'r, I: LayoutInterner<'a>>(&self, interner: &'r I) -> DbgProcLayout<'a, 'r, I> { + DbgProcLayout { + layout: *self, + interner, + } + } +} + +pub struct DbgProcLayout<'a, 'r, I: LayoutInterner<'a>> { + layout: ProcLayout<'a>, + interner: &'r I, +} + +impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgProcLayout<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let ProcLayout { + arguments, + result, + niche, + } = self.layout; + f.debug_struct("ProcLayout") + .field("arguments", &self.interner.dbg_deep_iter(arguments)) + .field("result", &self.interner.dbg_deep(result)) + .field("niche", &niche.dbg_deep(self.interner)) + .finish() + } } fn specialize_naked_symbol<'a>( @@ -4247,7 +4328,38 @@ pub fn with_hole<'a>( } } - Tuple { .. } => todo!("implement tuple hole"), + Tuple { + tuple_var, elems, .. + } => { + let sorted_elems_result = { + let mut layout_env = layout::Env::from_components( + layout_cache, + env.subs, + env.arena, + env.target_info, + ); + layout::sort_tuple_elems(&mut layout_env, tuple_var) + }; + let sorted_elems = match sorted_elems_result { + Ok(elems) => elems, + Err(_) => return runtime_error(env, "Can't create tuple with improper layout"), + }; + + // Hacky way to let us remove the owned elements from the vector, possibly out-of-order. + let mut elems = Vec::from_iter_in(elems.into_iter().map(Some), env.arena); + let take_elem_expr = move |index: usize| elems[index].take(); + + compile_struct_like( + env, + procs, + layout_cache, + sorted_elems, + take_elem_expr, + tuple_var, + hole, + assigned, + ) + } Record { record_var, @@ -4268,100 +4380,19 @@ pub fn with_hole<'a>( Err(_) => return runtime_error(env, "Can't create record with improper layout"), }; - let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena); - let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); + let take_field_expr = + move |field: Lowercase| fields.remove(&field).map(|f| (f.var, f.loc_expr)); - #[allow(clippy::enum_variant_names)] - enum Field { - // TODO: rename this since it can handle unspecialized expressions now too - FunctionOrUnspecialized(Symbol, Variable), - ValueSymbol, - Field(roc_can::expr::Field), - } - - for (label, variable, _) in sorted_fields.into_iter() { - // TODO how should function pointers be handled here? - use ReuseSymbol::*; - match fields.remove(&label) { - Some(field) => { - match can_reuse_symbol(env, procs, &field.loc_expr.value, field.var) { - Imported(symbol) - | LocalFunction(symbol) - | UnspecializedExpr(symbol) => { - field_symbols.push(symbol); - can_fields.push(Field::FunctionOrUnspecialized(symbol, variable)); - } - Value(symbol) => { - let reusable = procs.get_or_insert_symbol_specialization( - env, - layout_cache, - symbol, - field.var, - ); - field_symbols.push(reusable); - can_fields.push(Field::ValueSymbol); - } - NotASymbol => { - field_symbols.push(env.unique_symbol()); - can_fields.push(Field::Field(field)); - } - } - } - None => { - // this field was optional, but not given - continue; - } - } - } - - // creating a record from the var will unpack it if it's just a single field. - let layout = match layout_cache.from_var(env.arena, record_var, env.subs) { - Ok(layout) => layout, - Err(_) => return runtime_error(env, "Can't create record with improper layout"), - }; - - let field_symbols = field_symbols.into_bump_slice(); - - let mut stmt = if let [only_field] = field_symbols { - let mut hole = hole.clone(); - substitute_in_exprs(env.arena, &mut hole, assigned, *only_field); - hole - } else { - Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole) - }; - - for (opt_field, symbol) in can_fields.into_iter().rev().zip(field_symbols.iter().rev()) - { - match opt_field { - Field::ValueSymbol => { - // this symbol is already defined; nothing to do - } - Field::FunctionOrUnspecialized(symbol, variable) => { - stmt = specialize_symbol( - env, - procs, - layout_cache, - Some(variable), - symbol, - env.arena.alloc(stmt), - symbol, - ); - } - Field::Field(field) => { - stmt = with_hole( - env, - field.loc_expr.value, - field.var, - procs, - layout_cache, - *symbol, - env.arena.alloc(stmt), - ); - } - } - } - - stmt + compile_struct_like( + env, + procs, + layout_cache, + sorted_fields, + take_field_expr, + record_var, + hole, + assigned, + ) } EmptyRecord => let_empty_struct(assigned, hole), @@ -4681,49 +4712,18 @@ pub fn with_hole<'a>( } } - let record_symbol = possible_reuse_symbol_or_specialize( + compile_struct_like_access( env, procs, layout_cache, - &loc_expr.value, - record_var, - ); - - let mut stmt = match field_layouts.as_slice() { - [_] => { - let mut hole = hole.clone(); - substitute_in_exprs(env.arena, &mut hole, assigned, record_symbol); - - hole - } - _ => { - let expr = Expr::StructAtIndex { - index: index.expect("field not in its own type") as u64, - field_layouts: field_layouts.into_bump_slice(), - structure: record_symbol, - }; - - let layout = layout_cache - .from_var(env.arena, field_var, env.subs) - .unwrap_or_else(|err| { - panic!("TODO turn fn_var into a RuntimeError {:?}", err) - }); - - Stmt::Let(assigned, expr, layout, hole) - } - }; - - stmt = assign_to_symbol( - env, - procs, - layout_cache, - record_var, + field_layouts, + index.expect("field not in its own type") as _, *loc_expr, - record_symbol, - stmt, - ); - - stmt + record_var, + hole, + assigned, + field_var, + ) } RecordAccessor(accessor_data) => { @@ -4781,8 +4781,51 @@ pub fn with_hole<'a>( } } - TupleAccess { .. } => todo!(), - TupleAccessor(_) => todo!(), + TupleAccess { + tuple_var, + elem_var, + index: accessed_index, + loc_expr, + .. + } => { + let sorted_elems_result = { + let mut layout_env = layout::Env::from_components( + layout_cache, + env.subs, + env.arena, + env.target_info, + ); + layout::sort_tuple_elems(&mut layout_env, tuple_var) + }; + let sorted_elems = match sorted_elems_result { + Ok(fields) => fields, + Err(_) => return runtime_error(env, "Can't access tuple with improper layout"), + }; + let mut field_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena); + + let mut final_index = None; + + for (current, (index, _, elem_layout)) in sorted_elems.into_iter().enumerate() { + field_layouts.push(elem_layout); + + if index == accessed_index { + final_index = Some(current); + } + } + + compile_struct_like_access( + env, + procs, + layout_cache, + field_layouts, + final_index.expect("elem not in its own type") as u64, + *loc_expr, + tuple_var, + hole, + assigned, + elem_var, + ) + } OpaqueWrapFunction(wrap_fn_data) => { let opaque_var = wrap_fn_data.opaque_var; @@ -4920,9 +4963,6 @@ pub fn with_hole<'a>( _ => arena.alloc([record_layout]), }; - debug_assert_eq!(field_layouts.len(), symbols.len()); - debug_assert_eq!(fields.len(), symbols.len()); - if symbols.len() == 1 { // TODO we can probably special-case this more, skippiing the generation of // UpdateExisting @@ -5259,7 +5299,7 @@ pub fn with_hole<'a>( let resolved_proc = match resolved_proc { Resolved::Specialization(symbol) => symbol, - Resolved::NeedsGenerated(_) => { + Resolved::Derive(_) => { todo_abilities!("Generate impls for structural types") } }; @@ -5539,6 +5579,162 @@ pub fn with_hole<'a>( } } +/// Compiles an access into a tuple or record. +fn compile_struct_like_access<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + field_layouts: Vec<'a, InLayout<'a>>, + index: u64, + loc_expr: Loc, + struct_like_var: Variable, + hole: &'a Stmt<'a>, + assigned: Symbol, + elem_var: Variable, +) -> Stmt<'a> { + let struct_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &loc_expr.value, + struct_like_var, + ); + + let mut stmt = match field_layouts.as_slice() { + [_] => { + let mut hole = hole.clone(); + substitute_in_exprs(env.arena, &mut hole, assigned, struct_symbol); + + hole + } + _ => { + let expr = Expr::StructAtIndex { + index, + field_layouts: field_layouts.into_bump_slice(), + structure: struct_symbol, + }; + + let layout = layout_cache + .from_var(env.arena, elem_var, env.subs) + .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); + + Stmt::Let(assigned, expr, layout, hole) + } + }; + + stmt = assign_to_symbol( + env, + procs, + layout_cache, + struct_like_var, + loc_expr, + struct_symbol, + stmt, + ); + + stmt +} + +/// Compiles a record or a tuple. +// TODO: UnusedLayout is because `sort_record_fields` currently returns a three-tuple, but is, in +// fact, unneeded for the compilation. +fn compile_struct_like<'a, L, UnusedLayout>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + sorted_elems: Vec<(L, Variable, UnusedLayout)>, + mut take_elem_expr: impl FnMut(L) -> Option<(Variable, Box>)>, + struct_like_var: Variable, + hole: &'a Stmt<'a>, + assigned: Symbol, +) -> Stmt<'a> { + let mut elem_symbols = Vec::with_capacity_in(sorted_elems.len(), env.arena); + let mut can_elems = Vec::with_capacity_in(sorted_elems.len(), env.arena); + + #[allow(clippy::enum_variant_names)] + enum Field { + // TODO: rename this since it can handle unspecialized expressions now too + FunctionOrUnspecialized(Symbol, Variable), + ValueSymbol, + Field(Variable, Loc), + } + + for (index, variable, _) in sorted_elems.into_iter() { + // TODO how should function pointers be handled here? + use ReuseSymbol::*; + match take_elem_expr(index) { + Some((var, loc_expr)) => match can_reuse_symbol(env, procs, &loc_expr.value, var) { + Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => { + elem_symbols.push(symbol); + can_elems.push(Field::FunctionOrUnspecialized(symbol, variable)); + } + Value(symbol) => { + let reusable = + procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var); + elem_symbols.push(reusable); + can_elems.push(Field::ValueSymbol); + } + NotASymbol => { + elem_symbols.push(env.unique_symbol()); + can_elems.push(Field::Field(var, *loc_expr)); + } + }, + None => { + // this field was optional, but not given + continue; + } + } + } + + // creating a record from the var will unpack it if it's just a single field. + let layout = match layout_cache.from_var(env.arena, struct_like_var, env.subs) { + Ok(layout) => layout, + Err(_) => return runtime_error(env, "Can't create record with improper layout"), + }; + + let elem_symbols = elem_symbols.into_bump_slice(); + + let mut stmt = if let [only_field] = elem_symbols { + let mut hole = hole.clone(); + substitute_in_exprs(env.arena, &mut hole, assigned, *only_field); + hole + } else { + Stmt::Let(assigned, Expr::Struct(elem_symbols), layout, hole) + }; + + for (opt_field, symbol) in can_elems.into_iter().rev().zip(elem_symbols.iter().rev()) { + match opt_field { + Field::ValueSymbol => { + // this symbol is already defined; nothing to do + } + Field::FunctionOrUnspecialized(symbol, variable) => { + stmt = specialize_symbol( + env, + procs, + layout_cache, + Some(variable), + symbol, + env.arena.alloc(stmt), + symbol, + ); + } + Field::Field(var, loc_expr) => { + stmt = with_hole( + env, + loc_expr.value, + var, + procs, + layout_cache, + *symbol, + env.arena.alloc(stmt), + ); + } + } + } + + stmt +} + #[inline(always)] fn late_resolve_ability_specialization<'a>( env: &mut Env<'a, '_>, @@ -5564,12 +5760,17 @@ fn late_resolve_ability_specialization<'a>( solved, unspecialized, recursion_var: _, - ambient_function: _, + ambient_function, } = env.subs.get_lambda_set(*lambda_set); debug_assert!(unspecialized.is_empty()); let mut iter_lambda_set = solved.iter_all(); - debug_assert_eq!(iter_lambda_set.len(), 1); + debug_assert_eq!( + iter_lambda_set.len(), + 1, + "{:?}", + (env.subs.dbg(*lambda_set), env.subs.dbg(ambient_function)) + ); let spec_symbol_index = iter_lambda_set.next().unwrap().0; env.subs[spec_symbol_index] } else { @@ -5585,14 +5786,7 @@ fn late_resolve_ability_specialization<'a>( match specialization { Resolved::Specialization(symbol) => symbol, - Resolved::NeedsGenerated(var) => { - let derive_key = roc_derive_key::Derived::builtin( - member.try_into().expect("derived symbols must be builtins"), - env.subs, - var, - ) - .expect("specialization var not derivable!"); - + Resolved::Derive(derive_key) => { match derive_key { roc_derive_key::Derived::Immediate(imm) | roc_derive_key::Derived::SingleLambdaSetImmediate(imm) => { @@ -5927,7 +6121,7 @@ fn convert_tag_union<'a>( layout_cache.from_var(env.arena, variant_var, env.subs), "Wrapped" ); - let union_layout = match layout_cache.get_in(variant_layout) { + let union_layout = match layout_cache.interner.chase_recursive(variant_layout) { Layout::Union(ul) => ul, other => internal_error!( "unexpected layout {:?} for {:?}", @@ -6807,28 +7001,18 @@ fn from_can_when<'a>( } }; - use crate::decision_tree::Guard; + use decision_tree::Guard; let result = if let Some(loc_expr) = opt_guard { - let id = JoinPointId(env.unique_symbol()); - let symbol = env.unique_symbol(); - let jump = env.arena.alloc(Stmt::Jump(id, env.arena.alloc([symbol]))); - - let guard_stmt = with_hole( - env, - loc_expr.value, - Variable::BOOL, - procs, - layout_cache, - symbol, - jump, - ); + let guard_spec = GuardStmtSpec { + guard_expr: loc_expr.value, + identity: env.next_call_specialization_id(), + }; ( pattern.clone(), Guard::Guard { - id, pattern, - stmt: guard_stmt, + stmt_spec: guard_spec, }, branch_stmt, ) @@ -6845,7 +7029,7 @@ fn from_can_when<'a>( }); let mono_branches = Vec::from_iter_in(it, arena); - crate::decision_tree::optimize_when( + decision_tree::optimize_when( env, procs, layout_cache, @@ -6856,6 +7040,85 @@ fn from_can_when<'a>( ) } +/// A functor to generate IR for a guard under a `when` branch. +/// Used in the decision tree compiler, after building a decision tree and converting into IR. +/// +/// A guard might appear more than once in various places in the compiled decision tree, so the +/// functor here may be called more than once. As such, it implements clone, which duplicates the +/// guard AST for subsequent IR-regeneration. This is a bit wasteful, but in practice, guard ASTs +/// are quite small. Moreoever, they must be generated on a per-case basis, since the guard may +/// have calls or joins, whose specialization IDs and joinpoint IDs, respectively, must be unique. +#[derive(Debug, Clone)] +pub(crate) struct GuardStmtSpec { + guard_expr: roc_can::expr::Expr, + + /// Unique id to indentity identical guard statements, even across clones. + /// Needed so that we can implement [PartialEq] on this type. Re-uses call specialization IDs, + /// since the identity is kind of irrelevant. + identity: CallSpecId, +} + +impl PartialEq for GuardStmtSpec { + fn eq(&self, other: &Self) -> bool { + self.identity == other.identity + } +} + +impl std::hash::Hash for GuardStmtSpec { + fn hash(&self, state: &mut H) { + self.identity.id.hash(state); + } +} + +impl GuardStmtSpec { + /// Generates IR for the guard, and the joinpoint that the guard will jump to with the + /// calculated guard boolean value. + /// + /// The caller should create a joinpoint with the given joinpoint ID and decide how to branch + /// after the guard has been evaluated. + /// + /// The compiled guard statement expects the pattern before the guard to be destructed before the + /// returned statement. The caller should layer on the pattern destructuring, as bound from the + /// `when` condition value. + pub(crate) fn generate_guard_and_join<'a>( + self, + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + ) -> CompiledGuardStmt<'a> { + let Self { + guard_expr, + identity: _, + } = self; + + let join_point_id = JoinPointId(env.unique_symbol()); + let symbol = env.unique_symbol(); + let jump = env + .arena + .alloc(Stmt::Jump(join_point_id, env.arena.alloc([symbol]))); + + let stmt = with_hole( + env, + guard_expr, + Variable::BOOL, + procs, + layout_cache, + symbol, + jump, + ); + + CompiledGuardStmt { + join_point_id, + stmt, + } + } +} + +pub(crate) struct CompiledGuardStmt<'a> { + pub join_point_id: JoinPointId, + pub stmt: Stmt<'a>, +} + fn substitute(substitutions: &BumpMap, s: Symbol) -> Option { match substitutions.get(&s) { Some(new) => { @@ -6877,6 +7140,18 @@ fn substitute_in_exprs<'a>(arena: &'a Bump, stmt: &mut Stmt<'a>, from: Symbol, t } } +pub(crate) fn substitute_in_exprs_many<'a>( + arena: &'a Bump, + stmt: &mut Stmt<'a>, + subs: BumpMap, +) { + // TODO clean this up + let ref_stmt = arena.alloc(stmt.clone()); + if let Some(new) = substitute_in_stmt_help(arena, ref_stmt, &subs) { + *stmt = new.clone(); + } +} + fn substitute_in_stmt_help<'a>( arena: &'a Bump, stmt: &'a Stmt<'a>, @@ -7294,584 +7569,9 @@ fn substitute_in_expr<'a>( } } -#[allow(clippy::too_many_arguments)] -pub fn store_pattern<'a>( - env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, - can_pat: &Pattern<'a>, - outer_symbol: Symbol, - stmt: Stmt<'a>, -) -> Stmt<'a> { - match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) { - StorePattern::Productive(new) => new, - StorePattern::NotProductive(new) => new, - } -} - -enum StorePattern<'a> { - /// we bound new symbols - Productive(Stmt<'a>), - /// no new symbols were bound in this pattern - NotProductive(Stmt<'a>), -} - -/// It is crucial for correct RC insertion that we don't create dead variables! -#[allow(clippy::too_many_arguments)] -fn store_pattern_help<'a>( - env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, - can_pat: &Pattern<'a>, - outer_symbol: Symbol, - mut stmt: Stmt<'a>, -) -> StorePattern<'a> { - use Pattern::*; - - match can_pat { - Identifier(symbol) => { - substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol); - } - Underscore => { - // do nothing - return StorePattern::NotProductive(stmt); - } - As(subpattern, symbol) => { - let stored_subpattern = - store_pattern_help(env, procs, layout_cache, subpattern, outer_symbol, stmt); - - let mut stmt = match stored_subpattern { - StorePattern::Productive(stmt) => stmt, - StorePattern::NotProductive(stmt) => stmt, - }; - - substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol); - - return StorePattern::Productive(stmt); - } - IntLiteral(_, _) - | FloatLiteral(_, _) - | DecimalLiteral(_) - | EnumLiteral { .. } - | BitLiteral { .. } - | StrLiteral(_) => { - return StorePattern::NotProductive(stmt); - } - NewtypeDestructure { arguments, .. } => match arguments.as_slice() { - [(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); - fields.extend(arguments.iter().map(|x| x.1)); - - let layout = - layout_cache.put_in(Layout::struct_no_name_order(fields.into_bump_slice())); - - return store_newtype_pattern( - env, - procs, - layout_cache, - outer_symbol, - layout, - arguments, - stmt, - ); - } - }, - AppliedTag { - arguments, - layout, - tag_id, - .. - } => { - return store_tag_pattern( - env, - procs, - layout_cache, - outer_symbol, - *layout, - arguments, - *tag_id, - stmt, - ); - } - - List { - arity, - element_layout, - elements, - } => { - return store_list_pattern( - env, - procs, - layout_cache, - outer_symbol, - *arity, - *element_layout, - elements, - stmt, - ) - } - - Voided { .. } => { - return StorePattern::NotProductive(stmt); - } - - OpaqueUnwrap { argument, .. } => { - 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); - } - DestructType::Guard(guard_pattern) => { - return store_pattern_help( - env, - procs, - layout_cache, - guard_pattern, - outer_symbol, - stmt, - ); - } - } - } - } - RecordDestructure(destructs, sorted_fields) => { - let mut is_productive = false; - for (index, destruct) in destructs.iter().enumerate().rev() { - match store_record_destruct( - env, - procs, - layout_cache, - destruct, - index as u64, - outer_symbol, - sorted_fields, - stmt, - ) { - StorePattern::Productive(new) => { - is_productive = true; - stmt = new; - } - StorePattern::NotProductive(new) => { - stmt = new; - } - } - } - - if !is_productive { - return StorePattern::NotProductive(stmt); - } - } - } - - StorePattern::Productive(stmt) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct ListIndex( - /// Positive if we should index from the head, negative if we should index from the tail - /// 0 is lst[0] - /// -1 is lst[List.len lst - 1] - i64, -); - -impl ListIndex { - pub fn from_pattern_index(index: usize, arity: ListArity) -> Self { - match arity { - ListArity::Exact(_) => Self(index as _), - ListArity::Slice(head, tail) => { - if index < head { - Self(index as _) - } else { - // Slice(head=2, tail=5) - // - // s t ... w y z x q - // 0 1 2 3 4 5 6 index - // 0 1 2 3 4 (index - head) - // 5 4 3 2 1 (tail - (index - head)) - Self(-((tail - (index - head)) as i64)) - } - } - } - } -} - -pub(crate) type Store<'a> = (Symbol, InLayout<'a>, Expr<'a>); - -/// Builds the list index we should index into -#[must_use] -pub(crate) fn build_list_index_probe<'a>( - env: &mut Env<'a, '_>, - list_sym: Symbol, - list_index: &ListIndex, -) -> (Symbol, impl DoubleEndedIterator>) { - let usize_layout = Layout::usize(env.target_info); - - let list_index = list_index.0; - let index_sym = env.unique_symbol(); - - let (opt_len_store, opt_offset_store, index_store) = if list_index >= 0 { - let index_expr = Expr::Literal(Literal::Int((list_index as i128).to_ne_bytes())); - - let index_store = (index_sym, usize_layout, index_expr); - - (None, None, index_store) - } else { - let len_sym = env.unique_symbol(); - let len_expr = Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::ListLen, - update_mode: env.next_update_mode_id(), - }, - arguments: env.arena.alloc([list_sym]), - }); - - let offset = list_index.abs(); - let offset_sym = env.unique_symbol(); - let offset_expr = Expr::Literal(Literal::Int((offset as i128).to_ne_bytes())); - - let index_expr = Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::NumSub, - update_mode: env.next_update_mode_id(), - }, - arguments: env.arena.alloc([len_sym, offset_sym]), - }); - - let len_store = (len_sym, usize_layout, len_expr); - let offset_store = (offset_sym, usize_layout, offset_expr); - let index_store = (index_sym, usize_layout, index_expr); - - (Some(len_store), Some(offset_store), index_store) - }; - - let stores = (opt_len_store.into_iter()) - .chain(opt_offset_store) - .chain([index_store]); - - (index_sym, stores) -} - -#[allow(clippy::too_many_arguments)] -fn store_list_pattern<'a>( - env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, - list_sym: Symbol, - list_arity: ListArity, - element_layout: InLayout<'a>, - elements: &[Pattern<'a>], - mut stmt: Stmt<'a>, -) -> StorePattern<'a> { - use Pattern::*; - - let mut is_productive = false; - - for (index, element) in elements.iter().enumerate().rev() { - let compute_element_load = |env: &mut Env<'a, '_>| { - let list_index = ListIndex::from_pattern_index(index, list_arity); - - let (index_sym, needed_stores) = build_list_index_probe(env, list_sym, &list_index); - - let load = Expr::Call(Call { - call_type: CallType::LowLevel { - op: LowLevel::ListGetUnsafe, - update_mode: env.next_update_mode_id(), - }, - arguments: env.arena.alloc([list_sym, index_sym]), - }); - - (load, needed_stores) - }; - - let (store_loaded, needed_stores) = match element { - Identifier(symbol) => { - let (load, needed_stores) = compute_element_load(env); - - // store immediately in the given symbol - ( - Stmt::Let(*symbol, load, element_layout, env.arena.alloc(stmt)), - needed_stores, - ) - } - Underscore - | IntLiteral(_, _) - | FloatLiteral(_, _) - | DecimalLiteral(_) - | EnumLiteral { .. } - | BitLiteral { .. } - | StrLiteral(_) => { - // ignore - continue; - } - _ => { - // store the field in a symbol, and continue matching on it - let symbol = env.unique_symbol(); - - // first recurse, continuing to unpack symbol - match store_pattern_help(env, procs, layout_cache, element, symbol, stmt) { - StorePattern::Productive(new) => { - stmt = new; - let (load, needed_stores) = compute_element_load(env); - - // only if we bind one of its (sub)fields to a used name should we - // extract the field - ( - Stmt::Let(symbol, load, element_layout, env.arena.alloc(stmt)), - needed_stores, - ) - } - StorePattern::NotProductive(new) => { - // do nothing - stmt = new; - continue; - } - } - } - }; - - is_productive = true; - - stmt = store_loaded; - for (sym, lay, expr) in needed_stores.rev() { - stmt = Stmt::Let(sym, expr, lay, env.arena.alloc(stmt)); - } - } - - if is_productive { - StorePattern::Productive(stmt) - } else { - StorePattern::NotProductive(stmt) - } -} - -#[allow(clippy::too_many_arguments)] -fn store_tag_pattern<'a>( - env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, - structure: Symbol, - union_layout: UnionLayout<'a>, - arguments: &[(Pattern<'a>, InLayout<'a>)], - tag_id: TagIdIntType, - mut stmt: Stmt<'a>, -) -> StorePattern<'a> { - use Pattern::*; - - let mut is_productive = false; - - for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() { - let mut arg_layout = *arg_layout; - - if let Layout::RecursivePointer(_) = layout_cache.get_in(arg_layout) { - // TODO(recursive-layouts): fix after disjoint rec ptrs - arg_layout = layout_cache.put_in(Layout::Union(union_layout)); - } - - let load = Expr::UnionAtIndex { - index: index as u64, - structure, - tag_id, - union_layout, - }; - - match argument { - Identifier(symbol) => { - // store immediately in the given symbol - stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt)); - is_productive = true; - } - Underscore => { - // ignore - } - IntLiteral(_, _) - | FloatLiteral(_, _) - | DecimalLiteral(_) - | EnumLiteral { .. } - | BitLiteral { .. } - | StrLiteral(_) => {} - _ => { - // store the field in a symbol, and continue matching on it - let symbol = env.unique_symbol(); - - // first recurse, continuing to unpack symbol - match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) { - StorePattern::Productive(new) => { - is_productive = true; - stmt = new; - // only if we bind one of its (sub)fields to a used name should we - // extract the field - stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt)); - } - StorePattern::NotProductive(new) => { - // do nothing - stmt = new; - } - } - } - } - } - - if is_productive { - StorePattern::Productive(stmt) - } else { - StorePattern::NotProductive(stmt) - } -} - -#[allow(clippy::too_many_arguments)] -fn store_newtype_pattern<'a>( - env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, - structure: Symbol, - layout: InLayout<'a>, - arguments: &[(Pattern<'a>, InLayout<'a>)], - mut stmt: Stmt<'a>, -) -> StorePattern<'a> { - use Pattern::*; - - let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena); - let mut is_productive = false; - - for (_, layout) in arguments { - arg_layouts.push(*layout); - } - - for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() { - let mut arg_layout = *arg_layout; - - if let Layout::RecursivePointer(_) = layout_cache.get_in(arg_layout) { - arg_layout = layout; - } - - let load = Expr::StructAtIndex { - index: index as u64, - field_layouts: arg_layouts.clone().into_bump_slice(), - structure, - }; - - match argument { - Identifier(symbol) => { - stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt)); - is_productive = true; - } - Underscore => { - // ignore - } - IntLiteral(_, _) - | FloatLiteral(_, _) - | DecimalLiteral(_) - | EnumLiteral { .. } - | BitLiteral { .. } - | StrLiteral(_) => {} - _ => { - // store the field in a symbol, and continue matching on it - let symbol = env.unique_symbol(); - - // first recurse, continuing to unpack symbol - match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) { - StorePattern::Productive(new) => { - is_productive = true; - stmt = new; - // only if we bind one of its (sub)fields to a used name should we - // extract the field - stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt)); - } - StorePattern::NotProductive(new) => { - // do nothing - stmt = new; - } - } - } - } - } - - if is_productive { - StorePattern::Productive(stmt) - } else { - StorePattern::NotProductive(stmt) - } -} - -#[allow(clippy::too_many_arguments)] -fn store_record_destruct<'a>( - env: &mut Env<'a, '_>, - procs: &mut Procs<'a>, - layout_cache: &mut LayoutCache<'a>, - destruct: &RecordDestruct<'a>, - index: u64, - outer_symbol: Symbol, - sorted_fields: &'a [InLayout<'a>], - mut stmt: Stmt<'a>, -) -> StorePattern<'a> { - use Pattern::*; - - let load = Expr::StructAtIndex { - index, - field_layouts: sorted_fields, - structure: outer_symbol, - }; - - match &destruct.typ { - DestructType::Required(symbol) => { - stmt = Stmt::Let(*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)); - } - Underscore => { - // important that this is special-cased to do nothing: mono record patterns will extract all the - // fields, but those not bound in the source code are guarded with the underscore - // pattern. So given some record `{ x : a, y : b }`, a match - // - // { x } -> ... - // - // is actually - // - // { x, y: _ } -> ... - // - // internally. But `y` is never used, so we must make sure it't not stored/loaded. - return StorePattern::NotProductive(stmt); - } - IntLiteral(_, _) - | FloatLiteral(_, _) - | DecimalLiteral(_) - | EnumLiteral { .. } - | BitLiteral { .. } - | StrLiteral(_) => { - return StorePattern::NotProductive(stmt); - } - - _ => { - let symbol = env.unique_symbol(); - - match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) { - StorePattern::Productive(new) => { - stmt = new; - stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt)); - } - StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt), - } - } - }, - } - - StorePattern::Productive(stmt) -} - /// We want to re-use symbols that are not function symbols /// for any other expression, we create a new symbol, and will /// later make sure it gets assigned the correct value. - #[derive(Debug)] enum ReuseSymbol { Imported(Symbol), @@ -8735,7 +8435,6 @@ fn call_by_name_help<'a>( proc_name, layout_cache, fn_var, - &[], partial_proc, ) { Ok((proc, layout)) => { @@ -8883,7 +8582,6 @@ fn call_by_name_module_thunk<'a>( LambdaName::no_niche(proc_name), layout_cache, fn_var, - &[], partial_proc, ) { Ok((proc, raw_layout)) => { @@ -9060,947 +8758,6 @@ fn call_specialized_proc<'a>( } } -/// A pattern, including possible problems (e.g. shadowing) so that -/// codegen can generate a runtime error if this pattern is reached. -#[derive(Clone, Debug, PartialEq)] -pub enum Pattern<'a> { - Identifier(Symbol), - Underscore, - As(Box>, Symbol), - IntLiteral([u8; 16], IntWidth), - FloatLiteral(u64, FloatWidth), - DecimalLiteral([u8; 16]), - BitLiteral { - value: bool, - tag_name: TagName, - union: roc_exhaustive::Union, - }, - EnumLiteral { - tag_id: u8, - tag_name: TagName, - union: roc_exhaustive::Union, - }, - StrLiteral(Box), - - RecordDestructure(Vec<'a, RecordDestruct<'a>>, &'a [InLayout<'a>]), - NewtypeDestructure { - tag_name: TagName, - arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>, - }, - AppliedTag { - tag_name: TagName, - tag_id: TagIdIntType, - arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>, - layout: UnionLayout<'a>, - union: roc_exhaustive::Union, - }, - Voided { - tag_name: TagName, - }, - OpaqueUnwrap { - opaque: Symbol, - argument: Box<(Pattern<'a>, InLayout<'a>)>, - }, - List { - arity: ListArity, - element_layout: InLayout<'a>, - elements: Vec<'a, Pattern<'a>>, - }, -} - -impl<'a> Pattern<'a> { - /// This pattern contains a pattern match on Void (i.e. [], the empty tag union) - /// such branches are not reachable at runtime - pub fn is_voided(&self) -> bool { - let mut stack: std::vec::Vec<&Pattern> = vec![self]; - - while let Some(pattern) = stack.pop() { - match pattern { - Pattern::Identifier(_) - | Pattern::Underscore - | Pattern::IntLiteral(_, _) - | Pattern::FloatLiteral(_, _) - | Pattern::DecimalLiteral(_) - | Pattern::BitLiteral { .. } - | Pattern::EnumLiteral { .. } - | Pattern::StrLiteral(_) => { /* terminal */ } - Pattern::As(subpattern, _) => stack.push(subpattern), - Pattern::RecordDestructure(destructs, _) => { - for destruct in destructs { - match &destruct.typ { - DestructType::Required(_) => { /* do nothing */ } - DestructType::Guard(pattern) => { - stack.push(pattern); - } - } - } - } - Pattern::NewtypeDestructure { arguments, .. } => { - stack.extend(arguments.iter().map(|(t, _)| t)) - } - Pattern::Voided { .. } => return true, - Pattern::AppliedTag { arguments, .. } => { - stack.extend(arguments.iter().map(|(t, _)| t)) - } - Pattern::OpaqueUnwrap { argument, .. } => stack.push(&argument.0), - Pattern::List { elements, .. } => stack.extend(elements), - } - } - - false - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct RecordDestruct<'a> { - pub label: Lowercase, - pub variable: Variable, - pub layout: InLayout<'a>, - pub typ: DestructType<'a>, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum DestructType<'a> { - Required(Symbol), - Guard(Pattern<'a>), -} - -#[derive(Clone, Debug, PartialEq)] -pub struct WhenBranch<'a> { - pub patterns: Vec<'a, Pattern<'a>>, - pub value: Expr<'a>, - pub guard: Option>, -} - -#[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< - ( - Pattern<'a>, - Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, - ), - RuntimeError, -> { - let mut assignments = Vec::new_in(env.arena); - 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)>, -) -> Result, RuntimeError> { - use roc_can::pattern::Pattern::*; - - match can_pattern { - Underscore => Ok(Pattern::Underscore), - Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), - As(subpattern, symbol) => { - let mono_subpattern = - from_can_pattern_help(env, procs, layout_cache, &subpattern.value, assignments)?; - - Ok(Pattern::As(Box::new(mono_subpattern), *symbol)) - } - AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)), - IntLiteral(var, _, int_str, int, _bound) => Ok(make_num_literal_pattern( - env, - layout_cache, - *var, - int_str, - IntOrFloatValue::Int(*int), - )), - FloatLiteral(var, _, float_str, float, _bound) => Ok(make_num_literal_pattern( - env, - layout_cache, - *var, - float_str, - IntOrFloatValue::Float(*float), - )), - StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), - SingleQuote(var, _, c, _) => { - let layout = layout_cache.from_var(env.arena, *var, env.subs); - match layout.map(|l| layout_cache.get_in(l)) { - Ok(Layout::Builtin(Builtin::Int(width))) => { - Ok(Pattern::IntLiteral((*c as i128).to_ne_bytes(), width)) - } - o => internal_error!("an integer width was expected, but we found {:?}", o), - } - } - Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { - original_region: *region, - shadow: ident.clone(), - kind: ShadowKind::Variable, - }), - UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)), - MalformedPattern(_problem, region) => { - // TODO preserve malformed problem information here? - Err(RuntimeError::UnsupportedPattern(*region)) - } - OpaqueNotInScope(loc_ident) => { - // TODO(opaques) should be `RuntimeError::OpaqueNotDefined` - Err(RuntimeError::UnsupportedPattern(loc_ident.region)) - } - NumLiteral(var, num_str, num, _bound) => Ok(make_num_literal_pattern( - env, - layout_cache, - *var, - num_str, - IntOrFloatValue::Int(*num), - )), - - AppliedTag { - whole_var, - tag_name, - arguments, - .. - } => { - use crate::layout::UnionVariant::*; - use roc_exhaustive::Union; - - let res_variant = { - let mut layout_env = layout::Env::from_components( - layout_cache, - env.subs, - env.arena, - env.target_info, - ); - crate::layout::union_sorted_tags(&mut layout_env, *whole_var).map_err(Into::into) - }; - - let variant = match res_variant { - Ok(cached) => cached, - Err(LayoutProblem::UnresolvedTypeVar(_)) => { - return Err(RuntimeError::UnresolvedTypeVar) - } - Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType), - }; - - let result = match variant { - Never => unreachable!( - "there is no pattern of type `[]`, union var {:?}", - *whole_var - ), - Unit => Pattern::EnumLiteral { - tag_id: 0, - tag_name: tag_name.clone(), - union: Union { - render_as: RenderAs::Tag, - alternatives: vec![Ctor { - tag_id: TagId(0), - name: CtorName::Tag(tag_name.clone()), - arity: 0, - }], - }, - }, - BoolUnion { ttrue, ffalse } => { - let (ttrue, ffalse) = (ttrue.expect_tag(), ffalse.expect_tag()); - Pattern::BitLiteral { - value: tag_name == &ttrue, - tag_name: tag_name.clone(), - union: Union { - render_as: RenderAs::Tag, - alternatives: vec![ - Ctor { - tag_id: TagId(0), - name: CtorName::Tag(ffalse), - arity: 0, - }, - Ctor { - tag_id: TagId(1), - name: CtorName::Tag(ttrue), - arity: 0, - }, - ], - }, - } - } - ByteUnion(tag_names) => { - let tag_id = tag_names - .iter() - .position(|key| tag_name == key.expect_tag_ref()) - .expect("tag must be in its own type"); - - let mut ctors = std::vec::Vec::with_capacity(tag_names.len()); - for (i, tag_name) in tag_names.into_iter().enumerate() { - ctors.push(Ctor { - tag_id: TagId(i as _), - name: CtorName::Tag(tag_name.expect_tag()), - arity: 0, - }) - } - - let union = roc_exhaustive::Union { - render_as: RenderAs::Tag, - alternatives: ctors, - }; - - Pattern::EnumLiteral { - tag_id: tag_id as u8, - tag_name: tag_name.clone(), - union, - } - } - Newtype { - arguments: field_layouts, - .. - } => { - let mut arguments = arguments.clone(); - - arguments.sort_by(|arg1, arg2| { - let size1 = layout_cache - .from_var(env.arena, arg1.0, env.subs) - .map(|x| layout_cache.interner.alignment_bytes(x)) - .unwrap_or(0); - - let size2 = layout_cache - .from_var(env.arena, arg2.0, env.subs) - .map(|x| layout_cache.interner.alignment_bytes(x)) - .unwrap_or(0); - - size2.cmp(&size1) - }); - - 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, - procs, - layout_cache, - &loc_pat.value, - assignments, - )?, - *layout, - )); - } - - Pattern::NewtypeDestructure { - tag_name: tag_name.clone(), - arguments: mono_args, - } - } - NewtypeByVoid { - data_tag_arguments, - data_tag_name, - .. - } => { - let data_tag_name = data_tag_name.expect_tag(); - - if tag_name != &data_tag_name { - // this tag is not represented at runtime - Pattern::Voided { - tag_name: tag_name.clone(), - } - } else { - let mut arguments = arguments.clone(); - - arguments.sort_by(|arg1, arg2| { - let size1 = layout_cache - .from_var(env.arena, arg1.0, env.subs) - .map(|x| layout_cache.interner.alignment_bytes(x)) - .unwrap_or(0); - - let size2 = layout_cache - .from_var(env.arena, arg2.0, env.subs) - .map(|x| layout_cache.interner.alignment_bytes(x)) - .unwrap_or(0); - - size2.cmp(&size1) - }); - - let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); - let it = arguments.iter().zip(data_tag_arguments.iter()); - for ((_, loc_pat), layout) in it { - mono_args.push(( - from_can_pattern_help( - env, - procs, - layout_cache, - &loc_pat.value, - assignments, - )?, - *layout, - )); - } - - Pattern::NewtypeDestructure { - tag_name: tag_name.clone(), - arguments: mono_args, - } - } - } - - Wrapped(variant) => { - let (tag_id, argument_layouts) = variant.tag_name_to_id(tag_name); - let number_of_tags = variant.number_of_tags(); - let mut ctors = std::vec::Vec::with_capacity(number_of_tags); - - let arguments = { - let mut temp = arguments.clone(); - - temp.sort_by(|arg1, arg2| { - let layout1 = - layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap(); - let layout2 = - layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap(); - - let size1 = layout_cache.interner.alignment_bytes(layout1); - let size2 = layout_cache.interner.alignment_bytes(layout2); - - size2.cmp(&size1) - }); - - temp - }; - - // we must derive the union layout from the whole_var, building it up - // from `layouts` would unroll recursive tag unions, and that leads to - // problems down the line because we hash layouts and an unrolled - // version is not the same as the minimal version. - let whole_var_layout = layout_cache.from_var(env.arena, *whole_var, env.subs); - let layout = match whole_var_layout.map(|l| layout_cache.get_in(l)) { - Ok(Layout::Union(ul)) => ul, - _ => unreachable!(), - }; - - use WrappedVariant::*; - match variant { - NonRecursive { - sorted_tag_layouts: ref tags, - } => { - debug_assert!(tags.len() > 1); - - for (i, (tag_name, args)) in tags.iter().enumerate() { - ctors.push(Ctor { - tag_id: TagId(i as _), - name: CtorName::Tag(tag_name.expect_tag_ref().clone()), - arity: args.len(), - }) - } - - let union = roc_exhaustive::Union { - render_as: RenderAs::Tag, - alternatives: ctors, - }; - - let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); - - debug_assert_eq!( - arguments.len(), - argument_layouts.len(), - "The {:?} tag got {} arguments, but its layout expects {}!", - tag_name, - arguments.len(), - argument_layouts.len(), - ); - let it = argument_layouts.iter(); - - for ((_, loc_pat), layout) in arguments.iter().zip(it) { - mono_args.push(( - from_can_pattern_help( - env, - procs, - layout_cache, - &loc_pat.value, - assignments, - )?, - *layout, - )); - } - - Pattern::AppliedTag { - tag_name: tag_name.clone(), - tag_id: tag_id as _, - arguments: mono_args, - union, - layout, - } - } - - Recursive { - sorted_tag_layouts: ref tags, - } => { - debug_assert!(tags.len() > 1); - - for (i, (tag_name, args)) in tags.iter().enumerate() { - ctors.push(Ctor { - tag_id: TagId(i as _), - name: CtorName::Tag(tag_name.expect_tag_ref().clone()), - arity: args.len(), - }) - } - - let union = roc_exhaustive::Union { - render_as: RenderAs::Tag, - alternatives: ctors, - }; - - let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); - - debug_assert_eq!(arguments.len(), argument_layouts.len()); - let it = argument_layouts.iter(); - - for ((_, loc_pat), layout) in arguments.iter().zip(it) { - mono_args.push(( - from_can_pattern_help( - env, - procs, - layout_cache, - &loc_pat.value, - assignments, - )?, - *layout, - )); - } - - Pattern::AppliedTag { - tag_name: tag_name.clone(), - tag_id: tag_id as _, - arguments: mono_args, - union, - layout, - } - } - - NonNullableUnwrapped { - tag_name: w_tag_name, - fields, - } => { - debug_assert_eq!(w_tag_name.expect_tag_ref(), tag_name); - - ctors.push(Ctor { - tag_id: TagId(0), - name: CtorName::Tag(tag_name.clone()), - arity: fields.len(), - }); - - let union = roc_exhaustive::Union { - render_as: RenderAs::Tag, - alternatives: ctors, - }; - - let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); - - debug_assert_eq!(arguments.len(), argument_layouts.len()); - let it = argument_layouts.iter(); - - for ((_, loc_pat), layout) in arguments.iter().zip(it) { - mono_args.push(( - from_can_pattern_help( - env, - procs, - layout_cache, - &loc_pat.value, - assignments, - )?, - *layout, - )); - } - - Pattern::AppliedTag { - tag_name: tag_name.clone(), - tag_id: tag_id as _, - arguments: mono_args, - union, - layout, - } - } - - NullableWrapped { - sorted_tag_layouts: ref non_nulled_tags, - nullable_id, - nullable_name, - } => { - for id in 0..(non_nulled_tags.len() + 1) { - if id == nullable_id as usize { - ctors.push(Ctor { - tag_id: TagId(id as _), - name: CtorName::Tag(nullable_name.expect_tag_ref().clone()), - arity: 0, - }); - } else { - let i = if id < nullable_id.into() { id } else { id - 1 }; - let (tag_name, args) = &non_nulled_tags[i]; - ctors.push(Ctor { - tag_id: TagId(i as _), - name: CtorName::Tag(tag_name.expect_tag_ref().clone()), - arity: args.len(), - }); - } - } - - let union = roc_exhaustive::Union { - render_as: RenderAs::Tag, - alternatives: ctors, - }; - - let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); - - let it = if tag_name == nullable_name.expect_tag_ref() { - [].iter() - } else { - argument_layouts.iter() - }; - - for ((_, loc_pat), layout) in arguments.iter().zip(it) { - mono_args.push(( - from_can_pattern_help( - env, - procs, - layout_cache, - &loc_pat.value, - assignments, - )?, - *layout, - )); - } - - Pattern::AppliedTag { - tag_name: tag_name.clone(), - tag_id: tag_id as _, - arguments: mono_args, - union, - layout, - } - } - - NullableUnwrapped { - other_fields, - nullable_id, - nullable_name, - other_name: _, - } => { - debug_assert!(!other_fields.is_empty()); - - ctors.push(Ctor { - tag_id: TagId(nullable_id as _), - name: CtorName::Tag(nullable_name.expect_tag_ref().clone()), - arity: 0, - }); - - ctors.push(Ctor { - tag_id: TagId(!nullable_id as _), - name: CtorName::Tag(nullable_name.expect_tag_ref().clone()), - arity: other_fields.len(), - }); - - let union = roc_exhaustive::Union { - render_as: RenderAs::Tag, - alternatives: ctors, - }; - - let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); - - let it = if tag_name == nullable_name.expect_tag_ref() { - [].iter() - } else { - argument_layouts.iter() - }; - - for ((_, loc_pat), layout) in arguments.iter().zip(it) { - mono_args.push(( - from_can_pattern_help( - env, - procs, - layout_cache, - &loc_pat.value, - assignments, - )?, - *layout, - )); - } - - Pattern::AppliedTag { - tag_name: tag_name.clone(), - tag_id: tag_id as _, - arguments: mono_args, - union, - layout, - } - } - } - } - }; - - Ok(result) - } - - UnwrappedOpaque { - opaque, argument, .. - } => { - let (arg_var, loc_arg_pattern) = &(**argument); - let arg_layout = layout_cache - .from_var(env.arena, *arg_var, env.subs) - .unwrap(); - 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)), - }) - } - - RecordDestructure { - whole_var, - destructs, - .. - } => { - // sorted fields based on the type - let sorted_fields = { - let mut layout_env = layout::Env::from_components( - layout_cache, - env.subs, - env.arena, - env.target_info, - ); - crate::layout::sort_record_fields(&mut layout_env, *whole_var) - .map_err(RuntimeError::from)? - }; - - // sorted fields based on the destruct - let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); - let mut destructs_by_label = BumpMap::with_capacity_in(destructs.len(), env.arena); - destructs_by_label.extend(destructs.iter().map(|x| (&x.value.label, x))); - - let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); - - // next we step through both sequences of fields. The outer loop is the sequence based - // on the type, since not all fields need to actually be destructured in the source - // language. - // - // However in mono patterns, we do destruct all patterns (but use Underscore) when - // in the source the field is not matche in the source language. - // - // Optional fields somewhat complicate the matter here - - for (label, variable, res_layout) in sorted_fields.into_iter() { - match res_layout { - Ok(field_layout) => { - // the field is non-optional according to the type - - match destructs_by_label.remove(&label) { - Some(destruct) => { - // this field is destructured by the pattern - mono_destructs.push(from_can_record_destruct( - env, - procs, - layout_cache, - &destruct.value, - field_layout, - assignments, - )?); - } - None => { - // this field is not destructured by the pattern - // put in an underscore - mono_destructs.push(RecordDestruct { - label: label.clone(), - variable, - layout: field_layout, - typ: DestructType::Guard(Pattern::Underscore), - }); - } - } - - // the layout of this field is part of the layout of the record - field_layouts.push(field_layout); - } - Err(field_layout) => { - // the field is optional according to the type - match destructs_by_label.remove(&label) { - Some(destruct) => { - // this field is destructured by the pattern - match &destruct.value.typ { - roc_can::pattern::DestructType::Optional(_, loc_expr) => { - // if we reach this stage, the optional field is not present - // so we push the default assignment into the branch - assignments.push(( - destruct.value.symbol, - variable, - loc_expr.value.clone(), - )); - } - _ => unreachable!( - "only optional destructs can be optional fields" - ), - }; - } - None => { - // this field is not destructured by the pattern - // put in an underscore - mono_destructs.push(RecordDestruct { - label: label.clone(), - variable, - layout: field_layout, - typ: DestructType::Guard(Pattern::Underscore), - }); - } - } - } - } - } - - for (_, destruct) in destructs_by_label.drain() { - // this destruct is not in the type, but is in the pattern - // it must be an optional field, and we will use the default - match &destruct.value.typ { - roc_can::pattern::DestructType::Optional(field_var, loc_expr) => { - assignments.push(( - destruct.value.symbol, - // destruct.value.var, - *field_var, - loc_expr.value.clone(), - )); - } - _ => unreachable!("only optional destructs can be optional fields"), - } - } - - Ok(Pattern::RecordDestructure( - mono_destructs, - field_layouts.into_bump_slice(), - )) - } - - List { - list_var: _, - elem_var, - patterns, - } => { - let element_layout = match layout_cache.from_var(env.arena, *elem_var, env.subs) { - Ok(lay) => lay, - Err(LayoutProblem::UnresolvedTypeVar(_)) => { - return Err(RuntimeError::UnresolvedTypeVar) - } - Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType), - }; - - let arity = patterns.arity(); - - let mut mono_patterns = Vec::with_capacity_in(patterns.patterns.len(), env.arena); - for loc_pat in patterns.patterns.iter() { - let mono_pat = - from_can_pattern_help(env, procs, layout_cache, &loc_pat.value, assignments)?; - mono_patterns.push(mono_pat); - } - - Ok(Pattern::List { - arity, - element_layout, - elements: mono_patterns, - }) - } - } -} - -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: InLayout<'a>, - assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, -) -> Result, RuntimeError> { - Ok(RecordDestruct { - label: can_rd.label.clone(), - variable: can_rd.var, - layout: field_layout, - typ: match &can_rd.typ { - roc_can::pattern::DestructType::Required => DestructType::Required(can_rd.symbol), - roc_can::pattern::DestructType::Optional(_, _) => { - // if we reach this stage, the optional field is present - DestructType::Required(can_rd.symbol) - } - roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard( - from_can_pattern_help(env, procs, layout_cache, &loc_pattern.value, assignments)?, - ), - }, - }) -} - -#[derive(Debug, Clone, Copy)] -enum IntOrFloatValue { - Int(IntValue), - Float(f64), -} - -enum NumLiteral { - Int([u8; 16], IntWidth), - U128([u8; 16]), - Float(f64, FloatWidth), - Decimal([u8; 16]), -} - -impl NumLiteral { - fn to_expr_literal(&self) -> Literal<'static> { - match *self { - NumLiteral::Int(n, _) => Literal::Int(n), - NumLiteral::U128(n) => Literal::U128(n), - NumLiteral::Float(n, _) => Literal::Float(n), - NumLiteral::Decimal(n) => Literal::Decimal(n), - } - } - fn to_pattern(&self) -> Pattern<'static> { - match *self { - NumLiteral::Int(n, w) => Pattern::IntLiteral(n, w), - NumLiteral::U128(n) => Pattern::IntLiteral(n, IntWidth::U128), - NumLiteral::Float(n, w) => Pattern::FloatLiteral(f64::to_bits(n), w), - NumLiteral::Decimal(n) => Pattern::DecimalLiteral(n), - } - } -} - -fn make_num_literal<'a>( - interner: &TLLayoutInterner<'a>, - layout: InLayout<'a>, - num_str: &str, - num_value: IntOrFloatValue, -) -> NumLiteral { - match interner.get(layout) { - Layout::Builtin(Builtin::Int(width)) => match num_value { - IntOrFloatValue::Int(IntValue::I128(n)) => NumLiteral::Int(n, width), - IntOrFloatValue::Int(IntValue::U128(n)) => NumLiteral::U128(n), - IntOrFloatValue::Float(..) => { - internal_error!("Float value where int was expected, should have been a type error") - } - }, - Layout::Builtin(Builtin::Float(width)) => match num_value { - IntOrFloatValue::Float(n) => NumLiteral::Float(n, width), - IntOrFloatValue::Int(int_value) => match int_value { - IntValue::I128(n) => NumLiteral::Float(i128::from_ne_bytes(n) as f64, width), - IntValue::U128(n) => NumLiteral::Float(u128::from_ne_bytes(n) as f64, width), - }, - }, - Layout::Builtin(Builtin::Decimal) => { - let dec = match RocDec::from_str(num_str) { - Some(d) => d, - None => internal_error!( - "Invalid decimal for float literal = {}. This should be a type error!", - num_str - ), - }; - NumLiteral::Decimal(dec.to_ne_bytes()) - } - layout => internal_error!( - "Found a non-num layout where a number was expected: {:?}", - layout - ), - } -} - fn assign_num_literal_expr<'a>( env: &mut Env<'a, '_>, layout_cache: &mut LayoutCache<'a>, @@ -10019,20 +8776,6 @@ fn assign_num_literal_expr<'a>( Stmt::Let(assigned, Expr::Literal(literal), layout, hole) } -fn make_num_literal_pattern<'a>( - env: &mut Env<'a, '_>, - layout_cache: &mut LayoutCache<'a>, - variable: Variable, - num_str: &str, - num_value: IntOrFloatValue, -) -> Pattern<'a> { - let layout = layout_cache - .from_var(env.arena, variable, env.subs) - .unwrap(); - let literal = make_num_literal(&layout_cache.interner, layout, num_str, num_value); - literal.to_pattern() -} - type ToLowLevelCallArguments<'a> = ( LambdaName<'a>, Symbol, @@ -10407,7 +9150,20 @@ fn union_lambda_set_to_switch<'a>( return empty_lambda_set_error(env); } - let join_point_id = JoinPointId(env.unique_symbol()); + let (opt_join, branch_assigned, branch_hole) = match hole { + Stmt::Ret(_) => { + // No need to jump to a joinpoint, inline the return in each statement as-is. + // This makes further analyses, like TCO, easier as well. + (None, assigned, hole) + } + _ => { + let join_point_id = JoinPointId(env.unique_symbol()); + let assigned = env.unique_symbol(); + let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned])); + + (Some(join_point_id), assigned, &*env.arena.alloc(hole)) + } + }; let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena); @@ -10423,12 +9179,13 @@ fn union_lambda_set_to_switch<'a>( let stmt = union_lambda_set_branch( env, - join_point_id, lambda_name, closure_info, argument_symbols, argument_layouts, return_layout, + branch_assigned, + branch_hole, ); branches.push((i as u64, BranchInfo::None, stmt)); } @@ -10447,34 +9204,36 @@ fn union_lambda_set_to_switch<'a>( ret_layout: return_layout, }; - let param = Param { - symbol: assigned, - layout: return_layout, - ownership: Ownership::Owned, - }; + match opt_join { + None => switch, + Some(join_point_id) => { + let param = Param { + symbol: assigned, + layout: return_layout, + ownership: Ownership::Owned, + }; - Stmt::Join { - id: join_point_id, - parameters: &*env.arena.alloc([param]), - body: hole, - remainder: env.arena.alloc(switch), + Stmt::Join { + id: join_point_id, + parameters: &*env.arena.alloc([param]), + body: hole, + remainder: env.arena.alloc(switch), + } + } } } #[allow(clippy::too_many_arguments)] fn union_lambda_set_branch<'a>( env: &mut Env<'a, '_>, - join_point_id: JoinPointId, lambda_name: LambdaName<'a>, closure_info: ClosureInfo<'a>, argument_symbols_slice: &'a [Symbol], argument_layouts_slice: &'a [InLayout<'a>], return_layout: InLayout<'a>, + assigned: Symbol, + hole: &'a Stmt<'a>, ) -> Stmt<'a> { - let result_symbol = env.unique_symbol(); - - let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol])); - union_lambda_set_branch_help( env, lambda_name, @@ -10482,7 +9241,7 @@ fn union_lambda_set_branch<'a>( argument_symbols_slice, argument_layouts_slice, return_layout, - result_symbol, + assigned, env.arena.alloc(hole), ) } @@ -10568,18 +9327,32 @@ fn enum_lambda_set_to_switch<'a>( ) -> Stmt<'a> { debug_assert_ne!(lambda_set.len(), 0); - let join_point_id = JoinPointId(env.unique_symbol()); + let (opt_join, branch_assigned, branch_hole) = match hole { + Stmt::Ret(_) => { + // No need to jump to a joinpoint, inline the return in each statement as-is. + // This makes further analyses, like TCO, easier as well. + (None, assigned, hole) + } + _ => { + let join_point_id = JoinPointId(env.unique_symbol()); + let assigned = env.unique_symbol(); + let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned])); + + (Some(join_point_id), assigned, &*env.arena.alloc(hole)) + } + }; let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena); for (i, lambda_name) in lambda_set.into_iter().enumerate() { let stmt = enum_lambda_set_branch( env, - join_point_id, lambda_name, argument_symbols, argument_layouts, return_layout, + branch_assigned, + branch_hole, ); branches.push((i as u64, BranchInfo::None, stmt)); } @@ -10598,17 +9371,22 @@ fn enum_lambda_set_to_switch<'a>( ret_layout: return_layout, }; - let param = Param { - symbol: assigned, - layout: return_layout, - ownership: Ownership::Owned, - }; + match opt_join { + None => switch, + Some(join_point_id) => { + let param = Param { + symbol: assigned, + layout: return_layout, + ownership: Ownership::Owned, + }; - Stmt::Join { - id: join_point_id, - parameters: &*env.arena.alloc([param]), - body: hole, - remainder: env.arena.alloc(switch), + Stmt::Join { + id: join_point_id, + parameters: &*env.arena.alloc([param]), + body: hole, + remainder: env.arena.alloc(switch), + } + } } } @@ -10616,18 +9394,13 @@ fn enum_lambda_set_to_switch<'a>( #[allow(clippy::too_many_arguments)] fn enum_lambda_set_branch<'a>( env: &mut Env<'a, '_>, - join_point_id: JoinPointId, lambda_name: LambdaName<'a>, argument_symbols: &'a [Symbol], argument_layouts: &'a [InLayout<'a>], return_layout: InLayout<'a>, + assigned: Symbol, + hole: &'a Stmt<'a>, ) -> Stmt<'a> { - let result_symbol = env.unique_symbol(); - - let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol])); - - let assigned = result_symbol; - let call = self::Call { call_type: CallType::ByName { name: lambda_name, @@ -10637,7 +9410,7 @@ fn enum_lambda_set_branch<'a>( }, arguments: argument_symbols, }; - build_call(env, call, assigned, return_layout, env.arena.alloc(hole)) + build_call(env, call, assigned, return_layout, hole) } #[allow(clippy::too_many_arguments)] @@ -10714,3 +9487,347 @@ where remainder: env.arena.alloc(switch), } } + +#[derive(Debug, Default)] +pub struct GlueLayouts<'a> { + pub getters: std::vec::Vec<(Symbol, ProcLayout<'a>)>, +} + +type GlueProcId = u16; + +#[derive(Debug)] +pub struct GlueProc<'a> { + pub name: Symbol, + pub proc_layout: ProcLayout<'a>, + pub proc: Proc<'a>, +} + +pub struct GlueProcs<'a> { + pub getters: Vec<'a, (Layout<'a>, Vec<'a, GlueProc<'a>>)>, + pub extern_names: Vec<'a, (LambdaSetId, RawFunctionLayout<'a>)>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct LambdaSetId(pub u32); + +impl LambdaSetId { + #[must_use] + pub fn next(self) -> Self { + debug_assert!(self.0 < u32::MAX); + Self(self.0 + 1) + } +} + +pub fn generate_glue_procs<'a, 'i, I>( + home: ModuleId, + ident_ids: &mut IdentIds, + arena: &'a Bump, + layout_interner: &'i mut I, + layout: &'a Layout<'a>, +) -> GlueProcs<'a> +where + I: LayoutInterner<'a>, +{ + let mut answer = GlueProcs { + getters: Vec::new_in(arena), + extern_names: Vec::new_in(arena), + }; + + let mut lambda_set_id = LambdaSetId(0); + + let mut stack: Vec<'a, Layout<'a>> = Vec::from_iter_in([*layout], arena); + let mut next_unique_id = 0; + + macro_rules! handle_tag_field_layouts { + ($tag_id:expr, $layout:expr, $union_layout:expr, $field_layouts: expr) => {{ + if $field_layouts.iter().any(|l| { + layout_interner + .get(*l) + .has_varying_stack_size(layout_interner, arena) + }) { + let procs = generate_glue_procs_for_tag_fields( + layout_interner, + home, + &mut next_unique_id, + ident_ids, + arena, + $tag_id, + &$layout, + $union_layout, + $field_layouts, + ); + + answer.getters.push(($layout, procs)); + } + + for in_layout in $field_layouts.iter().rev() { + stack.push(layout_interner.get(*in_layout)); + } + }}; + } + + while let Some(layout) = stack.pop() { + match layout { + Layout::Builtin(builtin) => match builtin { + Builtin::Int(_) + | Builtin::Float(_) + | Builtin::Bool + | Builtin::Decimal + | Builtin::Str => { /* do nothing */ } + Builtin::List(element) => stack.push(layout_interner.get(element)), + }, + Layout::Struct { field_layouts, .. } => { + if field_layouts.iter().any(|l| { + layout_interner + .get(*l) + .has_varying_stack_size(layout_interner, arena) + }) { + let procs = generate_glue_procs_for_struct_fields( + layout_interner, + home, + &mut next_unique_id, + ident_ids, + arena, + &layout, + field_layouts, + ); + + answer.getters.push((layout, procs)); + } + + for in_layout in field_layouts.iter().rev() { + stack.push(layout_interner.get(*in_layout)); + } + } + Layout::Boxed(boxed) => { + stack.push(layout_interner.get(boxed)); + } + Layout::Union(union_layout) => match union_layout { + UnionLayout::NonRecursive(tags) => { + for in_layout in tags.iter().flat_map(|e| e.iter()) { + stack.push(layout_interner.get(*in_layout)); + } + } + UnionLayout::Recursive(tags) => { + for in_layout in tags.iter().flat_map(|e| e.iter()) { + stack.push(layout_interner.get(*in_layout)); + } + } + UnionLayout::NonNullableUnwrapped(field_layouts) => { + handle_tag_field_layouts!(0, layout, union_layout, field_layouts); + } + UnionLayout::NullableWrapped { + other_tags, + nullable_id, + } => { + let tag_ids = + (0..nullable_id).chain(nullable_id + 1..other_tags.len() as u16 + 1); + for (i, field_layouts) in tag_ids.zip(other_tags) { + handle_tag_field_layouts!(i, layout, union_layout, *field_layouts); + } + } + UnionLayout::NullableUnwrapped { other_fields, .. } => { + for in_layout in other_fields.iter().rev() { + stack.push(layout_interner.get(*in_layout)); + } + } + }, + Layout::LambdaSet(lambda_set) => { + let raw_function_layout = + RawFunctionLayout::Function(lambda_set.args, lambda_set, lambda_set.ret); + + let key = (lambda_set_id, raw_function_layout); + answer.extern_names.push(key); + + // this id is used, increment for the next one + lambda_set_id = lambda_set_id.next(); + + stack.push(layout_interner.get(lambda_set.runtime_representation())); + } + Layout::RecursivePointer(_) => { + /* do nothing, we've already generated for this type through the Union(_) */ + } + } + } + + answer +} + +fn generate_glue_procs_for_struct_fields<'a, 'i, I>( + layout_interner: &'i mut I, + home: ModuleId, + next_unique_id: &mut GlueProcId, + ident_ids: &mut IdentIds, + arena: &'a Bump, + unboxed_struct_layout: &Layout<'a>, + field_layouts: &[InLayout<'a>], +) -> Vec<'a, GlueProc<'a>> +where + I: LayoutInterner<'a>, +{ + let interned_unboxed_struct_layout = layout_interner.insert(*unboxed_struct_layout); + let boxed_struct_layout = Layout::Boxed(interned_unboxed_struct_layout); + let boxed_struct_layout = layout_interner.insert(boxed_struct_layout); + let mut answer = bumpalo::collections::Vec::with_capacity_in(field_layouts.len(), arena); + + let field_layouts = match layout_interner.get(interned_unboxed_struct_layout) { + Layout::Struct { field_layouts, .. } => field_layouts, + other => { + unreachable!( + "{:?} {:?}", + layout_interner.dbg(interned_unboxed_struct_layout), + other + ) + } + }; + + for (index, field) in field_layouts.iter().enumerate() { + let proc_layout = ProcLayout { + arguments: arena.alloc([boxed_struct_layout]), + result: *field, + niche: Niche::NONE, + }; + + let symbol = unique_glue_symbol(arena, next_unique_id, home, ident_ids); + + let argument = Symbol::new(home, ident_ids.gen_unique()); + let unboxed = Symbol::new(home, ident_ids.gen_unique()); + let result = Symbol::new(home, ident_ids.gen_unique()); + + home.register_debug_idents(ident_ids); + + let ret_stmt = arena.alloc(Stmt::Ret(result)); + + let field_get_expr = Expr::StructAtIndex { + index: index as u64, + field_layouts, + structure: unboxed, + }; + + let field_get_stmt = Stmt::Let(result, field_get_expr, *field, ret_stmt); + + let unbox_expr = Expr::ExprUnbox { symbol: argument }; + + let unbox_stmt = Stmt::Let( + unboxed, + unbox_expr, + interned_unboxed_struct_layout, + arena.alloc(field_get_stmt), + ); + + let proc = Proc { + name: LambdaName::no_niche(symbol), + args: arena.alloc([(boxed_struct_layout, argument)]), + body: unbox_stmt, + closure_data_layout: None, + ret_layout: *field, + is_self_recursive: SelfRecursive::NotSelfRecursive, + must_own_arguments: false, + host_exposed_layouts: HostExposedLayouts::NotHostExposed, + }; + + answer.push(GlueProc { + name: symbol, + proc_layout, + proc, + }); + } + + answer +} + +fn unique_glue_symbol( + arena: &Bump, + next_unique_id: &mut GlueProcId, + home: ModuleId, + ident_ids: &mut IdentIds, +) -> Symbol { + let unique_id = *next_unique_id; + + *next_unique_id = unique_id + 1; + + // then name of the platform `main.roc` is the empty string + let module_name = ""; + + // Turn unique_id into a Symbol without doing a heap allocation. + use std::fmt::Write; + let mut string = bumpalo::collections::String::with_capacity_in(32, arena); + + let _result = write!(&mut string, "roc__getter_{}_{}", module_name, unique_id); + debug_assert_eq!(_result, Ok(())); // This should never fail, but doesn't hurt to debug-check! + + let bump_string = string.into_bump_str(); + let ident_id = ident_ids.get_or_insert(bump_string); + + Symbol::new(home, ident_id) +} + +#[allow(clippy::too_many_arguments)] +fn generate_glue_procs_for_tag_fields<'a, 'i, I>( + layout_interner: &'i mut I, + home: ModuleId, + next_unique_id: &mut GlueProcId, + ident_ids: &mut IdentIds, + arena: &'a Bump, + tag_id: TagIdIntType, + unboxed_struct_layout: &Layout<'a>, + union_layout: UnionLayout<'a>, + field_layouts: &'a [InLayout<'a>], +) -> Vec<'a, GlueProc<'a>> +where + I: LayoutInterner<'a>, +{ + let interned = layout_interner.insert(*unboxed_struct_layout); + let boxed_struct_layout = Layout::Boxed(interned); + let boxed_struct_layout = layout_interner.insert(boxed_struct_layout); + let mut answer = bumpalo::collections::Vec::with_capacity_in(field_layouts.len(), arena); + + for (index, field) in field_layouts.iter().enumerate() { + let proc_layout = ProcLayout { + arguments: arena.alloc([boxed_struct_layout]), + result: *field, + niche: Niche::NONE, + }; + let symbol = unique_glue_symbol(arena, next_unique_id, home, ident_ids); + + let argument = Symbol::new(home, ident_ids.gen_unique()); + let unboxed = Symbol::new(home, ident_ids.gen_unique()); + let result = Symbol::new(home, ident_ids.gen_unique()); + + home.register_debug_idents(ident_ids); + + let ret_stmt = arena.alloc(Stmt::Ret(result)); + + let field_get_expr = Expr::UnionAtIndex { + structure: unboxed, + tag_id, + union_layout, + index: index as u64, + }; + + let field_get_stmt = Stmt::Let(result, field_get_expr, *field, ret_stmt); + + let unbox_expr = Expr::ExprUnbox { symbol: argument }; + + let unbox_stmt = Stmt::Let(unboxed, unbox_expr, interned, arena.alloc(field_get_stmt)); + + let proc = Proc { + name: LambdaName::no_niche(symbol), + args: arena.alloc([(boxed_struct_layout, argument)]), + body: unbox_stmt, + closure_data_layout: None, + ret_layout: *field, + is_self_recursive: SelfRecursive::NotSelfRecursive, + must_own_arguments: false, + host_exposed_layouts: HostExposedLayouts::NotHostExposed, + }; + + answer.push(GlueProc { + name: symbol, + proc_layout, + proc, + }); + } + + answer +} diff --git a/crates/compiler/mono/src/decision_tree.rs b/crates/compiler/mono/src/ir/decision_tree.rs similarity index 88% rename from crates/compiler/mono/src/decision_tree.rs rename to crates/compiler/mono/src/ir/decision_tree.rs index d9ed90e33b..c5c1a48425 100644 --- a/crates/compiler/mono/src/decision_tree.rs +++ b/crates/compiler/mono/src/ir/decision_tree.rs @@ -1,7 +1,8 @@ +use super::pattern::{build_list_index_probe, store_pattern, DestructType, ListIndex, Pattern}; use crate::borrow::Ownership; use crate::ir::{ - build_list_index_probe, BranchInfo, Call, CallType, DestructType, Env, Expr, JoinPointId, - ListIndex, Literal, Param, Pattern, Procs, Stmt, + substitute_in_exprs_many, BranchInfo, Call, CallType, CompiledGuardStmt, Env, Expr, + GuardStmtSpec, JoinPointId, Literal, Param, Procs, Stmt, }; use crate::layout::{ Builtin, InLayout, Layout, LayoutCache, LayoutInterner, TLLayoutInterner, TagIdIntType, @@ -9,6 +10,7 @@ use crate::layout::{ }; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; +use roc_collections::BumpMap; use roc_error_macros::internal_error; use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId, Union}; use roc_module::ident::TagName; @@ -19,6 +21,7 @@ use roc_module::symbol::Symbol; type Label = u64; const RECORD_TAG_NAME: &str = "#Record"; +const TUPLE_TAG_NAME: &str = "#Tuple"; /// Users of this module will mainly interact with this function. It takes /// some normal branches and gives out a decision tree that has "labels" at all @@ -41,14 +44,13 @@ fn compile<'a>( } #[derive(Clone, Debug, PartialEq)] -pub enum Guard<'a> { +pub(crate) enum Guard<'a> { NoGuard, Guard { /// pattern pattern: Pattern<'a>, - /// after assigning to symbol, the stmt jumps to this label - id: JoinPointId, - stmt: Stmt<'a>, + /// How to compile the guard statement. + stmt_spec: GuardStmtSpec, }, } @@ -56,6 +58,10 @@ impl<'a> Guard<'a> { fn is_none(&self) -> bool { self == &Guard::NoGuard } + + fn is_some(&self) -> bool { + !self.is_none() + } } type Edge<'a> = (GuardedTest<'a>, DecisionTree<'a>); @@ -72,19 +78,19 @@ enum DecisionTree<'a> { #[derive(Clone, Debug, PartialEq)] enum GuardedTest<'a> { - // e.g. `_ if True -> ...` + // e.g. `x if True -> ...` GuardedNoTest { /// pattern pattern: Pattern<'a>, - /// after assigning to symbol, the stmt jumps to this label - id: JoinPointId, - /// body - stmt: Stmt<'a>, + /// How to compile the guard body. + stmt_spec: GuardStmtSpec, }, + // e.g. ` -> ...` TestNotGuarded { test: Test<'a>, }, - Placeholder, + // e.g. `_ -> ...` or `x -> ...` + PlaceholderWithGuard, } #[derive(Clone, Copy, Debug, PartialEq, Hash)] @@ -187,15 +193,15 @@ impl<'a> Hash for Test<'a> { impl<'a> Hash for GuardedTest<'a> { fn hash(&self, state: &mut H) { match self { - GuardedTest::GuardedNoTest { id, .. } => { + GuardedTest::GuardedNoTest { stmt_spec, .. } => { state.write_u8(1); - id.hash(state); + stmt_spec.hash(state); } GuardedTest::TestNotGuarded { test } => { state.write_u8(0); test.hash(state); } - GuardedTest::Placeholder => { + GuardedTest::PlaceholderWithGuard => { state.write_u8(2); } } @@ -231,8 +237,8 @@ fn to_decision_tree<'a>( match first.guard { Guard::NoGuard => unreachable!(), - Guard::Guard { id, stmt, pattern } => { - let guarded_test = GuardedTest::GuardedNoTest { id, stmt, pattern }; + Guard::Guard { pattern, stmt_spec } => { + let guarded_test = GuardedTest::GuardedNoTest { pattern, stmt_spec }; // the guard test does not have a path let path = vec![]; @@ -263,6 +269,7 @@ fn to_decision_tree<'a>( let path = pick_path(&branches).clone(); let bs = branches.clone(); + let (edges, fallback) = gather_edges(interner, branches, &path); let mut decision_edges: Vec<_> = edges @@ -307,7 +314,7 @@ fn break_out_guard<'a>( ) -> DecisionTree<'a> { match edges .iter() - .position(|(t, _)| matches!(t, GuardedTest::Placeholder)) + .position(|(t, _)| matches!(t, GuardedTest::PlaceholderWithGuard)) { None => DecisionTree::Decision { path, @@ -346,7 +353,7 @@ fn guarded_tests_are_complete(tests: &[GuardedTest]) -> bool { .all(|t| matches!(t, GuardedTest::TestNotGuarded { .. })); match tests.last().unwrap() { - GuardedTest::Placeholder => false, + GuardedTest::PlaceholderWithGuard => false, GuardedTest::GuardedNoTest { .. } => false, GuardedTest::TestNotGuarded { test } => no_guard && tests_are_complete_help(test, length), } @@ -572,6 +579,31 @@ fn test_for_pattern<'a>(pattern: &Pattern<'a>) -> Option> { } } + TupleDestructure(destructs, _) => { + // not rendered, so pick the easiest + let union = Union { + render_as: RenderAs::Tag, + alternatives: vec![Ctor { + tag_id: TagId(0), + name: CtorName::Tag(TagName(TUPLE_TAG_NAME.into())), + arity: destructs.len(), + }], + }; + + let mut arguments = std::vec::Vec::new(); + + for destruct in destructs { + arguments.push((destruct.pat.clone(), destruct.layout)); + } + + IsCtor { + tag_id: 0, + ctor_name: CtorName::Tag(TagName(TUPLE_TAG_NAME.into())), + union, + arguments, + } + } + NewtypeDestructure { tag_name, arguments, @@ -661,7 +693,7 @@ fn test_at_path<'a>( if let Guard::Guard { .. } = &branch.guard { // no tests for this pattern remain, but we cannot discard it yet // because it has a guard! - Some(GuardedTest::Placeholder) + Some(GuardedTest::PlaceholderWithGuard) } else { None } @@ -683,10 +715,33 @@ fn edges_for<'a>( // if we test for a guard, skip all branches until one that has a guard let it = match test { - GuardedTest::GuardedNoTest { .. } | GuardedTest::Placeholder => { + GuardedTest::GuardedNoTest { .. } => { let index = branches .iter() - .position(|b| !b.guard.is_none()) + .position(|b| b.guard.is_some()) + .expect("if testing for a guard, one branch must have a guard"); + + branches[index..].iter() + } + GuardedTest::PlaceholderWithGuard => { + // Skip all branches until we hit the one with a placeholder and a guard. + let index = branches + .iter() + .position(|b| { + if b.guard.is_none() { + return false; + } + + let (_, pattern) = b + .patterns + .iter() + .find(|(branch_path, _)| branch_path == path) + .expect( + "if testing for a placeholder with guard, must find a branch matching the path", + ); + + test_for_pattern(pattern).is_none() + }) .expect("if testing for a guard, one branch must have a guard"); branches[index..].iter() @@ -715,7 +770,7 @@ fn to_relevant_branch<'a>( found_pattern: pattern, end, } => match guarded_test { - GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => { + GuardedTest::PlaceholderWithGuard | GuardedTest::GuardedNoTest { .. } => { // if there is no test, the pattern should not require any debug_assert!( matches!(pattern, Pattern::Identifier(_) | Pattern::Underscore,), @@ -790,6 +845,42 @@ fn to_relevant_branch_help<'a>( _ => None, }, + TupleDestructure(destructs, _) => match test { + IsCtor { + ctor_name: test_name, + tag_id, + .. + } => { + debug_assert!(test_name == &CtorName::Tag(TagName(TUPLE_TAG_NAME.into()))); + let destructs_len = destructs.len(); + let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| { + let pattern = destruct.pat.clone(); + + let mut new_path = path.to_vec(); + let next_instr = if destructs_len == 1 { + PathInstruction::NewType + } else { + PathInstruction::TagIndex { + index: index as u64, + tag_id: *tag_id, + } + }; + new_path.push(next_instr); + + (new_path, pattern) + }); + start.extend(sub_positions); + start.extend(end); + + Some(Branch { + goal: branch.goal, + guard: branch.guard.clone(), + patterns: start, + }) + } + _ => None, + }, + OpaqueUnwrap { opaque, argument } => match test { IsCtor { ctor_name: test_opaque_tag_name, @@ -1126,6 +1217,7 @@ fn needs_tests(pattern: &Pattern) -> bool { NewtypeDestructure { .. } | RecordDestructure(..) + | TupleDestructure(..) | AppliedTag { .. } | OpaqueUnwrap { .. } | BitLiteral { .. } @@ -1269,15 +1361,15 @@ fn small_branching_factor(branches: &[Branch], path: &[PathInstruction]) -> usiz relevant_tests.len() + (if !fallbacks { 0 } else { 1 }) } -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] enum Decider<'a, T> { Leaf(T), Guarded { - /// after assigning to symbol, the stmt jumps to this label - id: JoinPointId, - stmt: Stmt<'a>, pattern: Pattern<'a>, + /// The guard expression and how to compile it. + stmt_spec: GuardStmtSpec, + success: Box>, failure: Box>, }, @@ -1301,7 +1393,18 @@ enum Choice<'a> { type StoresVec<'a> = bumpalo::collections::Vec<'a, (Symbol, InLayout<'a>, Expr<'a>)>; -pub fn optimize_when<'a>( +struct JumpSpec<'a> { + target_index: u64, + id: JoinPointId, + /// Symbols, from the unpacked pattern, to add on when jumping to the target. + jump_pattern_param_symbols: &'a [Symbol], + + // Used to construct the joinpoint + join_params: &'a [Param<'a>], + join_body: Stmt<'a>, +} + +pub(crate) fn optimize_when<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, layout_cache: &mut LayoutCache<'a>, @@ -1310,11 +1413,11 @@ pub fn optimize_when<'a>( ret_layout: InLayout<'a>, opt_branches: bumpalo::collections::Vec<'a, (Pattern<'a>, Guard<'a>, Stmt<'a>)>, ) -> Stmt<'a> { - let (patterns, _indexed_branches) = opt_branches + let (patterns, indexed_branches): (_, Vec<_>) = opt_branches .into_iter() .enumerate() .map(|(index, (pattern, guard, branch))| { - let has_guard = !guard.is_none(); + let has_guard = guard.is_some(); ( (guard, pattern.clone(), index as u64), (index as u64, branch, pattern, has_guard), @@ -1322,8 +1425,6 @@ pub fn optimize_when<'a>( }) .unzip(); - let indexed_branches: Vec<_> = _indexed_branches; - let decision_tree = compile(&layout_cache.interner, patterns); let decider = tree_to_decider(decision_tree); @@ -1334,19 +1435,84 @@ pub fn optimize_when<'a>( let mut choices = MutMap::default(); let mut jumps = Vec::new(); - for (index, mut branch, pattern, has_guard) in indexed_branches.into_iter() { - // bind the fields referenced in the pattern. For guards this happens separately, so - // the pattern variables are defined when evaluating the guard. - if !has_guard { - branch = - crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch); + for (target, mut branch, pattern, has_guard) in indexed_branches.into_iter() { + let should_inline = { + let target_counts = &target_counts; + match target_counts.get(target as usize) { + None => unreachable!( + "this should never happen: {:?} not in {:?}", + target, target_counts + ), + Some(count) => *count == 1, + } + }; + + let join_params: &'a [Param<'a>]; + let jump_pattern_param_symbols: &'a [Symbol]; + match (has_guard, should_inline) { + (false, _) => { + // Bind the fields referenced in the pattern. + branch = store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch); + + join_params = &[]; + jump_pattern_param_symbols = &[]; + } + (true, true) => { + // Nothing more to do - the patterns will be bound when the guard is evaluated in + // `decide_to_branching`. + join_params = &[]; + jump_pattern_param_symbols = &[]; + } + (true, false) => { + // The patterns will be bound when the guard is evaluated, and then we need to get + // them back into the joinpoint here. + // + // So, figure out what symbols the pattern binds, and update the joinpoint + // parameter to take each symbol. Then, when the joinpoint is called, the unpacked + // symbols will be filled in. + // + // Since the joinpoint's parameters will be fresh symbols, the join body also needs + // updating. + let pattern_bindings = pattern.collect_symbols(cond_layout); + + let mut parameters_buf = bumpalo::collections::Vec::with_capacity_in(1, env.arena); + let mut pattern_symbols_buf = + bumpalo::collections::Vec::with_capacity_in(1, env.arena); + let mut substitutions = BumpMap::default(); + + for (pattern_symbol, layout) in pattern_bindings { + let param_symbol = env.unique_symbol(); + parameters_buf.push(Param { + symbol: param_symbol, + layout, + ownership: Ownership::Owned, + }); + pattern_symbols_buf.push(pattern_symbol); + substitutions.insert(pattern_symbol, param_symbol); + } + + join_params = parameters_buf.into_bump_slice(); + jump_pattern_param_symbols = pattern_symbols_buf.into_bump_slice(); + + substitute_in_exprs_many(env.arena, &mut branch, substitutions); + } } - let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch); + let ((branch_index, choice), opt_jump) = if should_inline { + ((target, Choice::Inline(branch)), None) + } else { + ((target, Choice::Jump(target)), Some((target, branch))) + }; - if let Some((index, body)) = opt_jump { + if let Some((target_index, body)) = opt_jump { let id = JoinPointId(env.unique_symbol()); - jumps.push((index, id, body)); + jumps.push(JumpSpec { + target_index, + id, + jump_pattern_param_symbols, + join_params, + join_body: body, + }); } choices.insert(branch_index, choice); @@ -1365,11 +1531,18 @@ pub fn optimize_when<'a>( &jumps, ); - for (_, id, body) in jumps.into_iter() { + for JumpSpec { + target_index: _, + id, + jump_pattern_param_symbols: _, + join_params, + join_body, + } in jumps.into_iter() + { stmt = Stmt::Join { id, - parameters: &[], - body: env.arena.alloc(body), + parameters: join_params, + body: env.arena.alloc(join_body), remainder: env.arena.alloc(stmt), }; } @@ -1406,7 +1579,7 @@ fn path_to_expr_help<'a>( PathInstruction::TagIndex { index, tag_id } => { let index = *index; - match layout_interner.get(layout) { + match layout_interner.chase_recursive(layout) { Layout::Union(union_layout) => { let inner_expr = Expr::UnionAtIndex { tag_id: *tag_id, @@ -1506,7 +1679,7 @@ fn test_to_comparison<'a>( // (e.g. record pattern guard matches) debug_assert!(union.alternatives.len() > 1); - match layout_interner.get(test_layout) { + match layout_interner.chase_recursive(test_layout) { Layout::Union(union_layout) => { let lhs = Expr::Literal(Literal::Int((tag_id as i128).to_ne_bytes())); @@ -1866,7 +2039,7 @@ fn decide_to_branching<'a>( cond_layout: InLayout<'a>, ret_layout: InLayout<'a>, decider: Decider<'a, Choice<'a>>, - jumps: &[(u64, JoinPointId, Stmt<'a>)], + jumps: &[JumpSpec<'a>], ) -> Stmt<'a> { use Choice::*; use Decider::*; @@ -1876,16 +2049,15 @@ fn decide_to_branching<'a>( match decider { Leaf(Jump(label)) => { let index = jumps - .binary_search_by_key(&label, |r| r.0) + .binary_search_by_key(&label, |r| r.target_index) .expect("jump not in list of jumps"); - Stmt::Jump(jumps[index].1, &[]) + Stmt::Jump(jumps[index].id, jumps[index].jump_pattern_param_symbols) } Leaf(Inline(expr)) => expr, Guarded { - id, - stmt, pattern, + stmt_spec, success, failure, } => { @@ -1931,14 +2103,19 @@ fn decide_to_branching<'a>( ownership: Ownership::Owned, }; + let CompiledGuardStmt { + join_point_id, + stmt, + } = stmt_spec.generate_guard_and_join(env, procs, layout_cache); + let join = Stmt::Join { - id, + id: join_point_id, parameters: arena.alloc([param]), - remainder: arena.alloc(stmt), body: arena.alloc(decide), + remainder: arena.alloc(stmt), }; - crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join) + store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join) } Chain { test_chain, @@ -2108,7 +2285,7 @@ fn decide_to_branching<'a>( // We have learned more about the exact layout of the cond (based on the path) // but tests are still relative to the original cond symbol - let inner_cond_layout_raw = layout_cache.get_in(inner_cond_layout); + let inner_cond_layout_raw = layout_cache.interner.chase_recursive(inner_cond_layout); let mut switch = if let Layout::Union(union_layout) = inner_cond_layout_raw { let tag_id_symbol = env.unique_symbol(); @@ -2219,15 +2396,17 @@ fn sort_edge_tests_by_priority(edges: &mut [Edge<'_>]) { edges.sort_by(|(t1, _), (t2, _)| match (t1, t2) { // Guarded takes priority (GuardedNoTest { .. }, GuardedNoTest { .. }) => Equal, - (GuardedNoTest { .. }, TestNotGuarded { .. }) | (GuardedNoTest { .. }, Placeholder) => Less, + (GuardedNoTest { .. }, TestNotGuarded { .. }) + | (GuardedNoTest { .. }, PlaceholderWithGuard) => Less, // Interesting case: what test do we pick? (TestNotGuarded { test: t1 }, TestNotGuarded { test: t2 }) => order_tests(t1, t2), // Otherwise we are between guarded and fall-backs (TestNotGuarded { .. }, GuardedNoTest { .. }) => Greater, - (TestNotGuarded { .. }, Placeholder) => Less, + (TestNotGuarded { .. }, PlaceholderWithGuard) => Less, // Placeholder is always last - (Placeholder, Placeholder) => Equal, - (Placeholder, GuardedNoTest { .. }) | (Placeholder, TestNotGuarded { .. }) => Greater, + (PlaceholderWithGuard, PlaceholderWithGuard) => Equal, + (PlaceholderWithGuard, GuardedNoTest { .. }) + | (PlaceholderWithGuard, TestNotGuarded { .. }) => Greater, }); fn order_tests(t1: &Test, t2: &Test) -> Ordering { @@ -2389,7 +2568,7 @@ fn fanout_decider_help<'a>( guarded_test: GuardedTest<'a>, ) -> (Test<'a>, Decider<'a, u64>) { match guarded_test { - GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => { + GuardedTest::PlaceholderWithGuard | GuardedTest::GuardedNoTest { .. } => { unreachable!("this would not end up in a switch") } GuardedTest::TestNotGuarded { test } => { @@ -2406,16 +2585,15 @@ fn chain_decider<'a>( success_tree: DecisionTree<'a>, ) -> Decider<'a, u64> { match guarded_test { - GuardedTest::GuardedNoTest { id, stmt, pattern } => { + GuardedTest::GuardedNoTest { pattern, stmt_spec } => { let failure = Box::new(tree_to_decider(failure_tree)); let success = Box::new(tree_to_decider(success_tree)); Decider::Guarded { - id, - stmt, pattern, + stmt_spec, success, - failure: failure.clone(), + failure, } } GuardedTest::TestNotGuarded { test } => { @@ -2426,7 +2604,7 @@ fn chain_decider<'a>( } } - GuardedTest::Placeholder => { + GuardedTest::PlaceholderWithGuard => { // ? tree_to_decider(success_tree) } @@ -2509,22 +2687,6 @@ fn count_targets(targets: &mut bumpalo::collections::Vec, initial: &Decider } } -#[allow(clippy::type_complexity)] -fn create_choices<'a>( - target_counts: &bumpalo::collections::Vec<'a, u64>, - target: u64, - branch: Stmt<'a>, -) -> ((u64, Choice<'a>), Option<(u64, Stmt<'a>)>) { - match target_counts.get(target as usize) { - None => unreachable!( - "this should never happen: {:?} not in {:?}", - target, target_counts - ), - Some(1) => ((target, Choice::Inline(branch)), None), - Some(_) => ((target, Choice::Jump(target)), Some((target, branch))), - } -} - fn insert_choices<'a>( choice_dict: &MutMap>, decider: Decider<'a, u64>, @@ -2538,15 +2700,13 @@ fn insert_choices<'a>( } Guarded { - id, - stmt, pattern, + stmt_spec, success, failure, } => Guarded { - id, - stmt, pattern, + stmt_spec, success: Box::new(insert_choices(choice_dict, *success)), failure: Box::new(insert_choices(choice_dict, *failure)), }, diff --git a/crates/compiler/mono/src/ir/literal.rs b/crates/compiler/mono/src/ir/literal.rs new file mode 100644 index 0000000000..8997bfa912 --- /dev/null +++ b/crates/compiler/mono/src/ir/literal.rs @@ -0,0 +1,116 @@ +use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_can::expr::IntValue; +use roc_error_macros::internal_error; +use roc_module::symbol::Symbol; +use roc_std::RocDec; + +use crate::layout::{Builtin, InLayout, Layout, LayoutInterner, TLLayoutInterner}; + +use super::pattern::Pattern; + +#[derive(Debug, Clone, Copy)] +pub enum IntOrFloatValue { + Int(IntValue), + Float(f64), +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Literal<'a> { + // Literals + /// stored as raw bytes rather than a number to avoid an alignment bump + Int([u8; 16]), + /// stored as raw bytes rather than a number to avoid an alignment bump + U128([u8; 16]), + Float(f64), + /// stored as raw bytes rather than a number to avoid an alignment bump + Decimal([u8; 16]), + Str(&'a str), + /// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool, + /// so they can (at least potentially) be emitted as 1-bit machine bools. + /// + /// So [True, False] compiles to this, and so do [A, B] and [Foo, Bar]. + /// However, a union like [True, False, Other Int] would not. + Bool(bool), + /// Closed tag unions containing between 3 and 256 tags (all of 0 arity) + /// compile to bytes, e.g. [Blue, Black, Red, Green, White] + Byte(u8), +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ListLiteralElement<'a> { + Literal(Literal<'a>), + Symbol(Symbol), +} + +impl<'a> ListLiteralElement<'a> { + pub fn to_symbol(&self) -> Option { + match self { + Self::Symbol(s) => Some(*s), + _ => None, + } + } +} + +pub enum NumLiteral { + Int([u8; 16], IntWidth), + U128([u8; 16]), + Float(f64, FloatWidth), + Decimal([u8; 16]), +} + +impl NumLiteral { + pub fn to_expr_literal(&self) -> Literal<'static> { + match *self { + NumLiteral::Int(n, _) => Literal::Int(n), + NumLiteral::U128(n) => Literal::U128(n), + NumLiteral::Float(n, _) => Literal::Float(n), + NumLiteral::Decimal(n) => Literal::Decimal(n), + } + } + pub fn to_pattern(&self) -> Pattern<'static> { + match *self { + NumLiteral::Int(n, w) => Pattern::IntLiteral(n, w), + NumLiteral::U128(n) => Pattern::IntLiteral(n, IntWidth::U128), + NumLiteral::Float(n, w) => Pattern::FloatLiteral(f64::to_bits(n), w), + NumLiteral::Decimal(n) => Pattern::DecimalLiteral(n), + } + } +} + +pub fn make_num_literal<'a>( + interner: &TLLayoutInterner<'a>, + layout: InLayout<'a>, + num_str: &str, + num_value: IntOrFloatValue, +) -> NumLiteral { + match interner.get(layout) { + Layout::Builtin(Builtin::Int(width)) => match num_value { + IntOrFloatValue::Int(IntValue::I128(n)) => NumLiteral::Int(n, width), + IntOrFloatValue::Int(IntValue::U128(n)) => NumLiteral::U128(n), + IntOrFloatValue::Float(..) => { + internal_error!("Float value where int was expected, should have been a type error") + } + }, + Layout::Builtin(Builtin::Float(width)) => match num_value { + IntOrFloatValue::Float(n) => NumLiteral::Float(n, width), + IntOrFloatValue::Int(int_value) => match int_value { + IntValue::I128(n) => NumLiteral::Float(i128::from_ne_bytes(n) as f64, width), + IntValue::U128(n) => NumLiteral::Float(u128::from_ne_bytes(n) as f64, width), + }, + }, + Layout::Builtin(Builtin::Decimal) => { + let dec = match RocDec::from_str(num_str) { + Some(d) => d, + None => internal_error!( + "Invalid decimal for float literal = {}. This should be a type error!", + num_str + ), + }; + NumLiteral::Decimal(dec.to_ne_bytes()) + } + layout => internal_error!( + "Found a non-num layout where a number was expected: {:?}", + layout + ), + } +} diff --git a/crates/compiler/mono/src/ir/pattern.rs b/crates/compiler/mono/src/ir/pattern.rs new file mode 100644 index 0000000000..4370a1aaf5 --- /dev/null +++ b/crates/compiler/mono/src/ir/pattern.rs @@ -0,0 +1,1815 @@ +use crate::ir::{substitute_in_exprs, Env, Expr, Procs, Stmt}; +use crate::layout::{ + self, Builtin, InLayout, Layout, LayoutCache, LayoutInterner, LayoutProblem, TagIdIntType, + UnionLayout, WrappedVariant, +}; +use bumpalo::collections::Vec; +use roc_builtins::bitcode::{FloatWidth, IntWidth}; +use roc_collections::all::{BumpMap, BumpMapDefault}; +use roc_error_macros::internal_error; +use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId}; +use roc_module::ident::{Lowercase, TagName}; +use roc_module::low_level::LowLevel; +use roc_module::symbol::Symbol; +use roc_problem::can::{RuntimeError, ShadowKind}; +use roc_types::subs::Variable; + +use super::literal::{make_num_literal, IntOrFloatValue}; +use super::{Call, CallType, Literal}; + +/// A pattern, including possible problems (e.g. shadowing) so that +/// codegen can generate a runtime error if this pattern is reached. +#[derive(Clone, Debug, PartialEq)] +pub enum Pattern<'a> { + Identifier(Symbol), + Underscore, + As(Box>, Symbol), + IntLiteral([u8; 16], IntWidth), + FloatLiteral(u64, FloatWidth), + DecimalLiteral([u8; 16]), + BitLiteral { + value: bool, + tag_name: TagName, + union: roc_exhaustive::Union, + }, + EnumLiteral { + tag_id: u8, + tag_name: TagName, + union: roc_exhaustive::Union, + }, + StrLiteral(Box), + + RecordDestructure(Vec<'a, RecordDestruct<'a>>, &'a [InLayout<'a>]), + TupleDestructure(Vec<'a, TupleDestruct<'a>>, &'a [InLayout<'a>]), + NewtypeDestructure { + tag_name: TagName, + arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>, + }, + AppliedTag { + tag_name: TagName, + tag_id: TagIdIntType, + arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>, + layout: UnionLayout<'a>, + union: roc_exhaustive::Union, + }, + Voided { + tag_name: TagName, + }, + OpaqueUnwrap { + opaque: Symbol, + argument: Box<(Pattern<'a>, InLayout<'a>)>, + }, + List { + arity: ListArity, + element_layout: InLayout<'a>, + elements: Vec<'a, Pattern<'a>>, + }, +} + +impl<'a> Pattern<'a> { + /// This pattern contains a pattern match on Void (i.e. [], the empty tag union) + /// such branches are not reachable at runtime + pub fn is_voided(&self) -> bool { + let mut stack: std::vec::Vec<&Pattern> = vec![self]; + + while let Some(pattern) = stack.pop() { + match pattern { + Pattern::Identifier(_) + | Pattern::Underscore + | Pattern::IntLiteral(_, _) + | Pattern::FloatLiteral(_, _) + | Pattern::DecimalLiteral(_) + | Pattern::BitLiteral { .. } + | Pattern::EnumLiteral { .. } + | Pattern::StrLiteral(_) => { /* terminal */ } + Pattern::As(subpattern, _) => stack.push(subpattern), + Pattern::RecordDestructure(destructs, _) => { + for destruct in destructs { + match &destruct.typ { + DestructType::Required(_) => { /* do nothing */ } + DestructType::Guard(pattern) => { + stack.push(pattern); + } + } + } + } + Pattern::TupleDestructure(destructs, _) => { + for destruct in destructs { + stack.push(&destruct.pat); + } + } + Pattern::NewtypeDestructure { arguments, .. } => { + stack.extend(arguments.iter().map(|(t, _)| t)) + } + Pattern::Voided { .. } => return true, + Pattern::AppliedTag { arguments, .. } => { + stack.extend(arguments.iter().map(|(t, _)| t)) + } + Pattern::OpaqueUnwrap { argument, .. } => stack.push(&argument.0), + Pattern::List { elements, .. } => stack.extend(elements), + } + } + + false + } + + pub fn collect_symbols( + &self, + layout: InLayout<'a>, + ) -> impl Iterator)> + '_ { + PatternBindingIter::One(self, layout) + } +} + +enum PatternBindingIter<'r, 'a> { + Done, + One(&'r Pattern<'a>, InLayout<'a>), + Stack(std::vec::Vec<(PatternBindingWork<'r, 'a>, InLayout<'a>)>), +} + +enum PatternBindingWork<'r, 'a> { + Pat(&'r Pattern<'a>), + RecordDestruct(&'r DestructType<'a>), +} + +impl<'r, 'a> Iterator for PatternBindingIter<'r, 'a> { + type Item = (Symbol, InLayout<'a>); + + fn next(&mut self) -> Option { + use Pattern::*; + use PatternBindingIter::*; + use PatternBindingWork::*; + match self { + Done => None, + One(pattern, layout) => { + let layout = *layout; + match pattern { + Identifier(symbol) => { + *self = Done; + (*symbol, layout).into() + } + Underscore => None, + As(pat, symbol) => { + *self = One(pat, layout); + (*symbol, layout).into() + } + RecordDestructure(destructs, _) => { + let stack = destructs + .iter() + .map(|destruct| (RecordDestruct(&destruct.typ), destruct.layout)) + .rev() + .collect(); + *self = Stack(stack); + self.next() + } + TupleDestructure(destructs, _) => { + let stack = destructs + .iter() + .map(|destruct| (Pat(&destruct.pat), destruct.layout)) + .rev() + .collect(); + *self = Stack(stack); + self.next() + } + NewtypeDestructure { arguments, .. } | AppliedTag { arguments, .. } => { + let stack = arguments.iter().map(|(p, l)| (Pat(p), *l)).rev().collect(); + *self = Stack(stack); + self.next() + } + OpaqueUnwrap { argument, .. } => { + *self = One(&argument.0, layout); + self.next() + } + List { + element_layout, + elements, + .. + } => { + let stack = elements + .iter() + .map(|p| (Pat(p), *element_layout)) + .rev() + .collect(); + *self = Stack(stack); + self.next() + } + IntLiteral(_, _) + | FloatLiteral(_, _) + | DecimalLiteral(_) + | BitLiteral { .. } + | EnumLiteral { .. } + | StrLiteral(_) + | Voided { .. } => None, + } + } + Stack(stack) => { + while let Some((pat, layout)) = stack.pop() { + match pat { + Pat(pattern) => match pattern { + Identifier(symbol) => return (*symbol, layout).into(), + As(pat, symbol) => { + stack.push((Pat(pat), layout)); + return (*symbol, layout).into(); + } + RecordDestructure(destructs, _) => stack.extend( + destructs + .iter() + .map(|destruct| { + (RecordDestruct(&destruct.typ), destruct.layout) + }) + .rev(), + ), + TupleDestructure(destructs, _) => stack.extend( + destructs + .iter() + .map(|destruct| (Pat(&destruct.pat), destruct.layout)) + .rev(), + ), + NewtypeDestructure { arguments, .. } | AppliedTag { arguments, .. } => { + stack.extend(arguments.iter().map(|(p, l)| (Pat(p), *l)).rev()) + } + OpaqueUnwrap { argument, .. } => { + stack.push((Pat(&argument.0), layout)); + } + List { + element_layout, + elements, + .. + } => { + stack.extend( + elements.iter().map(|p| (Pat(p), *element_layout)).rev(), + ); + } + IntLiteral(_, _) + | FloatLiteral(_, _) + | DecimalLiteral(_) + | BitLiteral { .. } + | EnumLiteral { .. } + | Underscore + | StrLiteral(_) + | Voided { .. } => {} + }, + PatternBindingWork::RecordDestruct(_) => todo!(), + } + } + + *self = Done; + None + } + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct RecordDestruct<'a> { + pub label: Lowercase, + pub variable: Variable, + pub layout: InLayout<'a>, + pub typ: DestructType<'a>, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TupleDestruct<'a> { + pub index: usize, + pub variable: Variable, + pub layout: InLayout<'a>, + pub pat: Pattern<'a>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum DestructType<'a> { + Required(Symbol), + Guard(Pattern<'a>), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct WhenBranch<'a> { + pub patterns: Vec<'a, Pattern<'a>>, + pub value: Expr<'a>, + pub guard: Option>, +} + +#[allow(clippy::type_complexity)] +pub 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< + ( + Pattern<'a>, + Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, + ), + RuntimeError, +> { + let mut assignments = Vec::new_in(env.arena); + 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)>, +) -> Result, RuntimeError> { + use roc_can::pattern::Pattern::*; + + match can_pattern { + Underscore => Ok(Pattern::Underscore), + Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), + As(subpattern, symbol) => { + let mono_subpattern = + from_can_pattern_help(env, procs, layout_cache, &subpattern.value, assignments)?; + + Ok(Pattern::As(Box::new(mono_subpattern), *symbol)) + } + AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)), + IntLiteral(var, _, int_str, int, _bound) => Ok(make_num_literal_pattern( + env, + layout_cache, + *var, + int_str, + IntOrFloatValue::Int(*int), + )), + FloatLiteral(var, _, float_str, float, _bound) => Ok(make_num_literal_pattern( + env, + layout_cache, + *var, + float_str, + IntOrFloatValue::Float(*float), + )), + StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), + SingleQuote(var, _, c, _) => { + let layout = layout_cache.from_var(env.arena, *var, env.subs); + match layout.map(|l| layout_cache.get_in(l)) { + Ok(Layout::Builtin(Builtin::Int(width))) => { + Ok(Pattern::IntLiteral((*c as i128).to_ne_bytes(), width)) + } + o => internal_error!("an integer width was expected, but we found {:?}", o), + } + } + Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { + original_region: *region, + shadow: ident.clone(), + kind: ShadowKind::Variable, + }), + UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)), + MalformedPattern(_problem, region) => { + // TODO preserve malformed problem information here? + Err(RuntimeError::UnsupportedPattern(*region)) + } + OpaqueNotInScope(loc_ident) => { + // TODO(opaques) should be `RuntimeError::OpaqueNotDefined` + Err(RuntimeError::UnsupportedPattern(loc_ident.region)) + } + NumLiteral(var, num_str, num, _bound) => Ok(make_num_literal_pattern( + env, + layout_cache, + *var, + num_str, + IntOrFloatValue::Int(*num), + )), + + AppliedTag { + whole_var, + tag_name, + arguments, + .. + } => { + use crate::layout::UnionVariant::*; + use roc_exhaustive::Union; + + let res_variant = { + let mut layout_env = layout::Env::from_components( + layout_cache, + env.subs, + env.arena, + env.target_info, + ); + crate::layout::union_sorted_tags(&mut layout_env, *whole_var).map_err(Into::into) + }; + + let variant = match res_variant { + Ok(cached) => cached, + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + return Err(RuntimeError::UnresolvedTypeVar) + } + Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType), + }; + + let result = match variant { + Never => unreachable!( + "there is no pattern of type `[]`, union var {:?}", + *whole_var + ), + Unit => Pattern::EnumLiteral { + tag_id: 0, + tag_name: tag_name.clone(), + union: Union { + render_as: RenderAs::Tag, + alternatives: vec![Ctor { + tag_id: TagId(0), + name: CtorName::Tag(tag_name.clone()), + arity: 0, + }], + }, + }, + BoolUnion { ttrue, ffalse } => { + let (ttrue, ffalse) = (ttrue.expect_tag(), ffalse.expect_tag()); + Pattern::BitLiteral { + value: tag_name == &ttrue, + tag_name: tag_name.clone(), + union: Union { + render_as: RenderAs::Tag, + alternatives: vec![ + Ctor { + tag_id: TagId(0), + name: CtorName::Tag(ffalse), + arity: 0, + }, + Ctor { + tag_id: TagId(1), + name: CtorName::Tag(ttrue), + arity: 0, + }, + ], + }, + } + } + ByteUnion(tag_names) => { + let tag_id = tag_names + .iter() + .position(|key| tag_name == key.expect_tag_ref()) + .expect("tag must be in its own type"); + + let mut ctors = std::vec::Vec::with_capacity(tag_names.len()); + for (i, tag_name) in tag_names.into_iter().enumerate() { + ctors.push(Ctor { + tag_id: TagId(i as _), + name: CtorName::Tag(tag_name.expect_tag()), + arity: 0, + }) + } + + let union = roc_exhaustive::Union { + render_as: RenderAs::Tag, + alternatives: ctors, + }; + + Pattern::EnumLiteral { + tag_id: tag_id as u8, + tag_name: tag_name.clone(), + union, + } + } + Newtype { + arguments: field_layouts, + .. + } => { + let mut arguments = arguments.clone(); + + arguments.sort_by(|arg1, arg2| { + let size1 = layout_cache + .from_var(env.arena, arg1.0, env.subs) + .map(|x| layout_cache.interner.alignment_bytes(x)) + .unwrap_or(0); + + let size2 = layout_cache + .from_var(env.arena, arg2.0, env.subs) + .map(|x| layout_cache.interner.alignment_bytes(x)) + .unwrap_or(0); + + size2.cmp(&size1) + }); + + 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, + procs, + layout_cache, + &loc_pat.value, + assignments, + )?, + *layout, + )); + } + + Pattern::NewtypeDestructure { + tag_name: tag_name.clone(), + arguments: mono_args, + } + } + NewtypeByVoid { + data_tag_arguments, + data_tag_name, + .. + } => { + let data_tag_name = data_tag_name.expect_tag(); + + if tag_name != &data_tag_name { + // this tag is not represented at runtime + Pattern::Voided { + tag_name: tag_name.clone(), + } + } else { + let mut arguments = arguments.clone(); + + arguments.sort_by(|arg1, arg2| { + let size1 = layout_cache + .from_var(env.arena, arg1.0, env.subs) + .map(|x| layout_cache.interner.alignment_bytes(x)) + .unwrap_or(0); + + let size2 = layout_cache + .from_var(env.arena, arg2.0, env.subs) + .map(|x| layout_cache.interner.alignment_bytes(x)) + .unwrap_or(0); + + size2.cmp(&size1) + }); + + let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); + let it = arguments.iter().zip(data_tag_arguments.iter()); + for ((_, loc_pat), layout) in it { + mono_args.push(( + from_can_pattern_help( + env, + procs, + layout_cache, + &loc_pat.value, + assignments, + )?, + *layout, + )); + } + + Pattern::NewtypeDestructure { + tag_name: tag_name.clone(), + arguments: mono_args, + } + } + } + + Wrapped(variant) => { + let (tag_id, argument_layouts) = variant.tag_name_to_id(tag_name); + let number_of_tags = variant.number_of_tags(); + let mut ctors = std::vec::Vec::with_capacity(number_of_tags); + + let arguments = { + let mut temp = arguments.clone(); + + temp.sort_by(|arg1, arg2| { + let layout1 = + layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap(); + let layout2 = + layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap(); + + let size1 = layout_cache.interner.alignment_bytes(layout1); + let size2 = layout_cache.interner.alignment_bytes(layout2); + + size2.cmp(&size1) + }); + + temp + }; + + // we must derive the union layout from the whole_var, building it up + // from `layouts` would unroll recursive tag unions, and that leads to + // problems down the line because we hash layouts and an unrolled + // version is not the same as the minimal version. + let whole_var_layout = layout_cache.from_var(env.arena, *whole_var, env.subs); + let layout = + match whole_var_layout.map(|l| layout_cache.interner.chase_recursive(l)) { + Ok(Layout::Union(ul)) => ul, + _ => internal_error!(), + }; + + use WrappedVariant::*; + match variant { + NonRecursive { + sorted_tag_layouts: ref tags, + } => { + debug_assert!(tags.len() > 1); + + for (i, (tag_name, args)) in tags.iter().enumerate() { + ctors.push(Ctor { + tag_id: TagId(i as _), + name: CtorName::Tag(tag_name.expect_tag_ref().clone()), + arity: args.len(), + }) + } + + let union = roc_exhaustive::Union { + render_as: RenderAs::Tag, + alternatives: ctors, + }; + + let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); + + debug_assert_eq!( + arguments.len(), + argument_layouts.len(), + "The {:?} tag got {} arguments, but its layout expects {}!", + tag_name, + arguments.len(), + argument_layouts.len(), + ); + let it = argument_layouts.iter(); + + for ((_, loc_pat), layout) in arguments.iter().zip(it) { + mono_args.push(( + from_can_pattern_help( + env, + procs, + layout_cache, + &loc_pat.value, + assignments, + )?, + *layout, + )); + } + + Pattern::AppliedTag { + tag_name: tag_name.clone(), + tag_id: tag_id as _, + arguments: mono_args, + union, + layout, + } + } + + Recursive { + sorted_tag_layouts: ref tags, + } => { + debug_assert!(tags.len() > 1); + + for (i, (tag_name, args)) in tags.iter().enumerate() { + ctors.push(Ctor { + tag_id: TagId(i as _), + name: CtorName::Tag(tag_name.expect_tag_ref().clone()), + arity: args.len(), + }) + } + + let union = roc_exhaustive::Union { + render_as: RenderAs::Tag, + alternatives: ctors, + }; + + let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); + + debug_assert_eq!(arguments.len(), argument_layouts.len()); + let it = argument_layouts.iter(); + + for ((_, loc_pat), layout) in arguments.iter().zip(it) { + mono_args.push(( + from_can_pattern_help( + env, + procs, + layout_cache, + &loc_pat.value, + assignments, + )?, + *layout, + )); + } + + Pattern::AppliedTag { + tag_name: tag_name.clone(), + tag_id: tag_id as _, + arguments: mono_args, + union, + layout, + } + } + + NonNullableUnwrapped { + tag_name: w_tag_name, + fields, + } => { + debug_assert_eq!(w_tag_name.expect_tag_ref(), tag_name); + + ctors.push(Ctor { + tag_id: TagId(0), + name: CtorName::Tag(tag_name.clone()), + arity: fields.len(), + }); + + let union = roc_exhaustive::Union { + render_as: RenderAs::Tag, + alternatives: ctors, + }; + + let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); + + debug_assert_eq!(arguments.len(), argument_layouts.len()); + let it = argument_layouts.iter(); + + for ((_, loc_pat), layout) in arguments.iter().zip(it) { + mono_args.push(( + from_can_pattern_help( + env, + procs, + layout_cache, + &loc_pat.value, + assignments, + )?, + *layout, + )); + } + + Pattern::AppliedTag { + tag_name: tag_name.clone(), + tag_id: tag_id as _, + arguments: mono_args, + union, + layout, + } + } + + NullableWrapped { + sorted_tag_layouts: ref non_nulled_tags, + nullable_id, + nullable_name, + } => { + for id in 0..(non_nulled_tags.len() + 1) { + if id == nullable_id as usize { + ctors.push(Ctor { + tag_id: TagId(id as _), + name: CtorName::Tag(nullable_name.expect_tag_ref().clone()), + arity: 0, + }); + } else { + let i = if id < nullable_id.into() { id } else { id - 1 }; + let (tag_name, args) = &non_nulled_tags[i]; + ctors.push(Ctor { + tag_id: TagId(i as _), + name: CtorName::Tag(tag_name.expect_tag_ref().clone()), + arity: args.len(), + }); + } + } + + let union = roc_exhaustive::Union { + render_as: RenderAs::Tag, + alternatives: ctors, + }; + + let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); + + let it = if tag_name == nullable_name.expect_tag_ref() { + [].iter() + } else { + argument_layouts.iter() + }; + + for ((_, loc_pat), layout) in arguments.iter().zip(it) { + mono_args.push(( + from_can_pattern_help( + env, + procs, + layout_cache, + &loc_pat.value, + assignments, + )?, + *layout, + )); + } + + Pattern::AppliedTag { + tag_name: tag_name.clone(), + tag_id: tag_id as _, + arguments: mono_args, + union, + layout, + } + } + + NullableUnwrapped { + other_fields, + nullable_id, + nullable_name, + other_name: _, + } => { + debug_assert!(!other_fields.is_empty()); + + ctors.push(Ctor { + tag_id: TagId(nullable_id as _), + name: CtorName::Tag(nullable_name.expect_tag_ref().clone()), + arity: 0, + }); + + ctors.push(Ctor { + tag_id: TagId(!nullable_id as _), + name: CtorName::Tag(nullable_name.expect_tag_ref().clone()), + arity: other_fields.len(), + }); + + let union = roc_exhaustive::Union { + render_as: RenderAs::Tag, + alternatives: ctors, + }; + + let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); + + let it = if tag_name == nullable_name.expect_tag_ref() { + [].iter() + } else { + argument_layouts.iter() + }; + + for ((_, loc_pat), layout) in arguments.iter().zip(it) { + mono_args.push(( + from_can_pattern_help( + env, + procs, + layout_cache, + &loc_pat.value, + assignments, + )?, + *layout, + )); + } + + Pattern::AppliedTag { + tag_name: tag_name.clone(), + tag_id: tag_id as _, + arguments: mono_args, + union, + layout, + } + } + } + } + }; + + Ok(result) + } + + UnwrappedOpaque { + opaque, argument, .. + } => { + let (arg_var, loc_arg_pattern) = &(**argument); + let arg_layout = layout_cache + .from_var(env.arena, *arg_var, env.subs) + .unwrap(); + 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)), + }) + } + + TupleDestructure { + whole_var, + destructs, + .. + } => { + // sorted fields based on the type + let sorted_elems = { + let mut layout_env = layout::Env::from_components( + layout_cache, + env.subs, + env.arena, + env.target_info, + ); + crate::layout::sort_tuple_elems(&mut layout_env, *whole_var) + .map_err(RuntimeError::from)? + }; + + // sorted fields based on the destruct + let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); + let mut destructs_by_index = Vec::with_capacity_in(destructs.len(), env.arena); + destructs_by_index.extend(destructs.iter().map(Some)); + + let mut elem_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena); + + for (index, variable, res_layout) in sorted_elems.into_iter() { + if index < destructs.len() { + // this elem is destructured by the pattern + mono_destructs.push(from_can_tuple_destruct( + env, + procs, + layout_cache, + &destructs[index].value, + res_layout, + assignments, + )?); + } else { + // this elem is not destructured by the pattern + // put in an underscore + mono_destructs.push(TupleDestruct { + index, + variable, + layout: res_layout, + pat: Pattern::Underscore, + }); + } + + // the layout of this field is part of the layout of the record + elem_layouts.push(res_layout); + } + + Ok(Pattern::TupleDestructure( + mono_destructs, + elem_layouts.into_bump_slice(), + )) + } + + RecordDestructure { + whole_var, + destructs, + .. + } => { + // sorted fields based on the type + let sorted_fields = { + let mut layout_env = layout::Env::from_components( + layout_cache, + env.subs, + env.arena, + env.target_info, + ); + crate::layout::sort_record_fields(&mut layout_env, *whole_var) + .map_err(RuntimeError::from)? + }; + + // sorted fields based on the destruct + let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); + let mut destructs_by_label = BumpMap::with_capacity_in(destructs.len(), env.arena); + destructs_by_label.extend(destructs.iter().map(|x| (&x.value.label, x))); + + let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); + + // next we step through both sequences of fields. The outer loop is the sequence based + // on the type, since not all fields need to actually be destructured in the source + // language. + // + // However in mono patterns, we do destruct all patterns (but use Underscore) when + // in the source the field is not matche in the source language. + // + // Optional fields somewhat complicate the matter here + + for (label, variable, res_layout) in sorted_fields.into_iter() { + match res_layout { + Ok(field_layout) => { + // the field is non-optional according to the type + + match destructs_by_label.remove(&label) { + Some(destruct) => { + // this field is destructured by the pattern + mono_destructs.push(from_can_record_destruct( + env, + procs, + layout_cache, + &destruct.value, + field_layout, + assignments, + )?); + } + None => { + // this field is not destructured by the pattern + // put in an underscore + mono_destructs.push(RecordDestruct { + label: label.clone(), + variable, + layout: field_layout, + typ: DestructType::Guard(Pattern::Underscore), + }); + } + } + + // the layout of this field is part of the layout of the record + field_layouts.push(field_layout); + } + Err(field_layout) => { + // the field is optional according to the type + match destructs_by_label.remove(&label) { + Some(destruct) => { + // this field is destructured by the pattern + match &destruct.value.typ { + roc_can::pattern::DestructType::Optional(_, loc_expr) => { + // if we reach this stage, the optional field is not present + // so we push the default assignment into the branch + assignments.push(( + destruct.value.symbol, + variable, + loc_expr.value.clone(), + )); + } + _ => unreachable!( + "only optional destructs can be optional fields" + ), + }; + } + None => { + // this field is not destructured by the pattern + // put in an underscore + mono_destructs.push(RecordDestruct { + label: label.clone(), + variable, + layout: field_layout, + typ: DestructType::Guard(Pattern::Underscore), + }); + } + } + } + } + } + + for (_, destruct) in destructs_by_label.drain() { + // this destruct is not in the type, but is in the pattern + // it must be an optional field, and we will use the default + match &destruct.value.typ { + roc_can::pattern::DestructType::Optional(field_var, loc_expr) => { + assignments.push(( + destruct.value.symbol, + // destruct.value.var, + *field_var, + loc_expr.value.clone(), + )); + } + _ => unreachable!("only optional destructs can be optional fields"), + } + } + + Ok(Pattern::RecordDestructure( + mono_destructs, + field_layouts.into_bump_slice(), + )) + } + + List { + list_var: _, + elem_var, + patterns, + } => { + let element_layout = match layout_cache.from_var(env.arena, *elem_var, env.subs) { + Ok(lay) => lay, + Err(LayoutProblem::UnresolvedTypeVar(_)) => { + return Err(RuntimeError::UnresolvedTypeVar) + } + Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType), + }; + + let arity = patterns.arity(); + + let mut mono_patterns = Vec::with_capacity_in(patterns.patterns.len(), env.arena); + for loc_pat in patterns.patterns.iter() { + let mono_pat = + from_can_pattern_help(env, procs, layout_cache, &loc_pat.value, assignments)?; + mono_patterns.push(mono_pat); + } + + Ok(Pattern::List { + arity, + element_layout, + elements: mono_patterns, + }) + } + } +} + +fn make_num_literal_pattern<'a>( + env: &mut Env<'a, '_>, + layout_cache: &mut LayoutCache<'a>, + variable: Variable, + num_str: &str, + num_value: IntOrFloatValue, +) -> Pattern<'a> { + let layout = layout_cache + .from_var(env.arena, variable, env.subs) + .unwrap(); + let literal = make_num_literal(&layout_cache.interner, layout, num_str, num_value); + literal.to_pattern() +} + +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: InLayout<'a>, + assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, +) -> Result, RuntimeError> { + Ok(RecordDestruct { + label: can_rd.label.clone(), + variable: can_rd.var, + layout: field_layout, + typ: match &can_rd.typ { + roc_can::pattern::DestructType::Required => DestructType::Required(can_rd.symbol), + roc_can::pattern::DestructType::Optional(_, _) => { + // if we reach this stage, the optional field is present + DestructType::Required(can_rd.symbol) + } + roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard( + from_can_pattern_help(env, procs, layout_cache, &loc_pattern.value, assignments)?, + ), + }, + }) +} + +fn from_can_tuple_destruct<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + can_rd: &roc_can::pattern::TupleDestruct, + field_layout: InLayout<'a>, + assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>, +) -> Result, RuntimeError> { + Ok(TupleDestruct { + index: can_rd.destruct_index, + variable: can_rd.var, + layout: field_layout, + pat: from_can_pattern_help(env, procs, layout_cache, &can_rd.typ.1.value, assignments)?, + }) +} + +#[allow(clippy::too_many_arguments)] +pub fn store_pattern<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + can_pat: &Pattern<'a>, + outer_symbol: Symbol, + stmt: Stmt<'a>, +) -> Stmt<'a> { + match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) { + StorePattern::Productive(new) => new, + StorePattern::NotProductive(new) => new, + } +} + +enum StorePattern<'a> { + /// we bound new symbols + Productive(Stmt<'a>), + /// no new symbols were bound in this pattern + NotProductive(Stmt<'a>), +} + +/// It is crucial for correct RC insertion that we don't create dead variables! +#[allow(clippy::too_many_arguments)] +fn store_pattern_help<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + can_pat: &Pattern<'a>, + outer_symbol: Symbol, + mut stmt: Stmt<'a>, +) -> StorePattern<'a> { + use Pattern::*; + + match can_pat { + Identifier(symbol) => { + substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol); + } + Underscore => { + // do nothing + return StorePattern::NotProductive(stmt); + } + As(subpattern, symbol) => { + let stored_subpattern = + store_pattern_help(env, procs, layout_cache, subpattern, outer_symbol, stmt); + + let mut stmt = match stored_subpattern { + StorePattern::Productive(stmt) => stmt, + StorePattern::NotProductive(stmt) => stmt, + }; + + substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol); + + return StorePattern::Productive(stmt); + } + IntLiteral(_, _) + | FloatLiteral(_, _) + | DecimalLiteral(_) + | EnumLiteral { .. } + | BitLiteral { .. } + | StrLiteral(_) => { + return StorePattern::NotProductive(stmt); + } + NewtypeDestructure { arguments, .. } => match arguments.as_slice() { + [(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); + fields.extend(arguments.iter().map(|x| x.1)); + + let layout = + layout_cache.put_in(Layout::struct_no_name_order(fields.into_bump_slice())); + + return store_newtype_pattern( + env, + procs, + layout_cache, + outer_symbol, + layout, + arguments, + stmt, + ); + } + }, + AppliedTag { + arguments, + layout, + tag_id, + .. + } => { + return store_tag_pattern( + env, + procs, + layout_cache, + outer_symbol, + *layout, + arguments, + *tag_id, + stmt, + ); + } + + List { + arity, + element_layout, + elements, + } => { + return store_list_pattern( + env, + procs, + layout_cache, + outer_symbol, + *arity, + *element_layout, + elements, + stmt, + ) + } + + Voided { .. } => { + return StorePattern::NotProductive(stmt); + } + + OpaqueUnwrap { argument, .. } => { + 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); + } + DestructType::Guard(guard_pattern) => { + return store_pattern_help( + env, + procs, + layout_cache, + guard_pattern, + outer_symbol, + stmt, + ); + } + } + } + } + RecordDestructure(destructs, sorted_fields) => { + let mut is_productive = false; + for (index, destruct) in destructs.iter().enumerate().rev() { + match store_record_destruct( + env, + procs, + layout_cache, + destruct, + index as u64, + outer_symbol, + sorted_fields, + stmt, + ) { + StorePattern::Productive(new) => { + is_productive = true; + stmt = new; + } + StorePattern::NotProductive(new) => { + stmt = new; + } + } + } + + if !is_productive { + return StorePattern::NotProductive(stmt); + } + } + + TupleDestructure(destructs, [_single_field]) => { + if let Some(destruct) = destructs.first() { + return store_pattern_help( + env, + procs, + layout_cache, + &destruct.pat, + outer_symbol, + stmt, + ); + } + } + TupleDestructure(destructs, sorted_fields) => { + let mut is_productive = false; + for (index, destruct) in destructs.iter().enumerate().rev() { + match store_tuple_destruct( + env, + procs, + layout_cache, + destruct, + index as u64, + outer_symbol, + sorted_fields, + stmt, + ) { + StorePattern::Productive(new) => { + is_productive = true; + stmt = new; + } + StorePattern::NotProductive(new) => { + stmt = new; + } + } + } + + if !is_productive { + return StorePattern::NotProductive(stmt); + } + } + } + + StorePattern::Productive(stmt) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct ListIndex( + /// Positive if we should index from the head, negative if we should index from the tail + /// 0 is lst[0] + /// -1 is lst[List.len lst - 1] + i64, +); + +impl ListIndex { + pub fn from_pattern_index(index: usize, arity: ListArity) -> Self { + match arity { + ListArity::Exact(_) => Self(index as _), + ListArity::Slice(head, tail) => { + if index < head { + Self(index as _) + } else { + // Slice(head=2, tail=5) + // + // s t ... w y z x q + // 0 1 2 3 4 5 6 index + // 0 1 2 3 4 (index - head) + // 5 4 3 2 1 (tail - (index - head)) + Self(-((tail - (index - head)) as i64)) + } + } + } + } +} + +pub(crate) type Store<'a> = (Symbol, InLayout<'a>, Expr<'a>); + +/// Builds the list index we should index into +#[must_use] +pub(crate) fn build_list_index_probe<'a>( + env: &mut Env<'a, '_>, + list_sym: Symbol, + list_index: &ListIndex, +) -> (Symbol, impl DoubleEndedIterator>) { + let usize_layout = Layout::usize(env.target_info); + + let list_index = list_index.0; + let index_sym = env.unique_symbol(); + + let (opt_len_store, opt_offset_store, index_store) = if list_index >= 0 { + let index_expr = Expr::Literal(Literal::Int((list_index as i128).to_ne_bytes())); + + let index_store = (index_sym, usize_layout, index_expr); + + (None, None, index_store) + } else { + let len_sym = env.unique_symbol(); + let len_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::ListLen, + update_mode: env.next_update_mode_id(), + }, + arguments: env.arena.alloc([list_sym]), + }); + + let offset = list_index.abs(); + let offset_sym = env.unique_symbol(); + let offset_expr = Expr::Literal(Literal::Int((offset as i128).to_ne_bytes())); + + let index_expr = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::NumSub, + update_mode: env.next_update_mode_id(), + }, + arguments: env.arena.alloc([len_sym, offset_sym]), + }); + + let len_store = (len_sym, usize_layout, len_expr); + let offset_store = (offset_sym, usize_layout, offset_expr); + let index_store = (index_sym, usize_layout, index_expr); + + (Some(len_store), Some(offset_store), index_store) + }; + + let stores = (opt_len_store.into_iter()) + .chain(opt_offset_store) + .chain([index_store]); + + (index_sym, stores) +} + +#[allow(clippy::too_many_arguments)] +fn store_list_pattern<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + list_sym: Symbol, + list_arity: ListArity, + element_layout: InLayout<'a>, + elements: &[Pattern<'a>], + mut stmt: Stmt<'a>, +) -> StorePattern<'a> { + use Pattern::*; + + let mut is_productive = false; + + for (index, element) in elements.iter().enumerate().rev() { + let compute_element_load = |env: &mut Env<'a, '_>| { + let list_index = ListIndex::from_pattern_index(index, list_arity); + + let (index_sym, needed_stores) = build_list_index_probe(env, list_sym, &list_index); + + let load = Expr::Call(Call { + call_type: CallType::LowLevel { + op: LowLevel::ListGetUnsafe, + update_mode: env.next_update_mode_id(), + }, + arguments: env.arena.alloc([list_sym, index_sym]), + }); + + (load, needed_stores) + }; + + let (store_loaded, needed_stores) = match element { + Identifier(symbol) => { + let (load, needed_stores) = compute_element_load(env); + + // store immediately in the given symbol + ( + Stmt::Let(*symbol, load, element_layout, env.arena.alloc(stmt)), + needed_stores, + ) + } + Underscore + | IntLiteral(_, _) + | FloatLiteral(_, _) + | DecimalLiteral(_) + | EnumLiteral { .. } + | BitLiteral { .. } + | StrLiteral(_) => { + // ignore + continue; + } + _ => { + // store the field in a symbol, and continue matching on it + let symbol = env.unique_symbol(); + + // first recurse, continuing to unpack symbol + match store_pattern_help(env, procs, layout_cache, element, symbol, stmt) { + StorePattern::Productive(new) => { + stmt = new; + let (load, needed_stores) = compute_element_load(env); + + // only if we bind one of its (sub)fields to a used name should we + // extract the field + ( + Stmt::Let(symbol, load, element_layout, env.arena.alloc(stmt)), + needed_stores, + ) + } + StorePattern::NotProductive(new) => { + // do nothing + stmt = new; + continue; + } + } + } + }; + + is_productive = true; + + stmt = store_loaded; + for (sym, lay, expr) in needed_stores.rev() { + stmt = Stmt::Let(sym, expr, lay, env.arena.alloc(stmt)); + } + } + + if is_productive { + StorePattern::Productive(stmt) + } else { + StorePattern::NotProductive(stmt) + } +} + +#[allow(clippy::too_many_arguments)] +fn store_tag_pattern<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + structure: Symbol, + union_layout: UnionLayout<'a>, + arguments: &[(Pattern<'a>, InLayout<'a>)], + tag_id: TagIdIntType, + mut stmt: Stmt<'a>, +) -> StorePattern<'a> { + use Pattern::*; + + let mut is_productive = false; + + for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() { + let mut arg_layout = *arg_layout; + + if let Layout::RecursivePointer(_) = layout_cache.get_in(arg_layout) { + // TODO(recursive-layouts): fix after disjoint rec ptrs + arg_layout = layout_cache.put_in(Layout::Union(union_layout)); + } + + let load = Expr::UnionAtIndex { + index: index as u64, + structure, + tag_id, + union_layout, + }; + + match argument { + Identifier(symbol) => { + // store immediately in the given symbol + stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt)); + is_productive = true; + } + Underscore => { + // ignore + } + IntLiteral(_, _) + | FloatLiteral(_, _) + | DecimalLiteral(_) + | EnumLiteral { .. } + | BitLiteral { .. } + | StrLiteral(_) => {} + _ => { + // store the field in a symbol, and continue matching on it + let symbol = env.unique_symbol(); + + // first recurse, continuing to unpack symbol + match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) { + StorePattern::Productive(new) => { + is_productive = true; + stmt = new; + // only if we bind one of its (sub)fields to a used name should we + // extract the field + stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt)); + } + StorePattern::NotProductive(new) => { + // do nothing + stmt = new; + } + } + } + } + } + + if is_productive { + StorePattern::Productive(stmt) + } else { + StorePattern::NotProductive(stmt) + } +} + +#[allow(clippy::too_many_arguments)] +fn store_newtype_pattern<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + structure: Symbol, + layout: InLayout<'a>, + arguments: &[(Pattern<'a>, InLayout<'a>)], + mut stmt: Stmt<'a>, +) -> StorePattern<'a> { + use Pattern::*; + + let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena); + let mut is_productive = false; + + for (_, layout) in arguments { + arg_layouts.push(*layout); + } + + for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() { + let mut arg_layout = *arg_layout; + + if let Layout::RecursivePointer(_) = layout_cache.get_in(arg_layout) { + arg_layout = layout; + } + + let load = Expr::StructAtIndex { + index: index as u64, + field_layouts: arg_layouts.clone().into_bump_slice(), + structure, + }; + + match argument { + Identifier(symbol) => { + stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt)); + is_productive = true; + } + Underscore => { + // ignore + } + IntLiteral(_, _) + | FloatLiteral(_, _) + | DecimalLiteral(_) + | EnumLiteral { .. } + | BitLiteral { .. } + | StrLiteral(_) => {} + _ => { + // store the field in a symbol, and continue matching on it + let symbol = env.unique_symbol(); + + // first recurse, continuing to unpack symbol + match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) { + StorePattern::Productive(new) => { + is_productive = true; + stmt = new; + // only if we bind one of its (sub)fields to a used name should we + // extract the field + stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt)); + } + StorePattern::NotProductive(new) => { + // do nothing + stmt = new; + } + } + } + } + } + + if is_productive { + StorePattern::Productive(stmt) + } else { + StorePattern::NotProductive(stmt) + } +} + +#[allow(clippy::too_many_arguments)] +fn store_tuple_destruct<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + destruct: &TupleDestruct<'a>, + index: u64, + outer_symbol: Symbol, + sorted_fields: &'a [InLayout<'a>], + mut stmt: Stmt<'a>, +) -> StorePattern<'a> { + use Pattern::*; + + let load = Expr::StructAtIndex { + index, + field_layouts: sorted_fields, + structure: outer_symbol, + }; + + match &destruct.pat { + Identifier(symbol) => { + stmt = Stmt::Let(*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 + // fields, but those not bound in the source code are guarded with the underscore + // pattern. So given some record `{ x : a, y : b }`, a match + // + // { x } -> ... + // + // is actually + // + // { x, y: _ } -> ... + // + // internally. But `y` is never used, so we must make sure it't not stored/loaded. + // + // This also happens with tuples, so when matching a tuple `(a, b, c)`, + // a pattern like `(x, y)` will be internally rewritten to `(x, y, _)`. + return StorePattern::NotProductive(stmt); + } + IntLiteral(_, _) + | FloatLiteral(_, _) + | DecimalLiteral(_) + | EnumLiteral { .. } + | BitLiteral { .. } + | StrLiteral(_) => { + return StorePattern::NotProductive(stmt); + } + + _ => { + let symbol = env.unique_symbol(); + + match store_pattern_help(env, procs, layout_cache, &destruct.pat, symbol, stmt) { + StorePattern::Productive(new) => { + stmt = new; + stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt)); + } + StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt), + } + } + } + + StorePattern::Productive(stmt) +} + +#[allow(clippy::too_many_arguments)] +fn store_record_destruct<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + destruct: &RecordDestruct<'a>, + index: u64, + outer_symbol: Symbol, + sorted_fields: &'a [InLayout<'a>], + mut stmt: Stmt<'a>, +) -> StorePattern<'a> { + use Pattern::*; + + let load = Expr::StructAtIndex { + index, + field_layouts: sorted_fields, + structure: outer_symbol, + }; + + match &destruct.typ { + DestructType::Required(symbol) => { + stmt = Stmt::Let(*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)); + } + Underscore => { + // important that this is special-cased to do nothing: mono record patterns will extract all the + // fields, but those not bound in the source code are guarded with the underscore + // pattern. So given some record `{ x : a, y : b }`, a match + // + // { x } -> ... + // + // is actually + // + // { x, y: _ } -> ... + // + // internally. But `y` is never used, so we must make sure it't not stored/loaded. + return StorePattern::NotProductive(stmt); + } + IntLiteral(_, _) + | FloatLiteral(_, _) + | DecimalLiteral(_) + | EnumLiteral { .. } + | BitLiteral { .. } + | StrLiteral(_) => { + return StorePattern::NotProductive(stmt); + } + + _ => { + let symbol = env.unique_symbol(); + + match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) { + StorePattern::Productive(new) => { + stmt = new; + stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt)); + } + StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt), + } + } + }, + } + + StorePattern::Productive(stmt) +} diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index a311aaf388..871f93b3b4 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -1,9 +1,11 @@ use crate::ir::Parens; +use crate::layout::intern::NeedsRecursionPointerFixup; use bitvec::vec::BitVec; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{default_hasher, FnvMap, MutMap}; +use roc_collections::VecSet; use roc_error_macros::{internal_error, todo_abilities}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, Symbol}; @@ -12,9 +14,12 @@ use roc_target::{PtrWidth, TargetInfo}; use roc_types::num::NumericRange; use roc_types::subs::{ self, Content, FlatType, GetSubsSlice, Label, OptVariable, RecordFields, Subs, TagExt, - UnsortedUnionLabels, Variable, VariableSubsSlice, + TupleElems, UnsortedUnionLabels, Variable, VariableSubsSlice, +}; +use roc_types::types::{ + gather_fields_unsorted_iter, gather_tuple_elems_unsorted_iter, RecordField, RecordFieldsError, + TupleElemsError, }; -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; @@ -120,9 +125,9 @@ pub struct LayoutCache<'a> { impl<'a> LayoutCache<'a> { pub fn new(interner: TLLayoutInterner<'a>, target_info: TargetInfo) -> Self { let mut cache = std::vec::Vec::with_capacity(4); - cache.push(CacheLayer::default()); + cache.push(Default::default()); let mut raw_cache = std::vec::Vec::with_capacity(4); - raw_cache.push(CacheLayer::default()); + raw_cache.push(Default::default()); Self { target_info, cache, @@ -299,14 +304,20 @@ impl<'a> LayoutCache<'a> { /// Invalidates the list of given root variables. /// Usually called after unification, when merged variables with changed contents need to be /// invalidated. - pub fn invalidate(&mut self, vars: impl IntoIterator) { + pub fn invalidate(&mut self, subs: &Subs, vars: impl IntoIterator) { + // TODO(layout-cache): optimize me somehow for var in vars.into_iter() { + let var = subs.get_root_key_without_compacting(var); for layer in self.cache.iter_mut().rev() { - layer.0.remove(&var); + layer + .0 + .retain(|k, _| !subs.equivalent_without_compacting(var, *k)); roc_tracing::debug!(?var, "invalidating cached layout"); } for layer in self.raw_function_cache.iter_mut().rev() { - layer.0.remove(&var); + layer + .0 + .retain(|k, _| !subs.equivalent_without_compacting(var, *k)); roc_tracing::debug!(?var, "invalidating cached layout"); } } @@ -481,7 +492,9 @@ impl<'a> RawFunctionLayout<'a> { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) } - LambdaSet(lset) => Self::layout_from_lambda_set(env, lset), + LambdaSet(_) => { + internal_error!("lambda set should only appear under a function, where it's handled independently."); + } Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), RangedNumber(..) => Layout::new_help(env, var, content).then(Self::ZeroArgumentThunk), @@ -554,15 +567,6 @@ impl<'a> RawFunctionLayout<'a> { } } - fn layout_from_lambda_set( - _env: &mut Env<'a, '_>, - _lset: subs::LambdaSet, - ) -> Cacheable> { - unreachable!() - // Lambda set is just a tag union from the layout's perspective. - // Self::layout_from_flat_type(env, lset.as_tag_union()) - } - fn layout_from_flat_type( env: &mut Env<'a, '_>, flat_type: FlatType, @@ -653,6 +657,17 @@ impl FieldOrderHash { fields.iter().for_each(|field| field.hash(&mut hasher)); Self(hasher.finish()) } + + pub fn from_ordered_tuple_elems(elems: &[usize]) -> Self { + if elems.is_empty() { + // HACK: we must make sure this is always equivalent to a `ZERO_FIELD_HASH`. + return Self::ZERO_FIELD_HASH; + } + + let mut hasher = DefaultHasher::new(); + elems.iter().for_each(|elem| elem.hash(&mut hasher)); + Self(hasher.finish()) + } } /// Types for code gen must be monomorphic. No type variables allowed! @@ -727,6 +742,7 @@ impl<'a> UnionLayout<'a> { self, alloc: &'b D, interner: &I, + seen_rec: &mut SeenRecPtrs<'a>, _parens: Parens, ) -> DocBuilder<'b, D, A> where @@ -740,14 +756,14 @@ impl<'a> UnionLayout<'a> { match self { NonRecursive(tags) => { let tags_doc = tags.iter().map(|fields| { - alloc.text("C ").append(alloc.intersperse( - fields.iter().map(|x| { - interner - .get(*x) - .to_doc(alloc, interner, Parens::InTypeParam) - }), - " ", - )) + alloc.text("C ").append( + alloc.intersperse( + fields + .iter() + .map(|x| interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)), + " ", + ), + ) }); alloc @@ -757,14 +773,14 @@ impl<'a> UnionLayout<'a> { } Recursive(tags) => { let tags_doc = tags.iter().map(|fields| { - alloc.text("C ").append(alloc.intersperse( - fields.iter().map(|x| { - interner - .get(*x) - .to_doc(alloc, interner, Parens::InTypeParam) - }), - " ", - )) + alloc.text("C ").append( + alloc.intersperse( + fields + .iter() + .map(|x| interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)), + " ", + ), + ) }); alloc .text("[") @@ -772,14 +788,14 @@ impl<'a> UnionLayout<'a> { .append(alloc.text("]")) } NonNullableUnwrapped(fields) => { - let fields_doc = alloc.text("C ").append(alloc.intersperse( - fields.iter().map(|x| { - interner - .get(*x) - .to_doc(alloc, interner, Parens::InTypeParam) - }), - " ", - )); + let fields_doc = alloc.text("C ").append( + alloc.intersperse( + fields + .iter() + .map(|x| interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)), + " ", + ), + ); alloc .text("[") .append(fields_doc) @@ -789,14 +805,14 @@ impl<'a> UnionLayout<'a> { nullable_id, other_fields, } => { - let fields_doc = alloc.text("C ").append(alloc.intersperse( - other_fields.iter().map(|x| { - interner - .get(*x) - .to_doc(alloc, interner, Parens::InTypeParam) - }), - " ", - )); + let fields_doc = alloc.text("C ").append( + alloc.intersperse( + other_fields + .iter() + .map(|x| interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)), + " ", + ), + ); let tags_doc = if nullable_id { alloc.concat(vec![alloc.text(", "), fields_doc]) } else { @@ -812,21 +828,20 @@ impl<'a> UnionLayout<'a> { other_tags, } => { let nullable_id = nullable_id as usize; - let tags_docs = (0..(other_tags.len() + 1)).map(|i| { - if i == nullable_id { - alloc.text("") - } else { - let idx = if i > nullable_id { i - 1 } else { i }; - alloc.text("C ").append(alloc.intersperse( - other_tags[idx].iter().map(|x| { - interner - .get(*x) - .to_doc(alloc, interner, Parens::InTypeParam) - }), - " ", - )) - } - }); + let tags_docs = + (0..(other_tags.len() + 1)).map(|i| { + if i == nullable_id { + alloc.text("") + } else { + let idx = if i > nullable_id { i - 1 } else { i }; + alloc.text("C ").append(alloc.intersperse( + other_tags[idx].iter().map(|x| { + interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam) + }), + " ", + )) + } + }); let tags_docs = alloc.intersperse(tags_docs, alloc.text(", ")); alloc .text("[") @@ -1273,7 +1288,12 @@ pub struct Niche<'a>(NichePriv<'a>); impl<'a> Niche<'a> { pub const NONE: Niche<'a> = Niche(NichePriv::Captures(&[])); - pub fn to_doc<'b, D, A, I>(self, alloc: &'b D, interner: &I) -> DocBuilder<'b, D, A> + pub fn to_doc<'b, D, A, I>( + self, + alloc: &'b D, + interner: &I, + seen_rec: &mut SeenRecPtrs<'a>, + ) -> DocBuilder<'b, D, A> where D: DocAllocator<'b, A>, D::Doc: Clone, @@ -1286,13 +1306,21 @@ impl<'a> Niche<'a> { alloc.intersperse( captures .iter() - .map(|c| interner.get(*c).to_doc(alloc, interner, Parens::NotNeeded)), + .map(|c| interner.to_doc(*c, alloc, seen_rec, Parens::NotNeeded)), alloc.reflow(", "), ), alloc.reflow("})"), ]), } } + + pub fn dbg_deep<'r, I: LayoutInterner<'a>>( + &'r self, + interner: &'r I, + ) -> crate::layout::intern::dbg::DbgFields<'a, 'r, I> { + let NichePriv::Captures(caps) = &self.0; + interner.dbg_deep_iter(caps) + } } #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] @@ -1517,35 +1545,7 @@ impl<'a> LambdaSet<'a> { where I: LayoutInterner<'a>, { - if left == right { - return true; - } - let left = interner.get(*left); - let right = interner.get(*right); - - let left = if matches!(left, Layout::RecursivePointer(_)) { - let runtime_repr = self.runtime_representation(); - debug_assert!(matches!( - interner.get(runtime_repr), - Layout::Union(UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. }) - )); - Layout::LambdaSet(*self) - } else { - left - }; - - let right = if matches!(right, Layout::RecursivePointer(_)) { - let runtime_repr = self.runtime_representation(); - debug_assert!(matches!( - interner.get(runtime_repr), - Layout::Union(UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. }) - )); - Layout::LambdaSet(*self) - } else { - right - }; - - left == right + interner.equiv(*left, *right) } fn layout_for_member(&self, interner: &I, comparator: F) -> ClosureRepresentation<'a> @@ -1558,7 +1558,7 @@ impl<'a> LambdaSet<'a> { return ClosureRepresentation::UnwrappedCapture(self.representation); } - let repr = interner.get(self.representation); + let repr = interner.chase_recursive(self.representation); match repr { Layout::Union(union) => { @@ -1621,11 +1621,27 @@ impl<'a> LambdaSet<'a> { union_layout: union, } } - UnionLayout::NonNullableUnwrapped(_) => todo!("recursive closures"), UnionLayout::NullableWrapped { nullable_id: _, other_tags: _, - } => todo!("recursive closures"), + } => { + let (index, (name, fields)) = self + .set + .iter() + .enumerate() + .find(|(_, (s, layouts))| comparator(*s, layouts)) + .unwrap(); + + let closure_name = *name; + + ClosureRepresentation::Union { + tag_id: index as TagIdIntType, + alphabetic_order_fields: fields, + closure_name, + union_layout: union, + } + } + UnionLayout::NonNullableUnwrapped(_) => internal_error!("I thought a non-nullable-unwrapped variant for a lambda set was impossible: how could such a lambda set be created without a base case?"), } } Layout::Struct { .. } => { @@ -1642,7 +1658,7 @@ impl<'a> LambdaSet<'a> { ClosureRepresentation::AlphabeticOrderStruct(fields) } layout => { - debug_assert!(self.has_enum_dispatch_repr(),); + debug_assert!(self.has_enum_dispatch_repr()); let enum_repr = match layout { Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool, Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8, @@ -1669,7 +1685,7 @@ impl<'a> LambdaSet<'a> { return ClosureCallOptions::UnwrappedCapture(self.representation); } - let repr = interner.get(self.representation); + let repr = interner.chase_recursive(self.representation); match repr { Layout::Union(union_layout) => { @@ -1764,7 +1780,7 @@ impl<'a> LambdaSet<'a> { Cacheable(result, criteria) }); - match result.map(|l| env.cache.get_in(l)) { + match result.map(|l| env.cache.interner.chase_recursive(l)) { Ok(Layout::LambdaSet(lambda_set)) => Cacheable(Ok(lambda_set), criteria), Err(err) => Cacheable(Err(err), criteria), Ok(layout) => internal_error!("other layout found for lambda set: {:?}", layout), @@ -1804,6 +1820,7 @@ impl<'a> LambdaSet<'a> { Vec::with_capacity_in(lambdas.len(), env.arena); let mut set_with_variables: std::vec::Vec<(&Symbol, &[Variable])> = std::vec::Vec::with_capacity(lambdas.len()); + let mut set_captures_have_naked_rec_ptr = false; let mut last_function_symbol = None; let mut lambdas_it = lambdas.iter().peekable(); @@ -1822,6 +1839,8 @@ impl<'a> LambdaSet<'a> { let mut criteria = CACHEABLE; let arg = cached!(Layout::from_var(env, *var), criteria); arguments.push(arg); + set_captures_have_naked_rec_ptr = + set_captures_have_naked_rec_ptr || criteria.has_naked_recursion_pointer; } let arguments = arguments.into_bump_slice(); @@ -1882,10 +1901,16 @@ impl<'a> LambdaSet<'a> { ); cache_criteria.and(criteria); + let needs_recursive_fixup = NeedsRecursionPointerFixup( + opt_recursion_var.is_some() && set_captures_have_naked_rec_ptr, + ); + let lambda_set = env.cache.interner.insert_lambda_set( + env.arena, fn_args, ret, env.arena.alloc(set.into_bump_slice()), + needs_recursive_fixup, representation, ); @@ -1895,9 +1920,11 @@ impl<'a> LambdaSet<'a> { // The lambda set is unbound which means it must be unused. Just give it the empty lambda set. // See also https://github.com/roc-lang/roc/issues/3163. let lambda_set = env.cache.interner.insert_lambda_set( + env.arena, fn_args, ret, &(&[] as &[(Symbol, &[InLayout])]), + NeedsRecursionPointerFixup(false), Layout::UNIT, ); Cacheable(Ok(lambda_set), cache_criteria) @@ -2128,6 +2155,16 @@ pub enum Builtin<'a> { List(InLayout<'a>), } +#[macro_export] +macro_rules! list_element_layout { + ($interner:expr, $list_layout:expr) => { + match $interner.get($list_layout) { + Layout::Builtin(Builtin::List(list_layout)) => list_layout, + _ => internal_error!("invalid list layout"), + } + }; +} + pub struct Env<'a, 'b> { target_info: TargetInfo, arena: &'a Bump, @@ -2353,7 +2390,9 @@ impl<'a> Layout<'a> { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) } - LambdaSet(lset) => layout_from_lambda_set(env, lset), + LambdaSet(_) => { + internal_error!("lambda set should only appear under a function, where it's handled independently."); + } Structure(flat_type) => layout_from_flat_type(env, flat_type), Alias(symbol, _args, actual_var, _) => { @@ -2702,42 +2741,58 @@ impl<'a> Layout<'a> { } } - pub fn to_doc<'b, D, A, I>( - self, - alloc: &'b D, - interner: &I, - parens: Parens, - ) -> DocBuilder<'b, D, A> + pub fn has_varying_stack_size(self, interner: &I, arena: &bumpalo::Bump) -> bool where - D: DocAllocator<'b, A>, - D::Doc: Clone, - A: Clone, I: LayoutInterner<'a>, { - use Layout::*; + let mut stack: Vec = bumpalo::collections::Vec::new_in(arena); - match self { - Builtin(builtin) => builtin.to_doc(alloc, interner, parens), - Struct { field_layouts, .. } => { - let fields_doc = field_layouts - .iter() - .map(|x| interner.get(*x).to_doc(alloc, interner, parens)); + stack.push(self); - alloc - .text("{") - .append(alloc.intersperse(fields_doc, ", ")) - .append(alloc.text("}")) + while let Some(layout) = stack.pop() { + match layout { + Layout::Builtin(builtin) => match builtin { + Builtin::Int(_) + | Builtin::Float(_) + | Builtin::Bool + | Builtin::Decimal + | Builtin::Str + // If there's any layer of indirection (behind a pointer), then it doesn't vary! + | Builtin::List(_) => { /* do nothing */ } + }, + // If there's any layer of indirection (behind a pointer), then it doesn't vary! + Layout::Struct { field_layouts, .. } => { + stack.extend(field_layouts.iter().map(|interned| interner.get(*interned))) + } + Layout::Union(tag_union) => match tag_union { + UnionLayout::NonRecursive(tags) | UnionLayout::Recursive(tags) => { + for tag in tags { + stack.extend(tag.iter().map(|interned| interner.get(*interned))); + } + } + UnionLayout::NonNullableUnwrapped(fields) => { + stack.extend(fields.iter().map(|interned| interner.get(*interned))); + } + UnionLayout::NullableWrapped { other_tags, .. } => { + for tag in other_tags { + stack.extend(tag.iter().map(|interned| interner.get(*interned))); + } + } + UnionLayout::NullableUnwrapped { other_fields, .. } => { + stack.extend(other_fields.iter().map(|interned| interner.get(*interned))); + } + }, + Layout::LambdaSet(_) => return true, + Layout::Boxed(_) => { + // If there's any layer of indirection (behind a pointer), then it doesn't vary! + } + Layout::RecursivePointer(_) => { + /* do nothing, we've already generated for this type through the Union(_) */ + } } - Union(union_layout) => union_layout.to_doc(alloc, interner, parens), - LambdaSet(lambda_set) => interner - .get(lambda_set.runtime_representation()) - .to_doc(alloc, interner, parens), - RecursivePointer(_) => alloc.text("*self"), - Boxed(inner) => alloc - .text("Boxed(") - .append(interner.get(inner).to_doc(alloc, interner, parens)) - .append(")"), } + + false } /// Used to build a `Layout::Struct` where the field name order is irrelevant. @@ -2773,6 +2828,8 @@ impl<'a> Layout<'a> { } } +pub type SeenRecPtrs<'a> = VecSet>; + impl<'a> Layout<'a> { pub fn usize(target_info: TargetInfo) -> InLayout<'a> { match target_info.ptr_width() { @@ -2821,6 +2878,18 @@ impl<'a> Layout<'a> { Dec => Layout::DEC, } } + + pub fn is_recursive_tag_union(self) -> bool { + matches!( + self, + Layout::Union( + UnionLayout::NullableUnwrapped { .. } + | UnionLayout::Recursive(_) + | UnionLayout::NullableWrapped { .. } + | UnionLayout::NonNullableUnwrapped { .. }, + ) + ) + } } impl<'a> Builtin<'a> { @@ -2900,6 +2969,7 @@ impl<'a> Builtin<'a> { self, alloc: &'b D, interner: &I, + seen_rec: &mut SeenRecPtrs<'a>, _parens: Parens, ) -> DocBuilder<'b, D, A> where @@ -2941,12 +3011,12 @@ impl<'a> Builtin<'a> { Decimal => alloc.text("Decimal"), Str => alloc.text("Str"), - List(layout) => { - let layout = interner.get(layout); - alloc - .text("List ") - .append(layout.to_doc(alloc, interner, Parens::InTypeParam)) - } + List(layout) => alloc.text("List ").append(interner.to_doc( + layout, + alloc, + seen_rec, + Parens::InTypeParam, + )), } } @@ -2973,37 +3043,6 @@ impl<'a> Builtin<'a> { } } -fn layout_from_lambda_set<'a>( - env: &mut Env<'a, '_>, - lset: subs::LambdaSet, -) -> Cacheable> { - // Lambda set is just a tag union from the layout's perspective. - let subs::LambdaSet { - solved, - recursion_var, - unspecialized, - ambient_function: _, - } = lset; - - if !unspecialized.is_empty() { - internal_error!( - "unspecialized lambda sets remain during layout generation for {:?}", - roc_types::subs::SubsFmtContent(&Content::LambdaSet(lset), env.subs) - ); - } - - match recursion_var.into_variable() { - None => { - let labels = solved.unsorted_lambdas(env.subs); - layout_from_non_recursive_union(env, &labels).map(Ok) - } - Some(rec_var) => { - let labels = solved.unsorted_lambdas(env.subs); - layout_from_recursive_union(env, rec_var, &labels) - } - } -} - fn layout_from_flat_type<'a>( env: &mut Env<'a, '_>, flat_type: FlatType, @@ -3189,8 +3228,52 @@ fn layout_from_flat_type<'a>( Cacheable(result, criteria) } - Tuple(_elems, _ext_var) => { - todo!(); + Tuple(elems, ext_var) => { + let mut criteria = CACHEABLE; + + // extract any values from the ext_var + let mut sortables = Vec::with_capacity_in(elems.len(), arena); + let it = match elems.unsorted_iterator(subs, ext_var) { + Ok(it) => it, + Err(TupleElemsError) => return Cacheable(Err(LayoutProblem::Erroneous), criteria), + }; + + for (index, elem) in it { + let elem_layout = cached!(Layout::from_var(env, elem), criteria); + sortables.push((index, elem_layout)); + } + + sortables.sort_by(|(index1, layout1), (index2, layout2)| { + cmp_fields( + &env.cache.interner, + index1, + *layout1, + index2, + *layout2, + target_info, + ) + }); + + let ordered_field_names = + Vec::from_iter_in(sortables.iter().map(|(index, _)| *index), arena); + let field_order_hash = + FieldOrderHash::from_ordered_tuple_elems(ordered_field_names.as_slice()); + + let result = if sortables.len() == 1 { + // If the tuple has only one field that isn't zero-sized, + // unwrap it. + Ok(sortables.pop().unwrap().1) + } else { + let layouts = Vec::from_iter_in(sortables.into_iter().map(|t| t.1), arena); + let struct_layout = Layout::Struct { + field_order_hash, + field_layouts: layouts.into_bump_slice(), + }; + + Ok(env.cache.put_in(struct_layout)) + }; + + Cacheable(result, criteria) } TagUnion(tags, ext_var) => { let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var); @@ -3225,6 +3308,48 @@ fn layout_from_flat_type<'a>( } } +pub type SortedTupleElem<'a> = (usize, Variable, InLayout<'a>); + +pub fn sort_tuple_elems<'a>( + env: &mut Env<'a, '_>, + var: Variable, +) -> Result>, LayoutProblem> { + let (it, _) = match gather_tuple_elems_unsorted_iter(env.subs, TupleElems::empty(), var) { + Ok(it) => it, + Err(_) => return Err(LayoutProblem::Erroneous), + }; + + sort_tuple_elems_help(env, it) +} + +fn sort_tuple_elems_help<'a>( + env: &mut Env<'a, '_>, + elems_map: impl Iterator, +) -> Result>, LayoutProblem> { + let target_info = env.target_info; + + let mut sorted_elems = Vec::with_capacity_in(elems_map.size_hint().0, env.arena); + + for (index, elem) in elems_map { + let Cacheable(layout, _) = Layout::from_var(env, elem); + let layout = layout?; + sorted_elems.push((index, elem, layout)); + } + + sorted_elems.sort_by(|(index1, _, res_layout1), (index2, _, res_layout2)| { + cmp_fields( + &env.cache.interner, + index1, + *res_layout1, + index2, + *res_layout2, + target_info, + ) + }); + + Ok(sorted_elems) +} + pub type SortedField<'a> = (Lowercase, Variable, Result, InLayout<'a>>); pub fn sort_record_fields<'a>( @@ -3500,18 +3625,6 @@ fn get_recursion_var(subs: &Subs, var: Variable) -> Option { } } -fn is_recursive_tag_union(layout: &Layout) -> bool { - matches!( - layout, - Layout::Union( - UnionLayout::NullableUnwrapped { .. } - | UnionLayout::Recursive(_) - | UnionLayout::NullableWrapped { .. } - | UnionLayout::NonNullableUnwrapped { .. }, - ) - ) -} - fn union_sorted_non_recursive_tags_help<'a, L>( env: &mut Env<'a, '_>, tags_list: &[(&'_ L, &[Variable])], @@ -3804,7 +3917,7 @@ where == env .subs .get_root_key_without_compacting(opt_rec_var.unwrap()) - && is_recursive_tag_union(&layout); + && layout.is_recursive_tag_union(); let arg_layout = if self_recursion { Layout::NAKED_RECURSIVE_PTR @@ -4075,6 +4188,7 @@ where // The naked pointer will get fixed-up to loopback to the union below when we // intern the union. tag_layout.push(Layout::NAKED_RECURSIVE_PTR); + criteria.and(NAKED_RECURSION_PTR); continue; } @@ -4115,12 +4229,17 @@ where } else { UnionLayout::Recursive(tag_layouts.into_bump_slice()) }; - criteria.pass_through_recursive_union(rec_var); - let union_layout = env - .cache - .interner - .insert_recursive(env.arena, Layout::Union(union_layout)); + let union_layout = if criteria.has_naked_recursion_pointer { + env.cache + .interner + .insert_recursive(env.arena, Layout::Union(union_layout)) + } else { + // There are no naked recursion pointers, so we can insert the layout as-is. + env.cache.interner.insert(Layout::Union(union_layout)) + }; + + criteria.pass_through_recursive_union(rec_var); Cacheable(Ok(union_layout), criteria) } diff --git a/crates/compiler/mono/src/layout/intern.rs b/crates/compiler/mono/src/layout/intern.rs index 186049be43..158a47dd74 100644 --- a/crates/compiler/mono/src/layout/intern.rs +++ b/crates/compiler/mono/src/layout/intern.rs @@ -12,7 +12,7 @@ use roc_collections::{default_hasher, BumpMap}; use roc_module::symbol::Symbol; use roc_target::TargetInfo; -use super::{Builtin, FieldOrderHash, LambdaSet, Layout, UnionLayout}; +use super::{Builtin, FieldOrderHash, LambdaSet, Layout, SeenRecPtrs, UnionLayout}; macro_rules! cache_interned_layouts { ($($i:literal, $name:ident, $vis:vis, $layout:expr)*; $total_constants:literal) => { @@ -121,6 +121,13 @@ impl<'a> Layout<'a> { } } +/// Whether a recursive lambda set being inserted into an interner needs fixing-up of naked +/// recursion pointers in the capture set. +/// Applicable only if +/// - the lambda set is indeed recursive, and +/// - its capture set contain naked pointer references +pub struct NeedsRecursionPointerFixup(pub bool); + pub trait LayoutInterner<'a>: Sized { /// Interns a value, returning its interned representation. /// If the value has been interned before, the old interned representation will be re-used. @@ -135,9 +142,11 @@ pub trait LayoutInterner<'a>: Sized { /// lambda set onto itself. fn insert_lambda_set( &mut self, + arena: &'a Bump, args: &'a &'a [InLayout<'a>], ret: InLayout<'a>, set: &'a &'a [(Symbol, &'a [InLayout<'a>])], + needs_recursive_fixup: NeedsRecursionPointerFixup, representation: InLayout<'a>, ) -> LambdaSet<'a>; @@ -198,14 +207,63 @@ pub trait LayoutInterner<'a>: Sized { Layout::runtime_representation_in(layout, self) } + fn chase_recursive(&self, mut layout: InLayout<'a>) -> Layout<'a> { + loop { + match self.get(layout) { + Layout::RecursivePointer(l) => layout = l, + other => return other, + } + } + } + + fn chase_recursive_in(&self, mut layout: InLayout<'a>) -> InLayout<'a> { + loop { + match self.get(layout) { + Layout::RecursivePointer(l) => layout = l, + _ => return layout, + } + } + } + fn safe_to_memcpy(&self, layout: InLayout<'a>) -> bool { self.get(layout).safe_to_memcpy(self) } + /// Checks if two layouts are equivalent up to isomorphism. + /// + /// This is only to be used when layouts need to be compared across statements and depths, + /// for example + /// - when looking up a layout index in a lambda set + /// - in the [checker][crate::debug::check_procs], where `x = UnionAtIndex(f, 0)` may have + /// that the recorded layout of `x` is at a different depth than that determined when we + /// index the recorded layout of `f` at 0. Hence the two layouts may have different + /// interned representations, even if they are in fact isomorphic. + fn equiv(&self, l1: InLayout<'a>, l2: InLayout<'a>) -> bool { + std::thread_local! { + static SCRATCHPAD: RefCell, InLayout<'static>)>>> = RefCell::new(Some(Vec::with_capacity(64))); + } + + SCRATCHPAD.with(|f| { + // SAFETY: the promotion to lifetime 'a only lasts during equivalence-checking; the + // scratchpad stack is cleared after every use. + let mut stack: Vec<(InLayout<'a>, InLayout<'a>)> = + unsafe { std::mem::transmute(f.take().unwrap()) }; + + let answer = equiv::equivalent(&mut stack, self, l1, l2); + stack.clear(); + + let stack: Vec<(InLayout<'static>, InLayout<'static>)> = + unsafe { std::mem::transmute(stack) }; + f.replace(Some(stack)); + answer + }) + } + fn to_doc<'b, D, A>( &self, layout: InLayout<'a>, alloc: &'b D, + seen_rec: &mut SeenRecPtrs<'a>, parens: crate::ir::Parens, ) -> ven_pretty::DocBuilder<'b, D, A> where @@ -213,14 +271,104 @@ pub trait LayoutInterner<'a>: Sized { D::Doc: Clone, A: Clone, { - self.get(layout).to_doc(alloc, self, parens) + use Layout::*; + + match self.get(layout) { + Builtin(builtin) => builtin.to_doc(alloc, self, seen_rec, parens), + Struct { field_layouts, .. } => { + let fields_doc = field_layouts + .iter() + .map(|x| self.to_doc(*x, alloc, seen_rec, parens)); + + alloc + .text("{") + .append(alloc.intersperse(fields_doc, ", ")) + .append(alloc.text("}")) + } + Union(union_layout) => { + let is_recursive = !matches!(union_layout, UnionLayout::NonRecursive(..)); + if is_recursive { + seen_rec.insert(layout); + } + let doc = union_layout.to_doc(alloc, self, seen_rec, parens); + if is_recursive { + seen_rec.remove(&layout); + } + doc + } + LambdaSet(lambda_set) => { + self.to_doc(lambda_set.runtime_representation(), alloc, seen_rec, parens) + } + RecursivePointer(rec_layout) => { + if seen_rec.contains(&rec_layout) { + alloc.text("*self") + } else { + self.to_doc(rec_layout, alloc, seen_rec, parens) + } + } + Boxed(inner) => alloc + .text("Boxed(") + .append(self.to_doc(inner, alloc, seen_rec, parens)) + .append(")"), + } } + fn to_doc_top<'b, D, A>( + &self, + layout: InLayout<'a>, + alloc: &'b D, + ) -> ven_pretty::DocBuilder<'b, D, A> + where + D: ven_pretty::DocAllocator<'b, A>, + D::Doc: Clone, + A: Clone, + { + self.to_doc( + layout, + alloc, + &mut Default::default(), + crate::ir::Parens::NotNeeded, + ) + } + + /// Pretty-print a representation of the layout. fn dbg(&self, layout: InLayout<'a>) -> String { let alloc: ven_pretty::Arena<()> = ven_pretty::Arena::new(); - let doc = self.to_doc(layout, &alloc, crate::ir::Parens::NotNeeded); + let doc = self.to_doc_top(layout, &alloc); doc.1.pretty(80).to_string() } + + /// Yields a debug representation of a layout, traversing its entire nested structure and + /// debug-printing all intermediate interned layouts. + /// + /// By default, a [Layout] is composed inductively by [interned layout][InLayout]s. + /// This makes debugging a layout more than one level challenging, as you may run into further + /// opaque interned layouts that need unwrapping. + /// + /// [`dbg_deep`][LayoutInterner::dbg_deep] works around this by returning a value whose debug + /// representation chases through all nested interned layouts as you would otherwise have to do + /// manually. + /// + /// ## Example + /// + /// ```ignore(illustrative) + /// fn is_rec_ptr<'a>(interner: &impl LayoutInterner<'a>, layout: InLayout<'a>) -> bool { + /// if matches!(interner.get(layout), Layout::RecursivePointer(..)) { + /// return true; + /// } + /// + /// let deep_dbg = interner.dbg_deep(layout); + /// roc_tracing::info!("not a recursive pointer, actually a {deep_dbg:?}"); + /// return false; + /// } + /// ``` + fn dbg_deep<'r>(&'r self, layout: InLayout<'a>) -> dbg::Dbg<'a, 'r, Self> { + dbg::Dbg(self, layout) + } + + fn dbg_deep_iter<'r>(&'r self, layouts: &'a [InLayout<'a>]) -> dbg::DbgFields<'a, 'r, Self> { + dbg::DbgFields(self, layouts) + } } /// An interned layout. @@ -260,6 +408,10 @@ impl<'a> InLayout<'a> { pub(crate) const unsafe fn from_index(index: usize) -> Self { Self(index, PhantomData) } + + pub fn index(&self) -> usize { + self.0 + } } /// A concurrent interner, suitable for usage between threads. @@ -407,40 +559,64 @@ impl<'a> GlobalLayoutInterner<'a> { fn get_or_insert_hashed_normalized_lambda_set( &self, + arena: &'a Bump, normalized: LambdaSet<'a>, + needs_recursive_fixup: NeedsRecursionPointerFixup, normalized_hash: u64, ) -> WrittenGlobalLambdaSet<'a> { let mut normalized_lambda_set_map = self.0.normalized_lambda_set_map.lock(); - let (_, full_lambda_set) = normalized_lambda_set_map - .raw_entry_mut() + if let Some((_, &full_lambda_set)) = normalized_lambda_set_map + .raw_entry() .from_key_hashed_nocheck(normalized_hash, &normalized) - .or_insert_with(|| { - // We don't already have an entry for the lambda set, which means it must be new to - // the world. Reserve a slot, insert the lambda set, and that should fill the slot - // in. - let mut map = self.0.map.lock(); - let mut vec = self.0.vec.write(); + { + let full_layout = self.0.vec.read()[full_lambda_set.full_layout.0]; + return WrittenGlobalLambdaSet { + full_lambda_set, + full_layout, + }; + } - let slot = unsafe { InLayout::from_index(vec.len()) }; + // We don't already have an entry for the lambda set, which means it must be new to + // the world. Reserve a slot, insert the lambda set, and that should fill the slot + // in. + let mut map = self.0.map.lock(); + let mut vec = self.0.vec.write(); - let lambda_set = LambdaSet { - full_layout: slot, - ..normalized - }; - let lambda_set_layout = Layout::LambdaSet(lambda_set); + let slot = unsafe { InLayout::from_index(vec.len()) }; + vec.push(Layout::VOID_NAKED); - vec.push(lambda_set_layout); + let set = if needs_recursive_fixup.0 { + let mut interner = LockedGlobalInterner { + map: &mut map, + normalized_lambda_set_map: &mut normalized_lambda_set_map, + vec: &mut vec, + target_info: self.0.target_info, + }; + reify::reify_lambda_set_captures(arena, &mut interner, slot, normalized.set) + } else { + normalized.set + }; - // TODO: Is it helpful to persist the hash and give it back to the thread-local - // interner? - let _old = map.insert(lambda_set_layout, slot); - debug_assert!(_old.is_none()); + let full_lambda_set = LambdaSet { + full_layout: slot, + set, + ..normalized + }; + let lambda_set_layout = Layout::LambdaSet(full_lambda_set); - (normalized, lambda_set) - }); - let full_layout = self.0.vec.read()[full_lambda_set.full_layout.0]; + vec[slot.0] = lambda_set_layout; + + // TODO: Is it helpful to persist the hash and give it back to the thread-local + // interner? + let _old = map.insert(lambda_set_layout, slot); + debug_assert!(_old.is_none()); + + let _old_normalized = normalized_lambda_set_map.insert(normalized, full_lambda_set); + debug_assert!(_old_normalized.is_none()); + + let full_layout = vec[full_lambda_set.full_layout.0]; WrittenGlobalLambdaSet { - full_lambda_set: *full_lambda_set, + full_lambda_set, full_layout, } } @@ -539,9 +715,11 @@ impl<'a> LayoutInterner<'a> for TLLayoutInterner<'a> { fn insert_lambda_set( &mut self, + arena: &'a Bump, args: &'a &'a [InLayout<'a>], ret: InLayout<'a>, set: &'a &'a [(Symbol, &'a [InLayout<'a>])], + needs_recursive_fixup: NeedsRecursionPointerFixup, representation: InLayout<'a>, ) -> LambdaSet<'a> { // The tricky bit of inserting a lambda set is we need to fill in the `full_layout` only @@ -567,7 +745,12 @@ impl<'a> LayoutInterner<'a> for TLLayoutInterner<'a> { let WrittenGlobalLambdaSet { full_lambda_set, full_layout, - } = global.get_or_insert_hashed_normalized_lambda_set(normalized, normalized_hash); + } = global.get_or_insert_hashed_normalized_lambda_set( + arena, + normalized, + needs_recursive_fixup, + normalized_hash, + ); // The Layout(lambda_set) isn't present in our thread; make sure it is for future // reference. @@ -689,9 +872,11 @@ macro_rules! st_impl { fn insert_lambda_set( &mut self, + arena: &'a Bump, args: &'a &'a [InLayout<'a>], ret: InLayout<'a>, set: &'a &'a [(Symbol, &'a [InLayout<'a>])], + needs_recursive_fixup: NeedsRecursionPointerFixup, representation: InLayout<'a>, ) -> LambdaSet<'a> { // IDEA: @@ -708,6 +893,14 @@ macro_rules! st_impl { // This lambda set must be new to the interner, reserve a slot and fill it in. let slot = unsafe { InLayout::from_index(self.vec.len()) }; + self.vec.push(Layout::VOID_NAKED); + + let set = if needs_recursive_fixup.0 { + reify::reify_lambda_set_captures(arena, self, slot, set) + } else { + set + }; + let lambda_set = LambdaSet { args, ret, @@ -715,11 +908,14 @@ macro_rules! st_impl { representation, full_layout: slot, }; - let filled_slot = self.insert(Layout::LambdaSet(lambda_set)); - assert_eq!(slot, filled_slot); + self.vec[slot.0] = Layout::LambdaSet(lambda_set); - self.normalized_lambda_set_map + let _old = self.map.insert(Layout::LambdaSet(lambda_set), slot); + debug_assert!(_old.is_none()); + + let _old = self.normalized_lambda_set_map .insert(normalized_lambda_set, lambda_set); + debug_assert!(_old.is_none()); lambda_set } @@ -768,10 +964,11 @@ st_impl!('r LockedGlobalInterner); mod reify { use bumpalo::{collections::Vec, Bump}; + use roc_module::symbol::Symbol; use crate::layout::{Builtin, LambdaSet, Layout, UnionLayout}; - use super::{InLayout, LayoutInterner}; + use super::{InLayout, LayoutInterner, NeedsRecursionPointerFixup}; // TODO: if recursion becomes a problem we could make this iterative pub fn reify_recursive_layout<'a>( @@ -912,18 +1109,330 @@ mod reify { }; let representation = reify_layout(arena, interner, slot, representation); - interner.insert_lambda_set(arena.alloc(args), ret, arena.alloc(set), representation) + interner.insert_lambda_set( + arena, + arena.alloc(args), + ret, + arena.alloc(set), + // All nested recursive pointers should been fixed up, since we just did that above. + NeedsRecursionPointerFixup(false), + representation, + ) + } + + pub fn reify_lambda_set_captures<'a>( + arena: &'a Bump, + interner: &mut impl LayoutInterner<'a>, + slot: InLayout<'a>, + set: &[(Symbol, &'a [InLayout<'a>])], + ) -> &'a &'a [(Symbol, &'a [InLayout<'a>])] { + let mut reified_set = Vec::with_capacity_in(set.len(), arena); + for (f, captures) in set.iter() { + let reified_captures = reify_layout_slice(arena, interner, slot, captures); + reified_set.push((*f, reified_captures)); + } + arena.alloc(reified_set.into_bump_slice()) + } +} + +mod equiv { + use crate::layout::{self, Layout, UnionLayout}; + + use super::{InLayout, LayoutInterner}; + + pub fn equivalent<'a>( + stack: &mut Vec<(InLayout<'a>, InLayout<'a>)>, + interner: &impl LayoutInterner<'a>, + l1: InLayout<'a>, + l2: InLayout<'a>, + ) -> bool { + stack.push((l1, l2)); + + macro_rules! equiv_fields { + ($fields1:expr, $fields2:expr) => {{ + if $fields1.len() != $fields2.len() { + return false; + } + stack.extend($fields1.iter().copied().zip($fields2.iter().copied())); + }}; + } + + macro_rules! equiv_unions { + ($tags1:expr, $tags2:expr) => {{ + if $tags1.len() != $tags2.len() { + return false; + } + for (payloads1, payloads2) in $tags1.iter().zip($tags2) { + equiv_fields!(payloads1, payloads2) + } + }}; + } + + while let Some((l1, l2)) = stack.pop() { + if l1 == l2 { + continue; + } + use Layout::*; + match (interner.get(l1), interner.get(l2)) { + (RecursivePointer(rec), _) => stack.push((rec, l2)), + (_, RecursivePointer(rec)) => stack.push((l1, rec)), + (Builtin(b1), Builtin(b2)) => { + use crate::layout::Builtin::*; + match (b1, b2) { + (List(e1), List(e2)) => stack.push((e1, e2)), + (b1, b2) => { + if b1 != b2 { + return false; + } + } + } + } + ( + Struct { + field_order_hash: foh1, + field_layouts: fl1, + }, + Struct { + field_order_hash: foh2, + field_layouts: fl2, + }, + ) => { + if foh1 != foh2 { + return false; + } + equiv_fields!(fl1, fl2) + } + (Boxed(b1), Boxed(b2)) => stack.push((b1, b2)), + (Union(u1), Union(u2)) => { + use UnionLayout::*; + match (u1, u2) { + (NonRecursive(tags1), NonRecursive(tags2)) => equiv_unions!(tags1, tags2), + (Recursive(tags1), Recursive(tags2)) => equiv_unions!(tags1, tags2), + (NonNullableUnwrapped(fields1), NonNullableUnwrapped(fields2)) => { + equiv_fields!(fields1, fields2) + } + ( + NullableWrapped { + nullable_id: null_id1, + other_tags: tags1, + }, + NullableWrapped { + nullable_id: null_id2, + other_tags: tags2, + }, + ) => { + if null_id1 != null_id2 { + return false; + } + equiv_unions!(tags1, tags2) + } + ( + NullableUnwrapped { + nullable_id: null_id1, + other_fields: fields1, + }, + NullableUnwrapped { + nullable_id: null_id2, + other_fields: fields2, + }, + ) => { + if null_id1 != null_id2 { + return false; + } + equiv_fields!(fields1, fields2) + } + _ => return false, + } + } + ( + LambdaSet(layout::LambdaSet { + args: args1, + ret: ret1, + set: set1, + representation: repr1, + full_layout: _, + }), + LambdaSet(layout::LambdaSet { + args: args2, + ret: ret2, + set: set2, + representation: repr2, + full_layout: _, + }), + ) => { + for ((fn1, captures1), (fn2, captures2)) in (**set1).iter().zip(*set2) { + if fn1 != fn2 { + return false; + } + equiv_fields!(captures1, captures2); + } + equiv_fields!(args1, args2); + stack.push((ret1, ret2)); + stack.push((repr1, repr2)); + } + _ => return false, + } + } + + true + } +} + +pub mod dbg { + use roc_module::symbol::Symbol; + + use crate::layout::{Builtin, LambdaSet, Layout, UnionLayout}; + + use super::{InLayout, LayoutInterner}; + + pub struct Dbg<'a, 'r, I: LayoutInterner<'a>>(pub &'r I, pub InLayout<'a>); + + impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for Dbg<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0.get(self.1) { + Layout::Builtin(b) => f + .debug_tuple("Builtin") + .field(&DbgBuiltin(self.0, b)) + .finish(), + Layout::Struct { + field_order_hash, + field_layouts, + } => f + .debug_struct("Struct") + .field("hash", &field_order_hash) + .field("fields", &DbgFields(self.0, field_layouts)) + .finish(), + Layout::Boxed(b) => f.debug_tuple("Boxed").field(&Dbg(self.0, b)).finish(), + Layout::Union(un) => f.debug_tuple("Union").field(&DbgUnion(self.0, un)).finish(), + Layout::LambdaSet(ls) => f + .debug_tuple("LambdaSet") + .field(&DbgLambdaSet(self.0, ls)) + .finish(), + Layout::RecursivePointer(rp) => { + f.debug_tuple("RecursivePointer").field(&rp.0).finish() + } + } + } + } + + pub struct DbgFields<'a, 'r, I: LayoutInterner<'a>>(pub &'r I, pub &'a [InLayout<'a>]); + + impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgFields<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries(self.1.iter().map(|l| Dbg(self.0, *l))) + .finish() + } + } + + struct DbgTags<'a, 'r, I: LayoutInterner<'a>>(&'r I, &'a [&'a [InLayout<'a>]]); + + impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgTags<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries(self.1.iter().map(|l| DbgFields(self.0, l))) + .finish() + } + } + + struct DbgBuiltin<'a, 'r, I: LayoutInterner<'a>>(&'r I, Builtin<'a>); + + impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgBuiltin<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.1 { + Builtin::Int(w) => f.debug_tuple("Int").field(&w).finish(), + Builtin::Float(w) => f.debug_tuple("Float").field(&w).finish(), + Builtin::Bool => f.debug_tuple("Bool").finish(), + Builtin::Decimal => f.debug_tuple("Decimal").finish(), + Builtin::Str => f.debug_tuple("Str").finish(), + Builtin::List(e) => f.debug_tuple("List").field(&Dbg(self.0, e)).finish(), + } + } + } + + struct DbgUnion<'a, 'r, I: LayoutInterner<'a>>(&'r I, UnionLayout<'a>); + + impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgUnion<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.1 { + UnionLayout::NonRecursive(payloads) => f + .debug_tuple("NonRecursive") + .field(&DbgTags(self.0, payloads)) + .finish(), + UnionLayout::Recursive(payloads) => f + .debug_tuple("Recursive") + .field(&DbgTags(self.0, payloads)) + .finish(), + UnionLayout::NonNullableUnwrapped(fields) => f + .debug_tuple("NonNullableUnwrapped") + .field(&DbgFields(self.0, fields)) + .finish(), + UnionLayout::NullableWrapped { + nullable_id, + other_tags, + } => f + .debug_struct("NullableWrapped") + .field("nullable_id", &nullable_id) + .field("other_tags", &DbgTags(self.0, other_tags)) + .finish(), + UnionLayout::NullableUnwrapped { + nullable_id, + other_fields, + } => f + .debug_struct("NullableUnwrapped") + .field("nullable_id", &nullable_id) + .field("other_tags", &DbgFields(self.0, other_fields)) + .finish(), + } + } + } + + struct DbgLambdaSet<'a, 'r, I: LayoutInterner<'a>>(&'r I, LambdaSet<'a>); + + impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgLambdaSet<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let LambdaSet { + args, + ret, + set, + representation, + full_layout, + } = self.1; + + f.debug_struct("LambdaSet") + .field("args", &DbgFields(self.0, args)) + .field("ret", &Dbg(self.0, ret)) + .field("set", &DbgCapturesSet(self.0, set)) + .field("representation", &Dbg(self.0, representation)) + .field("full_layout", &full_layout) + .finish() + } + } + + struct DbgCapturesSet<'a, 'r, I: LayoutInterner<'a>>(&'r I, &'a [(Symbol, &'a [InLayout<'a>])]); + + impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgCapturesSet<'a, 'r, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries( + self.1 + .iter() + .map(|(sym, captures)| (sym, DbgFields(self.0, captures))), + ) + .finish() + } } } #[cfg(test)] mod insert_lambda_set { + use bumpalo::Bump; use roc_module::symbol::Symbol; use roc_target::TargetInfo; use crate::layout::{LambdaSet, Layout}; - use super::{GlobalLayoutInterner, InLayout, LayoutInterner}; + use super::{GlobalLayoutInterner, InLayout, LayoutInterner, NeedsRecursionPointerFixup}; const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64(); const TEST_SET: &&[(Symbol, &[InLayout])] = @@ -931,50 +1440,57 @@ mod insert_lambda_set { const TEST_ARGS: &&[InLayout] = &(&[Layout::UNIT] as &[_]); const TEST_RET: InLayout = Layout::UNIT; + const FIXUP: NeedsRecursionPointerFixup = NeedsRecursionPointerFixup(true); + #[test] fn two_threads_write() { - let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO); - let set = TEST_SET; - let repr = Layout::UNIT; - for _ in 0..100 { - let mut handles = Vec::with_capacity(10); - for _ in 0..10 { - let mut interner = global.fork(); - handles.push(std::thread::spawn(move || { - interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr) - })) - } - let ins: Vec = handles.into_iter().map(|t| t.join().unwrap()).collect(); - let interned = ins[0]; - assert!(ins.iter().all(|in2| interned == *in2)); + let mut arenas: Vec<_> = std::iter::repeat_with(Bump::new).take(10).collect(); + let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO); + let set = TEST_SET; + let repr = Layout::UNIT; + std::thread::scope(|s| { + let mut handles = Vec::with_capacity(10); + for arena in arenas.iter_mut() { + let mut interner = global.fork(); + handles.push(s.spawn(move || { + interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, FIXUP, repr) + })) + } + let ins: Vec = handles.into_iter().map(|t| t.join().unwrap()).collect(); + let interned = ins[0]; + assert!(ins.iter().all(|in2| interned == *in2)); + }); } } #[test] fn insert_then_reintern() { + let arena = &Bump::new(); let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO); let mut interner = global.fork(); - let lambda_set = interner.insert_lambda_set(TEST_ARGS, TEST_RET, TEST_SET, Layout::UNIT); + let lambda_set = + interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, TEST_SET, FIXUP, Layout::UNIT); let lambda_set_layout_in = interner.insert(Layout::LambdaSet(lambda_set)); assert_eq!(lambda_set.full_layout, lambda_set_layout_in); } #[test] fn write_global_then_single_threaded() { + let arena = &Bump::new(); let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO); let set = TEST_SET; let repr = Layout::UNIT; let in1 = { let mut interner = global.fork(); - interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr) + interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, FIXUP, repr) }; let in2 = { let mut st_interner = global.unwrap().unwrap(); - st_interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr) + st_interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, FIXUP, repr) }; assert_eq!(in1, in2); @@ -982,18 +1498,19 @@ mod insert_lambda_set { #[test] fn write_single_threaded_then_global() { + let arena = &Bump::new(); let global = GlobalLayoutInterner::with_capacity(2, TARGET_INFO); let mut st_interner = global.unwrap().unwrap(); let set = TEST_SET; let repr = Layout::UNIT; - let in1 = st_interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr); + let in1 = st_interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, FIXUP, repr); let global = st_interner.into_global(); let mut interner = global.fork(); - let in2 = interner.insert_lambda_set(TEST_ARGS, TEST_RET, set, repr); + let in2 = interner.insert_lambda_set(arena, TEST_ARGS, TEST_RET, set, FIXUP, repr); assert_eq!(in1, in2); } @@ -1022,7 +1539,7 @@ mod insert_recursive_layout { } fn get_rec_ptr_index<'a>(interner: &impl LayoutInterner<'a>, layout: InLayout<'a>) -> usize { - match interner.get(layout) { + match interner.chase_recursive(layout) { Layout::Union(UnionLayout::Recursive(&[&[l1], &[l2]])) => { match (interner.get(l1), interner.get(l2)) { ( @@ -1146,7 +1663,7 @@ mod insert_recursive_layout { make_layout(arena, &mut interner) }; - let in1 = { + let in1: InLayout = { let mut interner = global.fork(); interner.insert_recursive(arena, layout) }; diff --git a/crates/compiler/mono/src/lib.rs b/crates/compiler/mono/src/lib.rs index 704d659d2f..d9fe5e5641 100644 --- a/crates/compiler/mono/src/lib.rs +++ b/crates/compiler/mono/src/lib.rs @@ -6,6 +6,8 @@ #![warn(clippy::dbg_macro)] // See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] +// Not a useful lint for us +#![allow(clippy::too_many_arguments)] pub mod borrow; pub mod code_gen_help; @@ -19,9 +21,4 @@ pub mod perceus; pub mod reset_reuse; pub mod tail_recursion; -// Temporary, while we can build up test cases and optimize the exhaustiveness checking. -// For now, following this warning's advice will lead to nasty type inference errors. -//#[allow(clippy::ptr_arg)] -pub mod decision_tree; - pub mod debug; diff --git a/crates/compiler/mono/src/low_level.rs b/crates/compiler/mono/src/low_level.rs index ddad554c81..d346838e88 100644 --- a/crates/compiler/mono/src/low_level.rs +++ b/crates/compiler/mono/src/low_level.rs @@ -120,6 +120,8 @@ enum FirstOrder { NumShiftRightBy, NumBytesToU16, NumBytesToU32, + NumBytesToU64, + NumBytesToU128, NumShiftRightZfBy, NumIntCast, NumFloatCast, diff --git a/crates/compiler/mono/src/reset_reuse.rs b/crates/compiler/mono/src/reset_reuse.rs index 125e84bf6a..0076e61d74 100644 --- a/crates/compiler/mono/src/reset_reuse.rs +++ b/crates/compiler/mono/src/reset_reuse.rs @@ -615,7 +615,7 @@ fn function_r_branch_body<'a, 'i>( scrutinee, layout, tag_id, - } => match env.interner.get(*layout) { + } => match env.interner.chase_recursive(*layout) { Layout::Union(UnionLayout::NonRecursive(_)) => temp, Layout::Union(union_layout) if !union_layout.tag_is_null(*tag_id) => { let ctor_info = CtorInfo { diff --git a/crates/compiler/parse/Cargo.toml b/crates/compiler/parse/Cargo.toml index 82b666b846..dbb2ca21c1 100644 --- a/crates/compiler/parse/Cargo.toml +++ b/crates/compiler/parse/Cargo.toml @@ -1,32 +1,31 @@ [package] name = "roc_parse" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Implements the Roc parser, which transforms a textual representation of a Roc program to an AST." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [features] "parse_debug_trace" = [] [dependencies] roc_collections = { path = "../collections" } -roc_region = { path = "../region" } roc_module = { path = "../module" } +roc_region = { path = "../region" } bumpalo.workspace = true encode_unicode.workspace = true [dev-dependencies] -roc_test_utils = { path = "../../test_utils" } -proptest = "1.0.0" - criterion.workspace = true -pretty_assertions.workspace = true indoc.workspace = true +pretty_assertions.workspace = true +proptest.workspace = true quickcheck.workspace = true quickcheck_macros.workspace = true [[bench]] -name = "bench_parse" harness = false +name = "bench_parse" diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index 35907841ea..677902474c 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PackageHeader, PlatformHeader}; +use crate::ident::Accessor; use crate::parser::ESingleQuote; use bumpalo::collections::{String, Vec}; use bumpalo::Bump; @@ -243,13 +244,12 @@ pub enum Expr<'a> { /// Look up exactly one field on a record, e.g. `x.foo`. RecordAccess(&'a Expr<'a>, &'a str), - /// e.g. `.foo` - RecordAccessorFunction(&'a str), + + /// e.g. `.foo` or `.0` + AccessorFunction(Accessor<'a>), /// Look up exactly one field on a tuple, e.g. `(x, y).1`. TupleAccess(&'a Expr<'a>, &'a str), - /// e.g. `.1` - TupleAccessorFunction(&'a str), // Collection Literals List(Collection<'a, &'a Loc>>), @@ -612,7 +612,7 @@ pub enum TypeAnnotation<'a> { }, Tuple { - fields: Collection<'a, Loc>>, + elems: Collection<'a, Loc>>, /// The row type variable in an open tuple, e.g. the `r` in `( Str, Str )r`. /// This is None if it's a closed tuple annotation like `( Str, Str )`. ext: Option<&'a Loc>>, @@ -1421,3 +1421,359 @@ impl<'a> ExtractSpaces<'a> for HasImpls<'a> { } } } + +pub trait Malformed { + /// Returns whether this node is malformed, or contains a malformed node (recursively). + fn is_malformed(&self) -> bool; +} + +impl<'a> Malformed for Module<'a> { + fn is_malformed(&self) -> bool { + self.header.is_malformed() + } +} + +impl<'a> Malformed for Header<'a> { + fn is_malformed(&self) -> bool { + match self { + Header::Interface(header) => header.is_malformed(), + Header::App(header) => header.is_malformed(), + Header::Package(header) => header.is_malformed(), + Header::Platform(header) => header.is_malformed(), + Header::Hosted(header) => header.is_malformed(), + } + } +} + +impl<'a, T: Malformed> Malformed for Spaces<'a, T> { + fn is_malformed(&self) -> bool { + self.item.is_malformed() + } +} + +impl<'a> Malformed for Expr<'a> { + fn is_malformed(&self) -> bool { + use Expr::*; + + match self { + Float(_) | + Num(_) | + NonBase10Int { .. } | + AccessorFunction(_) | + Var { .. } | + Underscore(_) | + Tag(_) | + OpaqueRef(_) | + SingleQuote(_) | // This is just a &str - not a bunch of segments + Crash => false, + + Str(inner) => inner.is_malformed(), + + RecordAccess(inner, _) | + TupleAccess(inner, _) => inner.is_malformed(), + + List(items) => items.is_malformed(), + + RecordUpdate { update, fields } => update.is_malformed() || fields.is_malformed(), + Record(items) => items.is_malformed(), + Tuple(items) => items.is_malformed(), + + Closure(args, body) => args.iter().any(|arg| arg.is_malformed()) || body.is_malformed(), + Defs(defs, body) => defs.is_malformed() || body.is_malformed(), + Backpassing(args, call, body) => args.iter().any(|arg| arg.is_malformed()) || call.is_malformed() || body.is_malformed(), + Expect(condition, continuation) | + Dbg(condition, continuation) => condition.is_malformed() || continuation.is_malformed(), + Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()), + BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(), + UnaryOp(expr, _) => expr.is_malformed(), + If(chain, els) => chain.iter().any(|(cond, body)| cond.is_malformed() || body.is_malformed()) || els.is_malformed(), + When(cond, branches) => cond.is_malformed() || branches.iter().any(|branch| branch.is_malformed()), + + SpaceBefore(expr, _) | + SpaceAfter(expr, _) | + ParensAround(expr) => expr.is_malformed(), + + MalformedIdent(_, _) | + MalformedClosure | + PrecedenceConflict(_) => true, + } + } +} + +impl<'a> Malformed for WhenBranch<'a> { + fn is_malformed(&self) -> bool { + self.patterns.iter().any(|pat| pat.is_malformed()) + || self.value.is_malformed() + || self.guard.map(|g| g.is_malformed()).unwrap_or_default() + } +} + +impl<'a, T: Malformed> Malformed for Collection<'a, T> { + fn is_malformed(&self) -> bool { + self.iter().any(|item| item.is_malformed()) + } +} + +impl<'a> Malformed for StrLiteral<'a> { + fn is_malformed(&self) -> bool { + match self { + StrLiteral::PlainLine(_) => false, + StrLiteral::Line(segs) => segs.iter().any(|seg| seg.is_malformed()), + StrLiteral::Block(lines) => lines + .iter() + .any(|segs| segs.iter().any(|seg| seg.is_malformed())), + } + } +} + +impl<'a> Malformed for StrSegment<'a> { + fn is_malformed(&self) -> bool { + match self { + StrSegment::Plaintext(_) | StrSegment::Unicode(_) | StrSegment::EscapedChar(_) => false, + StrSegment::Interpolated(expr) => expr.is_malformed(), + } + } +} + +impl<'a, T: Malformed> Malformed for &'a T { + fn is_malformed(&self) -> bool { + (*self).is_malformed() + } +} + +impl Malformed for Loc { + fn is_malformed(&self) -> bool { + self.value.is_malformed() + } +} + +impl Malformed for Option { + fn is_malformed(&self) -> bool { + self.as_ref() + .map(|value| value.is_malformed()) + .unwrap_or_default() + } +} + +impl<'a, T: Malformed> Malformed for AssignedField<'a, T> { + fn is_malformed(&self) -> bool { + match self { + AssignedField::RequiredValue(_, _, val) | AssignedField::OptionalValue(_, _, val) => { + val.is_malformed() + } + AssignedField::LabelOnly(_) => false, + AssignedField::SpaceBefore(field, _) | AssignedField::SpaceAfter(field, _) => { + field.is_malformed() + } + AssignedField::Malformed(_) => true, + } + } +} + +impl<'a> Malformed for Pattern<'a> { + fn is_malformed(&self) -> bool { + use Pattern::*; + + match self { + Identifier(_) | + Tag(_) | + OpaqueRef(_) => false, + Apply(func, args) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()), + RecordDestructure(items) => items.iter().any(|item| item.is_malformed()), + RequiredField(_, pat) => pat.is_malformed(), + OptionalField(_, expr) => expr.is_malformed(), + + NumLiteral(_) | + NonBase10Literal { .. } | + Underscore(_) | + SingleQuote(_) | // This is just a &str - not a bunch of segments + FloatLiteral(_) => false, + + StrLiteral(lit) => lit.is_malformed(), + Tuple(items) => items.iter().any(|item| item.is_malformed()), + List(items) => items.iter().any(|item| item.is_malformed()), + ListRest(_) =>false, + As(pat, _) => pat.is_malformed(), + SpaceBefore(pat, _) | + SpaceAfter(pat, _) => pat.is_malformed(), + + Malformed(_) | + MalformedIdent(_, _) | + QualifiedIdentifier { .. } => true, + } + } +} +impl<'a> Malformed for Defs<'a> { + fn is_malformed(&self) -> bool { + self.type_defs.iter().any(|def| def.is_malformed()) + || self.value_defs.iter().any(|def| def.is_malformed()) + } +} + +impl<'a> Malformed for TypeDef<'a> { + fn is_malformed(&self) -> bool { + match self { + TypeDef::Alias { header, ann } => header.is_malformed() || ann.is_malformed(), + TypeDef::Opaque { + header, + typ, + derived, + } => header.is_malformed() || typ.is_malformed() || derived.is_malformed(), + TypeDef::Ability { + header, + loc_has, + members, + } => { + header.is_malformed() + || loc_has.is_malformed() + || members.iter().any(|member| member.is_malformed()) + } + } + } +} + +impl<'a> Malformed for AbilityMember<'a> { + fn is_malformed(&self) -> bool { + self.typ.is_malformed() + } +} + +impl<'a> Malformed for Has<'a> { + fn is_malformed(&self) -> bool { + match self { + Has::Has => false, + Has::SpaceBefore(has, _) | Has::SpaceAfter(has, _) => has.is_malformed(), + } + } +} + +impl<'a> Malformed for HasAbility<'a> { + fn is_malformed(&self) -> bool { + match self { + HasAbility::HasAbility { ability, impls } => { + ability.is_malformed() || impls.iter().any(|impl_| impl_.is_malformed()) + } + HasAbility::SpaceBefore(has, _) | HasAbility::SpaceAfter(has, _) => has.is_malformed(), + } + } +} + +impl<'a> Malformed for HasAbilities<'a> { + fn is_malformed(&self) -> bool { + match self { + HasAbilities::Has(abilities) => abilities.iter().any(|ability| ability.is_malformed()), + HasAbilities::SpaceBefore(has, _) | HasAbilities::SpaceAfter(has, _) => { + has.is_malformed() + } + } + } +} + +impl<'a> Malformed for HasImpls<'a> { + fn is_malformed(&self) -> bool { + match self { + HasImpls::HasImpls(impls) => impls.iter().any(|ability| ability.is_malformed()), + HasImpls::SpaceBefore(has, _) | HasImpls::SpaceAfter(has, _) => has.is_malformed(), + } + } +} + +impl<'a> Malformed for ValueDef<'a> { + fn is_malformed(&self) -> bool { + match self { + ValueDef::Annotation(pat, annotation) => { + pat.is_malformed() || annotation.is_malformed() + } + ValueDef::Body(pat, expr) => pat.is_malformed() || expr.is_malformed(), + ValueDef::AnnotatedBody { + ann_pattern, + ann_type, + comment: _, + body_pattern, + body_expr, + } => { + ann_pattern.is_malformed() + || ann_type.is_malformed() + || body_pattern.is_malformed() + || body_expr.is_malformed() + } + ValueDef::Dbg { + condition, + preceding_comment: _, + } + | ValueDef::Expect { + condition, + preceding_comment: _, + } + | ValueDef::ExpectFx { + condition, + preceding_comment: _, + } => condition.is_malformed(), + } + } +} + +impl<'a> Malformed for TypeAnnotation<'a> { + fn is_malformed(&self) -> bool { + match self { + TypeAnnotation::Function(args, ret) => { + args.iter().any(|arg| arg.is_malformed()) || ret.is_malformed() + } + TypeAnnotation::Apply(_, _, args) => args.iter().any(|arg| arg.is_malformed()), + TypeAnnotation::BoundVariable(_) + | TypeAnnotation::Inferred + | TypeAnnotation::Wildcard => false, + TypeAnnotation::As(ty, _, head) => ty.is_malformed() || head.is_malformed(), + TypeAnnotation::Record { fields, ext } => { + fields.iter().any(|field| field.is_malformed()) + || ext.map(|ext| ext.is_malformed()).unwrap_or_default() + } + TypeAnnotation::Tuple { elems: fields, ext } => { + fields.iter().any(|field| field.is_malformed()) + || ext.map(|ext| ext.is_malformed()).unwrap_or_default() + } + TypeAnnotation::TagUnion { ext, tags } => { + tags.iter().any(|field| field.is_malformed()) + || ext.map(|ext| ext.is_malformed()).unwrap_or_default() + } + TypeAnnotation::Where(ann, clauses) => { + ann.is_malformed() || clauses.iter().any(|clause| clause.is_malformed()) + } + TypeAnnotation::SpaceBefore(ty, _) | TypeAnnotation::SpaceAfter(ty, _) => { + ty.is_malformed() + } + TypeAnnotation::Malformed(_) => true, + } + } +} + +impl<'a> Malformed for TypeHeader<'a> { + fn is_malformed(&self) -> bool { + self.vars.iter().any(|var| var.is_malformed()) + } +} + +impl<'a> Malformed for Tag<'a> { + fn is_malformed(&self) -> bool { + match self { + Tag::Apply { name: _, args } => args.iter().any(|arg| arg.is_malformed()), + Tag::SpaceBefore(tag, _) | Tag::SpaceAfter(tag, _) => tag.is_malformed(), + Tag::Malformed(_) => true, + } + } +} + +impl<'a> Malformed for HasClause<'a> { + fn is_malformed(&self) -> bool { + self.abilities.iter().any(|ability| ability.is_malformed()) + } +} + +impl<'a, T: Malformed> Malformed for Spaced<'a, T> { + fn is_malformed(&self) -> bool { + match self { + Spaced::Item(t) => t.is_malformed(), + Spaced::SpaceBefore(t, _) | Spaced::SpaceAfter(t, _) => t.is_malformed(), + } + } +} diff --git a/crates/compiler/parse/src/blankspace.rs b/crates/compiler/parse/src/blankspace.rs index 3f9cc06d3d..b0dbb7bb35 100644 --- a/crates/compiler/parse/src/blankspace.rs +++ b/crates/compiler/parse/src/blankspace.rs @@ -1,5 +1,6 @@ use crate::ast::CommentOrNewline; use crate::ast::Spaceable; +use crate::parser::Progress; use crate::parser::SpaceProblem; use crate::parser::{self, and, backtrackable, BadInputError, Parser, Progress::*}; use crate::state::State; @@ -7,6 +8,7 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::Loc; use roc_region::all::Position; +use roc_region::all::Region; pub fn space0_around_ee<'a, P, S, E>( parser: P, @@ -386,98 +388,132 @@ pub fn spaces<'a, E>() -> impl Parser<'a, &'a [CommentOrNewline<'a>], E> where E: 'a + SpaceProblem, { - move |arena, mut state: State<'a>, _min_indent: u32| { + move |arena, state: State<'a>, _min_indent: u32| { let mut newlines = Vec::new_in(arena); - let mut progress = NoProgress; - loop { - let whitespace = fast_eat_whitespace(state.bytes()); - if whitespace > 0 { - state.advance_mut(whitespace); - progress = MadeProgress; - } - match state.bytes().first() { - Some(b'#') => { - state.advance_mut(1); - - let is_doc_comment = state.bytes().first() == Some(&b'#') - && (state.bytes().get(1) == Some(&b' ') - || state.bytes().get(1) == Some(&b'\n') - || begins_with_crlf(&state.bytes()[1..]) - || Option::is_none(&state.bytes().get(1))); - - if is_doc_comment { - state.advance_mut(1); - if state.bytes().first() == Some(&b' ') { - state.advance_mut(1); - } - } - - let len = fast_eat_until_control_character(state.bytes()); - - // We already checked that the string is valid UTF-8 - debug_assert!(std::str::from_utf8(&state.bytes()[..len]).is_ok()); - let text = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..len]) }; - - let comment = if is_doc_comment { - CommentOrNewline::DocComment(text) - } else { - CommentOrNewline::LineComment(text) - }; - newlines.push(comment); - state.advance_mut(len); - - if begins_with_crlf(state.bytes()) { - state.advance_mut(1); - state = state.advance_newline(); - } else if state.bytes().first() == Some(&b'\n') { - state = state.advance_newline(); - } - - progress = MadeProgress; - } - Some(b'\r') => { - if state.bytes().get(1) == Some(&b'\n') { - newlines.push(CommentOrNewline::Newline); - state.advance_mut(1); - state = state.advance_newline(); - progress = MadeProgress; - } else { - return Err(( - progress, - E::space_problem( - BadInputError::HasMisplacedCarriageReturn, - state.pos(), - ), - )); - } - } - Some(b'\n') => { - newlines.push(CommentOrNewline::Newline); - state = state.advance_newline(); - progress = MadeProgress; - } - Some(b'\t') => { - return Err(( - progress, - E::space_problem(BadInputError::HasTab, state.pos()), - )); - } - Some(x) if *x < b' ' => { - return Err(( - progress, - E::space_problem(BadInputError::HasAsciiControl, state.pos()), - )); - } - _ => { - if !newlines.is_empty() { - state = state.mark_current_indent(); - } - break; - } - } + match consume_spaces(state, |_, space, _| newlines.push(space)) { + Ok((progress, state)) => Ok((progress, newlines.into_bump_slice(), state)), + Err((progress, err)) => Err((progress, err)), } - - Ok((progress, newlines.into_bump_slice(), state)) } } + +pub fn loc_spaces<'a, E>() -> impl Parser<'a, &'a [Loc>], E> +where + E: 'a + SpaceProblem, +{ + move |arena, state: State<'a>, _min_indent: u32| { + let mut newlines = Vec::new_in(arena); + + match consume_spaces(state, |start, space, end| { + newlines.push(Loc::at(Region::between(start, end), space)) + }) { + Ok((progress, state)) => Ok((progress, newlines.into_bump_slice(), state)), + Err((progress, err)) => Err((progress, err)), + } + } +} + +fn consume_spaces<'a, E, F>( + mut state: State<'a>, + mut on_space: F, +) -> Result<(Progress, State<'a>), (Progress, E)> +where + E: 'a + SpaceProblem, + F: FnMut(Position, CommentOrNewline<'a>, Position), +{ + let mut progress = NoProgress; + let mut found_newline = false; + loop { + let whitespace = fast_eat_whitespace(state.bytes()); + if whitespace > 0 { + state.advance_mut(whitespace); + progress = MadeProgress; + } + + let start = state.pos(); + + match state.bytes().first() { + Some(b'#') => { + state.advance_mut(1); + + let is_doc_comment = state.bytes().first() == Some(&b'#') + && (state.bytes().get(1) == Some(&b' ') + || state.bytes().get(1) == Some(&b'\n') + || begins_with_crlf(&state.bytes()[1..]) + || Option::is_none(&state.bytes().get(1))); + + if is_doc_comment { + state.advance_mut(1); + if state.bytes().first() == Some(&b' ') { + state.advance_mut(1); + } + } + + let len = fast_eat_until_control_character(state.bytes()); + + // We already checked that the string is valid UTF-8 + debug_assert!(std::str::from_utf8(&state.bytes()[..len]).is_ok()); + let text = unsafe { std::str::from_utf8_unchecked(&state.bytes()[..len]) }; + + let comment = if is_doc_comment { + CommentOrNewline::DocComment(text) + } else { + CommentOrNewline::LineComment(text) + }; + state.advance_mut(len); + on_space(start, comment, state.pos()); + found_newline = true; + + if begins_with_crlf(state.bytes()) { + state.advance_mut(1); + state = state.advance_newline(); + } else if state.bytes().first() == Some(&b'\n') { + state = state.advance_newline(); + } + + progress = MadeProgress; + } + Some(b'\r') => { + if state.bytes().get(1) == Some(&b'\n') { + state.advance_mut(1); + state = state.advance_newline(); + on_space(start, CommentOrNewline::Newline, state.pos()); + found_newline = true; + progress = MadeProgress; + } else { + return Err(( + progress, + E::space_problem(BadInputError::HasMisplacedCarriageReturn, state.pos()), + )); + } + } + Some(b'\n') => { + state = state.advance_newline(); + on_space(start, CommentOrNewline::Newline, state.pos()); + found_newline = true; + progress = MadeProgress; + } + Some(b'\t') => { + return Err(( + progress, + E::space_problem(BadInputError::HasTab, state.pos()), + )); + } + Some(x) if *x < b' ' => { + return Err(( + progress, + E::space_problem(BadInputError::HasAsciiControl, state.pos()), + )); + } + _ => { + if found_newline { + state = state.mark_current_indent(); + } + break; + } + } + } + + Ok((progress, state)) +} diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 19df864ced..9dce58ddbb 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -1,6 +1,6 @@ use crate::ast::{ AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Has, HasAbilities, - Pattern, Spaceable, TypeAnnotation, TypeDef, TypeHeader, ValueDef, + Pattern, Spaceable, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef, }; use crate::blankspace::{ space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e, @@ -1085,6 +1085,29 @@ enum AliasOrOpaque { Opaque, } +fn extract_tag_and_spaces<'a>(arena: &'a Bump, expr: Expr<'a>) -> Option> { + let mut expr = expr.extract_spaces(); + + loop { + match &expr.item { + Expr::ParensAround(inner_expr) => { + let inner_expr = inner_expr.extract_spaces(); + expr.item = inner_expr.item; + expr.before = merge_spaces(arena, expr.before, inner_expr.before); + expr.after = merge_spaces(arena, inner_expr.after, expr.after); + } + Expr::Tag(tag) => { + return Some(Spaces { + before: expr.before, + item: tag, + after: expr.after, + }); + } + _ => return None, + } + } +} + #[allow(clippy::too_many_arguments)] fn finish_parsing_alias_or_opaque<'a>( min_indent: u32, @@ -1105,117 +1128,113 @@ fn finish_parsing_alias_or_opaque<'a>( let mut defs = Defs::default(); - let state = match &expr.value.extract_spaces().item { - Expr::Tag(name) => { - let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena); + let state = if let Some(tag) = extract_tag_and_spaces(arena, expr.value) { + let name = tag.item; + let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena); - for argument in arguments { - match expr_to_pattern_help(arena, &argument.value) { - Ok(good) => { - type_arguments.push(Loc::at(argument.region, good)); - } - Err(()) => { - return Err(( - MadeProgress, - EExpr::Pattern( - arena.alloc(EPattern::NotAPattern(state.pos())), - state.pos(), - ), - )); - } + for argument in arguments { + match expr_to_pattern_help(arena, &argument.value) { + Ok(good) => { + type_arguments.push(Loc::at(argument.region, good)); } - } - - match kind { - AliasOrOpaque::Alias => { - let (_, signature, state) = - alias_signature_with_space_before().parse(arena, state, min_indent)?; - - let def_region = Region::span_across(&expr.region, &signature.region); - - let header = TypeHeader { - name: Loc::at(expr.region, name), - vars: type_arguments.into_bump_slice(), - }; - - let def = TypeDef::Alias { - header, - ann: signature, - }; - - defs.push_type_def(def, def_region, &[], &[]); - - state - } - - AliasOrOpaque::Opaque => { - let (_, (signature, derived), state) = - opaque_signature_with_space_before().parse(arena, state, indented_more)?; - - let def_region = Region::span_across(&expr.region, &signature.region); - - let header = TypeHeader { - name: Loc::at(expr.region, name), - vars: type_arguments.into_bump_slice(), - }; - - let def = TypeDef::Opaque { - header, - typ: signature, - derived, - }; - - defs.push_type_def(def, def_region, &[], &[]); - - state + Err(()) => { + return Err(( + MadeProgress, + EExpr::Pattern( + arena.alloc(EPattern::NotAPattern(state.pos())), + state.pos(), + ), + )); } } } - _ => { - let call = to_call(arena, arguments, expr); + match kind { + AliasOrOpaque::Alias => { + let (_, signature, state) = + alias_signature_with_space_before().parse(arena, state, min_indent)?; - match expr_to_pattern_help(arena, &call.value) { - Ok(good) => { - let parser = specialize( - EExpr::Type, - space0_before_e( - set_min_indent(indented_more, type_annotation::located(false)), - EType::TIndentStart, - ), - ); + let def_region = Region::span_across(&expr.region, &signature.region); - match parser.parse(arena, state.clone(), min_indent) { - Err((_, fail)) => return Err((MadeProgress, fail)), - Ok((_, mut ann_type, state)) => { - // put the spaces from after the operator in front of the call - if !spaces_after_operator.is_empty() { - ann_type = arena - .alloc(ann_type.value) - .with_spaces_before(spaces_after_operator, ann_type.region); - } + let header = TypeHeader { + name: Loc::at(expr.region, name), + vars: type_arguments.into_bump_slice(), + }; - let def_region = Region::span_across(&call.region, &ann_type.region); + let def = TypeDef::Alias { + header, + ann: signature, + }; - let value_def = - ValueDef::Annotation(Loc::at(expr_region, good), ann_type); + defs.push_type_def(def, def_region, &[], &[]); - defs.push_value_def(value_def, def_region, &[], &[]); + state + } - state + AliasOrOpaque::Opaque => { + let (_, (signature, derived), state) = + opaque_signature_with_space_before().parse(arena, state, indented_more)?; + + let def_region = Region::span_across(&expr.region, &signature.region); + + let header = TypeHeader { + name: Loc::at(expr.region, name), + vars: type_arguments.into_bump_slice(), + }; + + let def = TypeDef::Opaque { + header, + typ: signature, + derived, + }; + + defs.push_type_def(def, def_region, &[], &[]); + + state + } + } + } else { + let call = to_call(arena, arguments, expr); + + match expr_to_pattern_help(arena, &call.value) { + Ok(good) => { + let parser = specialize( + EExpr::Type, + space0_before_e( + set_min_indent(indented_more, type_annotation::located(false)), + EType::TIndentStart, + ), + ); + + match parser.parse(arena, state.clone(), min_indent) { + Err((_, fail)) => return Err((MadeProgress, fail)), + Ok((_, mut ann_type, state)) => { + // put the spaces from after the operator in front of the call + if !spaces_after_operator.is_empty() { + ann_type = arena + .alloc(ann_type.value) + .with_spaces_before(spaces_after_operator, ann_type.region); } + + let def_region = Region::span_across(&call.region, &ann_type.region); + + let value_def = ValueDef::Annotation(Loc::at(expr_region, good), ann_type); + + defs.push_value_def(value_def, def_region, &[], &[]); + + state } } - Err(_) => { - // this `:`/`:=` likely occurred inline; treat it as an invalid operator - let op = match kind { - AliasOrOpaque::Alias => ":", - AliasOrOpaque::Opaque => ":=", - }; - let fail = EExpr::BadOperator(op, loc_op.region.start()); + } + Err(_) => { + // this `:`/`:=` likely occurred inline; treat it as an invalid operator + let op = match kind { + AliasOrOpaque::Alias => ":", + AliasOrOpaque::Opaque => ":=", + }; + let fail = EExpr::BadOperator(op, loc_op.region.start()); - return Err((MadeProgress, fail)); - } + return Err((MadeProgress, fail)); } } }; @@ -1796,20 +1815,47 @@ pub fn loc_expr<'a>(accept_multi_backpassing: bool) -> impl Parser<'a, Loc( + arena: &'a Bump, + a: &'a [CommentOrNewline<'a>], + b: &'a [CommentOrNewline<'a>], +) -> &'a [CommentOrNewline<'a>] { + if a.is_empty() { + b + } else if b.is_empty() { + a + } else { + let mut merged = Vec::with_capacity_in(a.len() + b.len(), arena); + merged.extend_from_slice(a); + merged.extend_from_slice(b); + merged.into_bump_slice() + } +} + /// If the given Expr would parse the same way as a valid Pattern, convert it. /// Example: (foo) could be either an Expr::Var("foo") or Pattern::Identifier("foo") fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result, ()> { - match expr { + let mut expr = expr.extract_spaces(); + + if let Expr::ParensAround(loc_expr) = &expr.item { + let expr_inner = loc_expr.extract_spaces(); + + expr.before = merge_spaces(arena, expr.before, expr_inner.before); + expr.after = merge_spaces(arena, expr_inner.after, expr.after); + expr.item = expr_inner.item; + } + + let mut pat = match expr.item { Expr::Var { module_name, ident } => { if module_name.is_empty() { - Ok(Pattern::Identifier(ident)) + Pattern::Identifier(ident) } else { - Ok(Pattern::QualifiedIdentifier { module_name, ident }) + Pattern::QualifiedIdentifier { module_name, ident } } } - Expr::Underscore(opt_name) => Ok(Pattern::Underscore(opt_name)), - Expr::Tag(value) => Ok(Pattern::Tag(value)), - Expr::OpaqueRef(value) => Ok(Pattern::OpaqueRef(value)), + Expr::Underscore(opt_name) => Pattern::Underscore(opt_name), + Expr::Tag(value) => Pattern::Tag(value), + Expr::OpaqueRef(value) => Pattern::OpaqueRef(value), Expr::Apply(loc_val, loc_args, _) => { let region = loc_val.region; let value = expr_to_pattern_help(arena, &loc_val.value)?; @@ -1826,19 +1872,10 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::SpaceBefore( - arena.alloc(expr_to_pattern_help(arena, sub_expr)?), - spaces, - )), - Expr::SpaceAfter(sub_expr, spaces) => Ok(Pattern::SpaceAfter( - arena.alloc(expr_to_pattern_help(arena, sub_expr)?), - spaces, - )), - - Expr::ParensAround(sub_expr) => expr_to_pattern_help(arena, sub_expr), + Expr::SpaceBefore(..) | Expr::SpaceAfter(..) | Expr::ParensAround(..) => unreachable!(), Expr::Record(fields) => { let patterns = fields.map_items_result(arena, |loc_assigned_field| { @@ -1847,34 +1884,30 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Ok(Pattern::Tuple(fields.map_items_result( - arena, - |loc_expr| { - Ok(Loc { - region: loc_expr.region, - value: expr_to_pattern_help(arena, &loc_expr.value)?, - }) - }, - )?)), + Expr::Tuple(fields) => Pattern::Tuple(fields.map_items_result(arena, |loc_expr| { + Ok(Loc { + region: loc_expr.region, + value: expr_to_pattern_help(arena, &loc_expr.value)?, + }) + })?), - &Expr::Float(string) => Ok(Pattern::FloatLiteral(string)), - &Expr::Num(string) => Ok(Pattern::NumLiteral(string)), + Expr::Float(string) => Pattern::FloatLiteral(string), + Expr::Num(string) => Pattern::NumLiteral(string), Expr::NonBase10Int { string, base, is_negative, - } => Ok(Pattern::NonBase10Literal { + } => Pattern::NonBase10Literal { string, - base: *base, - is_negative: *is_negative, - }), + base, + is_negative, + }, // These would not have parsed as patterns - Expr::RecordAccessorFunction(_) + Expr::AccessorFunction(_) | Expr::RecordAccess(_, _) - | Expr::TupleAccessorFunction(_) | Expr::TupleAccess(_, _) | Expr::List { .. } | Expr::Closure(_, _) @@ -1889,12 +1922,23 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result Err(()), + | Expr::Crash => return Err(()), - Expr::Str(string) => Ok(Pattern::StrLiteral(*string)), - Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(string)), - Expr::MalformedIdent(string, problem) => Ok(Pattern::MalformedIdent(string, *problem)), + Expr::Str(string) => Pattern::StrLiteral(string), + Expr::SingleQuote(string) => Pattern::SingleQuote(string), + Expr::MalformedIdent(string, problem) => Pattern::MalformedIdent(string, problem), + }; + + // Now we re-add the spaces + + if !expr.before.is_empty() { + pat = Pattern::SpaceBefore(arena.alloc(pat), expr.before); } + if !expr.after.is_empty() { + pat = Pattern::SpaceAfter(arena.alloc(pat), expr.after); + } + + Ok(pat) } fn assigned_expr_field_to_pattern_help<'a>( @@ -2429,7 +2473,13 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> { // The first value in the iterator is the variable name, // e.g. `foo` in `foo.bar.baz` let mut answer = match iter.next() { - Some(ident) => Expr::Var { module_name, ident }, + Some(Accessor::RecordField(ident)) => Expr::Var { module_name, ident }, + Some(Accessor::TupleIndex(_)) => { + // TODO: make this state impossible to represent in Ident::Access, + // by splitting out parts[0] into a separate field with a type of `&'a str`, + // rather than a `&'a [Accessor<'a>]`. + panic!("Parsed an Ident::Access with a first part of a tuple index"); + } None => { panic!("Parsed an Ident::Access with no parts"); } @@ -2441,13 +2491,19 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> { // Wrap the previous answer in the new one, so we end up // with a nested Expr. That way, `foo.bar.baz` gets represented // in the AST as if it had been written (foo.bar).baz all along. - answer = Expr::RecordAccess(arena.alloc(answer), field); + match field { + Accessor::RecordField(field) => { + answer = Expr::RecordAccess(arena.alloc(answer), field); + } + Accessor::TupleIndex(index) => { + answer = Expr::TupleAccess(arena.alloc(answer), index); + } + } } answer } - Ident::RecordAccessorFunction(string) => Expr::RecordAccessorFunction(string), - Ident::TupleAccessorFunction(string) => Expr::TupleAccessorFunction(string), + Ident::AccessorFunction(string) => Expr::AccessorFunction(string), Ident::Malformed(string, problem) => Expr::MalformedIdent(string, problem), } } diff --git a/crates/compiler/parse/src/header.rs b/crates/compiler/parse/src/header.rs index c378be6c8b..bec2f89bcd 100644 --- a/crates/compiler/parse/src/header.rs +++ b/crates/compiler/parse/src/header.rs @@ -1,4 +1,6 @@ -use crate::ast::{Collection, CommentOrNewline, Spaced, Spaces, StrLiteral, TypeAnnotation}; +use crate::ast::{ + Collection, CommentOrNewline, Malformed, Spaced, Spaces, StrLiteral, TypeAnnotation, +}; use crate::blankspace::space0_e; use crate::ident::{lowercase_ident, UppercaseIdent}; use crate::parser::{optional, then}; @@ -342,3 +344,55 @@ pub fn package_name<'a>() -> impl Parser<'a, PackageName<'a>, EPackageName<'a>> }, ) } + +impl<'a, K, V> Malformed for KeywordItem<'a, K, V> +where + K: Malformed, + V: Malformed, +{ + fn is_malformed(&self) -> bool { + self.keyword.is_malformed() || self.item.is_malformed() + } +} + +impl<'a> Malformed for InterfaceHeader<'a> { + fn is_malformed(&self) -> bool { + false + } +} + +impl<'a> Malformed for HostedHeader<'a> { + fn is_malformed(&self) -> bool { + false + } +} + +impl<'a> Malformed for AppHeader<'a> { + fn is_malformed(&self) -> bool { + self.name.is_malformed() + } +} + +impl<'a> Malformed for PackageHeader<'a> { + fn is_malformed(&self) -> bool { + false + } +} + +impl<'a> Malformed for PlatformRequires<'a> { + fn is_malformed(&self) -> bool { + self.signature.is_malformed() + } +} + +impl<'a> Malformed for PlatformHeader<'a> { + fn is_malformed(&self) -> bool { + false + } +} + +impl<'a> Malformed for TypedIdent<'a> { + fn is_malformed(&self) -> bool { + self.ann.is_malformed() + } +} diff --git a/crates/compiler/parse/src/highlight.rs b/crates/compiler/parse/src/highlight.rs new file mode 100644 index 0000000000..2b561d1446 --- /dev/null +++ b/crates/compiler/parse/src/highlight.rs @@ -0,0 +1,661 @@ +use encode_unicode::CharExt; +use std::collections::HashSet; + +use bumpalo::Bump; +use roc_region::all::{Loc, Region}; + +use crate::{ + ast::CommentOrNewline, + blankspace::loc_spaces, + keyword::KEYWORDS, + number_literal::positive_number_literal, + parser::{EExpr, ParseResult, Parser}, + state::State, + string_literal::{parse_str_like_literal, StrLikeLiteral}, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Token { + LineComment, + DocComment, + Error, + SingleQuote, + String, + UnicodeEscape, + EscapedChar, + Interpolated, + Keyword, + UpperIdent, + LowerIdent, + Number, + QuestionMark, + Percent, + Caret, + Other, + Minus, + Bang, + BangEquals, + Plus, + Colon, + ColonEquals, + Bar, + DoubleBar, + And, + DoubleAnd, + Equals, + DoubleEquals, + GreaterThan, + GreaterThanEquals, + LessThan, + LessThanEquals, + Comma, + Backslash, + Slash, + DoubleSlash, + Pizza, + Brace, + Bracket, + AtSign, + Paren, + Arrow, + Pipe, + Backpass, + Decimal, + Multiply, + Underscore, +} + +pub fn highlight(text: &str) -> Vec> { + let mut tokens = Vec::new(); + let state = State::new(text.as_bytes()); + + let arena = Bump::new(); + + let header_keywords = HEADER_KEYWORDS.iter().copied().collect::>(); + let body_keywords = KEYWORDS.iter().copied().collect::>(); + + if let Ok((_prog, _, new_state)) = crate::module::header().parse(&arena, state.clone(), 0) { + let inner_state = + State::new(text[..state.bytes().len() - new_state.bytes().len()].as_bytes()); + highlight_inner(&arena, inner_state, &mut tokens, &header_keywords); + highlight_inner(&arena, new_state, &mut tokens, &body_keywords); + } else { + highlight_inner(&arena, state, &mut tokens, &body_keywords); + } + + tokens +} + +fn highlight_inner<'a>( + arena: &'a Bump, + mut state: State<'a>, + tokens: &mut Vec>, + keywords: &HashSet<&str>, +) { + loop { + let start = state.pos(); + if let Ok((b, _width)) = char::from_utf8_slice_start(state.bytes()) { + match b { + ' ' | '\n' | '\t' | '\r' | '#' => { + let res: ParseResult<'a, _, EExpr<'a>> = + loc_spaces().parse(arena, state.clone(), 0); + if let Ok((_, spaces, new_state)) = res { + state = new_state; + for space in spaces { + let token = match space.value { + CommentOrNewline::Newline => { + continue; + } + CommentOrNewline::LineComment(_) => Token::LineComment, + CommentOrNewline::DocComment(_) => Token::DocComment, + }; + tokens.push(Loc::at(space.region, token)); + } + } else { + fast_forward_to(&mut state, tokens, start, |c| c == b'\n'); + } + } + '"' | '\'' => { + if let Ok((_, item, new_state)) = + parse_str_like_literal().parse(arena, state.clone(), 0) + { + state = new_state; + match item { + StrLikeLiteral::SingleQuote(_) => { + tokens.push(Loc::at( + Region::between(start, state.pos()), + Token::SingleQuote, + )); + } + StrLikeLiteral::Str(_) => { + tokens.push(Loc::at( + Region::between(start, state.pos()), + Token::String, + )); + } + } + } else { + fast_forward_to(&mut state, tokens, start, |c| c == b'\n'); + } + } + c if c.is_alphabetic() => { + let buffer = state.bytes(); + let mut chomped = 0; + + let is_upper = c.is_uppercase(); + + while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if ch.is_alphabetic() || ch.is_ascii_digit() { + chomped += width; + } else { + // we're done + break; + } + } + + let ident = std::str::from_utf8(&buffer[..chomped]).unwrap(); + state.advance_mut(chomped); + + if keywords.contains(ident) { + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Keyword)); + } else { + tokens.push(Loc::at( + Region::between(start, state.pos()), + if is_upper { + Token::UpperIdent + } else { + Token::LowerIdent + }, + )); + } + } + '.' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Decimal)); + } + '0'..='9' => { + if let Ok((_, _item, new_state)) = + positive_number_literal().parse(arena, state.clone(), 0) + { + state = new_state; + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Number)); + } else { + fast_forward_to(&mut state, tokens, start, |b| !b.is_ascii_digit()); + } + } + ':' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'=') { + state.advance_mut(1); + Token::ColonEquals + } else { + Token::Colon + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + '|' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'>') { + state.advance_mut(1); + Token::Pizza + } else if state.bytes().first() == Some(&b'|') { + state.advance_mut(1); + Token::DoubleBar + } else { + Token::Bar + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + '&' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'&') { + state.advance_mut(1); + Token::DoubleAnd + } else { + Token::And + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + '-' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'>') { + state.advance_mut(1); + Token::Arrow + } else { + Token::Minus + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + '+' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Plus)); + } + '=' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'=') { + state.advance_mut(1); + Token::DoubleEquals + } else { + Token::Equals + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + '>' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'=') { + state.advance_mut(1); + Token::GreaterThanEquals + } else { + Token::GreaterThan + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + '<' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'=') { + state.advance_mut(1); + Token::LessThanEquals + } else if state.bytes().first() == Some(&b'-') { + state.advance_mut(1); + Token::Backpass + } else { + Token::LessThan + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + '!' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'=') { + state.advance_mut(1); + Token::BangEquals + } else { + Token::Bang + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + ',' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Comma)); + } + '_' => { + state.advance_mut(1); + tokens.push(Loc::at( + Region::between(start, state.pos()), + Token::Underscore, + )); + } + '?' => { + state.advance_mut(1); + tokens.push(Loc::at( + Region::between(start, state.pos()), + Token::QuestionMark, + )); + } + '%' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Percent)); + } + '*' => { + state.advance_mut(1); + tokens.push(Loc::at( + Region::between(start, state.pos()), + Token::Multiply, + )); + } + '^' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Caret)); + } + '\\' => { + state.advance_mut(1); + tokens.push(Loc::at( + Region::between(start, state.pos()), + Token::Backslash, + )); + } + '/' => { + state.advance_mut(1); + let tok = if state.bytes().first() == Some(&b'/') { + state.advance_mut(1); + Token::DoubleSlash + } else { + Token::Slash + }; + tokens.push(Loc::at(Region::between(start, state.pos()), tok)); + } + '@' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::AtSign)); + } + '{' | '}' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Brace)); + } + '[' | ']' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Bracket)); + } + '(' | ')' => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Paren)); + } + _ => { + state.advance_mut(1); + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Other)); + } + } + } else { + break; + } + } +} + +fn fast_forward_to( + state: &mut State, + tokens: &mut Vec>, + start: roc_region::all::Position, + end: impl Fn(u8) -> bool, +) { + while let Some(b) = state.bytes().first() { + if end(*b) { + break; + } + state.advance_mut(1); + } + tokens.push(Loc::at(Region::between(start, state.pos()), Token::Error)); +} + +pub const HEADER_KEYWORDS: [&str; 14] = [ + "interface", + "app", + "package", + "platform", + "hosted", + "exposes", + "imports", + "with", + "generates", + "package", + "packages", + "requires", + "provides", + "to", +]; + +#[cfg(test)] +mod tests { + use roc_region::all::Position; + + use super::*; + + #[test] + fn test_highlight_comments() { + let text = "# a\n#b\n#c"; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![ + Loc::at( + Region::between(Position::new(0), Position::new(3)), + Token::LineComment + ), + Loc::at( + Region::between(Position::new(4), Position::new(6)), + Token::LineComment + ), + Loc::at( + Region::between(Position::new(7), Position::new(9)), + Token::LineComment + ), + ] + ); + } + + #[test] + fn test_highlight_doc_comments() { + let text = "## a\n##b\n##c"; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![ + Loc::at( + Region::between(Position::new(0), Position::new(4)), + Token::DocComment + ), + // the next two are line comments because there's not a space at the beginning + Loc::at( + Region::between(Position::new(5), Position::new(8)), + Token::LineComment + ), + Loc::at( + Region::between(Position::new(9), Position::new(12)), + Token::LineComment + ), + ] + ); + } + + #[test] + fn test_highlight_strings() { + let text = r#""a""#; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![Loc::at( + Region::between(Position::new(0), Position::new(3)), + Token::String + )] + ); + } + + #[test] + fn test_highlight_single_quotes() { + let text = r#"'a'"#; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![Loc::at( + Region::between(Position::new(0), Position::new(3)), + Token::SingleQuote + )] + ); + } + + #[test] + fn test_highlight_header() { + let text = r#"app "test-app" provides [] to "./blah""#; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![ + Loc::at( + Region::between(Position::new(0), Position::new(3)), + Token::Keyword + ), + Loc::at( + Region::between(Position::new(4), Position::new(14)), + Token::String + ), + Loc::at( + Region::between(Position::new(15), Position::new(23)), + Token::Keyword + ), + Loc::at( + Region::between(Position::new(24), Position::new(25)), + Token::Bracket + ), + Loc::at( + Region::between(Position::new(25), Position::new(26)), + Token::Bracket + ), + Loc::at( + Region::between(Position::new(27), Position::new(29)), + Token::Keyword + ), + Loc::at( + Region::between(Position::new(30), Position::new(38)), + Token::String + ), + ] + ); + } + + #[test] + fn test_highlight_numbers() { + let text = "123.0 123 123. 123.0e10 123e10 123e-10 0x123"; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![ + Loc::at( + Region::between(Position::new(0), Position::new(5)), + Token::Number + ), + Loc::at( + Region::between(Position::new(6), Position::new(9)), + Token::Number + ), + Loc::at( + Region::between(Position::new(10), Position::new(14)), + Token::Number + ), + Loc::at( + Region::between(Position::new(15), Position::new(23)), + Token::Number + ), + Loc::at( + Region::between(Position::new(24), Position::new(30)), + Token::Number + ), + Loc::at( + Region::between(Position::new(31), Position::new(38)), + Token::Number + ), + Loc::at( + Region::between(Position::new(39), Position::new(44)), + Token::Number + ), + ] + ); + } + + #[test] + fn test_combine_tokens() { + let text = "-> := <- |> || >= <= =="; + let actual = highlight(text); + + let expected = vec![ + Loc::at( + Region::between(Position::new(0), Position::new(2)), + Token::Arrow, + ), + Loc::at( + Region::between(Position::new(3), Position::new(5)), + Token::ColonEquals, + ), + Loc::at( + Region::between(Position::new(6), Position::new(8)), + Token::Backpass, + ), + Loc::at( + Region::between(Position::new(9), Position::new(11)), + Token::Pizza, + ), + Loc::at( + Region::between(Position::new(12), Position::new(14)), + Token::DoubleBar, + ), + Loc::at( + Region::between(Position::new(15), Position::new(17)), + Token::GreaterThanEquals, + ), + Loc::at( + Region::between(Position::new(18), Position::new(20)), + Token::LessThanEquals, + ), + Loc::at( + Region::between(Position::new(21), Position::new(23)), + Token::DoubleEquals, + ), + ]; + + assert_eq!(actual, expected); + } + + #[test] + fn test_highlight_pattern_matching() { + let text = "Green | Yellow -> \"not red\""; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![ + Loc::at( + Region::between(Position::new(0), Position::new(5)), + Token::UpperIdent + ), + Loc::at( + Region::between(Position::new(6), Position::new(7)), + Token::Bar + ), + Loc::at( + Region::between(Position::new(8), Position::new(14)), + Token::UpperIdent + ), + Loc::at( + Region::between(Position::new(15), Position::new(17)), + Token::Arrow + ), + Loc::at( + Region::between(Position::new(18), Position::new(27)), + Token::String + ), + ] + ) + } + + #[test] + fn test_highlight_question_mark() { + let text = "title? Str"; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![ + Loc::at( + Region::between(Position::new(0), Position::new(5)), + Token::LowerIdent + ), + Loc::at( + Region::between(Position::new(5), Position::new(6)), + Token::QuestionMark + ), + Loc::at( + Region::between(Position::new(7), Position::new(10)), + Token::UpperIdent + ), + ] + ) + } + + #[test] + fn test_highlight_slash() { + let text = "first / second"; + let tokens = highlight(text); + assert_eq!( + tokens, + vec![ + Loc::at( + Region::between(Position::new(0), Position::new(5)), + Token::LowerIdent + ), + Loc::at( + Region::between(Position::new(6), Position::new(7)), + Token::Slash + ), + Loc::at( + Region::between(Position::new(8), Position::new(14)), + Token::LowerIdent + ), + ] + ) + } +} diff --git a/crates/compiler/parse/src/ident.rs b/crates/compiler/parse/src/ident.rs index 283d5c2d52..59a04b3824 100644 --- a/crates/compiler/parse/src/ident.rs +++ b/crates/compiler/parse/src/ident.rs @@ -41,12 +41,10 @@ pub enum Ident<'a> { /// foo or foo.bar or Foo.Bar.baz.qux Access { module_name: &'a str, - parts: &'a [&'a str], + parts: &'a [Accessor<'a>], }, - /// .foo { foo: 42 } - RecordAccessorFunction(&'a str), - /// .1 (1, 2, 3) - TupleAccessorFunction(&'a str), + /// `.foo { foo: 42 }` or `.1 (1, 2, 3)` + AccessorFunction(Accessor<'a>), /// .Foo or foo. or something like foo.Bar Malformed(&'a str, BadIdent), } @@ -71,8 +69,7 @@ impl<'a> Ident<'a> { len - 1 } - RecordAccessorFunction(string) => string.len(), - TupleAccessorFunction(string) => string.len(), + AccessorFunction(string) => string.len(), Malformed(string, _) => string.len(), } } @@ -197,7 +194,7 @@ pub fn parse_ident<'a>( if module_name.is_empty() { if let Some(first) = parts.first() { for keyword in crate::keyword::KEYWORDS.iter() { - if first == keyword { + if first == &Accessor::RecordField(keyword) { return Err((NoProgress, EExpr::Start(initial.pos()))); } } @@ -264,6 +261,7 @@ pub enum BadIdent { WeirdDotQualified(Position), StrayDot(Position), BadOpaqueRef(Position), + QualifiedTupleAccessor(Position), } fn is_alnum(ch: char) -> bool { @@ -339,11 +337,32 @@ where } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Accessor<'a> { RecordField(&'a str), TupleIndex(&'a str), } +impl<'a> Accessor<'a> { + pub fn len(&self) -> usize { + match self { + Accessor::RecordField(name) => name.len(), + Accessor::TupleIndex(name) => name.len(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() > 0 + } + + pub fn as_inner(&self) -> &'a str { + match self { + Accessor::RecordField(name) => name, + Accessor::TupleIndex(name) => name, + } + } +} + /// a `.foo` or `.1` accessor function fn chomp_accessor(buffer: &[u8], pos: Position) -> Result { // assumes the leading `.` has been chomped already @@ -415,13 +434,9 @@ fn chomp_identifier_chain<'a>( match char::from_utf8_slice_start(&buffer[chomped..]) { Ok((ch, width)) => match ch { '.' => match chomp_accessor(&buffer[1..], pos) { - Ok(Accessor::RecordField(accessor)) => { + Ok(accessor) => { let bytes_parsed = 1 + accessor.len(); - return Ok((bytes_parsed as u32, Ident::RecordAccessorFunction(accessor))); - } - Ok(Accessor::TupleIndex(accessor)) => { - let bytes_parsed = 1 + accessor.len(); - return Ok((bytes_parsed as u32, Ident::TupleAccessorFunction(accessor))); + return Ok((bytes_parsed as u32, Ident::AccessorFunction(accessor))); } Err(fail) => return Err((1, fail)), }, @@ -474,11 +489,18 @@ fn chomp_identifier_chain<'a>( if !first_is_uppercase { let first_part = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; - parts.push(first_part); + parts.push(Accessor::RecordField(first_part)); } match chomp_access_chain(&buffer[chomped..], &mut parts) { Ok(width) => { + if matches!(parts[0], Accessor::TupleIndex(_)) && first_is_uppercase { + return Err(( + chomped as u32, + BadIdent::QualifiedTupleAccessor(pos.bump_column(chomped as u32)), + )); + } + chomped += width as usize; let ident = Ident::Access { @@ -518,7 +540,7 @@ fn chomp_identifier_chain<'a>( let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; let ident = Ident::Access { module_name: "", - parts: arena.alloc([value]), + parts: arena.alloc([Accessor::RecordField(value)]), }; Ok((chomped as u32, ident)) } @@ -591,7 +613,7 @@ fn chomp_concrete_type(buffer: &[u8]) -> Result<(&str, &str, usize), Progress> { } } -fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Result { +fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, Accessor<'a>>) -> Result { let mut chomped = 0; while let Some(b'.') = buffer.get(chomped) { @@ -603,11 +625,23 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Res &buffer[chomped + 1..chomped + 1 + name.len()], ) }; - parts.push(value); + parts.push(Accessor::RecordField(value)); chomped += name.len() + 1; } - Err(_) => return Err(chomped as u32 + 1), + Err(_) => match chomp_integer_part(slice) { + Ok(name) => { + let value = unsafe { + std::str::from_utf8_unchecked( + &buffer[chomped + 1..chomped + 1 + name.len()], + ) + }; + parts.push(Accessor::TupleIndex(value)); + + chomped += name.len() + 1; + } + Err(_) => return Err(chomped as u32 + 1), + }, }, None => return Err(chomped as u32 + 1), } diff --git a/crates/compiler/parse/src/lib.rs b/crates/compiler/parse/src/lib.rs index dbdfe30ff4..41deda54f6 100644 --- a/crates/compiler/parse/src/lib.rs +++ b/crates/compiler/parse/src/lib.rs @@ -10,6 +10,7 @@ pub mod ast; pub mod blankspace; pub mod expr; pub mod header; +pub mod highlight; pub mod ident; pub mod keyword; pub mod module; diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index 9dc715d74f..e7e5a3ec02 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -536,8 +536,7 @@ pub enum EPattern<'a> { IndentEnd(Position), AsIndentStart(Position), - RecordAccessorFunction(Position), - TupleAccessorFunction(Position), + AccessorFunction(Position), } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/compiler/parse/src/pattern.rs b/crates/compiler/parse/src/pattern.rs index 197194dfcf..0854eb04be 100644 --- a/crates/compiler/parse/src/pattern.rs +++ b/crates/compiler/parse/src/pattern.rs @@ -1,6 +1,6 @@ use crate::ast::{Has, Pattern, PatternAs, Spaceable}; use crate::blankspace::{space0_e, spaces, spaces_before}; -use crate::ident::{lowercase_ident, parse_ident, Ident}; +use crate::ident::{lowercase_ident, parse_ident, Accessor, Ident}; use crate::keyword; use crate::parser::Progress::{self, *}; use crate::parser::{ @@ -377,42 +377,49 @@ fn loc_ident_pattern_help<'a>( Ident::Access { module_name, parts } => { // Plain identifiers (e.g. `foo`) are allowed in patterns, but // more complex ones (e.g. `Foo.bar` or `foo.bar.baz`) are not. - if crate::keyword::KEYWORDS.contains(&parts[0]) { - Err((NoProgress, EPattern::End(original_state.pos()))) - } else if module_name.is_empty() && parts.len() == 1 { - Ok(( - MadeProgress, - Loc { - region: loc_ident.region, - value: Pattern::Identifier(parts[0]), - }, - state, - )) - } else { - let malformed_str = if module_name.is_empty() { - parts.join(".") - } else { - format!("{}.{}", module_name, parts.join(".")) - }; - Ok(( - MadeProgress, - Loc { - region: loc_ident.region, - value: Pattern::Malformed( - String::from_str_in(&malformed_str, arena).into_bump_str(), - ), - }, - state, - )) + + for keyword in crate::keyword::KEYWORDS.iter() { + if parts[0] == Accessor::RecordField(keyword) { + return Err((NoProgress, EPattern::End(original_state.pos()))); + } } + + if module_name.is_empty() && parts.len() == 1 { + if let Accessor::RecordField(var) = &parts[0] { + return Ok(( + MadeProgress, + Loc { + region: loc_ident.region, + value: Pattern::Identifier(var), + }, + state, + )); + } + } + let mut malformed_str = String::new_in(arena); + + if !module_name.is_empty() { + malformed_str.push_str(module_name); + }; + for part in parts { + if !malformed_str.is_empty() { + malformed_str.push('.'); + } + malformed_str.push_str(part.as_inner()); + } + + Ok(( + MadeProgress, + Loc { + region: loc_ident.region, + value: Pattern::Malformed(malformed_str.into_bump_str()), + }, + state, + )) } - Ident::RecordAccessorFunction(_string) => Err(( + Ident::AccessorFunction(_string) => Err(( MadeProgress, - EPattern::RecordAccessorFunction(loc_ident.region.start()), - )), - Ident::TupleAccessorFunction(_string) => Err(( - MadeProgress, - EPattern::TupleAccessorFunction(loc_ident.region.start()), + EPattern::AccessorFunction(loc_ident.region.start()), )), Ident::Malformed(malformed, problem) => { debug_assert!(!malformed.is_empty()); diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index d3228c4276..48d665e61d 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -250,7 +250,7 @@ fn loc_type_in_parens<'a>( if fields.len() > 1 || ext.is_some() { Ok(( MadeProgress, - Loc::at(region, TypeAnnotation::Tuple { fields, ext }), + Loc::at(region, TypeAnnotation::Tuple { elems: fields, ext }), state, )) } else if fields.len() == 1 { diff --git a/crates/compiler/problem/Cargo.toml b/crates/compiler/problem/Cargo.toml index cd1cd8770b..d6b0d7cf4e 100644 --- a/crates/compiler/problem/Cargo.toml +++ b/crates/compiler/problem/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "roc_problem" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Provides types to describe problems that can occur when compiling Roc code." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_collections = { path = "../collections" } -roc_region = { path = "../region" } roc_module = { path = "../module" } -roc_types = { path = "../types" } roc_parse = { path = "../parse" } +roc_region = { path = "../region" } +roc_types = { path = "../types" } diff --git a/crates/compiler/region/Cargo.toml b/crates/compiler/region/Cargo.toml index b67e5a44c2..bc82774e74 100644 --- a/crates/compiler/region/Cargo.toml +++ b/crates/compiler/region/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "roc_region" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Data structures for storing source-code-location information, used heavily for contextual error messages." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -static_assertions = "1.1.0" +static_assertions.workspace = true diff --git a/crates/compiler/region/src/all.rs b/crates/compiler/region/src/all.rs index 457810cdf9..fb83fe2b37 100644 --- a/crates/compiler/region/src/all.rs +++ b/crates/compiler/region/src/all.rs @@ -129,6 +129,10 @@ impl Position { offset: self.offset - count as u32, } } + + pub fn byte_offset(&self) -> usize { + self.offset as usize + } } impl Debug for Position { @@ -322,6 +326,10 @@ impl Loc { value: transform(self.value), } } + + pub fn byte_range(&self) -> std::ops::Range { + self.region.start.byte_offset()..self.region.end.byte_offset() + } } impl fmt::Debug for Loc diff --git a/crates/compiler/roc_target/Cargo.toml b/crates/compiler/roc_target/Cargo.toml index 76325d052b..e75e309667 100644 --- a/crates/compiler/roc_target/Cargo.toml +++ b/crates/compiler/roc_target/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "roc_target" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Provides types and helpers for compiler targets such as default_x86_64." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -target-lexicon = "0.12.3" -strum = "0.24.0" -strum_macros = "0.24" +strum.workspace = true +strum_macros.workspace = true +target-lexicon.workspace = true diff --git a/crates/compiler/roc_target/src/lib.rs b/crates/compiler/roc_target/src/lib.rs index f7c3d0426d..a98b40116a 100644 --- a/crates/compiler/roc_target/src/lib.rs +++ b/crates/compiler/roc_target/src/lib.rs @@ -150,3 +150,57 @@ impl From for Architecture { } } } + +pub const WASM_TARGET_STR: &str = "wasm32"; +pub const LINUX_X86_64_TARGET_STR: &str = "linux-x86_64"; +pub const LINUX_ARM64_TARGET_STR: &str = "linux-arm64"; +pub const MACOS_ARM64_TARGET_STR: &str = "macos-arm64"; +pub const MACOS_X86_64_TARGET_STR: &str = "macos-x86_64"; +pub const WINDOWS_X86_64_TARGET_STR: &str = "windows-x86_64"; +pub const WINDOWS_X86_32_TARGET_STR: &str = "windows-x86_32"; +pub const WIDNOWS_ARM64_TARGET_STR: &str = "windows-arm64"; + +pub fn get_target_triple_str(target: &target_lexicon::Triple) -> Option<&'static str> { + match target { + target_lexicon::Triple { + architecture: target_lexicon::Architecture::Wasm32, + .. + } => Some(WASM_TARGET_STR), + target_lexicon::Triple { + operating_system: target_lexicon::OperatingSystem::Linux, + architecture: target_lexicon::Architecture::X86_64, + .. + } => Some(LINUX_X86_64_TARGET_STR), + target_lexicon::Triple { + operating_system: target_lexicon::OperatingSystem::Linux, + architecture: target_lexicon::Architecture::Aarch64(_), + .. + } => Some(LINUX_ARM64_TARGET_STR), + target_lexicon::Triple { + operating_system: target_lexicon::OperatingSystem::Darwin, + architecture: target_lexicon::Architecture::Aarch64(_), + .. + } => Some(MACOS_ARM64_TARGET_STR), + target_lexicon::Triple { + operating_system: target_lexicon::OperatingSystem::Darwin, + architecture: target_lexicon::Architecture::X86_64, + .. + } => Some(MACOS_X86_64_TARGET_STR), + target_lexicon::Triple { + operating_system: target_lexicon::OperatingSystem::Windows, + architecture: target_lexicon::Architecture::X86_64, + .. + } => Some(WINDOWS_X86_64_TARGET_STR), + target_lexicon::Triple { + operating_system: target_lexicon::OperatingSystem::Windows, + architecture: target_lexicon::Architecture::X86_32(_), + .. + } => Some(WINDOWS_X86_32_TARGET_STR), + target_lexicon::Triple { + operating_system: target_lexicon::OperatingSystem::Windows, + architecture: target_lexicon::Architecture::Aarch64(_), + .. + } => Some(WIDNOWS_ARM64_TARGET_STR), + _ => None, + } +} diff --git a/crates/compiler/serialize/Cargo.toml b/crates/compiler/serialize/Cargo.toml index 221013e628..9431277b53 100644 --- a/crates/compiler/serialize/Cargo.toml +++ b/crates/compiler/serialize/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "roc_serialize" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Provides helpers for serializing and deserializing to/from bytes." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_collections = { path = "../collections" } diff --git a/crates/compiler/solve/Cargo.toml b/crates/compiler/solve/Cargo.toml index 4d1627030b..852b0eac7b 100644 --- a/crates/compiler/solve/Cargo.toml +++ b/crates/compiler/solve/Cargo.toml @@ -1,44 +1,45 @@ [package] name = "roc_solve" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "The entry point of Roc's type inference system. Implements type inference and specialization of abilities." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] +roc_can = { path = "../can" } roc_collections = { path = "../collections" } +roc_debug_flags = { path = "../debug_flags" } +roc_derive = { path = "../derive" } +roc_derive_key = { path = "../derive_key" } roc_error_macros = { path = "../../error_macros" } roc_exhaustive = { path = "../exhaustive" } -roc_packaging = { path = "../../packaging" } -roc_region = { path = "../region" } roc_module = { path = "../module" } -roc_types = { path = "../types" } -roc_can = { path = "../can" } -roc_derive_key = { path = "../derive_key" } -roc_derive = { path = "../derive" } +roc_packaging = { path = "../../packaging" } roc_problem = { path = "../problem" } +roc_region = { path = "../region" } roc_solve_problem = { path = "../solve_problem" } +roc_types = { path = "../types" } roc_unify = { path = "../unify" } -roc_debug_flags = { path = "../debug_flags" } arrayvec.workspace = true bumpalo.workspace = true [dev-dependencies] -roc_load = { path = "../load" } roc_builtins = { path = "../builtins" } -roc_problem = { path = "../problem" } +roc_derive = { path = "../derive", features = ["debug-derived-symbols"] } +roc_load = { path = "../load" } roc_parse = { path = "../parse" } +roc_problem = { path = "../problem" } +roc_reporting = { path = "../../reporting" } roc_solve = { path = "../solve" } roc_target = { path = "../roc_target" } -roc_reporting = { path = "../../reporting" } -roc_derive = { path = "../derive", features = ["debug-derived-symbols"] } -pretty_assertions.workspace = true -indoc.workspace = true -tempfile.workspace = true bumpalo.workspace = true -regex.workspace = true -lazy_static.workspace = true +indoc.workspace = true insta.workspace = true +lazy_static.workspace = true +pretty_assertions.workspace = true +regex.workspace = true +tempfile.workspace = true diff --git a/crates/compiler/solve/src/ability.rs b/crates/compiler/solve/src/ability.rs index 50c3857ab0..47d0ba15a3 100644 --- a/crates/compiler/solve/src/ability.rs +++ b/crates/compiler/solve/src/ability.rs @@ -4,12 +4,13 @@ use roc_collections::{VecMap, VecSet}; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::ROC_PRINT_UNDERIVABLE; +use roc_derive_key::{DeriveError, Derived}; use roc_error_macros::internal_error; -use roc_module::symbol::Symbol; +use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_solve_problem::{ - NotDerivableContext, NotDerivableDecode, NotDerivableEq, TypeError, UnderivableReason, - Unfulfilled, + NotDerivableContext, NotDerivableDecode, NotDerivableEncode, NotDerivableEq, TypeError, + UnderivableReason, Unfulfilled, }; use roc_types::num::NumericRange; use roc_types::subs::{ @@ -369,8 +370,21 @@ impl ObligationCache { } let ImplKey { opaque, ability } = impl_key; + let has_declared_impl = abilities_store.has_declared_implementation(opaque, ability); + // Some builtins, like Float32 and Bool, would have a cyclic dependency on Encode/Decode/etc. + // if their Roc implementations explicitly defined some abilities they support. + let builtin_opaque_impl_ok = || match ability { + DeriveEncoding::ABILITY => DeriveEncoding::is_derivable_builtin_opaque(opaque), + DeriveDecoding::ABILITY => DeriveDecoding::is_derivable_builtin_opaque(opaque), + DeriveEq::ABILITY => DeriveEq::is_derivable_builtin_opaque(opaque), + DeriveHash::ABILITY => DeriveHash::is_derivable_builtin_opaque(opaque), + _ => false, + }; + + let has_declared_impl = has_declared_impl || builtin_opaque_impl_ok(); + let obligation_result = if !has_declared_impl { Err(Unfulfilled::OpaqueDoesNotImplement { typ: opaque, @@ -451,9 +465,9 @@ impl ObligationCache { #[inline(always)] #[rustfmt::skip] -fn is_builtin_int_alias(symbol: Symbol) -> bool { +fn is_builtin_fixed_int_alias(symbol: Symbol) -> bool { matches!(symbol, - Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 + | Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 | Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 | Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 | Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 @@ -463,10 +477,14 @@ fn is_builtin_int_alias(symbol: Symbol) -> bool { | Symbol::NUM_I32 | Symbol::NUM_SIGNED32 | Symbol::NUM_I64 | Symbol::NUM_SIGNED64 | Symbol::NUM_I128 | Symbol::NUM_SIGNED128 - | Symbol::NUM_NAT | Symbol::NUM_NATURAL ) } +#[inline(always)] +fn is_builtin_nat_alias(symbol: Symbol) -> bool { + matches!(symbol, Symbol::NUM_NAT | Symbol::NUM_NATURAL) +} + #[inline(always)] #[rustfmt::skip] fn is_builtin_float_alias(symbol: Symbol) -> bool { @@ -477,17 +495,21 @@ fn is_builtin_float_alias(symbol: Symbol) -> bool { } #[inline(always)] -#[rustfmt::skip] fn is_builtin_dec_alias(symbol: Symbol) -> bool { - matches!(symbol, - | Symbol::NUM_DEC | Symbol::NUM_DECIMAL, - ) + matches!(symbol, Symbol::NUM_DEC | Symbol::NUM_DECIMAL,) } #[inline(always)] -#[rustfmt::skip] fn is_builtin_number_alias(symbol: Symbol) -> bool { - is_builtin_int_alias(symbol) || is_builtin_float_alias(symbol) || is_builtin_dec_alias(symbol) + is_builtin_fixed_int_alias(symbol) + || is_builtin_nat_alias(symbol) + || is_builtin_float_alias(symbol) + || is_builtin_dec_alias(symbol) +} + +#[inline(always)] +fn is_builtin_bool_alias(symbol: Symbol) -> bool { + matches!(symbol, Symbol::BOOL_BOOL) } struct NotDerivable { @@ -826,7 +848,8 @@ impl DerivableVisitor for DeriveEncoding { #[inline(always)] fn is_derivable_builtin_opaque(symbol: Symbol) -> bool { - is_builtin_number_alias(symbol) + (is_builtin_number_alias(symbol) && !is_builtin_nat_alias(symbol)) + || is_builtin_bool_alias(symbol) } #[inline(always)] @@ -858,6 +881,15 @@ impl DerivableVisitor for DeriveEncoding { Ok(Descend(true)) } + #[inline(always)] + fn visit_tuple( + _subs: &Subs, + _var: Variable, + _elems: TupleElems, + ) -> Result { + Ok(Descend(true)) + } + #[inline(always)] fn visit_tag_union(_var: Variable) -> Result { Ok(Descend(true)) @@ -884,9 +916,16 @@ impl DerivableVisitor for DeriveEncoding { } #[inline(always)] - fn visit_alias(_var: Variable, symbol: Symbol) -> Result { + fn visit_alias(var: Variable, symbol: Symbol) -> Result { if is_builtin_number_alias(symbol) { - Ok(Descend(false)) + if is_builtin_nat_alias(symbol) { + Err(NotDerivable { + var, + context: NotDerivableContext::Encode(NotDerivableEncode::Nat), + }) + } else { + Ok(Descend(false)) + } } else { Ok(Descend(true)) } @@ -914,7 +953,8 @@ impl DerivableVisitor for DeriveDecoding { #[inline(always)] fn is_derivable_builtin_opaque(symbol: Symbol) -> bool { - is_builtin_number_alias(symbol) + (is_builtin_number_alias(symbol) && !is_builtin_nat_alias(symbol)) + || is_builtin_bool_alias(symbol) } #[inline(always)] @@ -957,6 +997,15 @@ impl DerivableVisitor for DeriveDecoding { Ok(Descend(true)) } + #[inline(always)] + fn visit_tuple( + _subs: &Subs, + _var: Variable, + _elems: TupleElems, + ) -> Result { + Ok(Descend(true)) + } + #[inline(always)] fn visit_tag_union(_var: Variable) -> Result { Ok(Descend(true)) @@ -983,9 +1032,16 @@ impl DerivableVisitor for DeriveDecoding { } #[inline(always)] - fn visit_alias(_var: Variable, symbol: Symbol) -> Result { + fn visit_alias(var: Variable, symbol: Symbol) -> Result { if is_builtin_number_alias(symbol) { - Ok(Descend(false)) + if is_builtin_nat_alias(symbol) { + Err(NotDerivable { + var, + context: NotDerivableContext::Decode(NotDerivableDecode::Nat), + }) + } else { + Ok(Descend(false)) + } } else { Ok(Descend(true)) } @@ -1013,7 +1069,7 @@ impl DerivableVisitor for DeriveHash { #[inline(always)] fn is_derivable_builtin_opaque(symbol: Symbol) -> bool { - is_builtin_number_alias(symbol) + is_builtin_number_alias(symbol) || is_builtin_bool_alias(symbol) } #[inline(always)] @@ -1056,6 +1112,15 @@ impl DerivableVisitor for DeriveHash { Ok(Descend(true)) } + #[inline(always)] + fn visit_tuple( + _subs: &Subs, + _var: Variable, + _elems: TupleElems, + ) -> Result { + Ok(Descend(true)) + } + #[inline(always)] fn visit_tag_union(_var: Variable) -> Result { Ok(Descend(true)) @@ -1112,7 +1177,10 @@ impl DerivableVisitor for DeriveEq { #[inline(always)] fn is_derivable_builtin_opaque(symbol: Symbol) -> bool { - is_builtin_int_alias(symbol) || is_builtin_dec_alias(symbol) + is_builtin_fixed_int_alias(symbol) + || is_builtin_nat_alias(symbol) + || is_builtin_dec_alias(symbol) + || is_builtin_bool_alias(symbol) } #[inline(always)] @@ -1159,6 +1227,15 @@ impl DerivableVisitor for DeriveEq { Ok(Descend(true)) } + #[inline(always)] + fn visit_tuple( + _subs: &Subs, + _var: Variable, + _elems: TupleElems, + ) -> Result { + Ok(Descend(true)) + } + #[inline(always)] fn visit_tag_union(_var: Variable) -> Result { Ok(Descend(true)) @@ -1257,12 +1334,12 @@ pub fn type_implementing_specialization( } /// Result of trying to resolve an ability specialization. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Resolved { /// A user-defined specialization should be used. Specialization(Symbol), - /// A specialization must be generated for the given type variable. - NeedsGenerated(Variable), + /// A specialization must be generated with the given derive key. + Derive(Derived), } /// An [`AbilityResolver`] is a shell of an abilities store that answers questions needed for @@ -1305,12 +1382,32 @@ impl AbilityResolver for AbilitiesStore { } } +/// Whether this a module whose types' ability implementations should be checked via derive_key, +/// because they do not explicitly list ability implementations due to circular dependencies. +#[inline] +pub(crate) fn builtin_module_with_unlisted_ability_impl(module_id: ModuleId) -> bool { + matches!(module_id, ModuleId::NUM | ModuleId::BOOL) +} + +#[derive(Debug)] +pub enum ResolveError { + NonDerivableAbility(Symbol), + DeriveError(DeriveError), + NoTypeImplementingSpecialization, +} + +impl From for ResolveError { + fn from(e: DeriveError) -> Self { + Self::DeriveError(e) + } +} + pub fn resolve_ability_specialization( subs: &mut Subs, resolver: &R, ability_member: Symbol, specialization_var: Variable, -) -> Option { +) -> Result { use roc_unify::unify::{unify, Mode}; let (parent_ability, signature_var) = resolver @@ -1334,29 +1431,51 @@ pub fn resolve_ability_specialization( subs.rollback_to(snapshot); - let obligated = type_implementing_specialization(&must_implement_ability, parent_ability)?; + use ResolveError::*; + + let obligated = type_implementing_specialization(&must_implement_ability, parent_ability) + .ok_or(NoTypeImplementingSpecialization)?; let resolved = match obligated { Obligated::Opaque(symbol) => { - let impl_key = roc_can::abilities::ImplKey { - opaque: symbol, - ability_member, - }; + if builtin_module_with_unlisted_ability_impl(symbol.module_id()) { + let derive_key = roc_derive_key::Derived::builtin_with_builtin_symbol( + ability_member.try_into().map_err(NonDerivableAbility)?, + symbol, + )?; - match resolver.get_implementation(impl_key)? { - roc_types::types::MemberImpl::Impl(spec_symbol) => { - Resolved::Specialization(spec_symbol) + Resolved::Derive(derive_key) + } else { + let impl_key = roc_can::abilities::ImplKey { + opaque: symbol, + ability_member, + }; + + match resolver + .get_implementation(impl_key) + .ok_or(NoTypeImplementingSpecialization)? + { + roc_types::types::MemberImpl::Impl(spec_symbol) => { + Resolved::Specialization(spec_symbol) + } + // TODO this is not correct. We can replace `Resolved` with `MemberImpl` entirely, + // which will make this simpler. + roc_types::types::MemberImpl::Error => { + Resolved::Specialization(Symbol::UNDERSCORE) + } } - // TODO this is not correct. We can replace `Resolved` with `MemberImpl` entirely, - // which will make this simpler. - roc_types::types::MemberImpl::Error => Resolved::Specialization(Symbol::UNDERSCORE), } } Obligated::Adhoc(variable) => { - // TODO: more rules need to be validated here, like is this a builtin ability? - Resolved::NeedsGenerated(variable) + let derive_key = roc_derive_key::Derived::builtin( + ability_member.try_into().map_err(NonDerivableAbility)?, + subs, + variable, + )?; + + Resolved::Derive(derive_key) } }; - Some(resolved) + Ok(resolved) } diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index 89097cf9d2..3a607e2494 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -1718,14 +1718,12 @@ fn solve( member, specialization_id, }) => { - if let Some(Resolved::Specialization(specialization)) = - resolve_ability_specialization( - subs, - abilities_store, - member, - specialization_variable, - ) - { + if let Ok(Resolved::Specialization(specialization)) = resolve_ability_specialization( + subs, + abilities_store, + member, + specialization_variable, + ) { abilities_store.insert_resolved(specialization_id, specialization); } @@ -1847,6 +1845,11 @@ fn open_tag_union(subs: &mut Subs, var: Variable) { stack.extend(subs.get_subs_slice(fields.variables())); } + Structure(Tuple(elems, _)) => { + // Open up all nested tag unions. + stack.extend(subs.get_subs_slice(elems.variables())); + } + Structure(Apply(Symbol::LIST_LIST, args)) => { // Open up nested tag unions. stack.extend(subs.get_subs_slice(args)); @@ -3918,7 +3921,23 @@ fn adjust_rank_content( Alias(_, args, real_var, _) => { let mut rank = Rank::toplevel(); - for var_index in args.all_variables() { + // Avoid visiting lambda set variables stored in the type variables of the alias + // independently. + // + // Why? Lambda set variables on the alias are not truly type arguments to the alias, + // and instead are links to the lambda sets that appear in functions under the real + // type of the alias. If their ranks are adjusted independently, we end up looking at + // function types "inside-out" - when the whole point of rank-adjustment is to look + // from the outside-in to determine at what rank a type lies! + // + // So, just wait to adjust their ranks until we visit the function types that contain + // them. If they should be generalized (or pulled to a lower rank) that will happen + // then; otherwise, we risk generalizing a lambda set too early, when its enclosing + // function type should not be. + let adjustable_variables = + (args.type_variables().into_iter()).chain(args.infer_ext_in_output_variables()); + + for var_index in adjustable_variables { let var = subs[var_index]; rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); } diff --git a/crates/compiler/solve/src/specialize.rs b/crates/compiler/solve/src/specialize.rs index 53db854639..7ba2fe5799 100644 --- a/crates/compiler/solve/src/specialize.rs +++ b/crates/compiler/solve/src/specialize.rs @@ -24,7 +24,10 @@ use roc_types::{ }; use roc_unify::unify::{unify, Env as UEnv, Mode, MustImplementConstraints}; -use crate::solve::{deep_copy_var_in, introduce, Pools}; +use crate::{ + ability::builtin_module_with_unlisted_ability_impl, + solve::{deep_copy_var_in, introduce, Pools}, +}; /// What phase in the compiler is reaching out to specialize lambda sets? /// This is important to distinguish subtle differences in the behavior of the solving algorithm. @@ -625,7 +628,9 @@ fn make_specialization_decision( use Content::*; use SpecializationTypeKey::*; match subs.get_content_without_compacting(var) { - Alias(opaque, _, _, AliasKind::Opaque) if opaque.module_id() != ModuleId::NUM => { + Alias(opaque, _, _, AliasKind::Opaque) + if !builtin_module_with_unlisted_ability_impl(opaque.module_id()) => + { if P::IS_LATE { SpecializeDecision::Specialize(Opaque(*opaque)) } else { @@ -743,7 +748,7 @@ fn get_specialization_lambda_set_ambient_function( let specialized_lambda_set = *specialization .specialization_lambda_sets .get(&lset_region) - .expect("lambda set region not resolved"); + .unwrap_or_else(|| panic!("lambda set region not resolved: {:?}", (spec_symbol, specialization))); Ok(specialized_lambda_set) } MemberImpl::Error => todo_abilities!(), diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index ea09b3f2fb..3727f5e900 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -1497,6 +1497,20 @@ mod solve_expr { infer_eq(".200", "( ... 200 omitted, a )* -> a"); } + #[test] + fn tuple_accessor_generalization() { + infer_eq( + indoc!( + r#" + get0 = .0 + + { a: get0 (1, 2), b: get0 ("a", "b", "c") } + "# + ), + "{ a : Num *, b : Str }", + ); + } + #[test] fn record_arg() { infer_eq("\\rec -> rec.x", "{ x : a }* -> a"); @@ -7791,8 +7805,8 @@ mod solve_expr { ), @r###" const : Str -[[const(2)]]-> (Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str) - compose : (Str -a-> Str), (Str -[[]]-> Str) -[[compose(1)]]-> (Str -a-> Str) - \c1, c2 -> compose c1 c2 : (Str -a-> Str), (Str -[[]]-> Str) -[[11]]-> (Str -a-> Str) + compose : (Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str), (Str -[[]]-> Str) -[[compose(1)]]-> (Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str) + \c1, c2 -> compose c1 c2 : (Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str), (Str -[[]]-> Str) -[[11]]-> (Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str) res : Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str res : Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str "### @@ -8316,7 +8330,7 @@ mod solve_expr { "# ), @r###" - job : { lst : List [Bar, FromG a] } -[[job(0)]]-> [G { lst : List [Bar, FromG a] }] as a + job : { lst : List [Bar, FromG ([G { lst : List [Bar, FromG a] }] as a)] } -[[job(0)]]-> [G { lst : List [Bar, FromG a] }] as a config : { lst : List [Bar, FromG ([G { lst : List [Bar, FromG a] }] as a)] } G config : [G { lst : List [Bar, FromG a] }] as a "### @@ -8359,7 +8373,23 @@ mod solve_expr { # ^^^^^^^^^ "# ), - @"Hash#Hash.hash(1) : a, I64 -[[Hash.hashI64(12)]]-> a | a has Hasher" + @"Hash#Hash.hash(1) : a, I64 -[[Hash.hashI64(13)]]-> a | a has Hasher" + ) + } + + #[test] + fn choose_bool_for_hash() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + \h -> Hash.hash h Bool.true + # ^^^^^^^^^ + "# + ), + @"Hash#Hash.hash(1) : a, Bool -[[Hash.hashBool(9)]]-> a | a has Hasher" ) } @@ -8594,12 +8624,11 @@ mod solve_expr { v2 = "" - apply = \fnParser, valParser-> \{} -[9]-> (fnParser {}) valParser + apply = \fnParser, valParser -> \{} -[9]-> (fnParser {}) valParser - map = \simpleParser, transform-> apply \{} -[12]-> transform simpleParser + map = \simpleParser, transform -> apply \{} -[12]-> transform simpleParser - parseInput = - \{}-> + parseInput = \{} -> when [ map v1 \{} -[13]-> "", map v2 \s -[14]-> s, @@ -8764,4 +8793,80 @@ mod solve_expr { @"main : List w_a" ); } + + #[test] + fn recursive_closure_with_transiently_used_capture() { + infer_queries!( + indoc!( + r#" + app "test" provides [f] to "./platform" + + thenDo = \x, callback -> + callback x + + f = \{} -> + code = 10u16 + + bf = \{} -> + #^^{-1} + thenDo code \_ -> bf {} + # ^^^^^^^^^^^ + + bf {} + "# + ), + @r###" + bf : {} -[[bf(5) U16]]-> * + \_ -> bf {} : U16 -[[6 U16]]-> * + "### + ); + } + + #[test] + fn derive_decoder_for_bool() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main : Decoder Bool _ + main = Decode.custom \bytes, fmt -> + Decode.decodeWith bytes Decode.decoder fmt + # ^^^^^^^^^^^^^^ + "# + ), + @"Decoding#Decode.decoder(4) : List U8, fmt -[[] + fmt:Decode.bool(19):1]-> { rest : List U8, result : [Err [TooShort], Ok [False, True]] } | fmt has DecoderFormatting" + print_only_under_alias: true + ); + } + + #[test] + fn derive_to_encoder_for_bool() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = Encode.toEncoder Bool.true + # ^^^^^^^^^^^^^^^^ + "# + ), + @"Encoding#Encode.toEncoder(2) : Bool -[[] + fmt:Encode.bool(17):1]-> Encoder fmt | fmt has EncoderFormatting" + ); + } + + #[test] + fn derive_eq_for_bool() { + infer_queries!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = Bool.isEq Bool.true Bool.false + # ^^^^^^^^^ + "# + ), + @"Eq#Bool.isEq(9) : Bool, Bool -[[Bool.structuralEq(11)]]-> Bool" + ); + } } diff --git a/crates/compiler/solve_problem/Cargo.toml b/crates/compiler/solve_problem/Cargo.toml index 73148f7c72..0c33244b60 100644 --- a/crates/compiler/solve_problem/Cargo.toml +++ b/crates/compiler/solve_problem/Cargo.toml @@ -1,16 +1,17 @@ [package] name = "roc_solve_problem" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Provides types to describe problems that can occur during solving." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -roc_collections = { path = "../collections" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_types = { path = "../types" } roc_can = { path = "../can" } -roc_problem = { path = "../problem" } +roc_collections = { path = "../collections" } roc_exhaustive = { path = "../exhaustive" } +roc_module = { path = "../module" } +roc_problem = { path = "../problem" } +roc_region = { path = "../region" } +roc_types = { path = "../types" } diff --git a/crates/compiler/solve_problem/src/lib.rs b/crates/compiler/solve_problem/src/lib.rs index d975d0ef64..b62f459dec 100644 --- a/crates/compiler/solve_problem/src/lib.rs +++ b/crates/compiler/solve_problem/src/lib.rs @@ -87,12 +87,19 @@ pub enum NotDerivableContext { Function, UnboundVar, Opaque(Symbol), + Encode(NotDerivableEncode), Decode(NotDerivableDecode), Eq(NotDerivableEq), } +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum NotDerivableEncode { + Nat, +} + #[derive(PartialEq, Eq, Debug, Clone)] pub enum NotDerivableDecode { + Nat, OptionalRecordField(Lowercase), } diff --git a/crates/compiler/str/Cargo.toml b/crates/compiler/str/Cargo.toml deleted file mode 100644 index 688048376a..0000000000 --- a/crates/compiler/str/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "roc_str" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" -description = "Provides a Roc Str with reference-counting so that it may be mutated in-place." - -[dependencies] -roc_collections = { path = "../collections" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_types = { path = "../types" } -roc_can = { path = "../can" } -roc_unify = { path = "../unify" } -roc_problem = { path = "../problem" } -bumpalo = { version = "3.6.1", features = ["collections"] } - -[dev-dependencies] -roc_constrain = { path = "../constrain" } -roc_builtins = { path = "../builtins" } -roc_parse = { path = "../parse" } -roc_solve = { path = "../solve" } -pretty_assertions = "0.5.1" -indoc = "0.3.3" -quickcheck = "0.8" -quickcheck_macros = "0.8" diff --git a/crates/compiler/str/README.md b/crates/compiler/str/README.md deleted file mode 100644 index 276278bb58..0000000000 --- a/crates/compiler/str/README.md +++ /dev/null @@ -1,209 +0,0 @@ -# `Str` - -This is the in-memory representation for `Str`. To explain how `Str` is laid out in memory, it's helpful to start with how `List` is laid out. - -## Empty list - -An empty `List Str` is essentially this Rust type with all 0s in memory: - -```rust -struct List { - pointer: *Str, // pointers are the same size as `usize` - length: usize -} -``` - -On a 64-bit system, this `struct` would take up 16B in memory. On a 32-bit system, it would take up 8B. - -Here's what the fields mean: - -- `pointer` is the memory address of the heap-allocated memory containing the `Bool` elements. For an empty list, the pointer is null (that is, 0). -- `length` is the number of `Bool` elements in the list. For an empty list, this is also 0. - -## Nonempty list - -Now let's say we define a `List Str` with two elements in it, like so: `["foo", "bar"]`. - -First we'd have the `struct` above, with both `length` and `capacity` set to 2. Then, we'd have some memory allocated on the heap, and `pointer` would store that memory's address. - -Here's how that heap memory would be laid out on a 64-bit system. It's a total of 48 bytes. - -```text -|------16B------|------16B------|---8B---|---8B---| - string #1 string #2 refcount unused -``` - -Just like how `List` is a `struct` that takes up `2 * usize` bytes in memory, `Str` takes up the same amount of memory - namely, 16B on a 64-bit system. That's why each of the two strings take up 16B of this heap-allocated memory. (Those structs may also point to other heap memory, but they could also be empty strings! Either way we just store the structs in the list, which take up 16B.) - -We'll get to what the refcount is for shortly, but first let's talk about the memory layout. The refcount is a `usize` integer, so 8B on our 64-bit system. Why is there 8B of unused memory after it? - -This is because of memory alignment. Whenever a system loads some memory from a memory address, it's much more efficient if the address is a multiple of the number of bytes it wants to get. So if we want to load a 16B string struct, we want its address to be a multiple of 16. - -When we're allocating memory on the heap, the way we specify what alignment we want is to say how big each element is, and how many of them we want. In this case, we say we want 16B elements, and we want 3 of them. Then we use the first 16B slot to store the 8B refcount, and the 8B after it are unused. - -This is memory-inefficient, but it's the price we pay for having all the 16B strings stored in addresses that are multiples of 16. It'd be worse for performance if we tried to pack everything tightly, so we accept the memory inefficiency as a cost of achieving better overall execution speed. - -> Note: if we happened to have 8B elements instead of 16B elements, the alignment would be 8 anyway and we'd have no unused memory. - -## Reference counting - -Let's go back to the refcount - short for "reference count." - -The refcount is a `usize` integer which counts how many times this `List` has been shared. For example, if we named this list `myList` and then wrote `[myList, myList, myList]` then we'd increment that refcount 3 times because `myList` is now being shared three more times. - -If we were to later call `List.pop` on that list, and the result was an in-place mutation that removed one of the `myList` entries, we'd decrement the refcount. If we did that again and again until the refcount got all the way down to 0, meaning nothing is using it anymore, then we'd deallocate these 48B of heap memory because nobody is using them anymore. - -In some cases, the compiler can detect that no reference counting is necessary. In that scenario, it doesn't bother allocating extra space for the refcount; instead, it inserts an instruction to allocate the memory at the appropriate place, another to free it later, and that's it. - -## Pointing to the first element - -The fact that the reference count may or may not be present could creat a tricky situation for some `List` operations. - -For example, should `List.get 0` return the first 16B of the heap-allocated bytes, or the second 16B? If there's a reference count in the first 16B, it should return the second 16B. If there's no refcount, it should return the first 16B. - -To solve this, the pointer in the List struct *always* points to the first element in the list. That means to access the reference count, it does negative pointer arithmetic to get the address at 16B *preceding* the memory address it has stored in its pointer field. - -## Growing lists - -If uniqueness typing tells us that a list is Unique, we know two things about it: - -1. It doesn't need a refcount, because nothing else ever references it. -2. It can be mutated in-place. - -One of the in-place mutations that can happen to a list is that its length can increase. For example, if I call `List.append list1 list2`, and `list1` is unique, then we'll attempt to append `list2`'s contents in-place into `list1`. - -Calling `List.append` on a Shared list results in allocating a new chunk of heap memory large enough to hold both lists (with a fresh refcount, since nothing is referencing the new memory yet), then copying the contents of both lists into the new memory, and finally decrementing the refcount of the old memory. - -Calling `List.append` on a Unique list can potentially be done in-place instead. - -First, `List.append` repurposes the `usize` slot normally used to store refcount, and stores a `capacity` counter in there instead of a refcount. (After all, unique lists don't need to be refcounted.) A list's capacity refers to how many elements the list *can* hold given the memory it has allocated to it, which is always guaranteed to be at least as many as its length. - -When calling `List.append list1 list2` on a unique `list1`, first we'll check to see if `list1.capacity <= list1.length + list2.length`. If it is, then we can copy in the new values without needing to allocate more memory for `list1`. - -If there is not enough capacity to fit both lists, then we can try to call [`realloc`](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/realloc?view=vs-2019) to hopefully extend the size of our allocated memory. If `realloc` succeeds (meaning there happened to be enough free memory right after our current allocation), then we update `capacity` to reflect the new amount of space, and move on. - -> **Note:** The reason we store capacity right after the last element in the list is because of how memory cache lines work. Whenever we need to access `capacity`, it's because we're about to increase the length of the list, which means that we will most certainly be writing to the memory location right after its last element. That in turn means that we'll need to have that memory location in cache, which in turn means that looking up the `capacity` there is guaranteed not to cause a cache miss. (It's possible that writing the new capacity value to a later address could cause a cache miss, but this strategy minimizes the chance of that happening.) An alternate design would be where we store the capacity right before the first element in the list. In that design we wouldn't have to re-write the capacity value at the end of the list every time we grew it, but we'd be much more likely to incur more cache misses that way - because we're working at the end of the list, not at the beginning. Cache misses are many times more expensive than an extra write to a memory address that's in cache already, not to mention the potential extra load instruction to add the length to the memory address of the first element (instead of subtracting 1 from that address), so we optimize for minimizing the highly expensive cache misses by always paying a tiny additional cost when increasing the length of the list, as well as a potential even tinier cost (zero, if the length already happens to be in a register) when looking up its capacity or refcount. - -If `realloc` fails, then we have to fall back on the same "allocate new memory and copy everything" strategy that we do with shared lists. - -When you have a way to anticipate that a list will want to grow incrementally to a certain size, you can avoid such extra allocations by using `List.reserve` to guarantee more capacity up front. (`List.reserve` works like Rust's [`Vec::reserve`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.reserve).) - -> **Note:** Calling `List.reserve 0 myList` will have no effect on a Unique list, but on a Shared list it will clone `myList` and return a Unique one. If you want to do a bunch of in-place mutations on a list, but it's currently Shared, calling `List.reserve 0` on it to get a Unique clone could actually be an effective performance optimization! - -## Capacity and alignment - -Some lists may end up beginning with excess capacity due to memory alignment requirements. Since the refcount is `usize`, all lists need a minimum of that alignment. For example, on a 64-bit system, a `List Bool` has an alignment of 8B even though bools can fit in 1B. - -This means the list `[True, True, False]` would have a memory layout like this): - -```text -|--------------8B--------------|--1B--|--1B--|--1B--|-----5B-----| - either refcount or capacity bool1 bool2 bool3 unused -``` - -As such, if this list is Unique, it would start out with a length of 3 and a capacity of 8. - -Since each bool value is a byte, it's okay for them to be packed side-by-side even though the overall alignment of the list elements is 8. This is fine because each of their individual memory addresses will end up being a multiple of their size in bytes. - -Note that unlike in the `List Str` example before, there wouldn't be any unused memory between the refcount (or capacity, depending on whether the list was shared or unique) and the first element in the list. That will always be the case when the size of the refcount is no bigger than the alignment of the list's elements. - -## Distinguishing between refcount and capacity in the host - -If I'm a platform author, and I receive a `List` from the application, it's important that I be able to tell whether I'm dealing with a refcount or a capacity. (The uniqueness type information will have been erased by this time, because some applications will return a Unique list while others return a Shared list, so I need to be able to tell using runtime information only which is which.) This way, I can know to either increment the refcount, or to feel free to mutate it in-place using the capacity value. - -We use a very simple system to distinguish the two: if the high bit in that `usize` value is 0, then it's capacity. If it's 1, then it's a refcount. - -This has a couple of implications: - -- `capacity` actually has a maximum of `isize::MAX`, not `usize::MAX` - because if its high bit flips to 1, then now suddenly it's considered a refcount by the host. As it happens, capacity's maximum value is naturally `isize::MAX` anyway, so there's no downside here. -- `refcount` actually begins at `isize::MIN` and increments towards 0, rather than beginning at 0 and incrementing towards a larger number. When a decrement instruction is executed and the refcount is `isize::MIN`, it gets freed instead. Since all refcounts do is count up and down, and they only ever compare the refcount to a fixed constant, there's no real performance cost to comparing to `isize::MIN` instead of to 0. So this has no significant downside either. - -Using this representation, hosts can trivially distinguish any list they receive as being either refcounted or having a capacity value, without any runtime cost in either the refcounted case or the capacity case. - -### Saturated reference count - -What happens if the reference count overflows? As in, we try to reference the same list more than `isize` times? - -In this situation, the reference count becomes unreliable. Suppose we try to increment it 3 more times after it's already been incremented `isize` times, and since we can't store any further numbers without flipping the high bit from 1 to 0 (meaning it will become a capacity value instead of a refcount), we leave it at -1. If we later decrement it `isize` times, we'll be down to `isize::MIN` and will free the memory, even though 3 things are still referencing that memory! - -This would be a total disaster, so what we do instead is that we decide to leak the memory. Once the reference count hits -1, we neither increment nor decrement it ever again, which in turn means we will never free it. So -1 is a special reference count meaning "this memory has become unreclaimable, and must never be freed." - -This has the downside of being potentially wasteful of the program's memory, but it's less detrimental to user experience than a crash, and it doesn't impact correctness at all. - -## Summary of Lists - -Lists are a `2 * usize` struct which contains a length and a pointer. - -That pointer is a memory address (null in the case of an empty list) which points to the first element in a sequential array of memory. - -If that pointer is shared in multiple places, then there will be a `usize` reference count stored right before the first element of the list. There may be unused memory after the refcount if `usize` is smaller than the alignment of one of the list's elements. - -Refcounts get incremented each time a list gets shared somewhere, and decremented each time that shared value is no longer referenced by anything else (for example, by going out of scope). Once there are no more references, the list's heap memory can be safely freed. If a reference count gets all the way up to `usize`, then it will never be decremented again and the memory will never be freed. - -Whenever a list grows, it will grow in-place if it's Unique and there is enough capacity. (Capacity is stored where a refcount would be in a Shared list.) If there isn't enough capacity - even after trying `realloc` - or if the list is Shared, then instead new heap memory will be allocated, all the necessary elements will get copied into it, and the original list's refcount will be decremented. - -## Strings - -Strings have several things in common with lists: - -- They are a `2 * usize` struct, sometimes with a non-null pointer to some heap memory -- They have a length and a capacity, and they can grow in basically the same way -- They are reference counted in basically the same way - -However, they also have two things going on that lists do not: - -- The Small String Optimization -- Literals stored in read-only memory - -## The Small String Optimization - -In practice, a lot of strings are pretty small. For example, the string `"Richard Feldman"` can be stored in 15 UTF-8 bytes. If we stored that string the same way we store a list, then on a 64-bit system we'd need a 16B struct, which would include a pointer to 24B of heap memory (including the refcount/capacity and one unused byte for alignment). - -That's a total of 48B to store 15B of data, when we could have fit the whole string into the original 16B we needed for the struct, with one byte left over. - -The Small String Optimization is where we store strings directly in the struct, assuming they can fit in there. We reserve one of those bytes to indicate whether this is a Small String or a larger one that actually uses a pointer. - -## String Memory Layout - -How do we tell small strings apart from nonsmall strings? - -We make use of the fact that lengths (for both strings *and* lists) are `usize` values which have a maximum value of `isize::MAX` rather than `usize::MAX`. This is because `List.get` compiles down to an array access operation, and LLVM uses `isize` indices for those because they do signed arithmetic on the pointer in case the caller wants to add a negative number to the address. (We don't want to, as it happens, but that's what the low-level API supports, so we are bound by its limitations.) - -Since the string's length is a `usize` value with a maximum of `isize::MAX`, we can be sure that its most significant bit will always be 0, not 1. (If it were a 1, that would be a negative `isize`!) We can use this fact to use that spare bit as a flag indicating whether the string is small: if that bit is a 1, it's a small string; otherwise, it's a nonsmall string. - -This makes calculating the length of the string a multi-step process: - -1. Get the length field out of the struct. -2. Look at its highest bit. If that bit is 0, return the length as-is. -3. If the bit is 1, then this is a small string, and its length is packed into the highest byte of the `usize` length field we're currently examining. Take that byte and bit shift it by 1 (to drop the `1` flag we used to indicate this is a small string), cast the resulting byte to `usize`, and that's our length. (Actually we bit shift by 4, not 1, because we only need 4 bits to store a length of 0-16, and the leftover 3 bits can potentially be useful in the future.) - -Using this strategy with a [conditional move instruction](https://stackoverflow.com/questions/14131096/why-is-a-conditional-move-not-vulnerable-for-branch-prediction-failure), we can always get the length of a `Str` in 2-3 cheap instructions on a single `usize` value, without any chance of a branch misprediction. - -Thus, the layout of a small string on a 64-bit big-endian architecture would be: - -```text -|-----------usize length field----------|-----------usize pointer field---------| -|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-| - len 'R' 'i' 'c' 'h' 'a' 'r' 'd' ' ' 'F' 'e' 'l' 'd' 'm' 'a' 'n' -``` - -The `len` value here would be the number 15, plus a 1 (to flag that this is a small string) that would always get bit-shifted away. The capacity of a small Unique string is always equal to `2 * usize`, because that's how much you can fit without promoting to a nonsmall string. - -## Endianness - -The preceding memory layout example works on a big-endian architecture, but most CPUs are little-endian. That means the high bit where we want to store the flag (the 0 or 1 -that would make an `isize` either negative or positive) will actually be the `usize`'s last byte rather than its first byte. - -That means we'd have to move swap the order of the struct's length and pointer fields. Here's how the string `"Roc string"` would be stored on a little-endian system: - -```text -|-----------usize pointer field---------|-----------usize length field----------| -|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-|-1B-| - 'R' 'o' 'c' ' ' 's' 't' 'r' 'i' 'n' 'g' 0 0 0 0 0 len -``` - -Here, `len` would have the same format as before (including the extra 1 in the same position, which we'd bit shift away) except that it'd store a length of 10 instead of 15. - -Notice that the leftover bytes are stored as zeroes. This is handy because it means we can convert small Roc strings into C strings (which are 0-terminated) for free as long as they have at least one unused byte. Also notice that `usize pointer field` and `usize length field` have been swapped compared to the preceding example! - -## Storing string literals in read-only memory diff --git a/crates/compiler/str/src/lib.rs b/crates/compiler/str/src/lib.rs deleted file mode 100644 index 17f19c0fe0..0000000000 --- a/crates/compiler/str/src/lib.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Provides `Roc` styled collection [reference counting](https://en.wikipedia.org/wiki/Reference_counting). -//! This means the collection may or may not be safe to mutate in-place, may or -//! may not be reference counted, and may or may not need to be freed when no -//! longer in use. Whether each of these is true for a given collection can be -//! determined by inspecting that collection at runtime. -#![crate_type = "lib"] -#![no_std] - -/// Str does Roc-style collection reference counting, which means the collection -/// may or may not be safe to mutate in-place, may or may not be reference counted, -/// and may or may not need to be freed when no longer in use. Whether each of -/// these is true for a given collection can be determined by inspecting -/// that collection at runtime. -/// -/// Details: -/// -/// 1. If the collection is empty, it does not allocate on the heap. -/// 2. If it is nonempty, its pointer points to the first element of a "backing array" on the heap. -/// 3. There is an extra `isize` right before that backing array (still on the heap) which stores the -/// "flexible reference count" ("flexcount" for short). -/// 4. The flexcount can refer to one of three things, depending on whether it is positive, -/// negative, or zero. -/// 5. If the flexcount is positive, then it's a capacity. The capacity refers to the number of -/// collection elements in the backing array. This collection can be mutated in-place, until it -/// runs out of capacity. At that point, it will need a new backing array. Once it goes out of -/// scope, the backing array should be freed by the system allocator - but free() must be passed -/// a pointer to the flexcount slot, not to element 0 (because the flexcount slot is where the -/// original allocation began). Capacity will always be at least 1, because otherwise we would -/// not have allocated on the heap in the first place. -/// 6. If the flexcount is 0, then this collection resides in readonly memory. That means it cannot -/// be mutated in-place (and attempting to do so will segfault), and it must not be attempted to -/// be freed. It exists in memory forever! -/// 7. If the flexcount is negative, then it is a reference count. Treat the collection as immutable, just like -/// if the flexcount were 0, except free it when there are no more references to it. Instead of the reference count -/// starting at 0 or 1 and incrementing when new references are added, this refcount starts with all bits being 1 (so, isize::MIN) and -/// increments towards 0 when new references are added. When a reference is removed, if all bits are 1, then it should be freed. If so many new references are added that it gets incremented all the way from isize::MAX to 0, then, as is best practice when running out of reference counts, it will leak. (Leaking memory is typically less bad than crashing, and this should essentially never happen.) This happens automatically because when the flexcount is 0, it's assumed that the collection is in readonly memory and should not be freed - which is nice because it means there is no extra conditional required to implement this edge case. -/// 8. If a collection has a refcount of isize::MIN (meaning nothing else references it), it may or may not be safe to convert it to a capacity, -/// depending on whether it contains other refcounted collections. For example, a Str -/// is a collection of all bytes, so if it has a refcount of all 1 bits, it can be safely -/// converted to a capacity (the initial capacity should be equal to the collection's length), -/// after which point it can be safely mutated in-place. However, a refcounted List of Lists with a refcount of isize::MIN will not be safe to convert to a capacity, unless the inner Lists also happen to have refcounts of isize::MIN. This is because mutate-in-place operations like removing an element from a list do not check for refcounts in the elements they remove, which means removing an element from the newly mutable-in-place list would cause memory leaks in its refcounted contents. (They'd have been removed, but their reference counts would not have been adjusted accordingly.) -/// -/// Note that because of these runtime conditionals, modifying and freeing Roc collections are both -/// cheaper operations in generated Roc code than in host code. Since the Roc compiler knows -/// statically whether a collection is refcounted, unique, or readonly, it does not bother with -/// these checks at runtime. A host, however, cannot have that information statically (since it may be different -/// for different applications), and so must check at runtime instead. -struct Str { - bytes: [16, u8]; -} - -#[no_mangle] -pub fn empty_() -> Str { - Str { - bytes : [0; 16] - } -} - -#[no_mangle] -pub fn len_(string: Str) -> usize { - let disc = discriminant(str); - - if disc == 0 { - // It's a - } -} - -#[inline(always)] -fn discriminant(string: &Str) -> u8 { - // cast the first 8 bytes to be u64, return its lsbyte -} diff --git a/crates/compiler/test_derive/Cargo.toml b/crates/compiler/test_derive/Cargo.toml index 35ed004fa0..2c5f5e14a3 100644 --- a/crates/compiler/test_derive/Cargo.toml +++ b/crates/compiler/test_derive/Cargo.toml @@ -1,35 +1,34 @@ [package] name = "test_derive" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Tests Roc's auto-derivers." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [[test]] name = "test_derive" path = "src/tests.rs" [dev-dependencies] -roc_collections = { path = "../collections" } -roc_module = { path = "../module" } roc_builtins = { path = "../builtins" } -roc_load_internal = { path = "../load_internal" } roc_can = { path = "../can" } -roc_derive_key = { path = "../derive_key" } +roc_collections = { path = "../collections" } +roc_constrain = { path = "../constrain" } +roc_debug_flags = { path = "../debug_flags" } roc_derive = { path = "../derive", features = ["debug-derived-symbols", "open-extension-vars"] } +roc_derive_key = { path = "../derive_key" } +roc_load_internal = { path = "../load_internal" } +roc_module = { path = "../module" } +roc_packaging = { path = "../../packaging" } +roc_region = { path = "../region" } +roc_reporting = { path = "../../reporting" } +roc_solve = { path = "../solve" } roc_target = { path = "../roc_target" } roc_types = { path = "../types" } -roc_packaging = { path = "../../packaging" } -roc_reporting = { path = "../../reporting" } -roc_constrain = { path = "../constrain" } -roc_region = { path = "../region" } -roc_solve = { path = "../solve" } -roc_debug_flags = { path = "../debug_flags" } ven_pretty = { path = "../../vendor/pretty" } bumpalo.workspace = true -indoc.workspace = true -pretty_assertions.workspace = true insta.workspace = true diff --git a/crates/compiler/test_derive/src/decoding.rs b/crates/compiler/test_derive/src/decoding.rs index 624ebc5847..14fe23abd1 100644 --- a/crates/compiler/test_derive/src/decoding.rs +++ b/crates/compiler/test_derive/src/decoding.rs @@ -28,6 +28,11 @@ test_key_eq! { explicit_empty_record_and_implicit_empty_record: v!(EMPTY_RECORD), v!({}) + same_tuple: + v!((v!(U8), v!(U16),)), v!((v!(U8), v!(U16),)) + same_tuple_fields_diff_types: + v!((v!(U8), v!(U16),)), v!((v!(U32), v!(U64),)) + list_list_diff_types: v!(Symbol::LIST_LIST v!(STR)), v!(Symbol::LIST_LIST v!(U8)) str_str: @@ -41,6 +46,9 @@ test_key_neq! { v!({ a: v!(U8), }), v!({ b: v!(U8), }) record_empty_vs_nonempty: v!(EMPTY_RECORD), v!({ a: v!(U8), }) + + different_tuple_arities: + v!((v!(U8), v!(U16),)), v!((v!(U8), v!(U16), v!(U32),)) } #[test] @@ -167,3 +175,59 @@ fn record_2_fields() { ) }) } + +#[test] +fn tuple_2_fields() { + derive_test(Decoder, v!((v!(STR), v!(U8),)), |golden| { + assert_snapshot!(golden, @r###" + # derived for ( Str, U8 )* + # Decoder ( val, val1 )* fmt | fmt has DecoderFormatting, val has Decoding, val1 has Decoding + # List U8, fmt -[[custom(22)]]-> { rest : List U8, result : [Err [TooShort], Ok ( val, val1 )a] } | fmt has DecoderFormatting, val has Decoding, val1 has Decoding + # Specialization lambda sets: + # @<1>: [[custom(22)]] + #Derived.decoder_(arity:2) = + custom + \#Derived.bytes3, #Derived.fmt3 -> + decodeWith + #Derived.bytes3 + (tuple + { e1: Err NoElem, e0: Err NoElem } + \#Derived.stateRecord2, #Derived.index -> + when #Derived.index is + 0 -> + Next (custom + \#Derived.bytes, #Derived.fmt -> + when decodeWith #Derived.bytes decoder #Derived.fmt is + #Derived.rec -> + { + result: when #Derived.rec.result is + Ok #Derived.val -> + Ok { stateRecord2 & e0: Ok #Derived.val } + Err #Derived.err -> Err #Derived.err, + rest: #Derived.rec.rest + }) + 1 -> + Next (custom + \#Derived.bytes2, #Derived.fmt2 -> + when decodeWith #Derived.bytes2 decoder #Derived.fmt2 is + #Derived.rec2 -> + { + result: when #Derived.rec2.result is + Ok #Derived.val2 -> + Ok { stateRecord2 & e1: Ok #Derived.val2 } + Err #Derived.err2 -> Err #Derived.err2, + rest: #Derived.rec2.rest + }) + _ -> TooLong + \#Derived.stateRecord -> + when #Derived.stateRecord.e0 is + Ok #Derived.0 -> + when #Derived.stateRecord.e1 is + Ok #Derived.1 -> Ok ( #Derived.0, #Derived.1 ) + _ -> Err TooShort + _ -> Err TooShort) + #Derived.fmt3 + "### + ) + }) +} diff --git a/crates/compiler/test_derive/src/encoding.rs b/crates/compiler/test_derive/src/encoding.rs index 4f6a15d7f3..ab45eb7751 100644 --- a/crates/compiler/test_derive/src/encoding.rs +++ b/crates/compiler/test_derive/src/encoding.rs @@ -33,6 +33,11 @@ test_key_eq! { v!({ a: v!(U8), b: v!(U8), }), v!({ ?a: v!(U8), ?b: v!(U8), }) + same_tuple: + v!((v!(U8), v!(U16),)), v!((v!(U8), v!(U16),)) + same_tuple_fields_diff_types: + v!((v!(U8), v!(U16),)), v!((v!(U32), v!(U64),)) + same_tag_union: v!([ A v!(U8) v!(STR), B v!(STR) ]), v!([ A v!(U8) v!(STR), B v!(STR) ]) same_tag_union_tags_diff_types: @@ -57,17 +62,17 @@ test_key_eq! { v!(Symbol::STR_STR), v!(Symbol::STR_STR) alias_eq_real_type: - v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!([False, True]) + v!(Symbol::ATTR_ATTR => v!([ True, False ])), v!([False, True]) diff_alias_same_real_type: - v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True])) + v!(Symbol::ATTR_ATTR => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True])) opaque_eq_real_type: - v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!([False, True]) + v!(@Symbol::ATTR_ATTR => v!([ True, False ])), v!([False, True]) diff_opaque_same_real_type: - v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::UNDERSCORE => v!([False, True])) + v!(@Symbol::ATTR_ATTR => v!([ True, False ])), v!(@Symbol::UNDERSCORE => v!([False, True])) opaque_real_type_eq_alias_real_type: - v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True])) + v!(@Symbol::ATTR_ATTR => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True])) } test_key_neq! { @@ -78,6 +83,9 @@ test_key_neq! { record_empty_vs_nonempty: v!(EMPTY_RECORD), v!({ a: v!(U8), }) + different_tuple_arities: + v!((v!(U8), v!(U16),)), v!((v!(U8), v!(U16), v!(U32),)) + different_tag_union_tags: v!([ A v!(U8) ]), v!([ B v!(U8) ]) tag_union_empty_vs_nonempty: @@ -86,14 +94,14 @@ test_key_neq! { v!([ Nil, Cons v!(^lst) ] as lst), v!([ Nil, Next v!(^lst) ] as lst) same_alias_diff_real_type: - v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::BOOL_BOOL => v!([ False, True, Maybe ])) + v!(Symbol::ATTR_ATTR => v!([ True, False ])), v!(Symbol::ATTR_ATTR => v!([ False, True, Maybe ])) diff_alias_diff_real_type: - v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([ False, True, Maybe ])) + v!(Symbol::ATTR_ATTR => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([ False, True, Maybe ])) same_opaque_diff_real_type: - v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::BOOL_BOOL => v!([ False, True, Maybe ])) + v!(@Symbol::ATTR_ATTR => v!([ True, False ])), v!(@Symbol::ATTR_ATTR => v!([ False, True, Maybe ])) diff_opaque_diff_real_type: - v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::UNDERSCORE => v!([ False, True, Maybe ])) + v!(@Symbol::ATTR_ATTR => v!([ True, False ])), v!(@Symbol::UNDERSCORE => v!([ False, True, Maybe ])) } // }}} hash tests @@ -265,6 +273,29 @@ fn two_field_record() { }) } +#[test] +fn two_field_tuple() { + derive_test(ToEncoder, v!((v!(U8), v!(STR),)), |golden| { + assert_snapshot!(golden, @r###" + # derived for ( U8, Str )* + # ( val, val1 )* -[[toEncoder_(arity:2)(0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding + # ( val, val1 )a -[[toEncoder_(arity:2)(0)]]-> (List U8, fmt -[[custom(2) ( val, val1 )a]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding + # Specialization lambda sets: + # @<1>: [[toEncoder_(arity:2)(0)]] + # @<2>: [[custom(2) ( val, val1 )*]] | val has Encoding, val1 has Encoding + #Derived.toEncoder_(arity:2) = + \#Derived.tup -> + custom + \#Derived.bytes, #Derived.fmt -> + appendWith + #Derived.bytes + (tuple [toEncoder #Derived.tup.0, toEncoder #Derived.tup.1]) + #Derived.fmt + "### + ) + }) +} + #[test] #[ignore = "NOTE: this would never actually happen, because [] is uninhabited, and hence toEncoder can never be called with a value of []! Rightfully it induces broken assertions in other parts of the compiler, so we ignore it."] diff --git a/crates/compiler/test_derive/src/hash.rs b/crates/compiler/test_derive/src/hash.rs index ee46d23257..4847f1044e 100644 --- a/crates/compiler/test_derive/src/hash.rs +++ b/crates/compiler/test_derive/src/hash.rs @@ -28,6 +28,11 @@ test_key_eq! { explicit_empty_record_and_implicit_empty_record: v!(EMPTY_RECORD), v!({}) + same_tuple: + v!((v!(U8), v!(U16),)), v!((v!(U8), v!(U16),)) + same_tuple_fields_diff_types: + v!((v!(U8), v!(U16),)), v!((v!(U32), v!(U64),)) + same_tag_union: v!([ A v!(U8) v!(STR), B v!(STR) ]), v!([ A v!(U8) v!(STR), B v!(STR) ]) same_tag_union_tags_diff_types: @@ -51,6 +56,9 @@ test_key_neq! { record_empty_vs_nonempty: v!(EMPTY_RECORD), v!({ a: v!(U8), }) + different_tuple_arities: + v!((v!(U8), v!(U16),)), v!((v!(U8), v!(U16), v!(U32),)) + different_tag_union_tags: v!([ A v!(U8) ]), v!([ B v!(U8) ]) tag_union_empty_vs_nonempty: @@ -201,6 +209,23 @@ fn two_field_record() { }) } +#[test] +fn two_element_tuple() { + derive_test(Hash, v!((v!(U8), v!(STR),)), |golden| { + assert_snapshot!(golden, @r###" + # derived for ( U8, Str )* + # hasher, ( a, a1 )* -[[hash_(arity:2)(0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher + # hasher, ( a, a1 )* -[[hash_(arity:2)(0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher + # Specialization lambda sets: + # @<1>: [[hash_(arity:2)(0)]] + #Derived.hash_(arity:2) = + \#Derived.hasher, #Derived.tup -> + hash (hash #Derived.hasher #Derived.tup.0) #Derived.tup.1 + "### + ) + }) +} + #[test] fn tag_one_label_no_payloads() { derive_test(Hash, v!([A]), |golden| { diff --git a/crates/compiler/test_derive/src/util.rs b/crates/compiler/test_derive/src/util.rs index 9d5c9daaac..566d433c9c 100644 --- a/crates/compiler/test_derive/src/util.rs +++ b/crates/compiler/test_derive/src/util.rs @@ -89,6 +89,22 @@ macro_rules! v { roc_derive::synth_var(subs, Content::Structure(FlatType::Record(fields, ext))) } }}; + (( $($make_v:expr,)* )$( $($ext:tt)+ )?) => {{ + #[allow(unused)] + use roc_types::subs::{Subs, RecordFields, Content, FlatType, Variable, TupleElems}; + |subs: &mut Subs| { + let elems = [ + $($make_v(subs),)* + ].into_iter().enumerate(); + let elems = TupleElems::insert_into_subs(subs, elems); + + #[allow(unused_mut, unused)] + let mut ext = Variable::EMPTY_TUPLE; + $( ext = $crate::v!($($ext)+)(subs); )? + + roc_derive::synth_var(subs, Content::Structure(FlatType::Tuple(elems, ext))) + } + }}; ([ $($tag:ident $($payload:expr)*),* ] as $rec_var:ident) => {{ use roc_types::subs::{Subs, SubsIndex, Variable, Content, FlatType, TagExt, UnionTags}; use roc_module::ident::TagName; diff --git a/crates/compiler/test_gen/Cargo.toml b/crates/compiler/test_gen/Cargo.toml index 6e9731a157..a5f5173ac3 100644 --- a/crates/compiler/test_gen/Cargo.toml +++ b/crates/compiler/test_gen/Cargo.toml @@ -1,67 +1,73 @@ [package] name = "test_gen" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Contains all of Roc's code generation tests." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [[test]] name = "test_gen" path = "src/tests.rs" [build-dependencies] -roc_builtins = { path = "../builtins" } -roc_utils = { path = "../../utils" } +roc_bitcode = { path = "../builtins/bitcode" } +roc_command_utils = { path = "../../utils/command" } wasi_libc_sys = { path = "../../wasi-libc-sys" } + tempfile.workspace = true [dev-dependencies] -roc_gen_llvm = { path = "../gen_llvm" } -roc_gen_dev = { path = "../gen_dev" } -roc_gen_wasm = { path = "../gen_wasm" } -roc_collections = { path = "../collections" } -roc_region = { path = "../region" } -roc_module = { path = "../module" } -roc_problem = { path = "../problem" } -roc_types = { path = "../types" } +roc_bitcode = { path = "../builtins/bitcode" } +roc_build = { path = "../build", features = ["target-aarch64", "target-x86_64", "target-wasm32"] } roc_builtins = { path = "../builtins" } +roc_can = { path = "../can" } +roc_collections = { path = "../collections" } +roc_command_utils = { path = "../../utils/command" } roc_constrain = { path = "../constrain" } -roc_unify = { path = "../unify" } -roc_utils = { path = "../../utils" } -roc_solve = { path = "../solve" } +roc_debug_flags = { path = "../debug_flags" } +roc_error_macros = { path = "../../error_macros" } +roc_gen_dev = { path = "../gen_dev" } +roc_gen_llvm = { path = "../gen_llvm" } +roc_gen_wasm = { path = "../gen_wasm" } +roc_load = { path = "../load" } +roc_module = { path = "../module" } roc_mono = { path = "../mono" } roc_packaging = { path = "../../packaging" } -roc_reporting = { path = "../../reporting" } -roc_load = { path = "../load" } -roc_can = { path = "../can" } roc_parse = { path = "../parse" } -roc_build = { path = "../build", features = ["target-aarch64", "target-x86_64", "target-wasm32"] } +roc_problem = { path = "../problem" } +roc_region = { path = "../region" } +roc_reporting = { path = "../../reporting" } +roc_solve = { path = "../solve" } +roc_std = { path = "../../roc_std" } roc_target = { path = "../roc_target" } -roc_error_macros = { path = "../../error_macros" } -roc_std = { path = "../../roc_std" } -roc_debug_flags = {path="../debug_flags"} -roc_wasm_module = {path="../../wasm_module"} -roc_wasm_interp = {path="../../wasm_interp"} +roc_types = { path = "../types" } +roc_unify = { path = "../unify" } +roc_wasm_interp = { path = "../../wasm_interp" } +roc_wasm_module = { path = "../../wasm_module" } bumpalo.workspace = true +criterion.workspace = true +indoc.workspace = true +inkwell.workspace = true +lazy_static.workspace = true libc.workspace = true libloading.workspace = true -criterion.workspace = true -tempfile.workspace = true -indoc.workspace = true -lazy_static.workspace = true -inkwell.workspace = true target-lexicon.workspace = true +tempfile.workspace = true [features] default = ["gen-llvm"] -gen-llvm = [] gen-dev = [] -gen-wasm = [] +gen-llvm = [] gen-llvm-wasm = ["gen-llvm"] +gen-wasm = [] [[bench]] name = "list_map" harness = false + +[package.metadata.cargo-udeps.ignore] +development = ["roc_wasm_interp"] \ No newline at end of file diff --git a/crates/compiler/test_gen/build.rs b/crates/compiler/test_gen/build.rs index ad87fb0747..a9abb28f13 100644 --- a/crates/compiler/test_gen/build.rs +++ b/crates/compiler/test_gen/build.rs @@ -1,5 +1,4 @@ -use roc_builtins::bitcode; -use roc_utils::zig; +use roc_command_utils::zig; use std::env; use std::fs; use std::path::Path; @@ -100,8 +99,8 @@ fn build_wasm_test_host() { let mut outfile = PathBuf::from(&out_dir).join(PLATFORM_FILENAME); outfile.set_extension("wasm"); - let builtins_host_tempfile = - bitcode::host_wasm_tempfile().expect("failed to write host builtins object to tempfile"); + let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile() + .expect("failed to write host builtins object to tempfile"); run_zig(&[ "wasm-ld", diff --git a/crates/compiler/test_gen/src/gen_abilities.rs b/crates/compiler/test_gen/src/gen_abilities.rs index 996bfb61ef..2b7311262b 100644 --- a/crates/compiler/test_gen/src/gen_abilities.rs +++ b/crates/compiler/test_gen/src/gen_abilities.rs @@ -485,6 +485,44 @@ mod encode_immediate { ) } + #[test] + #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] + fn ranged_number() { + assert_evals_to!( + indoc!( + r#" + app "test" imports [Encode, Json] provides [main] to "./platform" + + main = + when Str.fromUtf8 (Encode.toBytes [1, 2, 3] Json.toUtf8) is + Ok s -> s + _ -> "" + "# + ), + RocStr::from(r"[1,2,3]"), + RocStr + ) + } + + #[test] + #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] + fn bool() { + assert_evals_to!( + indoc!( + r#" + app "test" imports [Encode, Json] provides [main] to "./platform" + + main = + when Str.fromUtf8 (Encode.toBytes Bool.false Json.toUtf8) is + Ok s -> s + _ -> "" + "# + ), + RocStr::from(r"false"), + RocStr + ) + } + macro_rules! num_immediate { ($($num:expr, $typ:ident)*) => {$( #[test] @@ -787,6 +825,105 @@ fn encode_derived_record_with_many_types() { ) } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn encode_derived_tuple_two_fields() { + assert_evals_to!( + indoc!( + r#" + app "test" + imports [Encode, Json] + provides [main] to "./platform" + + main = + tup = ("foo", 10u8) + result = Str.fromUtf8 (Encode.toBytes tup Json.toUtf8) + when result is + Ok s -> s + _ -> "" + "# + ), + RocStr::from(r#"["foo",10]"#), + RocStr + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn encode_derived_tuple_of_tuples() { + assert_evals_to!( + indoc!( + r#" + app "test" + imports [Encode, Json] + provides [main] to "./platform" + + main = + tup = ( ("foo", 10u8), (23u8, "bar", 15u8) ) + result = Str.fromUtf8 (Encode.toBytes tup Json.toUtf8) + when result is + Ok s -> s + _ -> "" + "# + ), + RocStr::from(r#"[["foo",10],[23,"bar",15]]"#), + RocStr + ) +} + +#[test] +#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))] +fn encode_derived_generic_record_with_different_field_types() { + assert_evals_to!( + indoc!( + r#" + app "test" + imports [Encode, Json] + provides [main] to "./platform" + + Q a b := {a: a, b: b} has [Encoding] + + q = @Q {a: 10u32, b: "fieldb"} + + main = + result = Str.fromUtf8 (Encode.toBytes q Json.toUtf8) + when result is + Ok s -> s + _ -> "" + "# + ), + RocStr::from(r#"{"a":10,"b":"fieldb"}"#), + RocStr + ) +} + +#[test] +#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))] +fn encode_derived_generic_tag_with_different_field_types() { + assert_evals_to!( + indoc!( + r#" + app "test" + imports [Encode, Json] + provides [main] to "./platform" + + Q a b := [A a, B b] has [Encoding] + + q : Q Str U32 + q = @Q (B 67) + + main = + result = Str.fromUtf8 (Encode.toBytes q Json.toUtf8) + when result is + Ok s -> s + _ -> "" + "# + ), + RocStr::from(r#"{"B":[67]}"#), + RocStr + ) +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn decode_use_stdlib() { @@ -907,6 +1044,47 @@ mod decode_immediate { ) } + #[test] + #[cfg(any(feature = "gen-llvm"))] + fn ranged_number() { + assert_evals_to!( + indoc!( + r#" + app "test" imports [Json] provides [main] to "./platform" + + main = + input = Str.toUtf8 "[1,2,3]" + expected = [1,2,3] + + actual = Decode.fromBytes input Json.fromUtf8 |> Result.withDefault [] + + actual == expected + "# + ), + true, + bool + ) + } + + #[test] + #[cfg(any(feature = "gen-llvm"))] + fn bool() { + assert_evals_to!( + indoc!( + r#" + app "test" imports [Json] provides [main] to "./platform" + + main = + when Str.toUtf8 "false" |> Decode.fromBytes Json.fromUtf8 is + Ok s -> s + _ -> Bool.true + "# + ), + false, + bool + ) + } + macro_rules! num_immediate { ($($num:expr, $typ:ident)*) => {$( #[test] @@ -1180,6 +1358,50 @@ fn decode_record_of_record() { ) } +#[test] +#[cfg(all( + any(feature = "gen-llvm", feature = "gen-wasm"), + not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898 +))] +fn decode_tuple_two_elements() { + assert_evals_to!( + indoc!( + r#" + app "test" imports [Json] provides [main] to "./platform" + + main = + when Str.toUtf8 "[\"ab\",10]" |> Decode.fromBytes Json.fromUtf8 is + Ok ("ab", 10u8) -> "abcd" + _ -> "something went wrong" + "# + ), + RocStr::from("abcd"), + RocStr + ) +} + +#[test] +#[cfg(all( + any(feature = "gen-llvm", feature = "gen-wasm"), + not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898 +))] +fn decode_tuple_of_tuples() { + assert_evals_to!( + indoc!( + r#" + app "test" imports [Json] provides [main] to "./platform" + + main = + when Str.toUtf8 "[[\"ab\",10],[\"cd\",25]]" |> Decode.fromBytes Json.fromUtf8 is + Ok ( ("ab", 10u8), ("cd", 25u8) ) -> "abcd" + _ -> "something went wrong" + "# + ), + RocStr::from("abcd"), + RocStr + ) +} + #[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))] mod hash { #[cfg(feature = "gen-llvm")] @@ -1265,6 +1487,24 @@ mod hash { use super::{assert_evals_to, build_test}; use roc_std::RocList; + #[test] + fn bool_false() { + assert_evals_to!( + &build_test("Bool.false"), + RocList::from_slice(&[0]), + RocList + ) + } + + #[test] + fn bool_true() { + assert_evals_to!( + &build_test("Bool.true"), + RocList::from_slice(&[1]), + RocList + ) + } + #[test] fn i8() { assert_evals_to!( @@ -1440,6 +1680,35 @@ mod hash { ) } + #[test] + fn tuple_of_u8_and_str() { + assert_evals_to!( + &build_test(r#"(15u8, "bc")"#), + RocList::from_slice(&[15, 98, 99]), + RocList + ) + } + + #[test] + fn tuple_of_tuples() { + assert_evals_to!( + &build_test(r#"( (15u8, "bc"), (23u8, "ef") )"#), + RocList::from_slice(&[15, 98, 99, 23, 101, 102]), + RocList + ) + } + + #[test] + fn tuple_of_list_of_tuples() { + assert_evals_to!( + &build_test( + r#"( [ ( 15u8, 32u8 ), ( 23u8, 41u8 ) ], [ (45u8, 63u8), (58u8, 73u8) ] )"# + ), + RocList::from_slice(&[15, 32, 23, 41, 45, 63, 58, 73]), + RocList + ) + } + #[test] fn hash_singleton_union() { assert_evals_to!( @@ -1708,6 +1977,22 @@ mod eq { use indoc::indoc; use roc_std::RocStr; + #[test] + fn eq_tuple() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + ("a", "b") == ("a", "b") + "# + ), + true, + bool + ) + } + #[test] fn custom_eq_impl() { assert_evals_to!( diff --git a/crates/compiler/test_gen/src/gen_compare.rs b/crates/compiler/test_gen/src/gen_compare.rs index 78f2b4dbe2..7e2f31dc93 100644 --- a/crates/compiler/test_gen/src/gen_compare.rs +++ b/crates/compiler/test_gen/src/gen_compare.rs @@ -27,7 +27,7 @@ fn eq_i64() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn neq_i64() { assert_evals_to!( indoc!( @@ -61,7 +61,7 @@ fn eq_u64() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn neq_u64() { assert_evals_to!( indoc!( @@ -78,7 +78,7 @@ fn neq_u64() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn eq_bool_tag() { assert_evals_to!( indoc!( @@ -95,7 +95,7 @@ fn eq_bool_tag() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn neq_bool_tag() { assert_evals_to!( indoc!( @@ -111,6 +111,52 @@ fn neq_bool_tag() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn bool_logic() { + assert_evals_to!( + indoc!( + r#" + bool1 = Bool.true + bool2 = Bool.false + bool3 = !bool1 + + (bool1 && bool2) || bool2 && bool3 + "# + ), + false, + bool + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn and_bool() { + assert_evals_to!("Bool.true && Bool.true", true, bool); + assert_evals_to!("Bool.true && Bool.false", false, bool); + assert_evals_to!("Bool.false && Bool.true", false, bool); + assert_evals_to!("Bool.false && Bool.false", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn or_bool() { + assert_evals_to!("Bool.true || Bool.true", true, bool); + assert_evals_to!("Bool.true || Bool.false", true, bool); + assert_evals_to!("Bool.false || Bool.true", true, bool); + assert_evals_to!("Bool.false || Bool.false", false, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn not_bool() { + assert_evals_to!("!Bool.true", false, bool); + assert_evals_to!("!Bool.false", true, bool); + + assert_evals_to!("!(!Bool.true)", true, bool); + assert_evals_to!("!(!Bool.false)", false, bool); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn empty_record() { @@ -152,7 +198,7 @@ fn unit() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn newtype() { assert_evals_to!("Identity 42 == Identity 42", true, bool); assert_evals_to!("Identity 42 != Identity 42", false, bool); diff --git a/crates/compiler/test_gen/src/gen_dict.rs b/crates/compiler/test_gen/src/gen_dict.rs index f101cbddff..d29cb0eff8 100644 --- a/crates/compiler/test_gen/src/gen_dict.rs +++ b/crates/compiler/test_gen/src/gen_dict.rs @@ -16,7 +16,7 @@ use indoc::indoc; use roc_std::{RocList, RocStr}; #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn dict_empty_len() { assert_evals_to!( indoc!( @@ -30,7 +30,7 @@ fn dict_empty_len() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn dict_insert_empty() { assert_evals_to!( indoc!( @@ -46,7 +46,7 @@ fn dict_insert_empty() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn dict_empty_contains() { assert_evals_to!( indoc!( @@ -63,7 +63,7 @@ fn dict_empty_contains() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn dict_nonempty_contains() { assert_evals_to!( indoc!( @@ -81,7 +81,7 @@ fn dict_nonempty_contains() { #[test] #[ignore = "TODO figure out why this is broken with llvm wasm tests"] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn dict_empty_remove() { assert_evals_to!( indoc!( @@ -100,7 +100,7 @@ fn dict_empty_remove() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn dict_nonempty_remove() { assert_evals_to!( indoc!( @@ -120,7 +120,7 @@ fn dict_nonempty_remove() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn dict_nonempty_get() { assert_evals_to!( indoc!( @@ -256,11 +256,7 @@ fn from_list_with_fold_reallocates() { } #[test] -#[cfg(any(feature = "gen-llvm"))] -// TODO: Re-enable this test for wasm. -// Currently it causes "[trap] out of bounds memory access" due to the small strings. -// I was unable to find the root cause and with llvm and valgrind it passes with no issues. -// #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn small_str_keys() { assert_evals_to!( indoc!( @@ -269,14 +265,11 @@ fn small_str_keys() { myDict = Dict.empty {} |> Dict.insert "a" 100 - |> Dict.insert "b" 100 - |> Dict.insert "c" 100 - Dict.keys myDict "# ), - RocList::from_slice(&["a".into(), "b".into(), "c".into(),],), + RocList::from_slice(&["a".into()]), RocList ); } @@ -332,7 +325,7 @@ fn big_str_values() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn unit_values() { assert_evals_to!( indoc!( @@ -354,7 +347,7 @@ fn unit_values() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn single() { assert_evals_to!( indoc!( @@ -372,7 +365,7 @@ fn single() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn insert_all() { assert_evals_to!( indoc!( @@ -410,7 +403,7 @@ fn insert_all_prefer_second() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn keep_shared() { assert_evals_to!( indoc!( @@ -473,7 +466,7 @@ fn keep_shared_prefer_first() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn remove_all() { assert_evals_to!( indoc!( @@ -505,7 +498,7 @@ fn remove_all() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn remove_all_prefer_first() { assert_evals_to!( indoc!( @@ -536,7 +529,7 @@ fn remove_all_prefer_first() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn walk_sum_keys() { assert_evals_to!( indoc!( diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index d7eb76b342..84843f3139 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -52,7 +52,7 @@ fn int_list_literal() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn bool_list_literal() { assert_evals_to!( indoc!( @@ -84,7 +84,45 @@ fn bool_list_literal() { RocList::from_slice(&[false; 1]), RocList ); +} +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn bool_list_concat() { + assert_evals_to!( + indoc!( + r#" + List.concat [Bool.true, Bool.false] [Bool.false, Bool.true] + "# + ), + RocList::from_slice(&[true, false, false, true]), + RocList + ); + + assert_evals_to!( + indoc!( + r#" + List.concat [] [Bool.false, Bool.true] + "# + ), + RocList::from_slice(&[false, true]), + RocList + ); + + assert_evals_to!( + indoc!( + r#" + List.concat [Bool.true, Bool.false] [] + "# + ), + RocList::from_slice(&[true, false]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bool_list_literal_repeat() { assert_evals_to!( indoc!( r#" @@ -145,7 +183,7 @@ fn variously_sized_list_literals() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_append_basic() { assert_evals_to!( "List.append [1] 2", @@ -702,13 +740,13 @@ fn list_swap() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_append_to_empty_list() { assert_evals_to!("List.append [] 3", RocList::from_slice(&[3]), RocList); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_append_to_empty_list_of_int() { assert_evals_to!( indoc!( @@ -726,7 +764,7 @@ fn list_append_to_empty_list_of_int() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_append_bools() { assert_evals_to!( "List.append [Bool.true, Bool.false] Bool.true", @@ -736,7 +774,7 @@ fn list_append_bools() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_append_longer_list() { assert_evals_to!( "List.append [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] 23", @@ -746,7 +784,7 @@ fn list_append_longer_list() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_prepend() { assert_evals_to!("List.prepend [] 1", RocList::from_slice(&[1]), RocList); assert_evals_to!( @@ -768,7 +806,11 @@ fn list_prepend() { RocList::from_slice(&[6, 4]), RocList ); +} +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn list_prepend_str() { assert_evals_to!( indoc!( r#" @@ -785,7 +827,7 @@ fn list_prepend() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_prepend_bools() { assert_evals_to!( "List.prepend [Bool.true, Bool.false] Bool.true", @@ -795,7 +837,7 @@ fn list_prepend_bools() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_prepend_big_list() { assert_evals_to!( "List.prepend [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100] 9", @@ -1601,7 +1643,7 @@ fn list_reverse_empty_list() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_concat_two_empty_lists() { assert_evals_to!( "List.concat [] []", @@ -1611,7 +1653,7 @@ fn list_concat_two_empty_lists() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_concat_two_empty_lists_of_int() { assert_evals_to!( indoc!( @@ -1633,7 +1675,7 @@ fn list_concat_two_empty_lists_of_int() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_concat_second_list_is_empty() { assert_evals_to!( "List.concat [12, 13] []", @@ -1643,7 +1685,7 @@ fn list_concat_second_list_is_empty() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_concat_first_list_is_empty() { assert_evals_to!( "List.concat [] [23, 24]", @@ -1653,7 +1695,7 @@ fn list_concat_first_list_is_empty() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_concat_two_non_empty_lists() { assert_evals_to!( "List.concat [1, 2] [3, 4]", @@ -1679,7 +1721,7 @@ fn list_concat_two_bigger_non_empty_lists() { } #[allow(dead_code)] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn assert_concat_worked(num_elems1: i64, num_elems2: i64) { let vec1: Vec = (0..num_elems1) .map(|i| 12345 % (i + num_elems1 + num_elems2 + 1)) @@ -1701,7 +1743,7 @@ fn assert_concat_worked(num_elems1: i64, num_elems2: i64) { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_concat_empty_list() { assert_concat_worked(0, 0); assert_concat_worked(1, 0); @@ -1725,7 +1767,7 @@ fn list_concat_empty_list() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_concat_nonempty_lists() { assert_concat_worked(1, 1); assert_concat_worked(1, 2); @@ -1741,7 +1783,7 @@ fn list_concat_nonempty_lists() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_concat_large() { with_larger_debug_stack(|| { // these values produce mono ASTs so large that @@ -1817,13 +1859,25 @@ fn first_int_list() { assert_evals_to!( indoc!( r#" - when List.first [12, 9, 6, 3] is - Ok val -> val - Err _ -> -1 + List.first [12, 9, 6, 3] "# ), - 12, - i64 + RocResult::ok(12), + RocResult + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn first_str_list() { + assert_evals_to!( + indoc!( + r#" + List.first ["short", "bar"] + "# + ), + RocResult::ok(RocStr::from("short")), + RocResult ); } @@ -1833,45 +1887,42 @@ fn first_wildcard_empty_list() { assert_evals_to!( indoc!( r#" - when List.first [] is - Ok _ -> 5 - Err _ -> -1 + List.last [] |> Result.map (\_ -> 0i64) "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn first_empty_list() { assert_evals_to!( indoc!( r#" - when List.first [] is - Ok val -> val - Err _ -> -1 + list : List I64 + list = [] + + List.first list "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn last_int_list() { assert_evals_to!( indoc!( r#" - when List.last [12, 9, 6, 3] is - Ok val -> val - Err _ -> -1 + List.last [12, 9, 6, 3] "# ), - 3, - i64 + RocResult::ok(3), + RocResult ); } @@ -1881,13 +1932,11 @@ fn last_wildcard_empty_list() { assert_evals_to!( indoc!( r#" - when List.last [] is - Ok _ -> 5 - Err _ -> -1 + List.last [] |> Result.map (\_ -> 0i64) "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } @@ -1897,13 +1946,14 @@ fn last_empty_list() { assert_evals_to!( indoc!( r#" - when List.last [] is - Ok val -> val - Err _ -> -1 + list : List I64 + list = [] + + List.last list "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } @@ -1913,66 +1963,79 @@ fn get_empty_list() { assert_evals_to!( indoc!( r#" - when List.get [] 0 is - Ok val -> val - Err _ -> -1 + list : List I64 + list = [] + + List.get list 0 "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn get_wildcard_empty_list() { + // NOTE: by default, the return type is `Result [] [NotFound]`, which is actually represented + // as just `[NotFound]`. Casting that to `RocResult<(), ()>` is invalid! But accepting any `()` + // would make the test pointless. Therefore, we must explicitly change the type on the roc side assert_evals_to!( indoc!( r#" - when List.get [] 0 is - Ok _ -> 5 - Err _ -> -1 + List.get [] 0 + |> Result.map (\_ -> {}) "# ), - -1, - i64 + RocResult::err(()), + RocResult<(), ()> ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn get_str_list_ok() { + assert_evals_to!( + indoc!( + r#" + List.get ["foo", "bar"] 1 + "# + ), + RocResult::ok(RocStr::from("bar")), + RocResult + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn get_int_list_ok() { assert_evals_to!( indoc!( r#" - when List.get [12, 9, 6] 1 is - Ok val -> val - Err _ -> -1 + List.get [12, 9, 6] 1 "# ), - 9, - i64 + RocResult::ok(9), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn get_int_list_oob() { assert_evals_to!( indoc!( r#" - when List.get [12, 9, 6] 1000 is - Ok val -> val - Err _ -> -1 + List.get [12, 9, 6] 1000 "# ), - -1, - i64 + RocResult::err(()), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn replace_unique_int_list() { assert_evals_to!( indoc!( @@ -1987,7 +2050,7 @@ fn replace_unique_int_list() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn replace_unique_int_list_out_of_bounds() { assert_evals_to!( indoc!( @@ -2002,7 +2065,7 @@ fn replace_unique_int_list_out_of_bounds() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn replace_unique_int_list_get_old_value() { assert_evals_to!( indoc!( @@ -2017,7 +2080,7 @@ fn replace_unique_int_list_get_old_value() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn replace_unique_get_large_value() { assert_evals_to!( indoc!( @@ -2068,13 +2131,11 @@ fn get_set_unique_int_list_i64() { assert_evals_to!( indoc!( r#" - when List.get (List.set [12, 9, 7, 3] 1 42) 1 is - Ok val -> val - Err _ -> -1 + List.get (List.set [12, 9, 7, 3] 1 42) 1 "# ), - 42, - i64 + RocResult::ok(42), + RocResult ); } @@ -2084,13 +2145,11 @@ fn get_set_unique_int_list_i8() { assert_evals_to!( indoc!( r#" - when List.get (List.set [12, 9, 7, 3] 1 42i8) 1 is - Ok val -> val - Err _ -> -1i8 + List.get (List.set [12, 9, 7, 3] 1 42i8) 1 "# ), - 42, - i8 + RocResult::ok(42), + RocResult ); } @@ -2105,7 +2164,7 @@ fn set_unique_int_list() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn set_unique_list_oob() { assert_evals_to!( "List.set [3, 17, 4.1] 1337 9.25", @@ -2170,20 +2229,18 @@ fn set_shared_list_oob() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn get_unique_int_list() { assert_evals_to!( indoc!( r#" unique = [2, 4] - when List.get unique 1 is - Ok num -> num - Err _ -> -1 + List.get unique 1 "# ), - 4, - i64 + RocResult::ok(4), + RocResult ); } @@ -2205,7 +2262,7 @@ fn gen_wrap_len() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn gen_wrap_first() { assert_evals_to!( indoc!( @@ -2222,7 +2279,7 @@ fn gen_wrap_first() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn gen_duplicate() { assert_evals_to!( indoc!( @@ -2535,7 +2592,7 @@ fn list_literal_increment_decrement() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_pass_to_function() { assert_evals_to!( indoc!( @@ -2555,7 +2612,7 @@ fn list_pass_to_function() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_pass_to_set() { assert_evals_to!( indoc!( @@ -2617,7 +2674,7 @@ fn list_contains_str() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_manual_range() { assert_evals_to!( indoc!( @@ -2643,24 +2700,22 @@ fn list_min() { assert_evals_to!( indoc!( r#" - when List.min [] is - Ok val -> val - Err _ -> -1 - "# + List.min [] + |> Result.map (\_ -> {}) + "# ), - -1, - i64 + RocResult::err(()), + RocResult<(), ()> ); + assert_evals_to!( indoc!( r#" - when List.min [3, 1, 2] is - Ok val -> val - Err _ -> -1 - "# + List.min [3, 1, 2] + "# ), - 1, - i64 + RocResult::ok(1), + RocResult ); } @@ -2670,24 +2725,22 @@ fn list_max() { assert_evals_to!( indoc!( r#" - when List.max [] is - Ok val -> val - Err _ -> -1 - "# + List.max [] + |> Result.map (\_ -> {}) + "# ), - -1, - i64 + RocResult::err(()), + RocResult<(), ()> ); + assert_evals_to!( indoc!( r#" - when List.max [3, 1, 2] is - Ok val -> val - Err _ -> -1 - "# + List.max [3, 1, 2] + "# ), - 3, - i64 + RocResult::ok(3), + RocResult ); } @@ -3370,7 +3423,7 @@ fn with_capacity() { r#" l : List U64 l = List.withCapacity 10 - + l "# ), @@ -3382,7 +3435,7 @@ fn with_capacity() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn with_capacity_append() { // see https://github.com/roc-lang/roc/issues/1732 assert_evals_to!( @@ -3401,6 +3454,92 @@ fn with_capacity_append() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn reserve() { + assert_evals_to!( + indoc!( + r#" + List.reserve [] 15 + "# + ), + (15, RocList::empty()), + RocList, + |value: RocList| (value.capacity(), value) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn reserve_unchanged() { + assert_evals_to!( + indoc!( + r#" + a = [] + b = List.reserve a 15 + {a, b} + "# + ), + // a's capacity is unchanged when we reserve 15 more capcity + // both lists are empty. + (0, RocList::empty(), 15, RocList::empty()), + (RocList, RocList), + |(value_a, value_b): (RocList, RocList)| ( + value_a.capacity(), + value_a, + value_b.capacity(), + value_b + ) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn release_excess_capacity() { + assert_evals_to!( + indoc!( + r#" + List.reserve [] 15 + |> List.releaseExcessCapacity + "# + ), + (0, RocList::empty()), + RocList, + |value: RocList| (value.capacity(), value) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn release_excess_capacity_with_len() { + assert_evals_to!( + indoc!( + r#" + List.reserve [1] 50 + |> List.releaseExcessCapacity + "# + ), + (1, RocList::from_slice(&[1])), + RocList, + |value: RocList| (value.capacity(), value) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn release_excess_capacity_empty() { + assert_evals_to!( + indoc!( + r#" + List.releaseExcessCapacity [] + "# + ), + (0, RocList::empty()), + RocList, + |value: RocList| (value.capacity(), value) + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn call_function_in_empty_list() { @@ -3604,6 +3743,24 @@ fn list_walk_from_even_prefix_sum() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +// TODO: update how roc decides whether or not to print `User crashed` or `Roc failed` such that this prints `Roc failed ...`` +#[should_panic( + expected = r#"User crash with message: "List.range: failed to generate enough elements to fill the range before overflowing the numeric type"# +)] +fn list_range_length_overflow() { + assert_evals_to!( + indoc!( + r#" + List.range {start: At 255u8, end: Length 2} + "# + ), + RocList::::default(), + RocList:: + ); +} + #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] mod pattern_match { #[cfg(feature = "gen-llvm")] diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index c045e9876d..2e9276c86f 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -19,11 +19,11 @@ fn nat_alias() { assert_evals_to!( indoc!( r#" - i : Num.Nat - i = 1 + i : Num.Nat + i = 1 - i - "# + i + "# ), 1, usize @@ -31,16 +31,16 @@ fn nat_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn i128_signed_int_alias() { assert_evals_to!( indoc!( r#" - i : I128 - i = 128 + i : I128 + i = 128 - i - "# + i + "# ), 128, i128 @@ -71,11 +71,11 @@ fn i32_signed_int_alias() { assert_evals_to!( indoc!( r#" - i : I32 - i = 32 + i : I32 + i = 32 - i - "# + i + "# ), 32, i32 @@ -115,7 +115,7 @@ fn i8_signed_int_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn i128_hex_int_alias() { assert_evals_to!( indoc!( @@ -196,7 +196,7 @@ fn i8_hex_int_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn u128_signed_int_alias() { assert_evals_to!( indoc!( @@ -277,7 +277,7 @@ fn u8_signed_int_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn u128_hex_int_alias() { assert_evals_to!( indoc!( @@ -418,7 +418,7 @@ fn character_literal_new_line() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn dec_float_alias() { assert_evals_to!( indoc!( @@ -451,7 +451,7 @@ fn f64_float_alias() { ); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn f32_float_alias() { assert_evals_to!( indoc!( @@ -468,112 +468,51 @@ fn f32_float_alias() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn f64_sqrt() { - assert_evals_to!( - indoc!( - r#" - when Num.sqrtChecked 100 is - Ok val -> val - Err _ -> -1 - "# - ), - 10.0, - f64 - ); +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn f64_sqrt_100() { + assert_evals_to!("Num.sqrt 100", 10.0, f64); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn f64_sqrt_checked_0() { + assert_evals_to!("Num.sqrt 0", 0.0, f64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn f64_log() { - assert_evals_to!( - indoc!( - r#" - Num.log 7.38905609893 - "# - ), - 1.999999999999912, - f64 - ); -} - -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn f64_log_checked_one() { - assert_evals_to!( - indoc!( - r#" - when Num.logChecked 1 is - Ok val -> val - Err _ -> -1 - "# - ), - 0.0, - f64 - ); -} - -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn f64_sqrt_zero() { - assert_evals_to!( - indoc!( - r#" - when Num.sqrtChecked 0 is - Ok val -> val - Err _ -> -1 - "# - ), - 0.0, - f64 - ); +fn f64_sqrt_checked_positive() { + assert_evals_to!("Num.sqrtChecked 100", RocResult::ok(10.0), RocResult); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_sqrt_checked_negative() { - assert_evals_to!( - indoc!( - r#" - when Num.sqrtChecked -1 is - Err _ -> 42 - Ok val -> val - "# - ), - 42.0, - f64 - ); + assert_evals_to!("Num.sqrtChecked -1f64", RocResult::err(()), RocResult); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn f64_log() { + assert_evals_to!("Num.log 7.38905609893", 1.999999999999912, f64); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn f64_log_checked_one() { + assert_evals_to!("Num.logChecked 1", RocResult::ok(0.0), RocResult); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log_checked_zero() { - assert_evals_to!( - indoc!( - r#" - when Num.logChecked 0 is - Err _ -> 42 - Ok val -> val - "# - ), - 42.0, - f64 - ); + assert_evals_to!("Num.logChecked 0", RocResult::err(()), RocResult); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log_negative() { - assert_evals_to!( - indoc!( - r#" - Num.log -1 - "# - ), - true, - f64, - |f: f64| f.is_nan() - ); + assert_evals_to!("Num.log -1", true, f64, |f: f64| f.is_nan()); } #[test] @@ -890,16 +829,20 @@ fn gen_int_neq() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn gen_int_less_than() { - assert_evals_to!( - indoc!( - r#" - 4 < 5 - "# - ), - true, - bool - ); +fn int_less_than() { + assert_evals_to!("4 < 5", true, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn float_less_than() { + assert_evals_to!("4.0 < 5.0", true, bool); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn float_greater_than() { + assert_evals_to!("5.0 > 4.0", true, bool); } #[test] @@ -1081,34 +1024,6 @@ fn gen_sub_dec() { ); } -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn gen_sub_f64() { - assert_evals_to!( - indoc!( - r#" - 1.5 - 2.4 - 3 - "# - ), - -3.9, - f64 - ); -} - -#[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] -fn gen_sub_i64() { - assert_evals_to!( - indoc!( - r#" - 1 - 2 - 3 - "# - ), - -4, - i64 - ); -} - #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn gen_mul_dec() { @@ -1132,6 +1047,60 @@ fn gen_mul_dec() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn gen_sub_f64() { + assert_evals_to!("1.5f64 - 2.4 - 3", -3.9, f64); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn gen_sub_f32() { + assert_evals_to!("1.5f32 - 2.4 - 3", -3.9, f32); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn gen_sub_i8() { + assert_evals_to!("1i8 - 2i8 - 3i8", -4, i8); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn gen_sub_u8() { + assert_evals_to!("8u8 - 2u8 - 3u8", 3, u8); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn gen_sub_i16() { + assert_evals_to!("1i16 - 2i16 - 3i16", -4, i16); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn gen_sub_u16() { + assert_evals_to!("8u16 - 2u16 - 3u16", 3, u16); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn gen_sub_i32() { + assert_evals_to!("1i32 - 2i32 - 3i32", -4, i32); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn gen_sub_u32() { + assert_evals_to!("8u32 - 2u32 - 3u32", 3, u32); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn gen_sub_i64() { + assert_evals_to!("1 - 2 - 3", -4, i64); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn gen_signed_mul_quadword_and_lower() { @@ -1869,29 +1838,19 @@ fn float_add_overflow() { #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[should_panic(expected = r#"Roc failed with message: "integer subtraction overflowed!"#)] fn int_sub_overflow() { - assert_evals_to!( - indoc!( - r#" - -9_223_372_036_854_775_808 - 1 - "# - ), - 0, - i64 - ); + assert_evals_to!("-9_223_372_036_854_775_808 - 1", 0, i64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] fn int_sub_wrap() { assert_evals_to!( - indoc!( - r#" - Num.subWrap -9_223_372_036_854_775_808 1 - "# - ), + "Num.subWrap -9_223_372_036_854_775_808 1", std::i64::MAX, i64 ); + + assert_evals_to!("Num.subWrap -128i8 1", std::i8::MAX, i8); } #[test] @@ -2089,7 +2048,7 @@ fn float_mul_checked() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn shift_left_by() { assert_evals_to!("Num.shiftLeftBy 0b0000_0001 0", 0b0000_0001, i64); assert_evals_to!("Num.shiftLeftBy 0b0000_0001 1", 0b0000_0010, i64); @@ -2098,18 +2057,12 @@ fn shift_left_by() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn shift_right_by() { // Sign Extended Right Shift let is_llvm_release_mode = cfg!(feature = "gen-llvm") && !cfg!(debug_assertions); - // FIXME (Brian) Something funny happening with 8-bit binary literals in tests - assert_evals_to!( - "Num.shiftRightBy (Num.toI8 0b1100_0000u8) 2", - 0b1111_0000u8 as i8, - i8 - ); assert_evals_to!("Num.shiftRightBy 0b0100_0000i8 2", 0b0001_0000i8, i8); assert_evals_to!("Num.shiftRightBy 0b1110_0000u8 1", 0b1111_0000u8, u8); assert_evals_to!("Num.shiftRightBy 0b1100_0000u8 2", 0b1111_0000u8, u8); @@ -2144,9 +2097,28 @@ fn shift_right_by() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn shift_right_zf_by() { // Logical Right Shift + assert_evals_to!("Num.shiftRightZfBy 0b1100_0000u8 2", 0b0011_0000u8, u8); + assert_evals_to!("Num.shiftRightZfBy 0b0000_0010u8 1", 0b0000_0001u8, u8); + assert_evals_to!("Num.shiftRightZfBy 0b0000_1100u8 2", 0b0000_0011u8, u8); + assert_evals_to!("Num.shiftRightZfBy 0b1000_0000u8 12", 0b0000_0000u8, u8); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn shift_right_cast_i8() { + // FIXME (Brian) Something funny happening with 8-bit binary literals in tests + + // arithmetic + assert_evals_to!( + "Num.shiftRightBy (Num.toI8 0b1100_0000u8) 2", + 0b1111_0000u8 as i8, + i8 + ); + + // logical assert_evals_to!( "Num.shiftRightZfBy (Num.toI8 0b1100_0000u8) 2", 0b0011_0000i8, @@ -2834,6 +2806,74 @@ fn bytes_to_u32_subtly_out_of_bounds() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_clearly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU64 bytes 234 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_subtly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello world" + when Num.bytesToU64 bytes 4 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_clearly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU128 bytes 234 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u128 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_subtly_out_of_bounds() { + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello world!!!!!!" + when Num.bytesToU128 bytes 2 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u128 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u16_max_u8s() { @@ -2930,6 +2970,102 @@ fn bytes_to_u32_random_u8s() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_min_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU64 [0, 0, 0, 0, 0, 0, 0, 0] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 0, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_max_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU64 [255, 255, 255, 255, 255, 255, 255, 255] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 18_446_744_073_709_551_615, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u64_random_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU64 [252, 124, 128, 121, 1, 32, 177, 211] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 15_254_008_603_586_100_476, + u64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_min_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU128 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 0, + u128 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_max_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU128 [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 340_282_366_920_938_463_463_374_607_431_768_211_455, + u128 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bytes_to_u128_random_u8s() { + assert_evals_to!( + indoc!( + r#" + when Num.bytesToU128 [252, 124, 128, 121, 1, 32, 177, 211, 3, 57, 203, 122, 95, 164, 23, 145] 0 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 192_860_816_096_412_392_720_639_456_393_488_792_828, + u128 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn when_on_i32() { @@ -3835,3 +3971,30 @@ fn condition_polymorphic_num_becomes_float() { f32 ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn num_count_leading_zero_bits() { + assert_evals_to!(r#"Num.countLeadingZeroBits 0b0010_1000u8"#, 2, usize); + assert_evals_to!(r#"Num.countLeadingZeroBits 0b0010_1000u16"#, 10, usize); + assert_evals_to!(r#"Num.countLeadingZeroBits 0b0010_1000u32"#, 26, usize); + assert_evals_to!(r#"Num.countLeadingZeroBits 0b0010_1000u64"#, 58, usize); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn num_count_trailing_zero_bits() { + assert_evals_to!(r#"Num.countTrailingZeroBits 0b0010_1000u8"#, 3, usize); + assert_evals_to!(r#"Num.countTrailingZeroBits 0b0010_0000u16"#, 5, usize); + assert_evals_to!(r#"Num.countTrailingZeroBits 0u32"#, 32, usize); + assert_evals_to!(r#"Num.countTrailingZeroBits 0b0010_1111u64"#, 0, usize); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn num_count_one_bits() { + assert_evals_to!(r#"Num.countOneBits 0b0010_1000u8"#, 2, usize); + assert_evals_to!(r#"Num.countOneBits 0b0010_0000u16"#, 1, usize); + assert_evals_to!(r#"Num.countOneBits 0u32"#, 0, usize); + assert_evals_to!(r#"Num.countOneBits 0b0010_1111u64"#, 5, usize); +} diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index ce50e3d75b..ec44a7561d 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -9,9 +9,7 @@ use crate::helpers::wasm::assert_evals_to; use indoc::indoc; #[allow(unused_imports)] -use roc_std::RocList; -#[allow(unused_imports)] -use roc_std::RocStr; +use roc_std::{RocBox, RocList, RocStr}; #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] @@ -3170,7 +3168,6 @@ fn alias_defined_out_of_order() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -#[ignore = "TODO https://github.com/roc-lang/roc/issues/4905"] fn recursively_build_effect() { assert_evals_to!( indoc!( @@ -3257,9 +3254,25 @@ fn issue_2322() { ) } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_small_string() { + assert_evals_to!( + indoc!( + r#" + "short" + |> Box.box + |> Box.unbox + "# + ), + RocStr::from("short"), + RocStr + ) +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn box_and_unbox_string() { +fn box_and_unbox_big_string() { assert_evals_to!( indoc!( r#" @@ -3276,17 +3289,62 @@ fn box_and_unbox_string() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn box_and_unbox_num() { +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_num() { + assert_evals_to!("Box.box 123u64", RocBox::new(123), RocBox) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +#[ignore = "triggers some UB somewhere in at least the llvm and dev backends"] +fn box_str() { assert_evals_to!( - indoc!( - r#" - Box.unbox (Box.box (123u8)) - "# - ), - 123, - u8 - ) + "Box.box \"short\"", + RocBox::new(RocStr::from("short")), + RocBox + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_u64() { + assert_evals_to!("Box.unbox (Box.box (123u64))", 123, u64) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_u32() { + assert_evals_to!("Box.unbox (Box.box (123u32))", 123, u32) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_u16() { + assert_evals_to!("Box.unbox (Box.box (123u16))", 123, u16) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_u8() { + assert_evals_to!("Box.unbox (Box.box (123u8))", 123, u8) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_bool() { + assert_evals_to!("Box.unbox (Box.box (Bool.true))", true, bool) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_f64() { + assert_evals_to!("Box.unbox (Box.box (123.0f64))", 123.0, f64) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_f32() { + assert_evals_to!("Box.unbox (Box.box (123.0f32))", 123.0, f32) } #[test] @@ -3853,6 +3911,34 @@ fn compose_recursive_lambda_set_productive_inferred() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn compose_recursive_lambda_set_productive_nullable_wrapped() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + compose = \forward -> \f, g -> + if forward + then \x -> g (f x) + else \x -> f (g x) + + identity = \x -> x + exclame = \s -> "\(s)!" + whisper = \s -> "(\(s))" + + main = + res: Str -> Str + res = List.walk [ exclame, whisper ] identity (compose Bool.false) + res "hello" + "# + ), + RocStr::from("(hello)!"), + RocStr + ) +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn local_binding_aliases_function() { @@ -4254,3 +4340,49 @@ fn function_specialization_information_in_lambda_set_thunk_independent_defs() { u8 ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn when_guard_appears_multiple_times_in_compiled_decision_tree_issue_5176() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + go : U8 -> U8 + go = \byte -> + when byte is + 15 if Bool.true -> 1 + b if Bool.true -> b + 2 + _ -> 3 + + main = go '.' + "# + ), + b'.' + 2, + u8 + ) +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn recursive_lambda_set_resolved_only_upon_specialization() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + factCPS = \n, cont -> + if n == 0 then + cont 1 + else + factCPS (n - 1) \value -> cont (n * value) + + main = + factCPS 5u64 \x -> x + "# + ), + 120, + u64 + ); +} diff --git a/crates/compiler/test_gen/src/gen_records.rs b/crates/compiler/test_gen/src/gen_records.rs index 9f1106e4a9..33f63a6d43 100644 --- a/crates/compiler/test_gen/src/gen_records.rs +++ b/crates/compiler/test_gen/src/gen_records.rs @@ -360,7 +360,7 @@ fn i64_record1_literal() { // ); // } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn bool_literal() { assert_evals_to!( indoc!( @@ -1111,3 +1111,22 @@ fn toplevel_accessor_fn_thunk() { u8 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn pass_record_of_u8s() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + ra = \_ -> 1u8 + + main = + ra { a: 1u8, b: 0u8 } + "# + ), + true, + bool + ) +} diff --git a/crates/compiler/test_gen/src/gen_result.rs b/crates/compiler/test_gen/src/gen_result.rs index 53cc8aaf76..9729fbe9ce 100644 --- a/crates/compiler/test_gen/src/gen_result.rs +++ b/crates/compiler/test_gen/src/gen_result.rs @@ -227,7 +227,7 @@ fn is_err() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn roc_result_ok() { +fn roc_result_ok_i64() { assert_evals_to!( indoc!( r#" @@ -242,6 +242,26 @@ fn roc_result_ok() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn roc_result_ok_f64() { + // NOTE: the dev backend does not currently use float registers when returning a more + // complex type, but the rust side does expect it to. Hence this test fails with gen-dev + + assert_evals_to!( + indoc!( + r#" + result : Result F64 {} + result = Ok 42.0 + + result + "# + ), + RocResult::ok(42.0), + RocResult + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn roc_result_err() { diff --git a/crates/compiler/test_gen/src/gen_str.rs b/crates/compiler/test_gen/src/gen_str.rs index 600810a915..19b8a89c79 100644 --- a/crates/compiler/test_gen/src/gen_str.rs +++ b/crates/compiler/test_gen/src/gen_str.rs @@ -16,7 +16,7 @@ use indoc::indoc; use roc_std::{RocList, RocResult, RocStr}; #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_empty_delimiter() { assert_evals_to!( indoc!( @@ -46,7 +46,7 @@ fn str_split_empty_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_bigger_delimiter_small_str() { assert_evals_to!( indoc!( @@ -76,7 +76,7 @@ fn str_split_bigger_delimiter_small_str() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_str_concat_repeated() { assert_evals_to!( indoc!( @@ -100,7 +100,7 @@ fn str_split_str_concat_repeated() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_small_str_bigger_delimiter() { assert_evals_to!( indoc!(r#"Str.split "JJJ" "0123456789abcdefghi""#), @@ -110,7 +110,7 @@ fn str_split_small_str_bigger_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_big_str_small_delimiter() { assert_evals_to!( indoc!( @@ -140,7 +140,7 @@ fn str_split_big_str_small_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_small_str_small_delimiter() { assert_evals_to!( indoc!( @@ -154,7 +154,7 @@ fn str_split_small_str_small_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_bigger_delimiter_big_strs() { assert_evals_to!( indoc!( @@ -170,7 +170,7 @@ fn str_split_bigger_delimiter_big_strs() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_empty_strs() { assert_evals_to!( indoc!( @@ -184,7 +184,7 @@ fn str_split_empty_strs() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_minimal_example() { assert_evals_to!( indoc!( @@ -198,7 +198,7 @@ fn str_split_minimal_example() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_small_str_big_delimiter() { assert_evals_to!( indoc!( @@ -227,7 +227,7 @@ fn str_split_small_str_big_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_small_str_20_char_delimiter() { assert_evals_to!( indoc!( @@ -243,7 +243,7 @@ fn str_split_small_str_20_char_delimiter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_concat_big_to_big() { assert_evals_to!( indoc!( @@ -402,7 +402,7 @@ fn small_str_concat_empty_second_arg() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn small_str_concat_small_to_big() { assert_evals_to!( r#"Str.concat "abc" " this is longer than 15 chars""#, @@ -485,7 +485,7 @@ fn empty_str_is_empty() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with() { assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool); assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool); @@ -495,7 +495,7 @@ fn str_starts_with() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_scalar() { assert_evals_to!( &format!(r#"Str.startsWithScalar "foobar" {}"#, 'f' as u32), @@ -510,7 +510,7 @@ fn str_starts_with_scalar() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_ends_with() { assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool); assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool); @@ -518,19 +518,19 @@ fn str_ends_with() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_count_graphemes_small_str() { assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_count_graphemes_three_js() { assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_count_graphemes_big_str() { assert_evals_to!( r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#, @@ -540,7 +540,7 @@ fn str_count_graphemes_big_str() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_same_big_str() { assert_evals_to!( r#"Str.startsWith "123456789123456789" "123456789123456789""#, @@ -550,7 +550,7 @@ fn str_starts_with_same_big_str() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_different_big_str() { assert_evals_to!( r#"Str.startsWith "12345678912345678910" "123456789123456789""#, @@ -560,24 +560,24 @@ fn str_starts_with_different_big_str() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_same_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_different_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_starts_with_false_small_str() { assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_single_ascii() { assert_evals_to!( indoc!( @@ -593,7 +593,7 @@ fn str_from_utf8_pass_single_ascii() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_many_ascii() { assert_evals_to!( indoc!( @@ -609,7 +609,7 @@ fn str_from_utf8_pass_many_ascii() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_single_unicode() { assert_evals_to!( indoc!( @@ -625,7 +625,7 @@ fn str_from_utf8_pass_single_unicode() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_many_unicode() { assert_evals_to!( indoc!( @@ -641,7 +641,7 @@ fn str_from_utf8_pass_many_unicode() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_single_grapheme() { assert_evals_to!( indoc!( @@ -657,7 +657,7 @@ fn str_from_utf8_pass_single_grapheme() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_many_grapheme() { assert_evals_to!( indoc!( @@ -673,7 +673,7 @@ fn str_from_utf8_pass_many_grapheme() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_pass_all() { assert_evals_to!( indoc!( @@ -689,7 +689,7 @@ fn str_from_utf8_pass_all() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_invalid_start_byte() { assert_evals_to!( indoc!( @@ -709,7 +709,7 @@ fn str_from_utf8_fail_invalid_start_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_unexpected_end_of_sequence() { assert_evals_to!( indoc!( @@ -729,7 +729,7 @@ fn str_from_utf8_fail_unexpected_end_of_sequence() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_expected_continuation() { assert_evals_to!( indoc!( @@ -749,7 +749,7 @@ fn str_from_utf8_fail_expected_continuation() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_overlong_encoding() { assert_evals_to!( indoc!( @@ -769,7 +769,7 @@ fn str_from_utf8_fail_overlong_encoding() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_codepoint_too_large() { assert_evals_to!( indoc!( @@ -789,7 +789,7 @@ fn str_from_utf8_fail_codepoint_too_large() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_fail_surrogate_half() { assert_evals_to!( indoc!( @@ -809,7 +809,7 @@ fn str_from_utf8_fail_surrogate_half() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_equality() { assert_evals_to!(r#""a" == "a""#, true, bool); assert_evals_to!( @@ -865,7 +865,7 @@ fn nested_recursive_literal() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_join_comma_small() { assert_evals_to!( r#"Str.joinWith ["1", "2"] ", " "#, @@ -875,7 +875,7 @@ fn str_join_comma_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_join_comma_big() { assert_evals_to!( r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#, @@ -885,13 +885,13 @@ fn str_join_comma_big() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_join_comma_single() { assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_to_utf8() { assert_evals_to!( r#"Str.toUtf8 "hello""#, @@ -909,7 +909,7 @@ fn str_to_utf8() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range() { assert_evals_to!( indoc!( @@ -926,7 +926,7 @@ fn str_from_utf8_range() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_slice() { assert_evals_to!( indoc!( @@ -943,7 +943,7 @@ fn str_from_utf8_range_slice() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_slice_not_end() { assert_evals_to!( indoc!( @@ -960,7 +960,7 @@ fn str_from_utf8_range_slice_not_end() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_order_does_not_matter() { assert_evals_to!( indoc!( @@ -977,7 +977,7 @@ fn str_from_utf8_range_order_does_not_matter() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_out_of_bounds_start_value() { assert_evals_to!( indoc!( @@ -995,7 +995,7 @@ fn str_from_utf8_range_out_of_bounds_start_value() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_count_too_high() { assert_evals_to!( indoc!( @@ -1013,7 +1013,7 @@ fn str_from_utf8_range_count_too_high() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_from_utf8_range_count_too_high_for_start() { assert_evals_to!( indoc!( @@ -1031,7 +1031,7 @@ fn str_from_utf8_range_count_too_high_for_start() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_small_stays_small() { assert_evals_to!( indoc!(r#"Str.repeat "Roc" 3"#), @@ -1041,7 +1041,7 @@ fn str_repeat_small_stays_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_small_becomes_big() { assert_evals_to!( indoc!(r#"Str.repeat "less than 23 characters" 2"#), @@ -1051,7 +1051,7 @@ fn str_repeat_small_becomes_big() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_big() { assert_evals_to!( indoc!(r#"Str.repeat "more than 23 characters now" 2"#), @@ -1061,33 +1061,42 @@ fn str_repeat_big() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_empty_string() { let a = indoc!(r#"Str.repeat "" 3"#); - let b = RocStr::from(""); - assert_evals_to!(a, b, RocStr); + assert_evals_to!(a, RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_repeat_zero_times() { assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_empty_string() { assert_evals_to!(indoc!(r#"Str.trim """#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +fn str_trim_null_byte() { + assert_evals_to!( + indoc!(r#"Str.trim (Str.reserve "\u(0000)" 40)"#), + RocStr::from("\0"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_small_blank_string() { assert_evals_to!(indoc!(r#"Str.trim " ""#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_small_to_small() { assert_evals_to!( indoc!(r#"Str.trim " hello world ""#), @@ -1097,7 +1106,7 @@ fn str_trim_small_to_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_large_to_large_unique() { assert_evals_to!( indoc!(r#"Str.trim (Str.concat " " "hello world from a large string ")"#), @@ -1107,7 +1116,7 @@ fn str_trim_large_to_large_unique() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_large_to_small_unique() { assert_evals_to!( indoc!(r#"Str.trim (Str.concat " " "hello world ")"#), @@ -1174,13 +1183,13 @@ fn str_trim_small_to_small_shared() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_left_small_blank_string() { assert_evals_to!(indoc!(r#"Str.trimLeft " ""#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_left_small_to_small() { assert_evals_to!( indoc!(r#"Str.trimLeft " hello world ""#), @@ -1190,7 +1199,7 @@ fn str_trim_left_small_to_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_left_large_to_large_unique() { assert_evals_to!( indoc!(r#"Str.trimLeft (Str.concat " " "hello world from a large string ")"#), @@ -1200,7 +1209,7 @@ fn str_trim_left_large_to_large_unique() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_left_large_to_small_unique() { assert_evals_to!( indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#), @@ -1267,13 +1276,13 @@ fn str_trim_left_small_to_small_shared() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_right_small_blank_string() { assert_evals_to!(indoc!(r#"Str.trimRight " ""#), RocStr::from(""), RocStr); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_right_small_to_small() { assert_evals_to!( indoc!(r#"Str.trimRight " hello world ""#), @@ -1283,7 +1292,7 @@ fn str_trim_right_small_to_small() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_right_large_to_large_unique() { assert_evals_to!( indoc!(r#"Str.trimRight (Str.concat " hello world from a large string" " ")"#), @@ -1293,7 +1302,7 @@ fn str_trim_right_large_to_large_unique() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_trim_right_large_to_small_unique() { assert_evals_to!( indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#), @@ -1360,9 +1369,17 @@ fn str_trim_right_small_to_small_shared() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_to_nat() { - assert_evals_to!(r#"Str.toNat "1" |> Result.withDefault 0"#, 1, usize); + assert_evals_to!( + indoc!( + r#" + Str.toNat "1" + "# + ), + RocResult::ok(1), + RocResult + ); } #[test] @@ -1371,14 +1388,11 @@ fn str_to_i128() { assert_evals_to!( indoc!( r#" - when Str.toI128 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI128 "1" + "# ), - 1, - i128 + RocResult::ok(1), + RocResult ); } @@ -1388,41 +1402,39 @@ fn str_to_u128() { assert_evals_to!( indoc!( r#" - when Str.toU128 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toU128 "1" + "# ), - 1, - u128 + RocResult::ok(1), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_to_i64() { assert_evals_to!( indoc!( r#" - when Str.toI64 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI64 "1" + "# ), - 1, - i64 + RocResult::ok(1), + RocResult ); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_to_u64() { assert_evals_to!( - r#"Str.toU64 "1""#, - RocResult::ok(1u64), - RocResult + indoc!( + r#" + Str.toU64 "1" + "# + ), + RocResult::ok(1), + RocResult ); } @@ -1432,14 +1444,11 @@ fn str_to_i32() { assert_evals_to!( indoc!( r#" - when Str.toI32 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI32 "1" + "# ), - 1, - i32 + RocResult::ok(1), + RocResult ); } @@ -1447,9 +1456,13 @@ fn str_to_i32() { #[cfg(any(feature = "gen-llvm"))] fn str_to_u32() { assert_evals_to!( - r#"Str.toU32 "1""#, - RocResult::ok(1u32), - RocResult + indoc!( + r#" + Str.toU32 "1" + "# + ), + RocResult::ok(1), + RocResult ); } @@ -1459,14 +1472,11 @@ fn str_to_i16() { assert_evals_to!( indoc!( r#" - when Str.toI16 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI16 "1" + "# ), - 1, - i16 + RocResult::ok(1), + RocResult ); } @@ -1476,14 +1486,11 @@ fn str_to_u16() { assert_evals_to!( indoc!( r#" - when Str.toU16 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toU16 "1" + "# ), - 1, - u16 + RocResult::ok(1), + RocResult ); } @@ -1493,14 +1500,11 @@ fn str_to_i8() { assert_evals_to!( indoc!( r#" - when Str.toI8 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toI8 "1" + "# ), - 1, - i8 + RocResult::ok(1), + RocResult ); } @@ -1510,14 +1514,11 @@ fn str_to_u8() { assert_evals_to!( indoc!( r#" - when Str.toU8 "1" is - Ok n -> n - Err _ -> 0 - - "# + Str.toU8 "1" + "# ), - 1, - u8 + RocResult::ok(1), + RocResult ); } @@ -1575,7 +1576,7 @@ fn str_to_dec() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn issue_2811() { assert_evals_to!( indoc!( @@ -1591,7 +1592,7 @@ fn issue_2811() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn to_scalar_1_byte() { assert_evals_to!( indoc!( @@ -1615,7 +1616,7 @@ fn to_scalar_1_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn to_scalar_2_byte() { assert_evals_to!( indoc!( @@ -1639,7 +1640,7 @@ fn to_scalar_2_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn to_scalar_3_byte() { assert_evals_to!( indoc!( @@ -1663,7 +1664,7 @@ fn to_scalar_3_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn to_scalar_4_byte() { // from https://design215.com/toolbox/utf8-4byte-characters.php assert_evals_to!( @@ -1688,7 +1689,7 @@ fn to_scalar_4_byte() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_first_one_char() { assert_evals_to!( indoc!( @@ -1704,7 +1705,7 @@ fn str_split_first_one_char() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_first_multiple_chars() { assert_evals_to!( indoc!( @@ -1718,7 +1719,7 @@ fn str_split_first_multiple_chars() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_first_entire_input() { assert_evals_to!( indoc!( @@ -1732,7 +1733,7 @@ fn str_split_first_entire_input() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_first_not_found() { assert_evals_to!( indoc!( @@ -1746,7 +1747,7 @@ fn str_split_first_not_found() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_last_one_char() { assert_evals_to!( indoc!( @@ -1760,7 +1761,7 @@ fn str_split_last_one_char() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_last_multiple_chars() { assert_evals_to!( indoc!( @@ -1774,7 +1775,7 @@ fn str_split_last_multiple_chars() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_last_entire_input() { assert_evals_to!( indoc!( @@ -1788,12 +1789,12 @@ fn str_split_last_entire_input() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_split_last_not_found() { assert_evals_to!( indoc!( r#" - Str.splitFirst "foo" "bar" + Str.splitLast "foo" "bar" "# ), RocResult::err(()), @@ -1802,7 +1803,27 @@ fn str_split_last_not_found() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +fn str_split_overlapping_substring_1() { + assert_evals_to!( + r#"Str.split "aaa" "aa""#, + RocList::from_slice(&[RocStr::from(""), RocStr::from("a")]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +fn str_split_overlapping_substring_2() { + assert_evals_to!( + r#"Str.split "aaaa" "aa""#, + RocList::from_slice(&[RocStr::from(""), RocStr::from(""), RocStr::from("")]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_walk_utf8_with_index() { #[cfg(not(feature = "gen-llvm-wasm"))] assert_evals_to!( @@ -1842,7 +1863,7 @@ fn str_append_scalar() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] fn str_walk_scalars() { assert_evals_to!( indoc!( @@ -1921,7 +1942,7 @@ fn when_on_strings() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn with_capacity() { assert_evals_to!( indoc!( @@ -1935,7 +1956,7 @@ fn with_capacity() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn with_capacity_concat() { assert_evals_to!( indoc!( @@ -1949,7 +1970,7 @@ fn with_capacity_concat() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn str_with_prefix() { assert_evals_to!( indoc!( @@ -1973,7 +1994,7 @@ fn str_with_prefix() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn destructure_pattern_assigned_from_thunk_opaque() { assert_evals_to!( indoc!( @@ -1995,7 +2016,7 @@ fn destructure_pattern_assigned_from_thunk_opaque() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn destructure_pattern_assigned_from_thunk_tag() { assert_evals_to!( indoc!( @@ -2014,3 +2035,51 @@ fn destructure_pattern_assigned_from_thunk_tag() { RocStr ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn release_excess_capacity() { + assert_evals_to!( + indoc!( + r#" + Str.reserve "" 50 + |> Str.releaseExcessCapacity + "# + ), + (RocStr::empty().capacity(), RocStr::empty()), + RocStr, + |value: RocStr| (value.capacity(), value) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn release_excess_capacity_with_len() { + assert_evals_to!( + indoc!( + r#" + "123456789012345678901234567890" + |> Str.reserve 50 + |> Str.releaseExcessCapacity + "# + ), + (30, "123456789012345678901234567890".into()), + RocStr, + |value: RocStr| (value.capacity(), value) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn release_excess_capacity_empty() { + assert_evals_to!( + indoc!( + r#" + Str.releaseExcessCapacity "" + "# + ), + (RocStr::empty().capacity(), RocStr::empty()), + RocStr, + |value: RocStr| (value.capacity(), value) + ); +} diff --git a/crates/compiler/test_gen/src/gen_tags.rs b/crates/compiler/test_gen/src/gen_tags.rs index 7f2b297180..5b9e6ee834 100644 --- a/crates/compiler/test_gen/src/gen_tags.rs +++ b/crates/compiler/test_gen/src/gen_tags.rs @@ -2196,3 +2196,68 @@ fn nullable_wrapped_with_nullable_not_last_index() { RocStr ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn refcount_nullable_unwrapped_needing_no_refcount_issue_5027() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Effect : {} -> Str + + after = \effect, buildNext -> + \{} -> + when buildNext (effect {}) is + thunk -> thunk {} + + line : Effect + line = \{} -> "done" + + await : Effect, (Str -> Effect) -> Effect + await = \fx, cont -> + after + fx + cont + + succeed : {} -> Effect + succeed = \{} -> (\{} -> "success") + + test = + await line \s -> + if s == "done" then succeed {} else test + + main = test {} + "# + ), + RocStr::from("success"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn issue_5162_recast_nested_nullable_unwrapped_layout() { + assert_evals_to!( + indoc!( + r###" + app "test" provides [main] to "./platform" + + Concept : [ + AtomicConcept, + ExistentialRestriction { role : Str, concept : Concept } + ] + + bottom : Concept + bottom = AtomicConcept + + main = + when Dict.single bottom 0 is + _ -> Bool.true + "### + ), + true, + bool + ); +} diff --git a/crates/compiler/test_gen/src/gen_tuples.rs b/crates/compiler/test_gen/src/gen_tuples.rs new file mode 100644 index 0000000000..d74fbc9f54 --- /dev/null +++ b/crates/compiler/test_gen/src/gen_tuples.rs @@ -0,0 +1,620 @@ +#[cfg(feature = "gen-llvm")] +use crate::helpers::llvm::assert_evals_to; + +#[cfg(feature = "gen-dev")] +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; +use indoc::indoc; + +#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))] +use roc_std::RocStr; + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn basic_tuple() { + assert_evals_to!( + indoc!( + r#" + ( 15, 17, 19 ).0 + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + ( 15, 17, 19 ).1 + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + ( 15, 17, 19 ).2 + "# + ), + 19, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn f64_tuple() { + assert_evals_to!( + indoc!( + r#" + tup = (17.2, 15.1, 19.3) + + tup.0 + "# + ), + 17.2, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + tup = (17.2, 15.1, 19.3) + + tup.1 + "# + ), + 15.1, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + tup = (17.2, 15.1, 19.3) + + tup.2 + "# + ), + 19.3, + f64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn fn_tuple() { + assert_evals_to!( + indoc!( + r#" + getRec = \x -> ("foo", x, 19) + + (getRec 15).1 + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = (15, 17, 19) + + rec.2 + rec.0 + "# + ), + 34, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn int_tuple() { + assert_evals_to!( + indoc!( + r#" + rec = (15, 17, 19) + + rec.0 + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = (15, 17, 19) + + rec.1 + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = (15, 17, 19) + + rec.2 + "# + ), + 19, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn when_on_tuple() { + assert_evals_to!( + indoc!( + r#" + when (0x2, 0x3) is + (x, y) -> x + y + "# + ), + 5, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn when_tuple_with_guard_pattern() { + assert_evals_to!( + indoc!( + r#" + when (0x2, 1.23) is + (var, _) -> var + 3 + "# + ), + 5, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn let_with_tuple_pattern() { + assert_evals_to!( + indoc!( + r#" + (x, _ ) = (0x2, 1.23) + + x + "# + ), + 2, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + (_, y) = (0x2, 0x3) + + y + "# + ), + 3, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn tuple_guard_pattern() { + assert_evals_to!( + indoc!( + r#" + when (0x2, 1.23) is + (0x4, _) -> 5 + (x, _) -> x + 4 + "# + ), + 6, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + when (0x2, 0x3) is + (_, 0x4) -> 5 + (_, x) -> x + 4 + "# + ), + 7, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn twice_tuple_access() { + assert_evals_to!( + indoc!( + r#" + x = (0x2, 0x3) + + x.0 + x.1 + "# + ), + 5, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn i64_tuple2_literal() { + assert_evals_to!( + indoc!( + r#" + (3, 5) + "# + ), + (3, 5), + (i64, i64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn i64_tuple3_literal() { + assert_evals_to!( + indoc!( + r#" + (3, 5, 17) + "# + ), + (3, 5, 17), + (i64, i64, i64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn f64_tuple2_literal() { + assert_evals_to!( + indoc!( + r#" + (3.1, 5.1) + "# + ), + (3.1, 5.1), + (f64, f64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn bool_tuple4_literal() { + assert_evals_to!( + indoc!( + r#" + tuple : (Bool, Bool, Bool, Bool) + tuple = (Bool.true, Bool.false, Bool.false, Bool.true) + + tuple + "# + ), + (true, false, false, true), + (bool, bool, bool, bool) + ); +} + +// Not supported by wasm because of the size of the tuple: +// FromWasm32Memory is only implemented for tuples of up to 4 elements +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn i64_tuple9_literal() { + assert_evals_to!( + indoc!( + r#" + ( 3, 5, 17, 1, 9, 12, 13, 14, 15 ) + "# + ), + [3, 5, 17, 1, 9, 12, 13, 14, 15], + [i64; 9] + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn return_tuple() { + assert_evals_to!( + indoc!( + r#" + x = 4 + y = 3 + + (x, y) + "# + ), + (4, 3), + (i64, i64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn return_tuple_2() { + assert_evals_to!( + indoc!( + r#" + (3, 5) + "# + ), + [3, 5], + [i64; 2] + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn return_tuple_3() { + assert_evals_to!( + indoc!( + r#" + ( 3, 5, 4 ) + "# + ), + (3, 5, 4), + (i64, i64, i64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn return_tuple_4() { + assert_evals_to!( + indoc!( + r#" + ( 3, 5, 4, 2 ) + "# + ), + [3, 5, 4, 2], + [i64; 4] + ); +} + +// Not supported by wasm because of the size of the tuple: +// FromWasm32Memory is only implemented for tuples of up to 4 elements +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +fn return_tuple_5() { + assert_evals_to!( + indoc!( + r#" + ( 3, 5, 4, 2, 1 ) + "# + ), + [3, 5, 4, 2, 1], + [i64; 5] + ); +} + +// Not supported by wasm because of the size of the tuple: +// FromWasm32Memory is only implemented for tuples of up to 4 elements +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +fn return_tuple_6() { + assert_evals_to!( + indoc!( + r#" + ( 3, 5, 4, 2, 1, 7 ) + "# + ), + [3, 5, 4, 2, 1, 7], + [i64; 6] + ); +} + +// Not supported by wasm because of the size of the tuple: +// FromWasm32Memory is only implemented for tuples of up to 4 elements +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))] +fn return_tuple_7() { + assert_evals_to!( + indoc!( + r#" + ( 3, 5, 4, 2, 1, 7, 8 ) + "# + ), + [3, 5, 4, 2, 1, 7, 8], + [i64; 7] + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn return_tuple_float_int() { + assert_evals_to!( + indoc!( + r#" + (1.23, 0x1) + "# + ), + (1.23, 0x1), + (f64, i64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn return_tuple_int_float() { + assert_evals_to!( + indoc!( + r#" + ( 0x1, 1.23 ) + "# + ), + (0x1, 1.23), + (i64, f64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn return_tuple_float_float() { + assert_evals_to!( + indoc!( + r#" + ( 2.46, 1.23 ) + "# + ), + (2.46, 1.23), + (f64, f64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn return_tuple_float_float_float() { + assert_evals_to!( + indoc!( + r#" + ( 2.46, 1.23, 0.1 ) + "# + ), + (2.46, 1.23, 0.1), + (f64, f64, f64) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn return_nested_tuple() { + assert_evals_to!( + indoc!( + r#" + (0x0, (2.46, 1.23, 0.1)) + "# + ), + (0x0, (2.46, 1.23, 0.1)), + (i64, (f64, f64, f64)) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn nested_tuple_load() { + assert_evals_to!( + indoc!( + r#" + x = (0, (0x2, 0x5, 0x6)) + + y = x.1 + + y.2 + "# + ), + 6, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn tuple_accessor_twice() { + assert_evals_to!(".0 (4, 5) + .1 ( 2.46, 3 ) ", 7, i64); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn tuple_accessor_multi_element_tuple() { + assert_evals_to!( + indoc!( + r#" + .0 (4, "foo") + "# + ), + 4, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn booleans_in_tuple() { + assert_evals_to!(indoc!("(1 == 1, 1 == 1)"), (true, true), (bool, bool)); + assert_evals_to!(indoc!("(1 != 1, 1 == 1)"), (false, true), (bool, bool)); + assert_evals_to!(indoc!("(1 == 1, 1 != 1)"), (true, false), (bool, bool)); + assert_evals_to!(indoc!("(1 != 1, 1 != 1)"), (false, false), (bool, bool)); +} + +// TODO: this test fails for mysterious reasons +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn alignment_in_tuple() { + assert_evals_to!( + indoc!("(32, 1 == 1, 78u16)"), + (32i64, 78u16, true), + (i64, u16, bool) + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn tuple_length_polymorphism() { + assert_evals_to!( + indoc!( + r#" + a = (42, 43) + b = (1, 2, 44) + + f : (I64, I64)a, (I64, I64)b -> I64 + f = \(x1, x2), (x3, x4) -> x1 + x2 + x3 + x4 + + f a b + "# + ), + 88, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn generalized_tuple_accessor() { + assert_evals_to!( + indoc!( + r#" + return0 = .0 + + return0 ("foo", 1) + "# + ), + RocStr::from("foo"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn generalized_explicit_tuple_accessor() { + assert_evals_to!( + indoc!( + r#" + return0 = \x -> x.0 + + return0 ("foo", 1) + "# + ), + RocStr::from("foo"), + RocStr + ); +} diff --git a/crates/compiler/test_gen/src/helpers/debug-wasm-test.html b/crates/compiler/test_gen/src/helpers/debug-wasm-test.html index cbc52b508f..1765d4de44 100644 --- a/crates/compiler/test_gen/src/helpers/debug-wasm-test.html +++ b/crates/compiler/test_gen/src/helpers/debug-wasm-test.html @@ -23,7 +23,6 @@ background-color: #000; padding: 1px 4px; font-size: 16px; - border-radius: 6px; } input, button { diff --git a/crates/compiler/test_gen/src/helpers/dev.rs b/crates/compiler/test_gen/src/helpers/dev.rs index 1e0d2108fb..25e6eb134b 100644 --- a/crates/compiler/test_gen/src/helpers/dev.rs +++ b/crates/compiler/test_gen/src/helpers/dev.rs @@ -103,7 +103,7 @@ pub fn helper( // println!("=================================\n"); } - debug_assert_eq!(exposed_to_host.values.len(), 1); + debug_assert_eq!(exposed_to_host.top_level_values.len(), 1); let entry_point = match loaded.entry_point { EntryPoint::Executable { exposed_to_host, @@ -188,7 +188,7 @@ pub fn helper( let env = roc_gen_dev::Env { arena, module_id, - exposed_to_host: exposed_to_host.values.keys().copied().collect(), + exposed_to_host: exposed_to_host.top_level_values.keys().copied().collect(), lazy_literals, generate_allocators: true, // Needed for testing, since we don't have a platform }; @@ -208,7 +208,11 @@ pub fn helper( std::fs::write(&app_o_file, module_out).expect("failed to write object to file"); let builtins_host_tempfile = - bitcode::host_tempfile().expect("failed to write host builtins object to tempfile"); + roc_bitcode::host_tempfile().expect("failed to write host builtins object to tempfile"); + + if false { + std::fs::copy(&app_o_file, "/tmp/app.o").unwrap(); + } let (mut child, dylib_path) = link( &target, @@ -269,6 +273,7 @@ macro_rules! assert_evals_to { let transform = |success| { let expected = $expected; + #[allow(clippy::redundant_closure_call)] let given = $transform(success); assert_eq!(&given, &expected); }; diff --git a/crates/compiler/test_gen/src/helpers/from_wasm32_memory.rs b/crates/compiler/test_gen/src/helpers/from_wasm32_memory.rs index 0d3194f524..218292a588 100644 --- a/crates/compiler/test_gen/src/helpers/from_wasm32_memory.rs +++ b/crates/compiler/test_gen/src/helpers/from_wasm32_memory.rs @@ -1,7 +1,7 @@ use roc_error_macros::internal_error; use roc_gen_wasm::wasm32_sized::Wasm32Sized; use roc_mono::layout::Builtin; -use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128}; +use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128}; use roc_wasm_module::round_up_to_alignment; use std::convert::TryInto; @@ -60,7 +60,10 @@ impl FromWasm32Memory for RocStr { let str_words: &[u32; 3] = unsafe { std::mem::transmute(&str_bytes) }; let big_elem_ptr = str_words[Builtin::WRAPPER_PTR as usize] as usize; - let big_length = str_words[Builtin::WRAPPER_LEN as usize] as usize; + // If the str is a seamless slice, it's highest bit will be set to 1. + // We need to remove that bit or we will get an incorrect negative length. + // Since wasm length is 32bits, and with i32::MAX (0 followed by all 1s in 32 bit). + let big_length = str_words[Builtin::WRAPPER_LEN as usize] as usize & (i32::MAX as usize); let big_capacity = str_words[Builtin::WRAPPER_CAPACITY as usize] as usize; let last_byte = str_bytes[11]; @@ -104,6 +107,17 @@ impl FromWasm32Memory for RocList { } } +impl FromWasm32Memory for RocBox { + fn decode(memory: &[u8], offset: u32) -> Self { + let ptr = ::decode(memory, offset + 4 * Builtin::WRAPPER_PTR); + debug_assert_ne!(ptr, 0); + + let value = ::decode(memory, ptr); + + RocBox::new(value) + } +} + impl FromWasm32Memory for RocResult where T: FromWasm32Memory + Wasm32Sized, diff --git a/crates/compiler/test_gen/src/helpers/llvm.rs b/crates/compiler/test_gen/src/helpers/llvm.rs index 129e7e054b..ff744f03d0 100644 --- a/crates/compiler/test_gen/src/helpers/llvm.rs +++ b/crates/compiler/test_gen/src/helpers/llvm.rs @@ -5,6 +5,7 @@ use inkwell::module::Module; use libloading::Library; use roc_build::link::llvm_module_to_dylib; use roc_collections::all::MutSet; +use roc_command_utils::zig; use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult}; use roc_load::{EntryPoint, ExecutionMode, LoadConfig, LoadMonomorphizedError, Threading}; @@ -12,7 +13,6 @@ use roc_mono::ir::{CrashTag, OptLevel, SingleEntryPoint}; use roc_packaging::cache::RocCacheDir; use roc_region::all::LineInfo; use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE}; -use roc_utils::zig; use target_lexicon::Triple; #[cfg(feature = "gen-llvm-wasm")] @@ -253,6 +253,7 @@ fn create_llvm_module<'a>( let (main_fn_name, main_fn) = match config.mode { LlvmBackendMode::Binary => unreachable!(), LlvmBackendMode::BinaryDev => unreachable!(), + LlvmBackendMode::BinaryGlue => unreachable!(), LlvmBackendMode::CliTest => unreachable!(), LlvmBackendMode::WasmGenTest => roc_gen_llvm::llvm::build::build_wasm_test_wrapper( &env, @@ -572,47 +573,59 @@ pub fn try_run_lib_function( } } +// only used in tests +#[allow(unused)] +pub(crate) fn llvm_evals_to(src: &str, expected: U, transform: F, ignore_problems: bool) +where + U: PartialEq + std::fmt::Debug, + F: FnOnce(T) -> U, +{ + use bumpalo::Bump; + use inkwell::context::Context; + + let arena = Bump::new(); + let context = Context::create(); + + let config = crate::helpers::llvm::HelperConfig { + mode: LlvmBackendMode::GenTest, + add_debug_info: false, + ignore_problems, + opt_level: crate::helpers::llvm::OPT_LEVEL, + }; + + let (main_fn_name, errors, lib) = crate::helpers::llvm::helper(&arena, config, src, &context); + + let result = crate::helpers::llvm::try_run_lib_function::(main_fn_name, &lib); + + match result { + Ok(raw) => { + // only if there are no exceptions thrown, check for errors + assert!(errors.is_empty(), "Encountered errors:\n{}", errors); + + #[allow(clippy::redundant_closure_call)] + let given = transform(raw); + assert_eq!(&given, &expected, "LLVM test failed"); + + // on Windows, there are issues with the drop instances of some roc_std + #[cfg(windows)] + std::mem::forget(given); + } + Err((msg, tag)) => match tag { + CrashTag::Roc => panic!(r#"Roc failed with message: "{}""#, msg), + CrashTag::User => panic!(r#"User crash with message: "{}""#, msg), + }, + } +} + #[allow(unused_macros)] macro_rules! assert_llvm_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => { - use bumpalo::Bump; - use inkwell::context::Context; - use roc_gen_llvm::llvm::build::LlvmBackendMode; - use roc_mono::ir::CrashTag; - - let arena = Bump::new(); - let context = Context::create(); - - let config = $crate::helpers::llvm::HelperConfig { - mode: LlvmBackendMode::GenTest, - add_debug_info: false, - ignore_problems: $ignore_problems, - opt_level: $crate::helpers::llvm::OPT_LEVEL, - }; - - let (main_fn_name, errors, lib) = - $crate::helpers::llvm::helper(&arena, config, $src, &context); - - let result = $crate::helpers::llvm::try_run_lib_function::<$ty>(main_fn_name, &lib); - - match result { - Ok(raw) => { - // only if there are no exceptions thrown, check for errors - assert!(errors.is_empty(), "Encountered errors:\n{}", errors); - - #[allow(clippy::redundant_closure_call)] - let given = $transform(raw); - assert_eq!(&given, &$expected, "LLVM test failed"); - - // on Windows, there are issues with the drop instances of some roc_std - #[cfg(windows)] - std::mem::forget(given); - } - Err((msg, tag)) => match tag { - CrashTag::Roc => panic!(r#"Roc failed with message: "{}""#, msg), - CrashTag::User => panic!(r#"User crash with message: "{}""#, msg), - }, - } + crate::helpers::llvm::llvm_evals_to::<$ty, _, _>( + $src, + $expected, + $transform, + $ignore_problems, + ); }; ($src:expr, $expected:expr, $ty:ty) => { diff --git a/crates/compiler/test_gen/src/helpers/wasm.rs b/crates/compiler/test_gen/src/helpers/wasm.rs index dffa7d2250..e28ebe5b4d 100644 --- a/crates/compiler/test_gen/src/helpers/wasm.rs +++ b/crates/compiler/test_gen/src/helpers/wasm.rs @@ -114,10 +114,10 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>( .. } = loaded; - debug_assert_eq!(exposed_to_host.values.len(), 1); + debug_assert_eq!(exposed_to_host.top_level_values.len(), 1); let exposed_to_host = exposed_to_host - .values + .top_level_values .keys() .copied() .collect::>(); diff --git a/crates/compiler/test_gen/src/tests.rs b/crates/compiler/test_gen/src/tests.rs index a1cbff449d..b7d87f148a 100644 --- a/crates/compiler/test_gen/src/tests.rs +++ b/crates/compiler/test_gen/src/tests.rs @@ -17,6 +17,7 @@ pub mod gen_result; pub mod gen_set; pub mod gen_str; pub mod gen_tags; +pub mod gen_tuples; mod helpers; pub mod wasm_str; diff --git a/crates/compiler/test_mono/Cargo.toml b/crates/compiler/test_mono/Cargo.toml index 85766f29db..05de03ab18 100644 --- a/crates/compiler/test_mono/Cargo.toml +++ b/crates/compiler/test_mono/Cargo.toml @@ -1,25 +1,26 @@ [package] name = "test_mono" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Tests Roc's generation of the mono intermediate representation." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [[test]] name = "test_mono" path = "src/tests.rs" [dev-dependencies] -roc_collections = { path = "../collections" } -roc_module = { path = "../module", features = ["debug-symbols"] } roc_builtins = { path = "../builtins" } -roc_load = { path = "../load" } roc_can = { path = "../can" } +roc_collections = { path = "../collections" } +roc_load = { path = "../load" } +roc_module = { path = "../module", features = ["debug-symbols"] } roc_mono = { path = "../mono" } -roc_target = { path = "../roc_target" } roc_packaging = { path = "../../packaging" } roc_reporting = { path = "../../reporting" } +roc_target = { path = "../roc_target" } roc_tracing = { path = "../../tracing" } test_mono_macros = { path = "../test_mono_macros" } diff --git a/crates/compiler/test_mono/generated/anonymous_closure_in_polymorphic_expression_issue_4717.txt b/crates/compiler/test_mono/generated/anonymous_closure_in_polymorphic_expression_issue_4717.txt index 4538c4e459..8f1450af21 100644 --- a/crates/compiler/test_mono/generated/anonymous_closure_in_polymorphic_expression_issue_4717.txt +++ b/crates/compiler/test_mono/generated/anonymous_closure_in_polymorphic_expression_issue_4717.txt @@ -2,98 +2,98 @@ procedure Bool.11 (#Attr.2, #Attr.3): let Bool.24 : Int1 = lowlevel Eq #Attr.2 #Attr.3; ret Bool.24; -procedure List.26 (List.152, List.153, List.154): - let List.493 : [C U64, C U64] = CallByName List.90 List.152 List.153 List.154; - let List.496 : U8 = 1i64; - let List.497 : U8 = GetTagId List.493; - let List.498 : Int1 = lowlevel Eq List.496 List.497; - if List.498 then - let List.155 : U64 = UnionAtIndex (Id 1) (Index 0) List.493; - ret List.155; - else - let List.156 : U64 = UnionAtIndex (Id 0) (Index 0) List.493; +procedure List.26 (List.153, List.154, List.155): + let List.509 : [C U64, C U64] = CallByName List.92 List.153 List.154 List.155; + let List.512 : U8 = 1i64; + let List.513 : U8 = GetTagId List.509; + let List.514 : Int1 = lowlevel Eq List.512 List.513; + if List.514 then + let List.156 : U64 = UnionAtIndex (Id 1) (Index 0) List.509; ret List.156; - -procedure List.29 (List.294, List.295): - let List.492 : U64 = CallByName List.6 List.294; - let List.296 : U64 = CallByName Num.77 List.492 List.295; - let List.478 : List U8 = CallByName List.43 List.294 List.296; - ret List.478; - -procedure List.43 (List.292, List.293): - let List.490 : U64 = CallByName List.6 List.292; - let List.489 : U64 = CallByName Num.77 List.490 List.293; - let List.480 : {U64, U64} = Struct {List.293, List.489}; - let List.479 : List U8 = CallByName List.49 List.292 List.480; - ret List.479; - -procedure List.49 (List.366, List.367): - let List.487 : U64 = StructAtIndex 0 List.367; - let List.488 : U64 = 0i64; - let List.485 : Int1 = CallByName Bool.11 List.487 List.488; - if List.485 then - dec List.366; - let List.486 : List U8 = Array []; - ret List.486; else - let List.482 : U64 = StructAtIndex 1 List.367; - let List.483 : U64 = StructAtIndex 0 List.367; - let List.481 : List U8 = CallByName List.72 List.366 List.482 List.483; - ret List.481; + let List.157 : U64 = UnionAtIndex (Id 0) (Index 0) List.509; + ret List.157; + +procedure List.29 (List.298, List.299): + let List.508 : U64 = CallByName List.6 List.298; + let List.300 : U64 = CallByName Num.77 List.508 List.299; + let List.494 : List U8 = CallByName List.43 List.298 List.300; + ret List.494; + +procedure List.43 (List.296, List.297): + let List.506 : U64 = CallByName List.6 List.296; + let List.505 : U64 = CallByName Num.77 List.506 List.297; + let List.496 : {U64, U64} = Struct {List.297, List.505}; + let List.495 : List U8 = CallByName List.49 List.296 List.496; + ret List.495; + +procedure List.49 (List.370, List.371): + let List.503 : U64 = StructAtIndex 0 List.371; + let List.504 : U64 = 0i64; + let List.501 : Int1 = CallByName Bool.11 List.503 List.504; + if List.501 then + dec List.370; + let List.502 : List U8 = Array []; + ret List.502; + else + let List.498 : U64 = StructAtIndex 1 List.371; + let List.499 : U64 = StructAtIndex 0 List.371; + let List.497 : List U8 = CallByName List.72 List.370 List.498 List.499; + ret List.497; procedure List.6 (#Attr.2): - let List.491 : U64 = lowlevel ListLen #Attr.2; - ret List.491; + let List.507 : U64 = lowlevel ListLen #Attr.2; + ret List.507; procedure List.66 (#Attr.2, #Attr.3): - let List.514 : U8 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.514; + let List.530 : U8 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.530; procedure List.72 (#Attr.2, #Attr.3, #Attr.4): - let List.484 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; - ret List.484; + let List.500 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; + ret List.500; -procedure List.90 (List.426, List.427, List.428): - let List.500 : U64 = 0i64; - let List.501 : U64 = CallByName List.6 List.426; - let List.499 : [C U64, C U64] = CallByName List.91 List.426 List.427 List.428 List.500 List.501; - ret List.499; - -procedure List.91 (List.528, List.529, List.530, List.531, List.532): - joinpoint List.502 List.429 List.430 List.431 List.432 List.433: - let List.504 : Int1 = CallByName Num.22 List.432 List.433; - if List.504 then - let List.513 : U8 = CallByName List.66 List.429 List.432; - let List.505 : [C U64, C U64] = CallByName Test.4 List.430 List.513; - let List.510 : U8 = 1i64; - let List.511 : U8 = GetTagId List.505; - let List.512 : Int1 = lowlevel Eq List.510 List.511; - if List.512 then - let List.434 : U64 = UnionAtIndex (Id 1) (Index 0) List.505; - let List.508 : U64 = 1i64; - let List.507 : U64 = CallByName Num.19 List.432 List.508; - jump List.502 List.429 List.434 List.431 List.507 List.433; +procedure List.80 (List.544, List.545, List.546, List.547, List.548): + joinpoint List.518 List.433 List.434 List.435 List.436 List.437: + let List.520 : Int1 = CallByName Num.22 List.436 List.437; + if List.520 then + let List.529 : U8 = CallByName List.66 List.433 List.436; + let List.521 : [C U64, C U64] = CallByName Test.4 List.434 List.529; + let List.526 : U8 = 1i64; + let List.527 : U8 = GetTagId List.521; + let List.528 : Int1 = lowlevel Eq List.526 List.527; + if List.528 then + let List.438 : U64 = UnionAtIndex (Id 1) (Index 0) List.521; + let List.524 : U64 = 1i64; + let List.523 : U64 = CallByName Num.19 List.436 List.524; + jump List.518 List.433 List.438 List.435 List.523 List.437; else - let List.435 : U64 = UnionAtIndex (Id 0) (Index 0) List.505; - let List.509 : [C U64, C U64] = TagId(0) List.435; - ret List.509; + let List.439 : U64 = UnionAtIndex (Id 0) (Index 0) List.521; + let List.525 : [C U64, C U64] = TagId(0) List.439; + ret List.525; else - let List.503 : [C U64, C U64] = TagId(1) List.430; - ret List.503; + let List.519 : [C U64, C U64] = TagId(1) List.434; + ret List.519; in - jump List.502 List.528 List.529 List.530 List.531 List.532; + jump List.518 List.544 List.545 List.546 List.547 List.548; + +procedure List.92 (List.430, List.431, List.432): + let List.516 : U64 = 0i64; + let List.517 : U64 = CallByName List.6 List.430; + let List.515 : [C U64, C U64] = CallByName List.80 List.430 List.431 List.432 List.516 List.517; + ret List.515; procedure Num.19 (#Attr.2, #Attr.3): - let Num.258 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.277; procedure Num.22 (#Attr.2, #Attr.3): - let Num.259 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.259; + let Num.278 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.278; procedure Num.77 (#Attr.2, #Attr.3): - let Num.257 : U64 = lowlevel NumSubSaturated #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : U64 = lowlevel NumSubSaturated #Attr.2 #Attr.3; + ret Num.276; procedure Test.1 (Test.2): let Test.13 : U64 = 0i64; diff --git a/crates/compiler/test_mono/generated/call_function_in_empty_list.txt b/crates/compiler/test_mono/generated/call_function_in_empty_list.txt index cab62b26c7..334ceb2350 100644 --- a/crates/compiler/test_mono/generated/call_function_in_empty_list.txt +++ b/crates/compiler/test_mono/generated/call_function_in_empty_list.txt @@ -1,7 +1,7 @@ procedure List.5 (#Attr.2, #Attr.3): - let List.478 : List {} = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3; + let List.494 : List {} = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3; decref #Attr.2; - ret List.478; + ret List.494; procedure Test.2 (Test.3): let Test.7 : {} = Struct {}; diff --git a/crates/compiler/test_mono/generated/call_function_in_empty_list_unbound.txt b/crates/compiler/test_mono/generated/call_function_in_empty_list_unbound.txt index 7a0e9bcf29..1dee214efb 100644 --- a/crates/compiler/test_mono/generated/call_function_in_empty_list_unbound.txt +++ b/crates/compiler/test_mono/generated/call_function_in_empty_list_unbound.txt @@ -1,7 +1,7 @@ procedure List.5 (#Attr.2, #Attr.3): - let List.478 : List [] = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3; + let List.494 : List [] = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3; decref #Attr.2; - ret List.478; + ret List.494; procedure Test.2 (Test.3): let Test.7 : {} = Struct {}; diff --git a/crates/compiler/test_mono/generated/choose_correct_recursion_var_under_record.txt b/crates/compiler/test_mono/generated/choose_correct_recursion_var_under_record.txt index b66fa51420..2252a46f39 100644 --- a/crates/compiler/test_mono/generated/choose_correct_recursion_var_under_record.txt +++ b/crates/compiler/test_mono/generated/choose_correct_recursion_var_under_record.txt @@ -2,51 +2,51 @@ procedure Bool.1 (): let Bool.24 : Int1 = false; ret Bool.24; -procedure List.2 (List.95, List.96): - let List.492 : U64 = CallByName List.6 List.95; - let List.488 : Int1 = CallByName Num.22 List.96 List.492; - if List.488 then - let List.490 : Str = CallByName List.66 List.95 List.96; - let List.489 : [C {}, C Str] = TagId(1) List.490; - ret List.489; +procedure List.2 (List.96, List.97): + let List.508 : U64 = CallByName List.6 List.96; + let List.504 : Int1 = CallByName Num.22 List.97 List.508; + if List.504 then + let List.506 : Str = CallByName List.66 List.96 List.97; + let List.505 : [C {}, C Str] = TagId(1) List.506; + ret List.505; else - let List.487 : {} = Struct {}; - let List.486 : [C {}, C Str] = TagId(0) List.487; - ret List.486; + let List.503 : {} = Struct {}; + let List.502 : [C {}, C Str] = TagId(0) List.503; + ret List.502; procedure List.5 (#Attr.2, #Attr.3): - let List.494 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.10 #Attr.3; - ret List.494; + let List.510 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.10 #Attr.3; + ret List.510; procedure List.6 (#Attr.2): - let List.493 : U64 = lowlevel ListLen #Attr.2; - ret List.493; + let List.509 : U64 = lowlevel ListLen #Attr.2; + ret List.509; procedure List.66 (#Attr.2, #Attr.3): - let List.491 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.491; + let List.507 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.507; -procedure List.9 (List.283): - let List.485 : U64 = 0i64; - let List.478 : [C {}, C Str] = CallByName List.2 List.283 List.485; - let List.482 : U8 = 1i64; - let List.483 : U8 = GetTagId List.478; - let List.484 : Int1 = lowlevel Eq List.482 List.483; - if List.484 then - let List.284 : Str = UnionAtIndex (Id 1) (Index 0) List.478; - inc List.284; - dec List.478; - let List.479 : [C {}, C Str] = TagId(1) List.284; - ret List.479; +procedure List.9 (List.287): + let List.501 : U64 = 0i64; + let List.494 : [C {}, C Str] = CallByName List.2 List.287 List.501; + let List.498 : U8 = 1i64; + let List.499 : U8 = GetTagId List.494; + let List.500 : Int1 = lowlevel Eq List.498 List.499; + if List.500 then + let List.288 : Str = UnionAtIndex (Id 1) (Index 0) List.494; + inc List.288; + dec List.494; + let List.495 : [C {}, C Str] = TagId(1) List.288; + ret List.495; else - dec List.478; - let List.481 : {} = Struct {}; - let List.480 : [C {}, C Str] = TagId(0) List.481; - ret List.480; + dec List.494; + let List.497 : {} = Struct {}; + let List.496 : [C {}, C Str] = TagId(0) List.497; + ret List.496; procedure Num.22 (#Attr.2, #Attr.3): - let Num.256 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.275; procedure Result.5 (Result.12, Result.13): let Result.39 : U8 = 1i64; diff --git a/crates/compiler/test_mono/generated/choose_i128_layout.txt b/crates/compiler/test_mono/generated/choose_i128_layout.txt index 811ac35b19..b14d4d43fb 100644 --- a/crates/compiler/test_mono/generated/choose_i128_layout.txt +++ b/crates/compiler/test_mono/generated/choose_i128_layout.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.257 : I128 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : I128 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.276; procedure Test.0 (): let Test.6 : I128 = 18446744073709551616i64; diff --git a/crates/compiler/test_mono/generated/choose_u128_layout.txt b/crates/compiler/test_mono/generated/choose_u128_layout.txt index acf76c6677..4754f21bcd 100644 --- a/crates/compiler/test_mono/generated/choose_u128_layout.txt +++ b/crates/compiler/test_mono/generated/choose_u128_layout.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : U128 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : U128 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.2 : U128 = 170141183460469231731687303715884105728u128; diff --git a/crates/compiler/test_mono/generated/choose_u64_layout.txt b/crates/compiler/test_mono/generated/choose_u64_layout.txt index 7a88a19504..21383cc40e 100644 --- a/crates/compiler/test_mono/generated/choose_u64_layout.txt +++ b/crates/compiler/test_mono/generated/choose_u64_layout.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.2 : U64 = 9999999999999999999i64; diff --git a/crates/compiler/test_mono/generated/closure_in_list.txt b/crates/compiler/test_mono/generated/closure_in_list.txt index c2300aec72..2a672d6107 100644 --- a/crates/compiler/test_mono/generated/closure_in_list.txt +++ b/crates/compiler/test_mono/generated/closure_in_list.txt @@ -1,6 +1,6 @@ procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; procedure Test.1 (Test.5): let Test.2 : I64 = 41i64; diff --git a/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt b/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt new file mode 100644 index 0000000000..96174e5c73 --- /dev/null +++ b/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt @@ -0,0 +1,176 @@ +procedure Bool.2 (): + let Bool.23 : Int1 = true; + ret Bool.23; + +procedure List.139 (List.140, List.141, List.138): + let List.513 : [, C *self Int1, C *self Int1] = CallByName Test.6 List.140 List.141 List.138; + ret List.513; + +procedure List.18 (List.136, List.137, List.138): + let List.494 : [, C *self Int1, C *self Int1] = CallByName List.92 List.136 List.137 List.138; + ret List.494; + +procedure List.6 (#Attr.2): + let List.511 : U64 = lowlevel ListLen #Attr.2; + ret List.511; + +procedure List.66 (#Attr.2, #Attr.3): + let List.510 : Int1 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.510; + +procedure List.80 (List.517, List.518, List.519, List.520, List.521): + joinpoint List.500 List.433 List.434 List.435 List.436 List.437: + let List.502 : Int1 = CallByName Num.22 List.436 List.437; + if List.502 then + let List.509 : Int1 = CallByName List.66 List.433 List.436; + let List.503 : [, C *self Int1, C *self Int1] = CallByName List.139 List.434 List.509 List.435; + let List.506 : U64 = 1i64; + let List.505 : U64 = CallByName Num.19 List.436 List.506; + jump List.500 List.433 List.503 List.435 List.505 List.437; + else + ret List.434; + in + jump List.500 List.517 List.518 List.519 List.520 List.521; + +procedure List.92 (List.430, List.431, List.432): + let List.498 : U64 = 0i64; + let List.499 : U64 = CallByName List.6 List.430; + let List.497 : [, C *self Int1, C *self Int1] = CallByName List.80 List.430 List.431 List.432 List.498 List.499; + ret List.497; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.275 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; + +procedure Num.22 (#Attr.2, #Attr.3): + let Num.276 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.276; + +procedure Str.3 (#Attr.2, #Attr.3): + let Str.268 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.268; + +procedure Test.1 (Test.5): + ret Test.5; + +procedure Test.11 (Test.53, Test.54): + joinpoint Test.27 Test.12 #Attr.12: + let Test.8 : Int1 = UnionAtIndex (Id 2) (Index 1) #Attr.12; + let Test.7 : [, C *self Int1, C *self Int1] = UnionAtIndex (Id 2) (Index 0) #Attr.12; + inc Test.7; + dec #Attr.12; + joinpoint Test.31 Test.29: + let Test.30 : U8 = GetTagId Test.7; + switch Test.30: + case 0: + dec Test.7; + let Test.28 : Str = CallByName Test.2 Test.29; + dec Test.29; + ret Test.28; + + case 1: + let Test.28 : Str = CallByName Test.9 Test.29 Test.7; + ret Test.28; + + default: + jump Test.27 Test.29 Test.7; + + in + switch Test.8: + case 0: + let Test.32 : Str = CallByName Test.3 Test.12; + jump Test.31 Test.32; + + default: + let Test.32 : Str = CallByName Test.4 Test.12; + jump Test.31 Test.32; + + in + jump Test.27 Test.53 Test.54; + +procedure Test.2 (Test.13): + inc Test.13; + ret Test.13; + +procedure Test.3 (Test.14): + let Test.48 : Str = "!"; + let Test.47 : Str = CallByName Str.3 Test.14 Test.48; + dec Test.48; + ret Test.47; + +procedure Test.4 (Test.15): + let Test.44 : Str = "("; + let Test.46 : Str = ")"; + let Test.45 : Str = CallByName Str.3 Test.15 Test.46; + dec Test.46; + let Test.43 : Str = CallByName Str.3 Test.44 Test.45; + dec Test.45; + ret Test.43; + +procedure Test.6 (Test.7, Test.8, Test.5): + if Test.5 then + let Test.33 : [, C *self Int1, C *self Int1] = TagId(1) Test.7 Test.8; + ret Test.33; + else + let Test.26 : [, C *self Int1, C *self Int1] = TagId(2) Test.7 Test.8; + ret Test.26; + +procedure Test.9 (Test.10, #Attr.12): + let Test.8 : Int1 = UnionAtIndex (Id 1) (Index 1) #Attr.12; + let Test.7 : [, C *self Int1, C *self Int1] = UnionAtIndex (Id 1) (Index 0) #Attr.12; + inc Test.7; + dec #Attr.12; + let Test.37 : U8 = GetTagId Test.7; + joinpoint Test.38 Test.36: + switch Test.8: + case 0: + let Test.35 : Str = CallByName Test.3 Test.36; + ret Test.35; + + default: + let Test.35 : Str = CallByName Test.4 Test.36; + ret Test.35; + + in + switch Test.37: + case 0: + dec Test.7; + let Test.39 : Str = CallByName Test.2 Test.10; + dec Test.10; + jump Test.38 Test.39; + + case 1: + let Test.39 : Str = CallByName Test.9 Test.10 Test.7; + jump Test.38 Test.39; + + default: + let Test.39 : Str = CallByName Test.11 Test.10 Test.7; + jump Test.38 Test.39; + + +procedure Test.0 (): + let Test.41 : Int1 = false; + let Test.42 : Int1 = true; + let Test.20 : List Int1 = Array [Test.41, Test.42]; + let Test.21 : [, C *self Int1, C *self Int1] = TagId(0) ; + let Test.23 : Int1 = CallByName Bool.2; + let Test.22 : Int1 = CallByName Test.1 Test.23; + let Test.16 : [, C *self Int1, C *self Int1] = CallByName List.18 Test.20 Test.21 Test.22; + dec Test.20; + let Test.18 : Str = "hello"; + let Test.19 : U8 = GetTagId Test.16; + switch Test.19: + case 0: + dec Test.16; + let Test.17 : Str = CallByName Test.2 Test.18; + dec Test.18; + ret Test.17; + + case 1: + let Test.17 : Str = CallByName Test.9 Test.18 Test.16; + ret Test.17; + + default: + let Test.17 : Str = CallByName Test.11 Test.18 Test.16; + ret Test.17; + diff --git a/crates/compiler/test_mono/generated/dict.txt b/crates/compiler/test_mono/generated/dict.txt index d1c66212c0..171b053006 100644 --- a/crates/compiler/test_mono/generated/dict.txt +++ b/crates/compiler/test_mono/generated/dict.txt @@ -1,85 +1,85 @@ -procedure Dict.1 (Dict.508): - let Dict.511 : List {[], []} = Array []; - let Dict.518 : U64 = 0i64; - let Dict.519 : U64 = 8i64; - let Dict.512 : List U64 = CallByName List.11 Dict.518 Dict.519; - let Dict.515 : I8 = CallByName Dict.34; - let Dict.516 : U64 = 8i64; - let Dict.513 : List I8 = CallByName List.11 Dict.515 Dict.516; - let Dict.514 : U64 = 0i64; - let Dict.510 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.511, Dict.512, Dict.513, Dict.514}; - ret Dict.510; - -procedure Dict.34 (): - let Dict.517 : I8 = -128i64; +procedure Dict.1 (Dict.515): + let Dict.518 : List {[], []} = Array []; + let Dict.525 : U64 = 0i64; + let Dict.526 : U64 = 8i64; + let Dict.519 : List U64 = CallByName List.11 Dict.525 Dict.526; + let Dict.522 : I8 = CallByName Dict.34; + let Dict.523 : U64 = 8i64; + let Dict.520 : List I8 = CallByName List.11 Dict.522 Dict.523; + let Dict.521 : U64 = 0i64; + let Dict.517 : {List {[], []}, List U64, List I8, U64} = Struct {Dict.518, Dict.519, Dict.520, Dict.521}; ret Dict.517; -procedure Dict.4 (Dict.497): - let Dict.85 : U64 = StructAtIndex 3 Dict.497; - dec Dict.497; +procedure Dict.34 (): + let Dict.524 : I8 = -128i64; + ret Dict.524; + +procedure Dict.4 (Dict.504): + let Dict.85 : U64 = StructAtIndex 3 Dict.504; + dec Dict.504; ret Dict.85; -procedure List.11 (List.114, List.115): - let List.479 : List I8 = CallByName List.68 List.115; - let List.478 : List I8 = CallByName List.80 List.114 List.115 List.479; - ret List.478; +procedure List.11 (List.115, List.116): + let List.495 : List I8 = CallByName List.68 List.116; + let List.494 : List I8 = CallByName List.82 List.115 List.116 List.495; + ret List.494; -procedure List.11 (List.114, List.115): - let List.491 : List U64 = CallByName List.68 List.115; - let List.490 : List U64 = CallByName List.80 List.114 List.115 List.491; - ret List.490; +procedure List.11 (List.115, List.116): + let List.507 : List U64 = CallByName List.68 List.116; + let List.506 : List U64 = CallByName List.82 List.115 List.116 List.507; + ret List.506; procedure List.68 (#Attr.2): - let List.489 : List I8 = lowlevel ListWithCapacity #Attr.2; - ret List.489; + let List.505 : List I8 = lowlevel ListWithCapacity #Attr.2; + ret List.505; procedure List.68 (#Attr.2): - let List.501 : List U64 = lowlevel ListWithCapacity #Attr.2; - ret List.501; + let List.517 : List U64 = lowlevel ListWithCapacity #Attr.2; + ret List.517; procedure List.71 (#Attr.2, #Attr.3): - let List.486 : List I8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.486; + let List.502 : List I8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.502; procedure List.71 (#Attr.2, #Attr.3): - let List.498 : List U64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.498; + let List.514 : List U64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.514; -procedure List.80 (List.502, List.503, List.504): - joinpoint List.480 List.116 List.117 List.118: - let List.488 : U64 = 0i64; - let List.482 : Int1 = CallByName Num.24 List.117 List.488; - if List.482 then - let List.487 : U64 = 1i64; - let List.484 : U64 = CallByName Num.20 List.117 List.487; - let List.485 : List I8 = CallByName List.71 List.118 List.116; - jump List.480 List.116 List.484 List.485; +procedure List.82 (List.518, List.519, List.520): + joinpoint List.496 List.117 List.118 List.119: + let List.504 : U64 = 0i64; + let List.498 : Int1 = CallByName Num.24 List.118 List.504; + if List.498 then + let List.503 : U64 = 1i64; + let List.500 : U64 = CallByName Num.20 List.118 List.503; + let List.501 : List I8 = CallByName List.71 List.119 List.117; + jump List.496 List.117 List.500 List.501; else - ret List.118; + ret List.119; in - jump List.480 List.502 List.503 List.504; + jump List.496 List.518 List.519 List.520; -procedure List.80 (List.510, List.511, List.512): - joinpoint List.492 List.116 List.117 List.118: - let List.500 : U64 = 0i64; - let List.494 : Int1 = CallByName Num.24 List.117 List.500; - if List.494 then - let List.499 : U64 = 1i64; - let List.496 : U64 = CallByName Num.20 List.117 List.499; - let List.497 : List U64 = CallByName List.71 List.118 List.116; - jump List.492 List.116 List.496 List.497; +procedure List.82 (List.526, List.527, List.528): + joinpoint List.508 List.117 List.118 List.119: + let List.516 : U64 = 0i64; + let List.510 : Int1 = CallByName Num.24 List.118 List.516; + if List.510 then + let List.515 : U64 = 1i64; + let List.512 : U64 = CallByName Num.20 List.118 List.515; + let List.513 : List U64 = CallByName List.71 List.119 List.117; + jump List.508 List.117 List.512 List.513; else - ret List.118; + ret List.119; in - jump List.492 List.510 List.511 List.512; + jump List.508 List.526 List.527 List.528; procedure Num.20 (#Attr.2, #Attr.3): - let Num.257 : U64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.276; procedure Num.24 (#Attr.2, #Attr.3): - let Num.259 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; - ret Num.259; + let Num.278 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.278; procedure Test.0 (): let Test.3 : {} = Struct {}; diff --git a/crates/compiler/test_mono/generated/empty_list_of_function_type.txt b/crates/compiler/test_mono/generated/empty_list_of_function_type.txt index f1465173fb..9639d1eb45 100644 --- a/crates/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/crates/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -2,29 +2,29 @@ procedure Bool.1 (): let Bool.23 : Int1 = false; ret Bool.23; -procedure List.2 (List.95, List.96): - let List.484 : U64 = CallByName List.6 List.95; - let List.480 : Int1 = CallByName Num.22 List.96 List.484; - if List.480 then - let List.482 : {} = CallByName List.66 List.95 List.96; - let List.481 : [C {}, C {}] = TagId(1) List.482; - ret List.481; +procedure List.2 (List.96, List.97): + let List.500 : U64 = CallByName List.6 List.96; + let List.496 : Int1 = CallByName Num.22 List.97 List.500; + if List.496 then + let List.498 : {} = CallByName List.66 List.96 List.97; + let List.497 : [C {}, C {}] = TagId(1) List.498; + ret List.497; else - let List.479 : {} = Struct {}; - let List.478 : [C {}, C {}] = TagId(0) List.479; - ret List.478; + let List.495 : {} = Struct {}; + let List.494 : [C {}, C {}] = TagId(0) List.495; + ret List.494; procedure List.6 (#Attr.2): - let List.485 : U64 = lowlevel ListLen #Attr.2; - ret List.485; + let List.501 : U64 = lowlevel ListLen #Attr.2; + ret List.501; procedure List.66 (#Attr.2, #Attr.3): - let List.483 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.483; + let List.499 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.499; procedure Num.22 (#Attr.2, #Attr.3): - let Num.256 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.275; procedure Test.2 (Test.5): let Test.17 : Str = "bar"; diff --git a/crates/compiler/test_mono/generated/encode.txt b/crates/compiler/test_mono/generated/encode.txt index eae2acc8ad..89044bbd97 100644 --- a/crates/compiler/test_mono/generated/encode.txt +++ b/crates/compiler/test_mono/generated/encode.txt @@ -1,16 +1,16 @@ -procedure List.4 (List.106, List.107): - let List.481 : U64 = 1i64; - let List.479 : List U8 = CallByName List.70 List.106 List.481; - let List.478 : List U8 = CallByName List.71 List.479 List.107; - ret List.478; +procedure List.4 (List.107, List.108): + let List.497 : U64 = 1i64; + let List.495 : List U8 = CallByName List.70 List.107 List.497; + let List.494 : List U8 = CallByName List.71 List.495 List.108; + ret List.494; procedure List.70 (#Attr.2, #Attr.3): - let List.482 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.482; + let List.498 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.498; procedure List.71 (#Attr.2, #Attr.3): - let List.480 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.480; + let List.496 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.496; procedure Test.23 (Test.24, Test.35, Test.22): let Test.37 : List U8 = CallByName List.4 Test.24 Test.22; diff --git a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt index 0c7233dd44..1ca601475b 100644 --- a/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_nested_record_string.txt @@ -1,5 +1,5 @@ procedure #Derived.0 (#Derived.1): - let #Derived_gen.0 : Str = CallByName Encode.22 #Derived.1; + let #Derived_gen.0 : Str = CallByName Encode.23 #Derived.1; ret #Derived_gen.0; procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1): @@ -8,11 +8,11 @@ procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1): let #Derived_gen.6 : {Str, Str} = Struct {#Derived_gen.7, #Derived_gen.8}; let #Derived_gen.5 : List {Str, Str} = Array [#Derived_gen.6]; let #Derived_gen.4 : List {Str, Str} = CallByName Json.20 #Derived_gen.5; - let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.4 #Derived.4; + let #Derived_gen.3 : List U8 = CallByName Encode.24 #Derived.3 #Derived_gen.4 #Derived.4; ret #Derived_gen.3; procedure #Derived.5 (#Derived.6): - let #Derived_gen.14 : Str = CallByName Encode.22 #Derived.6; + let #Derived_gen.14 : Str = CallByName Encode.23 #Derived.6; ret #Derived_gen.14; procedure #Derived.7 (#Derived.8, #Derived.9, #Derived.6): @@ -21,334 +21,334 @@ procedure #Derived.7 (#Derived.8, #Derived.9, #Derived.6): let #Derived_gen.20 : {Str, Str} = Struct {#Derived_gen.21, #Derived_gen.22}; let #Derived_gen.19 : List {Str, Str} = Array [#Derived_gen.20]; let #Derived_gen.18 : List {Str, Str} = CallByName Json.20 #Derived_gen.19; - let #Derived_gen.17 : List U8 = CallByName Encode.23 #Derived.8 #Derived_gen.18 #Derived.9; + let #Derived_gen.17 : List U8 = CallByName Encode.24 #Derived.8 #Derived_gen.18 #Derived.9; ret #Derived_gen.17; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.106 : List U8 = CallByName #Derived.2 Encode.94 Encode.96 Encode.102; - ret Encode.106; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.111 : List U8 = CallByName #Derived.2 Encode.99 Encode.101 Encode.107; + ret Encode.111; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.113 : List U8 = CallByName Json.112 Encode.94 Encode.96 Encode.102; - ret Encode.113; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.118 : List U8 = CallByName Json.118 Encode.99 Encode.101 Encode.107; + ret Encode.118; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.115 : List U8 = CallByName #Derived.7 Encode.94 Encode.96 Encode.102; - ret Encode.115; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.120 : List U8 = CallByName #Derived.7 Encode.99 Encode.101 Encode.107; + ret Encode.120; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.125 : List U8 = CallByName Json.112 Encode.94 Encode.96 Encode.102; - ret Encode.125; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.130 : List U8 = CallByName Json.118 Encode.99 Encode.101 Encode.107; + ret Encode.130; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.128 : List U8 = CallByName Json.96 Encode.94 Encode.96 Encode.102; - ret Encode.128; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.133 : List U8 = CallByName Json.102 Encode.99 Encode.101 Encode.107; + ret Encode.133; -procedure Encode.25 (Encode.100, Encode.101): - let Encode.104 : List U8 = Array []; - let Encode.105 : Str = CallByName #Derived.0 Encode.100; - let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101; - ret Encode.103; +procedure Encode.26 (Encode.105, Encode.106): + let Encode.109 : List U8 = Array []; + let Encode.110 : Str = CallByName #Derived.0 Encode.105; + let Encode.108 : List U8 = CallByName Encode.24 Encode.109 Encode.110 Encode.106; + ret Encode.108; procedure Json.1 (): - let Json.396 : {} = Struct {}; - ret Json.396; + let Json.483 : {} = Struct {}; + ret Json.483; -procedure Json.112 (Json.113, Json.399, Json.111): - let Json.432 : I64 = 123i64; - let Json.431 : U8 = CallByName Num.125 Json.432; - let Json.115 : List U8 = CallByName List.4 Json.113 Json.431; - let Json.430 : U64 = CallByName List.6 Json.111; - let Json.407 : {List U8, U64} = Struct {Json.115, Json.430}; - let Json.408 : {} = Struct {}; - let Json.406 : {List U8, U64} = CallByName List.18 Json.111 Json.407 Json.408; - dec Json.111; - let Json.117 : List U8 = StructAtIndex 0 Json.406; - inc Json.117; - dec Json.406; - let Json.405 : I64 = 125i64; - let Json.404 : U8 = CallByName Num.125 Json.405; - let Json.403 : List U8 = CallByName List.4 Json.117 Json.404; - ret Json.403; +procedure Json.102 (Json.103, Json.562, Json.101): + let Json.571 : I64 = 34i64; + let Json.570 : U8 = CallByName Num.127 Json.571; + let Json.568 : List U8 = CallByName List.4 Json.103 Json.570; + let Json.569 : List U8 = CallByName Str.12 Json.101; + let Json.565 : List U8 = CallByName List.8 Json.568 Json.569; + let Json.567 : I64 = 34i64; + let Json.566 : U8 = CallByName Num.127 Json.567; + let Json.564 : List U8 = CallByName List.4 Json.565 Json.566; + ret Json.564; -procedure Json.112 (Json.113, Json.399, Json.111): - let Json.472 : I64 = 123i64; - let Json.471 : U8 = CallByName Num.125 Json.472; - let Json.115 : List U8 = CallByName List.4 Json.113 Json.471; - let Json.470 : U64 = CallByName List.6 Json.111; - let Json.447 : {List U8, U64} = Struct {Json.115, Json.470}; - let Json.448 : {} = Struct {}; - let Json.446 : {List U8, U64} = CallByName List.18 Json.111 Json.447 Json.448; - dec Json.111; - let Json.117 : List U8 = StructAtIndex 0 Json.446; - inc Json.117; - dec Json.446; - let Json.445 : I64 = 125i64; - let Json.444 : U8 = CallByName Num.125 Json.445; - let Json.443 : List U8 = CallByName List.4 Json.117 Json.444; - ret Json.443; +procedure Json.118 (Json.119, Json.486, Json.117): + let Json.519 : I64 = 123i64; + let Json.518 : U8 = CallByName Num.127 Json.519; + let Json.121 : List U8 = CallByName List.4 Json.119 Json.518; + let Json.517 : U64 = CallByName List.6 Json.117; + let Json.494 : {List U8, U64} = Struct {Json.121, Json.517}; + let Json.495 : {} = Struct {}; + let Json.493 : {List U8, U64} = CallByName List.18 Json.117 Json.494 Json.495; + dec Json.117; + let Json.123 : List U8 = StructAtIndex 0 Json.493; + inc Json.123; + dec Json.493; + let Json.492 : I64 = 125i64; + let Json.491 : U8 = CallByName Num.127 Json.492; + let Json.490 : List U8 = CallByName List.4 Json.123 Json.491; + ret Json.490; -procedure Json.114 (Json.401, Json.402): - let Json.120 : Str = StructAtIndex 0 Json.402; - inc Json.120; - let Json.121 : Str = StructAtIndex 1 Json.402; - inc Json.121; - dec Json.402; - let Json.118 : List U8 = StructAtIndex 0 Json.401; - inc Json.118; - let Json.119 : U64 = StructAtIndex 1 Json.401; - dec Json.401; - let Json.429 : I64 = 34i64; - let Json.428 : U8 = CallByName Num.125 Json.429; - let Json.426 : List U8 = CallByName List.4 Json.118 Json.428; - let Json.427 : List U8 = CallByName Str.12 Json.120; - let Json.423 : List U8 = CallByName List.8 Json.426 Json.427; - let Json.425 : I64 = 34i64; - let Json.424 : U8 = CallByName Num.125 Json.425; - let Json.420 : List U8 = CallByName List.4 Json.423 Json.424; - let Json.422 : I64 = 58i64; - let Json.421 : U8 = CallByName Num.125 Json.422; - let Json.418 : List U8 = CallByName List.4 Json.420 Json.421; - let Json.419 : {} = Struct {}; - let Json.122 : List U8 = CallByName Encode.23 Json.418 Json.121 Json.419; - joinpoint Json.413 Json.123: - let Json.411 : U64 = 1i64; - let Json.410 : U64 = CallByName Num.20 Json.119 Json.411; - let Json.409 : {List U8, U64} = Struct {Json.123, Json.410}; - ret Json.409; +procedure Json.118 (Json.119, Json.486, Json.117): + let Json.559 : I64 = 123i64; + let Json.558 : U8 = CallByName Num.127 Json.559; + let Json.121 : List U8 = CallByName List.4 Json.119 Json.558; + let Json.557 : U64 = CallByName List.6 Json.117; + let Json.534 : {List U8, U64} = Struct {Json.121, Json.557}; + let Json.535 : {} = Struct {}; + let Json.533 : {List U8, U64} = CallByName List.18 Json.117 Json.534 Json.535; + dec Json.117; + let Json.123 : List U8 = StructAtIndex 0 Json.533; + inc Json.123; + dec Json.533; + let Json.532 : I64 = 125i64; + let Json.531 : U8 = CallByName Num.127 Json.532; + let Json.530 : List U8 = CallByName List.4 Json.123 Json.531; + ret Json.530; + +procedure Json.120 (Json.488, Json.489): + let Json.126 : Str = StructAtIndex 0 Json.489; + inc Json.126; + let Json.127 : Str = StructAtIndex 1 Json.489; + inc Json.127; + dec Json.489; + let Json.124 : List U8 = StructAtIndex 0 Json.488; + inc Json.124; + let Json.125 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.516 : I64 = 34i64; + let Json.515 : U8 = CallByName Num.127 Json.516; + let Json.513 : List U8 = CallByName List.4 Json.124 Json.515; + let Json.514 : List U8 = CallByName Str.12 Json.126; + let Json.510 : List U8 = CallByName List.8 Json.513 Json.514; + let Json.512 : I64 = 34i64; + let Json.511 : U8 = CallByName Num.127 Json.512; + let Json.507 : List U8 = CallByName List.4 Json.510 Json.511; + let Json.509 : I64 = 58i64; + let Json.508 : U8 = CallByName Num.127 Json.509; + let Json.505 : List U8 = CallByName List.4 Json.507 Json.508; + let Json.506 : {} = Struct {}; + let Json.128 : List U8 = CallByName Encode.24 Json.505 Json.127 Json.506; + joinpoint Json.500 Json.129: + let Json.498 : U64 = 1i64; + let Json.497 : U64 = CallByName Num.20 Json.125 Json.498; + let Json.496 : {List U8, U64} = Struct {Json.129, Json.497}; + ret Json.496; in - let Json.417 : U64 = 1i64; - let Json.414 : Int1 = CallByName Num.24 Json.119 Json.417; - if Json.414 then - let Json.416 : I64 = 44i64; - let Json.415 : U8 = CallByName Num.125 Json.416; - let Json.412 : List U8 = CallByName List.4 Json.122 Json.415; - jump Json.413 Json.412; + let Json.504 : U64 = 1i64; + let Json.501 : Int1 = CallByName Num.24 Json.125 Json.504; + if Json.501 then + let Json.503 : I64 = 44i64; + let Json.502 : U8 = CallByName Num.127 Json.503; + let Json.499 : List U8 = CallByName List.4 Json.128 Json.502; + jump Json.500 Json.499; else - jump Json.413 Json.122; + jump Json.500 Json.128; -procedure Json.114 (Json.401, Json.402): - let Json.120 : Str = StructAtIndex 0 Json.402; - inc Json.120; - let Json.121 : Str = StructAtIndex 1 Json.402; - inc Json.121; - dec Json.402; - let Json.118 : List U8 = StructAtIndex 0 Json.401; - inc Json.118; - let Json.119 : U64 = StructAtIndex 1 Json.401; - dec Json.401; - let Json.469 : I64 = 34i64; - let Json.468 : U8 = CallByName Num.125 Json.469; - let Json.466 : List U8 = CallByName List.4 Json.118 Json.468; - let Json.467 : List U8 = CallByName Str.12 Json.120; - let Json.463 : List U8 = CallByName List.8 Json.466 Json.467; - let Json.465 : I64 = 34i64; - let Json.464 : U8 = CallByName Num.125 Json.465; - let Json.460 : List U8 = CallByName List.4 Json.463 Json.464; - let Json.462 : I64 = 58i64; - let Json.461 : U8 = CallByName Num.125 Json.462; - let Json.458 : List U8 = CallByName List.4 Json.460 Json.461; - let Json.459 : {} = Struct {}; - let Json.122 : List U8 = CallByName Encode.23 Json.458 Json.121 Json.459; - joinpoint Json.453 Json.123: - let Json.451 : U64 = 1i64; - let Json.450 : U64 = CallByName Num.20 Json.119 Json.451; - let Json.449 : {List U8, U64} = Struct {Json.123, Json.450}; - ret Json.449; +procedure Json.120 (Json.488, Json.489): + let Json.126 : Str = StructAtIndex 0 Json.489; + inc Json.126; + let Json.127 : Str = StructAtIndex 1 Json.489; + inc Json.127; + dec Json.489; + let Json.124 : List U8 = StructAtIndex 0 Json.488; + inc Json.124; + let Json.125 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.556 : I64 = 34i64; + let Json.555 : U8 = CallByName Num.127 Json.556; + let Json.553 : List U8 = CallByName List.4 Json.124 Json.555; + let Json.554 : List U8 = CallByName Str.12 Json.126; + let Json.550 : List U8 = CallByName List.8 Json.553 Json.554; + let Json.552 : I64 = 34i64; + let Json.551 : U8 = CallByName Num.127 Json.552; + let Json.547 : List U8 = CallByName List.4 Json.550 Json.551; + let Json.549 : I64 = 58i64; + let Json.548 : U8 = CallByName Num.127 Json.549; + let Json.545 : List U8 = CallByName List.4 Json.547 Json.548; + let Json.546 : {} = Struct {}; + let Json.128 : List U8 = CallByName Encode.24 Json.545 Json.127 Json.546; + joinpoint Json.540 Json.129: + let Json.538 : U64 = 1i64; + let Json.537 : U64 = CallByName Num.20 Json.125 Json.538; + let Json.536 : {List U8, U64} = Struct {Json.129, Json.537}; + ret Json.536; in - let Json.457 : U64 = 1i64; - let Json.454 : Int1 = CallByName Num.24 Json.119 Json.457; - if Json.454 then - let Json.456 : I64 = 44i64; - let Json.455 : U8 = CallByName Num.125 Json.456; - let Json.452 : List U8 = CallByName List.4 Json.122 Json.455; - jump Json.453 Json.452; + let Json.544 : U64 = 1i64; + let Json.541 : Int1 = CallByName Num.24 Json.125 Json.544; + if Json.541 then + let Json.543 : I64 = 44i64; + let Json.542 : U8 = CallByName Num.127 Json.543; + let Json.539 : List U8 = CallByName List.4 Json.128 Json.542; + jump Json.540 Json.539; else - jump Json.453 Json.122; + jump Json.540 Json.128; -procedure Json.18 (Json.95): - let Json.473 : Str = CallByName Encode.22 Json.95; - ret Json.473; +procedure Json.18 (Json.101): + let Json.560 : Str = CallByName Encode.23 Json.101; + ret Json.560; -procedure Json.20 (Json.111): - let Json.397 : List {Str, Str} = CallByName Encode.22 Json.111; - ret Json.397; +procedure Json.20 (Json.117): + let Json.484 : List {Str, Str} = CallByName Encode.23 Json.117; + ret Json.484; -procedure Json.20 (Json.111): - let Json.439 : List {Str, Str} = CallByName Encode.22 Json.111; - ret Json.439; +procedure Json.20 (Json.117): + let Json.526 : List {Str, Str} = CallByName Encode.23 Json.117; + ret Json.526; -procedure Json.96 (Json.97, Json.475, Json.95): - let Json.484 : I64 = 34i64; - let Json.483 : U8 = CallByName Num.125 Json.484; - let Json.481 : List U8 = CallByName List.4 Json.97 Json.483; - let Json.482 : List U8 = CallByName Str.12 Json.95; - let Json.478 : List U8 = CallByName List.8 Json.481 Json.482; - let Json.480 : I64 = 34i64; - let Json.479 : U8 = CallByName Num.125 Json.480; - let Json.477 : List U8 = CallByName List.4 Json.478 Json.479; - ret Json.477; +procedure List.139 (List.140, List.141, List.138): + let List.535 : {List U8, U64} = CallByName Json.120 List.140 List.141; + ret List.535; -procedure List.138 (List.139, List.140, List.137): - let List.519 : {List U8, U64} = CallByName Json.114 List.139 List.140; - ret List.519; +procedure List.139 (List.140, List.141, List.138): + let List.608 : {List U8, U64} = CallByName Json.120 List.140 List.141; + ret List.608; -procedure List.138 (List.139, List.140, List.137): - let List.592 : {List U8, U64} = CallByName Json.114 List.139 List.140; - ret List.592; - -procedure List.18 (List.135, List.136, List.137): - let List.500 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; - ret List.500; - -procedure List.18 (List.135, List.136, List.137): - let List.573 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; - ret List.573; - -procedure List.4 (List.106, List.107): - let List.572 : U64 = 1i64; - let List.571 : List U8 = CallByName List.70 List.106 List.572; - let List.570 : List U8 = CallByName List.71 List.571 List.107; - ret List.570; - -procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; - -procedure List.6 (#Attr.2): - let List.521 : U64 = lowlevel ListLen #Attr.2; - ret List.521; - -procedure List.6 (#Attr.2): - let List.595 : U64 = lowlevel ListLen #Attr.2; - ret List.595; - -procedure List.66 (#Attr.2, #Attr.3): - let List.516 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; +procedure List.18 (List.136, List.137, List.138): + let List.516 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; ret List.516; -procedure List.66 (#Attr.2, #Attr.3): - let List.589 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; +procedure List.18 (List.136, List.137, List.138): + let List.589 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; ret List.589; +procedure List.4 (List.107, List.108): + let List.588 : U64 = 1i64; + let List.587 : List U8 = CallByName List.70 List.107 List.588; + let List.586 : List U8 = CallByName List.71 List.587 List.108; + ret List.586; + +procedure List.6 (#Attr.2): + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; + +procedure List.6 (#Attr.2): + let List.537 : U64 = lowlevel ListLen #Attr.2; + ret List.537; + +procedure List.6 (#Attr.2): + let List.611 : U64 = lowlevel ListLen #Attr.2; + ret List.611; + +procedure List.66 (#Attr.2, #Attr.3): + let List.532 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.532; + +procedure List.66 (#Attr.2, #Attr.3): + let List.605 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.605; + procedure List.70 (#Attr.2, #Attr.3): - let List.551 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.551; + let List.567 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.567; procedure List.71 (#Attr.2, #Attr.3): - let List.549 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.549; + let List.565 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.565; procedure List.8 (#Attr.2, #Attr.3): - let List.594 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; - ret List.594; + let List.610 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.610; -procedure List.90 (List.426, List.427, List.428): - let List.504 : U64 = 0i64; - let List.505 : U64 = CallByName List.6 List.426; - let List.503 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.504 List.505; - ret List.503; - -procedure List.90 (List.426, List.427, List.428): - let List.577 : U64 = 0i64; - let List.578 : U64 = CallByName List.6 List.426; - let List.576 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.577 List.578; - ret List.576; - -procedure List.91 (List.531, List.532, List.533, List.534, List.535): - joinpoint List.506 List.429 List.430 List.431 List.432 List.433: - let List.508 : Int1 = CallByName Num.22 List.432 List.433; - if List.508 then - let List.515 : {Str, Str} = CallByName List.66 List.429 List.432; - let List.509 : {List U8, U64} = CallByName List.138 List.430 List.515 List.431; - let List.512 : U64 = 1i64; - let List.511 : U64 = CallByName Num.19 List.432 List.512; - jump List.506 List.429 List.509 List.431 List.511 List.433; +procedure List.80 (List.547, List.548, List.549, List.550, List.551): + joinpoint List.522 List.433 List.434 List.435 List.436 List.437: + let List.524 : Int1 = CallByName Num.22 List.436 List.437; + if List.524 then + let List.531 : {Str, Str} = CallByName List.66 List.433 List.436; + let List.525 : {List U8, U64} = CallByName List.139 List.434 List.531 List.435; + let List.528 : U64 = 1i64; + let List.527 : U64 = CallByName Num.19 List.436 List.528; + jump List.522 List.433 List.525 List.435 List.527 List.437; else - ret List.430; + ret List.434; in - jump List.506 List.531 List.532 List.533 List.534 List.535; + jump List.522 List.547 List.548 List.549 List.550 List.551; -procedure List.91 (List.605, List.606, List.607, List.608, List.609): - joinpoint List.579 List.429 List.430 List.431 List.432 List.433: - let List.581 : Int1 = CallByName Num.22 List.432 List.433; - if List.581 then - let List.588 : {Str, Str} = CallByName List.66 List.429 List.432; - let List.582 : {List U8, U64} = CallByName List.138 List.430 List.588 List.431; - let List.585 : U64 = 1i64; - let List.584 : U64 = CallByName Num.19 List.432 List.585; - jump List.579 List.429 List.582 List.431 List.584 List.433; +procedure List.80 (List.621, List.622, List.623, List.624, List.625): + joinpoint List.595 List.433 List.434 List.435 List.436 List.437: + let List.597 : Int1 = CallByName Num.22 List.436 List.437; + if List.597 then + let List.604 : {Str, Str} = CallByName List.66 List.433 List.436; + let List.598 : {List U8, U64} = CallByName List.139 List.434 List.604 List.435; + let List.601 : U64 = 1i64; + let List.600 : U64 = CallByName Num.19 List.436 List.601; + jump List.595 List.433 List.598 List.435 List.600 List.437; else - ret List.430; + ret List.434; in - jump List.579 List.605 List.606 List.607 List.608 List.609; + jump List.595 List.621 List.622 List.623 List.624 List.625; -procedure Num.125 (#Attr.2): - let Num.282 : U8 = lowlevel NumIntCast #Attr.2; - ret Num.282; +procedure List.92 (List.430, List.431, List.432): + let List.520 : U64 = 0i64; + let List.521 : U64 = CallByName List.6 List.430; + let List.519 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.520 List.521; + ret List.519; + +procedure List.92 (List.430, List.431, List.432): + let List.593 : U64 = 0i64; + let List.594 : U64 = CallByName List.6 List.430; + let List.592 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.593 List.594; + ret List.592; + +procedure Num.127 (#Attr.2): + let Num.301 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.301; procedure Num.19 (#Attr.2, #Attr.3): - let Num.285 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.285; + let Num.304 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.304; procedure Num.20 (#Attr.2, #Attr.3): - let Num.283 : U64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.283; + let Num.302 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.302; procedure Num.22 (#Attr.2, #Attr.3): - let Num.286 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.286; + let Num.305 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.305; procedure Num.24 (#Attr.2, #Attr.3): - let Num.284 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; - ret Num.284; + let Num.303 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.303; procedure Str.12 (#Attr.2): - let Str.282 : List U8 = lowlevel StrToUtf8 #Attr.2; - ret Str.282; + let Str.283 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.283; procedure Str.48 (#Attr.2, #Attr.3, #Attr.4): - let Str.274 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; - ret Str.274; + let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; + ret Str.275; -procedure Str.9 (Str.76): - let Str.272 : U64 = 0i64; - let Str.273 : U64 = CallByName List.6 Str.76; - let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.272 Str.273; - let Str.269 : Int1 = StructAtIndex 2 Str.77; - if Str.269 then - let Str.271 : Str = StructAtIndex 1 Str.77; - inc Str.271; - dec Str.77; - let Str.270 : [C {U64, U8}, C Str] = TagId(1) Str.271; - ret Str.270; +procedure Str.9 (Str.77): + let Str.273 : U64 = 0i64; + let Str.274 : U64 = CallByName List.6 Str.77; + let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274; + let Str.270 : Int1 = StructAtIndex 2 Str.78; + if Str.270 then + let Str.272 : Str = StructAtIndex 1 Str.78; + inc Str.272; + dec Str.78; + let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272; + ret Str.271; else - let Str.267 : U8 = StructAtIndex 3 Str.77; - let Str.268 : U64 = StructAtIndex 0 Str.77; - dec Str.77; - let Str.266 : {U64, U8} = Struct {Str.268, Str.267}; - let Str.265 : [C {U64, U8}, C Str] = TagId(0) Str.266; - ret Str.265; + let Str.268 : U8 = StructAtIndex 3 Str.78; + let Str.269 : U64 = StructAtIndex 0 Str.78; + dec Str.78; + let Str.267 : {U64, U8} = Struct {Str.269, Str.268}; + let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267; + ret Str.266; procedure Test.0 (): let Test.12 : Str = "bar"; let Test.10 : {} = CallByName Json.1; - let Test.8 : List U8 = CallByName Encode.25 Test.12 Test.10; + let Test.8 : List U8 = CallByName Encode.26 Test.12 Test.10; let Test.1 : [C {U64, U8}, C Str] = CallByName Str.9 Test.8; let Test.5 : U8 = 1i64; let Test.6 : U8 = GetTagId Test.1; diff --git a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt index 02d3f30240..83c6ab0798 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_one_field_string.txt @@ -1,5 +1,5 @@ procedure #Derived.0 (#Derived.1): - let #Derived_gen.0 : Str = CallByName Encode.22 #Derived.1; + let #Derived_gen.0 : Str = CallByName Encode.23 #Derived.1; ret #Derived_gen.0; procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1): @@ -8,224 +8,224 @@ procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1): let #Derived_gen.6 : {Str, Str} = Struct {#Derived_gen.7, #Derived_gen.8}; let #Derived_gen.5 : List {Str, Str} = Array [#Derived_gen.6]; let #Derived_gen.4 : List {Str, Str} = CallByName Json.20 #Derived_gen.5; - let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.4 #Derived.4; + let #Derived_gen.3 : List U8 = CallByName Encode.24 #Derived.3 #Derived_gen.4 #Derived.4; ret #Derived_gen.3; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.106 : List U8 = CallByName #Derived.2 Encode.94 Encode.96 Encode.102; - ret Encode.106; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.111 : List U8 = CallByName #Derived.2 Encode.99 Encode.101 Encode.107; + ret Encode.111; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.113 : List U8 = CallByName Json.112 Encode.94 Encode.96 Encode.102; - ret Encode.113; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.118 : List U8 = CallByName Json.118 Encode.99 Encode.101 Encode.107; + ret Encode.118; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.116 : List U8 = CallByName Json.96 Encode.94 Encode.96 Encode.102; - ret Encode.116; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.121 : List U8 = CallByName Json.102 Encode.99 Encode.101 Encode.107; + ret Encode.121; -procedure Encode.25 (Encode.100, Encode.101): - let Encode.104 : List U8 = Array []; - let Encode.105 : Str = CallByName #Derived.0 Encode.100; - let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101; - ret Encode.103; +procedure Encode.26 (Encode.105, Encode.106): + let Encode.109 : List U8 = Array []; + let Encode.110 : Str = CallByName #Derived.0 Encode.105; + let Encode.108 : List U8 = CallByName Encode.24 Encode.109 Encode.110 Encode.106; + ret Encode.108; procedure Json.1 (): - let Json.396 : {} = Struct {}; - ret Json.396; + let Json.483 : {} = Struct {}; + ret Json.483; -procedure Json.112 (Json.113, Json.399, Json.111): - let Json.432 : I64 = 123i64; - let Json.431 : U8 = CallByName Num.125 Json.432; - let Json.115 : List U8 = CallByName List.4 Json.113 Json.431; - let Json.430 : U64 = CallByName List.6 Json.111; - let Json.407 : {List U8, U64} = Struct {Json.115, Json.430}; - let Json.408 : {} = Struct {}; - let Json.406 : {List U8, U64} = CallByName List.18 Json.111 Json.407 Json.408; - dec Json.111; - let Json.117 : List U8 = StructAtIndex 0 Json.406; - inc Json.117; - dec Json.406; - let Json.405 : I64 = 125i64; - let Json.404 : U8 = CallByName Num.125 Json.405; - let Json.403 : List U8 = CallByName List.4 Json.117 Json.404; - ret Json.403; +procedure Json.102 (Json.103, Json.522, Json.101): + let Json.531 : I64 = 34i64; + let Json.530 : U8 = CallByName Num.127 Json.531; + let Json.528 : List U8 = CallByName List.4 Json.103 Json.530; + let Json.529 : List U8 = CallByName Str.12 Json.101; + let Json.525 : List U8 = CallByName List.8 Json.528 Json.529; + let Json.527 : I64 = 34i64; + let Json.526 : U8 = CallByName Num.127 Json.527; + let Json.524 : List U8 = CallByName List.4 Json.525 Json.526; + ret Json.524; -procedure Json.114 (Json.401, Json.402): - let Json.120 : Str = StructAtIndex 0 Json.402; - inc Json.120; - let Json.121 : Str = StructAtIndex 1 Json.402; - inc Json.121; - dec Json.402; - let Json.118 : List U8 = StructAtIndex 0 Json.401; - inc Json.118; - let Json.119 : U64 = StructAtIndex 1 Json.401; - dec Json.401; - let Json.429 : I64 = 34i64; - let Json.428 : U8 = CallByName Num.125 Json.429; - let Json.426 : List U8 = CallByName List.4 Json.118 Json.428; - let Json.427 : List U8 = CallByName Str.12 Json.120; - let Json.423 : List U8 = CallByName List.8 Json.426 Json.427; - let Json.425 : I64 = 34i64; - let Json.424 : U8 = CallByName Num.125 Json.425; - let Json.420 : List U8 = CallByName List.4 Json.423 Json.424; - let Json.422 : I64 = 58i64; - let Json.421 : U8 = CallByName Num.125 Json.422; - let Json.418 : List U8 = CallByName List.4 Json.420 Json.421; - let Json.419 : {} = Struct {}; - let Json.122 : List U8 = CallByName Encode.23 Json.418 Json.121 Json.419; - joinpoint Json.413 Json.123: - let Json.411 : U64 = 1i64; - let Json.410 : U64 = CallByName Num.20 Json.119 Json.411; - let Json.409 : {List U8, U64} = Struct {Json.123, Json.410}; - ret Json.409; +procedure Json.118 (Json.119, Json.486, Json.117): + let Json.519 : I64 = 123i64; + let Json.518 : U8 = CallByName Num.127 Json.519; + let Json.121 : List U8 = CallByName List.4 Json.119 Json.518; + let Json.517 : U64 = CallByName List.6 Json.117; + let Json.494 : {List U8, U64} = Struct {Json.121, Json.517}; + let Json.495 : {} = Struct {}; + let Json.493 : {List U8, U64} = CallByName List.18 Json.117 Json.494 Json.495; + dec Json.117; + let Json.123 : List U8 = StructAtIndex 0 Json.493; + inc Json.123; + dec Json.493; + let Json.492 : I64 = 125i64; + let Json.491 : U8 = CallByName Num.127 Json.492; + let Json.490 : List U8 = CallByName List.4 Json.123 Json.491; + ret Json.490; + +procedure Json.120 (Json.488, Json.489): + let Json.126 : Str = StructAtIndex 0 Json.489; + inc Json.126; + let Json.127 : Str = StructAtIndex 1 Json.489; + inc Json.127; + dec Json.489; + let Json.124 : List U8 = StructAtIndex 0 Json.488; + inc Json.124; + let Json.125 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.516 : I64 = 34i64; + let Json.515 : U8 = CallByName Num.127 Json.516; + let Json.513 : List U8 = CallByName List.4 Json.124 Json.515; + let Json.514 : List U8 = CallByName Str.12 Json.126; + let Json.510 : List U8 = CallByName List.8 Json.513 Json.514; + let Json.512 : I64 = 34i64; + let Json.511 : U8 = CallByName Num.127 Json.512; + let Json.507 : List U8 = CallByName List.4 Json.510 Json.511; + let Json.509 : I64 = 58i64; + let Json.508 : U8 = CallByName Num.127 Json.509; + let Json.505 : List U8 = CallByName List.4 Json.507 Json.508; + let Json.506 : {} = Struct {}; + let Json.128 : List U8 = CallByName Encode.24 Json.505 Json.127 Json.506; + joinpoint Json.500 Json.129: + let Json.498 : U64 = 1i64; + let Json.497 : U64 = CallByName Num.20 Json.125 Json.498; + let Json.496 : {List U8, U64} = Struct {Json.129, Json.497}; + ret Json.496; in - let Json.417 : U64 = 1i64; - let Json.414 : Int1 = CallByName Num.24 Json.119 Json.417; - if Json.414 then - let Json.416 : I64 = 44i64; - let Json.415 : U8 = CallByName Num.125 Json.416; - let Json.412 : List U8 = CallByName List.4 Json.122 Json.415; - jump Json.413 Json.412; + let Json.504 : U64 = 1i64; + let Json.501 : Int1 = CallByName Num.24 Json.125 Json.504; + if Json.501 then + let Json.503 : I64 = 44i64; + let Json.502 : U8 = CallByName Num.127 Json.503; + let Json.499 : List U8 = CallByName List.4 Json.128 Json.502; + jump Json.500 Json.499; else - jump Json.413 Json.122; + jump Json.500 Json.128; -procedure Json.18 (Json.95): - let Json.433 : Str = CallByName Encode.22 Json.95; - ret Json.433; +procedure Json.18 (Json.101): + let Json.520 : Str = CallByName Encode.23 Json.101; + ret Json.520; -procedure Json.20 (Json.111): - let Json.397 : List {Str, Str} = CallByName Encode.22 Json.111; - ret Json.397; +procedure Json.20 (Json.117): + let Json.484 : List {Str, Str} = CallByName Encode.23 Json.117; + ret Json.484; -procedure Json.96 (Json.97, Json.435, Json.95): - let Json.444 : I64 = 34i64; - let Json.443 : U8 = CallByName Num.125 Json.444; - let Json.441 : List U8 = CallByName List.4 Json.97 Json.443; - let Json.442 : List U8 = CallByName Str.12 Json.95; - let Json.438 : List U8 = CallByName List.8 Json.441 Json.442; - let Json.440 : I64 = 34i64; - let Json.439 : U8 = CallByName Num.125 Json.440; - let Json.437 : List U8 = CallByName List.4 Json.438 Json.439; - ret Json.437; +procedure List.139 (List.140, List.141, List.138): + let List.541 : {List U8, U64} = CallByName Json.120 List.140 List.141; + ret List.541; -procedure List.138 (List.139, List.140, List.137): - let List.525 : {List U8, U64} = CallByName Json.114 List.139 List.140; - ret List.525; - -procedure List.18 (List.135, List.136, List.137): - let List.506 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; - ret List.506; - -procedure List.4 (List.106, List.107): - let List.505 : U64 = 1i64; - let List.504 : List U8 = CallByName List.70 List.106 List.505; - let List.503 : List U8 = CallByName List.71 List.504 List.107; - ret List.503; - -procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; - -procedure List.6 (#Attr.2): - let List.528 : U64 = lowlevel ListLen #Attr.2; - ret List.528; - -procedure List.66 (#Attr.2, #Attr.3): - let List.522 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; +procedure List.18 (List.136, List.137, List.138): + let List.522 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; ret List.522; +procedure List.4 (List.107, List.108): + let List.521 : U64 = 1i64; + let List.520 : List U8 = CallByName List.70 List.107 List.521; + let List.519 : List U8 = CallByName List.71 List.520 List.108; + ret List.519; + +procedure List.6 (#Attr.2): + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; + +procedure List.6 (#Attr.2): + let List.544 : U64 = lowlevel ListLen #Attr.2; + ret List.544; + +procedure List.66 (#Attr.2, #Attr.3): + let List.538 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.538; + procedure List.70 (#Attr.2, #Attr.3): - let List.484 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.484; + let List.500 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.500; procedure List.71 (#Attr.2, #Attr.3): - let List.482 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.482; + let List.498 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.498; procedure List.8 (#Attr.2, #Attr.3): - let List.527 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; - ret List.527; + let List.543 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.543; -procedure List.90 (List.426, List.427, List.428): - let List.510 : U64 = 0i64; - let List.511 : U64 = CallByName List.6 List.426; - let List.509 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.510 List.511; - ret List.509; - -procedure List.91 (List.538, List.539, List.540, List.541, List.542): - joinpoint List.512 List.429 List.430 List.431 List.432 List.433: - let List.514 : Int1 = CallByName Num.22 List.432 List.433; - if List.514 then - let List.521 : {Str, Str} = CallByName List.66 List.429 List.432; - let List.515 : {List U8, U64} = CallByName List.138 List.430 List.521 List.431; - let List.518 : U64 = 1i64; - let List.517 : U64 = CallByName Num.19 List.432 List.518; - jump List.512 List.429 List.515 List.431 List.517 List.433; +procedure List.80 (List.554, List.555, List.556, List.557, List.558): + joinpoint List.528 List.433 List.434 List.435 List.436 List.437: + let List.530 : Int1 = CallByName Num.22 List.436 List.437; + if List.530 then + let List.537 : {Str, Str} = CallByName List.66 List.433 List.436; + let List.531 : {List U8, U64} = CallByName List.139 List.434 List.537 List.435; + let List.534 : U64 = 1i64; + let List.533 : U64 = CallByName Num.19 List.436 List.534; + jump List.528 List.433 List.531 List.435 List.533 List.437; else - ret List.430; + ret List.434; in - jump List.512 List.538 List.539 List.540 List.541 List.542; + jump List.528 List.554 List.555 List.556 List.557 List.558; -procedure Num.125 (#Attr.2): - let Num.263 : U8 = lowlevel NumIntCast #Attr.2; - ret Num.263; +procedure List.92 (List.430, List.431, List.432): + let List.526 : U64 = 0i64; + let List.527 : U64 = CallByName List.6 List.430; + let List.525 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.526 List.527; + ret List.525; + +procedure Num.127 (#Attr.2): + let Num.282 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.282; procedure Num.19 (#Attr.2, #Attr.3): - let Num.266 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.266; + let Num.285 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.285; procedure Num.20 (#Attr.2, #Attr.3): - let Num.264 : U64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.264; + let Num.283 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.283; procedure Num.22 (#Attr.2, #Attr.3): - let Num.267 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.267; + let Num.286 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.286; procedure Num.24 (#Attr.2, #Attr.3): - let Num.265 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; - ret Num.265; + let Num.284 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.284; procedure Str.12 (#Attr.2): - let Str.280 : List U8 = lowlevel StrToUtf8 #Attr.2; - ret Str.280; + let Str.281 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.281; procedure Str.48 (#Attr.2, #Attr.3, #Attr.4): - let Str.274 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; - ret Str.274; + let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; + ret Str.275; -procedure Str.9 (Str.76): - let Str.272 : U64 = 0i64; - let Str.273 : U64 = CallByName List.6 Str.76; - let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.272 Str.273; - let Str.269 : Int1 = StructAtIndex 2 Str.77; - if Str.269 then - let Str.271 : Str = StructAtIndex 1 Str.77; - inc Str.271; - dec Str.77; - let Str.270 : [C {U64, U8}, C Str] = TagId(1) Str.271; - ret Str.270; +procedure Str.9 (Str.77): + let Str.273 : U64 = 0i64; + let Str.274 : U64 = CallByName List.6 Str.77; + let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274; + let Str.270 : Int1 = StructAtIndex 2 Str.78; + if Str.270 then + let Str.272 : Str = StructAtIndex 1 Str.78; + inc Str.272; + dec Str.78; + let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272; + ret Str.271; else - let Str.267 : U8 = StructAtIndex 3 Str.77; - let Str.268 : U64 = StructAtIndex 0 Str.77; - dec Str.77; - let Str.266 : {U64, U8} = Struct {Str.268, Str.267}; - let Str.265 : [C {U64, U8}, C Str] = TagId(0) Str.266; - ret Str.265; + let Str.268 : U8 = StructAtIndex 3 Str.78; + let Str.269 : U64 = StructAtIndex 0 Str.78; + dec Str.78; + let Str.267 : {U64, U8} = Struct {Str.269, Str.268}; + let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267; + ret Str.266; procedure Test.0 (): let Test.11 : Str = "foo"; let Test.10 : {} = CallByName Json.1; - let Test.8 : List U8 = CallByName Encode.25 Test.11 Test.10; + let Test.8 : List U8 = CallByName Encode.26 Test.11 Test.10; let Test.1 : [C {U64, U8}, C Str] = CallByName Str.9 Test.8; let Test.5 : U8 = 1i64; let Test.6 : U8 = GetTagId Test.1; diff --git a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt index 829fbcd5a2..48701aa52b 100644 --- a/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt +++ b/crates/compiler/test_mono/generated/encode_derived_record_two_field_strings.txt @@ -1,5 +1,5 @@ procedure #Derived.0 (#Derived.1): - let #Derived_gen.0 : {Str, Str} = CallByName Encode.22 #Derived.1; + let #Derived_gen.0 : {Str, Str} = CallByName Encode.23 #Derived.1; ret #Derived_gen.0; procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1): @@ -16,226 +16,226 @@ procedure #Derived.2 (#Derived.3, #Derived.4, #Derived.1): let #Derived_gen.7 : {Str, Str} = Struct {#Derived_gen.8, #Derived_gen.9}; let #Derived_gen.5 : List {Str, Str} = Array [#Derived_gen.6, #Derived_gen.7]; let #Derived_gen.4 : List {Str, Str} = CallByName Json.20 #Derived_gen.5; - let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.3 #Derived_gen.4 #Derived.4; + let #Derived_gen.3 : List U8 = CallByName Encode.24 #Derived.3 #Derived_gen.4 #Derived.4; ret #Derived_gen.3; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.106 : List U8 = CallByName #Derived.2 Encode.94 Encode.96 Encode.102; - ret Encode.106; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.111 : List U8 = CallByName #Derived.2 Encode.99 Encode.101 Encode.107; + ret Encode.111; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.113 : List U8 = CallByName Json.112 Encode.94 Encode.96 Encode.102; - ret Encode.113; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.118 : List U8 = CallByName Json.118 Encode.99 Encode.101 Encode.107; + ret Encode.118; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.117 : List U8 = CallByName Json.96 Encode.94 Encode.96 Encode.102; - ret Encode.117; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.122 : List U8 = CallByName Json.102 Encode.99 Encode.101 Encode.107; + ret Encode.122; -procedure Encode.25 (Encode.100, Encode.101): - let Encode.104 : List U8 = Array []; - let Encode.105 : {Str, Str} = CallByName #Derived.0 Encode.100; - let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101; - ret Encode.103; +procedure Encode.26 (Encode.105, Encode.106): + let Encode.109 : List U8 = Array []; + let Encode.110 : {Str, Str} = CallByName #Derived.0 Encode.105; + let Encode.108 : List U8 = CallByName Encode.24 Encode.109 Encode.110 Encode.106; + ret Encode.108; procedure Json.1 (): - let Json.396 : {} = Struct {}; - ret Json.396; + let Json.483 : {} = Struct {}; + ret Json.483; -procedure Json.112 (Json.113, Json.399, Json.111): - let Json.432 : I64 = 123i64; - let Json.431 : U8 = CallByName Num.125 Json.432; - let Json.115 : List U8 = CallByName List.4 Json.113 Json.431; - let Json.430 : U64 = CallByName List.6 Json.111; - let Json.407 : {List U8, U64} = Struct {Json.115, Json.430}; - let Json.408 : {} = Struct {}; - let Json.406 : {List U8, U64} = CallByName List.18 Json.111 Json.407 Json.408; - dec Json.111; - let Json.117 : List U8 = StructAtIndex 0 Json.406; - inc Json.117; - dec Json.406; - let Json.405 : I64 = 125i64; - let Json.404 : U8 = CallByName Num.125 Json.405; - let Json.403 : List U8 = CallByName List.4 Json.117 Json.404; - ret Json.403; +procedure Json.102 (Json.103, Json.522, Json.101): + let Json.531 : I64 = 34i64; + let Json.530 : U8 = CallByName Num.127 Json.531; + let Json.528 : List U8 = CallByName List.4 Json.103 Json.530; + let Json.529 : List U8 = CallByName Str.12 Json.101; + let Json.525 : List U8 = CallByName List.8 Json.528 Json.529; + let Json.527 : I64 = 34i64; + let Json.526 : U8 = CallByName Num.127 Json.527; + let Json.524 : List U8 = CallByName List.4 Json.525 Json.526; + ret Json.524; -procedure Json.114 (Json.401, Json.402): - let Json.120 : Str = StructAtIndex 0 Json.402; - inc Json.120; - let Json.121 : Str = StructAtIndex 1 Json.402; - inc Json.121; - dec Json.402; - let Json.118 : List U8 = StructAtIndex 0 Json.401; - inc Json.118; - let Json.119 : U64 = StructAtIndex 1 Json.401; - dec Json.401; - let Json.429 : I64 = 34i64; - let Json.428 : U8 = CallByName Num.125 Json.429; - let Json.426 : List U8 = CallByName List.4 Json.118 Json.428; - let Json.427 : List U8 = CallByName Str.12 Json.120; - let Json.423 : List U8 = CallByName List.8 Json.426 Json.427; - let Json.425 : I64 = 34i64; - let Json.424 : U8 = CallByName Num.125 Json.425; - let Json.420 : List U8 = CallByName List.4 Json.423 Json.424; - let Json.422 : I64 = 58i64; - let Json.421 : U8 = CallByName Num.125 Json.422; - let Json.418 : List U8 = CallByName List.4 Json.420 Json.421; - let Json.419 : {} = Struct {}; - let Json.122 : List U8 = CallByName Encode.23 Json.418 Json.121 Json.419; - joinpoint Json.413 Json.123: - let Json.411 : U64 = 1i64; - let Json.410 : U64 = CallByName Num.20 Json.119 Json.411; - let Json.409 : {List U8, U64} = Struct {Json.123, Json.410}; - ret Json.409; +procedure Json.118 (Json.119, Json.486, Json.117): + let Json.519 : I64 = 123i64; + let Json.518 : U8 = CallByName Num.127 Json.519; + let Json.121 : List U8 = CallByName List.4 Json.119 Json.518; + let Json.517 : U64 = CallByName List.6 Json.117; + let Json.494 : {List U8, U64} = Struct {Json.121, Json.517}; + let Json.495 : {} = Struct {}; + let Json.493 : {List U8, U64} = CallByName List.18 Json.117 Json.494 Json.495; + dec Json.117; + let Json.123 : List U8 = StructAtIndex 0 Json.493; + inc Json.123; + dec Json.493; + let Json.492 : I64 = 125i64; + let Json.491 : U8 = CallByName Num.127 Json.492; + let Json.490 : List U8 = CallByName List.4 Json.123 Json.491; + ret Json.490; + +procedure Json.120 (Json.488, Json.489): + let Json.126 : Str = StructAtIndex 0 Json.489; + inc Json.126; + let Json.127 : Str = StructAtIndex 1 Json.489; + inc Json.127; + dec Json.489; + let Json.124 : List U8 = StructAtIndex 0 Json.488; + inc Json.124; + let Json.125 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.516 : I64 = 34i64; + let Json.515 : U8 = CallByName Num.127 Json.516; + let Json.513 : List U8 = CallByName List.4 Json.124 Json.515; + let Json.514 : List U8 = CallByName Str.12 Json.126; + let Json.510 : List U8 = CallByName List.8 Json.513 Json.514; + let Json.512 : I64 = 34i64; + let Json.511 : U8 = CallByName Num.127 Json.512; + let Json.507 : List U8 = CallByName List.4 Json.510 Json.511; + let Json.509 : I64 = 58i64; + let Json.508 : U8 = CallByName Num.127 Json.509; + let Json.505 : List U8 = CallByName List.4 Json.507 Json.508; + let Json.506 : {} = Struct {}; + let Json.128 : List U8 = CallByName Encode.24 Json.505 Json.127 Json.506; + joinpoint Json.500 Json.129: + let Json.498 : U64 = 1i64; + let Json.497 : U64 = CallByName Num.20 Json.125 Json.498; + let Json.496 : {List U8, U64} = Struct {Json.129, Json.497}; + ret Json.496; in - let Json.417 : U64 = 1i64; - let Json.414 : Int1 = CallByName Num.24 Json.119 Json.417; - if Json.414 then - let Json.416 : I64 = 44i64; - let Json.415 : U8 = CallByName Num.125 Json.416; - let Json.412 : List U8 = CallByName List.4 Json.122 Json.415; - jump Json.413 Json.412; + let Json.504 : U64 = 1i64; + let Json.501 : Int1 = CallByName Num.24 Json.125 Json.504; + if Json.501 then + let Json.503 : I64 = 44i64; + let Json.502 : U8 = CallByName Num.127 Json.503; + let Json.499 : List U8 = CallByName List.4 Json.128 Json.502; + jump Json.500 Json.499; else - jump Json.413 Json.122; + jump Json.500 Json.128; -procedure Json.18 (Json.95): - let Json.445 : Str = CallByName Encode.22 Json.95; - ret Json.445; +procedure Json.18 (Json.101): + let Json.532 : Str = CallByName Encode.23 Json.101; + ret Json.532; -procedure Json.20 (Json.111): - let Json.397 : List {Str, Str} = CallByName Encode.22 Json.111; - ret Json.397; +procedure Json.20 (Json.117): + let Json.484 : List {Str, Str} = CallByName Encode.23 Json.117; + ret Json.484; -procedure Json.96 (Json.97, Json.435, Json.95): - let Json.444 : I64 = 34i64; - let Json.443 : U8 = CallByName Num.125 Json.444; - let Json.441 : List U8 = CallByName List.4 Json.97 Json.443; - let Json.442 : List U8 = CallByName Str.12 Json.95; - let Json.438 : List U8 = CallByName List.8 Json.441 Json.442; - let Json.440 : I64 = 34i64; - let Json.439 : U8 = CallByName Num.125 Json.440; - let Json.437 : List U8 = CallByName List.4 Json.438 Json.439; - ret Json.437; +procedure List.139 (List.140, List.141, List.138): + let List.541 : {List U8, U64} = CallByName Json.120 List.140 List.141; + ret List.541; -procedure List.138 (List.139, List.140, List.137): - let List.525 : {List U8, U64} = CallByName Json.114 List.139 List.140; - ret List.525; - -procedure List.18 (List.135, List.136, List.137): - let List.506 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; - ret List.506; - -procedure List.4 (List.106, List.107): - let List.505 : U64 = 1i64; - let List.504 : List U8 = CallByName List.70 List.106 List.505; - let List.503 : List U8 = CallByName List.71 List.504 List.107; - ret List.503; - -procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; - -procedure List.6 (#Attr.2): - let List.528 : U64 = lowlevel ListLen #Attr.2; - ret List.528; - -procedure List.66 (#Attr.2, #Attr.3): - let List.522 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; +procedure List.18 (List.136, List.137, List.138): + let List.522 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; ret List.522; +procedure List.4 (List.107, List.108): + let List.521 : U64 = 1i64; + let List.520 : List U8 = CallByName List.70 List.107 List.521; + let List.519 : List U8 = CallByName List.71 List.520 List.108; + ret List.519; + +procedure List.6 (#Attr.2): + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; + +procedure List.6 (#Attr.2): + let List.544 : U64 = lowlevel ListLen #Attr.2; + ret List.544; + +procedure List.66 (#Attr.2, #Attr.3): + let List.538 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.538; + procedure List.70 (#Attr.2, #Attr.3): - let List.484 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.484; + let List.500 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.500; procedure List.71 (#Attr.2, #Attr.3): - let List.482 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.482; + let List.498 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.498; procedure List.8 (#Attr.2, #Attr.3): - let List.527 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; - ret List.527; + let List.543 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.543; -procedure List.90 (List.426, List.427, List.428): - let List.510 : U64 = 0i64; - let List.511 : U64 = CallByName List.6 List.426; - let List.509 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.510 List.511; - ret List.509; - -procedure List.91 (List.538, List.539, List.540, List.541, List.542): - joinpoint List.512 List.429 List.430 List.431 List.432 List.433: - let List.514 : Int1 = CallByName Num.22 List.432 List.433; - if List.514 then - let List.521 : {Str, Str} = CallByName List.66 List.429 List.432; - let List.515 : {List U8, U64} = CallByName List.138 List.430 List.521 List.431; - let List.518 : U64 = 1i64; - let List.517 : U64 = CallByName Num.19 List.432 List.518; - jump List.512 List.429 List.515 List.431 List.517 List.433; +procedure List.80 (List.554, List.555, List.556, List.557, List.558): + joinpoint List.528 List.433 List.434 List.435 List.436 List.437: + let List.530 : Int1 = CallByName Num.22 List.436 List.437; + if List.530 then + let List.537 : {Str, Str} = CallByName List.66 List.433 List.436; + let List.531 : {List U8, U64} = CallByName List.139 List.434 List.537 List.435; + let List.534 : U64 = 1i64; + let List.533 : U64 = CallByName Num.19 List.436 List.534; + jump List.528 List.433 List.531 List.435 List.533 List.437; else - ret List.430; + ret List.434; in - jump List.512 List.538 List.539 List.540 List.541 List.542; + jump List.528 List.554 List.555 List.556 List.557 List.558; -procedure Num.125 (#Attr.2): - let Num.263 : U8 = lowlevel NumIntCast #Attr.2; - ret Num.263; +procedure List.92 (List.430, List.431, List.432): + let List.526 : U64 = 0i64; + let List.527 : U64 = CallByName List.6 List.430; + let List.525 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.526 List.527; + ret List.525; + +procedure Num.127 (#Attr.2): + let Num.282 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.282; procedure Num.19 (#Attr.2, #Attr.3): - let Num.266 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.266; + let Num.285 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.285; procedure Num.20 (#Attr.2, #Attr.3): - let Num.264 : U64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.264; + let Num.283 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.283; procedure Num.22 (#Attr.2, #Attr.3): - let Num.267 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.267; + let Num.286 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.286; procedure Num.24 (#Attr.2, #Attr.3): - let Num.265 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; - ret Num.265; + let Num.284 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.284; procedure Str.12 (#Attr.2): - let Str.280 : List U8 = lowlevel StrToUtf8 #Attr.2; - ret Str.280; + let Str.281 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.281; procedure Str.48 (#Attr.2, #Attr.3, #Attr.4): - let Str.274 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; - ret Str.274; + let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; + ret Str.275; -procedure Str.9 (Str.76): - let Str.272 : U64 = 0i64; - let Str.273 : U64 = CallByName List.6 Str.76; - let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.272 Str.273; - let Str.269 : Int1 = StructAtIndex 2 Str.77; - if Str.269 then - let Str.271 : Str = StructAtIndex 1 Str.77; - inc Str.271; - dec Str.77; - let Str.270 : [C {U64, U8}, C Str] = TagId(1) Str.271; - ret Str.270; +procedure Str.9 (Str.77): + let Str.273 : U64 = 0i64; + let Str.274 : U64 = CallByName List.6 Str.77; + let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274; + let Str.270 : Int1 = StructAtIndex 2 Str.78; + if Str.270 then + let Str.272 : Str = StructAtIndex 1 Str.78; + inc Str.272; + dec Str.78; + let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272; + ret Str.271; else - let Str.267 : U8 = StructAtIndex 3 Str.77; - let Str.268 : U64 = StructAtIndex 0 Str.77; - dec Str.77; - let Str.266 : {U64, U8} = Struct {Str.268, Str.267}; - let Str.265 : [C {U64, U8}, C Str] = TagId(0) Str.266; - ret Str.265; + let Str.268 : U8 = StructAtIndex 3 Str.78; + let Str.269 : U64 = StructAtIndex 0 Str.78; + dec Str.78; + let Str.267 : {U64, U8} = Struct {Str.269, Str.268}; + let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267; + ret Str.266; procedure Test.0 (): let Test.11 : Str = "foo"; let Test.12 : Str = "bar"; let Test.9 : {Str, Str} = Struct {Test.11, Test.12}; let Test.10 : {} = CallByName Json.1; - let Test.8 : List U8 = CallByName Encode.25 Test.9 Test.10; + let Test.8 : List U8 = CallByName Encode.26 Test.9 Test.10; let Test.1 : [C {U64, U8}, C Str] = CallByName Str.9 Test.8; let Test.5 : U8 = 1i64; let Test.6 : U8 = GetTagId Test.1; diff --git a/crates/compiler/test_mono/generated/encode_derived_string.txt b/crates/compiler/test_mono/generated/encode_derived_string.txt index 7195cf4c87..daf607feda 100644 --- a/crates/compiler/test_mono/generated/encode_derived_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_string.txt @@ -1,92 +1,92 @@ -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.106 : List U8 = CallByName Json.96 Encode.94 Encode.96 Encode.102; - ret Encode.106; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.111 : List U8 = CallByName Json.102 Encode.99 Encode.101 Encode.107; + ret Encode.111; -procedure Encode.25 (Encode.100, Encode.101): - let Encode.104 : List U8 = Array []; - let Encode.105 : Str = CallByName Json.18 Encode.100; - let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101; - ret Encode.103; +procedure Encode.26 (Encode.105, Encode.106): + let Encode.109 : List U8 = Array []; + let Encode.110 : Str = CallByName Json.18 Encode.105; + let Encode.108 : List U8 = CallByName Encode.24 Encode.109 Encode.110 Encode.106; + ret Encode.108; procedure Json.1 (): - let Json.396 : {} = Struct {}; - ret Json.396; + let Json.483 : {} = Struct {}; + ret Json.483; -procedure Json.18 (Json.95): - let Json.397 : Str = CallByName Encode.22 Json.95; - ret Json.397; +procedure Json.102 (Json.103, Json.486, Json.101): + let Json.495 : I64 = 34i64; + let Json.494 : U8 = CallByName Num.127 Json.495; + let Json.492 : List U8 = CallByName List.4 Json.103 Json.494; + let Json.493 : List U8 = CallByName Str.12 Json.101; + let Json.489 : List U8 = CallByName List.8 Json.492 Json.493; + let Json.491 : I64 = 34i64; + let Json.490 : U8 = CallByName Num.127 Json.491; + let Json.488 : List U8 = CallByName List.4 Json.489 Json.490; + ret Json.488; -procedure Json.96 (Json.97, Json.399, Json.95): - let Json.408 : I64 = 34i64; - let Json.407 : U8 = CallByName Num.125 Json.408; - let Json.405 : List U8 = CallByName List.4 Json.97 Json.407; - let Json.406 : List U8 = CallByName Str.12 Json.95; - let Json.402 : List U8 = CallByName List.8 Json.405 Json.406; - let Json.404 : I64 = 34i64; - let Json.403 : U8 = CallByName Num.125 Json.404; - let Json.401 : List U8 = CallByName List.4 Json.402 Json.403; - ret Json.401; +procedure Json.18 (Json.101): + let Json.484 : Str = CallByName Encode.23 Json.101; + ret Json.484; -procedure List.4 (List.106, List.107): - let List.487 : U64 = 1i64; - let List.486 : List U8 = CallByName List.70 List.106 List.487; - let List.485 : List U8 = CallByName List.71 List.486 List.107; - ret List.485; +procedure List.4 (List.107, List.108): + let List.503 : U64 = 1i64; + let List.502 : List U8 = CallByName List.70 List.107 List.503; + let List.501 : List U8 = CallByName List.71 List.502 List.108; + ret List.501; procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; procedure List.70 (#Attr.2, #Attr.3): - let List.484 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.484; + let List.500 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.500; procedure List.71 (#Attr.2, #Attr.3): - let List.482 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.482; + let List.498 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.498; procedure List.8 (#Attr.2, #Attr.3): - let List.488 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; - ret List.488; + let List.504 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.504; -procedure Num.125 (#Attr.2): - let Num.257 : U8 = lowlevel NumIntCast #Attr.2; - ret Num.257; +procedure Num.127 (#Attr.2): + let Num.276 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.276; procedure Str.12 (#Attr.2): - let Str.279 : List U8 = lowlevel StrToUtf8 #Attr.2; - ret Str.279; + let Str.280 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.280; procedure Str.48 (#Attr.2, #Attr.3, #Attr.4): - let Str.274 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; - ret Str.274; + let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; + ret Str.275; -procedure Str.9 (Str.76): - let Str.272 : U64 = 0i64; - let Str.273 : U64 = CallByName List.6 Str.76; - let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.272 Str.273; - let Str.269 : Int1 = StructAtIndex 2 Str.77; - if Str.269 then - let Str.271 : Str = StructAtIndex 1 Str.77; - inc Str.271; - dec Str.77; - let Str.270 : [C {U64, U8}, C Str] = TagId(1) Str.271; - ret Str.270; +procedure Str.9 (Str.77): + let Str.273 : U64 = 0i64; + let Str.274 : U64 = CallByName List.6 Str.77; + let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274; + let Str.270 : Int1 = StructAtIndex 2 Str.78; + if Str.270 then + let Str.272 : Str = StructAtIndex 1 Str.78; + inc Str.272; + dec Str.78; + let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272; + ret Str.271; else - let Str.267 : U8 = StructAtIndex 3 Str.77; - let Str.268 : U64 = StructAtIndex 0 Str.77; - dec Str.77; - let Str.266 : {U64, U8} = Struct {Str.268, Str.267}; - let Str.265 : [C {U64, U8}, C Str] = TagId(0) Str.266; - ret Str.265; + let Str.268 : U8 = StructAtIndex 3 Str.78; + let Str.269 : U64 = StructAtIndex 0 Str.78; + dec Str.78; + let Str.267 : {U64, U8} = Struct {Str.269, Str.268}; + let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267; + ret Str.266; procedure Test.0 (): let Test.9 : Str = "abc"; let Test.10 : {} = CallByName Json.1; - let Test.8 : List U8 = CallByName Encode.25 Test.9 Test.10; + let Test.8 : List U8 = CallByName Encode.26 Test.9 Test.10; let Test.1 : [C {U64, U8}, C Str] = CallByName Str.9 Test.8; let Test.5 : U8 = 1i64; let Test.6 : U8 = GetTagId Test.1; diff --git a/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt index 40580ef311..179e245b1f 100644 --- a/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt @@ -1,240 +1,240 @@ procedure #Derived.0 (#Derived.1): - let #Derived_gen.0 : Str = CallByName Encode.22 #Derived.1; + let #Derived_gen.0 : Str = CallByName Encode.23 #Derived.1; ret #Derived_gen.0; procedure #Derived.3 (#Derived.4, #Derived.5, #Derived.1): joinpoint #Derived_gen.5 #Derived_gen.4: - let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.4 #Derived_gen.4 #Derived.5; + let #Derived_gen.3 : List U8 = CallByName Encode.24 #Derived.4 #Derived_gen.4 #Derived.5; ret #Derived_gen.3; in let #Derived_gen.7 : Str = "A"; let #Derived_gen.9 : Str = CallByName Json.18 #Derived.1; let #Derived_gen.8 : List Str = Array [#Derived_gen.9]; - let #Derived_gen.6 : {Str, List Str} = CallByName Json.21 #Derived_gen.7 #Derived_gen.8; + let #Derived_gen.6 : {Str, List Str} = CallByName Json.22 #Derived_gen.7 #Derived_gen.8; jump #Derived_gen.5 #Derived_gen.6; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.106 : List U8 = CallByName #Derived.3 Encode.94 Encode.96 Encode.102; - ret Encode.106; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.111 : List U8 = CallByName #Derived.3 Encode.99 Encode.101 Encode.107; + ret Encode.111; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.113 : List U8 = CallByName Json.126 Encode.94 Encode.96 Encode.102; - ret Encode.113; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.118 : List U8 = CallByName Json.144 Encode.99 Encode.101 Encode.107; + ret Encode.118; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.116 : List U8 = CallByName Json.96 Encode.94 Encode.96 Encode.102; - ret Encode.116; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.121 : List U8 = CallByName Json.102 Encode.99 Encode.101 Encode.107; + ret Encode.121; -procedure Encode.25 (Encode.100, Encode.101): - let Encode.104 : List U8 = Array []; - let Encode.105 : Str = CallByName #Derived.0 Encode.100; - let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101; - ret Encode.103; +procedure Encode.26 (Encode.105, Encode.106): + let Encode.109 : List U8 = Array []; + let Encode.110 : Str = CallByName #Derived.0 Encode.105; + let Encode.108 : List U8 = CallByName Encode.24 Encode.109 Encode.110 Encode.106; + ret Encode.108; procedure Json.1 (): - let Json.396 : {} = Struct {}; - ret Json.396; + let Json.483 : {} = Struct {}; + ret Json.483; -procedure Json.126 (Json.127, Json.399, #Attr.12): - let Json.125 : List Str = StructAtIndex 1 #Attr.12; - inc Json.125; - let Json.124 : Str = StructAtIndex 0 #Attr.12; - inc Json.124; +procedure Json.102 (Json.103, Json.527, Json.101): + let Json.536 : I64 = 34i64; + let Json.535 : U8 = CallByName Num.127 Json.536; + let Json.533 : List U8 = CallByName List.4 Json.103 Json.535; + let Json.534 : List U8 = CallByName Str.12 Json.101; + let Json.530 : List U8 = CallByName List.8 Json.533 Json.534; + let Json.532 : I64 = 34i64; + let Json.531 : U8 = CallByName Num.127 Json.532; + let Json.529 : List U8 = CallByName List.4 Json.530 Json.531; + ret Json.529; + +procedure Json.144 (Json.145, Json.486, #Attr.12): + let Json.143 : List Str = StructAtIndex 1 #Attr.12; + inc Json.143; + let Json.142 : Str = StructAtIndex 0 #Attr.12; + inc Json.142; dec #Attr.12; - let Json.437 : I64 = 123i64; - let Json.436 : U8 = CallByName Num.125 Json.437; - let Json.433 : List U8 = CallByName List.4 Json.127 Json.436; - let Json.435 : I64 = 34i64; - let Json.434 : U8 = CallByName Num.125 Json.435; - let Json.431 : List U8 = CallByName List.4 Json.433 Json.434; - let Json.432 : List U8 = CallByName Str.12 Json.124; - let Json.428 : List U8 = CallByName List.8 Json.431 Json.432; - let Json.430 : I64 = 34i64; - let Json.429 : U8 = CallByName Num.125 Json.430; - let Json.425 : List U8 = CallByName List.4 Json.428 Json.429; - let Json.427 : I64 = 58i64; - let Json.426 : U8 = CallByName Num.125 Json.427; - let Json.422 : List U8 = CallByName List.4 Json.425 Json.426; - let Json.424 : I64 = 91i64; - let Json.423 : U8 = CallByName Num.125 Json.424; - let Json.129 : List U8 = CallByName List.4 Json.422 Json.423; - let Json.421 : U64 = CallByName List.6 Json.125; - let Json.409 : {List U8, U64} = Struct {Json.129, Json.421}; - let Json.410 : {} = Struct {}; - let Json.408 : {List U8, U64} = CallByName List.18 Json.125 Json.409 Json.410; - dec Json.125; - let Json.131 : List U8 = StructAtIndex 0 Json.408; - inc Json.131; - dec Json.408; - let Json.407 : I64 = 93i64; - let Json.406 : U8 = CallByName Num.125 Json.407; - let Json.403 : List U8 = CallByName List.4 Json.131 Json.406; - let Json.405 : I64 = 125i64; - let Json.404 : U8 = CallByName Num.125 Json.405; - let Json.402 : List U8 = CallByName List.4 Json.403 Json.404; - ret Json.402; + let Json.524 : I64 = 123i64; + let Json.523 : U8 = CallByName Num.127 Json.524; + let Json.520 : List U8 = CallByName List.4 Json.145 Json.523; + let Json.522 : I64 = 34i64; + let Json.521 : U8 = CallByName Num.127 Json.522; + let Json.518 : List U8 = CallByName List.4 Json.520 Json.521; + let Json.519 : List U8 = CallByName Str.12 Json.142; + let Json.515 : List U8 = CallByName List.8 Json.518 Json.519; + let Json.517 : I64 = 34i64; + let Json.516 : U8 = CallByName Num.127 Json.517; + let Json.512 : List U8 = CallByName List.4 Json.515 Json.516; + let Json.514 : I64 = 58i64; + let Json.513 : U8 = CallByName Num.127 Json.514; + let Json.509 : List U8 = CallByName List.4 Json.512 Json.513; + let Json.511 : I64 = 91i64; + let Json.510 : U8 = CallByName Num.127 Json.511; + let Json.147 : List U8 = CallByName List.4 Json.509 Json.510; + let Json.508 : U64 = CallByName List.6 Json.143; + let Json.496 : {List U8, U64} = Struct {Json.147, Json.508}; + let Json.497 : {} = Struct {}; + let Json.495 : {List U8, U64} = CallByName List.18 Json.143 Json.496 Json.497; + dec Json.143; + let Json.149 : List U8 = StructAtIndex 0 Json.495; + inc Json.149; + dec Json.495; + let Json.494 : I64 = 93i64; + let Json.493 : U8 = CallByName Num.127 Json.494; + let Json.490 : List U8 = CallByName List.4 Json.149 Json.493; + let Json.492 : I64 = 125i64; + let Json.491 : U8 = CallByName Num.127 Json.492; + let Json.489 : List U8 = CallByName List.4 Json.490 Json.491; + ret Json.489; -procedure Json.128 (Json.401, Json.134): - let Json.132 : List U8 = StructAtIndex 0 Json.401; - inc Json.132; - let Json.133 : U64 = StructAtIndex 1 Json.401; - dec Json.401; - let Json.420 : {} = Struct {}; - let Json.135 : List U8 = CallByName Encode.23 Json.132 Json.134 Json.420; - joinpoint Json.415 Json.136: - let Json.413 : U64 = 1i64; - let Json.412 : U64 = CallByName Num.20 Json.133 Json.413; - let Json.411 : {List U8, U64} = Struct {Json.136, Json.412}; - ret Json.411; +procedure Json.146 (Json.488, Json.152): + let Json.150 : List U8 = StructAtIndex 0 Json.488; + inc Json.150; + let Json.151 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.507 : {} = Struct {}; + let Json.153 : List U8 = CallByName Encode.24 Json.150 Json.152 Json.507; + joinpoint Json.502 Json.154: + let Json.500 : U64 = 1i64; + let Json.499 : U64 = CallByName Num.20 Json.151 Json.500; + let Json.498 : {List U8, U64} = Struct {Json.154, Json.499}; + ret Json.498; in - let Json.419 : U64 = 1i64; - let Json.416 : Int1 = CallByName Num.24 Json.133 Json.419; - if Json.416 then - let Json.418 : I64 = 44i64; - let Json.417 : U8 = CallByName Num.125 Json.418; - let Json.414 : List U8 = CallByName List.4 Json.135 Json.417; - jump Json.415 Json.414; + let Json.506 : U64 = 1i64; + let Json.503 : Int1 = CallByName Num.24 Json.151 Json.506; + if Json.503 then + let Json.505 : I64 = 44i64; + let Json.504 : U8 = CallByName Num.127 Json.505; + let Json.501 : List U8 = CallByName List.4 Json.153 Json.504; + jump Json.502 Json.501; else - jump Json.415 Json.135; + jump Json.502 Json.153; -procedure Json.18 (Json.95): - let Json.438 : Str = CallByName Encode.22 Json.95; - ret Json.438; +procedure Json.18 (Json.101): + let Json.525 : Str = CallByName Encode.23 Json.101; + ret Json.525; -procedure Json.21 (Json.124, Json.125): - let Json.398 : {Str, List Str} = Struct {Json.124, Json.125}; - let Json.397 : {Str, List Str} = CallByName Encode.22 Json.398; - ret Json.397; +procedure Json.22 (Json.142, Json.143): + let Json.485 : {Str, List Str} = Struct {Json.142, Json.143}; + let Json.484 : {Str, List Str} = CallByName Encode.23 Json.485; + ret Json.484; -procedure Json.96 (Json.97, Json.440, Json.95): - let Json.449 : I64 = 34i64; - let Json.448 : U8 = CallByName Num.125 Json.449; - let Json.446 : List U8 = CallByName List.4 Json.97 Json.448; - let Json.447 : List U8 = CallByName Str.12 Json.95; - let Json.443 : List U8 = CallByName List.8 Json.446 Json.447; - let Json.445 : I64 = 34i64; - let Json.444 : U8 = CallByName Num.125 Json.445; - let Json.442 : List U8 = CallByName List.4 Json.443 Json.444; - ret Json.442; +procedure List.139 (List.140, List.141, List.138): + let List.547 : {List U8, U64} = CallByName Json.146 List.140 List.141; + ret List.547; -procedure List.138 (List.139, List.140, List.137): - let List.531 : {List U8, U64} = CallByName Json.128 List.139 List.140; - ret List.531; - -procedure List.18 (List.135, List.136, List.137): - let List.512 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; - ret List.512; - -procedure List.4 (List.106, List.107): - let List.511 : U64 = 1i64; - let List.510 : List U8 = CallByName List.70 List.106 List.511; - let List.509 : List U8 = CallByName List.71 List.510 List.107; - ret List.509; - -procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; - -procedure List.6 (#Attr.2): - let List.532 : U64 = lowlevel ListLen #Attr.2; - ret List.532; - -procedure List.66 (#Attr.2, #Attr.3): - let List.528 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; +procedure List.18 (List.136, List.137, List.138): + let List.528 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; ret List.528; +procedure List.4 (List.107, List.108): + let List.527 : U64 = 1i64; + let List.526 : List U8 = CallByName List.70 List.107 List.527; + let List.525 : List U8 = CallByName List.71 List.526 List.108; + ret List.525; + +procedure List.6 (#Attr.2): + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; + +procedure List.6 (#Attr.2): + let List.548 : U64 = lowlevel ListLen #Attr.2; + ret List.548; + +procedure List.66 (#Attr.2, #Attr.3): + let List.544 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.544; + procedure List.70 (#Attr.2, #Attr.3): - let List.484 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.484; + let List.500 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.500; procedure List.71 (#Attr.2, #Attr.3): - let List.482 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.482; + let List.498 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.498; procedure List.8 (#Attr.2, #Attr.3): - let List.534 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; - ret List.534; + let List.550 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.550; -procedure List.90 (List.426, List.427, List.428): - let List.516 : U64 = 0i64; - let List.517 : U64 = CallByName List.6 List.426; - let List.515 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.516 List.517; - ret List.515; - -procedure List.91 (List.544, List.545, List.546, List.547, List.548): - joinpoint List.518 List.429 List.430 List.431 List.432 List.433: - let List.520 : Int1 = CallByName Num.22 List.432 List.433; - if List.520 then - let List.527 : Str = CallByName List.66 List.429 List.432; - let List.521 : {List U8, U64} = CallByName List.138 List.430 List.527 List.431; - let List.524 : U64 = 1i64; - let List.523 : U64 = CallByName Num.19 List.432 List.524; - jump List.518 List.429 List.521 List.431 List.523 List.433; +procedure List.80 (List.560, List.561, List.562, List.563, List.564): + joinpoint List.534 List.433 List.434 List.435 List.436 List.437: + let List.536 : Int1 = CallByName Num.22 List.436 List.437; + if List.536 then + let List.543 : Str = CallByName List.66 List.433 List.436; + let List.537 : {List U8, U64} = CallByName List.139 List.434 List.543 List.435; + let List.540 : U64 = 1i64; + let List.539 : U64 = CallByName Num.19 List.436 List.540; + jump List.534 List.433 List.537 List.435 List.539 List.437; else - ret List.430; + ret List.434; in - jump List.518 List.544 List.545 List.546 List.547 List.548; + jump List.534 List.560 List.561 List.562 List.563 List.564; -procedure Num.125 (#Attr.2): - let Num.265 : U8 = lowlevel NumIntCast #Attr.2; - ret Num.265; +procedure List.92 (List.430, List.431, List.432): + let List.532 : U64 = 0i64; + let List.533 : U64 = CallByName List.6 List.430; + let List.531 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.532 List.533; + ret List.531; + +procedure Num.127 (#Attr.2): + let Num.284 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.284; procedure Num.19 (#Attr.2, #Attr.3): - let Num.268 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.268; + let Num.287 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.287; procedure Num.20 (#Attr.2, #Attr.3): - let Num.266 : U64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.266; + let Num.285 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.285; procedure Num.22 (#Attr.2, #Attr.3): - let Num.269 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.269; + let Num.288 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.288; procedure Num.24 (#Attr.2, #Attr.3): - let Num.267 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; - ret Num.267; + let Num.286 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.286; procedure Str.12 (#Attr.2): - let Str.280 : List U8 = lowlevel StrToUtf8 #Attr.2; - ret Str.280; + let Str.281 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.281; procedure Str.48 (#Attr.2, #Attr.3, #Attr.4): - let Str.274 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; - ret Str.274; + let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; + ret Str.275; -procedure Str.9 (Str.76): - let Str.272 : U64 = 0i64; - let Str.273 : U64 = CallByName List.6 Str.76; - let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.272 Str.273; - let Str.269 : Int1 = StructAtIndex 2 Str.77; - if Str.269 then - let Str.271 : Str = StructAtIndex 1 Str.77; - inc Str.271; - dec Str.77; - let Str.270 : [C {U64, U8}, C Str] = TagId(1) Str.271; - ret Str.270; +procedure Str.9 (Str.77): + let Str.273 : U64 = 0i64; + let Str.274 : U64 = CallByName List.6 Str.77; + let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274; + let Str.270 : Int1 = StructAtIndex 2 Str.78; + if Str.270 then + let Str.272 : Str = StructAtIndex 1 Str.78; + inc Str.272; + dec Str.78; + let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272; + ret Str.271; else - let Str.267 : U8 = StructAtIndex 3 Str.77; - let Str.268 : U64 = StructAtIndex 0 Str.77; - dec Str.77; - let Str.266 : {U64, U8} = Struct {Str.268, Str.267}; - let Str.265 : [C {U64, U8}, C Str] = TagId(0) Str.266; - ret Str.265; + let Str.268 : U8 = StructAtIndex 3 Str.78; + let Str.269 : U64 = StructAtIndex 0 Str.78; + dec Str.78; + let Str.267 : {U64, U8} = Struct {Str.269, Str.268}; + let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267; + ret Str.266; procedure Test.0 (): let Test.12 : Str = "foo"; let Test.11 : {} = CallByName Json.1; - let Test.10 : List U8 = CallByName Encode.25 Test.12 Test.11; + let Test.10 : List U8 = CallByName Encode.26 Test.12 Test.11; let Test.2 : [C {U64, U8}, C Str] = CallByName Str.9 Test.10; let Test.7 : U8 = 1i64; let Test.8 : U8 = GetTagId Test.2; diff --git a/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt b/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt index 72a865f670..fbb31a4ace 100644 --- a/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_tag_two_payloads_string.txt @@ -1,10 +1,10 @@ procedure #Derived.0 (#Derived.1): - let #Derived_gen.0 : {Str, Str} = CallByName Encode.22 #Derived.1; + let #Derived_gen.0 : {Str, Str} = CallByName Encode.23 #Derived.1; ret #Derived_gen.0; procedure #Derived.4 (#Derived.5, #Derived.6, #Derived.1): joinpoint #Derived_gen.5 #Derived_gen.4: - let #Derived_gen.3 : List U8 = CallByName Encode.23 #Derived.5 #Derived_gen.4 #Derived.6; + let #Derived_gen.3 : List U8 = CallByName Encode.24 #Derived.5 #Derived_gen.4 #Derived.6; ret #Derived_gen.3; in let #Derived.2 : Str = StructAtIndex 0 #Derived.1; @@ -16,233 +16,233 @@ procedure #Derived.4 (#Derived.5, #Derived.6, #Derived.1): let #Derived_gen.9 : Str = CallByName Json.18 #Derived.2; let #Derived_gen.10 : Str = CallByName Json.18 #Derived.3; let #Derived_gen.8 : List Str = Array [#Derived_gen.9, #Derived_gen.10]; - let #Derived_gen.6 : {Str, List Str} = CallByName Json.21 #Derived_gen.7 #Derived_gen.8; + let #Derived_gen.6 : {Str, List Str} = CallByName Json.22 #Derived_gen.7 #Derived_gen.8; jump #Derived_gen.5 #Derived_gen.6; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.22 (Encode.93): - ret Encode.93; +procedure Encode.23 (Encode.98): + ret Encode.98; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.106 : List U8 = CallByName #Derived.4 Encode.94 Encode.96 Encode.102; - ret Encode.106; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.111 : List U8 = CallByName #Derived.4 Encode.99 Encode.101 Encode.107; + ret Encode.111; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.113 : List U8 = CallByName Json.126 Encode.94 Encode.96 Encode.102; - ret Encode.113; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.118 : List U8 = CallByName Json.144 Encode.99 Encode.101 Encode.107; + ret Encode.118; -procedure Encode.23 (Encode.94, Encode.102, Encode.96): - let Encode.117 : List U8 = CallByName Json.96 Encode.94 Encode.96 Encode.102; - ret Encode.117; +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.122 : List U8 = CallByName Json.102 Encode.99 Encode.101 Encode.107; + ret Encode.122; -procedure Encode.25 (Encode.100, Encode.101): - let Encode.104 : List U8 = Array []; - let Encode.105 : {Str, Str} = CallByName #Derived.0 Encode.100; - let Encode.103 : List U8 = CallByName Encode.23 Encode.104 Encode.105 Encode.101; - ret Encode.103; +procedure Encode.26 (Encode.105, Encode.106): + let Encode.109 : List U8 = Array []; + let Encode.110 : {Str, Str} = CallByName #Derived.0 Encode.105; + let Encode.108 : List U8 = CallByName Encode.24 Encode.109 Encode.110 Encode.106; + ret Encode.108; procedure Json.1 (): - let Json.396 : {} = Struct {}; - ret Json.396; + let Json.483 : {} = Struct {}; + ret Json.483; -procedure Json.126 (Json.127, Json.399, #Attr.12): - let Json.125 : List Str = StructAtIndex 1 #Attr.12; - inc Json.125; - let Json.124 : Str = StructAtIndex 0 #Attr.12; - inc Json.124; +procedure Json.102 (Json.103, Json.527, Json.101): + let Json.536 : I64 = 34i64; + let Json.535 : U8 = CallByName Num.127 Json.536; + let Json.533 : List U8 = CallByName List.4 Json.103 Json.535; + let Json.534 : List U8 = CallByName Str.12 Json.101; + let Json.530 : List U8 = CallByName List.8 Json.533 Json.534; + let Json.532 : I64 = 34i64; + let Json.531 : U8 = CallByName Num.127 Json.532; + let Json.529 : List U8 = CallByName List.4 Json.530 Json.531; + ret Json.529; + +procedure Json.144 (Json.145, Json.486, #Attr.12): + let Json.143 : List Str = StructAtIndex 1 #Attr.12; + inc Json.143; + let Json.142 : Str = StructAtIndex 0 #Attr.12; + inc Json.142; dec #Attr.12; - let Json.437 : I64 = 123i64; - let Json.436 : U8 = CallByName Num.125 Json.437; - let Json.433 : List U8 = CallByName List.4 Json.127 Json.436; - let Json.435 : I64 = 34i64; - let Json.434 : U8 = CallByName Num.125 Json.435; - let Json.431 : List U8 = CallByName List.4 Json.433 Json.434; - let Json.432 : List U8 = CallByName Str.12 Json.124; - let Json.428 : List U8 = CallByName List.8 Json.431 Json.432; - let Json.430 : I64 = 34i64; - let Json.429 : U8 = CallByName Num.125 Json.430; - let Json.425 : List U8 = CallByName List.4 Json.428 Json.429; - let Json.427 : I64 = 58i64; - let Json.426 : U8 = CallByName Num.125 Json.427; - let Json.422 : List U8 = CallByName List.4 Json.425 Json.426; - let Json.424 : I64 = 91i64; - let Json.423 : U8 = CallByName Num.125 Json.424; - let Json.129 : List U8 = CallByName List.4 Json.422 Json.423; - let Json.421 : U64 = CallByName List.6 Json.125; - let Json.409 : {List U8, U64} = Struct {Json.129, Json.421}; - let Json.410 : {} = Struct {}; - let Json.408 : {List U8, U64} = CallByName List.18 Json.125 Json.409 Json.410; - dec Json.125; - let Json.131 : List U8 = StructAtIndex 0 Json.408; - inc Json.131; - dec Json.408; - let Json.407 : I64 = 93i64; - let Json.406 : U8 = CallByName Num.125 Json.407; - let Json.403 : List U8 = CallByName List.4 Json.131 Json.406; - let Json.405 : I64 = 125i64; - let Json.404 : U8 = CallByName Num.125 Json.405; - let Json.402 : List U8 = CallByName List.4 Json.403 Json.404; - ret Json.402; + let Json.524 : I64 = 123i64; + let Json.523 : U8 = CallByName Num.127 Json.524; + let Json.520 : List U8 = CallByName List.4 Json.145 Json.523; + let Json.522 : I64 = 34i64; + let Json.521 : U8 = CallByName Num.127 Json.522; + let Json.518 : List U8 = CallByName List.4 Json.520 Json.521; + let Json.519 : List U8 = CallByName Str.12 Json.142; + let Json.515 : List U8 = CallByName List.8 Json.518 Json.519; + let Json.517 : I64 = 34i64; + let Json.516 : U8 = CallByName Num.127 Json.517; + let Json.512 : List U8 = CallByName List.4 Json.515 Json.516; + let Json.514 : I64 = 58i64; + let Json.513 : U8 = CallByName Num.127 Json.514; + let Json.509 : List U8 = CallByName List.4 Json.512 Json.513; + let Json.511 : I64 = 91i64; + let Json.510 : U8 = CallByName Num.127 Json.511; + let Json.147 : List U8 = CallByName List.4 Json.509 Json.510; + let Json.508 : U64 = CallByName List.6 Json.143; + let Json.496 : {List U8, U64} = Struct {Json.147, Json.508}; + let Json.497 : {} = Struct {}; + let Json.495 : {List U8, U64} = CallByName List.18 Json.143 Json.496 Json.497; + dec Json.143; + let Json.149 : List U8 = StructAtIndex 0 Json.495; + inc Json.149; + dec Json.495; + let Json.494 : I64 = 93i64; + let Json.493 : U8 = CallByName Num.127 Json.494; + let Json.490 : List U8 = CallByName List.4 Json.149 Json.493; + let Json.492 : I64 = 125i64; + let Json.491 : U8 = CallByName Num.127 Json.492; + let Json.489 : List U8 = CallByName List.4 Json.490 Json.491; + ret Json.489; -procedure Json.128 (Json.401, Json.134): - let Json.132 : List U8 = StructAtIndex 0 Json.401; - inc Json.132; - let Json.133 : U64 = StructAtIndex 1 Json.401; - dec Json.401; - let Json.420 : {} = Struct {}; - let Json.135 : List U8 = CallByName Encode.23 Json.132 Json.134 Json.420; - joinpoint Json.415 Json.136: - let Json.413 : U64 = 1i64; - let Json.412 : U64 = CallByName Num.20 Json.133 Json.413; - let Json.411 : {List U8, U64} = Struct {Json.136, Json.412}; - ret Json.411; +procedure Json.146 (Json.488, Json.152): + let Json.150 : List U8 = StructAtIndex 0 Json.488; + inc Json.150; + let Json.151 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.507 : {} = Struct {}; + let Json.153 : List U8 = CallByName Encode.24 Json.150 Json.152 Json.507; + joinpoint Json.502 Json.154: + let Json.500 : U64 = 1i64; + let Json.499 : U64 = CallByName Num.20 Json.151 Json.500; + let Json.498 : {List U8, U64} = Struct {Json.154, Json.499}; + ret Json.498; in - let Json.419 : U64 = 1i64; - let Json.416 : Int1 = CallByName Num.24 Json.133 Json.419; - if Json.416 then - let Json.418 : I64 = 44i64; - let Json.417 : U8 = CallByName Num.125 Json.418; - let Json.414 : List U8 = CallByName List.4 Json.135 Json.417; - jump Json.415 Json.414; + let Json.506 : U64 = 1i64; + let Json.503 : Int1 = CallByName Num.24 Json.151 Json.506; + if Json.503 then + let Json.505 : I64 = 44i64; + let Json.504 : U8 = CallByName Num.127 Json.505; + let Json.501 : List U8 = CallByName List.4 Json.153 Json.504; + jump Json.502 Json.501; else - jump Json.415 Json.135; + jump Json.502 Json.153; -procedure Json.18 (Json.95): - let Json.450 : Str = CallByName Encode.22 Json.95; - ret Json.450; +procedure Json.18 (Json.101): + let Json.537 : Str = CallByName Encode.23 Json.101; + ret Json.537; -procedure Json.21 (Json.124, Json.125): - let Json.398 : {Str, List Str} = Struct {Json.124, Json.125}; - let Json.397 : {Str, List Str} = CallByName Encode.22 Json.398; - ret Json.397; +procedure Json.22 (Json.142, Json.143): + let Json.485 : {Str, List Str} = Struct {Json.142, Json.143}; + let Json.484 : {Str, List Str} = CallByName Encode.23 Json.485; + ret Json.484; -procedure Json.96 (Json.97, Json.440, Json.95): - let Json.449 : I64 = 34i64; - let Json.448 : U8 = CallByName Num.125 Json.449; - let Json.446 : List U8 = CallByName List.4 Json.97 Json.448; - let Json.447 : List U8 = CallByName Str.12 Json.95; - let Json.443 : List U8 = CallByName List.8 Json.446 Json.447; - let Json.445 : I64 = 34i64; - let Json.444 : U8 = CallByName Num.125 Json.445; - let Json.442 : List U8 = CallByName List.4 Json.443 Json.444; - ret Json.442; +procedure List.139 (List.140, List.141, List.138): + let List.547 : {List U8, U64} = CallByName Json.146 List.140 List.141; + ret List.547; -procedure List.138 (List.139, List.140, List.137): - let List.531 : {List U8, U64} = CallByName Json.128 List.139 List.140; - ret List.531; - -procedure List.18 (List.135, List.136, List.137): - let List.512 : {List U8, U64} = CallByName List.90 List.135 List.136 List.137; - ret List.512; - -procedure List.4 (List.106, List.107): - let List.511 : U64 = 1i64; - let List.510 : List U8 = CallByName List.70 List.106 List.511; - let List.509 : List U8 = CallByName List.71 List.510 List.107; - ret List.509; - -procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; - -procedure List.6 (#Attr.2): - let List.532 : U64 = lowlevel ListLen #Attr.2; - ret List.532; - -procedure List.66 (#Attr.2, #Attr.3): - let List.528 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; +procedure List.18 (List.136, List.137, List.138): + let List.528 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; ret List.528; +procedure List.4 (List.107, List.108): + let List.527 : U64 = 1i64; + let List.526 : List U8 = CallByName List.70 List.107 List.527; + let List.525 : List U8 = CallByName List.71 List.526 List.108; + ret List.525; + +procedure List.6 (#Attr.2): + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; + +procedure List.6 (#Attr.2): + let List.548 : U64 = lowlevel ListLen #Attr.2; + ret List.548; + +procedure List.66 (#Attr.2, #Attr.3): + let List.544 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.544; + procedure List.70 (#Attr.2, #Attr.3): - let List.484 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.484; + let List.500 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.500; procedure List.71 (#Attr.2, #Attr.3): - let List.482 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.482; + let List.498 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.498; procedure List.8 (#Attr.2, #Attr.3): - let List.534 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; - ret List.534; + let List.550 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.550; -procedure List.90 (List.426, List.427, List.428): - let List.516 : U64 = 0i64; - let List.517 : U64 = CallByName List.6 List.426; - let List.515 : {List U8, U64} = CallByName List.91 List.426 List.427 List.428 List.516 List.517; - ret List.515; - -procedure List.91 (List.544, List.545, List.546, List.547, List.548): - joinpoint List.518 List.429 List.430 List.431 List.432 List.433: - let List.520 : Int1 = CallByName Num.22 List.432 List.433; - if List.520 then - let List.527 : Str = CallByName List.66 List.429 List.432; - let List.521 : {List U8, U64} = CallByName List.138 List.430 List.527 List.431; - let List.524 : U64 = 1i64; - let List.523 : U64 = CallByName Num.19 List.432 List.524; - jump List.518 List.429 List.521 List.431 List.523 List.433; +procedure List.80 (List.560, List.561, List.562, List.563, List.564): + joinpoint List.534 List.433 List.434 List.435 List.436 List.437: + let List.536 : Int1 = CallByName Num.22 List.436 List.437; + if List.536 then + let List.543 : Str = CallByName List.66 List.433 List.436; + let List.537 : {List U8, U64} = CallByName List.139 List.434 List.543 List.435; + let List.540 : U64 = 1i64; + let List.539 : U64 = CallByName Num.19 List.436 List.540; + jump List.534 List.433 List.537 List.435 List.539 List.437; else - ret List.430; + ret List.434; in - jump List.518 List.544 List.545 List.546 List.547 List.548; + jump List.534 List.560 List.561 List.562 List.563 List.564; -procedure Num.125 (#Attr.2): - let Num.265 : U8 = lowlevel NumIntCast #Attr.2; - ret Num.265; +procedure List.92 (List.430, List.431, List.432): + let List.532 : U64 = 0i64; + let List.533 : U64 = CallByName List.6 List.430; + let List.531 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.532 List.533; + ret List.531; + +procedure Num.127 (#Attr.2): + let Num.284 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.284; procedure Num.19 (#Attr.2, #Attr.3): - let Num.268 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.268; + let Num.287 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.287; procedure Num.20 (#Attr.2, #Attr.3): - let Num.266 : U64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.266; + let Num.285 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.285; procedure Num.22 (#Attr.2, #Attr.3): - let Num.269 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.269; + let Num.288 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.288; procedure Num.24 (#Attr.2, #Attr.3): - let Num.267 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; - ret Num.267; + let Num.286 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.286; procedure Str.12 (#Attr.2): - let Str.280 : List U8 = lowlevel StrToUtf8 #Attr.2; - ret Str.280; + let Str.281 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.281; procedure Str.48 (#Attr.2, #Attr.3, #Attr.4): - let Str.274 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; - ret Str.274; + let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; + ret Str.275; -procedure Str.9 (Str.76): - let Str.272 : U64 = 0i64; - let Str.273 : U64 = CallByName List.6 Str.76; - let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.272 Str.273; - let Str.269 : Int1 = StructAtIndex 2 Str.77; - if Str.269 then - let Str.271 : Str = StructAtIndex 1 Str.77; - inc Str.271; - dec Str.77; - let Str.270 : [C {U64, U8}, C Str] = TagId(1) Str.271; - ret Str.270; +procedure Str.9 (Str.77): + let Str.273 : U64 = 0i64; + let Str.274 : U64 = CallByName List.6 Str.77; + let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274; + let Str.270 : Int1 = StructAtIndex 2 Str.78; + if Str.270 then + let Str.272 : Str = StructAtIndex 1 Str.78; + inc Str.272; + dec Str.78; + let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272; + ret Str.271; else - let Str.267 : U8 = StructAtIndex 3 Str.77; - let Str.268 : U64 = StructAtIndex 0 Str.77; - dec Str.77; - let Str.266 : {U64, U8} = Struct {Str.268, Str.267}; - let Str.265 : [C {U64, U8}, C Str] = TagId(0) Str.266; - ret Str.265; + let Str.268 : U8 = StructAtIndex 3 Str.78; + let Str.269 : U64 = StructAtIndex 0 Str.78; + dec Str.78; + let Str.267 : {U64, U8} = Struct {Str.269, Str.268}; + let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267; + ret Str.266; procedure Test.0 (): let Test.13 : Str = "foo"; let Test.12 : Str = "foo"; let Test.1 : {Str, Str} = Struct {Test.12, Test.13}; let Test.11 : {} = CallByName Json.1; - let Test.10 : List U8 = CallByName Encode.25 Test.1 Test.11; + let Test.10 : List U8 = CallByName Encode.26 Test.1 Test.11; let Test.2 : [C {U64, U8}, C Str] = CallByName Str.9 Test.10; let Test.7 : U8 = 1i64; let Test.8 : U8 = GetTagId Test.2; diff --git a/crates/compiler/test_mono/generated/factorial.txt b/crates/compiler/test_mono/generated/factorial.txt index b0319aa20a..e0c96b0c43 100644 --- a/crates/compiler/test_mono/generated/factorial.txt +++ b/crates/compiler/test_mono/generated/factorial.txt @@ -1,10 +1,10 @@ procedure Num.20 (#Attr.2, #Attr.3): - let Num.257 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.276; procedure Num.21 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.15, Test.16): joinpoint Test.7 Test.2 Test.3: diff --git a/crates/compiler/test_mono/generated/function_specialization_information_in_lambda_set_thunk.txt b/crates/compiler/test_mono/generated/function_specialization_information_in_lambda_set_thunk.txt index c6e1133e78..c344eb7f53 100644 --- a/crates/compiler/test_mono/generated/function_specialization_information_in_lambda_set_thunk.txt +++ b/crates/compiler/test_mono/generated/function_specialization_information_in_lambda_set_thunk.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.8): let Test.3 : I64 = 10i64; @@ -23,19 +23,19 @@ procedure Test.4 (Test.5, Test.3): jump Test.19 Test.20; default: - let Test.21 : I64 = CallByName Test.7 Test.18; - jump Test.19 Test.21; + let Test.20 : I64 = CallByName Test.7 Test.18; + jump Test.19 Test.20; -procedure Test.6 (Test.22): - let Test.25 : Int1 = true; - let Test.24 : I64 = CallByName Test.2; - let Test.23 : I64 = CallByName Test.4 Test.25 Test.24; - ret Test.23; +procedure Test.6 (Test.21): + let Test.24 : Int1 = true; + let Test.23 : I64 = CallByName Test.2; + let Test.22 : I64 = CallByName Test.4 Test.24 Test.23; + ret Test.22; -procedure Test.7 (Test.26): - let Test.27 : I64 = 10i64; - ret Test.27; +procedure Test.7 (Test.25): + let Test.26 : I64 = 10i64; + ret Test.26; procedure Test.0 (): let Test.11 : Int1 = false; diff --git a/crates/compiler/test_mono/generated/function_specialization_information_in_lambda_set_thunk_independent_defs.txt b/crates/compiler/test_mono/generated/function_specialization_information_in_lambda_set_thunk_independent_defs.txt index 754b2f895a..0c7b8891e0 100644 --- a/crates/compiler/test_mono/generated/function_specialization_information_in_lambda_set_thunk_independent_defs.txt +++ b/crates/compiler/test_mono/generated/function_specialization_information_in_lambda_set_thunk_independent_defs.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.257 : U8 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : U8 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.276; procedure Test.1 (Test.9): let Test.4 : U8 = 10i64; diff --git a/crates/compiler/test_mono/generated/guard_pattern_true.txt b/crates/compiler/test_mono/generated/guard_pattern_true.txt index dfded1801e..95d17605f5 100644 --- a/crates/compiler/test_mono/generated/guard_pattern_true.txt +++ b/crates/compiler/test_mono/generated/guard_pattern_true.txt @@ -4,24 +4,24 @@ procedure Bool.1 (): procedure Test.1 (Test.2): let Test.5 : I64 = 2i64; - joinpoint Test.10: - let Test.9 : I64 = 0i64; - ret Test.9; + joinpoint Test.8: + let Test.7 : I64 = 0i64; + ret Test.7; in let Test.12 : I64 = 2i64; let Test.13 : Int1 = lowlevel Eq Test.12 Test.5; if Test.13 then - joinpoint Test.7 Test.11: - if Test.11 then + joinpoint Test.10 Test.9: + if Test.9 then let Test.6 : I64 = 42i64; ret Test.6; else - jump Test.10; + jump Test.8; in - let Test.8 : Int1 = CallByName Bool.1; - jump Test.7 Test.8; + let Test.11 : Int1 = CallByName Bool.1; + jump Test.10 Test.11; else - jump Test.10; + jump Test.8; procedure Test.0 (): let Test.4 : {} = Struct {}; diff --git a/crates/compiler/test_mono/generated/if_guard_bind_variable_false.txt b/crates/compiler/test_mono/generated/if_guard_bind_variable_false.txt index b8a0dde808..d0083f78ff 100644 --- a/crates/compiler/test_mono/generated/if_guard_bind_variable_false.txt +++ b/crates/compiler/test_mono/generated/if_guard_bind_variable_false.txt @@ -4,17 +4,17 @@ procedure Bool.11 (#Attr.2, #Attr.3): procedure Test.1 (Test.3): let Test.6 : I64 = 10i64; - joinpoint Test.8 Test.12: - if Test.12 then + joinpoint Test.10 Test.9: + if Test.9 then let Test.7 : I64 = 0i64; ret Test.7; else - let Test.11 : I64 = 42i64; - ret Test.11; + let Test.8 : I64 = 42i64; + ret Test.8; in - let Test.10 : I64 = 5i64; - let Test.9 : Int1 = CallByName Bool.11 Test.6 Test.10; - jump Test.8 Test.9; + let Test.12 : I64 = 5i64; + let Test.11 : Int1 = CallByName Bool.11 Test.6 Test.12; + jump Test.10 Test.11; procedure Test.0 (): let Test.5 : {} = Struct {}; diff --git a/crates/compiler/test_mono/generated/inline_return_joinpoints_in_bool_lambda_set.txt b/crates/compiler/test_mono/generated/inline_return_joinpoints_in_bool_lambda_set.txt new file mode 100644 index 0000000000..fe6e0ec65d --- /dev/null +++ b/crates/compiler/test_mono/generated/inline_return_joinpoints_in_bool_lambda_set.txt @@ -0,0 +1,34 @@ +procedure Bool.1 (): + let Bool.23 : Int1 = false; + ret Bool.23; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; + +procedure Test.3 (Test.4): + ret Test.4; + +procedure Test.0 (Test.14): + joinpoint Test.5 Test.1: + joinpoint Test.10 Test.2: + let Test.8 : I64 = 1i64; + let Test.7 : I64 = CallByName Num.19 Test.1 Test.8; + switch Test.2: + case 0: + jump Test.5 Test.7; + + default: + let Test.6 : I64 = CallByName Test.3 Test.7; + ret Test.6; + + in + let Test.12 : Int1 = CallByName Bool.1; + if Test.12 then + let Test.9 : Int1 = false; + jump Test.10 Test.9; + else + let Test.9 : Int1 = true; + jump Test.10 Test.9; + in + jump Test.5 Test.14; diff --git a/crates/compiler/test_mono/generated/inline_return_joinpoints_in_enum_lambda_set.txt b/crates/compiler/test_mono/generated/inline_return_joinpoints_in_enum_lambda_set.txt new file mode 100644 index 0000000000..93430cffc9 --- /dev/null +++ b/crates/compiler/test_mono/generated/inline_return_joinpoints_in_enum_lambda_set.txt @@ -0,0 +1,60 @@ +procedure Num.19 (#Attr.2, #Attr.3): + let Num.277 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.277; + +procedure Test.2 (Test.3): + switch Test.3: + case 0: + let Test.16 : U8 = 0u8; + ret Test.16; + + case 1: + let Test.17 : U8 = 1u8; + ret Test.17; + + case 2: + let Test.19 : U8 = 2u8; + ret Test.19; + + default: + let Test.22 : U8 = 3u8; + ret Test.22; + + +procedure Test.4 (Test.5): + ret Test.5; + +procedure Test.6 (Test.7): + let Test.21 : I64 = 1i64; + let Test.20 : I64 = CallByName Num.19 Test.7 Test.21; + ret Test.20; + +procedure Test.8 (Test.9): + let Test.24 : I64 = 2i64; + let Test.23 : I64 = CallByName Num.19 Test.9 Test.24; + ret Test.23; + +procedure Test.0 (Test.30): + joinpoint Test.11 Test.1: + let Test.25 : I64 = 1i64; + let Test.13 : I64 = CallByName Num.19 Test.1 Test.25; + let Test.15 : U8 = 0u8; + let Test.14 : U8 = CallByName Test.2 Test.15; + switch Test.14: + case 0: + jump Test.11 Test.13; + + case 1: + let Test.12 : I64 = CallByName Test.4 Test.13; + ret Test.12; + + case 2: + let Test.12 : I64 = CallByName Test.6 Test.13; + ret Test.12; + + default: + let Test.12 : I64 = CallByName Test.8 Test.13; + ret Test.12; + + in + jump Test.11 Test.30; diff --git a/crates/compiler/test_mono/generated/inline_return_joinpoints_in_union_lambda_set.txt b/crates/compiler/test_mono/generated/inline_return_joinpoints_in_union_lambda_set.txt new file mode 100644 index 0000000000..361c97da60 --- /dev/null +++ b/crates/compiler/test_mono/generated/inline_return_joinpoints_in_union_lambda_set.txt @@ -0,0 +1,36 @@ +procedure Num.19 (#Attr.2, #Attr.3): + let Num.276 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.276; + +procedure Test.2 (Test.3, Test.1): + let Test.17 : Int1 = false; + let Test.18 : Int1 = lowlevel Eq Test.17 Test.3; + if Test.18 then + let Test.13 : [C , C I64] = TagId(0) ; + ret Test.13; + else + let Test.14 : [C , C I64] = TagId(1) Test.1; + ret Test.14; + +procedure Test.4 (Test.5, #Attr.12): + let Test.1 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.16 : I64 = CallByName Num.19 Test.5 Test.1; + ret Test.16; + +procedure Test.0 (Test.25): + joinpoint Test.7 Test.1: + let Test.20 : I64 = 1i64; + let Test.9 : I64 = CallByName Num.19 Test.1 Test.20; + let Test.12 : Int1 = false; + let Test.10 : [C , C I64] = CallByName Test.2 Test.12 Test.1; + let Test.11 : U8 = GetTagId Test.10; + switch Test.11: + case 0: + jump Test.7 Test.9; + + default: + let Test.8 : I64 = CallByName Test.4 Test.9 Test.10; + ret Test.8; + + in + jump Test.7 Test.25; diff --git a/crates/compiler/test_mono/generated/ir_int_add.txt b/crates/compiler/test_mono/generated/ir_int_add.txt index 6180e4569c..dd14a05419 100644 --- a/crates/compiler/test_mono/generated/ir_int_add.txt +++ b/crates/compiler/test_mono/generated/ir_int_add.txt @@ -1,10 +1,10 @@ procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; procedure Num.19 (#Attr.2, #Attr.3): - let Num.258 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.277; procedure Test.0 (): let Test.1 : List I64 = Array [1i64, 2i64]; diff --git a/crates/compiler/test_mono/generated/ir_plus.txt b/crates/compiler/test_mono/generated/ir_plus.txt index 2994403a5a..19f312321c 100644 --- a/crates/compiler/test_mono/generated/ir_plus.txt +++ b/crates/compiler/test_mono/generated/ir_plus.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.2 : I64 = 1i64; diff --git a/crates/compiler/test_mono/generated/ir_round.txt b/crates/compiler/test_mono/generated/ir_round.txt index 0ba65b4515..b385b68115 100644 --- a/crates/compiler/test_mono/generated/ir_round.txt +++ b/crates/compiler/test_mono/generated/ir_round.txt @@ -1,6 +1,6 @@ procedure Num.45 (#Attr.2): - let Num.256 : I64 = lowlevel NumRound #Attr.2; - ret Num.256; + let Num.275 : I64 = lowlevel NumRound #Attr.2; + ret Num.275; procedure Test.0 (): let Test.2 : Float64 = 3.6f64; diff --git a/crates/compiler/test_mono/generated/ir_two_defs.txt b/crates/compiler/test_mono/generated/ir_two_defs.txt index aa2e631fbb..5088e4342b 100644 --- a/crates/compiler/test_mono/generated/ir_two_defs.txt +++ b/crates/compiler/test_mono/generated/ir_two_defs.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.1 : I64 = 3i64; diff --git a/crates/compiler/test_mono/generated/ir_when_idiv.txt b/crates/compiler/test_mono/generated/ir_when_idiv.txt index 5bd2abf771..1a24b0dd7f 100644 --- a/crates/compiler/test_mono/generated/ir_when_idiv.txt +++ b/crates/compiler/test_mono/generated/ir_when_idiv.txt @@ -1,22 +1,22 @@ procedure Num.30 (#Attr.2): - let Num.263 : I64 = 0i64; - let Num.262 : Int1 = lowlevel Eq #Attr.2 Num.263; - ret Num.262; + let Num.282 : I64 = 0i64; + let Num.281 : Int1 = lowlevel Eq #Attr.2 Num.282; + ret Num.281; procedure Num.39 (#Attr.2, #Attr.3): - let Num.258 : I64 = lowlevel NumDivTruncUnchecked #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : I64 = lowlevel NumDivTruncUnchecked #Attr.2 #Attr.3; + ret Num.277; -procedure Num.40 (Num.228, Num.229): - let Num.259 : Int1 = CallByName Num.30 Num.229; - if Num.259 then - let Num.261 : {} = Struct {}; - let Num.260 : [C {}, C I64] = TagId(0) Num.261; - ret Num.260; +procedure Num.40 (Num.247, Num.248): + let Num.278 : Int1 = CallByName Num.30 Num.248; + if Num.278 then + let Num.280 : {} = Struct {}; + let Num.279 : [C {}, C I64] = TagId(0) Num.280; + ret Num.279; else - let Num.257 : I64 = CallByName Num.39 Num.228 Num.229; - let Num.256 : [C {}, C I64] = TagId(1) Num.257; - ret Num.256; + let Num.276 : I64 = CallByName Num.39 Num.247 Num.248; + let Num.275 : [C {}, C I64] = TagId(1) Num.276; + ret Num.275; procedure Test.0 (): let Test.8 : I64 = 1000i64; diff --git a/crates/compiler/test_mono/generated/ir_when_just.txt b/crates/compiler/test_mono/generated/ir_when_just.txt index 95413986b7..a830281301 100644 --- a/crates/compiler/test_mono/generated/ir_when_just.txt +++ b/crates/compiler/test_mono/generated/ir_when_just.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.10 : I64 = 41i64; diff --git a/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt b/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt index bedc8876e7..6d1f743220 100644 --- a/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt +++ b/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt @@ -6,66 +6,66 @@ procedure Bool.2 (): let Bool.23 : Int1 = true; ret Bool.23; -procedure List.2 (List.95, List.96): - let List.492 : U64 = CallByName List.6 List.95; - let List.488 : Int1 = CallByName Num.22 List.96 List.492; - if List.488 then - let List.490 : I64 = CallByName List.66 List.95 List.96; - let List.489 : [C {}, C I64] = TagId(1) List.490; - ret List.489; +procedure List.2 (List.96, List.97): + let List.508 : U64 = CallByName List.6 List.96; + let List.504 : Int1 = CallByName Num.22 List.97 List.508; + if List.504 then + let List.506 : I64 = CallByName List.66 List.96 List.97; + let List.505 : [C {}, C I64] = TagId(1) List.506; + ret List.505; else - let List.487 : {} = Struct {}; - let List.486 : [C {}, C I64] = TagId(0) List.487; - ret List.486; + let List.503 : {} = Struct {}; + let List.502 : [C {}, C I64] = TagId(0) List.503; + ret List.502; procedure List.6 (#Attr.2): - let List.493 : U64 = lowlevel ListLen #Attr.2; - ret List.493; + let List.509 : U64 = lowlevel ListLen #Attr.2; + ret List.509; procedure List.66 (#Attr.2, #Attr.3): - let List.491 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.491; + let List.507 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.507; -procedure List.9 (List.283): - let List.485 : U64 = 0i64; - let List.478 : [C {}, C I64] = CallByName List.2 List.283 List.485; - let List.482 : U8 = 1i64; - let List.483 : U8 = GetTagId List.478; - let List.484 : Int1 = lowlevel Eq List.482 List.483; - if List.484 then - let List.284 : I64 = UnionAtIndex (Id 1) (Index 0) List.478; - let List.479 : [C Int1, C I64] = TagId(1) List.284; - ret List.479; +procedure List.9 (List.287): + let List.501 : U64 = 0i64; + let List.494 : [C {}, C I64] = CallByName List.2 List.287 List.501; + let List.498 : U8 = 1i64; + let List.499 : U8 = GetTagId List.494; + let List.500 : Int1 = lowlevel Eq List.498 List.499; + if List.500 then + let List.288 : I64 = UnionAtIndex (Id 1) (Index 0) List.494; + let List.495 : [C Int1, C I64] = TagId(1) List.288; + ret List.495; else - let List.481 : Int1 = true; - let List.480 : [C Int1, C I64] = TagId(0) List.481; - ret List.480; + let List.497 : Int1 = true; + let List.496 : [C Int1, C I64] = TagId(0) List.497; + ret List.496; procedure Num.22 (#Attr.2, #Attr.3): - let Num.256 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.275; -procedure Str.27 (Str.96): - let Str.265 : [C Int1, C I64] = CallByName Str.69 Str.96; - ret Str.265; +procedure Str.27 (Str.97): + let Str.266 : [C Int1, C I64] = CallByName Str.70 Str.97; + ret Str.266; procedure Str.47 (#Attr.2): - let Str.273 : {I64, U8} = lowlevel StrToNum #Attr.2; - ret Str.273; + let Str.274 : {I64, U8} = lowlevel StrToNum #Attr.2; + ret Str.274; -procedure Str.69 (Str.231): - let Str.232 : {I64, U8} = CallByName Str.47 Str.231; - let Str.271 : U8 = StructAtIndex 1 Str.232; - let Str.272 : U8 = 0i64; - let Str.268 : Int1 = CallByName Bool.11 Str.271 Str.272; - if Str.268 then - let Str.270 : I64 = StructAtIndex 0 Str.232; - let Str.269 : [C Int1, C I64] = TagId(1) Str.270; - ret Str.269; +procedure Str.70 (Str.232): + let Str.233 : {I64, U8} = CallByName Str.47 Str.232; + let Str.272 : U8 = StructAtIndex 1 Str.233; + let Str.273 : U8 = 0i64; + let Str.269 : Int1 = CallByName Bool.11 Str.272 Str.273; + if Str.269 then + let Str.271 : I64 = StructAtIndex 0 Str.233; + let Str.270 : [C Int1, C I64] = TagId(1) Str.271; + ret Str.270; else - let Str.267 : Int1 = false; - let Str.266 : [C Int1, C I64] = TagId(0) Str.267; - ret Str.266; + let Str.268 : Int1 = false; + let Str.267 : [C Int1, C I64] = TagId(0) Str.268; + ret Str.267; procedure Test.0 (): let Test.3 : Int1 = CallByName Bool.2; diff --git a/crates/compiler/test_mono/generated/issue_4557.txt b/crates/compiler/test_mono/generated/issue_4557.txt index ba9e7e473b..ac27c966f7 100644 --- a/crates/compiler/test_mono/generated/issue_4557.txt +++ b/crates/compiler/test_mono/generated/issue_4557.txt @@ -8,11 +8,11 @@ procedure Bool.4 (#Attr.2, #Attr.3): procedure Test.1 (Test.2, Test.3): let Test.17 : {Int1, Int1} = Struct {Test.2, Test.3}; - let Test.34 : Int1 = StructAtIndex 0 Test.17; - let Test.33 : Int1 = StructAtIndex 1 Test.17; - let Test.19 : Int1 = CallByName Test.1 Test.33 Test.34; - let Test.27 : {} = Struct {}; - joinpoint Test.28 Test.21: + let Test.32 : Int1 = StructAtIndex 0 Test.17; + let Test.31 : Int1 = StructAtIndex 1 Test.17; + let Test.19 : Int1 = CallByName Test.1 Test.31 Test.32; + let Test.26 : {} = Struct {}; + joinpoint Test.27 Test.21: let Test.23 : {} = Struct {}; joinpoint Test.24 Test.22: let Test.20 : Int1 = CallByName Bool.11 Test.21 Test.22; @@ -21,36 +21,36 @@ procedure Test.1 (Test.2, Test.3): let Test.18 : Int1 = CallByName Bool.4 Test.19 Test.20; ret Test.18; in - switch Test.33: + switch Test.31: case 0: let Test.25 : Str = CallByName Test.9 Test.23; jump Test.24 Test.25; default: - let Test.26 : Str = CallByName Test.11 Test.23; - jump Test.24 Test.26; + let Test.25 : Str = CallByName Test.11 Test.23; + jump Test.24 Test.25; in - switch Test.34: + switch Test.32: case 0: - let Test.29 : Str = CallByName Test.9 Test.27; - jump Test.28 Test.29; + let Test.28 : Str = CallByName Test.9 Test.26; + jump Test.27 Test.28; default: - let Test.30 : Str = CallByName Test.11 Test.27; - jump Test.28 Test.30; + let Test.28 : Str = CallByName Test.11 Test.26; + jump Test.27 Test.28; -procedure Test.11 (Test.36): - let Test.37 : Str = "a"; - ret Test.37; +procedure Test.11 (Test.34): + let Test.35 : Str = "a"; + ret Test.35; -procedure Test.9 (Test.39): - let Test.40 : Str = "a"; - ret Test.40; +procedure Test.9 (Test.37): + let Test.38 : Str = "a"; + ret Test.38; procedure Test.0 (): - let Test.38 : Int1 = false; - let Test.35 : Int1 = true; - let Test.13 : Int1 = CallByName Test.1 Test.38 Test.35; + let Test.36 : Int1 = false; + let Test.33 : Int1 = true; + let Test.13 : Int1 = CallByName Test.1 Test.36 Test.33; ret Test.13; diff --git a/crates/compiler/test_mono/generated/issue_4749.txt b/crates/compiler/test_mono/generated/issue_4749.txt index aabc060876..86d6b55d68 100644 --- a/crates/compiler/test_mono/generated/issue_4749.txt +++ b/crates/compiler/test_mono/generated/issue_4749.txt @@ -1,3 +1,7 @@ +procedure Bool.1 (): + let Bool.36 : Int1 = false; + ret Bool.36; + procedure Bool.11 (#Attr.2, #Attr.3): let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3; dec #Attr.3; @@ -5,308 +9,372 @@ procedure Bool.11 (#Attr.2, #Attr.3): ret Bool.23; procedure Bool.11 (#Attr.2, #Attr.3): - let Bool.31 : Int1 = lowlevel Eq #Attr.2 #Attr.3; - ret Bool.31; + let Bool.39 : Int1 = lowlevel Eq #Attr.2 #Attr.3; + ret Bool.39; procedure Bool.11 (#Attr.2, #Attr.3): - let Bool.38 : Int1 = lowlevel Eq #Attr.2 #Attr.3; - ret Bool.38; + let Bool.46 : Int1 = lowlevel Eq #Attr.2 #Attr.3; + ret Bool.46; procedure Bool.12 (#Attr.2, #Attr.3): - let Bool.30 : Int1 = lowlevel NotEq #Attr.2 #Attr.3; - ret Bool.30; + let Bool.38 : Int1 = lowlevel NotEq #Attr.2 #Attr.3; + ret Bool.38; + +procedure Bool.2 (): + let Bool.35 : Int1 = true; + ret Bool.35; procedure Bool.7 (Bool.19, Bool.20): - let Bool.29 : Int1 = CallByName Bool.12 Bool.19 Bool.20; - ret Bool.29; + let Bool.37 : Int1 = CallByName Bool.12 Bool.19 Bool.20; + ret Bool.37; -procedure Decode.23 (Decode.94): - ret Decode.94; +procedure Decode.24 (Decode.101): + ret Decode.101; -procedure Decode.24 (Decode.95, Decode.114, Decode.97): - let Decode.127 : {List U8, [C {}, C Str]} = CallByName Json.293 Decode.95 Decode.97; - ret Decode.127; +procedure Decode.25 (Decode.102, Decode.121, Decode.104): + let Decode.134 : {List U8, [C {}, C Str]} = CallByName Json.315 Decode.102 Decode.104; + ret Decode.134; -procedure Decode.25 (Decode.98, Decode.99): - let Decode.126 : {} = CallByName Json.41; - let Decode.125 : {List U8, [C {}, C Str]} = CallByName Decode.24 Decode.98 Decode.126 Decode.99; - ret Decode.125; +procedure Decode.26 (Decode.105, Decode.106): + let Decode.133 : {} = CallByName Json.43; + let Decode.132 : {List U8, [C {}, C Str]} = CallByName Decode.25 Decode.105 Decode.133 Decode.106; + ret Decode.132; -procedure Decode.26 (Decode.100, Decode.101): - let Decode.115 : {List U8, [C {}, C Str]} = CallByName Decode.25 Decode.100 Decode.101; - let Decode.103 : List U8 = StructAtIndex 0 Decode.115; - inc Decode.103; - let Decode.102 : [C {}, C Str] = StructAtIndex 1 Decode.115; - inc Decode.102; - dec Decode.115; - let Decode.118 : Int1 = CallByName List.1 Decode.103; - if Decode.118 then - dec Decode.103; - let Decode.122 : U8 = 1i64; - let Decode.123 : U8 = GetTagId Decode.102; - let Decode.124 : Int1 = lowlevel Eq Decode.122 Decode.123; - if Decode.124 then - let Decode.104 : Str = UnionAtIndex (Id 1) (Index 0) Decode.102; - inc Decode.104; - dec Decode.102; - let Decode.119 : [C [C List U8, C ], C Str] = TagId(1) Decode.104; - ret Decode.119; +procedure Decode.27 (Decode.107, Decode.108): + let Decode.122 : {List U8, [C {}, C Str]} = CallByName Decode.26 Decode.107 Decode.108; + let Decode.110 : List U8 = StructAtIndex 0 Decode.122; + inc Decode.110; + let Decode.109 : [C {}, C Str] = StructAtIndex 1 Decode.122; + inc Decode.109; + dec Decode.122; + let Decode.125 : Int1 = CallByName List.1 Decode.110; + if Decode.125 then + dec Decode.110; + let Decode.129 : U8 = 1i64; + let Decode.130 : U8 = GetTagId Decode.109; + let Decode.131 : Int1 = lowlevel Eq Decode.129 Decode.130; + if Decode.131 then + let Decode.111 : Str = UnionAtIndex (Id 1) (Index 0) Decode.109; + inc Decode.111; + dec Decode.109; + let Decode.126 : [C [C List U8, C ], C Str] = TagId(1) Decode.111; + ret Decode.126; else - dec Decode.102; - let Decode.121 : [C List U8, C ] = TagId(1) ; - let Decode.120 : [C [C List U8, C ], C Str] = TagId(0) Decode.121; - ret Decode.120; + dec Decode.109; + let Decode.128 : [C List U8, C ] = TagId(1) ; + let Decode.127 : [C [C List U8, C ], C Str] = TagId(0) Decode.128; + ret Decode.127; else - dec Decode.102; - let Decode.117 : [C List U8, C ] = TagId(0) Decode.103; - let Decode.116 : [C [C List U8, C ], C Str] = TagId(0) Decode.117; - ret Decode.116; + dec Decode.109; + let Decode.124 : [C List U8, C ] = TagId(0) Decode.110; + let Decode.123 : [C [C List U8, C ], C Str] = TagId(0) Decode.124; + ret Decode.123; -procedure Json.139 (Json.452, Json.453): - joinpoint Json.421 Json.418 Json.138: - let Json.141 : List U8 = StructAtIndex 0 Json.418; - inc Json.141; - let Json.140 : List U8 = StructAtIndex 1 Json.418; - inc Json.140; - dec Json.418; - let Json.422 : [C {}, C U8] = CallByName List.9 Json.141; - let Json.436 : U8 = 1i64; - let Json.437 : U8 = GetTagId Json.422; - let Json.438 : Int1 = lowlevel Eq Json.436 Json.437; - if Json.438 then - let Json.142 : U8 = UnionAtIndex (Id 1) (Index 0) Json.422; - let Json.424 : Int1 = CallByName Json.283 Json.142; - if Json.424 then - let Json.434 : U64 = 1i64; - let Json.430 : {List U8, List U8} = CallByName List.52 Json.141 Json.434; - let Json.431 : {} = Struct {}; - let Json.428 : List U8 = CallByName Json.143 Json.430; - let Json.429 : List U8 = CallByName List.4 Json.140 Json.142; - let Json.426 : {List U8, List U8} = Struct {Json.428, Json.429}; - jump Json.421 Json.426 Json.138; +procedure Json.160 (Json.570, Json.571): + joinpoint Json.508 Json.505 Json.159: + let Json.162 : List U8 = StructAtIndex 0 Json.505; + inc Json.162; + let Json.161 : List U8 = StructAtIndex 1 Json.505; + inc Json.161; + dec Json.505; + joinpoint Json.546: + let Json.543 : {List U8, List U8} = Struct {Json.162, Json.161}; + ret Json.543; + in + let Json.554 : U64 = lowlevel ListLen Json.162; + let Json.555 : U64 = 2i64; + let Json.556 : Int1 = lowlevel NumGte Json.554 Json.555; + if Json.556 then + let Json.545 : U64 = 0i64; + let Json.163 : U8 = lowlevel ListGetUnsafe Json.162 Json.545; + let Json.544 : U64 = 1i64; + let Json.164 : U8 = lowlevel ListGetUnsafe Json.162 Json.544; + let Json.516 : Int1 = CallByName Json.23 Json.163 Json.164; + if Json.516 then + let Json.523 : U64 = 2i64; + let Json.520 : List U8 = CallByName List.29 Json.162 Json.523; + let Json.522 : List U8 = CallByName List.4 Json.161 Json.163; + let Json.521 : List U8 = CallByName List.4 Json.522 Json.164; + let Json.518 : {List U8, List U8} = Struct {Json.520, Json.521}; + jump Json.508 Json.518 Json.159; else - let Json.423 : {List U8, List U8} = Struct {Json.141, Json.140}; - ret Json.423; + let Json.510 : Int1 = CallByName Json.305 Json.163; + if Json.510 then + let Json.514 : List U8 = CallByName List.38 Json.162; + let Json.515 : List U8 = CallByName List.4 Json.161 Json.163; + let Json.512 : {List U8, List U8} = Struct {Json.514, Json.515}; + jump Json.508 Json.512 Json.159; + else + let Json.509 : {List U8, List U8} = Struct {Json.162, Json.161}; + ret Json.509; else - let Json.435 : {List U8, List U8} = Struct {Json.141, Json.140}; - ret Json.435; + let Json.551 : U64 = lowlevel ListLen Json.162; + let Json.552 : U64 = 1i64; + let Json.553 : Int1 = lowlevel NumGte Json.551 Json.552; + if Json.553 then + let Json.550 : U64 = 0i64; + let Json.165 : U8 = lowlevel ListGetUnsafe Json.162 Json.550; + joinpoint Json.548 Json.547: + if Json.547 then + let Json.541 : List U8 = CallByName List.38 Json.162; + let Json.542 : List U8 = CallByName List.4 Json.161 Json.165; + let Json.539 : {List U8, List U8} = Struct {Json.541, Json.542}; + jump Json.508 Json.539 Json.159; + else + jump Json.546; + in + let Json.549 : Int1 = CallByName Json.305 Json.165; + jump Json.548 Json.549; + else + jump Json.546; in - jump Json.421 Json.452 Json.453; - -procedure Json.143 (Json.432): - let Json.433 : List U8 = StructAtIndex 1 Json.432; - inc Json.433; - dec Json.432; - ret Json.433; + jump Json.508 Json.570 Json.571; procedure Json.2 (): - let Json.396 : {} = Struct {}; - ret Json.396; + let Json.483 : {} = Struct {}; + ret Json.483; -procedure Json.22 (Json.137, Json.138): - let Json.440 : List U8 = Array []; - let Json.420 : {List U8, List U8} = Struct {Json.137, Json.440}; - let Json.419 : {List U8, List U8} = CallByName Json.139 Json.420 Json.138; - ret Json.419; - -procedure Json.283 (Json.284): - let Json.442 : U8 = 34i64; - let Json.441 : Int1 = CallByName Bool.7 Json.284 Json.442; - ret Json.441; - -procedure Json.293 (Json.294, Json.399): - let Json.400 : {List U8, [C {}, C Str]} = CallByName Json.40 Json.294; - ret Json.400; - -procedure Json.40 (Json.276): - let Json.446 : U64 = 1i64; - inc Json.276; - let Json.445 : {List U8, List U8} = CallByName List.52 Json.276 Json.446; - let Json.277 : List U8 = StructAtIndex 0 Json.445; - inc Json.277; - let Json.279 : List U8 = StructAtIndex 1 Json.445; - inc Json.279; - dec Json.445; - let Json.444 : U8 = 34i64; - let Json.443 : List U8 = Array [Json.444]; - let Json.404 : Int1 = CallByName Bool.11 Json.277 Json.443; - dec Json.443; - dec Json.277; - if Json.404 then - dec Json.276; - let Json.417 : {} = Struct {}; - let Json.416 : {List U8, List U8} = CallByName Json.22 Json.279 Json.417; - let Json.282 : List U8 = StructAtIndex 0 Json.416; - inc Json.282; - let Json.281 : List U8 = StructAtIndex 1 Json.416; - inc Json.281; - dec Json.416; - let Json.405 : [C {U64, U8}, C Str] = CallByName Str.9 Json.281; - let Json.413 : U8 = 1i64; - let Json.414 : U8 = GetTagId Json.405; - let Json.415 : Int1 = lowlevel Eq Json.413 Json.414; - if Json.415 then - let Json.285 : Str = UnionAtIndex (Id 1) (Index 0) Json.405; - inc Json.285; - dec Json.405; - let Json.409 : U64 = 1i64; - let Json.408 : {List U8, List U8} = CallByName List.52 Json.282 Json.409; - let Json.287 : List U8 = StructAtIndex 1 Json.408; - inc Json.287; - dec Json.408; - let Json.407 : [C {}, C Str] = TagId(1) Json.285; - let Json.406 : {List U8, [C {}, C Str]} = Struct {Json.287, Json.407}; - ret Json.406; - else - dec Json.405; - let Json.412 : {} = Struct {}; - let Json.411 : [C {}, C Str] = TagId(0) Json.412; - let Json.410 : {List U8, [C {}, C Str]} = Struct {Json.282, Json.411}; - ret Json.410; - else - dec Json.279; - let Json.403 : {} = Struct {}; - let Json.402 : [C {}, C Str] = TagId(0) Json.403; - let Json.401 : {List U8, [C {}, C Str]} = Struct {Json.276, Json.402}; - ret Json.401; - -procedure Json.41 (): - let Json.398 : {} = Struct {}; - let Json.397 : {} = CallByName Decode.23 Json.398; - ret Json.397; - -procedure List.1 (List.94): - let List.479 : U64 = CallByName List.6 List.94; - let List.480 : U64 = 0i64; - let List.478 : Int1 = CallByName Bool.11 List.479 List.480; - ret List.478; - -procedure List.2 (List.95, List.96): - let List.536 : U64 = CallByName List.6 List.95; - let List.532 : Int1 = CallByName Num.22 List.96 List.536; - if List.532 then - let List.534 : U8 = CallByName List.66 List.95 List.96; - let List.533 : [C {}, C U8] = TagId(1) List.534; - ret List.533; - else - let List.531 : {} = Struct {}; - let List.530 : [C {}, C U8] = TagId(0) List.531; - ret List.530; - -procedure List.4 (List.106, List.107): - let List.520 : U64 = 1i64; - let List.518 : List U8 = CallByName List.70 List.106 List.520; - let List.517 : List U8 = CallByName List.71 List.518 List.107; - ret List.517; - -procedure List.49 (List.366, List.367): - let List.492 : U64 = StructAtIndex 0 List.367; - let List.493 : U64 = 0i64; - let List.490 : Int1 = CallByName Bool.11 List.492 List.493; - if List.490 then - dec List.366; - let List.491 : List U8 = Array []; - ret List.491; - else - let List.487 : U64 = StructAtIndex 1 List.367; - let List.488 : U64 = StructAtIndex 0 List.367; - let List.486 : List U8 = CallByName List.72 List.366 List.487 List.488; - ret List.486; - -procedure List.52 (List.381, List.382): - let List.383 : U64 = CallByName List.6 List.381; - joinpoint List.515 List.384: - let List.513 : U64 = 0i64; - let List.512 : {U64, U64} = Struct {List.384, List.513}; - inc List.381; - let List.385 : List U8 = CallByName List.49 List.381 List.512; - let List.511 : U64 = CallByName Num.20 List.383 List.384; - let List.510 : {U64, U64} = Struct {List.511, List.384}; - let List.386 : List U8 = CallByName List.49 List.381 List.510; - let List.509 : {List U8, List U8} = Struct {List.385, List.386}; - ret List.509; +procedure Json.23 (Json.155, Json.156): + let Json.524 : {U8, U8} = Struct {Json.155, Json.156}; + joinpoint Json.533: + let Json.532 : Int1 = CallByName Bool.1; + ret Json.532; in - let List.516 : Int1 = CallByName Num.24 List.383 List.382; - if List.516 then - jump List.515 List.382; + let Json.535 : U8 = StructAtIndex 0 Json.524; + let Json.536 : U8 = 92i64; + let Json.537 : Int1 = lowlevel Eq Json.536 Json.535; + if Json.537 then + let Json.534 : U8 = StructAtIndex 1 Json.524; + switch Json.534: + case 98: + let Json.525 : Int1 = CallByName Bool.2; + ret Json.525; + + case 102: + let Json.526 : Int1 = CallByName Bool.2; + ret Json.526; + + case 110: + let Json.527 : Int1 = CallByName Bool.2; + ret Json.527; + + case 114: + let Json.528 : Int1 = CallByName Bool.2; + ret Json.528; + + case 116: + let Json.529 : Int1 = CallByName Bool.2; + ret Json.529; + + case 34: + let Json.530 : Int1 = CallByName Bool.2; + ret Json.530; + + case 92: + let Json.531 : Int1 = CallByName Bool.2; + ret Json.531; + + default: + jump Json.533; + else - jump List.515 List.383; + jump Json.533; + +procedure Json.24 (Json.158, Json.159): + let Json.558 : List U8 = Array []; + let Json.507 : {List U8, List U8} = Struct {Json.158, Json.558}; + let Json.506 : {List U8, List U8} = CallByName Json.160 Json.507 Json.159; + ret Json.506; + +procedure Json.305 (Json.306): + let Json.560 : U8 = 34i64; + let Json.559 : Int1 = CallByName Bool.7 Json.306 Json.560; + ret Json.559; + +procedure Json.315 (Json.316, Json.486): + let Json.487 : {List U8, [C {}, C Str]} = CallByName Json.42 Json.316; + ret Json.487; + +procedure Json.42 (Json.298): + let Json.564 : U64 = 1i64; + inc Json.298; + let Json.563 : {List U8, List U8} = CallByName List.52 Json.298 Json.564; + let Json.299 : List U8 = StructAtIndex 0 Json.563; + inc Json.299; + let Json.301 : List U8 = StructAtIndex 1 Json.563; + inc Json.301; + dec Json.563; + let Json.562 : U8 = 34i64; + let Json.561 : List U8 = Array [Json.562]; + let Json.491 : Int1 = CallByName Bool.11 Json.299 Json.561; + dec Json.561; + dec Json.299; + if Json.491 then + dec Json.298; + let Json.504 : {} = Struct {}; + let Json.503 : {List U8, List U8} = CallByName Json.24 Json.301 Json.504; + let Json.304 : List U8 = StructAtIndex 0 Json.503; + inc Json.304; + let Json.303 : List U8 = StructAtIndex 1 Json.503; + inc Json.303; + dec Json.503; + let Json.492 : [C {U64, U8}, C Str] = CallByName Str.9 Json.303; + let Json.500 : U8 = 1i64; + let Json.501 : U8 = GetTagId Json.492; + let Json.502 : Int1 = lowlevel Eq Json.500 Json.501; + if Json.502 then + let Json.307 : Str = UnionAtIndex (Id 1) (Index 0) Json.492; + inc Json.307; + dec Json.492; + let Json.496 : U64 = 1i64; + let Json.495 : {List U8, List U8} = CallByName List.52 Json.304 Json.496; + let Json.309 : List U8 = StructAtIndex 1 Json.495; + inc Json.309; + dec Json.495; + let Json.494 : [C {}, C Str] = TagId(1) Json.307; + let Json.493 : {List U8, [C {}, C Str]} = Struct {Json.309, Json.494}; + ret Json.493; + else + dec Json.492; + let Json.499 : {} = Struct {}; + let Json.498 : [C {}, C Str] = TagId(0) Json.499; + let Json.497 : {List U8, [C {}, C Str]} = Struct {Json.304, Json.498}; + ret Json.497; + else + dec Json.301; + let Json.490 : {} = Struct {}; + let Json.489 : [C {}, C Str] = TagId(0) Json.490; + let Json.488 : {List U8, [C {}, C Str]} = Struct {Json.298, Json.489}; + ret Json.488; + +procedure Json.43 (): + let Json.485 : {} = Struct {}; + let Json.484 : {} = CallByName Decode.24 Json.485; + ret Json.484; + +procedure List.1 (List.95): + let List.495 : U64 = CallByName List.6 List.95; + let List.496 : U64 = 0i64; + let List.494 : Int1 = CallByName Bool.11 List.495 List.496; + ret List.494; + +procedure List.29 (List.298, List.299): + let List.549 : U64 = CallByName List.6 List.298; + let List.300 : U64 = CallByName Num.77 List.549 List.299; + let List.544 : List U8 = CallByName List.43 List.298 List.300; + ret List.544; + +procedure List.31 (#Attr.2, #Attr.3): + let List.541 : List U8 = lowlevel ListDropAt #Attr.2 #Attr.3; + ret List.541; + +procedure List.38 (List.292): + let List.543 : U64 = 0i64; + let List.542 : List U8 = CallByName List.31 List.292 List.543; + ret List.542; + +procedure List.4 (List.107, List.108): + let List.538 : U64 = 1i64; + let List.537 : List U8 = CallByName List.70 List.107 List.538; + let List.536 : List U8 = CallByName List.71 List.537 List.108; + ret List.536; + +procedure List.43 (List.296, List.297): + let List.548 : U64 = CallByName List.6 List.296; + let List.547 : U64 = CallByName Num.77 List.548 List.297; + let List.546 : {U64, U64} = Struct {List.297, List.547}; + let List.545 : List U8 = CallByName List.49 List.296 List.546; + ret List.545; + +procedure List.49 (List.370, List.371): + let List.508 : U64 = StructAtIndex 0 List.371; + let List.509 : U64 = 0i64; + let List.506 : Int1 = CallByName Bool.11 List.508 List.509; + if List.506 then + dec List.370; + let List.507 : List U8 = Array []; + ret List.507; + else + let List.503 : U64 = StructAtIndex 1 List.371; + let List.504 : U64 = StructAtIndex 0 List.371; + let List.502 : List U8 = CallByName List.72 List.370 List.503 List.504; + ret List.502; + +procedure List.52 (List.385, List.386): + let List.387 : U64 = CallByName List.6 List.385; + joinpoint List.523 List.388: + let List.521 : U64 = 0i64; + let List.520 : {U64, U64} = Struct {List.388, List.521}; + inc List.385; + let List.389 : List U8 = CallByName List.49 List.385 List.520; + let List.519 : U64 = CallByName Num.20 List.387 List.388; + let List.518 : {U64, U64} = Struct {List.519, List.388}; + let List.390 : List U8 = CallByName List.49 List.385 List.518; + let List.517 : {List U8, List U8} = Struct {List.389, List.390}; + ret List.517; + in + let List.524 : Int1 = CallByName Num.24 List.387 List.386; + if List.524 then + jump List.523 List.386; + else + jump List.523 List.387; procedure List.6 (#Attr.2): - let List.556 : U64 = lowlevel ListLen #Attr.2; - ret List.556; - -procedure List.66 (#Attr.2, #Attr.3): - let List.535 : U8 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.535; + let List.571 : U64 = lowlevel ListLen #Attr.2; + ret List.571; procedure List.70 (#Attr.2, #Attr.3): - let List.521 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.521; + let List.529 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.529; procedure List.71 (#Attr.2, #Attr.3): - let List.519 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.519; + let List.527 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.527; procedure List.72 (#Attr.2, #Attr.3, #Attr.4): - let List.489 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; - ret List.489; - -procedure List.9 (List.283): - let List.529 : U64 = 0i64; - let List.522 : [C {}, C U8] = CallByName List.2 List.283 List.529; - let List.526 : U8 = 1i64; - let List.527 : U8 = GetTagId List.522; - let List.528 : Int1 = lowlevel Eq List.526 List.527; - if List.528 then - let List.284 : U8 = UnionAtIndex (Id 1) (Index 0) List.522; - let List.523 : [C {}, C U8] = TagId(1) List.284; - ret List.523; - else - let List.525 : {} = Struct {}; - let List.524 : [C {}, C U8] = TagId(0) List.525; - ret List.524; + let List.505 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; + ret List.505; procedure Num.20 (#Attr.2, #Attr.3): - let Num.258 : U64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.258; - -procedure Num.22 (#Attr.2, #Attr.3): - let Num.262 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.262; + let Num.276 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.276; procedure Num.24 (#Attr.2, #Attr.3): - let Num.261 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; - ret Num.261; + let Num.278 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.278; + +procedure Num.77 (#Attr.2, #Attr.3): + let Num.280 : U64 = lowlevel NumSubSaturated #Attr.2 #Attr.3; + ret Num.280; procedure Str.48 (#Attr.2, #Attr.3, #Attr.4): - let Str.274 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; - ret Str.274; + let Str.275 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; + ret Str.275; -procedure Str.9 (Str.76): - let Str.272 : U64 = 0i64; - let Str.273 : U64 = CallByName List.6 Str.76; - let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.272 Str.273; - let Str.269 : Int1 = StructAtIndex 2 Str.77; - if Str.269 then - let Str.271 : Str = StructAtIndex 1 Str.77; - inc Str.271; - dec Str.77; - let Str.270 : [C {U64, U8}, C Str] = TagId(1) Str.271; - ret Str.270; +procedure Str.9 (Str.77): + let Str.273 : U64 = 0i64; + let Str.274 : U64 = CallByName List.6 Str.77; + let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.273 Str.274; + let Str.270 : Int1 = StructAtIndex 2 Str.78; + if Str.270 then + let Str.272 : Str = StructAtIndex 1 Str.78; + inc Str.272; + dec Str.78; + let Str.271 : [C {U64, U8}, C Str] = TagId(1) Str.272; + ret Str.271; else - let Str.267 : U8 = StructAtIndex 3 Str.77; - let Str.268 : U64 = StructAtIndex 0 Str.77; - dec Str.77; - let Str.266 : {U64, U8} = Struct {Str.268, Str.267}; - let Str.265 : [C {U64, U8}, C Str] = TagId(0) Str.266; - ret Str.265; + let Str.268 : U8 = StructAtIndex 3 Str.78; + let Str.269 : U64 = StructAtIndex 0 Str.78; + dec Str.78; + let Str.267 : {U64, U8} = Struct {Str.269, Str.268}; + let Str.266 : [C {U64, U8}, C Str] = TagId(0) Str.267; + ret Str.266; procedure Test.3 (): let Test.0 : List U8 = Array [82i64, 111i64, 99i64]; let Test.8 : {} = CallByName Json.2; inc Test.0; - let Test.1 : [C [C List U8, C ], C Str] = CallByName Decode.26 Test.0 Test.8; + let Test.1 : [C [C List U8, C ], C Str] = CallByName Decode.27 Test.0 Test.8; let Test.7 : Str = "Roc"; let Test.6 : [C [C List U8, C ], C Str] = TagId(1) Test.7; inc Test.1; diff --git a/crates/compiler/test_mono/generated/issue_4759.txt b/crates/compiler/test_mono/generated/issue_4759.txt new file mode 100644 index 0000000000..ba80e36d15 --- /dev/null +++ b/crates/compiler/test_mono/generated/issue_4759.txt @@ -0,0 +1,13 @@ +procedure Test.1 (Test.2): + dec Test.2; + let Test.7 : Str = "ux"; + let Test.8 : Str = "uy"; + let Test.6 : {Str, Str} = Struct {Test.7, Test.8}; + ret Test.6; + +procedure Test.0 (): + let Test.10 : Str = "x"; + let Test.11 : Str = "y"; + let Test.9 : {Str, Str} = Struct {Test.10, Test.11}; + let Test.3 : {Str, Str} = CallByName Test.1 Test.9; + ret Test.3; diff --git a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt index 1145a82d45..11f20e2fc6 100644 --- a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt +++ b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt @@ -1,3 +1,7 @@ +procedure Bool.1 (): + let Bool.36 : Int1 = false; + ret Bool.36; + procedure Bool.11 (#Attr.2, #Attr.3): let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3; dec #Attr.3; @@ -9,298 +13,362 @@ procedure Bool.11 (#Attr.2, #Attr.3): ret Bool.24; procedure Bool.11 (#Attr.2, #Attr.3): - let Bool.31 : Int1 = lowlevel Eq #Attr.2 #Attr.3; - ret Bool.31; + let Bool.39 : Int1 = lowlevel Eq #Attr.2 #Attr.3; + ret Bool.39; procedure Bool.11 (#Attr.2, #Attr.3): - let Bool.38 : Int1 = lowlevel Eq #Attr.2 #Attr.3; - ret Bool.38; + let Bool.46 : Int1 = lowlevel Eq #Attr.2 #Attr.3; + ret Bool.46; procedure Bool.12 (#Attr.2, #Attr.3): - let Bool.30 : Int1 = lowlevel NotEq #Attr.2 #Attr.3; - ret Bool.30; + let Bool.38 : Int1 = lowlevel NotEq #Attr.2 #Attr.3; + ret Bool.38; + +procedure Bool.2 (): + let Bool.35 : Int1 = true; + ret Bool.35; procedure Bool.7 (Bool.19, Bool.20): - let Bool.29 : Int1 = CallByName Bool.12 Bool.19 Bool.20; - ret Bool.29; + let Bool.37 : Int1 = CallByName Bool.12 Bool.19 Bool.20; + ret Bool.37; -procedure Decode.23 (Decode.94): - ret Decode.94; +procedure Decode.24 (Decode.101): + ret Decode.101; -procedure Decode.24 (Decode.95, Decode.114, Decode.97): - let Decode.117 : {List U8, [C {}, C Str]} = CallByName Json.293 Decode.95 Decode.97; - ret Decode.117; +procedure Decode.25 (Decode.102, Decode.121, Decode.104): + let Decode.124 : {List U8, [C {}, C Str]} = CallByName Json.315 Decode.102 Decode.104; + ret Decode.124; -procedure Decode.25 (Decode.98, Decode.99): - let Decode.116 : {} = CallByName Json.41; - let Decode.115 : {List U8, [C {}, C Str]} = CallByName Decode.24 Decode.98 Decode.116 Decode.99; - ret Decode.115; +procedure Decode.26 (Decode.105, Decode.106): + let Decode.123 : {} = CallByName Json.43; + let Decode.122 : {List U8, [C {}, C Str]} = CallByName Decode.25 Decode.105 Decode.123 Decode.106; + ret Decode.122; -procedure Json.139 (Json.452, Json.453): - joinpoint Json.421 Json.418 Json.138: - let Json.141 : List U8 = StructAtIndex 0 Json.418; - inc Json.141; - let Json.140 : List U8 = StructAtIndex 1 Json.418; - inc Json.140; - dec Json.418; - let Json.422 : [C {}, C U8] = CallByName List.9 Json.141; - let Json.436 : U8 = 1i64; - let Json.437 : U8 = GetTagId Json.422; - let Json.438 : Int1 = lowlevel Eq Json.436 Json.437; - if Json.438 then - let Json.142 : U8 = UnionAtIndex (Id 1) (Index 0) Json.422; - let Json.424 : Int1 = CallByName Json.283 Json.142; - if Json.424 then - let Json.434 : U64 = 1i64; - let Json.430 : {List U8, List U8} = CallByName List.52 Json.141 Json.434; - let Json.431 : {} = Struct {}; - let Json.428 : List U8 = CallByName Json.143 Json.430; - let Json.429 : List U8 = CallByName List.4 Json.140 Json.142; - let Json.426 : {List U8, List U8} = Struct {Json.428, Json.429}; - jump Json.421 Json.426 Json.138; +procedure Json.160 (Json.570, Json.571): + joinpoint Json.508 Json.505 Json.159: + let Json.162 : List U8 = StructAtIndex 0 Json.505; + inc Json.162; + let Json.161 : List U8 = StructAtIndex 1 Json.505; + inc Json.161; + dec Json.505; + joinpoint Json.546: + let Json.543 : {List U8, List U8} = Struct {Json.162, Json.161}; + ret Json.543; + in + let Json.554 : U64 = lowlevel ListLen Json.162; + let Json.555 : U64 = 2i64; + let Json.556 : Int1 = lowlevel NumGte Json.554 Json.555; + if Json.556 then + let Json.545 : U64 = 0i64; + let Json.163 : U8 = lowlevel ListGetUnsafe Json.162 Json.545; + let Json.544 : U64 = 1i64; + let Json.164 : U8 = lowlevel ListGetUnsafe Json.162 Json.544; + let Json.516 : Int1 = CallByName Json.23 Json.163 Json.164; + if Json.516 then + let Json.523 : U64 = 2i64; + let Json.520 : List U8 = CallByName List.29 Json.162 Json.523; + let Json.522 : List U8 = CallByName List.4 Json.161 Json.163; + let Json.521 : List U8 = CallByName List.4 Json.522 Json.164; + let Json.518 : {List U8, List U8} = Struct {Json.520, Json.521}; + jump Json.508 Json.518 Json.159; else - let Json.423 : {List U8, List U8} = Struct {Json.141, Json.140}; - ret Json.423; + let Json.510 : Int1 = CallByName Json.305 Json.163; + if Json.510 then + let Json.514 : List U8 = CallByName List.38 Json.162; + let Json.515 : List U8 = CallByName List.4 Json.161 Json.163; + let Json.512 : {List U8, List U8} = Struct {Json.514, Json.515}; + jump Json.508 Json.512 Json.159; + else + let Json.509 : {List U8, List U8} = Struct {Json.162, Json.161}; + ret Json.509; else - let Json.435 : {List U8, List U8} = Struct {Json.141, Json.140}; - ret Json.435; + let Json.551 : U64 = lowlevel ListLen Json.162; + let Json.552 : U64 = 1i64; + let Json.553 : Int1 = lowlevel NumGte Json.551 Json.552; + if Json.553 then + let Json.550 : U64 = 0i64; + let Json.165 : U8 = lowlevel ListGetUnsafe Json.162 Json.550; + joinpoint Json.548 Json.547: + if Json.547 then + let Json.541 : List U8 = CallByName List.38 Json.162; + let Json.542 : List U8 = CallByName List.4 Json.161 Json.165; + let Json.539 : {List U8, List U8} = Struct {Json.541, Json.542}; + jump Json.508 Json.539 Json.159; + else + jump Json.546; + in + let Json.549 : Int1 = CallByName Json.305 Json.165; + jump Json.548 Json.549; + else + jump Json.546; in - jump Json.421 Json.452 Json.453; - -procedure Json.143 (Json.432): - let Json.433 : List U8 = StructAtIndex 1 Json.432; - inc Json.433; - dec Json.432; - ret Json.433; + jump Json.508 Json.570 Json.571; procedure Json.2 (): - let Json.396 : {} = Struct {}; - ret Json.396; + let Json.483 : {} = Struct {}; + ret Json.483; -procedure Json.22 (Json.137, Json.138): - let Json.440 : List U8 = Array []; - let Json.420 : {List U8, List U8} = Struct {Json.137, Json.440}; - let Json.419 : {List U8, List U8} = CallByName Json.139 Json.420 Json.138; - ret Json.419; - -procedure Json.283 (Json.284): - let Json.442 : U8 = 34i64; - let Json.441 : Int1 = CallByName Bool.7 Json.284 Json.442; - ret Json.441; - -procedure Json.293 (Json.294, Json.399): - let Json.400 : {List U8, [C {}, C Str]} = CallByName Json.40 Json.294; - ret Json.400; - -procedure Json.40 (Json.276): - let Json.446 : U64 = 1i64; - inc Json.276; - let Json.445 : {List U8, List U8} = CallByName List.52 Json.276 Json.446; - let Json.277 : List U8 = StructAtIndex 0 Json.445; - inc Json.277; - let Json.279 : List U8 = StructAtIndex 1 Json.445; - inc Json.279; - dec Json.445; - let Json.444 : U8 = 34i64; - let Json.443 : List U8 = Array [Json.444]; - let Json.404 : Int1 = CallByName Bool.11 Json.277 Json.443; - dec Json.443; - dec Json.277; - if Json.404 then - dec Json.276; - let Json.417 : {} = Struct {}; - let Json.416 : {List U8, List U8} = CallByName Json.22 Json.279 Json.417; - let Json.282 : List U8 = StructAtIndex 0 Json.416; - inc Json.282; - let Json.281 : List U8 = StructAtIndex 1 Json.416; - inc Json.281; - dec Json.416; - let Json.405 : [C {U64, U8}, C Str] = CallByName Str.9 Json.281; - let Json.413 : U8 = 1i64; - let Json.414 : U8 = GetTagId Json.405; - let Json.415 : Int1 = lowlevel Eq Json.413 Json.414; - if Json.415 then - let Json.285 : Str = UnionAtIndex (Id 1) (Index 0) Json.405; - inc Json.285; - dec Json.405; - let Json.409 : U64 = 1i64; - let Json.408 : {List U8, List U8} = CallByName List.52 Json.282 Json.409; - let Json.287 : List U8 = StructAtIndex 1 Json.408; - inc Json.287; - dec Json.408; - let Json.407 : [C {}, C Str] = TagId(1) Json.285; - let Json.406 : {List U8, [C {}, C Str]} = Struct {Json.287, Json.407}; - ret Json.406; - else - dec Json.405; - let Json.412 : {} = Struct {}; - let Json.411 : [C {}, C Str] = TagId(0) Json.412; - let Json.410 : {List U8, [C {}, C Str]} = Struct {Json.282, Json.411}; - ret Json.410; - else - dec Json.279; - let Json.403 : {} = Struct {}; - let Json.402 : [C {}, C Str] = TagId(0) Json.403; - let Json.401 : {List U8, [C {}, C Str]} = Struct {Json.276, Json.402}; - ret Json.401; - -procedure Json.41 (): - let Json.398 : {} = Struct {}; - let Json.397 : {} = CallByName Decode.23 Json.398; - ret Json.397; - -procedure List.2 (List.95, List.96): - let List.530 : U64 = CallByName List.6 List.95; - let List.526 : Int1 = CallByName Num.22 List.96 List.530; - if List.526 then - let List.528 : U8 = CallByName List.66 List.95 List.96; - let List.527 : [C {}, C U8] = TagId(1) List.528; - ret List.527; - else - let List.525 : {} = Struct {}; - let List.524 : [C {}, C U8] = TagId(0) List.525; - ret List.524; - -procedure List.4 (List.106, List.107): - let List.514 : U64 = 1i64; - let List.512 : List U8 = CallByName List.70 List.106 List.514; - let List.511 : List U8 = CallByName List.71 List.512 List.107; - ret List.511; - -procedure List.49 (List.366, List.367): - let List.486 : U64 = StructAtIndex 0 List.367; - let List.487 : U64 = 0i64; - let List.484 : Int1 = CallByName Bool.11 List.486 List.487; - if List.484 then - dec List.366; - let List.485 : List U8 = Array []; - ret List.485; - else - let List.481 : U64 = StructAtIndex 1 List.367; - let List.482 : U64 = StructAtIndex 0 List.367; - let List.480 : List U8 = CallByName List.72 List.366 List.481 List.482; - ret List.480; - -procedure List.52 (List.381, List.382): - let List.383 : U64 = CallByName List.6 List.381; - joinpoint List.509 List.384: - let List.507 : U64 = 0i64; - let List.506 : {U64, U64} = Struct {List.384, List.507}; - inc List.381; - let List.385 : List U8 = CallByName List.49 List.381 List.506; - let List.505 : U64 = CallByName Num.20 List.383 List.384; - let List.504 : {U64, U64} = Struct {List.505, List.384}; - let List.386 : List U8 = CallByName List.49 List.381 List.504; - let List.503 : {List U8, List U8} = Struct {List.385, List.386}; - ret List.503; +procedure Json.23 (Json.155, Json.156): + let Json.524 : {U8, U8} = Struct {Json.155, Json.156}; + joinpoint Json.533: + let Json.532 : Int1 = CallByName Bool.1; + ret Json.532; in - let List.510 : Int1 = CallByName Num.24 List.383 List.382; - if List.510 then - jump List.509 List.382; + let Json.535 : U8 = StructAtIndex 0 Json.524; + let Json.536 : U8 = 92i64; + let Json.537 : Int1 = lowlevel Eq Json.536 Json.535; + if Json.537 then + let Json.534 : U8 = StructAtIndex 1 Json.524; + switch Json.534: + case 98: + let Json.525 : Int1 = CallByName Bool.2; + ret Json.525; + + case 102: + let Json.526 : Int1 = CallByName Bool.2; + ret Json.526; + + case 110: + let Json.527 : Int1 = CallByName Bool.2; + ret Json.527; + + case 114: + let Json.528 : Int1 = CallByName Bool.2; + ret Json.528; + + case 116: + let Json.529 : Int1 = CallByName Bool.2; + ret Json.529; + + case 34: + let Json.530 : Int1 = CallByName Bool.2; + ret Json.530; + + case 92: + let Json.531 : Int1 = CallByName Bool.2; + ret Json.531; + + default: + jump Json.533; + else - jump List.509 List.383; + jump Json.533; + +procedure Json.24 (Json.158, Json.159): + let Json.558 : List U8 = Array []; + let Json.507 : {List U8, List U8} = Struct {Json.158, Json.558}; + let Json.506 : {List U8, List U8} = CallByName Json.160 Json.507 Json.159; + ret Json.506; + +procedure Json.305 (Json.306): + let Json.560 : U8 = 34i64; + let Json.559 : Int1 = CallByName Bool.7 Json.306 Json.560; + ret Json.559; + +procedure Json.315 (Json.316, Json.486): + let Json.487 : {List U8, [C {}, C Str]} = CallByName Json.42 Json.316; + ret Json.487; + +procedure Json.42 (Json.298): + let Json.564 : U64 = 1i64; + inc Json.298; + let Json.563 : {List U8, List U8} = CallByName List.52 Json.298 Json.564; + let Json.299 : List U8 = StructAtIndex 0 Json.563; + inc Json.299; + let Json.301 : List U8 = StructAtIndex 1 Json.563; + inc Json.301; + dec Json.563; + let Json.562 : U8 = 34i64; + let Json.561 : List U8 = Array [Json.562]; + let Json.491 : Int1 = CallByName Bool.11 Json.299 Json.561; + dec Json.561; + dec Json.299; + if Json.491 then + dec Json.298; + let Json.504 : {} = Struct {}; + let Json.503 : {List U8, List U8} = CallByName Json.24 Json.301 Json.504; + let Json.304 : List U8 = StructAtIndex 0 Json.503; + inc Json.304; + let Json.303 : List U8 = StructAtIndex 1 Json.503; + inc Json.303; + dec Json.503; + let Json.492 : [C {U64, U8}, C Str] = CallByName Str.9 Json.303; + let Json.500 : U8 = 1i64; + let Json.501 : U8 = GetTagId Json.492; + let Json.502 : Int1 = lowlevel Eq Json.500 Json.501; + if Json.502 then + let Json.307 : Str = UnionAtIndex (Id 1) (Index 0) Json.492; + inc Json.307; + dec Json.492; + let Json.496 : U64 = 1i64; + let Json.495 : {List U8, List U8} = CallByName List.52 Json.304 Json.496; + let Json.309 : List U8 = StructAtIndex 1 Json.495; + inc Json.309; + dec Json.495; + let Json.494 : [C {}, C Str] = TagId(1) Json.307; + let Json.493 : {List U8, [C {}, C Str]} = Struct {Json.309, Json.494}; + ret Json.493; + else + dec Json.492; + let Json.499 : {} = Struct {}; + let Json.498 : [C {}, C Str] = TagId(0) Json.499; + let Json.497 : {List U8, [C {}, C Str]} = Struct {Json.304, Json.498}; + ret Json.497; + else + dec Json.301; + let Json.490 : {} = Struct {}; + let Json.489 : [C {}, C Str] = TagId(0) Json.490; + let Json.488 : {List U8, [C {}, C Str]} = Struct {Json.298, Json.489}; + ret Json.488; + +procedure Json.43 (): + let Json.485 : {} = Struct {}; + let Json.484 : {} = CallByName Decode.24 Json.485; + ret Json.484; + +procedure List.29 (List.298, List.299): + let List.543 : U64 = CallByName List.6 List.298; + let List.300 : U64 = CallByName Num.77 List.543 List.299; + let List.538 : List U8 = CallByName List.43 List.298 List.300; + ret List.538; + +procedure List.31 (#Attr.2, #Attr.3): + let List.535 : List U8 = lowlevel ListDropAt #Attr.2 #Attr.3; + ret List.535; + +procedure List.38 (List.292): + let List.537 : U64 = 0i64; + let List.536 : List U8 = CallByName List.31 List.292 List.537; + ret List.536; + +procedure List.4 (List.107, List.108): + let List.532 : U64 = 1i64; + let List.531 : List U8 = CallByName List.70 List.107 List.532; + let List.530 : List U8 = CallByName List.71 List.531 List.108; + ret List.530; + +procedure List.43 (List.296, List.297): + let List.542 : U64 = CallByName List.6 List.296; + let List.541 : U64 = CallByName Num.77 List.542 List.297; + let List.540 : {U64, U64} = Struct {List.297, List.541}; + let List.539 : List U8 = CallByName List.49 List.296 List.540; + ret List.539; + +procedure List.49 (List.370, List.371): + let List.502 : U64 = StructAtIndex 0 List.371; + let List.503 : U64 = 0i64; + let List.500 : Int1 = CallByName Bool.11 List.502 List.503; + if List.500 then + dec List.370; + let List.501 : List U8 = Array []; + ret List.501; + else + let List.497 : U64 = StructAtIndex 1 List.371; + let List.498 : U64 = StructAtIndex 0 List.371; + let List.496 : List U8 = CallByName List.72 List.370 List.497 List.498; + ret List.496; + +procedure List.52 (List.385, List.386): + let List.387 : U64 = CallByName List.6 List.385; + joinpoint List.517 List.388: + let List.515 : U64 = 0i64; + let List.514 : {U64, U64} = Struct {List.388, List.515}; + inc List.385; + let List.389 : List U8 = CallByName List.49 List.385 List.514; + let List.513 : U64 = CallByName Num.20 List.387 List.388; + let List.512 : {U64, U64} = Struct {List.513, List.388}; + let List.390 : List U8 = CallByName List.49 List.385 List.512; + let List.511 : {List U8, List U8} = Struct {List.389, List.390}; + ret List.511; + in + let List.518 : Int1 = CallByName Num.24 List.387 List.386; + if List.518 then + jump List.517 List.386; + else + jump List.517 List.387; procedure List.6 (#Attr.2): - let List.550 : U64 = lowlevel ListLen #Attr.2; - ret List.550; - -procedure List.66 (#Attr.2, #Attr.3): - let List.529 : U8 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.529; + let List.565 : U64 = lowlevel ListLen #Attr.2; + ret List.565; procedure List.70 (#Attr.2, #Attr.3): - let List.515 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.515; + let List.523 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.523; procedure List.71 (#Attr.2, #Attr.3): - let List.513 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.513; + let List.521 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.521; procedure List.72 (#Attr.2, #Attr.3, #Attr.4): - let List.483 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; - ret List.483; - -procedure List.9 (List.283): - let List.523 : U64 = 0i64; - let List.516 : [C {}, C U8] = CallByName List.2 List.283 List.523; - let List.520 : U8 = 1i64; - let List.521 : U8 = GetTagId List.516; - let List.522 : Int1 = lowlevel Eq List.520 List.521; - if List.522 then - let List.284 : U8 = UnionAtIndex (Id 1) (Index 0) List.516; - let List.517 : [C {}, C U8] = TagId(1) List.284; - ret List.517; - else - let List.519 : {} = Struct {}; - let List.518 : [C {}, C U8] = TagId(0) List.519; - ret List.518; + let List.499 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; + ret List.499; procedure Num.20 (#Attr.2, #Attr.3): - let Num.258 : U64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.258; - -procedure Num.22 (#Attr.2, #Attr.3): - let Num.262 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.262; + let Num.276 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.276; procedure Num.24 (#Attr.2, #Attr.3): - let Num.261 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; - ret Num.261; + let Num.278 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.278; + +procedure Num.77 (#Attr.2, #Attr.3): + let Num.280 : U64 = lowlevel NumSubSaturated #Attr.2 #Attr.3; + ret Num.280; procedure Str.12 (#Attr.2): - let Str.274 : List U8 = lowlevel StrToUtf8 #Attr.2; - ret Str.274; + let Str.275 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.275; -procedure Str.27 (Str.96): - let Str.265 : [C {}, C I64] = CallByName Str.69 Str.96; - ret Str.265; +procedure Str.27 (Str.97): + let Str.266 : [C {}, C I64] = CallByName Str.70 Str.97; + ret Str.266; procedure Str.47 (#Attr.2): - let Str.273 : {I64, U8} = lowlevel StrToNum #Attr.2; - ret Str.273; + let Str.274 : {I64, U8} = lowlevel StrToNum #Attr.2; + ret Str.274; procedure Str.48 (#Attr.2, #Attr.3, #Attr.4): - let Str.288 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; - ret Str.288; + let Str.289 : {U64, Str, Int1, U8} = lowlevel StrFromUtf8Range #Attr.2 #Attr.3 #Attr.4; + ret Str.289; -procedure Str.69 (Str.231): - let Str.232 : {I64, U8} = CallByName Str.47 Str.231; - let Str.271 : U8 = StructAtIndex 1 Str.232; - let Str.272 : U8 = 0i64; - let Str.268 : Int1 = CallByName Bool.11 Str.271 Str.272; - if Str.268 then - let Str.270 : I64 = StructAtIndex 0 Str.232; - let Str.269 : [C {}, C I64] = TagId(1) Str.270; - ret Str.269; +procedure Str.70 (Str.232): + let Str.233 : {I64, U8} = CallByName Str.47 Str.232; + let Str.272 : U8 = StructAtIndex 1 Str.233; + let Str.273 : U8 = 0i64; + let Str.269 : Int1 = CallByName Bool.11 Str.272 Str.273; + if Str.269 then + let Str.271 : I64 = StructAtIndex 0 Str.233; + let Str.270 : [C {}, C I64] = TagId(1) Str.271; + ret Str.270; else - let Str.267 : {} = Struct {}; - let Str.266 : [C {}, C I64] = TagId(0) Str.267; - ret Str.266; + let Str.268 : {} = Struct {}; + let Str.267 : [C {}, C I64] = TagId(0) Str.268; + ret Str.267; -procedure Str.9 (Str.76): - let Str.286 : U64 = 0i64; - let Str.287 : U64 = CallByName List.6 Str.76; - let Str.77 : {U64, Str, Int1, U8} = CallByName Str.48 Str.76 Str.286 Str.287; - let Str.283 : Int1 = StructAtIndex 2 Str.77; - if Str.283 then - let Str.285 : Str = StructAtIndex 1 Str.77; - inc Str.285; - dec Str.77; - let Str.284 : [C {U64, U8}, C Str] = TagId(1) Str.285; - ret Str.284; +procedure Str.9 (Str.77): + let Str.287 : U64 = 0i64; + let Str.288 : U64 = CallByName List.6 Str.77; + let Str.78 : {U64, Str, Int1, U8} = CallByName Str.48 Str.77 Str.287 Str.288; + let Str.284 : Int1 = StructAtIndex 2 Str.78; + if Str.284 then + let Str.286 : Str = StructAtIndex 1 Str.78; + inc Str.286; + dec Str.78; + let Str.285 : [C {U64, U8}, C Str] = TagId(1) Str.286; + ret Str.285; else - let Str.281 : U8 = StructAtIndex 3 Str.77; - let Str.282 : U64 = StructAtIndex 0 Str.77; - dec Str.77; - let Str.280 : {U64, U8} = Struct {Str.282, Str.281}; - let Str.279 : [C {U64, U8}, C Str] = TagId(0) Str.280; - ret Str.279; + let Str.282 : U8 = StructAtIndex 3 Str.78; + let Str.283 : U64 = StructAtIndex 0 Str.78; + dec Str.78; + let Str.281 : {U64, U8} = Struct {Str.283, Str.282}; + let Str.280 : [C {U64, U8}, C Str] = TagId(0) Str.281; + ret Str.280; procedure Test.0 (): let Test.37 : Str = "-1234"; let Test.35 : List U8 = CallByName Str.12 Test.37; let Test.36 : {} = CallByName Json.2; - let Test.34 : {List U8, [C {}, C Str]} = CallByName Decode.25 Test.35 Test.36; + let Test.34 : {List U8, [C {}, C Str]} = CallByName Decode.26 Test.35 Test.36; let Test.2 : List U8 = StructAtIndex 0 Test.34; inc Test.2; let Test.1 : [C {}, C Str] = StructAtIndex 1 Test.34; diff --git a/crates/compiler/test_mono/generated/lambda_capture_niche_u8_vs_u64.txt b/crates/compiler/test_mono/generated/lambda_capture_niche_u8_vs_u64.txt index 351d5255f1..33ffa57579 100644 --- a/crates/compiler/test_mono/generated/lambda_capture_niche_u8_vs_u64.txt +++ b/crates/compiler/test_mono/generated/lambda_capture_niche_u8_vs_u64.txt @@ -1,54 +1,51 @@ -procedure Num.94 (#Attr.2): - let Num.256 : Str = lowlevel NumToStr #Attr.2; - ret Num.256; +procedure Num.96 (#Attr.2): + let Num.275 : Str = lowlevel NumToStr #Attr.2; + ret Num.275; -procedure Num.94 (#Attr.2): - let Num.257 : Str = lowlevel NumToStr #Attr.2; - ret Num.257; +procedure Num.96 (#Attr.2): + let Num.276 : Str = lowlevel NumToStr #Attr.2; + ret Num.276; procedure Test.1 (Test.4): - let Test.16 : [C U8, C U64] = TagId(1) Test.4; - ret Test.16; + let Test.13 : [C U8, C U64] = TagId(1) Test.4; + ret Test.13; procedure Test.1 (Test.4): - let Test.22 : [C U8, C U64] = TagId(0) Test.4; - ret Test.22; - -procedure Test.5 (Test.17, #Attr.12): - let Test.4 : U64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.19 : Str = CallByName Num.94 Test.4; + let Test.19 : [C U8, C U64] = TagId(0) Test.4; ret Test.19; -procedure Test.5 (Test.17, #Attr.12): +procedure Test.5 (Test.14, #Attr.12): + let Test.4 : U64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; + let Test.16 : Str = CallByName Num.96 Test.4; + ret Test.16; + +procedure Test.5 (Test.14, #Attr.12): let Test.4 : U8 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.25 : Str = CallByName Num.94 Test.4; - ret Test.25; + let Test.22 : Str = CallByName Num.96 Test.4; + ret Test.22; procedure Test.0 (): let Test.2 : Int1 = true; - joinpoint Test.13 Test.3: + joinpoint Test.10 Test.3: let Test.8 : {} = Struct {}; let Test.9 : U8 = GetTagId Test.3; - joinpoint Test.10 Test.7: - ret Test.7; - in switch Test.9: case 0: - let Test.11 : Str = CallByName Test.5 Test.8 Test.3; - jump Test.10 Test.11; + let Test.7 : Str = CallByName Test.5 Test.8 Test.3; + ret Test.7; default: - let Test.12 : Str = CallByName Test.5 Test.8 Test.3; - jump Test.10 Test.12; + let Test.7 : Str = CallByName Test.5 Test.8 Test.3; + ret Test.7; in - let Test.26 : Int1 = true; - let Test.27 : Int1 = lowlevel Eq Test.26 Test.2; - if Test.27 then - let Test.15 : U64 = 123i64; - let Test.14 : [C U8, C U64] = CallByName Test.1 Test.15; - jump Test.13 Test.14; + let Test.23 : Int1 = true; + let Test.24 : Int1 = lowlevel Eq Test.23 Test.2; + if Test.24 then + let Test.12 : U64 = 123i64; + let Test.11 : [C U8, C U64] = CallByName Test.1 Test.12; + jump Test.10 Test.11; else - let Test.21 : U8 = 18i64; - let Test.20 : [C U8, C U64] = CallByName Test.1 Test.21; - jump Test.13 Test.20; + let Test.18 : U8 = 18i64; + let Test.17 : [C U8, C U64] = CallByName Test.1 Test.18; + jump Test.10 Test.17; diff --git a/crates/compiler/test_mono/generated/lambda_capture_niches_have_captured_function_in_closure.txt b/crates/compiler/test_mono/generated/lambda_capture_niches_have_captured_function_in_closure.txt index f4b8c206eb..87fadf120d 100644 --- a/crates/compiler/test_mono/generated/lambda_capture_niches_have_captured_function_in_closure.txt +++ b/crates/compiler/test_mono/generated/lambda_capture_niches_have_captured_function_in_closure.txt @@ -1,83 +1,80 @@ -procedure Test.11 (Test.37): - let Test.38 : Str = ""; - ret Test.38; +procedure Test.11 (Test.34): + let Test.35 : Str = ""; + ret Test.35; -procedure Test.13 (Test.51, Test.12): +procedure Test.13 (Test.48, Test.12): ret Test.12; -procedure Test.15 (Test.39): - let Test.40 : Str = ""; - ret Test.40; +procedure Test.15 (Test.36): + let Test.37 : Str = ""; + ret Test.37; -procedure Test.16 (Test.54): - let Test.56 : Str = "s1"; - ret Test.56; +procedure Test.16 (Test.51): + let Test.53 : Str = "s1"; + ret Test.53; procedure Test.2 (Test.7, Test.8): - let Test.30 : [C {} {}, C {} {}] = TagId(1) Test.7 Test.8; - ret Test.30; + let Test.27 : [C {} {}, C {} {}] = TagId(1) Test.7 Test.8; + ret Test.27; procedure Test.2 (Test.7, Test.8): - let Test.44 : [C {} {}, C {} {}] = TagId(0) Test.7 Test.8; - ret Test.44; + let Test.41 : [C {} {}, C {} {}] = TagId(0) Test.7 Test.8; + ret Test.41; procedure Test.3 (Test.17): - let Test.36 : {} = Struct {}; - ret Test.36; + let Test.33 : {} = Struct {}; + ret Test.33; procedure Test.4 (Test.18): inc Test.18; ret Test.18; -procedure Test.9 (Test.29, #Attr.12): +procedure Test.9 (Test.26, #Attr.12): let Test.8 : {} = UnionAtIndex (Id 0) (Index 1) #Attr.12; let Test.7 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.49 : {} = Struct {}; - let Test.48 : Str = CallByName Test.16 Test.49; - let Test.45 : Str = CallByName Test.4 Test.48; - dec Test.48; - let Test.47 : {} = Struct {}; - let Test.46 : Str = CallByName Test.13 Test.47 Test.45; - ret Test.46; + let Test.46 : {} = Struct {}; + let Test.45 : Str = CallByName Test.16 Test.46; + let Test.42 : Str = CallByName Test.4 Test.45; + dec Test.45; + let Test.44 : {} = Struct {}; + let Test.43 : Str = CallByName Test.13 Test.44 Test.42; + ret Test.43; -procedure Test.9 (Test.29, #Attr.12): +procedure Test.9 (Test.26, #Attr.12): let Test.8 : {} = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.7 : {} = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.35 : {} = Struct {}; - let Test.34 : Str = CallByName Test.15 Test.35; - let Test.31 : {} = CallByName Test.3 Test.34; - dec Test.34; - let Test.33 : {} = Struct {}; - let Test.32 : Str = CallByName Test.11 Test.33; - ret Test.32; + let Test.32 : {} = Struct {}; + let Test.31 : Str = CallByName Test.15 Test.32; + let Test.28 : {} = CallByName Test.3 Test.31; + dec Test.31; + let Test.30 : {} = Struct {}; + let Test.29 : Str = CallByName Test.11 Test.30; + ret Test.29; procedure Test.0 (): let Test.5 : Int1 = true; - joinpoint Test.25 Test.6: + joinpoint Test.22 Test.6: let Test.20 : {} = Struct {}; let Test.21 : U8 = GetTagId Test.6; - joinpoint Test.22 Test.19: - ret Test.19; - in switch Test.21: case 0: - let Test.23 : Str = CallByName Test.9 Test.20 Test.6; - jump Test.22 Test.23; + let Test.19 : Str = CallByName Test.9 Test.20 Test.6; + ret Test.19; default: - let Test.24 : Str = CallByName Test.9 Test.20 Test.6; - jump Test.22 Test.24; + let Test.19 : Str = CallByName Test.9 Test.20 Test.6; + ret Test.19; in - let Test.57 : Int1 = true; - let Test.58 : Int1 = lowlevel Eq Test.57 Test.5; - if Test.58 then - let Test.27 : {} = Struct {}; - let Test.28 : {} = Struct {}; - let Test.26 : [C {} {}, C {} {}] = CallByName Test.2 Test.27 Test.28; - jump Test.25 Test.26; + let Test.54 : Int1 = true; + let Test.55 : Int1 = lowlevel Eq Test.54 Test.5; + if Test.55 then + let Test.24 : {} = Struct {}; + let Test.25 : {} = Struct {}; + let Test.23 : [C {} {}, C {} {}] = CallByName Test.2 Test.24 Test.25; + jump Test.22 Test.23; else - let Test.42 : {} = Struct {}; - let Test.43 : {} = Struct {}; - let Test.41 : [C {} {}, C {} {}] = CallByName Test.2 Test.42 Test.43; - jump Test.25 Test.41; + let Test.39 : {} = Struct {}; + let Test.40 : {} = Struct {}; + let Test.38 : [C {} {}, C {} {}] = CallByName Test.2 Test.39 Test.40; + jump Test.22 Test.38; diff --git a/crates/compiler/test_mono/generated/lambda_capture_niches_with_non_capturing_function.txt b/crates/compiler/test_mono/generated/lambda_capture_niches_with_non_capturing_function.txt index 4985129845..32d303f9ea 100644 --- a/crates/compiler/test_mono/generated/lambda_capture_niches_with_non_capturing_function.txt +++ b/crates/compiler/test_mono/generated/lambda_capture_niches_with_non_capturing_function.txt @@ -1,59 +1,56 @@ procedure Test.1 (Test.5): - let Test.19 : [C , C {}, C U64] = TagId(1) Test.5; - ret Test.19; + let Test.15 : [C , C {}, C U64] = TagId(1) Test.5; + ret Test.15; procedure Test.1 (Test.5): - let Test.27 : [C , C {}, C U64] = TagId(2) Test.5; - ret Test.27; + let Test.23 : [C , C {}, C U64] = TagId(2) Test.5; + ret Test.23; procedure Test.2 (Test.8): - let Test.24 : Str = ""; - ret Test.24; + let Test.20 : Str = ""; + ret Test.20; -procedure Test.6 (Test.20, #Attr.12): +procedure Test.6 (Test.16, #Attr.12): let Test.5 : U64 = UnionAtIndex (Id 2) (Index 0) #Attr.12; - let Test.30 : Str = ""; - ret Test.30; + let Test.26 : Str = ""; + ret Test.26; -procedure Test.6 (Test.20, #Attr.12): +procedure Test.6 (Test.16, #Attr.12): let Test.5 : {} = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.22 : Str = ""; - ret Test.22; + let Test.18 : Str = ""; + ret Test.18; procedure Test.0 (): let Test.3 : U8 = 0u8; - joinpoint Test.16 Test.4: + joinpoint Test.12 Test.4: let Test.10 : {} = Struct {}; let Test.11 : U8 = GetTagId Test.4; - joinpoint Test.12 Test.9: - ret Test.9; - in switch Test.11: case 0: - let Test.13 : Str = CallByName Test.2 Test.10; - jump Test.12 Test.13; + let Test.9 : Str = CallByName Test.2 Test.10; + ret Test.9; case 1: - let Test.14 : Str = CallByName Test.6 Test.10 Test.4; - jump Test.12 Test.14; + let Test.9 : Str = CallByName Test.6 Test.10 Test.4; + ret Test.9; default: - let Test.15 : Str = CallByName Test.6 Test.10 Test.4; - jump Test.12 Test.15; + let Test.9 : Str = CallByName Test.6 Test.10 Test.4; + ret Test.9; in switch Test.3: case 0: - let Test.18 : {} = Struct {}; - let Test.17 : [C , C {}, C U64] = CallByName Test.1 Test.18; - jump Test.16 Test.17; + let Test.14 : {} = Struct {}; + let Test.13 : [C , C {}, C U64] = CallByName Test.1 Test.14; + jump Test.12 Test.13; case 1: - let Test.23 : [C , C {}, C U64] = TagId(0) ; - jump Test.16 Test.23; + let Test.19 : [C , C {}, C U64] = TagId(0) ; + jump Test.12 Test.19; default: - let Test.26 : U64 = 1i64; - let Test.25 : [C , C {}, C U64] = CallByName Test.1 Test.26; - jump Test.16 Test.25; + let Test.22 : U64 = 1i64; + let Test.21 : [C , C {}, C U64] = CallByName Test.1 Test.22; + jump Test.12 Test.21; diff --git a/crates/compiler/test_mono/generated/lambda_capture_niches_with_other_lambda_capture.txt b/crates/compiler/test_mono/generated/lambda_capture_niches_with_other_lambda_capture.txt index c41608e5c6..c87461357e 100644 --- a/crates/compiler/test_mono/generated/lambda_capture_niches_with_other_lambda_capture.txt +++ b/crates/compiler/test_mono/generated/lambda_capture_niches_with_other_lambda_capture.txt @@ -1,28 +1,28 @@ procedure Test.1 (Test.5): - let Test.20 : [C {}, C U64, C Str] = TagId(0) Test.5; - ret Test.20; + let Test.16 : [C {}, C U64, C Str] = TagId(0) Test.5; + ret Test.16; procedure Test.1 (Test.5): - let Test.32 : [C {}, C U64, C Str] = TagId(1) Test.5; - ret Test.32; + let Test.28 : [C {}, C U64, C Str] = TagId(1) Test.5; + ret Test.28; procedure Test.2 (Test.7): - let Test.26 : [C {}, C U64, C Str] = TagId(2) Test.7; - ret Test.26; + let Test.22 : [C {}, C U64, C Str] = TagId(2) Test.7; + ret Test.22; -procedure Test.6 (Test.21, #Attr.12): +procedure Test.6 (Test.17, #Attr.12): let Test.5 : U64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; dec #Attr.12; - let Test.35 : Str = ""; - ret Test.35; + let Test.31 : Str = ""; + ret Test.31; -procedure Test.6 (Test.21, #Attr.12): +procedure Test.6 (Test.17, #Attr.12): let Test.5 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; dec #Attr.12; - let Test.23 : Str = ""; - ret Test.23; + let Test.19 : Str = ""; + ret Test.19; -procedure Test.8 (Test.27, #Attr.12): +procedure Test.8 (Test.23, #Attr.12): let Test.7 : Str = UnionAtIndex (Id 2) (Index 0) #Attr.12; inc Test.7; dec #Attr.12; @@ -30,39 +30,36 @@ procedure Test.8 (Test.27, #Attr.12): procedure Test.0 (): let Test.3 : U8 = 0u8; - joinpoint Test.17 Test.4: + joinpoint Test.13 Test.4: let Test.11 : {} = Struct {}; let Test.12 : U8 = GetTagId Test.4; - joinpoint Test.13 Test.10: - ret Test.10; - in switch Test.12: case 0: - let Test.14 : Str = CallByName Test.6 Test.11 Test.4; - jump Test.13 Test.14; + let Test.10 : Str = CallByName Test.6 Test.11 Test.4; + ret Test.10; case 1: - let Test.15 : Str = CallByName Test.6 Test.11 Test.4; - jump Test.13 Test.15; + let Test.10 : Str = CallByName Test.6 Test.11 Test.4; + ret Test.10; default: - let Test.16 : Str = CallByName Test.8 Test.11 Test.4; - jump Test.13 Test.16; + let Test.10 : Str = CallByName Test.8 Test.11 Test.4; + ret Test.10; in switch Test.3: case 0: - let Test.19 : {} = Struct {}; - let Test.18 : [C {}, C U64, C Str] = CallByName Test.1 Test.19; - jump Test.17 Test.18; + let Test.15 : {} = Struct {}; + let Test.14 : [C {}, C U64, C Str] = CallByName Test.1 Test.15; + jump Test.13 Test.14; case 1: - let Test.25 : Str = "foo"; - let Test.24 : [C {}, C U64, C Str] = CallByName Test.2 Test.25; - jump Test.17 Test.24; + let Test.21 : Str = "foo"; + let Test.20 : [C {}, C U64, C Str] = CallByName Test.2 Test.21; + jump Test.13 Test.20; default: - let Test.31 : U64 = 1i64; - let Test.30 : [C {}, C U64, C Str] = CallByName Test.1 Test.31; - jump Test.17 Test.30; + let Test.27 : U64 = 1i64; + let Test.26 : [C {}, C U64, C Str] = CallByName Test.1 Test.27; + jump Test.13 Test.26; diff --git a/crates/compiler/test_mono/generated/lambda_set_niche_same_layout_different_constructor.txt b/crates/compiler/test_mono/generated/lambda_set_niche_same_layout_different_constructor.txt index 7e624d7a76..b551973cf8 100644 --- a/crates/compiler/test_mono/generated/lambda_set_niche_same_layout_different_constructor.txt +++ b/crates/compiler/test_mono/generated/lambda_set_niche_same_layout_different_constructor.txt @@ -2,6 +2,10 @@ procedure Test.1 (Test.4): inc Test.4; ret Test.4; +procedure Test.21 (Test.23, #Attr.12): + let Test.22 : Str = CallByName Test.5 Test.23 #Attr.12; + ret Test.22; + procedure Test.5 (Test.12, Test.4): dec Test.4; let Test.14 : Str = ""; diff --git a/crates/compiler/test_mono/generated/lambda_set_with_imported_toplevels_issue_4733.txt b/crates/compiler/test_mono/generated/lambda_set_with_imported_toplevels_issue_4733.txt index f771acb496..ef6c2e8aa4 100644 --- a/crates/compiler/test_mono/generated/lambda_set_with_imported_toplevels_issue_4733.txt +++ b/crates/compiler/test_mono/generated/lambda_set_with_imported_toplevels_issue_4733.txt @@ -7,34 +7,31 @@ procedure Bool.2 (): ret Bool.24; procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Num.21 (#Attr.2, #Attr.3): - let Num.257 : U64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : U64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.276; procedure Test.0 (Test.8): - let Test.23 : Int1 = CallByName Bool.2; - if Test.23 then - let Test.24 : Int1 = true; - ret Test.24; + let Test.20 : Int1 = CallByName Bool.2; + if Test.20 then + let Test.21 : Int1 = true; + ret Test.21; else - let Test.22 : Int1 = false; - ret Test.22; + let Test.19 : Int1 = false; + ret Test.19; procedure Test.5 (Test.6, Test.2): - joinpoint Test.19 Test.18: - ret Test.18; - in switch Test.2: case 0: - let Test.20 : U64 = CallByName Num.19 Test.6 Test.6; - jump Test.19 Test.20; + let Test.18 : U64 = CallByName Num.19 Test.6 Test.6; + ret Test.18; default: - let Test.21 : U64 = CallByName Num.21 Test.6 Test.6; - jump Test.19 Test.21; + let Test.18 : U64 = CallByName Num.21 Test.6 Test.6; + ret Test.18; procedure Test.7 (): diff --git a/crates/compiler/test_mono/generated/list_append.txt b/crates/compiler/test_mono/generated/list_append.txt index e595974541..6fd454f686 100644 --- a/crates/compiler/test_mono/generated/list_append.txt +++ b/crates/compiler/test_mono/generated/list_append.txt @@ -1,16 +1,16 @@ -procedure List.4 (List.106, List.107): - let List.481 : U64 = 1i64; - let List.479 : List I64 = CallByName List.70 List.106 List.481; - let List.478 : List I64 = CallByName List.71 List.479 List.107; - ret List.478; +procedure List.4 (List.107, List.108): + let List.497 : U64 = 1i64; + let List.495 : List I64 = CallByName List.70 List.107 List.497; + let List.494 : List I64 = CallByName List.71 List.495 List.108; + ret List.494; procedure List.70 (#Attr.2, #Attr.3): - let List.482 : List I64 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.482; + let List.498 : List I64 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.498; procedure List.71 (#Attr.2, #Attr.3): - let List.480 : List I64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.480; + let List.496 : List I64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.496; procedure Test.0 (): let Test.2 : List I64 = Array [1i64]; diff --git a/crates/compiler/test_mono/generated/list_append_closure.txt b/crates/compiler/test_mono/generated/list_append_closure.txt index 1c0aa8ab40..8c99c82d4a 100644 --- a/crates/compiler/test_mono/generated/list_append_closure.txt +++ b/crates/compiler/test_mono/generated/list_append_closure.txt @@ -1,16 +1,16 @@ -procedure List.4 (List.106, List.107): - let List.481 : U64 = 1i64; - let List.479 : List I64 = CallByName List.70 List.106 List.481; - let List.478 : List I64 = CallByName List.71 List.479 List.107; - ret List.478; +procedure List.4 (List.107, List.108): + let List.497 : U64 = 1i64; + let List.495 : List I64 = CallByName List.70 List.107 List.497; + let List.494 : List I64 = CallByName List.71 List.495 List.108; + ret List.494; procedure List.70 (#Attr.2, #Attr.3): - let List.482 : List I64 = lowlevel ListReserve #Attr.2 #Attr.3; - ret List.482; + let List.498 : List I64 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.498; procedure List.71 (#Attr.2, #Attr.3): - let List.480 : List I64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; - ret List.480; + let List.496 : List I64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.496; procedure Test.1 (Test.2): let Test.6 : I64 = 42i64; diff --git a/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt b/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt index ff5f26ca01..f585b00608 100644 --- a/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,35 +1,35 @@ -procedure List.3 (List.103, List.104, List.105): - let List.481 : {List I64, I64} = CallByName List.64 List.103 List.104 List.105; - let List.480 : List I64 = StructAtIndex 0 List.481; - inc List.480; - dec List.481; - ret List.480; +procedure List.3 (List.104, List.105, List.106): + let List.497 : {List I64, I64} = CallByName List.64 List.104 List.105 List.106; + let List.496 : List I64 = StructAtIndex 0 List.497; + inc List.496; + dec List.497; + ret List.496; procedure List.6 (#Attr.2): - let List.479 : U64 = lowlevel ListLen #Attr.2; - ret List.479; + let List.495 : U64 = lowlevel ListLen #Attr.2; + ret List.495; -procedure List.64 (List.100, List.101, List.102): - let List.486 : U64 = CallByName List.6 List.100; - let List.483 : Int1 = CallByName Num.22 List.101 List.486; - if List.483 then - let List.484 : {List I64, I64} = CallByName List.67 List.100 List.101 List.102; - ret List.484; +procedure List.64 (List.101, List.102, List.103): + let List.502 : U64 = CallByName List.6 List.101; + let List.499 : Int1 = CallByName Num.22 List.102 List.502; + if List.499 then + let List.500 : {List I64, I64} = CallByName List.67 List.101 List.102 List.103; + ret List.500; else - let List.482 : {List I64, I64} = Struct {List.100, List.102}; - ret List.482; + let List.498 : {List I64, I64} = Struct {List.101, List.103}; + ret List.498; procedure List.67 (#Attr.2, #Attr.3, #Attr.4): - let List.485 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret List.485; + let List.501 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.501; procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Num.22 (#Attr.2, #Attr.3): - let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.276; procedure Test.1 (): let Test.8 : List I64 = Array [1i64, 2i64, 3i64]; diff --git a/crates/compiler/test_mono/generated/list_get.txt b/crates/compiler/test_mono/generated/list_get.txt index df01e372ca..83729335b2 100644 --- a/crates/compiler/test_mono/generated/list_get.txt +++ b/crates/compiler/test_mono/generated/list_get.txt @@ -1,26 +1,26 @@ -procedure List.2 (List.95, List.96): - let List.484 : U64 = CallByName List.6 List.95; - let List.480 : Int1 = CallByName Num.22 List.96 List.484; - if List.480 then - let List.482 : I64 = CallByName List.66 List.95 List.96; - let List.481 : [C {}, C I64] = TagId(1) List.482; - ret List.481; +procedure List.2 (List.96, List.97): + let List.500 : U64 = CallByName List.6 List.96; + let List.496 : Int1 = CallByName Num.22 List.97 List.500; + if List.496 then + let List.498 : I64 = CallByName List.66 List.96 List.97; + let List.497 : [C {}, C I64] = TagId(1) List.498; + ret List.497; else - let List.479 : {} = Struct {}; - let List.478 : [C {}, C I64] = TagId(0) List.479; - ret List.478; + let List.495 : {} = Struct {}; + let List.494 : [C {}, C I64] = TagId(0) List.495; + ret List.494; procedure List.6 (#Attr.2): - let List.485 : U64 = lowlevel ListLen #Attr.2; - ret List.485; + let List.501 : U64 = lowlevel ListLen #Attr.2; + ret List.501; procedure List.66 (#Attr.2, #Attr.3): - let List.483 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.483; + let List.499 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.499; procedure Num.22 (#Attr.2, #Attr.3): - let Num.256 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.2): let Test.6 : List I64 = Array [1i64, 2i64, 3i64]; diff --git a/crates/compiler/test_mono/generated/list_len.txt b/crates/compiler/test_mono/generated/list_len.txt index faf9917673..2c9b2a0743 100644 --- a/crates/compiler/test_mono/generated/list_len.txt +++ b/crates/compiler/test_mono/generated/list_len.txt @@ -1,14 +1,14 @@ procedure List.6 (#Attr.2): - let List.478 : U64 = lowlevel ListLen #Attr.2; - ret List.478; + let List.494 : U64 = lowlevel ListLen #Attr.2; + ret List.494; procedure List.6 (#Attr.2): - let List.479 : U64 = lowlevel ListLen #Attr.2; - ret List.479; + let List.495 : U64 = lowlevel ListLen #Attr.2; + ret List.495; procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.1 : List I64 = Array [1i64, 2i64, 3i64]; diff --git a/crates/compiler/test_mono/generated/list_map_closure_borrows.txt b/crates/compiler/test_mono/generated/list_map_closure_borrows.txt index f793d3b887..ecd39766c1 100644 --- a/crates/compiler/test_mono/generated/list_map_closure_borrows.txt +++ b/crates/compiler/test_mono/generated/list_map_closure_borrows.txt @@ -1,38 +1,38 @@ -procedure List.2 (List.95, List.96): - let List.484 : U64 = CallByName List.6 List.95; - let List.480 : Int1 = CallByName Num.22 List.96 List.484; - if List.480 then - let List.482 : Str = CallByName List.66 List.95 List.96; - let List.481 : [C {}, C Str] = TagId(1) List.482; - ret List.481; +procedure List.2 (List.96, List.97): + let List.500 : U64 = CallByName List.6 List.96; + let List.496 : Int1 = CallByName Num.22 List.97 List.500; + if List.496 then + let List.498 : Str = CallByName List.66 List.96 List.97; + let List.497 : [C {}, C Str] = TagId(1) List.498; + ret List.497; else - let List.479 : {} = Struct {}; - let List.478 : [C {}, C Str] = TagId(0) List.479; - ret List.478; + let List.495 : {} = Struct {}; + let List.494 : [C {}, C Str] = TagId(0) List.495; + ret List.494; procedure List.5 (#Attr.2, #Attr.3): - let List.486 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; - ret List.486; + let List.502 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; + ret List.502; procedure List.6 (#Attr.2): - let List.485 : U64 = lowlevel ListLen #Attr.2; - ret List.485; + let List.501 : U64 = lowlevel ListLen #Attr.2; + ret List.501; procedure List.66 (#Attr.2, #Attr.3): - let List.483 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.483; + let List.499 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.499; procedure Num.22 (#Attr.2, #Attr.3): - let Num.256 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.275; procedure Str.16 (#Attr.2, #Attr.3): - let Str.265 : Str = lowlevel StrRepeat #Attr.2 #Attr.3; - ret Str.265; + let Str.266 : Str = lowlevel StrRepeat #Attr.2 #Attr.3; + ret Str.266; procedure Str.3 (#Attr.2, #Attr.3): - let Str.266 : Str = lowlevel StrConcat #Attr.2 #Attr.3; - ret Str.266; + let Str.267 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.267; procedure Test.1 (): let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; diff --git a/crates/compiler/test_mono/generated/list_map_closure_owns.txt b/crates/compiler/test_mono/generated/list_map_closure_owns.txt index 6f9219cdd4..efd1bfc071 100644 --- a/crates/compiler/test_mono/generated/list_map_closure_owns.txt +++ b/crates/compiler/test_mono/generated/list_map_closure_owns.txt @@ -1,35 +1,35 @@ -procedure List.2 (List.95, List.96): - let List.484 : U64 = CallByName List.6 List.95; - let List.480 : Int1 = CallByName Num.22 List.96 List.484; - if List.480 then - let List.482 : Str = CallByName List.66 List.95 List.96; - let List.481 : [C {}, C Str] = TagId(1) List.482; - ret List.481; +procedure List.2 (List.96, List.97): + let List.500 : U64 = CallByName List.6 List.96; + let List.496 : Int1 = CallByName Num.22 List.97 List.500; + if List.496 then + let List.498 : Str = CallByName List.66 List.96 List.97; + let List.497 : [C {}, C Str] = TagId(1) List.498; + ret List.497; else - let List.479 : {} = Struct {}; - let List.478 : [C {}, C Str] = TagId(0) List.479; - ret List.478; + let List.495 : {} = Struct {}; + let List.494 : [C {}, C Str] = TagId(0) List.495; + ret List.494; procedure List.5 (#Attr.2, #Attr.3): - let List.486 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; + let List.502 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3; decref #Attr.2; - ret List.486; + ret List.502; procedure List.6 (#Attr.2): - let List.485 : U64 = lowlevel ListLen #Attr.2; - ret List.485; + let List.501 : U64 = lowlevel ListLen #Attr.2; + ret List.501; procedure List.66 (#Attr.2, #Attr.3): - let List.483 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.483; + let List.499 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.499; procedure Num.22 (#Attr.2, #Attr.3): - let Num.256 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.275; procedure Str.3 (#Attr.2, #Attr.3): - let Str.266 : Str = lowlevel StrConcat #Attr.2 #Attr.3; - ret Str.266; + let Str.267 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.267; procedure Test.1 (): let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; diff --git a/crates/compiler/test_mono/generated/list_map_take_capturing_or_noncapturing.txt b/crates/compiler/test_mono/generated/list_map_take_capturing_or_noncapturing.txt index 4cd6b80ef2..2b19c495ea 100644 --- a/crates/compiler/test_mono/generated/list_map_take_capturing_or_noncapturing.txt +++ b/crates/compiler/test_mono/generated/list_map_take_capturing_or_noncapturing.txt @@ -1,29 +1,29 @@ procedure List.5 (#Attr.2, #Attr.3): - let List.479 : U8 = GetTagId #Attr.3; - joinpoint List.480 List.478: - inc List.478; - ret List.478; + let List.495 : U8 = GetTagId #Attr.3; + joinpoint List.496 List.494: + inc List.494; + ret List.494; in - switch List.479: + switch List.495: case 0: - let List.481 : List U8 = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.4 #Attr.3; + let List.497 : List U8 = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.4 #Attr.3; decref #Attr.2; - jump List.480 List.481; + jump List.496 List.497; case 1: - let List.482 : List U8 = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.6 #Attr.3; + let List.498 : List U8 = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.6 #Attr.3; decref #Attr.2; - jump List.480 List.482; + jump List.496 List.498; default: - let List.483 : List U8 = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.8 #Attr.3; + let List.499 : List U8 = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.8 #Attr.3; decref #Attr.2; - jump List.480 List.483; + jump List.496 List.499; procedure Num.19 (#Attr.2, #Attr.3): - let Num.258 : U8 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : U8 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.277; procedure Test.4 (Test.5, #Attr.12): let Test.1 : U8 = UnionAtIndex (Id 0) (Index 0) #Attr.12; diff --git a/crates/compiler/test_mono/generated/list_pass_to_function.txt b/crates/compiler/test_mono/generated/list_pass_to_function.txt index 4fc891ee2c..4b9b7112ec 100644 --- a/crates/compiler/test_mono/generated/list_pass_to_function.txt +++ b/crates/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,31 +1,31 @@ -procedure List.3 (List.103, List.104, List.105): - let List.479 : {List I64, I64} = CallByName List.64 List.103 List.104 List.105; - let List.478 : List I64 = StructAtIndex 0 List.479; - inc List.478; - dec List.479; - ret List.478; +procedure List.3 (List.104, List.105, List.106): + let List.495 : {List I64, I64} = CallByName List.64 List.104 List.105 List.106; + let List.494 : List I64 = StructAtIndex 0 List.495; + inc List.494; + dec List.495; + ret List.494; procedure List.6 (#Attr.2): - let List.485 : U64 = lowlevel ListLen #Attr.2; - ret List.485; + let List.501 : U64 = lowlevel ListLen #Attr.2; + ret List.501; -procedure List.64 (List.100, List.101, List.102): - let List.484 : U64 = CallByName List.6 List.100; - let List.481 : Int1 = CallByName Num.22 List.101 List.484; - if List.481 then - let List.482 : {List I64, I64} = CallByName List.67 List.100 List.101 List.102; - ret List.482; +procedure List.64 (List.101, List.102, List.103): + let List.500 : U64 = CallByName List.6 List.101; + let List.497 : Int1 = CallByName Num.22 List.102 List.500; + if List.497 then + let List.498 : {List I64, I64} = CallByName List.67 List.101 List.102 List.103; + ret List.498; else - let List.480 : {List I64, I64} = Struct {List.100, List.102}; - ret List.480; + let List.496 : {List I64, I64} = Struct {List.101, List.103}; + ret List.496; procedure List.67 (#Attr.2, #Attr.3, #Attr.4): - let List.483 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret List.483; + let List.499 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.499; procedure Num.22 (#Attr.2, #Attr.3): - let Num.256 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.275; procedure Test.2 (Test.3): let Test.6 : U64 = 0i64; diff --git a/crates/compiler/test_mono/generated/list_sort_asc.txt b/crates/compiler/test_mono/generated/list_sort_asc.txt index e1a3bbc88e..b32b58ccc6 100644 --- a/crates/compiler/test_mono/generated/list_sort_asc.txt +++ b/crates/compiler/test_mono/generated/list_sort_asc.txt @@ -1,20 +1,20 @@ procedure List.28 (#Attr.2, #Attr.3): - let List.480 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3; + let List.496 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3; let #Derived_gen.0 : Int1 = lowlevel ListIsUnique #Attr.2; if #Derived_gen.0 then - ret List.480; + ret List.496; else decref #Attr.2; - ret List.480; + ret List.496; -procedure List.59 (List.278): - let List.479 : {} = Struct {}; - let List.478 : List I64 = CallByName List.28 List.278 List.479; - ret List.478; +procedure List.59 (List.282): + let List.495 : {} = Struct {}; + let List.494 : List I64 = CallByName List.28 List.282 List.495; + ret List.494; procedure Num.46 (#Attr.2, #Attr.3): - let Num.256 : U8 = lowlevel NumCompare #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : U8 = lowlevel NumCompare #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.2 : List I64 = Array [4i64, 3i64, 2i64, 1i64]; diff --git a/crates/compiler/test_mono/generated/nested_pattern_match.txt b/crates/compiler/test_mono/generated/nested_pattern_match.txt index 4b2af8c406..3da4e73dcc 100644 --- a/crates/compiler/test_mono/generated/nested_pattern_match.txt +++ b/crates/compiler/test_mono/generated/nested_pattern_match.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.19 : I64 = 41i64; diff --git a/crates/compiler/test_mono/generated/nullable_wrapped_with_non_nullable_singleton_tags.txt b/crates/compiler/test_mono/generated/nullable_wrapped_with_non_nullable_singleton_tags.txt index 9ae949bba3..a4baa33d97 100644 --- a/crates/compiler/test_mono/generated/nullable_wrapped_with_non_nullable_singleton_tags.txt +++ b/crates/compiler/test_mono/generated/nullable_wrapped_with_non_nullable_singleton_tags.txt @@ -1,6 +1,6 @@ procedure Str.3 (#Attr.2, #Attr.3): - let Str.266 : Str = lowlevel StrConcat #Attr.2 #Attr.3; - ret Str.266; + let Str.267 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.267; procedure Test.2 (Test.4): let Test.16 : U8 = GetTagId Test.4; diff --git a/crates/compiler/test_mono/generated/num_width_gt_u8_layout_as_float.txt b/crates/compiler/test_mono/generated/num_width_gt_u8_layout_as_float.txt index e5c7df8fe3..ef70723086 100644 --- a/crates/compiler/test_mono/generated/num_width_gt_u8_layout_as_float.txt +++ b/crates/compiler/test_mono/generated/num_width_gt_u8_layout_as_float.txt @@ -1,6 +1,6 @@ procedure Num.37 (#Attr.2, #Attr.3): - let Num.256 : Float64 = lowlevel NumDivFrac #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : Float64 = lowlevel NumDivFrac #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.2 : Float64 = 1f64; diff --git a/crates/compiler/test_mono/generated/optional_when.txt b/crates/compiler/test_mono/generated/optional_when.txt index 28667ba1d6..191cc305b4 100644 --- a/crates/compiler/test_mono/generated/optional_when.txt +++ b/crates/compiler/test_mono/generated/optional_when.txt @@ -1,6 +1,6 @@ procedure Num.21 (#Attr.2, #Attr.3): - let Num.258 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.277; procedure Test.1 (Test.6): let Test.21 : Int1 = false; diff --git a/crates/compiler/test_mono/generated/polymorphic_expression_unification.txt b/crates/compiler/test_mono/generated/polymorphic_expression_unification.txt index 637b2dbd98..de7a281c5a 100644 --- a/crates/compiler/test_mono/generated/polymorphic_expression_unification.txt +++ b/crates/compiler/test_mono/generated/polymorphic_expression_unification.txt @@ -3,8 +3,8 @@ procedure Bool.11 (#Attr.2, #Attr.3): ret Bool.23; procedure Str.3 (#Attr.2, #Attr.3): - let Str.266 : Str = lowlevel StrConcat #Attr.2 #Attr.3; - ret Str.266; + let Str.267 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.267; procedure Test.2 (Test.7): let Test.24 : Str = ".trace(\""; diff --git a/crates/compiler/test_mono/generated/quicksort_help.txt b/crates/compiler/test_mono/generated/quicksort_help.txt index 8be05e9641..f00adf21db 100644 --- a/crates/compiler/test_mono/generated/quicksort_help.txt +++ b/crates/compiler/test_mono/generated/quicksort_help.txt @@ -1,14 +1,14 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Num.20 (#Attr.2, #Attr.3): - let Num.257 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.276; procedure Num.22 (#Attr.2, #Attr.3): - let Num.258 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.277; procedure Test.1 (Test.24, Test.25, Test.26): joinpoint Test.12 Test.2 Test.3 Test.4: diff --git a/crates/compiler/test_mono/generated/quicksort_swap.txt b/crates/compiler/test_mono/generated/quicksort_swap.txt index 5a6c27d6d5..d471a039b6 100644 --- a/crates/compiler/test_mono/generated/quicksort_swap.txt +++ b/crates/compiler/test_mono/generated/quicksort_swap.txt @@ -1,47 +1,47 @@ -procedure List.2 (List.95, List.96): - let List.500 : U64 = CallByName List.6 List.95; - let List.497 : Int1 = CallByName Num.22 List.96 List.500; - if List.497 then - let List.499 : I64 = CallByName List.66 List.95 List.96; - let List.498 : [C {}, C I64] = TagId(1) List.499; - ret List.498; +procedure List.2 (List.96, List.97): + let List.516 : U64 = CallByName List.6 List.96; + let List.513 : Int1 = CallByName Num.22 List.97 List.516; + if List.513 then + let List.515 : I64 = CallByName List.66 List.96 List.97; + let List.514 : [C {}, C I64] = TagId(1) List.515; + ret List.514; else - let List.496 : {} = Struct {}; - let List.495 : [C {}, C I64] = TagId(0) List.496; - ret List.495; + let List.512 : {} = Struct {}; + let List.511 : [C {}, C I64] = TagId(0) List.512; + ret List.511; -procedure List.3 (List.103, List.104, List.105): - let List.487 : {List I64, I64} = CallByName List.64 List.103 List.104 List.105; - let List.486 : List I64 = StructAtIndex 0 List.487; - inc List.486; - dec List.487; - ret List.486; +procedure List.3 (List.104, List.105, List.106): + let List.503 : {List I64, I64} = CallByName List.64 List.104 List.105 List.106; + let List.502 : List I64 = StructAtIndex 0 List.503; + inc List.502; + dec List.503; + ret List.502; procedure List.6 (#Attr.2): - let List.485 : U64 = lowlevel ListLen #Attr.2; - ret List.485; + let List.501 : U64 = lowlevel ListLen #Attr.2; + ret List.501; -procedure List.64 (List.100, List.101, List.102): - let List.484 : U64 = CallByName List.6 List.100; - let List.481 : Int1 = CallByName Num.22 List.101 List.484; - if List.481 then - let List.482 : {List I64, I64} = CallByName List.67 List.100 List.101 List.102; - ret List.482; +procedure List.64 (List.101, List.102, List.103): + let List.500 : U64 = CallByName List.6 List.101; + let List.497 : Int1 = CallByName Num.22 List.102 List.500; + if List.497 then + let List.498 : {List I64, I64} = CallByName List.67 List.101 List.102 List.103; + ret List.498; else - let List.480 : {List I64, I64} = Struct {List.100, List.102}; - ret List.480; + let List.496 : {List I64, I64} = Struct {List.101, List.103}; + ret List.496; procedure List.66 (#Attr.2, #Attr.3): - let List.493 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.493; + let List.509 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.509; procedure List.67 (#Attr.2, #Attr.3, #Attr.4): - let List.483 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret List.483; + let List.499 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.499; procedure Num.22 (#Attr.2, #Attr.3): - let Num.258 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.277; procedure Test.1 (Test.2): let Test.28 : U64 = 0i64; diff --git a/crates/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt b/crates/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt index 2f7c2c8b22..eea16dcef9 100644 --- a/crates/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt +++ b/crates/compiler/test_mono/generated/record_optional_field_function_no_use_default.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.4): let Test.2 : I64 = StructAtIndex 0 Test.4; diff --git a/crates/compiler/test_mono/generated/record_optional_field_function_use_default.txt b/crates/compiler/test_mono/generated/record_optional_field_function_use_default.txt index 49267ae4b1..74a2941f14 100644 --- a/crates/compiler/test_mono/generated/record_optional_field_function_use_default.txt +++ b/crates/compiler/test_mono/generated/record_optional_field_function_use_default.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.4): let Test.2 : I64 = 10i64; diff --git a/crates/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt b/crates/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt index 21ebd109c8..f9d2495d40 100644 --- a/crates/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt +++ b/crates/compiler/test_mono/generated/record_optional_field_let_no_use_default.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.2): let Test.3 : I64 = StructAtIndex 0 Test.2; diff --git a/crates/compiler/test_mono/generated/record_optional_field_let_use_default.txt b/crates/compiler/test_mono/generated/record_optional_field_let_use_default.txt index c960fd1fb2..feb8083f78 100644 --- a/crates/compiler/test_mono/generated/record_optional_field_let_use_default.txt +++ b/crates/compiler/test_mono/generated/record_optional_field_let_use_default.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.2): let Test.3 : I64 = 10i64; diff --git a/crates/compiler/test_mono/generated/recursive_call_capturing_function.txt b/crates/compiler/test_mono/generated/recursive_call_capturing_function.txt index b969b12528..eca9933033 100644 --- a/crates/compiler/test_mono/generated/recursive_call_capturing_function.txt +++ b/crates/compiler/test_mono/generated/recursive_call_capturing_function.txt @@ -3,8 +3,8 @@ procedure Bool.2 (): ret Bool.23; procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : U32 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : U32 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.2): let Test.8 : U32 = 0i64; diff --git a/crates/compiler/test_mono/generated/recursive_closure_with_transiently_used_capture.txt b/crates/compiler/test_mono/generated/recursive_closure_with_transiently_used_capture.txt new file mode 100644 index 0000000000..077bebf6c7 --- /dev/null +++ b/crates/compiler/test_mono/generated/recursive_closure_with_transiently_used_capture.txt @@ -0,0 +1,18 @@ +procedure Test.1 (Test.2, Test.3): + let Test.14 : [] = CallByName Test.6 Test.2 Test.3; + ret Test.14; + +procedure Test.5 (Test.8, Test.4): + let Test.12 : [] = CallByName Test.1 Test.4 Test.4; + ret Test.12; + +procedure Test.6 (Test.15, Test.4): + let Test.18 : {} = Struct {}; + let Test.17 : [] = CallByName Test.5 Test.18 Test.4; + ret Test.17; + +procedure Test.0 (Test.7): + let Test.4 : U16 = 10i64; + let Test.10 : {} = Struct {}; + let Test.9 : [] = CallByName Test.5 Test.10 Test.4; + ret Test.9; diff --git a/crates/compiler/test_mono/generated/recursive_function_and_union_with_inference_hole.txt b/crates/compiler/test_mono/generated/recursive_function_and_union_with_inference_hole.txt new file mode 100644 index 0000000000..c1e56de63c --- /dev/null +++ b/crates/compiler/test_mono/generated/recursive_function_and_union_with_inference_hole.txt @@ -0,0 +1,18 @@ +procedure List.5 (#Attr.2, #Attr.3): + let List.494 : List [C List *self] = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3; + ret List.494; + +procedure Test.2 (Test.5): + let Test.6 : List [C List *self] = UnionAtIndex (Id 0) (Index 0) Test.5; + let Test.15 : {} = Struct {}; + let Test.7 : List [C List *self] = CallByName List.5 Test.6 Test.15; + let Test.14 : [C List *self] = TagId(0) Test.7; + ret Test.14; + +procedure Test.0 (): + let Test.16 : List [C List *self] = Array []; + let Test.12 : [C List *self] = TagId(0) Test.16; + let Test.10 : [C List *self] = CallByName Test.2 Test.12; + dec Test.12; + let Test.11 : Str = ""; + ret Test.11; diff --git a/crates/compiler/test_mono/generated/recursive_lambda_set_has_nested_non_recursive_lambda_sets_issue_5026.txt b/crates/compiler/test_mono/generated/recursive_lambda_set_has_nested_non_recursive_lambda_sets_issue_5026.txt new file mode 100644 index 0000000000..ebb13ee424 --- /dev/null +++ b/crates/compiler/test_mono/generated/recursive_lambda_set_has_nested_non_recursive_lambda_sets_issue_5026.txt @@ -0,0 +1,52 @@ +procedure Bool.2 (): + let Bool.23 : Int1 = true; + ret Bool.23; + +procedure Test.10 (Test.25): + let Test.29 : Int1 = CallByName Bool.2; + if Test.29 then + let Test.30 : [, C {}] = CallByName Test.0; + ret Test.30; + else + let Test.26 : [, C {}] = TagId(1) ; + ret Test.26; + +procedure Test.11 (Test.27): + let Test.28 : Str = "done"; + ret Test.28; + +procedure Test.2 (Test.5): + let Test.17 : [, C {}] = TagId(0) Test.5; + ret Test.17; + +procedure Test.3 (Test.7): + let Test.14 : [, C {}] = CallByName Test.2 Test.7; + ret Test.14; + +procedure Test.6 (Test.16, #Attr.12): + let Test.5 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; + dec #Attr.12; + let Test.19 : {} = Struct {}; + let Test.22 : Str = "foobar"; + let Test.20 : [, C {}] = CallByName Test.8 Test.22 Test.5; + dec Test.22; + let Test.21 : U8 = GetTagId Test.20; + switch Test.21: + case 0: + let Test.18 : Str = CallByName Test.6 Test.19 Test.20; + ret Test.18; + + default: + dec Test.20; + let Test.18 : Str = CallByName Test.11 Test.19; + ret Test.18; + + +procedure Test.8 (Test.9, Test.7): + let Test.24 : [, C {}] = CallByName Test.10 Test.9; + ret Test.24; + +procedure Test.0 (): + let Test.13 : {} = Struct {}; + let Test.12 : [, C {}] = CallByName Test.3 Test.13; + ret Test.12; diff --git a/crates/compiler/test_mono/generated/recursive_lambda_set_resolved_only_upon_specialization.txt b/crates/compiler/test_mono/generated/recursive_lambda_set_resolved_only_upon_specialization.txt new file mode 100644 index 0000000000..984500a25b --- /dev/null +++ b/crates/compiler/test_mono/generated/recursive_lambda_set_resolved_only_upon_specialization.txt @@ -0,0 +1,65 @@ +procedure Bool.11 (#Attr.2, #Attr.3): + let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3; + ret Bool.23; + +procedure Num.20 (#Attr.2, #Attr.3): + let Num.276 : U8 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.276; + +procedure Num.21 (#Attr.2, #Attr.3): + let Num.275 : U8 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.275; + +procedure Test.1 (Test.26, Test.27): + joinpoint Test.11 Test.2 Test.3: + let Test.24 : U8 = 0i64; + let Test.20 : Int1 = CallByName Bool.11 Test.2 Test.24; + if Test.20 then + let Test.22 : U8 = 1i64; + let Test.23 : U8 = GetTagId Test.3; + switch Test.23: + case 0: + let Test.21 : U8 = CallByName Test.4 Test.22 Test.3; + ret Test.21; + + default: + dec Test.3; + let Test.21 : U8 = CallByName Test.6 Test.22; + ret Test.21; + + else + let Test.19 : U8 = 1i64; + let Test.13 : U8 = CallByName Num.20 Test.2 Test.19; + let Test.14 : [, C *self U8] = TagId(0) Test.3 Test.2; + jump Test.11 Test.13 Test.14; + in + jump Test.11 Test.26 Test.27; + +procedure Test.4 (Test.28, Test.29): + joinpoint Test.15 Test.5 #Attr.12: + let Test.2 : U8 = UnionAtIndex (Id 0) (Index 1) #Attr.12; + let Test.3 : [, C *self U8] = UnionAtIndex (Id 0) (Index 0) #Attr.12; + inc Test.3; + dec #Attr.12; + let Test.17 : U8 = CallByName Num.21 Test.2 Test.5; + let Test.18 : U8 = GetTagId Test.3; + switch Test.18: + case 0: + jump Test.15 Test.17 Test.3; + + default: + dec Test.3; + let Test.16 : U8 = CallByName Test.6 Test.17; + ret Test.16; + + in + jump Test.15 Test.28 Test.29; + +procedure Test.6 (Test.7): + ret Test.7; + +procedure Test.0 (): + let Test.9 : U8 = 5i64; + let Test.10 : [, C *self U8] = TagId(1) ; + let Test.8 : U8 = CallByName Test.1 Test.9 Test.10; + ret Test.8; diff --git a/crates/compiler/test_mono/generated/recursively_build_effect.txt b/crates/compiler/test_mono/generated/recursively_build_effect.txt new file mode 100644 index 0000000000..e2c106f268 --- /dev/null +++ b/crates/compiler/test_mono/generated/recursively_build_effect.txt @@ -0,0 +1,85 @@ +procedure Num.20 (#Attr.2, #Attr.3): + let Num.275 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.275; + +procedure Str.3 (#Attr.2, #Attr.3): + let Str.268 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.268; + +procedure Test.11 (Test.29, #Attr.12): + let Test.10 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; + dec #Attr.12; + ret Test.10; + +procedure Test.11 (Test.29, Test.10): + ret Test.10; + +procedure Test.14 (Test.62, Test.63): + joinpoint Test.37 Test.36 #Attr.12: + let Test.12 : {} = UnionAtIndex (Id 1) (Index 1) #Attr.12; + let Test.13 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; + dec #Attr.12; + let Test.43 : {} = Struct {}; + let Test.42 : {} = CallByName Test.11 Test.43 Test.12; + let Test.38 : [C {}, C I64 {}] = CallByName Test.9 Test.42 Test.13; + let Test.40 : {} = Struct {}; + let Test.41 : U8 = GetTagId Test.38; + switch Test.41: + case 0: + let Test.39 : {} = CallByName Test.11 Test.40 Test.38; + ret Test.39; + + default: + jump Test.37 Test.40 Test.38; + + in + jump Test.37 Test.62 Test.63; + +procedure Test.2 (): + let Test.6 : Str = "Hello"; + let Test.7 : Str = "World"; + let Test.21 : Str = ", "; + let Test.23 : Str = "!"; + let Test.22 : Str = CallByName Str.3 Test.7 Test.23; + dec Test.23; + let Test.20 : Str = CallByName Str.3 Test.21 Test.22; + dec Test.22; + let Test.19 : Str = CallByName Str.3 Test.6 Test.20; + dec Test.20; + ret Test.19; + +procedure Test.3 (Test.8): + let Test.54 : I64 = 0i64; + let Test.55 : Int1 = lowlevel Eq Test.54 Test.8; + if Test.55 then + let Test.27 : {} = Struct {}; + let Test.26 : [C {}, C I64 {}] = CallByName Test.4 Test.27; + ret Test.26; + else + let Test.49 : {} = Struct {}; + let Test.33 : {} = CallByName Test.4 Test.49; + let Test.32 : [C {}, C I64 {}] = CallByName Test.5 Test.33 Test.8; + ret Test.32; + +procedure Test.4 (Test.10): + let Test.28 : [C {}, C I64 {}] = TagId(0) Test.10; + ret Test.28; + +procedure Test.4 (Test.10): + ret Test.10; + +procedure Test.5 (Test.16, Test.13): + let Test.35 : [C {}, C I64 {}] = TagId(1) Test.13 Test.16; + ret Test.35; + +procedure Test.9 (Test.44, Test.8): + let Test.48 : I64 = 1i64; + let Test.47 : I64 = CallByName Num.20 Test.8 Test.48; + let Test.46 : [C {}, C I64 {}] = CallByName Test.3 Test.47; + ret Test.46; + +procedure Test.0 (): + let Test.24 : I64 = 4i64; + let Test.17 : [C {}, C I64 {}] = CallByName Test.3 Test.24; + let Test.18 : Str = CallByName Test.2; + ret Test.18; diff --git a/crates/compiler/test_mono/generated/rigids.txt b/crates/compiler/test_mono/generated/rigids.txt index edff70c703..8988dfda03 100644 --- a/crates/compiler/test_mono/generated/rigids.txt +++ b/crates/compiler/test_mono/generated/rigids.txt @@ -1,47 +1,47 @@ -procedure List.2 (List.95, List.96): - let List.500 : U64 = CallByName List.6 List.95; - let List.497 : Int1 = CallByName Num.22 List.96 List.500; - if List.497 then - let List.499 : I64 = CallByName List.66 List.95 List.96; - let List.498 : [C {}, C I64] = TagId(1) List.499; - ret List.498; +procedure List.2 (List.96, List.97): + let List.516 : U64 = CallByName List.6 List.96; + let List.513 : Int1 = CallByName Num.22 List.97 List.516; + if List.513 then + let List.515 : I64 = CallByName List.66 List.96 List.97; + let List.514 : [C {}, C I64] = TagId(1) List.515; + ret List.514; else - let List.496 : {} = Struct {}; - let List.495 : [C {}, C I64] = TagId(0) List.496; - ret List.495; + let List.512 : {} = Struct {}; + let List.511 : [C {}, C I64] = TagId(0) List.512; + ret List.511; -procedure List.3 (List.103, List.104, List.105): - let List.487 : {List I64, I64} = CallByName List.64 List.103 List.104 List.105; - let List.486 : List I64 = StructAtIndex 0 List.487; - inc List.486; - dec List.487; - ret List.486; +procedure List.3 (List.104, List.105, List.106): + let List.503 : {List I64, I64} = CallByName List.64 List.104 List.105 List.106; + let List.502 : List I64 = StructAtIndex 0 List.503; + inc List.502; + dec List.503; + ret List.502; procedure List.6 (#Attr.2): - let List.485 : U64 = lowlevel ListLen #Attr.2; - ret List.485; + let List.501 : U64 = lowlevel ListLen #Attr.2; + ret List.501; -procedure List.64 (List.100, List.101, List.102): - let List.484 : U64 = CallByName List.6 List.100; - let List.481 : Int1 = CallByName Num.22 List.101 List.484; - if List.481 then - let List.482 : {List I64, I64} = CallByName List.67 List.100 List.101 List.102; - ret List.482; +procedure List.64 (List.101, List.102, List.103): + let List.500 : U64 = CallByName List.6 List.101; + let List.497 : Int1 = CallByName Num.22 List.102 List.500; + if List.497 then + let List.498 : {List I64, I64} = CallByName List.67 List.101 List.102 List.103; + ret List.498; else - let List.480 : {List I64, I64} = Struct {List.100, List.102}; - ret List.480; + let List.496 : {List I64, I64} = Struct {List.101, List.103}; + ret List.496; procedure List.66 (#Attr.2, #Attr.3): - let List.493 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.493; + let List.509 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.509; procedure List.67 (#Attr.2, #Attr.3, #Attr.4): - let List.483 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; - ret List.483; + let List.499 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4; + ret List.499; procedure Num.22 (#Attr.2, #Attr.3): - let Num.258 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.277; procedure Test.1 (Test.2, Test.3, Test.4): let Test.29 : [C {}, C I64] = CallByName List.2 Test.4 Test.3; diff --git a/crates/compiler/test_mono/generated/specialize_closures.txt b/crates/compiler/test_mono/generated/specialize_closures.txt index 7d96852e4f..4d1ee3ac7e 100644 --- a/crates/compiler/test_mono/generated/specialize_closures.txt +++ b/crates/compiler/test_mono/generated/specialize_closures.txt @@ -3,39 +3,36 @@ procedure Bool.2 (): ret Bool.24; procedure Num.19 (#Attr.2, #Attr.3): - let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.276; procedure Num.21 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.275; procedure Test.1 (Test.2, Test.3): let Test.15 : U8 = GetTagId Test.2; - joinpoint Test.16 Test.14: - ret Test.14; - in switch Test.15: case 0: - let Test.17 : I64 = CallByName Test.7 Test.3 Test.2; - jump Test.16 Test.17; + let Test.14 : I64 = CallByName Test.7 Test.3 Test.2; + ret Test.14; default: - let Test.18 : I64 = CallByName Test.8 Test.3 Test.2; - jump Test.16 Test.18; + let Test.14 : I64 = CallByName Test.8 Test.3 Test.2; + ret Test.14; procedure Test.7 (Test.9, #Attr.12): let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.24 : I64 = CallByName Num.19 Test.9 Test.4; - ret Test.24; + let Test.21 : I64 = CallByName Num.19 Test.9 Test.4; + ret Test.21; procedure Test.8 (Test.10, #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.22 : I64 = CallByName Num.21 Test.10 Test.5; - ret Test.22; + let Test.19 : I64 = CallByName Num.21 Test.10 Test.5; + ret Test.19; else ret Test.10; @@ -43,15 +40,15 @@ procedure Test.0 (): let Test.4 : I64 = 1i64; let Test.5 : I64 = 2i64; let Test.6 : Int1 = CallByName Bool.2; - joinpoint Test.20 Test.12: + joinpoint Test.17 Test.12: let Test.13 : I64 = 42i64; let Test.11 : I64 = CallByName Test.1 Test.12 Test.13; ret Test.11; in - let Test.23 : Int1 = CallByName Bool.2; - if Test.23 then - let Test.19 : [C I64, C I64 Int1] = TagId(0) Test.4; - jump Test.20 Test.19; + let Test.20 : Int1 = CallByName Bool.2; + if Test.20 then + let Test.16 : [C I64, C I64 Int1] = TagId(0) Test.4; + jump Test.17 Test.16; else - let Test.19 : [C I64, C I64 Int1] = TagId(1) Test.5 Test.6; - jump Test.20 Test.19; + let Test.16 : [C I64, C I64 Int1] = TagId(1) Test.5 Test.6; + jump Test.17 Test.16; diff --git a/crates/compiler/test_mono/generated/specialize_lowlevel.txt b/crates/compiler/test_mono/generated/specialize_lowlevel.txt index aeb27ec1c2..38ce048821 100644 --- a/crates/compiler/test_mono/generated/specialize_lowlevel.txt +++ b/crates/compiler/test_mono/generated/specialize_lowlevel.txt @@ -3,46 +3,43 @@ procedure Bool.2 (): ret Bool.23; procedure Num.19 (#Attr.2, #Attr.3): - let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.276; procedure Num.21 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumMul #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.275; procedure Test.6 (Test.8, #Attr.12): let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let Test.21 : I64 = CallByName Num.19 Test.8 Test.4; - ret Test.21; + let Test.18 : I64 = CallByName Num.19 Test.8 Test.4; + ret Test.18; procedure Test.7 (Test.9, #Attr.12): let Test.5 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; - let Test.19 : I64 = CallByName Num.21 Test.9 Test.5; - ret Test.19; + let Test.16 : I64 = CallByName Num.21 Test.9 Test.5; + ret Test.16; procedure Test.0 (): let Test.4 : I64 = 1i64; let Test.5 : I64 = 2i64; let Test.11 : I64 = 42i64; - joinpoint Test.18 Test.12: + joinpoint Test.15 Test.12: let Test.13 : U8 = GetTagId Test.12; - joinpoint Test.14 Test.10: - ret Test.10; - in switch Test.13: case 0: - let Test.15 : I64 = CallByName Test.6 Test.11 Test.12; - jump Test.14 Test.15; + let Test.10 : I64 = CallByName Test.6 Test.11 Test.12; + ret Test.10; default: - let Test.16 : I64 = CallByName Test.7 Test.11 Test.12; - jump Test.14 Test.16; + let Test.10 : I64 = CallByName Test.7 Test.11 Test.12; + ret Test.10; in - let Test.20 : Int1 = CallByName Bool.2; - if Test.20 then - let Test.17 : [C I64, C I64] = TagId(0) Test.4; - jump Test.18 Test.17; + let Test.17 : Int1 = CallByName Bool.2; + if Test.17 then + let Test.14 : [C I64, C I64] = TagId(0) Test.4; + jump Test.15 Test.14; else - let Test.17 : [C I64, C I64] = TagId(1) Test.5; - jump Test.18 Test.17; + let Test.14 : [C I64, C I64] = TagId(1) Test.5; + jump Test.15 Test.14; diff --git a/crates/compiler/test_mono/generated/tail_call_elimination.txt b/crates/compiler/test_mono/generated/tail_call_elimination.txt index 8d5dd76e08..9d990096aa 100644 --- a/crates/compiler/test_mono/generated/tail_call_elimination.txt +++ b/crates/compiler/test_mono/generated/tail_call_elimination.txt @@ -1,10 +1,10 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Num.20 (#Attr.2, #Attr.3): - let Num.257 : I64 = lowlevel NumSub #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : I64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.276; procedure Test.1 (Test.15, Test.16): joinpoint Test.7 Test.2 Test.3: diff --git a/crates/compiler/test_mono/generated/tail_call_with_different_layout.txt b/crates/compiler/test_mono/generated/tail_call_with_different_layout.txt index 257381c6a4..1befbe7edc 100644 --- a/crates/compiler/test_mono/generated/tail_call_with_different_layout.txt +++ b/crates/compiler/test_mono/generated/tail_call_with_different_layout.txt @@ -6,6 +6,22 @@ procedure Test.1 (Test.2, Test.3): let Test.21 : {U16, {}} = Struct {Test.2, Test.3}; ret Test.21; +procedure Test.30 (Test.31): + let Test.32 : {U8, {}} = Unbox Test.31; + dec Test.31; + let Test.33 : U8 = StructAtIndex 0 Test.32; + ret Test.33; + +procedure Test.34 (Test.35): + let Test.36 : {U8, {}} = Unbox Test.35; + dec Test.35; + let Test.37 : {} = StructAtIndex 1 Test.36; + ret Test.37; + +procedure Test.38 (Test.40, #Attr.12): + let Test.39 : Str = CallByName Test.4 Test.40 #Attr.12; + ret Test.39; + procedure Test.4 (Test.13, #Attr.12): let Test.3 : {} = StructAtIndex 1 #Attr.12; let Test.2 : U16 = StructAtIndex 0 #Attr.12; @@ -22,6 +38,10 @@ procedure Test.4 (Test.13, #Attr.12): let Test.15 : Str = CallByName Test.4 Test.16 Test.5; ret Test.15; +procedure Test.41 (Test.43, #Attr.12): + let Test.42 : {U16, {}} = CallByName Test.6 Test.43; + ret Test.42; + procedure Test.6 (Test.17): let Test.19 : U16 = 1i64; let Test.20 : {} = Struct {}; diff --git a/crates/compiler/test_mono/generated/tail_call_with_same_layout_different_lambda_sets.txt b/crates/compiler/test_mono/generated/tail_call_with_same_layout_different_lambda_sets.txt index 962c5526b1..c8a7737468 100644 --- a/crates/compiler/test_mono/generated/tail_call_with_same_layout_different_lambda_sets.txt +++ b/crates/compiler/test_mono/generated/tail_call_with_same_layout_different_lambda_sets.txt @@ -6,6 +6,22 @@ procedure Test.1 (Test.2, Test.3): let Test.21 : {U8, {}} = Struct {Test.2, Test.3}; ret Test.21; +procedure Test.30 (Test.31): + let Test.32 : {U8, {}} = Unbox Test.31; + dec Test.31; + let Test.33 : U8 = StructAtIndex 0 Test.32; + ret Test.33; + +procedure Test.34 (Test.35): + let Test.36 : {U8, {}} = Unbox Test.35; + dec Test.35; + let Test.37 : {} = StructAtIndex 1 Test.36; + ret Test.37; + +procedure Test.38 (Test.40, #Attr.12): + let Test.39 : Str = CallByName Test.4 Test.40 #Attr.12; + ret Test.39; + procedure Test.4 (Test.13, #Attr.12): let Test.3 : {} = StructAtIndex 1 #Attr.12; let Test.2 : U8 = StructAtIndex 0 #Attr.12; @@ -22,6 +38,10 @@ procedure Test.4 (Test.13, #Attr.12): let Test.24 : Str = CallByName Test.8 Test.25; ret Test.24; +procedure Test.41 (Test.43, #Attr.12): + let Test.42 : {U8, {}} = CallByName Test.6 Test.43; + ret Test.42; + procedure Test.6 (Test.17): let Test.19 : U8 = 1i64; let Test.20 : {} = Struct {}; diff --git a/crates/compiler/test_mono/generated/tuple_pattern_match.txt b/crates/compiler/test_mono/generated/tuple_pattern_match.txt new file mode 100644 index 0000000000..c96c80b04e --- /dev/null +++ b/crates/compiler/test_mono/generated/tuple_pattern_match.txt @@ -0,0 +1,29 @@ +procedure Test.0 (): + let Test.15 : I64 = 1i64; + let Test.16 : I64 = 2i64; + let Test.1 : {I64, I64} = Struct {Test.15, Test.16}; + joinpoint Test.5: + let Test.2 : Str = "A"; + ret Test.2; + in + let Test.12 : I64 = StructAtIndex 1 Test.1; + let Test.13 : I64 = 2i64; + let Test.14 : Int1 = lowlevel Eq Test.13 Test.12; + if Test.14 then + let Test.6 : I64 = StructAtIndex 0 Test.1; + let Test.7 : I64 = 1i64; + let Test.8 : Int1 = lowlevel Eq Test.7 Test.6; + if Test.8 then + jump Test.5; + else + let Test.3 : Str = "B"; + ret Test.3; + else + let Test.9 : I64 = StructAtIndex 0 Test.1; + let Test.10 : I64 = 1i64; + let Test.11 : Int1 = lowlevel Eq Test.10 Test.9; + if Test.11 then + jump Test.5; + else + let Test.4 : Str = "C"; + ret Test.4; diff --git a/crates/compiler/test_mono/generated/unreachable_branch_is_eliminated_but_produces_lambda_specializations.txt b/crates/compiler/test_mono/generated/unreachable_branch_is_eliminated_but_produces_lambda_specializations.txt index b56a7e1301..0ed3c650c9 100644 --- a/crates/compiler/test_mono/generated/unreachable_branch_is_eliminated_but_produces_lambda_specializations.txt +++ b/crates/compiler/test_mono/generated/unreachable_branch_is_eliminated_but_produces_lambda_specializations.txt @@ -1,29 +1,26 @@ procedure Test.1 (Test.2): - let Test.14 : Int1 = false; - ret Test.14; + let Test.11 : Int1 = false; + ret Test.11; -procedure Test.3 (Test.13): - let Test.15 : Str = "t1"; +procedure Test.3 (Test.10): + let Test.12 : Str = "t1"; + ret Test.12; + +procedure Test.4 (Test.13): + let Test.15 : Str = "t2"; ret Test.15; -procedure Test.4 (Test.16): - let Test.18 : Str = "t2"; - ret Test.18; - procedure Test.0 (): - let Test.19 : Str = "abc"; - let Test.6 : Int1 = CallByName Test.1 Test.19; - dec Test.19; + let Test.16 : Str = "abc"; + let Test.6 : Int1 = CallByName Test.1 Test.16; + dec Test.16; let Test.9 : {} = Struct {}; - joinpoint Test.10 Test.8: - ret Test.8; - in switch Test.6: case 0: - let Test.11 : Str = CallByName Test.3 Test.9; - jump Test.10 Test.11; + let Test.8 : Str = CallByName Test.3 Test.9; + ret Test.8; default: - let Test.12 : Str = CallByName Test.4 Test.9; - jump Test.10 Test.12; + let Test.8 : Str = CallByName Test.4 Test.9; + ret Test.8; diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt new file mode 100644 index 0000000000..e4c9053060 --- /dev/null +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt @@ -0,0 +1,235 @@ +procedure Bool.2 (): + let Bool.23 : Int1 = true; + ret Bool.23; + +procedure Encode.23 (Encode.98): + ret Encode.98; + +procedure Encode.23 (Encode.98): + ret Encode.98; + +procedure Encode.23 (Encode.98): + ret Encode.98; + +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.111 : List U8 = CallByName Test.5 Encode.99 Encode.101 Encode.107; + ret Encode.111; + +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.118 : List U8 = CallByName Json.144 Encode.99 Encode.101 Encode.107; + ret Encode.118; + +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.123 : List U8 = CallByName Json.102 Encode.99 Encode.101 Encode.107; + ret Encode.123; + +procedure Encode.26 (Encode.105, Encode.106): + let Encode.109 : List U8 = Array []; + let Encode.110 : {Str, Str} = CallByName Test.2 Encode.105; + let Encode.108 : List U8 = CallByName Encode.24 Encode.109 Encode.110 Encode.106; + ret Encode.108; + +procedure Json.1 (): + let Json.483 : {} = Struct {}; + ret Json.483; + +procedure Json.102 (Json.103, Json.530, Json.101): + let Json.539 : I64 = 34i64; + let Json.538 : U8 = CallByName Num.127 Json.539; + let Json.536 : List U8 = CallByName List.4 Json.103 Json.538; + let Json.537 : List U8 = CallByName Str.12 Json.101; + let Json.533 : List U8 = CallByName List.8 Json.536 Json.537; + let Json.535 : I64 = 34i64; + let Json.534 : U8 = CallByName Num.127 Json.535; + let Json.532 : List U8 = CallByName List.4 Json.533 Json.534; + ret Json.532; + +procedure Json.144 (Json.145, Json.486, #Attr.12): + let Json.143 : List Str = StructAtIndex 1 #Attr.12; + inc Json.143; + let Json.142 : Str = StructAtIndex 0 #Attr.12; + inc Json.142; + dec #Attr.12; + let Json.524 : I64 = 123i64; + let Json.523 : U8 = CallByName Num.127 Json.524; + let Json.520 : List U8 = CallByName List.4 Json.145 Json.523; + let Json.522 : I64 = 34i64; + let Json.521 : U8 = CallByName Num.127 Json.522; + let Json.518 : List U8 = CallByName List.4 Json.520 Json.521; + let Json.519 : List U8 = CallByName Str.12 Json.142; + let Json.515 : List U8 = CallByName List.8 Json.518 Json.519; + let Json.517 : I64 = 34i64; + let Json.516 : U8 = CallByName Num.127 Json.517; + let Json.512 : List U8 = CallByName List.4 Json.515 Json.516; + let Json.514 : I64 = 58i64; + let Json.513 : U8 = CallByName Num.127 Json.514; + let Json.509 : List U8 = CallByName List.4 Json.512 Json.513; + let Json.511 : I64 = 91i64; + let Json.510 : U8 = CallByName Num.127 Json.511; + let Json.147 : List U8 = CallByName List.4 Json.509 Json.510; + let Json.508 : U64 = CallByName List.6 Json.143; + let Json.496 : {List U8, U64} = Struct {Json.147, Json.508}; + let Json.497 : {} = Struct {}; + let Json.495 : {List U8, U64} = CallByName List.18 Json.143 Json.496 Json.497; + dec Json.143; + let Json.149 : List U8 = StructAtIndex 0 Json.495; + inc Json.149; + dec Json.495; + let Json.494 : I64 = 93i64; + let Json.493 : U8 = CallByName Num.127 Json.494; + let Json.490 : List U8 = CallByName List.4 Json.149 Json.493; + let Json.492 : I64 = 125i64; + let Json.491 : U8 = CallByName Num.127 Json.492; + let Json.489 : List U8 = CallByName List.4 Json.490 Json.491; + ret Json.489; + +procedure Json.146 (Json.488, Json.152): + let Json.150 : List U8 = StructAtIndex 0 Json.488; + inc Json.150; + let Json.151 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.507 : {} = Struct {}; + let Json.153 : List U8 = CallByName Encode.24 Json.150 Json.152 Json.507; + joinpoint Json.502 Json.154: + let Json.500 : U64 = 1i64; + let Json.499 : U64 = CallByName Num.20 Json.151 Json.500; + let Json.498 : {List U8, U64} = Struct {Json.154, Json.499}; + ret Json.498; + in + let Json.506 : U64 = 1i64; + let Json.503 : Int1 = CallByName Num.24 Json.151 Json.506; + if Json.503 then + let Json.505 : I64 = 44i64; + let Json.504 : U8 = CallByName Num.127 Json.505; + let Json.501 : List U8 = CallByName List.4 Json.153 Json.504; + jump Json.502 Json.501; + else + jump Json.502 Json.153; + +procedure Json.18 (Json.101): + let Json.540 : Str = CallByName Encode.23 Json.101; + ret Json.540; + +procedure Json.22 (Json.142, Json.143): + let Json.526 : {Str, List Str} = Struct {Json.142, Json.143}; + let Json.525 : {Str, List Str} = CallByName Encode.23 Json.526; + ret Json.525; + +procedure List.139 (List.140, List.141, List.138): + let List.545 : {List U8, U64} = CallByName Json.146 List.140 List.141; + ret List.545; + +procedure List.18 (List.136, List.137, List.138): + let List.526 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; + ret List.526; + +procedure List.4 (List.107, List.108): + let List.525 : U64 = 1i64; + let List.524 : List U8 = CallByName List.70 List.107 List.525; + let List.523 : List U8 = CallByName List.71 List.524 List.108; + ret List.523; + +procedure List.6 (#Attr.2): + let List.546 : U64 = lowlevel ListLen #Attr.2; + ret List.546; + +procedure List.66 (#Attr.2, #Attr.3): + let List.542 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.542; + +procedure List.70 (#Attr.2, #Attr.3): + let List.498 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.498; + +procedure List.71 (#Attr.2, #Attr.3): + let List.496 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.496; + +procedure List.8 (#Attr.2, #Attr.3): + let List.548 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.548; + +procedure List.80 (List.558, List.559, List.560, List.561, List.562): + joinpoint List.532 List.433 List.434 List.435 List.436 List.437: + let List.534 : Int1 = CallByName Num.22 List.436 List.437; + if List.534 then + let List.541 : Str = CallByName List.66 List.433 List.436; + let List.535 : {List U8, U64} = CallByName List.139 List.434 List.541 List.435; + let List.538 : U64 = 1i64; + let List.537 : U64 = CallByName Num.19 List.436 List.538; + jump List.532 List.433 List.535 List.435 List.537 List.437; + else + ret List.434; + in + jump List.532 List.558 List.559 List.560 List.561 List.562; + +procedure List.92 (List.430, List.431, List.432): + let List.530 : U64 = 0i64; + let List.531 : U64 = CallByName List.6 List.430; + let List.529 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.530 List.531; + ret List.529; + +procedure Num.127 (#Attr.2): + let Num.284 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.284; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.287 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.287; + +procedure Num.20 (#Attr.2, #Attr.3): + let Num.285 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.285; + +procedure Num.22 (#Attr.2, #Attr.3): + let Num.288 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.288; + +procedure Num.24 (#Attr.2, #Attr.3): + let Num.286 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.286; + +procedure Str.12 (#Attr.2): + let Str.267 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.267; + +procedure Test.2 (Test.10): + let Test.15 : {Str, Str} = CallByName Encode.23 Test.10; + ret Test.15; + +procedure Test.3 (): + let Test.9 : Str = ""; + inc Test.9; + let Test.14 : {Str, Str} = Struct {Test.9, Test.9}; + ret Test.14; + +procedure Test.5 (Test.6, Test.7, Test.4): + joinpoint Test.20 Test.8: + let Test.18 : List U8 = CallByName Encode.24 Test.6 Test.8 Test.7; + ret Test.18; + in + let Test.25 : Int1 = CallByName Bool.2; + if Test.25 then + let Test.26 : Str = "A"; + let Test.29 : Str = StructAtIndex 0 Test.4; + inc Test.29; + dec Test.4; + let Test.28 : Str = CallByName Json.18 Test.29; + let Test.27 : List Str = Array [Test.28]; + let Test.19 : {Str, List Str} = CallByName Json.22 Test.26 Test.27; + jump Test.20 Test.19; + else + let Test.21 : Str = "B"; + let Test.24 : Str = StructAtIndex 1 Test.4; + inc Test.24; + dec Test.4; + let Test.23 : Str = CallByName Json.18 Test.24; + let Test.22 : List Str = Array [Test.23]; + let Test.19 : {Str, List Str} = CallByName Json.22 Test.21 Test.22; + jump Test.20 Test.19; + +procedure Test.0 (): + let Test.12 : {Str, Str} = CallByName Test.3; + let Test.13 : {} = CallByName Json.1; + let Test.11 : List U8 = CallByName Encode.26 Test.12 Test.13; + ret Test.11; diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification.txt new file mode 100644 index 0000000000..adc95cb888 --- /dev/null +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification.txt @@ -0,0 +1,79 @@ +procedure Bool.2 (): + let Bool.23 : Int1 = true; + ret Bool.23; + +procedure Test.12 (Test.52): + let Test.72 : Int1 = false; + ret Test.72; + +procedure Test.13 (Test.51): + let Test.80 : Int1 = true; + ret Test.80; + +procedure Test.14 (Test.50): + ret Test.50; + +procedure Test.15 (Test.49): + let Test.71 : {} = Struct {}; + let Test.70 : Int1 = CallByName Test.12 Test.71; + ret Test.70; + +procedure Test.16 (Test.48): + let Test.79 : {} = Struct {}; + let Test.78 : Int1 = CallByName Test.13 Test.79; + ret Test.78; + +procedure Test.17 (Test.47): + ret Test.47; + +procedure Test.35 (Test.36, Test.73): + inc Test.36; + ret Test.36; + +procedure Test.37 (Test.38, Test.81): + inc Test.38; + ret Test.38; + +procedure Test.40 (Test.41, Test.65, Test.39): + let Test.68 : {} = Struct {}; + switch Test.39: + case 0: + let Test.67 : List U8 = CallByName Test.35 Test.41 Test.68; + ret Test.67; + + default: + let Test.67 : List U8 = CallByName Test.37 Test.41 Test.68; + ret Test.67; + + +procedure Test.43 (Test.44, Test.42): + joinpoint Test.62 Test.60: + let Test.59 : List U8 = Array []; + let Test.58 : List U8 = CallByName Test.40 Test.59 Test.44 Test.60; + dec Test.59; + ret Test.58; + in + let Test.75 : Int1 = CallByName Bool.2; + if Test.75 then + let Test.77 : Str = StructAtIndex 0 Test.42; + inc Test.77; + dec Test.42; + let Test.76 : Int1 = CallByName Test.16 Test.77; + dec Test.77; + let Test.61 : Int1 = CallByName Test.14 Test.76; + jump Test.62 Test.61; + else + let Test.69 : U8 = StructAtIndex 1 Test.42; + dec Test.42; + let Test.63 : Int1 = CallByName Test.15 Test.69; + let Test.61 : Int1 = CallByName Test.14 Test.63; + jump Test.62 Test.61; + +procedure Test.0 (): + let Test.83 : Str = ""; + let Test.84 : U8 = 7i64; + let Test.55 : {Str, U8} = Struct {Test.83, Test.84}; + let Test.46 : {Str, U8} = CallByName Test.17 Test.55; + let Test.54 : {} = Struct {}; + let Test.53 : List U8 = CallByName Test.43 Test.54 Test.46; + ret Test.53; diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt new file mode 100644 index 0000000000..9099e68523 --- /dev/null +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt @@ -0,0 +1,371 @@ +procedure #Derived.0 (#Derived.1): + let #Derived_gen.10 : [C {}, C {}] = TagId(0) #Derived.1; + let #Derived_gen.9 : [C {}, C {}] = CallByName Encode.23 #Derived_gen.10; + ret #Derived_gen.9; + +procedure #Derived.2 (#Derived.3, #Derived.4, #Attr.12): + let #Derived.1 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; + joinpoint #Derived_gen.14 #Derived_gen.13: + let #Derived_gen.12 : List U8 = CallByName Encode.24 #Derived.3 #Derived_gen.13 #Derived.4; + ret #Derived_gen.12; + in + let #Derived_gen.16 : Str = "A"; + let #Derived_gen.17 : List [] = Array []; + let #Derived_gen.15 : {Str, List []} = CallByName Json.22 #Derived_gen.16 #Derived_gen.17; + jump #Derived_gen.14 #Derived_gen.15; + +procedure #Derived.5 (#Derived.6): + let #Derived_gen.1 : [C {}, C {}] = TagId(1) #Derived.6; + let #Derived_gen.0 : [C {}, C {}] = CallByName Encode.23 #Derived_gen.1; + ret #Derived_gen.0; + +procedure #Derived.7 (#Derived.8, #Derived.9, #Attr.12): + let #Derived.6 : {} = UnionAtIndex (Id 1) (Index 0) #Attr.12; + joinpoint #Derived_gen.5 #Derived_gen.4: + let #Derived_gen.3 : List U8 = CallByName Encode.24 #Derived.8 #Derived_gen.4 #Derived.9; + ret #Derived_gen.3; + in + let #Derived_gen.7 : Str = "B"; + let #Derived_gen.8 : List [] = Array []; + let #Derived_gen.6 : {Str, List []} = CallByName Json.22 #Derived_gen.7 #Derived_gen.8; + jump #Derived_gen.5 #Derived_gen.6; + +procedure Bool.2 (): + let Bool.23 : Int1 = true; + ret Bool.23; + +procedure Encode.23 (Encode.98): + ret Encode.98; + +procedure Encode.23 (Encode.98): + ret Encode.98; + +procedure Encode.23 (Encode.98): + ret Encode.98; + +procedure Encode.23 (Encode.98): + ret Encode.98; + +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.111 : List U8 = CallByName Test.5 Encode.99 Encode.101 Encode.107; + ret Encode.111; + +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.118 : List U8 = CallByName Json.144 Encode.99 Encode.101 Encode.107; + ret Encode.118; + +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.122 : U8 = GetTagId Encode.107; + switch Encode.122: + case 0: + let Encode.121 : List U8 = CallByName #Derived.2 Encode.99 Encode.101 Encode.107; + ret Encode.121; + + default: + let Encode.121 : List U8 = CallByName #Derived.7 Encode.99 Encode.101 Encode.107; + ret Encode.121; + + +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.134 : List U8 = CallByName Json.144 Encode.99 Encode.101 Encode.107; + ret Encode.134; + +procedure Encode.24 (Encode.99, Encode.107, Encode.101): + let Encode.138 : Str = "a Lambda Set is empty. Most likely there is a type error in your program."; + Crash Encode.138 + +procedure Encode.26 (Encode.105, Encode.106): + let Encode.109 : List U8 = Array []; + let Encode.110 : {{}, {}} = CallByName Test.2 Encode.105; + let Encode.108 : List U8 = CallByName Encode.24 Encode.109 Encode.110 Encode.106; + ret Encode.108; + +procedure Json.1 (): + let Json.483 : {} = Struct {}; + ret Json.483; + +procedure Json.144 (Json.145, Json.486, #Attr.12): + let Json.143 : List [C {}, C {}] = StructAtIndex 1 #Attr.12; + inc Json.143; + let Json.142 : Str = StructAtIndex 0 #Attr.12; + inc Json.142; + dec #Attr.12; + let Json.524 : I64 = 123i64; + let Json.523 : U8 = CallByName Num.127 Json.524; + let Json.520 : List U8 = CallByName List.4 Json.145 Json.523; + let Json.522 : I64 = 34i64; + let Json.521 : U8 = CallByName Num.127 Json.522; + let Json.518 : List U8 = CallByName List.4 Json.520 Json.521; + let Json.519 : List U8 = CallByName Str.12 Json.142; + let Json.515 : List U8 = CallByName List.8 Json.518 Json.519; + let Json.517 : I64 = 34i64; + let Json.516 : U8 = CallByName Num.127 Json.517; + let Json.512 : List U8 = CallByName List.4 Json.515 Json.516; + let Json.514 : I64 = 58i64; + let Json.513 : U8 = CallByName Num.127 Json.514; + let Json.509 : List U8 = CallByName List.4 Json.512 Json.513; + let Json.511 : I64 = 91i64; + let Json.510 : U8 = CallByName Num.127 Json.511; + let Json.147 : List U8 = CallByName List.4 Json.509 Json.510; + let Json.508 : U64 = CallByName List.6 Json.143; + let Json.496 : {List U8, U64} = Struct {Json.147, Json.508}; + let Json.497 : {} = Struct {}; + let Json.495 : {List U8, U64} = CallByName List.18 Json.143 Json.496 Json.497; + dec Json.143; + let Json.149 : List U8 = StructAtIndex 0 Json.495; + inc Json.149; + dec Json.495; + let Json.494 : I64 = 93i64; + let Json.493 : U8 = CallByName Num.127 Json.494; + let Json.490 : List U8 = CallByName List.4 Json.149 Json.493; + let Json.492 : I64 = 125i64; + let Json.491 : U8 = CallByName Num.127 Json.492; + let Json.489 : List U8 = CallByName List.4 Json.490 Json.491; + ret Json.489; + +procedure Json.144 (Json.145, Json.486, #Attr.12): + let Json.143 : List [] = StructAtIndex 1 #Attr.12; + inc Json.143; + let Json.142 : Str = StructAtIndex 0 #Attr.12; + inc Json.142; + dec #Attr.12; + let Json.574 : I64 = 123i64; + let Json.573 : U8 = CallByName Num.127 Json.574; + let Json.570 : List U8 = CallByName List.4 Json.145 Json.573; + let Json.572 : I64 = 34i64; + let Json.571 : U8 = CallByName Num.127 Json.572; + let Json.568 : List U8 = CallByName List.4 Json.570 Json.571; + let Json.569 : List U8 = CallByName Str.12 Json.142; + let Json.565 : List U8 = CallByName List.8 Json.568 Json.569; + let Json.567 : I64 = 34i64; + let Json.566 : U8 = CallByName Num.127 Json.567; + let Json.562 : List U8 = CallByName List.4 Json.565 Json.566; + let Json.564 : I64 = 58i64; + let Json.563 : U8 = CallByName Num.127 Json.564; + let Json.559 : List U8 = CallByName List.4 Json.562 Json.563; + let Json.561 : I64 = 91i64; + let Json.560 : U8 = CallByName Num.127 Json.561; + let Json.147 : List U8 = CallByName List.4 Json.559 Json.560; + let Json.558 : U64 = CallByName List.6 Json.143; + let Json.546 : {List U8, U64} = Struct {Json.147, Json.558}; + let Json.547 : {} = Struct {}; + let Json.545 : {List U8, U64} = CallByName List.18 Json.143 Json.546 Json.547; + dec Json.143; + let Json.149 : List U8 = StructAtIndex 0 Json.545; + inc Json.149; + dec Json.545; + let Json.544 : I64 = 93i64; + let Json.543 : U8 = CallByName Num.127 Json.544; + let Json.540 : List U8 = CallByName List.4 Json.149 Json.543; + let Json.542 : I64 = 125i64; + let Json.541 : U8 = CallByName Num.127 Json.542; + let Json.539 : List U8 = CallByName List.4 Json.540 Json.541; + ret Json.539; + +procedure Json.146 (Json.488, Json.152): + let Json.150 : List U8 = StructAtIndex 0 Json.488; + inc Json.150; + let Json.151 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.507 : {} = Struct {}; + let Json.153 : List U8 = CallByName Encode.24 Json.150 Json.152 Json.507; + joinpoint Json.502 Json.154: + let Json.500 : U64 = 1i64; + let Json.499 : U64 = CallByName Num.20 Json.151 Json.500; + let Json.498 : {List U8, U64} = Struct {Json.154, Json.499}; + ret Json.498; + in + let Json.506 : U64 = 1i64; + let Json.503 : Int1 = CallByName Num.24 Json.151 Json.506; + if Json.503 then + let Json.505 : I64 = 44i64; + let Json.504 : U8 = CallByName Num.127 Json.505; + let Json.501 : List U8 = CallByName List.4 Json.153 Json.504; + jump Json.502 Json.501; + else + jump Json.502 Json.153; + +procedure Json.146 (Json.488, Json.152): + let Json.150 : List U8 = StructAtIndex 0 Json.488; + inc Json.150; + let Json.151 : U64 = StructAtIndex 1 Json.488; + dec Json.488; + let Json.557 : {} = Struct {}; + let Json.153 : List U8 = CallByName Encode.24 Json.150 Json.152 Json.557; + dec Json.150; + joinpoint Json.552 Json.154: + let Json.550 : U64 = 1i64; + let Json.549 : U64 = CallByName Num.20 Json.151 Json.550; + let Json.548 : {List U8, U64} = Struct {Json.154, Json.549}; + ret Json.548; + in + let Json.556 : U64 = 1i64; + let Json.553 : Int1 = CallByName Num.24 Json.151 Json.556; + if Json.553 then + let Json.555 : I64 = 44i64; + let Json.554 : U8 = CallByName Num.127 Json.555; + let Json.551 : List U8 = CallByName List.4 Json.153 Json.554; + jump Json.552 Json.551; + else + jump Json.552 Json.153; + +procedure Json.22 (Json.142, Json.143): + let Json.526 : {Str, List [C {}, C {}]} = Struct {Json.142, Json.143}; + let Json.525 : {Str, List [C {}, C {}]} = CallByName Encode.23 Json.526; + ret Json.525; + +procedure Json.22 (Json.142, Json.143): + let Json.576 : {Str, List []} = Struct {Json.142, Json.143}; + let Json.575 : {Str, List []} = CallByName Encode.23 Json.576; + ret Json.575; + +procedure List.139 (List.140, List.141, List.138): + let List.539 : {List U8, U64} = CallByName Json.146 List.140 List.141; + ret List.539; + +procedure List.139 (List.140, List.141, List.138): + let List.612 : {List U8, U64} = CallByName Json.146 List.140 List.141; + ret List.612; + +procedure List.18 (List.136, List.137, List.138): + let List.520 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; + ret List.520; + +procedure List.18 (List.136, List.137, List.138): + let List.593 : {List U8, U64} = CallByName List.92 List.136 List.137 List.138; + ret List.593; + +procedure List.4 (List.107, List.108): + let List.592 : U64 = 1i64; + let List.591 : List U8 = CallByName List.70 List.107 List.592; + let List.590 : List U8 = CallByName List.71 List.591 List.108; + ret List.590; + +procedure List.6 (#Attr.2): + let List.540 : U64 = lowlevel ListLen #Attr.2; + ret List.540; + +procedure List.6 (#Attr.2): + let List.613 : U64 = lowlevel ListLen #Attr.2; + ret List.613; + +procedure List.66 (#Attr.2, #Attr.3): + let List.536 : [C {}, C {}] = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.536; + +procedure List.66 (#Attr.2, #Attr.3): + let List.609 : [] = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.609; + +procedure List.70 (#Attr.2, #Attr.3): + let List.571 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3; + ret List.571; + +procedure List.71 (#Attr.2, #Attr.3): + let List.569 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; + ret List.569; + +procedure List.8 (#Attr.2, #Attr.3): + let List.614 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; + ret List.614; + +procedure List.80 (List.551, List.552, List.553, List.554, List.555): + joinpoint List.526 List.433 List.434 List.435 List.436 List.437: + let List.528 : Int1 = CallByName Num.22 List.436 List.437; + if List.528 then + let List.535 : [C {}, C {}] = CallByName List.66 List.433 List.436; + let List.529 : {List U8, U64} = CallByName List.139 List.434 List.535 List.435; + let List.532 : U64 = 1i64; + let List.531 : U64 = CallByName Num.19 List.436 List.532; + jump List.526 List.433 List.529 List.435 List.531 List.437; + else + ret List.434; + in + jump List.526 List.551 List.552 List.553 List.554 List.555; + +procedure List.80 (List.624, List.625, List.626, List.627, List.628): + joinpoint List.599 List.433 List.434 List.435 List.436 List.437: + let List.601 : Int1 = CallByName Num.22 List.436 List.437; + if List.601 then + let List.608 : [] = CallByName List.66 List.433 List.436; + let List.602 : {List U8, U64} = CallByName List.139 List.434 List.608 List.435; + let List.605 : U64 = 1i64; + let List.604 : U64 = CallByName Num.19 List.436 List.605; + jump List.599 List.433 List.602 List.435 List.604 List.437; + else + ret List.434; + in + jump List.599 List.624 List.625 List.626 List.627 List.628; + +procedure List.92 (List.430, List.431, List.432): + let List.524 : U64 = 0i64; + let List.525 : U64 = CallByName List.6 List.430; + let List.523 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.524 List.525; + ret List.523; + +procedure List.92 (List.430, List.431, List.432): + let List.597 : U64 = 0i64; + let List.598 : U64 = CallByName List.6 List.430; + let List.596 : {List U8, U64} = CallByName List.80 List.430 List.431 List.432 List.597 List.598; + ret List.596; + +procedure Num.127 (#Attr.2): + let Num.303 : U8 = lowlevel NumIntCast #Attr.2; + ret Num.303; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.306 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.306; + +procedure Num.20 (#Attr.2, #Attr.3): + let Num.304 : U64 = lowlevel NumSub #Attr.2 #Attr.3; + ret Num.304; + +procedure Num.22 (#Attr.2, #Attr.3): + let Num.307 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.307; + +procedure Num.24 (#Attr.2, #Attr.3): + let Num.305 : Int1 = lowlevel NumGt #Attr.2 #Attr.3; + ret Num.305; + +procedure Str.12 (#Attr.2): + let Str.268 : List U8 = lowlevel StrToUtf8 #Attr.2; + ret Str.268; + +procedure Test.2 (Test.11): + let Test.18 : {{}, {}} = CallByName Encode.23 Test.11; + ret Test.18; + +procedure Test.3 (): + let Test.16 : {} = Struct {}; + let Test.17 : {} = Struct {}; + let Test.15 : {{}, {}} = Struct {Test.16, Test.17}; + ret Test.15; + +procedure Test.5 (Test.6, Test.7, Test.4): + joinpoint Test.23 Test.8: + let Test.21 : List U8 = CallByName Encode.24 Test.6 Test.8 Test.7; + ret Test.21; + in + let Test.28 : Int1 = CallByName Bool.2; + if Test.28 then + let Test.29 : Str = "A"; + let Test.32 : {} = StructAtIndex 0 Test.4; + let Test.31 : [C {}, C {}] = CallByName #Derived.0 Test.32; + let Test.30 : List [C {}, C {}] = Array [Test.31]; + let Test.22 : {Str, List [C {}, C {}]} = CallByName Json.22 Test.29 Test.30; + jump Test.23 Test.22; + else + let Test.24 : Str = "B"; + let Test.27 : {} = StructAtIndex 1 Test.4; + let Test.26 : [C {}, C {}] = CallByName #Derived.5 Test.27; + let Test.25 : List [C {}, C {}] = Array [Test.26]; + let Test.22 : {Str, List [C {}, C {}]} = CallByName Json.22 Test.24 Test.25; + jump Test.23 Test.22; + +procedure Test.0 (): + let Test.13 : {{}, {}} = CallByName Test.3; + let Test.14 : {} = CallByName Json.1; + let Test.12 : List U8 = CallByName Encode.26 Test.13 Test.14; + ret Test.12; diff --git a/crates/compiler/test_mono/generated/weakening_avoids_overspecialization.txt b/crates/compiler/test_mono/generated/weakening_avoids_overspecialization.txt index 6720a3c26e..be27c33390 100644 --- a/crates/compiler/test_mono/generated/weakening_avoids_overspecialization.txt +++ b/crates/compiler/test_mono/generated/weakening_avoids_overspecialization.txt @@ -2,98 +2,98 @@ procedure Bool.11 (#Attr.2, #Attr.3): let Bool.24 : Int1 = lowlevel Eq #Attr.2 #Attr.3; ret Bool.24; -procedure List.26 (List.152, List.153, List.154): - let List.493 : [C U64, C U64] = CallByName List.90 List.152 List.153 List.154; - let List.496 : U8 = 1i64; - let List.497 : U8 = GetTagId List.493; - let List.498 : Int1 = lowlevel Eq List.496 List.497; - if List.498 then - let List.155 : U64 = UnionAtIndex (Id 1) (Index 0) List.493; - ret List.155; - else - let List.156 : U64 = UnionAtIndex (Id 0) (Index 0) List.493; +procedure List.26 (List.153, List.154, List.155): + let List.509 : [C U64, C U64] = CallByName List.92 List.153 List.154 List.155; + let List.512 : U8 = 1i64; + let List.513 : U8 = GetTagId List.509; + let List.514 : Int1 = lowlevel Eq List.512 List.513; + if List.514 then + let List.156 : U64 = UnionAtIndex (Id 1) (Index 0) List.509; ret List.156; - -procedure List.29 (List.294, List.295): - let List.492 : U64 = CallByName List.6 List.294; - let List.296 : U64 = CallByName Num.77 List.492 List.295; - let List.478 : List U8 = CallByName List.43 List.294 List.296; - ret List.478; - -procedure List.43 (List.292, List.293): - let List.490 : U64 = CallByName List.6 List.292; - let List.489 : U64 = CallByName Num.77 List.490 List.293; - let List.480 : {U64, U64} = Struct {List.293, List.489}; - let List.479 : List U8 = CallByName List.49 List.292 List.480; - ret List.479; - -procedure List.49 (List.366, List.367): - let List.487 : U64 = StructAtIndex 0 List.367; - let List.488 : U64 = 0i64; - let List.485 : Int1 = CallByName Bool.11 List.487 List.488; - if List.485 then - dec List.366; - let List.486 : List U8 = Array []; - ret List.486; else - let List.482 : U64 = StructAtIndex 1 List.367; - let List.483 : U64 = StructAtIndex 0 List.367; - let List.481 : List U8 = CallByName List.72 List.366 List.482 List.483; - ret List.481; + let List.157 : U64 = UnionAtIndex (Id 0) (Index 0) List.509; + ret List.157; + +procedure List.29 (List.298, List.299): + let List.508 : U64 = CallByName List.6 List.298; + let List.300 : U64 = CallByName Num.77 List.508 List.299; + let List.494 : List U8 = CallByName List.43 List.298 List.300; + ret List.494; + +procedure List.43 (List.296, List.297): + let List.506 : U64 = CallByName List.6 List.296; + let List.505 : U64 = CallByName Num.77 List.506 List.297; + let List.496 : {U64, U64} = Struct {List.297, List.505}; + let List.495 : List U8 = CallByName List.49 List.296 List.496; + ret List.495; + +procedure List.49 (List.370, List.371): + let List.503 : U64 = StructAtIndex 0 List.371; + let List.504 : U64 = 0i64; + let List.501 : Int1 = CallByName Bool.11 List.503 List.504; + if List.501 then + dec List.370; + let List.502 : List U8 = Array []; + ret List.502; + else + let List.498 : U64 = StructAtIndex 1 List.371; + let List.499 : U64 = StructAtIndex 0 List.371; + let List.497 : List U8 = CallByName List.72 List.370 List.498 List.499; + ret List.497; procedure List.6 (#Attr.2): - let List.491 : U64 = lowlevel ListLen #Attr.2; - ret List.491; + let List.507 : U64 = lowlevel ListLen #Attr.2; + ret List.507; procedure List.66 (#Attr.2, #Attr.3): - let List.514 : U8 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; - ret List.514; + let List.530 : U8 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + ret List.530; procedure List.72 (#Attr.2, #Attr.3, #Attr.4): - let List.484 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; - ret List.484; + let List.500 : List U8 = lowlevel ListSublist #Attr.2 #Attr.3 #Attr.4; + ret List.500; -procedure List.90 (List.426, List.427, List.428): - let List.500 : U64 = 0i64; - let List.501 : U64 = CallByName List.6 List.426; - let List.499 : [C U64, C U64] = CallByName List.91 List.426 List.427 List.428 List.500 List.501; - ret List.499; - -procedure List.91 (List.528, List.529, List.530, List.531, List.532): - joinpoint List.502 List.429 List.430 List.431 List.432 List.433: - let List.504 : Int1 = CallByName Num.22 List.432 List.433; - if List.504 then - let List.513 : U8 = CallByName List.66 List.429 List.432; - let List.505 : [C U64, C U64] = CallByName Test.3 List.430 List.513; - let List.510 : U8 = 1i64; - let List.511 : U8 = GetTagId List.505; - let List.512 : Int1 = lowlevel Eq List.510 List.511; - if List.512 then - let List.434 : U64 = UnionAtIndex (Id 1) (Index 0) List.505; - let List.508 : U64 = 1i64; - let List.507 : U64 = CallByName Num.19 List.432 List.508; - jump List.502 List.429 List.434 List.431 List.507 List.433; +procedure List.80 (List.544, List.545, List.546, List.547, List.548): + joinpoint List.518 List.433 List.434 List.435 List.436 List.437: + let List.520 : Int1 = CallByName Num.22 List.436 List.437; + if List.520 then + let List.529 : U8 = CallByName List.66 List.433 List.436; + let List.521 : [C U64, C U64] = CallByName Test.3 List.434 List.529; + let List.526 : U8 = 1i64; + let List.527 : U8 = GetTagId List.521; + let List.528 : Int1 = lowlevel Eq List.526 List.527; + if List.528 then + let List.438 : U64 = UnionAtIndex (Id 1) (Index 0) List.521; + let List.524 : U64 = 1i64; + let List.523 : U64 = CallByName Num.19 List.436 List.524; + jump List.518 List.433 List.438 List.435 List.523 List.437; else - let List.435 : U64 = UnionAtIndex (Id 0) (Index 0) List.505; - let List.509 : [C U64, C U64] = TagId(0) List.435; - ret List.509; + let List.439 : U64 = UnionAtIndex (Id 0) (Index 0) List.521; + let List.525 : [C U64, C U64] = TagId(0) List.439; + ret List.525; else - let List.503 : [C U64, C U64] = TagId(1) List.430; - ret List.503; + let List.519 : [C U64, C U64] = TagId(1) List.434; + ret List.519; in - jump List.502 List.528 List.529 List.530 List.531 List.532; + jump List.518 List.544 List.545 List.546 List.547 List.548; + +procedure List.92 (List.430, List.431, List.432): + let List.516 : U64 = 0i64; + let List.517 : U64 = CallByName List.6 List.430; + let List.515 : [C U64, C U64] = CallByName List.80 List.430 List.431 List.432 List.516 List.517; + ret List.515; procedure Num.19 (#Attr.2, #Attr.3): - let Num.258 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.258; + let Num.277 : U64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.277; procedure Num.22 (#Attr.2, #Attr.3): - let Num.259 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; - ret Num.259; + let Num.278 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; + ret Num.278; procedure Num.77 (#Attr.2, #Attr.3): - let Num.257 : U64 = lowlevel NumSubSaturated #Attr.2 #Attr.3; - ret Num.257; + let Num.276 : U64 = lowlevel NumSubSaturated #Attr.2 #Attr.3; + ret Num.276; procedure Test.3 (Test.4, Test.12): let Test.13 : [C U64, C U64] = TagId(0) Test.4; diff --git a/crates/compiler/test_mono/generated/when_guard_appears_multiple_times_in_compiled_decision_tree_issue_5176.txt b/crates/compiler/test_mono/generated/when_guard_appears_multiple_times_in_compiled_decision_tree_issue_5176.txt new file mode 100644 index 0000000000..08f2861fbc --- /dev/null +++ b/crates/compiler/test_mono/generated/when_guard_appears_multiple_times_in_compiled_decision_tree_issue_5176.txt @@ -0,0 +1,51 @@ +procedure Bool.2 (): + let Bool.25 : Int1 = true; + ret Bool.25; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.275 : U8 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; + +procedure Test.1 (Test.2): + joinpoint Test.12: + let Test.9 : U8 = 3i64; + ret Test.9; + in + joinpoint Test.11 Test.10: + let Test.8 : U8 = 2i64; + let Test.7 : U8 = CallByName Num.19 Test.10 Test.8; + ret Test.7; + in + let Test.22 : U8 = 15i64; + let Test.23 : Int1 = lowlevel Eq Test.22 Test.2; + if Test.23 then + joinpoint Test.17 Test.13: + if Test.13 then + let Test.6 : U8 = 1i64; + ret Test.6; + else + joinpoint Test.15 Test.14: + if Test.14 then + jump Test.11 Test.2; + else + jump Test.12; + in + let Test.16 : Int1 = CallByName Bool.2; + jump Test.15 Test.16; + in + let Test.18 : Int1 = CallByName Bool.2; + jump Test.17 Test.18; + else + joinpoint Test.20 Test.19: + if Test.19 then + jump Test.11 Test.2; + else + jump Test.12; + in + let Test.21 : Int1 = CallByName Bool.2; + jump Test.20 Test.21; + +procedure Test.0 (): + let Test.5 : U8 = 46i64; + let Test.4 : U8 = CallByName Test.1 Test.5; + ret Test.4; diff --git a/crates/compiler/test_mono/generated/when_nested_maybe.txt b/crates/compiler/test_mono/generated/when_nested_maybe.txt index 4b2af8c406..3da4e73dcc 100644 --- a/crates/compiler/test_mono/generated/when_nested_maybe.txt +++ b/crates/compiler/test_mono/generated/when_nested_maybe.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.19 : I64 = 41i64; diff --git a/crates/compiler/test_mono/generated/when_on_record.txt b/crates/compiler/test_mono/generated/when_on_record.txt index 83aabd4e7f..6735c9b9bb 100644 --- a/crates/compiler/test_mono/generated/when_on_record.txt +++ b/crates/compiler/test_mono/generated/when_on_record.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.5 : I64 = 2i64; diff --git a/crates/compiler/test_mono/generated/when_on_two_values.txt b/crates/compiler/test_mono/generated/when_on_two_values.txt index 14788ffbaf..263451ff02 100644 --- a/crates/compiler/test_mono/generated/when_on_two_values.txt +++ b/crates/compiler/test_mono/generated/when_on_two_values.txt @@ -1,6 +1,6 @@ procedure Num.19 (#Attr.2, #Attr.3): - let Num.256 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; - ret Num.256; + let Num.275 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.275; procedure Test.0 (): let Test.15 : I64 = 3i64; diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index 5cc09be3c1..61ac009f83 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -148,7 +148,7 @@ fn compiles_to_ir(test_name: &str, src: &str, mode: &str, no_check: bool) { assert!(type_problems.is_empty()); - let main_fn_symbol = exposed_to_host.values.keys().copied().next(); + let main_fn_symbol = exposed_to_host.top_level_values.keys().copied().next(); if !no_check { check_procedures(arena, &interns, &mut layout_interner, &procedures); @@ -592,7 +592,7 @@ fn record_optional_field_function_use_default() { "# } -#[mono_test(no_check = "https://github.com/roc-lang/roc/issues/4694")] +#[mono_test] fn quicksort_help() { // do we still need with_larger_debug_stack? r#" @@ -2087,10 +2087,7 @@ fn match_list() { } #[mono_test] -#[ignore = "https://github.com/roc-lang/roc/issues/4561"] fn recursive_function_and_union_with_inference_hole() { - let _tracing_guards = roc_tracing::setup_tracing!(); - indoc!( r#" app "test" provides [main] to "./platform" @@ -2192,6 +2189,20 @@ fn list_one_vs_one_spread_issue_4685() { ) } +#[mono_test] +fn tuple_pattern_match() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = when (1, 2) is + (1, _) -> "A" + (_, 2) -> "B" + (_, _) -> "C" + "# + ) +} + #[mono_test(mode = "test")] fn issue_4705() { indoc!( @@ -2457,19 +2468,19 @@ fn issue_4772_weakened_monomorphic_destructure() { getNumber = { result, rest } = Decode.fromBytesPartial (Str.toUtf8 "-1234") Json.fromUtf8 - - when result is - Ok val -> - when Str.toI64 val is + + when result is + Ok val -> + when Str.toI64 val is Ok number -> Ok {val : number, input : rest} Err InvalidNumStr -> Err (ParsingFailure "not a number") - Err _ -> + Err _ -> Err (ParsingFailure "not a number") - expect + expect result = getNumber result == Ok {val : -1234i64, input : []} "### @@ -2496,3 +2507,355 @@ fn weakening_avoids_overspecialization() { "### ) } + +#[mono_test] +fn recursively_build_effect() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + greeting = + hi = "Hello" + name = "World" + + "\(hi), \(name)!" + + main = + when nestHelp 4 is + _ -> greeting + + nestHelp : I64 -> XEffect {} + nestHelp = \m -> + when m is + 0 -> + always {} + + _ -> + always {} |> after \_ -> nestHelp (m - 1) + + + XEffect a := {} -> a + + always : a -> XEffect a + always = \x -> @XEffect (\{} -> x) + + after : XEffect a, (a -> XEffect b) -> XEffect b + after = \(@XEffect e), toB -> + @XEffect \{} -> + when toB (e {}) is + @XEffect e2 -> + e2 {} + "# + ) +} + +#[mono_test] +#[ignore = "roc glue code generation cannot handle a type that this test generates"] +fn recursive_lambda_set_has_nested_non_recursive_lambda_sets_issue_5026() { + indoc!( + r#" + app "test" provides [looper] to "./platform" + + Effect : {} -> Str + + after = \buildNext -> + afterInner = \{} -> (buildNext "foobar") {} + afterInner + + await : (Str -> Effect) -> Effect + await = \cont -> after (\result -> cont result) + + looper = await \_ -> if Bool.true then looper else \{} -> "done" + "# + ) +} + +#[mono_test] +fn unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification() { + // This is a regression test for the ambient lambda set specialization algorithm. + // + // In the program below, monomorphization of `toEncoderQ` with the `Q` in `main` induces the + // resolution of `t.a` and `t.b`, and the unification of their pending unspecialization lambda + // sets, when `t.a` and `t.b` have been resolved to concrete types, but before the + // specialization procedure steps in to resolve the lambda sets concretely. That's because + // monomorphization unifies the general type of `toEncoderQ` with the concrete type, forcing + // concretization of `t`, but the specialization procedure runs only after the unification is + // complete. + // + // In this case, it's imperative that the unspecialized lambda sets of `toEncoder t.a` and + // `toEncoder t.b` wind up in the same lambda set, that is in + // + // tag : @MEncoder (Bytes, Linear -[[] + @MU8:toEncoder:1 + @MStr:toEncoder+1] -> Bytes) + // -[lTag]-> + // @MEncoder (Bytes, Linear -[[Linear:lTag:3 { @MEncoder (Bytes, Linear -[[] + @MU8:toEncoder:1 + @MStr:toEncoder:1] -> Bytes) }]] -> Bytes) + // + // rather than forcing the lambda set inside to `tag` to become disjoint, as e.g. + // + // tag : @MEncoder (Bytes, Linear -[[] + @MU8:toEncoder:1 + @MStr:toEncoder+1] -> Bytes) + // -[lTag]-> + // @MEncoder (Bytes, Linear -[[ + // Linear:lTag:3 { @MEncoder (Bytes, Linear -[[] + @MU8:toEncoder:1] -> Bytes) }, + // Linear:lTag:3 { @MEncoder (Bytes, Linear -[[] + @MStr:toEncoder:1] -> Bytes) }, + // ]] -> Bytes) + indoc!( + r#" + app "test" provides [main] to "./platform" + + MEncoder fmt := List U8, fmt -> List U8 | fmt has Format + + MEncoding has + toEncoder : val -> MEncoder fmt | val has MEncoding, fmt has Format + + Format has + u8 : {} -> MEncoder fmt | fmt has Format + str : {} -> MEncoder fmt | fmt has Format + tag : MEncoder fmt -> MEncoder fmt | fmt has Format + + Linear := {} has [Format {u8: lU8, str: lStr, tag: lTag}] + + MU8 := U8 has [MEncoding {toEncoder: toEncoderU8}] + MStr := Str has [MEncoding {toEncoder: toEncoderStr}] + + Q a b := { a: a, b: b } + + lU8 = \{} -> @MEncoder (\lst, @Linear {} -> lst) + lStr = \{} -> @MEncoder (\lst, @Linear {} -> lst) + + lTag = \@MEncoder doFormat -> @MEncoder (\lst, @Linear {} -> + doFormat lst (@Linear {}) + ) + + toEncoderU8 = \@MU8 _ -> u8 {} + + toEncoderStr = \@MStr _ -> str {} + + toEncoderQ = + \@Q t -> \fmt -> + @MEncoder doit = if Bool.true + then tag (toEncoder t.a) + else tag (toEncoder t.b) + + doit [] fmt + + main = + fmt = toEncoderQ (@Q {a : @MStr "", b: @MU8 7}) + fmt (@Linear {}) + "# + ) +} + +#[mono_test] +fn unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable() +{ + // This is a regression test for the ambient lambda set specialization algorithm. + // + // The principle of the test is equivalent to that of `unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification`. + // + // However, this test requires a larger reproduction because it is negative behavior is only + // visible in the presence of builtin ability usage (in this case, `Encoding` and + // `EncoderFormatting`). + // + // In this test, the payload types `[A]*` and `[B]*` of the encoded type `Q` are unifiable in + // their unspecialized lambda set representations under `toEncoderQ`; however, they must not + // be, because they in fact represent to different specializations of needed encoders. In + // particular, the lambda set `[[] + [A]:toEncoder:1 + [B]:toEncoder:1]` must be preserved, + // rather than collapsing to `[[] + [A, B]:toEncoder:1]`. + indoc!( + r#" + app "test" imports [Json] provides [main] to "./platform" + + Q a b := { a: a, b: b } has [Encoding {toEncoder: toEncoderQ}] + + toEncoderQ = + \@Q t -> Encode.custom \bytes, fmt -> + f = if Bool.true + then Encode.tag "A" [Encode.toEncoder t.a] + else Encode.tag "B" [Encode.toEncoder t.b] + + Encode.appendWith bytes f fmt + + accessor = @Q {a : A, b: B} + + main = + Encode.toBytes accessor Json.toUtf8 + "# + ) +} + +#[mono_test] +fn unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types() { + // This is a regression test for the ambient lambda set specialization algorithm. + // + // The principle of the test is equivalent to that of `unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification`. + // + // However, this test requires a larger reproduction because it is negative behavior is only + // visible in the presence of builtin ability usage (in this case, `Encoding` and + // `EncoderFormatting`). + // + // In this test, the payload types `Str` and `Str` of the encoded type `Q` are unifiable in + // their unspecialized lambda set representations under `toEncoderQ`, and moreoever they are + // equivalent specializations, since they both come from the same root variable `x`. In as + // such, the lambda set `[[] + Str:toEncoder:1]` should be produced during compaction, rather + // than staying as the expanded `[[] + Str:toEncoder:1 + Str:toEncoder:1]` after the types of + // `t.a` and `t.b` are filled in. + indoc!( + r#" + app "test" imports [Json] provides [main] to "./platform" + + Q a b := { a: a, b: b } has [Encoding {toEncoder: toEncoderQ}] + + toEncoderQ = + \@Q t -> Encode.custom \bytes, fmt -> + f = if Bool.true + then Encode.tag "A" [Encode.toEncoder t.a] + else Encode.tag "B" [Encode.toEncoder t.b] + + Encode.appendWith bytes f fmt + + accessor = + x = "" + @Q {a : x, b: x} + + main = + Encode.toBytes accessor Json.toUtf8 + "# + ) +} + +#[mono_test] +fn inline_return_joinpoints_in_bool_lambda_set() { + indoc!( + r#" + app "test" provides [f] to "./platform" + + f = \x -> + caller = if Bool.false then f else \n -> n + caller (x + 1) + "# + ) +} + +#[mono_test] +fn inline_return_joinpoints_in_enum_lambda_set() { + indoc!( + r#" + app "test" provides [f] to "./platform" + + f = \x -> + caller = \t -> when t is + A -> f + B -> \n -> n + C -> \n -> n + 1 + D -> \n -> n + 2 + (caller A) (x + 1) + "# + ) +} + +#[mono_test] +fn inline_return_joinpoints_in_union_lambda_set() { + indoc!( + r#" + app "test" provides [f] to "./platform" + + f = \x -> + caller = \t -> when t is + A -> f + B -> \n -> n + x + (caller A) (x + 1) + "# + ) +} + +#[mono_test] +fn recursive_closure_with_transiently_used_capture() { + indoc!( + r#" + app "test" provides [f] to "./platform" + + thenDo = \x, callback -> + callback x + + f = \{} -> + code = 10u16 + + bf = \{} -> + thenDo code \_ -> bf {} + + bf {} + "# + ) +} + +#[mono_test] +fn when_guard_appears_multiple_times_in_compiled_decision_tree_issue_5176() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + go : U8 -> U8 + go = \byte -> + when byte is + 15 if Bool.true -> 1 + b if Bool.true -> b + 2 + _ -> 3 + + main = go '.' + "# + ) +} + +#[mono_test] +fn recursive_lambda_set_resolved_only_upon_specialization() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + factCPS = \n, cont -> + if n == 0u8 then + cont 1u8 + else + factCPS (n - 1) \value -> cont (n * value) + + main = + factCPS 5 \x -> x + "# + ) +} + +#[mono_test] +fn compose_recursive_lambda_set_productive_nullable_wrapped() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + compose = \forward -> \f, g -> + if forward + then \x -> g (f x) + else \x -> f (g x) + + identity = \x -> x + exclame = \s -> "\(s)!" + whisper = \s -> "(\(s))" + + main = + res: Str -> Str + res = List.walk [ exclame, whisper ] identity (compose Bool.true) + res "hello" + "# + ) +} + +#[mono_test] +fn issue_4759() { + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = + update { a : { x : "x", y: "y" } } + + update = \state -> { state & a : { x : "ux", y: "uy" } } + "# + ) +} diff --git a/crates/compiler/test_mono_macros/Cargo.lock b/crates/compiler/test_mono_macros/Cargo.lock deleted file mode 100644 index be4e5474f6..0000000000 --- a/crates/compiler/test_mono_macros/Cargo.lock +++ /dev/null @@ -1,47 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "proc-macro2" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "syn" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "test_mono_macros" -version = "0.0.1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/crates/compiler/test_mono_macros/Cargo.toml b/crates/compiler/test_mono_macros/Cargo.toml index b649dcab2d..2043423840 100644 --- a/crates/compiler/test_mono_macros/Cargo.toml +++ b/crates/compiler/test_mono_macros/Cargo.toml @@ -1,15 +1,16 @@ [package] name = "test_mono_macros" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Macros for use in test_mono." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [lib] proc-macro = true [dependencies] -syn = { version = "1.0.81", features = ["full", "extra-traits"] } -quote = "1.0.10" -proc-macro2 = "1.0.32" +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true diff --git a/crates/compiler/test_syntax/Cargo.toml b/crates/compiler/test_syntax/Cargo.toml index 513aea2d70..cb22da5c1f 100644 --- a/crates/compiler/test_syntax/Cargo.toml +++ b/crates/compiler/test_syntax/Cargo.toml @@ -1,24 +1,25 @@ [package] name = "test_syntax" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Tests for the parse + fmt crates." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [features] "parse_debug_trace" = ["roc_parse/parse_debug_trace"] [dependencies] +bumpalo.workspace = true roc_collections = { path = "../collections" } -roc_region = { path = "../region" } +roc_fmt = { path = "../fmt" } roc_module = { path = "../module" } roc_parse = { path = "../parse" } -roc_fmt = { path = "../fmt" } +roc_region = { path = "../region" } roc_test_utils = { path = "../../test_utils" } -bumpalo.workspace = true [dev-dependencies] -pretty_assertions.workspace = true indoc.workspace = true +pretty_assertions.workspace = true walkdir.workspace = true diff --git a/crates/compiler/test_syntax/fuzz/Cargo.lock b/crates/compiler/test_syntax/fuzz/Cargo.lock deleted file mode 100644 index 8c0eef0753..0000000000 --- a/crates/compiler/test_syntax/fuzz/Cargo.lock +++ /dev/null @@ -1,654 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[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 = "arbitrary" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "bumpalo" -version = "3.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - -[[package]] -name = "either" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "getrandom" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", - "bumpalo", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "im" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" -dependencies = [ - "bitmaps", - "rand_core", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - -[[package]] -name = "im-rc" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" -dependencies = [ - "bitmaps", - "rand_core", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - -[[package]] -name = "libc" -version = "0.2.131" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" - -[[package]] -name = "libfuzzer-sys" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8c42ab62f43795ed77a965ed07994c5584cdc94fd0ebf14b22ac1524077acc" -dependencies = [ - "arbitrary", - "cc", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "num_cpus" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "object" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" - -[[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.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" -dependencies = [ - "ctor", - "diff", - "output_vt100", - "yansi", -] - -[[package]] -name = "proc-macro2" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" - -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rayon" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "remove_dir_all" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882f368737489ea543bc5c340e6f3d34a28c39980bd9a979e47322b26f60ac40" -dependencies = [ - "libc", - "log", - "num_cpus", - "rayon", - "winapi", -] - -[[package]] -name = "roc_collections" -version = "0.0.1" -dependencies = [ - "bitvec", - "bumpalo", - "fnv", - "hashbrown", - "im", - "im-rc", - "wyhash", -] - -[[package]] -name = "roc_error_macros" -version = "0.0.1" - -[[package]] -name = "roc_fmt" -version = "0.0.1" -dependencies = [ - "bumpalo", - "roc_collections", - "roc_module", - "roc_parse", - "roc_region", -] - -[[package]] -name = "roc_ident" -version = "0.0.1" - -[[package]] -name = "roc_module" -version = "0.0.1" -dependencies = [ - "bumpalo", - "roc_collections", - "roc_error_macros", - "roc_ident", - "roc_region", - "snafu", - "static_assertions", -] - -[[package]] -name = "roc_parse" -version = "0.0.1" -dependencies = [ - "bumpalo", - "encode_unicode", - "roc_collections", - "roc_module", - "roc_region", -] - -[[package]] -name = "roc_region" -version = "0.0.1" -dependencies = [ - "static_assertions", -] - -[[package]] -name = "roc_test_utils" -version = "0.0.1" -dependencies = [ - "pretty_assertions", - "remove_dir_all", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sized-chunks" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps", - "typenum", -] - -[[package]] -name = "snafu" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2" -dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "1.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "test_syntax" -version = "0.0.1" -dependencies = [ - "bumpalo", - "roc_collections", - "roc_fmt", - "roc_module", - "roc_parse", - "roc_region", - "roc_test_utils", -] - -[[package]] -name = "test_syntax-fuzz" -version = "0.0.0" -dependencies = [ - "bumpalo", - "libfuzzer-sys", - "test_syntax", -] - -[[package]] -name = "typenum" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" - -[[package]] -name = "unicode-ident" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" - -[[package]] -name = "version_check" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[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" - -[[package]] -name = "wyhash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295" -dependencies = [ - "rand_core", -] - -[[package]] -name = "wyz" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" -dependencies = [ - "tap", -] - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/crates/compiler/test_syntax/fuzz/Cargo.toml b/crates/compiler/test_syntax/fuzz/Cargo.toml index 65ce2f8e36..26a786ff2a 100644 --- a/crates/compiler/test_syntax/fuzz/Cargo.toml +++ b/crates/compiler/test_syntax/fuzz/Cargo.toml @@ -1,18 +1,20 @@ [package] name = "test_syntax-fuzz" -version = "0.0.0" -authors = ["Automatically generated"] publish = false -edition = "2021" + +authors.workspace = true +edition.workspace = true +version.workspace = true [package.metadata] cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.3" -bumpalo = { version = "3.6.1", features = ["collections"] } test_syntax = { path = "../../test_syntax" } +bumpalo.workspace = true +libfuzzer-sys.workspace = true + # Prevent this from interfering with workspaces [workspace] members = ["."] diff --git a/crates/compiler/test_syntax/src/test_helpers.rs b/crates/compiler/test_syntax/src/test_helpers.rs index a9d35e9851..e0038c5705 100644 --- a/crates/compiler/test_syntax/src/test_helpers.rs +++ b/crates/compiler/test_syntax/src/test_helpers.rs @@ -1,7 +1,7 @@ use bumpalo::Bump; use roc_fmt::{annotation::Formattable, module::fmt_module}; use roc_parse::{ - ast::{Defs, Expr, Module}, + ast::{Defs, Expr, Malformed, Module}, module::module_defs, parser::{Parser, SyntaxError}, state::State, @@ -104,6 +104,20 @@ impl<'a> Output<'a> { } } +impl<'a> Malformed for Output<'a> { + fn is_malformed(&self) -> bool { + match self { + Output::Header(header) => header.is_malformed(), + Output::ModuleDefs(defs) => defs.is_malformed(), + Output::Expr(expr) => expr.is_malformed(), + Output::Full { + header, + module_defs, + } => header.is_malformed() || module_defs.is_malformed(), + } + } +} + impl<'a> RemoveSpaces<'a> for Output<'a> { fn remove_spaces(&self, arena: &'a Bump) -> Self { match self { @@ -229,10 +243,11 @@ impl<'a> Input<'a> { let reformatted = reparsed_ast.format(); if output != reformatted { - eprintln!("Formatting bug; formatting is not stable.\nOriginal code:\n{}\n\nFormatted code:\n{}\n\nAST:\n{:#?}\n\n", + eprintln!("Formatting bug; formatting is not stable.\nOriginal code:\n{}\n\nFormatted code:\n{}\n\nAST:\n{:#?}\n\nReparsed AST:\n{:#?}\n\n", self.as_str(), output.as_ref().as_str(), - actual); + actual, + reparsed_ast); eprintln!("Reformatting the formatted code changed it again, as follows:\n\n"); assert_multiline_str_eq!(output.as_ref().as_str(), reformatted.as_ref().as_str()); diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/bad_opaque_ref.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/bad_opaque_ref.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/bad_opaque_ref.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/malformed/bad_opaque_ref.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/bad_opaque_ref.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/bad_opaque_ref.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/bad_opaque_ref.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/malformed/bad_opaque_ref.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/bad_opaque_ref.expr.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/bad_opaque_ref.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/bad_opaque_ref.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/malformed/bad_opaque_ref.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/malformed_ident_due_to_underscore.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_pattern_field_access.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/malformed_pattern_field_access.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/malformed/malformed_pattern_field_access.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/malformed_pattern_field_access.expr.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_pattern_field_access.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/malformed_pattern_field_access.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/malformed/malformed_pattern_field_access.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_pattern_module_name.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/malformed_pattern_module_name.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/malformed/malformed_pattern_module_name.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/malformed_pattern_module_name.expr.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_pattern_module_name.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/malformed_pattern_module_name.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/malformed/malformed_pattern_module_name.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/malformed/module_dot_tuple.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/module_dot_tuple.expr.result-ast new file mode 100644 index 0000000000..7d14b81efe --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/malformed/module_dot_tuple.expr.result-ast @@ -0,0 +1,6 @@ +MalformedIdent( + "I.5", + QualifiedTupleAccessor( + @1, + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/malformed/module_dot_tuple.expr.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/module_dot_tuple.expr.roc new file mode 100644 index 0000000000..151839618e --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/malformed/module_dot_tuple.expr.roc @@ -0,0 +1 @@ +I.5 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/qualified_tag.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/qualified_tag.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/qualified_tag.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/malformed/qualified_tag.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/qualified_tag.expr.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/qualified_tag.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/qualified_tag.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/malformed/qualified_tag.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.roc b/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.formatted.roc new file mode 100644 index 0000000000..6b3df9a06e --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.formatted.roc @@ -0,0 +1,3 @@ +a +&& (\x -> x) + 8 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.result-ast new file mode 100644 index 0000000000..5b9f0a5d9d --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.result-ast @@ -0,0 +1,35 @@ +BinOps( + [ + ( + @0-1 Var { + module_name: "", + ident: "a", + }, + @2-4 And, + ), + ], + @5-12 Apply( + @5-10 Closure( + [ + @6-7 Identifier( + "x", + ), + ], + @9-10 Var { + module_name: "", + ident: "x", + }, + ), + [ + @11-12 SpaceBefore( + Num( + "8", + ), + [ + Newline, + ], + ), + ], + Space, + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.roc new file mode 100644 index 0000000000..f58fe8a282 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop.expr.roc @@ -0,0 +1,2 @@ +a && \x->x +8 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.formatted.roc new file mode 100644 index 0000000000..f44f1344c3 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.formatted.roc @@ -0,0 +1,4 @@ +i +> (\s -> s +) + -a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.result-ast new file mode 100644 index 0000000000..b8d84e0beb --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.result-ast @@ -0,0 +1,39 @@ +BinOps( + [ + ( + @0-1 Var { + module_name: "", + ident: "i", + }, + @1-2 GreaterThan, + ), + ], + @2-10 Apply( + @2-7 SpaceAfter( + Closure( + [ + @3-4 Identifier( + "s", + ), + ], + @6-7 Var { + module_name: "", + ident: "s", + }, + ), + [ + Newline, + ], + ), + [ + @8-10 UnaryOp( + @9-10 Var { + module_name: "", + ident: "a", + }, + @8-9 Negate, + ), + ], + Space, + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.roc new file mode 100644 index 0000000000..e414e59bb6 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/closure_in_binop_with_spaces.expr.roc @@ -0,0 +1,2 @@ +i>\s->s +-a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.formatted.roc new file mode 100644 index 0000000000..727025b89a --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.formatted.roc @@ -0,0 +1 @@ +{ e & } \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.result-ast new file mode 100644 index 0000000000..f80fadf9a5 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.result-ast @@ -0,0 +1,7 @@ +RecordUpdate { + update: @1-2 Var { + module_name: "", + ident: "e", + }, + fields: [], +} diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.roc new file mode 100644 index 0000000000..41c40656bf --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_update.expr.roc @@ -0,0 +1 @@ +{e&} \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.formatted.roc new file mode 100644 index 0000000000..cf14a87bf9 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.formatted.roc @@ -0,0 +1,3 @@ +B : {} + +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.result-ast new file mode 100644 index 0000000000..9e696ac3a9 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.result-ast @@ -0,0 +1,47 @@ +Defs( + Defs { + tags: [ + Index(0), + ], + regions: [ + @0-4, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [ + Alias { + header: TypeHeader { + name: @0-1 "B", + vars: [], + }, + ann: @2-4 Record { + fields: [], + ext: None, + }, + }, + ], + value_defs: [], + }, + @6-10 SpaceBefore( + ParensAround( + SpaceBefore( + Var { + module_name: "", + ident: "a", + }, + [ + Newline, + ], + ), + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.roc new file mode 100644 index 0000000000..8b4ef09c79 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.roc @@ -0,0 +1,4 @@ +B:{} + +( +a) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.formatted.roc new file mode 100644 index 0000000000..914592c776 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.formatted.roc @@ -0,0 +1,7 @@ +table : + { + height : Pixels, + } + -> Table +table = \{ height } -> crash "not implemented" +table \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.result-ast new file mode 100644 index 0000000000..af396a0186 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.result-ast @@ -0,0 +1,131 @@ +Defs( + Defs { + tags: [ + Index(2147483649), + ], + regions: [ + @0-89, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Annotation( + @0-5 Identifier( + "table", + ), + @8-44 Function( + [ + @8-35 Record { + fields: [ + @14-29 SpaceBefore( + SpaceAfter( + RequiredValue( + @14-20 "height", + [], + @23-29 Apply( + "", + "Pixels", + [], + ), + ), + [ + Newline, + ], + ), + [ + Newline, + ], + ), + ], + ext: None, + }, + ], + @39-44 Apply( + "", + "Table", + [], + ), + ), + ), + AnnotatedBody { + ann_pattern: @0-5 Identifier( + "table", + ), + ann_type: @8-44 Function( + [ + @8-35 Record { + fields: [ + @14-29 SpaceBefore( + SpaceAfter( + RequiredValue( + @14-20 "height", + [], + @23-29 Apply( + "", + "Pixels", + [], + ), + ), + [ + Newline, + ], + ), + [ + Newline, + ], + ), + ], + ext: None, + }, + ], + @39-44 Apply( + "", + "Table", + [], + ), + ), + comment: None, + body_pattern: @45-50 Identifier( + "table", + ), + body_expr: @53-89 Closure( + [ + @54-62 RecordDestructure( + [ + @55-61 Identifier( + "height", + ), + ], + ), + ], + @66-89 Apply( + @66-71 Crash, + [ + @72-89 Str( + PlainLine( + "not implemented", + ), + ), + ], + Space, + ), + ), + }, + ], + }, + @90-95 SpaceBefore( + Var { + module_name: "", + ident: "table", + }, + [ + Newline, + ], + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.roc new file mode 100644 index 0000000000..67ad2e42bb --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.roc @@ -0,0 +1,5 @@ +table : { + height : Pixels + } -> Table +table = \{height} -> crash "not implemented" +table \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast index e37c89546d..9cda2a8e5a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast @@ -22,7 +22,7 @@ Defs( @4-20 Function( [ @4-10 Tuple { - fields: [ + elems: [ @5-8 Apply( "", "Str", @@ -37,7 +37,7 @@ Defs( }, ], @14-20 Tuple { - fields: [ + elems: [ @15-18 Apply( "", "Str", @@ -59,7 +59,7 @@ Defs( ann_type: @4-20 Function( [ @4-10 Tuple { - fields: [ + elems: [ @5-8 Apply( "", "Str", @@ -74,7 +74,7 @@ Defs( }, ], @14-20 Tuple { - fields: [ + elems: [ @15-18 Apply( "", "Str", diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast index 50ccc54f6f..c6c9bb71c5 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast @@ -28,7 +28,7 @@ Defs( ), ], @11-21 Tuple { - fields: [ + elems: [ @12-15 Apply( "", "I64", @@ -57,7 +57,7 @@ Defs( ), ], @11-21 Tuple { - fields: [ + elems: [ @12-15 Apply( "", "I64", diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.formatted.roc index 7ce6f168b2..9c1f4352bf 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.formatted.roc @@ -1,4 +1,4 @@ -my_list = [ +myList = [ 0, [ a, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast index c2135064d2..a5f0148f89 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast @@ -4,7 +4,7 @@ Defs( Index(2147483648), ], regions: [ - @0-58, + @0-57, ], space_before: [ Slice(start = 0, length = 0), @@ -16,16 +16,13 @@ Defs( type_defs: [], value_defs: [ Body( - @0-7 MalformedIdent( - "my_list", - Underscore( - @3, - ), + @0-6 Identifier( + "myList", ), - @10-58 List( + @9-57 List( Collection { items: [ - @16-17 SpaceBefore( + @15-16 SpaceBefore( Num( "0", ), @@ -33,11 +30,11 @@ Defs( Newline, ], ), - @23-48 SpaceBefore( + @22-47 SpaceBefore( List( Collection { items: [ - @33-34 SpaceBefore( + @32-33 SpaceBefore( Var { module_name: "", ident: "a", @@ -46,7 +43,7 @@ Defs( Newline, ], ), - @44-45 SpaceBefore( + @43-44 SpaceBefore( Var { module_name: "", ident: "b", @@ -65,7 +62,7 @@ Defs( Newline, ], ), - @54-55 SpaceBefore( + @53-54 SpaceBefore( Num( "1", ), @@ -82,7 +79,7 @@ Defs( ), ], }, - @59-61 SpaceBefore( + @58-60 SpaceBefore( Num( "42", ), diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.roc index cdfc50f2e0..f6924ce5fb 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.roc @@ -1,4 +1,4 @@ -my_list = [ +myList = [ 0, [ a, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.formatted.roc index 4442440e07..5a937217c9 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.formatted.roc @@ -1,4 +1,4 @@ -my_list = [ +myList = [ 0, 1, ] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast index 3a7d3753bd..55ed23c1b5 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast @@ -4,7 +4,7 @@ Defs( Index(2147483648), ], regions: [ - @0-26, + @0-25, ], space_before: [ Slice(start = 0, length = 0), @@ -16,15 +16,12 @@ Defs( type_defs: [], value_defs: [ Body( - @0-7 MalformedIdent( - "my_list", - Underscore( - @3, - ), + @0-6 Identifier( + "myList", ), - @10-26 List( + @9-25 List( [ - @16-17 SpaceBefore( + @15-16 SpaceBefore( Num( "0", ), @@ -32,7 +29,7 @@ Defs( Newline, ], ), - @23-24 SpaceBefore( + @22-23 SpaceBefore( SpaceAfter( Num( "1", @@ -50,7 +47,7 @@ Defs( ), ], }, - @27-29 SpaceBefore( + @26-28 SpaceBefore( Num( "42", ), diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.roc index 24b7269dec..2118e0fa01 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.roc @@ -1,4 +1,4 @@ -my_list = [ +myList = [ 0, 1 ] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast index d1eb0b352d..5c4eb6f363 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast @@ -4,7 +4,7 @@ Defs( Index(2147483648), ], regions: [ - @0-27, + @0-26, ], space_before: [ Slice(start = 0, length = 0), @@ -16,16 +16,13 @@ Defs( type_defs: [], value_defs: [ Body( - @0-7 MalformedIdent( - "my_list", - Underscore( - @3, - ), + @0-6 Identifier( + "myList", ), - @10-27 List( + @9-26 List( Collection { items: [ - @16-17 SpaceBefore( + @15-16 SpaceBefore( Num( "0", ), @@ -33,7 +30,7 @@ Defs( Newline, ], ), - @23-24 SpaceBefore( + @22-23 SpaceBefore( Num( "1", ), @@ -50,7 +47,7 @@ Defs( ), ], }, - @28-30 SpaceBefore( + @27-29 SpaceBefore( Num( "42", ), diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.roc index f6e475d2df..7b492fe356 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.roc @@ -1,4 +1,4 @@ -my_list = [ +myList = [ 0, 1, ] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.formatted.roc new file mode 100644 index 0000000000..4fa5c5f894 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.formatted.roc @@ -0,0 +1,4 @@ +[ + K, +] +- i \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.result-ast new file mode 100644 index 0000000000..89d20368d7 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.result-ast @@ -0,0 +1,23 @@ +BinOps( + [ + ( + @0-5 List( + Collection { + items: [ + @1-2 Tag( + "K", + ), + ], + final_comments: [ + Newline, + ], + }, + ), + @5-6 Minus, + ), + ], + @6-7 Var { + module_name: "", + ident: "i", + }, +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.roc new file mode 100644 index 0000000000..e8153c3ccb --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_minus_newlines.expr.roc @@ -0,0 +1,2 @@ +[K, +]-i \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.formatted.roc index f85e7a3632..d73b102c78 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.formatted.roc @@ -1,3 +1,3 @@ -! -""" -""" \ No newline at end of file +-( + """ + """) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.result-ast index 5caf01d342..28fc240d93 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.result-ast @@ -4,5 +4,5 @@ UnaryOp( [], ), ), - @0-1 Not, + @0-1 Negate, ) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.roc index b22950073b..a31dbb5240 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string.expr.roc @@ -1 +1 @@ -!"""""" \ No newline at end of file +-"""""" \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.formatted.roc new file mode 100644 index 0000000000..363eae8f45 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.formatted.roc @@ -0,0 +1,4 @@ +-( + """ + "< + """) \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.result-ast new file mode 100644 index 0000000000..5db13f024c --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.result-ast @@ -0,0 +1,8 @@ +UnaryOp( + @1-9 Str( + PlainLine( + "\"<", + ), + ), + @0-1 Negate, +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.roc new file mode 100644 index 0000000000..75b9cad09e --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/negate_multiline_string_with_quote.expr.roc @@ -0,0 +1 @@ +-""""<""" \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.formatted.roc new file mode 100644 index 0000000000..f85e7a3632 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.formatted.roc @@ -0,0 +1,3 @@ +! +""" +""" \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.result-ast new file mode 100644 index 0000000000..5caf01d342 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.result-ast @@ -0,0 +1,8 @@ +UnaryOp( + @1-7 Str( + Block( + [], + ), + ), + @0-1 Not, +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.roc new file mode 100644 index 0000000000..b22950073b --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/not_multiline_string.expr.roc @@ -0,0 +1 @@ +!"""""" \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.formatted.roc new file mode 100644 index 0000000000..d8c2642763 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.formatted.roc @@ -0,0 +1,4 @@ +a : e +Na := + e +e0 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.result-ast new file mode 100644 index 0000000000..caf6bad6a3 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.result-ast @@ -0,0 +1,54 @@ +Defs( + Defs { + tags: [ + Index(2147483648), + Index(0), + ], + regions: [ + @0-3, + @4-11, + ], + space_before: [ + Slice(start = 0, length = 0), + Slice(start = 0, length = 1), + ], + space_after: [ + Slice(start = 0, length = 0), + Slice(start = 1, length = 0), + ], + spaces: [ + Newline, + ], + type_defs: [ + Opaque { + header: TypeHeader { + name: @4-6 "Na", + vars: [], + }, + typ: @10-11 SpaceBefore( + BoundVariable( + "e", + ), + [ + Newline, + ], + ), + derived: None, + }, + ], + value_defs: [ + Annotation( + @0-1 Identifier( + "a", + ), + @2-3 BoundVariable( + "e", + ), + ), + ], + }, + @12-14 Var { + module_name: "", + ident: "e0", + }, +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.roc new file mode 100644 index 0000000000..92ed4b6c5b --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_type_def_with_newline.expr.roc @@ -0,0 +1,3 @@ +a:e +Na:= + e e0 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.formatted.roc new file mode 100644 index 0000000000..6d8c1510be --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.formatted.roc @@ -0,0 +1,2 @@ +U (b a) : b +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.result-ast new file mode 100644 index 0000000000..9c7c132261 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.result-ast @@ -0,0 +1,49 @@ +Defs( + Defs { + tags: [ + Index(0), + ], + regions: [ + @0-8, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [ + Alias { + header: TypeHeader { + name: @0-1 "U", + vars: [ + @2-5 Apply( + @2-3 Identifier( + "b", + ), + [ + @4-5 Identifier( + "a", + ), + ], + ), + ], + }, + ann: @7-8 BoundVariable( + "b", + ), + }, + ], + value_defs: [], + }, + @9-10 SpaceBefore( + Var { + module_name: "", + ident: "a", + }, + [ + Newline, + ], + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.roc new file mode 100644 index 0000000000..6e754b5d4f --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.roc @@ -0,0 +1,2 @@ +U(b a):b +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.formatted.roc new file mode 100644 index 0000000000..5b6326454b --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.formatted.roc @@ -0,0 +1,3 @@ +i # +N : b +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.result-ast new file mode 100644 index 0000000000..25d551f77b --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.result-ast @@ -0,0 +1,52 @@ +Defs( + Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @0-9, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Annotation( + @0-1 Apply( + @0-1 Identifier( + "i", + ), + [ + @5-6 SpaceBefore( + Tag( + "N", + ), + [ + Newline, + LineComment( + "", + ), + ], + ), + ], + ), + @8-9 BoundVariable( + "b", + ), + ), + ], + }, + @10-11 SpaceBefore( + Var { + module_name: "", + ident: "a", + }, + [ + Newline, + ], + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.roc new file mode 100644 index 0000000000..280f36ca11 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_value_def_annotation.expr.roc @@ -0,0 +1,4 @@ +i +(# +N):b +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.formatted.roc new file mode 100644 index 0000000000..e10fcdc770 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.formatted.roc @@ -0,0 +1,2 @@ +D : b +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.result-ast new file mode 100644 index 0000000000..fbc4c78d44 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.result-ast @@ -0,0 +1,38 @@ +Defs( + Defs { + tags: [ + Index(0), + ], + regions: [ + @1-5, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [ + Alias { + header: TypeHeader { + name: @1-2 "D", + vars: [], + }, + ann: @4-5 BoundVariable( + "b", + ), + }, + ], + value_defs: [], + }, + @6-7 SpaceBefore( + Var { + module_name: "", + ident: "a", + }, + [ + Newline, + ], + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.roc new file mode 100644 index 0000000000..c5e855be88 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.roc @@ -0,0 +1,2 @@ +(D):b +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.formatted.roc new file mode 100644 index 0000000000..e26a460bc9 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.formatted.roc @@ -0,0 +1,2 @@ +A : b +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.result-ast new file mode 100644 index 0000000000..3a8d3030da --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.result-ast @@ -0,0 +1,38 @@ +Defs( + Defs { + tags: [ + Index(0), + ], + regions: [ + @2-6, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [ + Alias { + header: TypeHeader { + name: @2-3 "A", + vars: [], + }, + ann: @5-6 BoundVariable( + "b", + ), + }, + ], + value_defs: [], + }, + @7-8 SpaceBefore( + Var { + module_name: "", + ident: "a", + }, + [ + Newline, + ], + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.roc new file mode 100644 index 0000000000..2d4ef22b27 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.roc @@ -0,0 +1,3 @@ +( +A):b +a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.formatted.roc new file mode 100644 index 0000000000..fa39ab96dd --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.formatted.roc @@ -0,0 +1,5 @@ +""" + + +# +""" # \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.result-ast new file mode 100644 index 0000000000..6b31d1dc6e --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.result-ast @@ -0,0 +1,24 @@ +SpaceAfter( + Str( + Block( + [ + [ + Plaintext( + "\n", + ), + Plaintext( + "\n", + ), + Plaintext( + "#", + ), + ], + ], + ), + ), + [ + LineComment( + "", + ), + ], +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.roc new file mode 100644 index 0000000000..f503f93537 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/str_block_multiple_newlines.expr.roc @@ -0,0 +1,4 @@ +""" + + +#"""# \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.result-ast new file mode 100644 index 0000000000..c659930aaa --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.result-ast @@ -0,0 +1,50 @@ +Defs( + Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @0-15, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Body( + @0-3 Identifier( + "abc", + ), + @6-15 Tuple( + [ + @7-8 Num( + "1", + ), + @10-11 Num( + "2", + ), + @13-14 Num( + "3", + ), + ], + ), + ), + ], + }, + @16-21 SpaceBefore( + TupleAccess( + Var { + module_name: "", + ident: "abc", + }, + "0", + ), + [ + Newline, + ], + ), +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.roc new file mode 100644 index 0000000000..ca3a64639a --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.roc @@ -0,0 +1,2 @@ +abc = (1, 2, 3) +abc.0 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_accessor_function.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_accessor_function.expr.result-ast index a54857c683..122d168422 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_accessor_function.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_accessor_function.expr.result-ast @@ -1,6 +1,8 @@ Apply( - @0-2 TupleAccessorFunction( - "1", + @0-2 AccessorFunction( + TupleIndex( + "1", + ), ), [ @3-12 Tuple( diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast index 3aae783860..50167155f7 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast @@ -22,7 +22,7 @@ Defs( @3-27 Function( [ @3-13 Tuple { - fields: [ + elems: [ @4-7 Apply( "", "Str", @@ -38,7 +38,7 @@ Defs( }, ], @17-27 Tuple { - fields: [ + elems: [ @18-21 Apply( "", "Str", @@ -61,7 +61,7 @@ Defs( ann_type: @3-27 Function( [ @3-13 Tuple { - fields: [ + elems: [ @4-7 Apply( "", "Str", @@ -77,7 +77,7 @@ Defs( }, ], @17-27 Tuple { - fields: [ + elems: [ @18-21 Apply( "", "Str", diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast index 3d9bab17da..78a9811c47 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast @@ -22,7 +22,7 @@ Defs( @3-29 Function( [ @3-14 Tuple { - fields: [ + elems: [ @4-7 Apply( "", "Str", @@ -42,7 +42,7 @@ Defs( }, ], @18-29 Tuple { - fields: [ + elems: [ @19-22 Apply( "", "Str", @@ -69,7 +69,7 @@ Defs( ann_type: @3-29 Function( [ @3-14 Tuple { - fields: [ + elems: [ @4-7 Apply( "", "Str", @@ -89,7 +89,7 @@ Defs( }, ], @18-29 Tuple { - fields: [ + elems: [ @19-22 Apply( "", "Str", diff --git a/crates/compiler/test_syntax/tests/test_fmt.rs b/crates/compiler/test_syntax/tests/test_fmt.rs index c00627dcc8..8dd02e681b 100644 --- a/crates/compiler/test_syntax/tests/test_fmt.rs +++ b/crates/compiler/test_syntax/tests/test_fmt.rs @@ -2164,6 +2164,43 @@ mod test_fmt { ); } + #[test] + fn type_definition_add_space_around_optional_record() { + expr_formats_to( + indoc!( + r#" + f : { a ?Str } + + f"# + ), + indoc!( + r#" + f : { a ? Str } + + f"# + ), + ); + + expr_formats_to( + indoc!( + r#" + f : { + a ?Str, + } + + f"# + ), + indoc!( + r#" + f : { + a ? Str, + } + + f"# + ), + ); + } + #[test] #[ignore] fn final_comment_in_empty_record_type_definition() { diff --git a/crates/compiler/test_syntax/tests/test_snapshots.rs b/crates/compiler/test_syntax/tests/test_snapshots.rs index 57d998fc06..34d5062c1b 100644 --- a/crates/compiler/test_syntax/tests/test_snapshots.rs +++ b/crates/compiler/test_syntax/tests/test_snapshots.rs @@ -10,9 +10,9 @@ mod test_snapshots { use bumpalo::collections::vec::Vec; use bumpalo::{self, Bump}; use roc_parse::ast::Expr::{self, *}; - use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrSegment::*; use roc_parse::ast::{self, EscapedChar}; + use roc_parse::ast::{Malformed, StrLiteral::*}; use roc_parse::parser::SyntaxError; use roc_parse::test_helpers::parse_expr_with; use roc_region::all::{Loc, Region}; @@ -35,25 +35,45 @@ mod test_snapshots { }; } - macro_rules! should_pass { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum TestExpectation { + Pass, // The test parses successfully and there are no Malformed nodes + Fail, // The test gives a parse error + Malformed, // The test parses successfully but there are Malformed nodes + } + + impl TestExpectation { + fn to_dir_name(self) -> &'static str { + match self { + TestExpectation::Pass => "pass", + TestExpectation::Fail => "fail", + TestExpectation::Malformed => "malformed", + } + } + } + + macro_rules! test_expectation { (pass) => { - true + TestExpectation::Pass }; (fail) => { - false + TestExpectation::Fail + }; + (malformed) => { + TestExpectation::Malformed }; } macro_rules! snapshot_tests { ( - $($pass_or_fail:ident / $test_name:ident . $kind:ident),* + $($pass_or_fail_or_malformed:ident / $test_name:ident . $kind:ident),* $(,)? ) => { #[test] fn no_extra_snapshot_test_files() { let tests = &[ $(concat!( - stringify!($pass_or_fail), + stringify!($pass_or_fail_or_malformed), "/", stringify!($test_name), ".", @@ -70,7 +90,7 @@ mod test_snapshots { let pass_or_fail_names = list(&base); let mut extra_test_files = std::collections::HashSet::new(); for res in pass_or_fail_names { - assert!(res == "pass" || res == "fail", "a pass or fail filename was neither \"pass\" nor \"fail\", but rather: {:?}", res); + assert!(res == "pass" || res == "fail" || res == "malformed", "expected only pass/fail/malformed dirs, but I see: {:?}", res); let res_dir = base.join(&res); for file in list(&res_dir) { let test = if let Some(test) = file.strip_suffix(".formatted.roc") { @@ -150,7 +170,7 @@ mod test_snapshots { #[test] fn $test_name() { snapshot_test( - should_pass!($pass_or_fail), + test_expectation!($pass_or_fail_or_malformed), stringify!($test_name), stringify!($kind), |input| snapshot_input!($kind => input)); @@ -227,6 +247,13 @@ mod test_snapshots { fail/when_over_indented_int.expr, fail/when_over_indented_underscore.expr, fail/wild_case_arrow.expr, + malformed/bad_opaque_ref.expr, + malformed/malformed_ident_due_to_underscore.expr, + malformed/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399 + malformed/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399 + malformed/module_dot_tuple.expr, + malformed/qualified_tag.expr, + malformed/underscore_expr_in_def.expr, pass/ability_demand_signature_is_multiline.expr, pass/ability_multi_line.expr, pass/ability_single_line.expr, @@ -244,7 +271,6 @@ mod test_snapshots { pass/apply_two_args.expr, pass/apply_unary_negation.expr, pass/apply_unary_not.expr, - pass/bad_opaque_ref.expr, pass/basic_apply.expr, pass/basic_docs.expr, pass/basic_field.expr, @@ -253,6 +279,8 @@ mod test_snapshots { pass/basic_var.expr, pass/bound_variable.expr, pass/call_with_newlines.expr, + pass/closure_in_binop.expr, + pass/closure_in_binop_with_spaces.expr, pass/closure_with_underscores.expr, pass/comment_after_annotation.expr, pass/comment_after_def.moduledefs, @@ -276,12 +304,15 @@ mod test_snapshots { pass/empty_package_header.header, pass/empty_platform_header.header, pass/empty_record.expr, + pass/empty_record_update.expr, pass/empty_string.expr, pass/equals.expr, pass/equals_with_spaces.expr, pass/expect.expr, pass/expect_fx.moduledefs, + pass/extra_newline_in_parens.expr, pass/float_with_underscores.expr, + pass/fn_with_record_arg.expr, pass/full_app_header.header, pass/full_app_header_trailing_commas.header, pass/function_effect_types.header, @@ -297,13 +328,11 @@ mod test_snapshots { pass/list_closing_indent_not_enough.expr, pass/list_closing_same_indent_no_trailing_comma.expr, pass/list_closing_same_indent_with_trailing_comma.expr, + pass/list_minus_newlines.expr, pass/list_pattern_weird_indent.expr, pass/list_patterns.expr, pass/lowest_float.expr, pass/lowest_int.expr, - pass/malformed_ident_due_to_underscore.expr, - pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399 - pass/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399 pass/minimal_app_header.header, pass/minus_twelve_minus_five.expr, pass/mixed_docs.expr, @@ -321,11 +350,12 @@ mod test_snapshots { pass/multiple_operators.expr, pass/neg_inf_float.expr, pass/negate_multiline_string.expr, + pass/negate_multiline_string_with_quote.expr, pass/negative_float.expr, pass/negative_in_apply_def.expr, pass/negative_int.expr, - pass/nested_def_annotation.moduledefs, pass/nested_backpassing_no_newline_before.expr, + pass/nested_def_annotation.moduledefs, pass/nested_def_without_newline.expr, pass/nested_if.expr, pass/nested_module.header, @@ -346,6 +376,7 @@ mod test_snapshots { pass/nonempty_package_header.header, pass/nonempty_platform_header.header, pass/not_docs.expr, + pass/not_multiline_string.expr, pass/number_literal_suffixes.expr, pass/one_backpassing.expr, pass/one_char_string.expr, @@ -360,6 +391,7 @@ mod test_snapshots { pass/opaque_reference_pattern.expr, pass/opaque_reference_pattern_with_arguments.expr, pass/opaque_simple.moduledefs, + pass/opaque_type_def_with_newline.expr, pass/opaque_with_type_arguments.moduledefs, pass/ops_with_newlines.expr, pass/outdented_app_with_record.expr, @@ -367,6 +399,10 @@ mod test_snapshots { pass/outdented_list.expr, pass/outdented_record.expr, pass/packed_singleton_list.expr, + pass/parens_in_type_def_apply.expr, + pass/parens_in_value_def_annotation.expr, + pass/parenthesized_type_def.expr, + pass/parenthesized_type_def_space_before.expr, pass/parenthetical_apply.expr, pass/parenthetical_basic_field.expr, pass/parenthetical_field_qualified_var.expr, @@ -384,7 +420,6 @@ mod test_snapshots { pass/positive_int.expr, pass/provides_type.header, pass/qualified_field.expr, - pass/qualified_tag.expr, pass/qualified_var.expr, pass/record_access_after_tuple.expr, pass/record_destructure_def.expr, @@ -399,12 +434,14 @@ mod test_snapshots { pass/spaced_singleton_list.expr, pass/spaces_inside_empty_list.expr, pass/standalone_module_defs.moduledefs, + pass/str_block_multiple_newlines.expr, pass/string_without_escape.expr, pass/sub_var_with_spaces.expr, pass/sub_with_spaces.expr, pass/tag_pattern.expr, pass/ten_times_eleven.expr, pass/three_arg_closure.expr, + pass/tuple_access_after_ident.expr, pass/tuple_access_after_record.expr, pass/tuple_accessor_function.expr, pass/tuple_type.expr, @@ -423,7 +460,6 @@ mod test_snapshots { pass/unary_not.expr, pass/unary_not_with_parens.expr, pass/underscore_backpassing.expr, - pass/underscore_expr_in_def.expr, pass/underscore_in_assignment_pattern.expr, pass/value_def_confusion.expr, pass/var_else.expr, @@ -501,13 +537,13 @@ mod test_snapshots { } } - fn snapshot_test(should_pass: bool, name: &str, ty: &str, func: F) + fn snapshot_test(expect: TestExpectation, name: &str, ty: &str, func: F) where F: for<'a> Fn(&'a str) -> Input<'a>, { let mut parent = std::path::PathBuf::from("tests"); parent.push("snapshots"); - parent.push(if should_pass { "pass" } else { "fail" }); + parent.push(expect.to_dir_name()); let input_path = parent.join(&format!("{}.{}.roc", name, ty)); let result_path = parent.join(&format!("{}.{}.result-ast", name, ty)); let formatted_path = parent.join(&format!("{}.{}.formatted.roc", name, ty)); @@ -523,21 +559,36 @@ mod test_snapshots { let arena = Bump::new(); let result = match input.parse_in(&arena) { - Ok(ast) => Ok(ast.debug_format_inner()), + Ok(ast) => { + if expect == TestExpectation::Pass { + assert!(!ast.is_malformed()); + } + Ok(ast.debug_format_inner()) + } Err(err) => Err(format!("{:?}", err)), }; - let actual_result = if should_pass { - result.expect("The source code for this test did not successfully parse!") - } else { - result.expect_err( + if expect == TestExpectation::Pass { + let tokens = roc_parse::highlight::highlight(&source); + for token in tokens { + if token.value == roc_parse::highlight::Token::Error { + panic!("Found an error highlight token in the input: {:?}", token); + } + } + } + + let actual_result = + if expect == TestExpectation::Pass || expect == TestExpectation::Malformed { + result.expect("The source code for this test did not successfully parse!") + } else { + result.expect_err( "The source code for this test successfully parsed, but it was not expected to!", ) - }; + }; compare_snapshots(&result_path, Some(&actual_result)); - if should_pass { + if expect == TestExpectation::Pass || expect == TestExpectation::Malformed { input.check_invariants(check_saved_formatting(input.as_str(), formatted_path), true); } } diff --git a/crates/compiler/types/Cargo.toml b/crates/compiler/types/Cargo.toml index 1922eeb2c8..bb727200ea 100644 --- a/crates/compiler/types/Cargo.toml +++ b/crates/compiler/types/Cargo.toml @@ -1,18 +1,19 @@ [package] name = "roc_types" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Various representations and utilities for dealing with types in the Roc compiler." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_collections = { path = "../collections" } -roc_region = { path = "../region" } +roc_debug_flags = { path = "../debug_flags" } +roc_error_macros = { path = "../../error_macros" } roc_module = { path = "../module" } -roc_error_macros = {path="../../error_macros"} -roc_debug_flags = {path="../debug_flags"} -roc_serialize = {path="../serialize"} +roc_region = { path = "../region" } +roc_serialize = { path = "../serialize" } ven_pretty = { path = "../../vendor/pretty" } diff --git a/crates/compiler/types/src/num.rs b/crates/compiler/types/src/num.rs index 5a85e0a1bb..2de741c1dc 100644 --- a/crates/compiler/types/src/num.rs +++ b/crates/compiler/types/src/num.rs @@ -146,6 +146,11 @@ impl NumericRange { .expect("if number doesn't fit, should have been a type error"), } } + + /// Chooses the type variable to compile this ranged number as. + pub fn default_compilation_variable(&self) -> Variable { + int_lit_width_to_variable(self.default_compilation_width()) + } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] diff --git a/crates/compiler/types/src/pretty_print.rs b/crates/compiler/types/src/pretty_print.rs index 4ce0269e83..25527f0f72 100644 --- a/crates/compiler/types/src/pretty_print.rs +++ b/crates/compiler/types/src/pretty_print.rs @@ -8,6 +8,7 @@ use crate::types::{ name_type_var, name_type_var_with_hint, AbilitySet, Polarity, RecordField, Uls, }; use roc_collections::all::MutMap; +use roc_collections::VecSet; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -404,7 +405,7 @@ fn find_names_needed( } struct NamedResult { - recursion_structs_to_expand: Vec, + recursion_structs_to_expand: VecSet, } fn name_all_type_vars(variable: Variable, subs: &mut Subs, debug_print: DebugPrint) -> NamedResult { @@ -423,20 +424,27 @@ fn name_all_type_vars(variable: Variable, subs: &mut Subs, debug_print: DebugPri debug_print.print_only_under_alias, ); - let mut recursion_structs_to_expand = vec![]; + let mut recursion_structs_to_expand = VecSet::default(); for root in roots { // show the type variable number instead of `*`. useful for debugging // set_root_name(root, (format!("<{:?}>", root).into()), subs); match appearances.get(&root) { Some(Appearances::Multiple) => { + if let Content::RecursionVar { structure, .. } = + subs.get_content_without_compacting(root) + { + recursion_structs_to_expand + .insert(subs.get_root_key_without_compacting(*structure)); + } letters_used = name_root(letters_used, root, subs, &mut taken, debug_print); } Some(Appearances::Single) => { if let Content::RecursionVar { structure, .. } = subs.get_content_without_compacting(root) { - recursion_structs_to_expand.push(*structure); + recursion_structs_to_expand + .insert(subs.get_root_key_without_compacting(*structure)); letters_used = name_root(letters_used, root, subs, &mut taken, debug_print); } else if debug_print.print_weakened_vars && is_weakened_unbound(subs, root) { letters_used = name_root(letters_used, root, subs, &mut taken, debug_print); @@ -554,11 +562,11 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) { #[derive(Default)] struct Context<'a> { able_variables: Vec<(&'a str, AbilitySet)>, - recursion_structs_to_expand: Vec, + recursion_structs_to_expand: VecSet, } -fn content_to_string( - content: &Content, +fn variable_to_string( + var: Variable, subs: &Subs, home: ModuleId, interns: &Interns, @@ -580,7 +588,7 @@ fn content_to_string( write_content( &env, &mut ctx, - content, + var, subs, &mut buf, Parens::Unnecessary, @@ -613,9 +621,8 @@ pub fn name_and_print_var( debug_print: DebugPrint, ) -> String { let named_result = name_all_type_vars(var, subs, debug_print); - let content = subs.get_content_without_compacting(var); - content_to_string( - content, + variable_to_string( + var, subs, home, interns, @@ -625,21 +632,20 @@ pub fn name_and_print_var( ) } -pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Content { +pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> Variable { debug_assert_eq!(args.len(), 1); let arg_var_index = args .into_iter() .next() .expect("Num was not applied to a type argument!"); - let arg_var = subs[arg_var_index]; - subs.get_content_without_compacting(arg_var) + subs[arg_var_index] } fn write_content<'a>( env: &Env, ctx: &mut Context<'a>, - content: &Content, + var: Variable, subs: &'a Subs, buf: &mut String, parens: Parens, @@ -647,7 +653,7 @@ fn write_content<'a>( ) { use crate::subs::Content::*; - match content { + match subs.get_content_without_compacting(var) { FlexVar(Some(name_index)) => { let name = &subs.field_names[name_index.index as usize]; buf.push_str(name.as_str()) @@ -676,22 +682,11 @@ fn write_content<'a>( structure, } => match opt_name { Some(name_index) => { - if let Some(idx) = ctx - .recursion_structs_to_expand - .iter() - .position(|v| v == structure) - { - ctx.recursion_structs_to_expand.swap_remove(idx); + let structure_root = subs.get_root_key_without_compacting(*structure); + if ctx.recursion_structs_to_expand.remove(&structure_root) { + write_content(env, ctx, *structure, subs, buf, parens, pol); - write_content( - env, - ctx, - subs.get_content_without_compacting(*structure), - subs, - buf, - parens, - pol, - ); + ctx.recursion_structs_to_expand.insert(structure_root); } else { let name = &subs.field_names[name_index.index as usize]; buf.push_str(name.as_str()) @@ -701,14 +696,14 @@ fn write_content<'a>( unreachable!("This should always be filled in!") } }, - Structure(flat_type) => write_flat_type(env, ctx, flat_type, subs, buf, parens, pol), + Structure(flat_type) => write_flat_type(env, ctx, var, flat_type, subs, buf, parens, pol), Alias(symbol, args, actual, _kind) => { let write_parens = parens == Parens::InTypeParam && !args.is_empty(); match *symbol { Symbol::NUM_NUM => { let content = get_single_arg(subs, args); - match *content { + match *subs.get_content_without_compacting(content) { Alias(nested, args, _actual, _kind) => match nested { Symbol::NUM_INTEGER => { write_integer( @@ -769,8 +764,7 @@ fn write_content<'a>( || args.any_infer_ext_var_is_material(subs) => { write_parens!(write_parens, buf, { - let content = subs.get_content_without_compacting(*actual); - write_content(env, ctx, content, subs, buf, parens, pol) + write_content(env, ctx, *actual, subs, buf, parens, pol) }) } @@ -780,21 +774,12 @@ fn write_content<'a>( for var_index in args.named_type_arguments() { let var = subs[var_index]; buf.push(' '); - write_content( - env, - ctx, - subs.get_content_without_compacting(var), - subs, - buf, - Parens::InTypeParam, - pol, - ); + write_content(env, ctx, var, subs, buf, Parens::InTypeParam, pol); } 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, pol); + write_content(env, ctx, *actual, subs, buf, parens, pol); buf.push_str("]]"); }); }), @@ -846,28 +831,12 @@ fn write_content<'a>( if let Some(rec_var) = recursion_var.into_variable() { buf.push_str(" as "); - write_content( - env, - ctx, - subs.get_content_without_compacting(rec_var), - subs, - buf, - parens, - pol, - ) + write_content(env, ctx, rec_var, subs, buf, parens, pol) } for Uls(var, member, region) in subs.get_subs_slice(*unspecialized) { buf.push_str(" + "); - write_content( - env, - ctx, - subs.get_content_without_compacting(*var), - subs, - buf, - Parens::Unnecessary, - pol, - ); + write_content(env, ctx, *var, subs, buf, Parens::Unnecessary, pol); buf.push(':'); buf.push_str(&print_symbol(member)); buf.push(':'); @@ -882,15 +851,7 @@ fn write_content<'a>( if i > 0 { buf.push_str(", "); } - write_content( - env, - ctx, - subs.get_content_without_compacting(var), - subs, - buf, - Parens::Unnecessary, - pol, - ); + write_content(env, ctx, var, subs, buf, Parens::Unnecessary, pol); } buf.push(')'); } @@ -901,7 +862,7 @@ fn write_content<'a>( fn write_float<'a>( env: &Env, ctx: &mut Context<'a>, - content: &Content, + var: Variable, subs: &'a Subs, buf: &mut String, parens: Parens, @@ -909,13 +870,13 @@ fn write_float<'a>( pol: Polarity, ) { use crate::subs::Content::*; - match content { + match subs.get_content_without_compacting(var) { Alias(Symbol::NUM_BINARY32, _, _, _) => buf.push_str("F32"), Alias(Symbol::NUM_BINARY64, _, _, _) => buf.push_str("F64"), Alias(Symbol::NUM_DECIMAL, _, _, _) => buf.push_str("Dec"), _ => write_parens!(write_parens, buf, { buf.push_str("Float "); - write_content(env, ctx, content, subs, buf, parens, pol); + write_content(env, ctx, var, subs, buf, parens, pol); }), } } @@ -923,7 +884,7 @@ fn write_float<'a>( fn write_integer<'a>( env: &Env, ctx: &mut Context<'a>, - content: &Content, + var: Variable, subs: &'a Subs, buf: &mut String, parens: Parens, @@ -934,19 +895,19 @@ fn write_integer<'a>( macro_rules! derive_num_writes { ($($lit:expr, $tag:path)*) => { - match content { + match subs.get_content_without_compacting(var) { $( &Alias($tag, _, _, _) => { buf.push_str($lit) }, )* - actual => { + _ => { write_parens!( write_parens, buf, { buf.push_str("Int "); - write_content(env, ctx, actual, subs, buf, parens, pol); + write_content(env, ctx, var, subs, buf, parens, pol); } ) } @@ -1008,13 +969,13 @@ fn write_ext_content<'a>( parens: Parens, pol: Polarity, ) { - if let ExtContent::Content(_, content) = ext_content { + if let ExtContent::Content(var, _) = ext_content { // This is an open record or tag union, so print the variable // right after the '}' or ']' // // e.g. the "*" at the end of `{ x: I64 }*` // or the "r" at the end of `{ x: I64 }r` - write_content(env, ctx, content, subs, buf, parens, pol) + write_content(env, ctx, var, subs, buf, parens, pol) } } @@ -1046,15 +1007,7 @@ fn write_sorted_tags2<'a, L>( for var in vars { buf.push(' '); - write_content( - env, - ctx, - subs.get_content_without_compacting(*var), - subs, - buf, - Parens::InTypeParam, - pol, - ); + write_content(env, ctx, *var, subs, buf, Parens::InTypeParam, pol); } } } @@ -1099,15 +1052,7 @@ fn write_sorted_tags<'a>( for var in vars { buf.push(' '); - write_content( - env, - ctx, - subs.get_content_without_compacting(*var), - subs, - buf, - Parens::InTypeParam, - pol, - ); + write_content(env, ctx, *var, subs, buf, Parens::InTypeParam, pol); } } @@ -1117,6 +1062,7 @@ fn write_sorted_tags<'a>( fn write_flat_type<'a>( env: &Env, ctx: &mut Context<'a>, + var: Variable, flat_type: &FlatType, subs: &'a Subs, buf: &mut String, @@ -1185,15 +1131,7 @@ fn write_flat_type<'a>( Required(_) | Demanded(_) | RigidRequired(_) => buf.push_str(" : "), }; - write_content( - env, - ctx, - subs.get_content_without_compacting(var), - subs, - buf, - Parens::Unnecessary, - pol, - ); + write_content(env, ctx, var, subs, buf, Parens::Unnecessary, pol); } buf.push_str(" }"); @@ -1203,13 +1141,13 @@ fn write_flat_type<'a>( Content::Structure(EmptyRecord) => { // This is a closed record. We're done! } - content => { + _ => { // This is an open record, so print the variable // right after the '}' // // e.g. the "*" at the end of `{ x: I64 }*` // or the "r" at the end of `{ x: I64 }r` - write_content(env, ctx, content, subs, buf, parens, pol) + write_content(env, ctx, ext_var, subs, buf, parens, pol) } } } @@ -1247,15 +1185,7 @@ fn write_flat_type<'a>( } expected_next_index = index + 1; - write_content( - env, - ctx, - subs.get_content_without_compacting(var), - subs, - buf, - Parens::Unnecessary, - pol, - ); + write_content(env, ctx, var, subs, buf, Parens::Unnecessary, pol); } buf.push_str(" )"); @@ -1264,13 +1194,13 @@ fn write_flat_type<'a>( Content::Structure(EmptyTuple) => { // This is a closed tuple. We're done! } - content => { + _ => { // This is an open tuple, so print the variable // right after the ')' // // e.g. the "*" at the end of `( I64, I64 )*` // or the "r" at the end of `( I64, I64 )r` - write_content(env, ctx, content, subs, buf, parens, pol) + write_content(env, ctx, ext_var, subs, buf, parens, pol) } } } @@ -1319,6 +1249,9 @@ fn write_flat_type<'a>( } RecursiveTagUnion(rec_var, tags, ext_var) => { + ctx.recursion_structs_to_expand + .remove(&subs.get_root_key_without_compacting(var)); + write_parens!(parens == Parens::InTypeParam, buf, { buf.push('['); @@ -1346,15 +1279,7 @@ fn write_flat_type<'a>( ); buf.push_str(" as "); - write_content( - env, - ctx, - subs.get_content_without_compacting(*rec_var), - subs, - buf, - parens, - pol, - ) + write_content(env, ctx, *rec_var, subs, buf, parens, pol) }) } } @@ -1474,9 +1399,9 @@ fn write_apply<'a>( Symbol::NUM_FLOATINGPOINT if nested_args.len() == 1 => { buf.push_str("F64"); } - _ => default_case(subs, arg_content), + _ => default_case(subs, *arg), }, - _ => default_case(subs, arg_content), + _ => default_case(subs, *arg), } } _ => { @@ -1488,15 +1413,7 @@ fn write_apply<'a>( for arg in args { buf.push(' '); - write_content( - env, - ctx, - subs.get_content_without_compacting(*arg), - subs, - buf, - Parens::InTypeParam, - pol, - ); + write_content(env, ctx, *arg, subs, buf, Parens::InTypeParam, pol); } if write_parens { @@ -1532,42 +1449,18 @@ fn write_fn<'a>( needs_comma = true; } - write_content( - env, - ctx, - subs.get_content_without_compacting(*arg), - subs, - buf, - Parens::InFn, - Polarity::Neg, - ); + write_content(env, ctx, *arg, subs, buf, Parens::InFn, Polarity::Neg); } if !env.debug.print_lambda_sets { buf.push_str(" -> "); } else { buf.push_str(" -"); - write_content( - env, - ctx, - subs.get_content_without_compacting(closure), - subs, - buf, - parens, - pol, - ); + write_content(env, ctx, closure, subs, buf, parens, pol); buf.push_str("-> "); } - write_content( - env, - ctx, - subs.get_content_without_compacting(ret), - subs, - buf, - Parens::InFn, - Polarity::Pos, - ); + write_content(env, ctx, ret, subs, buf, Parens::InFn, Polarity::Pos); if use_parens { buf.push(')'); diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index d59df274da..df259af3cf 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -4459,6 +4459,7 @@ pub struct StorageSubs { struct StorageSubsOffsets { utable: u32, variables: u32, + tuple_elem_indices: u32, tag_names: u32, symbol_names: u32, field_names: u32, @@ -4542,6 +4543,7 @@ impl StorageSubs { let self_offsets = StorageSubsOffsets { utable: self.subs.utable.len() as u32, variables: self.subs.variables.len() as u32, + tuple_elem_indices: self.subs.tuple_elem_indices.len() as u32, tag_names: self.subs.tag_names.len() as u32, symbol_names: self.subs.symbol_names.len() as u32, field_names: self.subs.field_names.len() as u32, @@ -4553,6 +4555,7 @@ impl StorageSubs { let offsets = StorageSubsOffsets { utable: (target.utable.len() - Variable::NUM_RESERVED_VARS) as u32, variables: target.variables.len() as u32, + tuple_elem_indices: target.tuple_elem_indices.len() as u32, tag_names: target.tag_names.len() as u32, symbol_names: target.symbol_names.len() as u32, field_names: target.field_names.len() as u32, @@ -4593,6 +4596,10 @@ impl StorageSubs { .map(|v| Self::offset_variable(&offsets, *v)), ); + target + .tuple_elem_indices + .extend(self.subs.tuple_elem_indices); + target.variable_slices.extend( self.subs .variable_slices @@ -4613,6 +4620,11 @@ impl StorageSubs { (self_offsets.utable + offsets.utable) as usize ); + debug_assert_eq!( + target.tuple_elem_indices.len(), + (self_offsets.tuple_elem_indices + offsets.tuple_elem_indices) as usize + ); + debug_assert_eq!( target.tag_names.len(), (self_offsets.tag_names + offsets.tag_names) as usize @@ -4756,6 +4768,7 @@ impl StorageSubs { } fn offset_tuple_elems(offsets: &StorageSubsOffsets, mut tuple_elems: TupleElems) -> TupleElems { + tuple_elems.elem_index_start += offsets.tuple_elem_indices; tuple_elems.variables_start += offsets.variables; tuple_elems diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index a1b1bf6dd6..21b7b3c7ca 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -1786,7 +1786,9 @@ static mut TYPE_CLONE_COUNT: std::sync::atomic::AtomicUsize = pub fn get_type_clone_count() -> usize { if cfg!(debug_assertions) { - unsafe { TYPE_CLONE_COUNT.load(std::sync::atomic::Ordering::SeqCst) } + // A global counter just needs relaxed, and nothing relies upon this atomic for any + // happens-before relationships. + unsafe { TYPE_CLONE_COUNT.load(std::sync::atomic::Ordering::Relaxed) } } else { 0 } @@ -1796,7 +1798,7 @@ impl Clone for Type { fn clone(&self) -> Self { #[cfg(debug_assertions)] unsafe { - TYPE_CLONE_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + TYPE_CLONE_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) }; match self { @@ -3615,6 +3617,13 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } } +/// Either a field name for a record or an index into a tuple +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum IndexOrField { + Field(Lowercase), + Index(usize), +} + #[derive(Debug)] pub struct RecordStructure { /// Invariant: these should be sorted! @@ -3775,10 +3784,9 @@ pub enum Category { // records Record, - RecordAccessor(Lowercase), + Accessor(IndexOrField), RecordAccess(Lowercase), Tuple, - TupleAccessor(usize), TupleAccess(usize), DefaultValue(Lowercase), // for setting optional fields @@ -3794,6 +3802,7 @@ pub enum Category { #[derive(Debug, Clone, PartialEq, Eq)] pub enum PatternCategory { Record, + Tuple, List, EmptyRecord, PatternGuard, @@ -4598,18 +4607,9 @@ pub fn gather_tags_unsorted_iter( let mut stack = vec![other_fields]; - #[cfg(debug_assertions)] - let mut seen_head_union = false; - loop { match subs.get_content_without_compacting(ext.var()) { Structure(TagUnion(sub_fields, sub_ext)) => { - #[cfg(debug_assertions)] - { - assert!(!seen_head_union, "extension variable is another tag union, but I expected it to be either open or closed!"); - seen_head_union = true; - } - stack.push(*sub_fields); ext = *sub_ext; diff --git a/crates/compiler/unify/Cargo.toml b/crates/compiler/unify/Cargo.toml index d8823b0967..4f2f899ca3 100644 --- a/crates/compiler/unify/Cargo.toml +++ b/crates/compiler/unify/Cargo.toml @@ -1,13 +1,14 @@ [package] -authors = ["The Roc Contributors"] -edition = "2021" -license = "UPL-1.0" name = "roc_unify" -version = "0.0.1" description = "Implements Roc's unification algorithm, the heartstone of Roc's type inference." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -bitflags = "1.3.2" +bitflags.workspace = true [dependencies.roc_collections] path = "../collections" @@ -24,11 +25,5 @@ path = "../types" [dependencies.roc_debug_flags] path = "../debug_flags" -[dependencies.roc_can] -path = "../can" - -[dependencies.roc_solve_problem] -path = "../solve_problem" - [dependencies.roc_tracing] path = "../../tracing" diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index 77c3317306..4b6d737dfb 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -1568,8 +1568,10 @@ fn unspecialized_lambda_set_sorter(subs: &Subs, uls1: Uls, uls2: Uls) -> std::cm } (FlexAbleVar(..), _) => Greater, (_, FlexAbleVar(..)) => Less, - // For everything else, the order is irrelevant - (_, _) => Less, + // For everything else, sort by the root key + (_, _) => subs + .get_root_key_without_compacting(var1) + .cmp(&subs.get_root_key_without_compacting(var2)), } } ord => ord, @@ -1731,6 +1733,8 @@ fn unify_unspecialized_lambdas( let kept = uls_left.next().unwrap(); merged_uls.push(*kept); } else { + // CASE: disjoint_flex_specializations + // // ... a1 ... // ... b1 ... // => ... a1, b1 ... @@ -1800,20 +1804,38 @@ fn unify_unspecialized_lambdas( let _dropped = uls_left.next().unwrap(); } (_, _) => { - // ... {foo: _} ... - // ... {foo: _} ... - // => ... {foo: _} ... - // - // Unify them, then advance one. - // (the choice is arbitrary, so we choose the left) - - let outcome = unify_pool(env, pool, var_l, var_r, mode); - if !outcome.mismatches.is_empty() { - return Err(outcome); + if env.subs.equivalent_without_compacting(var_l, var_r) { + // ... a1 ... + // ... b1=a1 ... + // => ... a1 ... + // + // Keep the one on the left, drop the one on the right. Then progress + // both, because the next variable on the left must be disjoint from + // the current on the right (resp. next variable on the right vs. + // current left) - if they aren't, then the invariant was broken. + // + // Then progress both, because the invariant tells us they must be + // disjoint, and if there were any concrete variables, they would have + // appeared earlier. + let _dropped = uls_right.next().unwrap(); + let kept = uls_left.next().unwrap(); + merged_uls.push(*kept); + } else { + // Even if these two variables unify, since they are not equivalent, + // they correspond to different specializations! As such we must not + // merge them. + // + // Instead, keep both, but do so by adding and advancing the side with + // the lower root. See CASE disjoint_flex_specializations for + // reasoning. + if env.subs.get_root_key(var_l) < env.subs.get_root_key(var_r) { + let kept = uls_left.next().unwrap(); + merged_uls.push(*kept); + } else { + let kept = uls_right.next().unwrap(); + merged_uls.push(*kept); + } } - whole_outcome.union(outcome); - - let _dropped = uls_left.next().unwrap(); } } } @@ -3756,30 +3778,38 @@ fn unify_recursion( structure: Variable, other: &Content, ) -> Outcome { - if !matches!(other, RecursionVar { .. }) { - if env.seen_recursion_pair(ctx.first, ctx.second) { - return Default::default(); - } - - env.add_recursion_pair(ctx.first, ctx.second); + if env.seen_recursion_pair(ctx.first, ctx.second) { + return Default::default(); } + env.add_recursion_pair(ctx.first, ctx.second); + let outcome = match other { RecursionVar { opt_name: other_opt_name, - structure: _other_structure, + structure: other_structure, } => { - // NOTE: structure and other_structure may not be unified yet, but will be - // we should not do that here, it would create an infinite loop! + // We haven't seen these two recursion vars yet, so go and unify their structures. + // We need to do this before we merge the two recursion vars, since the unification of + // the structures may be material. + + let mut outcome = unify_pool(env, pool, structure, *other_structure, ctx.mode); + if !outcome.mismatches.is_empty() { + return outcome; + } + let name = (*opt_name).or(*other_opt_name); - merge( + let merge_outcome = merge( env, ctx, RecursionVar { opt_name: name, structure, }, - ) + ); + + outcome.union(merge_outcome); + outcome } Structure(_) => { @@ -3841,9 +3871,7 @@ fn unify_recursion( Error => merge(env, ctx, Error), }; - if !matches!(other, RecursionVar { .. }) { - env.remove_recursion_pair(ctx.first, ctx.second); - } + env.remove_recursion_pair(ctx.first, ctx.second); outcome } diff --git a/crates/docs/Cargo.toml b/crates/docs/Cargo.toml index 4a31e1740f..b29d35a0a7 100644 --- a/crates/docs/Cargo.toml +++ b/crates/docs/Cargo.toml @@ -1,30 +1,32 @@ [package] name = "roc_docs" -version = "0.0.1" -license = "UPL-1.0" -authors = ["The Roc Contributors"] -edition = "2021" description = "Generates html documentation from Roc files and is used for Rocs builtins." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -pulldown-cmark = { version = "0.9.2", default-features = false } roc_ast = { path = "../ast" } -roc_load = { path = "../compiler/load" } roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } -roc_code_markup = { path = "../code_markup"} -roc_module = { path = "../compiler/module" } -roc_region = { path = "../compiler/region" } -roc_types = { path = "../compiler/types" } -roc_parse = { path = "../compiler/parse" } -roc_target = { path = "../compiler/roc_target" } +roc_code_markup = { path = "../code_markup" } roc_collections = { path = "../compiler/collections" } -roc_highlight = { path = "../highlight"} -roc_packaging = { path = "../packaging"} -roc_reporting = { path = "../reporting"} +roc_highlight = { path = "../highlight" } +roc_load = { path = "../compiler/load" } +roc_module = { path = "../compiler/module" } +roc_packaging = { path = "../packaging" } +roc_parse = { path = "../compiler/parse" } +roc_region = { path = "../compiler/region" } +roc_reporting = { path = "../reporting" } +roc_target = { path = "../compiler/roc_target" } +roc_types = { path = "../compiler/types" } + bumpalo.workspace = true -snafu.workspace = true peg.workspace = true +pulldown-cmark.workspace = true +snafu.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/crates/docs/src/docs_error.rs b/crates/docs/src/docs_error.rs deleted file mode 100644 index 7ee2f06ced..0000000000 --- a/crates/docs/src/docs_error.rs +++ /dev/null @@ -1,55 +0,0 @@ -use peg::error::ParseError; -use roc_ast::ast_error::ASTError; -use roc_module::module_err::ModuleError; -use roc_parse::parser::SyntaxError; -use snafu::Snafu; - -#[derive(Debug, Snafu)] -#[snafu(visibility(pub))] -#[allow(clippy::enum_variant_names)] -pub enum DocsError { - WrapASTError { - #[snafu(backtrace)] - source: ASTError, - }, - WrapModuleError { - #[snafu(backtrace)] - source: ModuleError, - }, - WrapSyntaxError { - msg: String, - }, - WrapPegParseError { - source: ParseError, - }, -} - -pub type DocsResult = std::result::Result; - -impl<'a> From> for DocsError { - fn from(syntax_err: SyntaxError) -> Self { - Self::WrapSyntaxError { - msg: format!("{:?}", syntax_err), - } - } -} - -impl From for DocsError { - fn from(ast_err: ASTError) -> Self { - Self::WrapASTError { source: ast_err } - } -} - -impl From for DocsError { - fn from(module_err: ModuleError) -> Self { - Self::WrapModuleError { source: module_err } - } -} - -impl From> for DocsError { - fn from(peg_parse_err: ParseError) -> Self { - Self::WrapPegParseError { - source: peg_parse_err, - } - } -} diff --git a/crates/docs/src/html.rs b/crates/docs/src/html.rs deleted file mode 100644 index baad3715db..0000000000 --- a/crates/docs/src/html.rs +++ /dev/null @@ -1,85 +0,0 @@ -use roc_code_markup::{markup::nodes::MarkupNode, slow_pool::SlowPool}; - -// determine appropriate css class for MarkupNode -pub fn mark_node_to_html(mark_node: &MarkupNode, mark_node_pool: &SlowPool, buf: &mut String) { - let mut additional_newlines = 0; - - match mark_node { - MarkupNode::Nested { - children_ids, - newlines_at_end, - .. - } => { - for &child_id in children_ids { - mark_node_to_html(mark_node_pool.get(child_id), mark_node_pool, buf) - } - - additional_newlines = *newlines_at_end; - } - MarkupNode::Text { - content, - syn_high_style, - newlines_at_end, - .. - } => { - use roc_code_markup::syntax_highlight::HighlightStyle::*; - - let css_class = match syn_high_style { - Operator => "operator", - String => "string", - FunctionName => "function-name", - FunctionArgName => "function-arg-name", - Type => "type", - Bracket => "bracket", - Number => "number", - PackageRelated => "package-related", - Value => "value", - RecordField => "recordfield", - Import => "import", - Provides => "provides", - Blank => "blank", - Comment => "comment", - DocsComment => "docs-comment", - UppercaseIdent => "uppercase-ident", - LowercaseIdent => "lowercase-ident", - Keyword => "keyword-ident", - }; - - write_html_to_buf(content, css_class, buf); - - additional_newlines = *newlines_at_end; - } - MarkupNode::Blank { - newlines_at_end, .. - } => { - let mut content_str = " ".to_string(); - - for _ in 0..*newlines_at_end { - content_str.push('\n'); - } - - write_html_to_buf(&content_str, "blank", buf); - - additional_newlines = *newlines_at_end; - } - MarkupNode::Indent { .. } => { - let content_str = mark_node.get_content(); - - write_html_to_buf(&content_str, "indent", buf); - } - } - - for _ in 0..additional_newlines { - buf.push('\n') - } -} - -fn write_html_to_buf(content: &str, css_class: &'static str, buf: &mut String) { - let opening_tag: String = [""].concat(); - - buf.push_str(opening_tag.as_str()); - - buf.push_str(content); - - buf.push_str(""); -} diff --git a/crates/docs/src/lib.rs b/crates/docs/src/lib.rs index 08b28bada8..075610a1a3 100644 --- a/crates/docs/src/lib.rs +++ b/crates/docs/src/lib.rs @@ -3,29 +3,23 @@ extern crate pulldown_cmark; extern crate roc_load; use bumpalo::Bump; -use docs_error::{DocsError, DocsResult}; -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::VecSet; -use roc_highlight::highlight_parser::{highlight_defs, highlight_expr}; use roc_load::docs::{DocEntry, TypeAnnotation}; use roc_load::docs::{ModuleDocumentation, RecordField}; use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading}; use roc_module::symbol::{Interns, Symbol}; use roc_packaging::cache::{self, RocCacheDir}; -use roc_parse::ident::{parse_ident, Ident}; +use roc_parse::ident::{parse_ident, Accessor, Ident}; use roc_parse::state::State; use roc_region::all::Region; use std::fs; use std::path::{Path, PathBuf}; -mod docs_error; -mod html; - const BUILD_DIR: &str = "./generated-docs"; +const LINK_SVG: &str = include_str!("./static/link.svg"); + pub fn generate_docs_html(root_file: PathBuf) { let build_dir = Path::new(BUILD_DIR); let loaded_module = load_module_for_docs(root_file); @@ -131,49 +125,6 @@ fn page_title(package_name: &str, module_name: &str) -> String { format!("{module_name} - {package_name}") } -// converts plain-text code to highlighted html -pub fn syntax_highlight_expr(code_str: &str) -> DocsResult { - let trimmed_code_str = code_str.trim_end().trim(); - let mut mark_node_pool = SlowPool::default(); - - let mut highlighted_html_str = String::new(); - - match highlight_expr(trimmed_code_str, &mut mark_node_pool) { - Ok(root_mark_node_id) => { - let root_mark_node = mark_node_pool.get(root_mark_node_id); - mark_node_to_html(root_mark_node, &mark_node_pool, &mut highlighted_html_str); - - Ok(highlighted_html_str) - } - Err(err) => Err(DocsError::from(err)), - } -} - -// converts plain-text code to highlighted html -pub fn syntax_highlight_top_level_defs(code_str: &str) -> DocsResult { - let trimmed_code_str = code_str.trim_end().trim(); - - let mut mark_node_pool = SlowPool::default(); - - let mut highlighted_html_str = String::new(); - - match highlight_defs(trimmed_code_str, &mut mark_node_pool) { - Ok(mark_node_id_vec) => { - let def_mark_nodes: Vec<&MarkupNode> = mark_node_id_vec - .iter() - .map(|mn_id| mark_node_pool.get(*mn_id)) - .collect(); - - for mn in def_mark_nodes { - mark_node_to_html(mn, &mark_node_pool, &mut highlighted_html_str) - } - - Ok(highlighted_html_str) - } - Err(err) => Err(DocsError::from(err)), - } -} - fn render_module_documentation( module: &ModuleDocumentation, root_module: &LoadedModule, @@ -197,7 +148,7 @@ fn render_module_documentation( for entry in &module.entries { match entry { DocEntry::DocDef(doc_def) => { - // Only redner entries that are exposed + // Only render entries that are exposed if all_exposed_symbols.contains(&doc_def.symbol) { buf.push_str("
"); @@ -205,7 +156,8 @@ fn render_module_documentation( let href = format!("#{name}"); let mut content = String::new(); - push_html(&mut content, "a", vec![("href", href.as_str())], name); + push_html(&mut content, "a", vec![("href", href.as_str())], LINK_SVG); + push_html(&mut content, "strong", vec![], name); for type_var in &doc_def.type_vars { content.push(' '); @@ -588,6 +540,7 @@ fn type_annotation_to_html( type_annotation_to_html(indent_level, buf, extension, true); } TypeAnnotation::Function { args, output } => { + let mut paren_is_open = false; let mut peekable_args = args.iter().peekable(); while let Some(arg) = peekable_args.next() { if is_multiline { @@ -596,8 +549,14 @@ fn type_annotation_to_html( } indent(buf, indent_level + 1); } + if needs_parens && !paren_is_open { + buf.push('('); + paren_is_open = true; + } - type_annotation_to_html(indent_level, buf, arg, false); + let child_needs_parens = + matches!(arg, TypeAnnotation::Function { args: _, output: _ }); + type_annotation_to_html(indent_level, buf, arg, child_needs_parens); if peekable_args.peek().is_some() { buf.push_str(", "); @@ -618,6 +577,9 @@ fn type_annotation_to_html( } type_annotation_to_html(next_indent_level, buf, output, false); + if needs_parens && paren_is_open { + buf.push(')'); + } } TypeAnnotation::Ability { members: _ } => { // TODO(abilities): fill me in @@ -728,7 +690,6 @@ fn doc_url<'a>( module_name = symbol.module_string(interns); } Err(_) => { - dbg!(scope); // TODO return Err here panic!( "Tried to generate an automatic link in docs for symbol `{}`, but that symbol was not in scope in this module.", @@ -816,7 +777,7 @@ fn markdown_to_html( let mut iter = parts.iter(); match iter.next() { - Some(symbol_name) if iter.next().is_none() => { + Some(Accessor::RecordField(symbol_name)) if iter.next().is_none() => { let DocUrl { url, title } = doc_url( all_exposed_symbols, scope, @@ -857,65 +818,24 @@ fn markdown_to_html( let markdown_options = pulldown_cmark::Options::ENABLE_TABLES; - let mut expecting_code_block = false; + let mut in_code_block: Option = None; + let mut to_highlight = String::new(); let mut docs_parser = vec![]; - let (_, _) = pulldown_cmark::Parser::new_with_broken_link_callback( + let parser = pulldown_cmark::Parser::new_with_broken_link_callback( markdown, markdown_options, Some(&mut broken_link_callback), - ) - .fold((0, 0), |(start_quote_count, end_quote_count), event| { + ); - match &event { - // Replace this sequence (`>>>` syntax): - // Start(BlockQuote) - // Start(BlockQuote) - // Start(BlockQuote) - // Start(Paragraph) - // For `Start(CodeBlock(Fenced(Borrowed("roc"))))` - Event::Start(BlockQuote) => { - docs_parser.push(event); - (start_quote_count + 1, 0) + for event in parser { + match event { + Event::Code(cow_str) => { + let highlighted_html = + roc_highlight::highlight_roc_code_inline(cow_str.to_string().as_str()); + docs_parser.push(Event::Html(CowStr::from(highlighted_html))); } - Event::Start(Paragraph) => { - if start_quote_count == 3 { - docs_parser.pop(); - docs_parser.pop(); - docs_parser.pop(); - docs_parser.push(Event::Start(CodeBlock(CodeBlockKind::Fenced( - CowStr::Borrowed("roc"), - )))); - } else { - docs_parser.push(event); - } - (0, 0) - } - // Replace this sequence (`>>>` syntax): - // End(Paragraph) - // End(BlockQuote) - // End(BlockQuote) - // End(BlockQuote) - // For `End(CodeBlock(Fenced(Borrowed("roc"))))` - Event::End(Paragraph) => { - docs_parser.push(event); - (0, 1) - } - Event::End(BlockQuote) => { - if end_quote_count == 3 { - docs_parser.pop(); - docs_parser.pop(); - docs_parser.pop(); - docs_parser.push(Event::End(CodeBlock(CodeBlockKind::Fenced( - CowStr::Borrowed("roc"), - )))); - (0, 0) - } else { - docs_parser.push(event); - (0, end_quote_count + 1) - } - } - Event::End(Link(LinkType::ShortcutUnknown, _url, _title)) => { + Event::End(Link(LinkType::ShortcutUnknown, ref _url, ref _title)) => { // Replace the preceding Text node with a Code node, so it // renders as the equivalent of [`List.len`] instead of [List.len] match docs_parser.pop() { @@ -929,41 +849,59 @@ fn markdown_to_html( } docs_parser.push(event); - - (start_quote_count, end_quote_count) } - Event::Start(CodeBlock(CodeBlockKind::Fenced(_))) => { - expecting_code_block = true; - docs_parser.push(event); - (0, 0) + Event::Start(CodeBlock(CodeBlockKind::Fenced(cow_str))) => { + in_code_block = Some(cow_str); } Event::End(CodeBlock(_)) => { - expecting_code_block = false; - docs_parser.push(event); - (0, 0) - } - Event::Text(CowStr::Borrowed(code_str)) if expecting_code_block => { + match in_code_block { + Some(cow_str) => { + if cow_str.contains("unchecked") { + // TODO HANDLE UNCHECKED + } - match syntax_highlight_expr( - code_str - ) - { - Ok(highlighted_code_str) => { - docs_parser.push(Event::Html(CowStr::from(highlighted_code_str))); - } - Err(syntax_error) => { - panic!("Unexpected parse failure when parsing this for rendering in docs:\n\n{}\n\nParse error was:\n\n{:?}\n\n", code_str, syntax_error) - } - }; + if cow_str.contains("repl") { + // TODO HANDLE REPL + } - (0, 0) - } - _ => { + // TODO HANDLE CHECKING BY DEFAULT + let highlighted_html = roc_highlight::highlight_roc_code(&to_highlight); + docs_parser.push(Event::Html(CowStr::from(highlighted_html))); + } + None => { + // Indented code block + + let highlighted_html = roc_highlight::highlight_roc_code(&to_highlight); + docs_parser.push(Event::Html(CowStr::from(highlighted_html))); + } + } + + // Reset codeblock buffer + to_highlight = String::new(); + in_code_block = None; + + // Push Event::End(CodeBlock) docs_parser.push(event); - (0, 0) + } + Event::Text(t) => { + match in_code_block { + Some(_) => { + // If we're in a code block, build up the string of text + to_highlight.push_str(&t); + } + None => { + docs_parser.push(Event::Text(t)); + } + } + } + Event::Html(html) => { + docs_parser.push(Event::Text(html)); + } + e => { + docs_parser.push(e); } } - }); + } pulldown_cmark::html::push_html(buf, docs_parser.into_iter()); } diff --git a/crates/docs/src/static/link.svg b/crates/docs/src/static/link.svg new file mode 100644 index 0000000000..50eaa23920 --- /dev/null +++ b/crates/docs/src/static/link.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/crates/docs/src/static/search.js b/crates/docs/src/static/search.js index 00693f3d4d..7fced7e2d9 100644 --- a/crates/docs/src/static/search.js +++ b/crates/docs/src/static/search.js @@ -65,4 +65,60 @@ } }); + const isTouchSupported = () => { + try{ document.createEvent("TouchEvent"); return true; } + catch(e){ return false; } + } + + // Select all elements that are children of
 elements
+  const codeBlocks = document.querySelectorAll("pre > samp");
+  
+  // Iterate over each code block
+  codeBlocks.forEach((codeBlock) => {
+    // Create a "Copy" button
+    const copyButton = document.createElement("button");
+    copyButton.classList.add("copy-button");
+    copyButton.textContent = "Copy";
+  
+    // Add event listener to copy button
+    copyButton.addEventListener("click", () => {
+      const codeText = codeBlock.innerText;
+      navigator.clipboard.writeText(codeText);
+      copyButton.textContent = "Copied!";
+      copyButton.classList.add("copy-button-copied");
+      copyButton.addEventListener("mouseleave", () => {
+          copyButton.textContent = "Copy";
+          copyButton.classList.remove('copy-button-copied');
+      });
+    });
+  
+    // Create a container for the copy button and append it to the document
+    const buttonContainer = document.createElement("div");
+    buttonContainer.classList.add("button-container");
+    buttonContainer.appendChild(copyButton);
+    codeBlock.parentNode.insertBefore(buttonContainer, codeBlock);
+  
+    // Hide the button container by default
+    buttonContainer.style.display = "none";
+  
+    if (isTouchSupported()) {
+      // Show the button container on click for touch support (e.g. mobile)
+      document.addEventListener("click", (event) => {
+        if (event.target.closest("pre > samp") !== codeBlock) {
+          buttonContainer.style.display = "none";
+        } else {
+          buttonContainer.style.display = "block";
+        }
+      });
+    } else {
+      // Show the button container on hover for non-touch support (e.g. desktop)
+      codeBlock.parentNode.addEventListener("mouseenter", () => {
+        buttonContainer.style.display = "block";
+      });
+  
+      codeBlock.parentNode.addEventListener("mouseleave", () => {
+        buttonContainer.style.display = "none";
+      });  
+    }
+  });
 })();
diff --git a/crates/docs/src/static/styles.css b/crates/docs/src/static/styles.css
index 3c73586699..a1706e8735 100644
--- a/crates/docs/src/static/styles.css
+++ b/crates/docs/src/static/styles.css
@@ -1,43 +1,40 @@
 :root {
-  --purple-1: #f2f1f9;
-  --purple-2: #d9d2ef;
-  --purple-3: #a17ef1;
-  --purple-4: #8d51f6;
-  --purple-5: #742af4;
-  --purple-6: #23143d;
-  --purple-7: #25222a;
-  --purple-8: #161519;
-  --purple-9: #111014;
-  --gray-1: #f8f8f8;
-  --gray-2: #f0f0f0;
-  --gray-3: #dddddd;
-  --gray-4: #5b5b5b;
-  --gray-5: #212121;
-  --link-color: var(--purple-5);
-  --code-link-color: #5721d4;
-  --text-color: var(--gray-5);
-  --code-color: #222222;
-  --code-bg-color: var(--gray-2);
+  /* WCAG AAA Compliant colors */
+  --code-bg: #f4f8f9;
+  --gray: #717171;
+  --orange: #bf5000;
+  --green: #0b8400;
+  --cyan: #067c94;
+  --blue: #05006d;
+  --violet: #7c38f5;
+  --violet-bg: #ece2fd;
+  --magenta: #a20031;
+
+  --link-color: var(--violet);
+  --code-link-color: var(--violet);
+  --text-color: #000;
+  --text-hover-color: var(--violet);
   --body-bg-color: #ffffff;
-  --border-color: #e9e9e9;
+  --border-color: #717171;
   --faded-color: #4c4c4c;
-  --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
-  --font-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  --font-sans: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial,
+      sans-serif;
+  --font-mono: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier,
+      monospace;
   --top-header-height: 67px;
   --sidebar-width: 280px;
-  --top-bar-bg: #8257e5;
-  --top-bar-fg: #ffffff;
-  --nav-link-hover-color: #000000;
-  --type-signature-color: var(--purple-5);
-  --type-signature-bg-color: var(--purple-1);
-  --module-entry-border-color: var(--gray-3);
 }
 
 a {
-  color: #972395;
+  color: var(--violet);
 }
 
-table tr th, table tr td {
+table tr th {
+  border: 1px solid var(--gray);
+}
+
+table tr th,
+table tr td {
   padding: 6px 13px;
 }
 
@@ -48,7 +45,7 @@ table tr th, table tr td {
 .logo svg {
   height: 48px;
   width: 48px;
-  fill: var(--top-bar-fg);
+  fill: var(--violet);
 }
 
 .logo:hover {
@@ -56,11 +53,10 @@ table tr th, table tr td {
 }
 
 .logo svg:hover {
-  fill: var(--nav-link-hover-color);
+  fill: var(--green);
 }
 
 .pkg-full-name {
-  color: var(--text-color);
   display: flex;
   align-items: center;
   font-size: 32px;
@@ -77,22 +73,33 @@ table tr th, table tr td {
   font-family: var(--font-mono);
   font-size: 18px;
   font-weight: normal;
-  color: var(--type-signature-color);
-  background-color: var(--type-signature-bg-color);
-  width: fit-content;
+  color: var(--violet);
+  width: auto;
   margin-top: 0;
   margin-bottom: 24px;
   padding: 8px 16px;
-  border-radius: 8px;
+  border-left: 2px solid var(--violet);
 }
 
 .entry-name a {
-  font-weight: bold;
-  color: var(--type-signature-color);
+  visibility: hidden;
+  display: inline-block;
+  width: 18px;
+  height: 14px;
+  margin-left: -8px;
+  margin-right: 4px;
+  user-select: none;
+  color: var(--violet);
 }
 
-.entry-name a:visited {
-  color: var(--type-signature-color);
+.entry-name:hover a {
+  visibility: visible;
+  text-decoration: none;
+}
+
+.entry-name:not(:hover) a {
+  visibility: hidden;
+  transition: visibility 2s;
 }
 
 .pkg-full-name a {
@@ -104,7 +111,8 @@ a {
   text-decoration: none;
 }
 
-a:hover, a:hover code {
+a:hover,
+a:hover code {
   text-decoration: underline;
 }
 
@@ -113,15 +121,16 @@ a:hover, a:hover code {
   display: flex;
   align-items: center;
   height: 100%;
-  background-color: var(--top-bar-bg);
+  background-color: var(--violet-bg);
 }
 
-.pkg-and-logo a, .pkg-and-logo a:visited {
-  color: var(--top-bar-fg);
+.pkg-and-logo a,
+.pkg-and-logo a:visited {
+  color: var(--violet);
 }
 
 .pkg-and-logo a:hover {
-  color: var(--nav-link-hover-color);
+  color: var(--green);
   text-decoration: none;
 }
 
@@ -140,7 +149,10 @@ a:hover, a:hover code {
 
 body {
   display: grid;
-  grid-template-columns: [before-sidebar] 1fr [sidebar] var(--sidebar-width) [main-content] fit-content(calc(1280px - var(--sidebar-width))) [end] 1fr;
+  grid-template-columns:
+      [before-sidebar] 1fr [sidebar] var(--sidebar-width)
+      [main-content] fit-content(calc(1280px - var(--sidebar-width)))
+      [end] 1fr;
   grid-template-rows: [top-header] var(--top-header-height) [above-footer] auto [footer] auto;
   box-sizing: border-box;
   margin: 0;
@@ -166,16 +178,22 @@ main {
 }
 
 section {
-  border: 1px solid var(--module-entry-border-color);
-  border-radius: 8px;
-  padding: 32px;
-  margin: 32px 0;
+  padding: 0px 0px 16px 0px;
+  margin: 72px 0px;
 }
 
 section > *:last-child {
   margin-bottom: 0;
 }
 
+section h1,
+section h2,
+section h3,
+section h4,
+section p {
+padding: 0px 16px;
+}
+
 #sidebar-nav {
   grid-column-start: sidebar;
   grid-column-end: sidebar;
@@ -195,7 +213,7 @@ section > *:last-child {
   grid-column-end: sidebar;
   grid-row-start: top-header;
   grid-row-end: top-header;
-  background-color: var(--top-bar-bg);
+  background-color: var(--violet-bg);
 }
 
 .top-header {
@@ -216,18 +234,18 @@ section > *:last-child {
 
 .top-header-triangle {
   /* This used to be a clip-path, but Firefox on Android (at least as of early 2020)
-   * rendered the page extremely slowly in that version. With this approach it's super fast.
-   */
+ * rendered the page extremely slowly in that version. With this approach it's super fast.
+ */
   width: 0;
   height: 0;
   border-style: solid;
   border-width: var(--top-header-height) 0 0 48px;
-  border-color: transparent transparent transparent var(--top-bar-bg);
+  border-color: transparent transparent transparent var(--violet-bg);
 }
 
 p {
-    overflow-wrap: break-word;
-    margin: 24px 0;
+  overflow-wrap: break-word;
+  margin: 24px 0;
 }
 
 footer {
@@ -288,10 +306,16 @@ footer p {
   font-weight: bold;
   margin-top: 18px;
   margin-bottom: 48px;
+  color: var(--violet);
 }
 
-.module-name a, .module-name a:visited {
-  color: inherit;
+.module-name a,
+.module-name a:visited {
+color: inherit;
+}
+
+.module-name a:hover {
+  color: var(--green);
 }
 
 .sidebar-module-link {
@@ -311,7 +335,8 @@ footer p {
   font-weight: bold;
 }
 
-a, a:visited {
+a,
+a:visited {
   color: var(--link-color);
 }
 
@@ -333,31 +358,31 @@ h4 {
   padding: 8px 16px;
   display: block;
   box-sizing: border-box;
-  border-radius: 8px;
   font-family: var(--font-mono);
-  background-color: var(--code-bg-color);
+  background-color: var(--code-bg);
 }
 
 code {
   font-family: var(--font-mono);
   color: var(--code-color);
-  background-color: var(--code-bg-color);
+  background-color: var(--code-bg);
   display: inline-block;
 }
 
 p code {
   padding: 0 8px;
-  border-radius: 4px;
 }
 
-code a, a code {
+code a,
+a code {
   text-decoration: none;
   color: var(--code-link-color);
   background: none;
   padding: 0;
 }
 
-code a:visited, a:visited code {
+code a:visited,
+a:visited code {
   color: var(--code-link-color);
 }
 
@@ -365,9 +390,15 @@ pre {
   margin: 36px 0;
   padding: 8px 16px;
   box-sizing: border-box;
-  border-radius: 8px;
-  background-color: var(--code-bg-color);
+  background-color: var(--code-bg);
+  overflow-x: hidden;
+  position: relative;
+  word-wrap: normal;
+}
+
+pre>samp {
   overflow-x: auto;
+  display: block;
 }
 
 .hidden {
@@ -375,36 +406,14 @@ pre {
   display: none !important;
 }
 
-.syntax-number {
-    color: #60B7BF;
-}
-.syntax-string {
-  color:#F7577C;
-}
-
-.syntax-bracket {
-  color:#FF335F;
-}
-.syntax-closure-dash,
-.syntax-closure-arrow,
-.syntax-operator
-{
-    color: #ffffff;
-}
-.syntax-comma {
-  color: #9573E6;
-}
-.syntax-comment {
-  color: #ff0000;
-}
-
 #module-search:placeholder-shown {
   padding: 0;
   opacity: 0;
   height: 0;
 }
 
-#module-search, #module-search:focus {
+#module-search,
+#module-search:focus {
   opacity: 1;
   padding: 12px 16px;
   height: 48px;
@@ -430,7 +439,7 @@ pre {
   margin-top: 6px;
   border: none;
   color: var(--faded-color);
-  background-color: var(--code-bg-color);
+  background-color: var(--code-bg);
   font-family: var(--font-serif);
 }
 
@@ -464,7 +473,6 @@ pre {
   border: 1px solid #666;
   padding: 1px 3px 3px;
   font-style: normal;
-  border-radius: 4px;
   line-height: 15px;
 }
 
@@ -476,133 +484,203 @@ pre {
 
 @media (prefers-color-scheme: dark) {
   :root {
-    --body-bg-color: var(--purple-8);
-    --code-bg-color: var(--purple-7);
-    --border-color: #555555;
-    --code-color: #eeeeee;
-    --text-color: var(--gray-1);
-    --logo-solid: #777777;
-    --faded-color: #bbbbbb;
-    --link-color: #c5a8ff;
-    --code-link-color: #b894ff;
-    --top-bar-bg: #6845b9;
-    --top-bar-fg: #eeeeee;
-    --type-signature-color: var(--grey-1);
-    --type-signature-bg-color: var(--purple-4);
-    --module-entry-border-color: var(--purple-7);
+      /* WCAG AAA Compliant colors */
+      --code-bg: #202746;
+      --gray: #b6b6b6;
+      --orange: #fd6e08;
+      --green: #8ecc88;
+      --cyan: #12c9be;
+      --blue: #b1afdf;
+      --violet: #CAADFB;
+      --violet-bg: #332944;
+      --magenta: #f39bac;
+
+      --link-color: var(--violet);
+      --code-link-color: var(--violet);
+      --text-color: #eaeaea;
+      --body-bg-color: #0e0e0f;
+      --border-color: var(--gray);
+      --code-color: #eeeeee;
+      --logo-solid: #8f8f8f;
+      --faded-color: #bbbbbb;
+      --gray: #6e6e6e;
   }
 
   html {
-    scrollbar-color: #444444 #2f2f2f;
+      scrollbar-color: #8f8f8f #2f2f2f;
   }
 }
 
 @media only screen and (max-device-width: 480px) and (orientation: portrait) {
   #search-link-hint {
-    display: none;
+      display: none;
   }
 
   .search-button {
-    display: block; /* This is only visible in mobile. */
+      display: block; /* This is only visible in mobile. */
   }
 
   .top-header {
-    justify-content: space-between;
-    width: auto;
+      justify-content: space-between;
+      width: auto;
   }
 
   .pkg-full-name {
-    margin-left: 8px;
-    margin-right: 12px;
-    font-size: 24px;
-    padding-bottom: 14px;
+      margin-left: 8px;
+      margin-right: 12px;
+      font-size: 24px;
+      padding-bottom: 14px;
   }
 
   .pkg-full-name a {
-    vertical-align: middle;
-    padding: 18px 0;
+      vertical-align: middle;
+      padding: 18px 0;
   }
 
   .logo {
-    padding-left: 2px;
-    width: 50px;
-    height: 54px;
+      padding-left: 2px;
+      width: 50px;
+      height: 54px;
   }
 
   .version {
-    margin: 0;
-    font-weight: normal;
-    font-size: 18px;
-    padding-bottom: 16px;
+      margin: 0;
+      font-weight: normal;
+      font-size: 18px;
+      padding-bottom: 16px;
   }
 
   .module-name {
-    font-size: 36px;
-    margin-top: 8px;
-    margin-bottom: 8px;
-    max-width: calc(100% - 18px);
-    overflow: hidden;
-    text-overflow: ellipsis;
+      font-size: 36px;
+      margin-top: 8px;
+      margin-bottom: 8px;
+      max-width: calc(100% - 18px);
+      overflow: hidden;
+      text-overflow: ellipsis;
   }
 
   main {
-    grid-column-start: none;
-    grid-column-end: none;
-    grid-row-start: above-footer;
-    grid-row-end: above-footer;
-    padding: 18px;
-    font-size: 16px;
+      grid-column-start: none;
+      grid-column-end: none;
+      grid-row-start: above-footer;
+      grid-row-end: above-footer;
+      padding: 18px;
+      font-size: 16px;
   }
 
   #sidebar-nav {
-    grid-column-start: none;
-    grid-column-end: none;
-    grid-row-start: sidebar;
-    grid-row-end: sidebar;
-    margin-top: 0;
-    padding-left: 0;
-    width: auto;
+      grid-column-start: none;
+      grid-column-end: none;
+      grid-row-start: sidebar;
+      grid-row-end: sidebar;
+      margin-top: 0;
+      padding-left: 0;
+      width: auto;
   }
 
   #sidebar-heading {
-    font-size: 24px;
-    margin: 16px;
+      font-size: 24px;
+      margin: 16px;
   }
 
   h3 {
-    font-size: 18px;
-    margin: 0;
-    padding: 0;
+      font-size: 18px;
+      margin: 0;
+      padding: 0;
   }
 
   h4 {
-    font-size: 16px;
+      font-size: 16px;
   }
 
   body {
-    grid-template-columns: auto;
-    grid-template-rows: [top-header] var(--top-header-height) [before-sidebar] auto [sidebar] auto [above-footer] auto [footer] auto;
-/* [before-sidebar] 1fr [sidebar] var(--sidebar-width) [main-content] fit-content(calc(1280px - var(--sidebar-width))) [end] 1fr; */
+      grid-template-columns: auto;
+      grid-template-rows: [top-header] var(--top-header-height) [before-sidebar] auto [sidebar] auto [above-footer] auto [footer] auto;
+      /* [before-sidebar] 1fr [sidebar] var(--sidebar-width) [main-content] fit-content(calc(1280px - var(--sidebar-width))) [end] 1fr; */
 
-    margin: 0;
-    min-width: 320px;
-    max-width: 100%;
+      margin: 0;
+      min-width: 320px;
+      max-width: 100%;
   }
 
   .top-header-triangle {
-    display: none;
+      display: none;
   }
 
   .pkg-and-logo {
-    width: 100%;
+      width: 100%;
   }
 
   .pkg-full-name {
-    flex-grow: 1;
+      flex-grow: 1;
   }
 
   .pkg-full-name a {
-    padding-top: 24px;
-    padding-bottom: 12px;
+      padding-top: 24px;
+      padding-bottom: 12px;
   }
 }
+
+/* Comments `#` and Documentation comments `##` */
+samp .comment,
+code .comment {
+  color: var(--green);
+}
+/* Number, String, Tag, Type literals */
+samp .literal,
+code .literal {
+  color: var(--cyan);
+}
+/* Keywords and punctuation */
+samp .kw,
+code .kw {
+  color: var(--magenta);
+}
+/* Operators */
+samp .op,
+code .op {
+  color: var(--orange);
+}
+
+/* Delimieters */
+samp .delimeter,
+code .delimeter {
+  color: var(--gray);
+}
+
+/* Variables modules and field names */
+samp .lowerident,
+code .lowerident {
+  color: var(--blue);
+}
+
+/* Types, Tags, and Modules */
+samp .upperident,
+code .upperident {
+  color: var(--green);
+}
+
+samp .dim,
+code .dim {
+  opacity: 0.55;
+}
+
+.button-container {
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+
+.copy-button {
+  background: var(--code-bg);
+  border: 1px solid var(--magenta);
+  color: var(--magenta);
+  display: inline-block;
+  cursor: pointer;
+  margin: 8px;
+}
+
+.copy-button:hover {
+  border-color: var(--green);
+  color: var(--green);
+}
diff --git a/crates/docs/tests/insert_syntax_highlighting.rs b/crates/docs/tests/insert_syntax_highlighting.rs
deleted file mode 100644
index e3ecc67195..0000000000
--- a/crates/docs/tests/insert_syntax_highlighting.rs
+++ /dev/null
@@ -1,168 +0,0 @@
-#[macro_use]
-extern crate pretty_assertions;
-// Keep this around until the commented out tests can be enabled again.
-/*#[macro_use]
-extern crate indoc;*/
-
-#[cfg(test)]
-mod insert_doc_syntax_highlighting {
-
-    use roc_docs::{syntax_highlight_expr, syntax_highlight_top_level_defs};
-
-    fn expect_html(code_str: &str, want: &str, use_expr: bool) {
-        if use_expr {
-            match syntax_highlight_expr(code_str) {
-                Ok(highlighted_code_str) => {
-                    assert_eq!(highlighted_code_str, want);
-                }
-                Err(syntax_error) => {
-                    panic!("Unexpected parse failure when parsing this for rendering in docs:\n\n{}\n\nParse error was:\n\n{:?}\n\n", code_str, syntax_error)
-                }
-            };
-        } else {
-            match syntax_highlight_top_level_defs(code_str) {
-                Ok(highlighted_code_str) => {
-                    assert_eq!(highlighted_code_str, want);
-                }
-                Err(syntax_error) => {
-                    panic!("Unexpected parse failure when parsing this for rendering in docs:\n\n{}\n\nParse error was:\n\n{:?}\n\n", code_str, syntax_error)
-                }
-            };
-        }
-    }
-
-    fn expect_html_expr(code_str: &str, want: &str) {
-        expect_html(code_str, want, true)
-    }
-
-    fn expect_html_def(code_str: &str, want: &str) {
-        expect_html(code_str, want, false)
-    }
-
-    #[test]
-    fn number_expr() {
-        expect_html_expr("2", r#"2"#);
-    }
-
-    // These tests have been commented out due to introduction of a new syntax highlighting approach.
-    // You can make these tests work by following the instructions at the top of this file here: roc/highlight/src/highlight_parser.rs
-    /*#[test]
-    fn string_expr() {
-        expect_html_expr(r#""abc""#, r#""abc""#);
-    }
-
-    #[test]
-    fn empty_list_expr() {
-        expect_html_expr(
-            r#"[]"#,
-            r#"[  ]"#,
-        );
-    }
-
-    #[test]
-    fn single_elt_list_expr() {
-        expect_html_expr(
-            r#"[ 0 ]"#,
-            r#"[ 0 ]"#,
-        );
-    }
-
-    #[test]
-    fn multi_elt_list_expr() {
-        expect_html_expr(
-            r#"[ "hello", "WoRlD" ]"#,
-            r#"[ "hello", "WoRlD" ]"#,
-        );
-    }
-
-    #[test]
-    fn record_expr() {
-        expect_html_expr(
-            r#"{ a: "hello!" }"#,
-            "{ a: \"hello!\" }",
-        );
-    }
-
-    #[test]
-    fn nested_record_expr() {
-        expect_html_expr(
-            r#"{ a: { bB: "WoRlD" } }"#,
-            "{ a: { bB: \"WoRlD\" } }",
-        );
-    }*/
-
-    #[test]
-    fn top_level_def_val_num() {
-        expect_html_def(
-            r#"myVal = 0"#,
-            "myVal = 0\n\n",
-        );
-    }
-
-    /*#[test]
-    fn top_level_def_val_str() {
-        expect_html_def(
-            r#"myVal = "Hello, World!""#,
-            "myVal = \"Hello, World!\"\n\n\n",
-        );
-    }
-
-    #[test]
-    fn tld_newline_in_str() {
-        expect_html_def(
-            r#"myVal = "Hello, Newline!\n""#,
-            "myVal = \"Hello, Newline!\n\"\n\n\n",
-        );
-    }
-
-    #[test]
-    fn tld_list() {
-        expect_html_def(
-            r#"myVal = [ 1, 2, 3 ]"#,
-            "myVal = [ 1, 2, 3 ]\n\n\n",
-        );
-    }
-
-    #[test]
-    fn call_builtin() {
-        expect_html_def(
-            r#"myVal = Num.toStr 1234"#,
-            "myVal = Num.toStr 1234\n\n\n",
-        );
-    }
-
-    #[test]
-    fn function() {
-        expect_html_def(
-            r#"myId = \something ->
-                something"#,
-            "myId = \\something -> \n    something\n\n\n",
-        );
-    }
-
-    #[test]
-    fn tld_with_comment_before() {
-        expect_html_def(
-            indoc!(
-                r#"
-                # COMMENT
-                myVal = "Hello, World!"
-                "#,
-            ),
-            "# COMMENT\nmyVal = \"Hello, World!\"\n\n\n\n\n",
-        );
-    }*/
-
-    // TODO see issue #2134
-    /*#[test]
-    fn tld_with_comment_after() {
-        expect_html_def(
-            indoc!(
-                r#"
-                myVal = "Hello, World!" # COMMENT
-                "#,
-            ),
-            "myVal = \"Hello, World!\"# COMMENT\n\n\n\n",
-        );
-    }*/
-}
diff --git a/crates/docs_cli/Cargo.toml b/crates/docs_cli/Cargo.toml
index afe9dd2784..8495e48179 100644
--- a/crates/docs_cli/Cargo.toml
+++ b/crates/docs_cli/Cargo.toml
@@ -1,11 +1,12 @@
 [package]
 name = "roc_docs_cli"
-version = "0.0.1"
-license = "UPL-1.0"
-authors = ["The Roc Contributors"]
-edition = "2021"
 description = "Provides a binary that is only used for static build servers."
 
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
 # 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
@@ -19,7 +20,8 @@ bench = false
 
 [dependencies]
 roc_docs = { path = "../docs" }
-clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions", "derive"] }
+
+clap.workspace = true
 
 [target.'cfg(windows)'.dependencies]
-libc.workspace = true 
+libc.workspace = true
diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml
index 95162b1a44..10966b1ccc 100644
--- a/crates/editor/Cargo.toml
+++ b/crates/editor/Cargo.toml
@@ -1,11 +1,12 @@
 [package]
 name = "roc_editor"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
 description = "An editor for Roc"
 
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
 [package.metadata.cargo-udeps.ignore]
 # confy is currently unused but should not be removed
 normal = ["confy"]
@@ -14,59 +15,51 @@ normal = ["confy"]
 
 [features]
 default = []
-with_sound = ["rodio"]
 
 [dependencies]
 roc_ast = { path = "../ast" }
-roc_collections = { path = "../compiler/collections" }
-roc_load = { path = "../compiler/load" }
 roc_builtins = { path = "../compiler/builtins" }
 roc_can = { path = "../compiler/can" }
-roc_code_markup = { path = "../code_markup"}
-roc_parse = { path = "../compiler/parse" }
-roc_region = { path = "../compiler/region" }
+roc_code_markup = { path = "../code_markup" }
+roc_collections = { path = "../compiler/collections" }
+roc_command_utils = { path = "../utils/command" }
+roc_load = { path = "../compiler/load" }
 roc_module = { path = "../compiler/module" }
-roc_problem = { path = "../compiler/problem" }
-roc_types = { path = "../compiler/types" }
-roc_unify = { path = "../compiler/unify" }
-roc_utils = { path = "../utils"}
 roc_packaging = { path = "../packaging" }
+roc_parse = { path = "../compiler/parse" }
+roc_problem = { path = "../compiler/problem" }
+roc_region = { path = "../compiler/region" }
 roc_reporting = { path = "../reporting" }
 roc_solve = { path = "../compiler/solve" }
+roc_types = { path = "../compiler/types" }
+roc_unify = { path = "../compiler/unify" }
 ven_graph = { path = "../vendor/pathfinding" }
-bumpalo = { version = "3.11.1", features = ["collections"] }
-arrayvec = "0.7.2"
-libc = "0.2.135"
-page_size = "0.4.2"
-# once winit 0.26 is out, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time.
-winit = "0.26.0"
-wgpu = "0.12.0"
-wgpu_glyph = "0.16.0"
-glyph_brush = "0.7.5"
-log = "0.4.14"
-futures = "0.3.24"
-cgmath = "0.18.0"
-snafu = { version = "0.7.1", features = ["backtraces"] }
-colored = "2.0.0"
-pest = "2.3.1"
-pest_derive = "2.3.1"
-copypasta = "0.8.1"
-palette = "0.6.1"
-confy = { git = 'https://github.com/rust-cli/confy', features = [
-  "yaml_conf"
-], default-features = false }
-serde = { version = "1.0.144", features = ["derive"] }
-nonempty = "0.8.0"
-rodio = { version = "0.15.0", optional = true } # to play sounds
-threadpool = "1.8.1"
 
+arrayvec.workspace = true
+bumpalo.workspace = true
+bytemuck.workspace = true
+cgmath.workspace = true
+colored.workspace = true
+confy.workspace = true
+copypasta.workspace = true
 fs_extra.workspace = true
-
-[dependencies.bytemuck]
-version = "1.12.1"
-features = ["derive"]
+futures.workspace = true
+glyph_brush.workspace = true
+libc.workspace = true
+log.workspace = true
+nonempty.workspace = true
+page_size.workspace = true
+palette.workspace = true
+pest.workspace = true
+pest_derive.workspace = true
+serde.workspace = true
+snafu.workspace = true
+threadpool.workspace = true
+wgpu.workspace = true
+wgpu_glyph.workspace = true
+winit.workspace = true
 
 [dev-dependencies]
-rand = "0.8.4"
-tempfile = "3.2.0"
-uuid = { version = "1.1.2", features = ["v4"] }
+rand.workspace = true
+tempfile.workspace = true
+uuid.workspace = true
diff --git a/crates/editor/src/editor/main.rs b/crates/editor/src/editor/main.rs
index 28bb97c9ff..3bb28ef407 100644
--- a/crates/editor/src/editor/main.rs
+++ b/crates/editor/src/editor/main.rs
@@ -32,7 +32,7 @@ use roc_packaging::cache::{self, RocCacheDir};
 use roc_types::subs::VarStore;
 use std::collections::HashSet;
 use std::env;
-use std::fs::{self, File};
+use std::fs::{self, metadata, File};
 use std::io::Write;
 use std::path::PathBuf;
 use std::{error::Error, io, path::Path};
@@ -54,13 +54,13 @@ use winit::{
 
 /// The editor is actually launched from the CLI if you pass it zero arguments,
 /// or if you provide it 1 or more files or directories to open on launch.
-pub fn launch(project_dir_path_opt: Option<&Path>) -> io::Result<()> {
-    run_event_loop(project_dir_path_opt).expect("Error running event loop");
+pub fn launch(project_path_opt: Option<&Path>) -> io::Result<()> {
+    run_event_loop(project_path_opt).expect("Error running event loop");
 
     Ok(())
 }
 
-fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box> {
+fn run_event_loop(project_path_opt: Option<&Path>) -> Result<(), Box> {
     // Open window and create a surface
     let mut event_loop = winit::event_loop::EventLoop::new();
 
@@ -124,10 +124,10 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box(
 const ROC_PROJECTS_FOLDER: &str = "roc-projects";
 const ROC_NEW_PROJECT_FOLDER: &str = "new-roc-project-1";
 
-fn read_main_roc_file(project_dir_path_opt: Option<&Path>) -> (PathBuf, String) {
-    if let Some(project_dir_path) = project_dir_path_opt {
+fn read_main_roc_file(project_path_opt: Option<&Path>) -> (PathBuf, String) {
+    if let Some(project_path) = project_path_opt {
+        let path_metadata = metadata(project_path).unwrap_or_else(|err| panic!("You provided the path {:?}, but I could not read the metadata for the provided path; error: {:?}", &project_path, err));
+
+        if path_metadata.is_file() {
+            let file_content_as_str = std::fs::read_to_string(project_path).unwrap_or_else(|err| {
+                panic!(
+                    "You provided the file {:?}, but I could not read it; error: {}",
+                    &project_path, err
+                )
+            });
+
+            return (project_path.to_path_buf(), file_content_as_str);
+        }
+
         let mut ls_config = HashSet::new();
         ls_config.insert(DirEntryAttr::FullName);
 
-        let dir_items = ls(project_dir_path, &ls_config)
-            .unwrap_or_else(|err| panic!("Failed to list items in project directory: {:?}", err))
+        let dir_items = ls(project_path, &ls_config)
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Failed to list items in project directory; error: {:?}",
+                    err
+                )
+            })
             .items;
 
         let file_names = dir_items.iter().flat_map(|info_hash_map| {
@@ -514,13 +532,13 @@ fn read_main_roc_file(project_dir_path_opt: Option<&Path>) -> (PathBuf, String)
             .collect();
 
         if let Some(&roc_file_name) = roc_file_names.first() {
-            let full_roc_file_path = project_dir_path.join(roc_file_name);
-            let file_as_str = std::fs::read_to_string(Path::new(&full_roc_file_path))
-                .unwrap_or_else(|err| panic!("In the provided project {:?}, I found the roc file {:?}, but I failed to read it: {}", &project_dir_path, full_roc_file_path, err));
+            let full_roc_file_path = project_path.join(roc_file_name);
+            let file_content_as_str = std::fs::read_to_string(Path::new(&full_roc_file_path))
+                .unwrap_or_else(|err| panic!("In the provided project {:?}, I found the roc file {:?}, but I failed to read it: {}", &project_path, full_roc_file_path, err));
 
-            (full_roc_file_path, file_as_str)
+            (full_roc_file_path, file_content_as_str)
         } else {
-            init_new_roc_project(project_dir_path)
+            init_new_roc_project(project_path)
         }
     } else {
         init_new_roc_project(&Path::new(ROC_PROJECTS_FOLDER).join(ROC_NEW_PROJECT_FOLDER))
diff --git a/crates/editor/src/editor/mod.rs b/crates/editor/src/editor/mod.rs
index 23d273400d..5012aa0b4d 100644
--- a/crates/editor/src/editor/mod.rs
+++ b/crates/editor/src/editor/mod.rs
@@ -8,7 +8,5 @@ mod mvc;
 mod render_ast;
 mod render_debug;
 mod resources;
-#[cfg(feature = "with_sound")]
-mod sound;
 mod theme;
 mod util;
diff --git a/crates/editor/src/editor/mvc/ed_update.rs b/crates/editor/src/editor/mvc/ed_update.rs
index 507fd98358..ceb789a51d 100644
--- a/crates/editor/src/editor/mvc/ed_update.rs
+++ b/crates/editor/src/editor/mvc/ed_update.rs
@@ -21,8 +21,6 @@ use crate::editor::mvc::string_update::start_new_string;
 use crate::editor::mvc::string_update::update_small_string;
 use crate::editor::mvc::string_update::update_string;
 use crate::editor::mvc::tld_value_update::{start_new_tld_value, update_tld_val_name};
-#[cfg(feature = "with_sound")]
-use crate::editor::sound::play_sound;
 use crate::ui::text::caret_w_select::CaretWSelect;
 use crate::ui::text::lines::MoveCaretFun;
 use crate::ui::text::selection::validate_raw_sel;
@@ -55,6 +53,7 @@ use roc_code_markup::markup::nodes::EQUALS;
 use roc_code_markup::slow_pool::MarkNodeId;
 use roc_code_markup::slow_pool::SlowPool;
 use roc_collections::all::MutMap;
+use roc_command_utils::cargo;
 use roc_module::ident::Lowercase;
 use roc_module::symbol::Symbol;
 use roc_region::all::Region;
@@ -62,7 +61,6 @@ use roc_solve::module::Solved;
 use roc_types::pretty_print::name_and_print_var;
 use roc_types::pretty_print::DebugPrint;
 use roc_types::subs::{Subs, VarStore, Variable};
-use roc_utils::cargo;
 use snafu::OptionExt;
 use threadpool::ThreadPool;
 use winit::event::VirtualKeyCode;
@@ -546,12 +544,6 @@ impl<'a> EdModel<'a> {
                 self.show_debug_view = !self.show_debug_view;
                 self.dirty = true;
             }
-            F12 => {
-                #[cfg(feature = "with_sound")]
-                _sound_thread_pool.execute(move || {
-                    play_sound("./editor/src/editor/resources/sounds/bell_sound.mp3");
-                });
-            }
             _ => (),
         }
 
diff --git a/crates/editor/src/editor/sound.rs b/crates/editor/src/editor/sound.rs
deleted file mode 100644
index 76a9994de6..0000000000
--- a/crates/editor/src/editor/sound.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-use rodio::{Decoder, OutputStream, Sink};
-use std::fs::File;
-use std::io::BufReader;
-
-pub(crate) fn play_sound(sound_path_str: &str) {
-    let out_stream_res = OutputStream::try_default();
-
-    match out_stream_res {
-        Ok((_, out_stream_handle)) => match Sink::try_new(&out_stream_handle) {
-            Ok(sink) => match File::open(sound_path_str) {
-                Ok(file) => {
-                    let reader = BufReader::new(file);
-
-                    match Decoder::new(reader) {
-                        Ok(decoder) => {
-                            sink.append(decoder);
-                            sink.sleep_until_end();
-                        }
-                        Err(e) => {
-                            println!("Failed to create Decoder from BufReader from sound file at {}. Error message: {:?}", sound_path_str, e);
-                        }
-                    }
-                }
-                Err(e) => {
-                    println!(
-                        "Failed to open sound file at {}. Error message: {}",
-                        sound_path_str, e
-                    );
-                }
-            },
-            Err(e) => {
-                println!(
-                    "Failed to create Sink to play sound. Error message: {:?}",
-                    e
-                );
-            }
-        },
-        Err(e) => {
-            println!(
-                "Failed to create OutputStream to play sound. Error message: {:?}",
-                e
-            );
-        }
-    }
-}
diff --git a/crates/error_macros/Cargo.toml b/crates/error_macros/Cargo.toml
index dba08b9e44..812b2805e4 100644
--- a/crates/error_macros/Cargo.toml
+++ b/crates/error_macros/Cargo.toml
@@ -1,9 +1,10 @@
 [package]
 name = "roc_error_macros"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
 description = "Provides macros for consistent reporting of errors in Roc's rust code."
 
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
 [dependencies]
diff --git a/crates/glue/.gitignore b/crates/glue/.gitignore
new file mode 100644
index 0000000000..4bdc65207f
--- /dev/null
+++ b/crates/glue/.gitignore
@@ -0,0 +1,3 @@
+*.so
+*.dylib
+*.dll
diff --git a/crates/glue/Cargo.toml b/crates/glue/Cargo.toml
index 0e2c145924..3a8d5dc64b 100644
--- a/crates/glue/Cargo.toml
+++ b/crates/glue/Cargo.toml
@@ -1,37 +1,44 @@
 [package]
 name = "roc_glue"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
 description = "Generates code needed for platform hosts to communicate with Roc apps. This tool is not necessary for writing a platform in another language, however, it's a great convenience! Currently supports Rust platforms, and the plan is to support any language via a plugin model."
 
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
 [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_packaging = { path = "../packaging" }
-roc_types = { path = "../compiler/types" }
+roc_build = { path = "../compiler/build" }
 roc_builtins = { path = "../compiler/builtins" }
-roc_module = { path = "../compiler/module" }
+roc_can = { path = "../compiler/can" }
 roc_collections = { path = "../compiler/collections" }
-roc_target = { path = "../compiler/roc_target" }
 roc_error_macros = { path = "../error_macros" }
+roc_gen_llvm= { path = "../compiler/gen_llvm" }
+roc_linker = { path = "../linker"}
+roc_load = { path = "../compiler/load" }
+roc_module = { path = "../compiler/module" }
+roc_mono = { path = "../compiler/mono" }
+roc_packaging = { path = "../packaging" }
+roc_reporting = { path = "../reporting" }
+roc_std = { path = "../roc_std" }
+roc_target = { path = "../compiler/roc_target" }
 roc_tracing = { path = "../tracing" }
-bumpalo = { version = "3.11.1", features = ["collections"] }
-target-lexicon = "0.12.3"
-clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions", "derive"] }
-strum = "0.24.0"
-strum_macros = "0.24"
-indexmap = "1.8.1"
-fnv = "1.0.7"
+roc_types = { path = "../compiler/types" }
+
+backtrace.workspace = true
+bumpalo.workspace = true
+fnv.workspace = true
+indexmap.workspace = true
+libc.workspace = true
+libloading.workspace = true
+strum.workspace = true
+strum_macros.workspace = true
+target-lexicon.workspace = true
 
 [dev-dependencies]
-pretty_assertions = "1.3.0"
-tempfile = "3.2.0"
-indoc = "1.0.7"
 cli_utils = { path = "../cli_utils" }
-roc_test_utils = { path = "../test_utils" }
-dircpy = "0.3.13"
+dircpy.workspace = true
+
+indoc.workspace = true
+pretty_assertions.workspace = true
+tempfile.workspace = true
diff --git a/crates/glue/src/RocType.roc b/crates/glue/src/RocType.roc
index cbc6cffa22..eef0bac5a8 100644
--- a/crates/glue/src/RocType.roc
+++ b/crates/glue/src/RocType.roc
@@ -1,17 +1,19 @@
-platform "roc-lang/rbt"
-    requires {} { makeGlue : Str -> Types }
+platform "roc-lang/glue"
+    requires {} { makeGlue : List Types -> Result (List File) Str }
     exposes []
     packages {}
     imports []
     provides [makeGlueForHost]
 
-makeGlueForHost : Str -> Types
-makeGlueForHost = makeGlue
+makeGlueForHost : List Types -> Result (List File) Str
+makeGlueForHost = \x -> makeGlue x
+
+File : { name : Str, content : Str }
 
 # TODO move into separate Target.roc interface once glue works across interfaces.
 Target : {
-    architecture: Architecture,
-    operatingSystem: OperatingSystem,
+    architecture : Architecture,
+    operatingSystem : OperatingSystem,
 }
 
 Architecture : [
@@ -28,22 +30,36 @@ OperatingSystem : [
     Wasi,
 ]
 
-TypeId := Nat
+# TODO change this to an opaque type once glue supports abilities.
+TypeId : Nat
+#      has [
+#          Eq {
+#              isEq: isEqTypeId,
+#          },
+#          Hash {
+#              hash: hashTypeId,
+#          }
+#      ]
+# isEqTypeId = \@TypeId lhs, @TypeId rhs -> lhs == rhs
+# hashTypeId = \hasher, @TypeId id -> Hash.hash hasher id
+# TODO: switch AssocList uses to Dict once roc_std is updated.
+Tuple1 : [T Str TypeId]
+Tuple2 : [T TypeId (List TypeId)]
 
-Types := {
+Types : {
     # These are all indexed by TypeId
-    types: List RocType,
-    sizes: List U32,
-    aligns: List U32,
+    types : List RocType,
+    sizes : List U32,
+    aligns : List U32,
 
     # Needed to check for duplicates
-    typesByName: Dict Str TypeId,
+    typesByName : List Tuple1,
 
     ## 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: Dict TypeId (List TypeId),
-    target: Target,
+    deps : List Tuple2,
+    target : Target,
 }
 
 RocType : [
@@ -57,25 +73,24 @@ RocType : [
     RocBox TypeId,
     TagUnion RocTagUnion,
     EmptyTagUnion,
-    Struct {
-        name: Str,
-        fields: List { name: Str, type: TypeId }
-    },
-    TagUnionPayload {
-        name: Str,
-        fields: List { discriminant: Nat, type: TypeId },
-    },
+    Struct
+        {
+            name : Str,
+            fields : RocStructFields,
+        },
+    TagUnionPayload
+        {
+            name : Str,
+            fields : RocStructFields,
+        },
     ## A recursive pointer, e.g. in StrConsList : [Nil, Cons Str StrConsList],
     ## this would be the field of Cons containing the (recursive) StrConsList type,
     ## and the TypeId is the TypeId of StrConsList itself.
     RecursivePointer TypeId,
-    Function {
-        name: Str,
-        args: List TypeId,
-        ret: TypeId,
-    },
+    Function RocFn,
     # A zero-sized type, such as an empty record or a single-tag union with no payload
     Unit,
+    Unsized,
 ]
 
 RocNum : [
@@ -95,61 +110,86 @@ RocNum : [
 ]
 
 RocTagUnion : [
-    Enumeration {
-        name: Str,
-        tags: List Str,
-        size: U32,
-    },
+    Enumeration
+        {
+            name : Str,
+            tags : List Str,
+            size : U32,
+        },
     ## A non-recursive tag union
     ## e.g. `Result a e : [Ok a, Err e]`
-    NonRecursive {
-        name: Str,
-        tags: List { name : Str, payload : [Some TypeId, None] },
-        discriminantSize: U32,
-        discriminantOffset: U32,
-    },
+    NonRecursive
+        {
+            name : Str,
+            tags : List { name : Str, payload : [Some TypeId, None] },
+            discriminantSize : U32,
+            discriminantOffset : U32,
+        },
     ## A recursive tag union (general case)
     ## e.g. `Expr : [Sym Str, Add Expr Expr]`
-    Recursive {
-        name: Str,
-        tags: List { name : Str, payload : [Some TypeId, None] },
-        discriminantSize: U32,
-        discriminantOffset: U32,
-    },
+    Recursive
+        {
+            name : Str,
+            tags : List { name : Str, payload : [Some TypeId, None] },
+            discriminantSize : U32,
+            discriminantOffset : U32,
+        },
     ## A recursive tag union that has an empty variant
     ## Optimization: Represent the empty variant as null pointer => no memory usage & fast comparison
     ## It has more than one other variant, so they need tag IDs (payloads are "wrapped")
     ## e.g. `FingerTree a : [Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a)]`
     ## see also: https://youtu.be/ip92VMpf_-A?t=164
-    NullableWrapped {
-        name: Str,
-        indexOfNullTag: U16,
-        tags: List { name : Str, payload : [Some TypeId, None] },
-        discriminantSize: U32,
-        discriminantOffset: U32,
-    },
+    NullableWrapped
+        {
+            name : Str,
+            indexOfNullTag : U16,
+            tags : List { name : Str, payload : [Some TypeId, None] },
+            discriminantSize : U32,
+            discriminantOffset : U32,
+        },
     ## Optimization: No need to store a tag ID (the payload is "unwrapped")
     ## e.g. `RoseTree a : [Tree a (List (RoseTree a))]`
-    NonNullableUnwrapped {
-        name: Str,
-        tagName: Str,
-        payload: TypeId, # These always have a payload.
-    },
+    NonNullableUnwrapped
+        {
+            name : Str,
+            tagName : Str,
+            payload : TypeId, # These always have a payload.
+        },
     ## Optimization: No need to store a tag ID (the payload is "unwrapped")
     ## e.g. `[Foo Str Bool]`
-    SingleTagStruct {
-        name: Str,
-        tagName: Str,
-        payloadFields: List TypeId,
-    },
+    SingleTagStruct
+        {
+            name : Str,
+            tagName : Str,
+            payload: RocSingleTagPayload,
+        },
     ## A recursive tag union with only two variants, where one is empty.
     ## Optimizations: Use null for the empty variant AND don't store a tag ID for the other variant.
     ## e.g. `ConsList a : [Nil, Cons a (ConsList a)]`
-    NullableUnwrapped {
-        name: Str,
-        nullTag: Str,
-        nonNullTag: Str,
-        nonNullPayload: TypeId,
-        whichTagIsNull: [FirstTagIsNull, SecondTagIsNull],
-    },
+    NullableUnwrapped
+        {
+            name : Str,
+            nullTag : Str,
+            nonNullTag : Str,
+            nonNullPayload : TypeId,
+            whichTagIsNull : [FirstTagIsNull, SecondTagIsNull],
+        },
 ]
+
+RocStructFields : [
+    HasNoClosure (List { name: Str, id: TypeId }),
+    HasClosure (List { name: Str, id: TypeId, accessors: { getter: Str } }),
+]
+
+RocSingleTagPayload: [
+    HasClosure (List { name: Str, id: TypeId }),
+    HasNoClosure (List { id: TypeId }),
+]
+
+RocFn : {
+    functionName: Str,
+    externName: Str,
+    args: List TypeId,
+    lambdaSet: TypeId,
+    ret: TypeId,
+}
diff --git a/crates/glue/src/RustGlue.roc b/crates/glue/src/RustGlue.roc
new file mode 100644
index 0000000000..cab6b65541
--- /dev/null
+++ b/crates/glue/src/RustGlue.roc
@@ -0,0 +1,944 @@
+app "rust-glue"
+    packages { pf: "RocType.roc" }
+    imports []
+    provides [makeGlue] to pf
+
+makeGlue = \types ->
+    modFileContent =
+        List.walk types "" \content, { target } ->
+            archStr = archName target.architecture
+
+            Str.concat
+                content
+                """
+                #[cfg(target_arch = "\(archStr)")]
+                mod \(archStr);
+                #[cfg(target_arch = "\(archStr)")]
+                pub use \(archStr)::*;
+                
+                """
+
+    types
+    |> List.map typesWithDict
+    |> List.map convertTypesToFile
+    |> List.append { name: "mod.rs", content: modFileContent }
+    |> Ok
+
+convertTypesToFile = \types ->
+    content =
+        walkWithIndex types.types fileHeader \buf, id, type ->
+            when type is
+                Struct { name, fields } ->
+                    generateStruct buf types id name fields Public
+
+                TagUnionPayload { name, fields } ->
+                    generateStruct buf types id name (nameTagUnionPayloadFields fields) Private
+
+                TagUnion (Enumeration { name, tags, size }) ->
+                    generateEnumeration buf types type name tags size
+
+                TagUnion (NonRecursive { name, tags, discriminantSize, discriminantOffset }) ->
+                    if !(List.isEmpty tags) then
+                        generateNonRecursiveTagUnion buf types id name tags discriminantSize discriminantOffset None
+                    else
+                        buf
+
+                TagUnion (Recursive { name, tags, discriminantSize, discriminantOffset }) ->
+                    if !(List.isEmpty tags) then
+                        generateRecursiveTagUnion buf types id name tags discriminantSize discriminantOffset None
+                    else
+                        buf
+
+                TagUnion (NullableWrapped { name, indexOfNullTag, tags, discriminantSize, discriminantOffset }) ->
+                    generateRecursiveTagUnion buf types id name tags discriminantSize discriminantOffset (Some indexOfNullTag)
+
+                TagUnion (NullableUnwrapped { name, nullTag, nonNullTag, nonNullPayload, whichTagIsNull }) ->
+                    generateNullableUnwrapped buf types id name nullTag nonNullTag nonNullPayload whichTagIsNull
+
+                TagUnion (SingleTagStruct { name, tagName, payload }) ->
+                    generateSingleTagStruct buf types name tagName payload
+
+                TagUnion (NonNullableUnwrapped { name, tagName, payload }) ->
+                    generateRecursiveTagUnion buf types id name [{ name: tagName, payload: Some payload }] 0 0 None
+
+                Function _ ->
+                    # TODO: actually generate glue functions.
+                    buf
+
+                RecursivePointer _ ->
+                    # This is recursively pointing to a type that should already have been added,
+                    # so no extra work needs to happen.
+                    buf
+
+                Unit
+                | Unsized
+                | EmptyTagUnion
+                | Num _
+                | Bool
+                | RocResult _ _
+                | RocStr
+                | RocDict _ _
+                | RocSet _
+                | RocList _
+                | RocBox _ ->
+                    # These types don't need to be declared in Rust.
+                    # TODO: Eventually we want to generate roc_std. So these types will need to be emitted.
+                    buf
+    archStr = archName types.target.architecture
+
+    {
+        name: "\(archStr).rs",
+        content,
+    }
+
+generateStruct = \buf, types, id, name, structFields, visibility ->
+    escapedName = escapeKW name
+    repr =
+        length =
+            when structFields is
+                HasClosure fields -> List.len fields
+                HasNoClosure fields -> List.len fields
+        if length <= 1 then
+            "transparent"
+        else
+            "C"
+
+    pub =
+        when visibility is
+            Public -> "pub"
+            Private -> ""
+
+    structType = getType types id
+
+    buf
+    |> generateDeriveStr types structType IncludeDebug
+    |> Str.concat "#[repr(\(repr))]\n\(pub) struct \(escapedName) {\n"
+    |> generateStructFields types Public structFields
+    |> Str.concat "}\n\n"
+
+generateStructFields = \buf, types, visibility, structFields ->
+    when structFields is
+        HasNoClosure fields ->
+            List.walk fields buf (generateStructFieldWithoutClosure types visibility)
+        HasClosure _ ->
+            Str.concat buf "// TODO: Struct fields with closures"
+
+generateStructFieldWithoutClosure = \types, visibility ->
+    \accum, { name: fieldName, id } ->
+        typeStr = typeName types id
+        escapedFieldName = escapeKW fieldName
+
+        pub =
+            when visibility is
+                Public -> "pub"
+                Private -> ""
+
+        Str.concat accum "\(indent)\(pub) \(escapedFieldName): \(typeStr),\n"
+
+nameTagUnionPayloadFields = \payloadFields ->
+    # Tag union payloads have numbered fields, so we prefix them
+    # with an "f" because Rust doesn't allow struct fields to be numbers.
+    when payloadFields is
+        HasNoClosure fields ->
+            renamedFields = List.map fields \{ name, id } -> { name: "f\(name)", id }
+            HasNoClosure renamedFields
+        HasClosure fields ->
+            renamedFields = List.map fields \{ name, id, accessors } -> { name: "f\(name)", id, accessors }
+            HasClosure renamedFields
+
+generateEnumeration = \buf, types, enumType, name, tags, tagBytes ->
+    escapedName = escapeKW name
+
+    reprBits = tagBytes * 8 |> Num.toStr
+
+    buf
+    |> generateDeriveStr types enumType ExcludeDebug
+    |> Str.concat "#[repr(u\(reprBits))]\npub enum \(escapedName) {\n"
+    |> \b -> walkWithIndex tags b generateEnumTags
+    |>
+    # Enums require a custom debug impl to ensure naming is identical on all platforms.
+    Str.concat
+        """
+        }
+
+        impl core::fmt::Debug for \(escapedName) {
+            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+                match self {
+        
+        """
+    |> \b -> List.walk tags b (generateEnumTagsDebug name)
+    |> Str.concat "\(indent)\(indent)}\n\(indent)}\n}\n\n"
+
+generateEnumTags = \accum, index, name ->
+    indexStr = Num.toStr index
+
+    Str.concat accum "\(indent)\(name) = \(indexStr),\n"
+
+generateEnumTagsDebug = \name ->
+    \accum, tagName ->
+        Str.concat accum "\(indent)\(indent)\(indent)Self::\(tagName) => f.write_str(\"\(name)::\(tagName)\"),\n"
+
+generateNonRecursiveTagUnion = \buf, types, id, name, tags, discriminantSize, discriminantOffset, _nullTagIndex ->
+    escapedName = escapeKW name
+    discriminantName = "discriminant_\(escapedName)"
+    discriminantOffsetStr = Num.toStr discriminantOffset
+    tagNames = List.map tags \{ name: n } -> n
+    # self = "self"
+    selfMut = "self"
+    # other = "other"
+    unionName = escapedName
+
+    buf
+    |> generateDiscriminant types discriminantName tagNames discriminantSize
+    |> Str.concat "#[repr(C)]\npub union \(unionName) {\n"
+    |> \b -> List.walk tags b (generateUnionField types)
+    |> generateTagUnionSizer types id tags
+    |> Str.concat
+        """
+        }
+
+        impl \(escapedName) {
+            \(discriminantDocComment)
+            pub fn discriminant(&self) -> \(discriminantName) {
+                unsafe {
+                    let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+                    core::mem::transmute::(*bytes.as_ptr().add(\(discriminantOffsetStr)))
+                }
+            }
+
+            /// Internal helper
+            fn set_discriminant(&mut self, discriminant: \(discriminantName)) {
+                let discriminant_ptr: *mut \(discriminantName) = (self as *mut \(escapedName)).cast();
+
+                unsafe {
+                    *(discriminant_ptr.add(\(discriminantOffsetStr))) = discriminant;
+                }
+            }
+        }
+
+        
+        """
+    |> Str.concat "// TODO: NonRecursive TagUnion constructor impls\n\n"
+    |> \b ->
+        type = getType types id
+        if cannotDeriveCopy types type then
+            # A custom drop impl is only needed when we can't derive copy.
+            b
+            |> Str.concat
+                """
+                impl Drop for \(escapedName) {
+                    fn drop(&mut self) {
+                        // Drop the payloads
+                
+                """
+            |> generateTagUnionDropPayload types selfMut tags discriminantName discriminantSize 2
+            |> Str.concat
+                """
+                    }
+                }
+
+                
+                """
+        else
+            b
+
+generateRecursiveTagUnion = \buf, types, id, name, tags, discriminantSize, _discriminantOffset, _nullTagIndex ->
+    escapedName = escapeKW name
+    discriminantName = "discriminant_\(escapedName)"
+    tagNames = List.map tags \{ name: n } -> n
+    # self = "(&*self.union_pointer())"
+    # selfMut = "(&mut *self.union_pointer())"
+    # other = "(&*other.union_pointer())"
+    unionName = "union_\(escapedName)"
+
+    buf
+    |> generateDiscriminant types discriminantName tagNames discriminantSize
+    |> Str.concat
+        """
+        #[repr(transparent)]
+        pub struct \(escapedName) {
+            pointer: *mut \(unionName),
+        }
+
+        #[repr(C)]
+        union \(unionName) {
+        """
+    |> \b -> List.walk tags b (generateUnionField types)
+    |> generateTagUnionSizer types id tags
+    |> Str.concat "}\n\n"
+    |> Str.concat "// TODO: Recursive TagUnion impls\n\n"
+
+generateTagUnionDropPayload = \buf, types, selfMut, tags, discriminantName, discriminantSize, indents ->
+    if discriminantSize == 0 then
+        when List.first tags is
+            Ok { name } ->
+                # There's only one tag, so there's no discriminant and no need to match;
+                # just drop the pointer.
+                buf
+                |> writeIndents indents
+                |> Str.concat "unsafe { core::mem::ManuallyDrop::drop(&mut core::ptr::read(self.pointer).\(name)); }"
+
+            Err ListWasEmpty ->
+                crash "unreachable"
+    else
+        buf
+        |> writeTagImpls tags discriminantName indents \name, payload ->
+            when payload is
+                Some id if cannotDeriveCopy types (getType types id) ->
+                    "unsafe {{ core::mem::ManuallyDrop::drop(&mut \(selfMut).\(name)) }},"
+
+                _ ->
+                    # If it had no payload, or if the payload had no pointers,
+                    # there's nothing to clean up, so do `=> {}` for the branch.
+                    "{}"
+
+writeIndents = \buf, indents ->
+    if indents <= 0 then
+        buf
+    else
+        buf
+        |> Str.concat indent
+        |> writeIndents (indents - 1)
+
+writeTagImpls = \buf, tags, discriminantName, indents, f ->
+    buf
+    |> writeIndents indents
+    |> Str.concat "match self.discriminant() {\n"
+    |> \b -> List.walk tags b \accum, { name, payload } ->
+            branchStr = f name payload
+            accum
+            |> writeIndents (indents + 1)
+            |> Str.concat "\(discriminantName)::\(name) => \(branchStr)\n"
+    |> writeIndents indents
+    |> Str.concat "}\n"
+
+generateTagUnionSizer = \buf, types, id, tags ->
+    if List.len tags > 1 then
+        # When there's a discriminant (so, multiple tags) and there is
+        # no alignment padding after the largest variant,
+        # the compiler will make extra room for the discriminant.
+        # We need that to be reflected in the overall size of the enum,
+        # so add an extra variant with the appropriate size.
+        #
+        # (Do this even if theoretically shouldn't be necessary, since
+        # there's no runtime cost and it more explicitly syncs the
+        # union's size with what we think it should be.)
+        size = getSizeRoundedToAlignment types id
+        sizeStr = Num.toStr size
+
+        Str.concat buf "\(indent)_sizer: [u8; \(sizeStr)],\n"
+    else
+        buf
+
+generateDiscriminant = \buf, types, name, tags, size ->
+    if size > 0 then
+        enumType =
+            TagUnion
+                (
+                    Enumeration {
+                        name,
+                        tags,
+                        size,
+                    }
+                )
+
+        buf
+        |> generateEnumeration types enumType name tags size
+    else
+        buf
+
+generateUnionField = \types ->
+    \accum, { name: fieldName, payload } ->
+        when payload is
+            Some id ->
+                typeStr = typeName types id
+                escapedFieldName = escapeKW fieldName
+
+                type = getType types id
+                fullTypeStr =
+                    if cannotDeriveCopy types type then
+                        # types with pointers need ManuallyDrop
+                        # because rust unions don't (and can't)
+                        # know how to drop them automatically!
+                        "core::mem::ManuallyDrop<\(typeStr)>"
+                    else
+                        typeStr
+
+                Str.concat accum "\(indent)\(escapedFieldName): \(fullTypeStr),\n"
+
+            None ->
+                # If there's no payload, we don't need a discriminant for it.
+                accum
+
+generateNullableUnwrapped = \buf, _types, _id, _name, _nullTag, _nonNullTag, _nonNullPayload, _whichTagIsNull ->
+    Str.concat buf "// TODO: TagUnion NullableUnwrapped\n\n"
+
+generateSingleTagStruct = \buf, types, name, tagName, payload ->
+    # Store single-tag unions as structs rather than enums,
+    # because they have only one alternative. However, still
+    # offer the usual tag union APIs.
+    escapedName = escapeKW name
+    repr =
+        length =
+            when payload is
+                HasClosure fields -> List.len fields
+                HasNoClosure fields -> List.len fields
+        if length <= 1 then
+            "transparent"
+        else
+            "C"
+
+    when payload is
+        HasNoClosure fields ->
+            asStructFields =
+                List.mapWithIndex fields \{ id }, index ->
+                    indexStr = Num.toStr index
+
+                    { name: "f\(indexStr)", id }
+                |> HasNoClosure
+            asStructType =
+                Struct {
+                    name,
+                    fields: asStructFields,
+                }
+
+            buf
+            |> generateDeriveStr types asStructType ExcludeDebug
+            |> Str.concat "#[repr(\(repr))]\npub struct \(escapedName) "
+            |> \b ->
+                if List.isEmpty fields then
+                    generateZeroElementSingleTagStruct b escapedName tagName
+                else
+                    generateMultiElementSingleTagStruct b types escapedName tagName fields asStructFields
+        HasClosure _ ->
+            Str.concat buf "\\TODO: SingleTagStruct with closures"
+
+generateMultiElementSingleTagStruct = \buf, types, name, tagName, payloadFields, asStructFields ->
+    buf
+    |> Str.concat "{\n"
+    |> generateStructFields types Private asStructFields
+    |> Str.concat "}\n\n"
+    |> Str.concat
+        """
+        impl \(name) {
+        
+        """
+    |> \b ->
+        fieldTypes =
+            payloadFields
+            |> List.map \{ id } ->
+                typeName types id
+        args =
+            fieldTypes
+            |> List.mapWithIndex \fieldTypeName, index ->
+                indexStr = Num.toStr index
+
+                "f\(indexStr): \(fieldTypeName)"
+        fields =
+            payloadFields
+            |> List.mapWithIndex \_, index ->
+                indexStr = Num.toStr index
+
+                "f\(indexStr)"
+
+        fieldAccesses =
+            fields
+            |> List.map \field ->
+                "self.\(field)"
+
+        {
+            b,
+            args,
+            fields,
+            fieldTypes,
+            fieldAccesses,
+        }
+    |> \{ b, args, fields, fieldTypes, fieldAccesses } ->
+        argsStr = Str.joinWith args ", "
+        fieldsStr = Str.joinWith fields "\n\(indent)\(indent)\(indent)"
+
+        {
+            b: Str.concat
+                b
+                """
+                \(indent)/// A tag named ``\(tagName)``, with the given payload.
+                \(indent)pub fn \(tagName)(\(argsStr)) -> Self {
+                \(indent)    Self {
+                \(indent)        \(fieldsStr)
+                \(indent)    }
+                \(indent)}
+
+                
+                """,
+            fieldTypes,
+            fieldAccesses,
+        }
+    |> \{ b, fieldTypes, fieldAccesses } ->
+        retType = asRustTuple fieldTypes
+        retExpr = asRustTuple fieldAccesses
+
+        {
+            b: Str.concat
+                b
+                """
+                \(indent)/// Since `\(name)` only has one tag (namely, `\(tagName)`),
+                \(indent)/// convert it to `\(tagName)`'s payload.
+                \(indent)pub fn into_\(tagName)(self) -> \(retType) {
+                \(indent)    \(retExpr)
+                \(indent)}
+
+                
+                """,
+            fieldTypes,
+            fieldAccesses,
+        }
+    |> \{ b, fieldTypes, fieldAccesses } ->
+        retType =
+            fieldTypes
+            |> List.map \ft -> "&\(ft)"
+            |> asRustTuple
+        retExpr =
+            fieldAccesses
+            |> List.map \fa -> "&\(fa)"
+            |> asRustTuple
+
+        Str.concat
+            b
+            """
+            \(indent)/// Since `\(name)` only has one tag (namely, `\(tagName)`),
+            \(indent)/// convert it to `\(tagName)`'s payload.
+            \(indent)pub fn as_\(tagName)(&self) -> \(retType) {
+            \(indent)    \(retExpr)
+            \(indent)}
+            
+            """
+    |> Str.concat
+        """
+        }
+
+
+        impl core::fmt::Debug for \(name) {
+            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+                f.debug_tuple("\(name)::\(tagName)")
+        
+        """
+    |> \b ->
+        payloadFields
+        |> List.mapWithIndex \_, index ->
+            indexStr = Num.toStr index
+
+            "\(indent)\(indent)\(indent)\(indent).field(&self.f\(indexStr))\n"
+        |> List.walk b Str.concat
+    |> Str.concat
+        """
+                        .finish()
+            }
+        }
+
+        
+        """
+
+asRustTuple = \list ->
+    # If there is 1 element in the list we just return it
+    # Otherwise, we make a proper tuple string.
+    joined = Str.joinWith list ", "
+
+    if List.len list == 1 then
+        joined
+    else
+        "(\(joined))"
+
+generateZeroElementSingleTagStruct = \buf, name, tagName ->
+    # A single tag with no payload is a zero-sized unit type, so
+    # represent it as a zero-sized struct (e.g. "struct Foo()").
+    buf
+    |> Str.concat "();\n\n"
+    |> Str.concat
+        """
+        impl \(name) {
+            /// A tag named \(tagName), which has no payload.
+            pub const \(tagName): Self = Self();
+
+            /// Other `into_` methods return a payload, but since \(tagName) tag
+            /// has no payload, this does nothing and is only here for completeness.
+            pub fn into_\(tagName)(self) {
+                ()
+            }
+
+            /// Other `as_` methods return a payload, but since \(tagName) tag
+            /// has no payload, this does nothing and is only here for completeness.
+            pub fn as_\(tagName)(&self) {
+                ()
+            }
+        }
+
+        impl core::fmt::Debug for \(name) {
+            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+                f.write_str("\(name)::\(tagName)")
+            }
+        }
+
+        
+        """
+
+generateDeriveStr = \buf, types, type, includeDebug ->
+    buf
+    |> Str.concat "#[derive(Clone, "
+    |> \b ->
+        if !(cannotDeriveCopy types type) then
+            Str.concat b "Copy, "
+        else
+            b
+    |> \b ->
+        if !(cannotDeriveDefault types type) then
+            Str.concat b "Default, "
+        else
+            b
+    |> \b ->
+        when includeDebug is
+            IncludeDebug ->
+                Str.concat b "Debug, "
+
+            ExcludeDebug ->
+                b
+    |> \b ->
+        if !(hasFloat types type) then
+            Str.concat b "Eq, Ord, Hash, "
+        else
+            b
+    |> Str.concat "PartialEq, PartialOrd)]\n"
+
+cannotDeriveCopy = \types, type ->
+    when type is
+        Unit | Unsized | EmptyTagUnion | Bool | Num _ | TagUnion (Enumeration _) | Function _ -> Bool.false
+        RocStr | RocList _ | RocDict _ _ | RocSet _ | RocBox _ | TagUnion (NullableUnwrapped _) | TagUnion (NullableWrapped _) | TagUnion (Recursive _) | TagUnion (NonNullableUnwrapped _) | RecursivePointer _ -> Bool.true
+        TagUnion (SingleTagStruct { payload: HasNoClosure fields }) ->
+            List.any fields \{ id } -> cannotDeriveCopy types (getType types id)
+
+        TagUnion (SingleTagStruct { payload: HasClosure fields }) ->
+            List.any fields \{ id } -> cannotDeriveCopy types (getType types id)
+
+        TagUnion (NonRecursive { tags }) ->
+            List.any tags \{ payload } ->
+                when payload is
+                    Some id -> cannotDeriveCopy types (getType types id)
+                    None -> Bool.false
+
+        RocResult okId errId ->
+            cannotDeriveCopy types (getType types okId)
+            || cannotDeriveCopy types (getType types errId)
+
+        Struct { fields: HasNoClosure fields } | TagUnionPayload { fields: HasNoClosure fields } ->
+            List.any fields \{ id } -> cannotDeriveCopy types (getType types id)
+
+        Struct { fields: HasClosure fields } | TagUnionPayload { fields: HasClosure fields } ->
+            List.any fields \{ id } -> cannotDeriveCopy types (getType types id)
+
+cannotDeriveDefault = \types, type ->
+    when type is
+        Unit | Unsized | EmptyTagUnion | TagUnion _ | RocResult _ _ | RecursivePointer _ | Function _ -> Bool.true
+        RocStr | Bool | Num _ | Struct { fields: HasClosure _ } | TagUnionPayload { fields: HasClosure _ } -> Bool.false
+        RocList id | RocSet id | RocBox id ->
+            cannotDeriveDefault types (getType types id)
+
+        RocDict keyId valId ->
+            cannotDeriveCopy types (getType types keyId)
+            || cannotDeriveCopy types (getType types valId)
+
+        Struct { fields: HasNoClosure fields } | TagUnionPayload { fields: HasNoClosure fields } ->
+            List.any fields \{ id } -> cannotDeriveDefault types (getType types id)
+
+hasFloat = \types, type ->
+    hasFloatHelp types type (Set.empty {})
+
+hasFloatHelp = \types, type, doNotRecurse ->
+    # TODO: is doNotRecurse problematic? Do we need an updated doNotRecurse for calls up the tree?
+    # I think there is a change it really only matters for RecursivePointer, so it may be fine.
+    # Otherwise we need to deal with threading through updates to doNotRecurse
+    when type is
+        Num kind ->
+            when kind is
+                F32 | F64 -> Bool.true
+                _ -> Bool.false
+
+        Unit | Unsized | EmptyTagUnion | RocStr | Bool | TagUnion (Enumeration _) | Function _ -> Bool.false
+        RocList id | RocSet id | RocBox id ->
+            hasFloatHelp types (getType types id) doNotRecurse
+
+        RocDict id0 id1 | RocResult id0 id1 ->
+            hasFloatHelp types (getType types id0) doNotRecurse
+            || hasFloatHelp types (getType types id1) doNotRecurse
+
+        Struct { fields: HasNoClosure fields } | TagUnionPayload { fields: HasNoClosure fields } ->
+            List.any fields \{ id } -> hasFloatHelp types (getType types id) doNotRecurse
+
+        Struct { fields: HasClosure fields } | TagUnionPayload { fields: HasClosure fields } ->
+            List.any fields \{ id } -> hasFloatHelp types (getType types id) doNotRecurse
+
+        TagUnion (SingleTagStruct { payload: HasNoClosure fields }) ->
+            List.any fields \{ id } -> hasFloatHelp types (getType types id) doNotRecurse
+
+        TagUnion (SingleTagStruct { payload: HasClosure fields }) ->
+            List.any fields \{ id } -> hasFloatHelp types (getType types id) doNotRecurse
+
+        TagUnion (Recursive { tags }) ->
+            List.any tags \{ payload } ->
+                when payload is
+                    Some id -> hasFloatHelp types (getType types id) doNotRecurse
+                    None -> Bool.false
+
+        TagUnion (NonRecursive { tags }) ->
+            List.any tags \{ payload } ->
+                when payload is
+                    Some id -> hasFloatHelp types (getType types id) doNotRecurse
+                    None -> Bool.false
+
+        TagUnion (NullableWrapped { tags }) ->
+            List.any tags \{ payload } ->
+                when payload is
+                    Some id -> hasFloatHelp types (getType types id) doNotRecurse
+                    None -> Bool.false
+
+        TagUnion (NonNullableUnwrapped { payload }) ->
+            if Set.contains doNotRecurse payload then
+                Bool.false
+            else
+                nextDoNotRecurse = Set.insert doNotRecurse payload
+
+                hasFloatHelp types (getType types payload) nextDoNotRecurse
+
+        TagUnion (NullableUnwrapped { nonNullPayload }) ->
+            if Set.contains doNotRecurse nonNullPayload then
+                Bool.false
+            else
+                nextDoNotRecurse = Set.insert doNotRecurse nonNullPayload
+
+                hasFloatHelp types (getType types nonNullPayload) nextDoNotRecurse
+
+        RecursivePointer payload ->
+            if Set.contains doNotRecurse payload then
+                Bool.false
+            else
+                nextDoNotRecurse = Set.insert doNotRecurse payload
+
+                hasFloatHelp types (getType types payload) nextDoNotRecurse
+
+typeName = \types, id ->
+    when getType types id is
+        Unit -> "()"
+        Unsized -> "roc_std::RocList"
+        EmptyTagUnion -> "std::convert::Infallible"
+        RocStr -> "roc_std::RocStr"
+        Bool -> "bool"
+        Num U8 -> "u8"
+        Num U16 -> "u16"
+        Num U32 -> "u32"
+        Num U64 -> "u64"
+        Num U128 -> "u128"
+        Num I8 -> "i8"
+        Num I16 -> "i16"
+        Num I32 -> "i32"
+        Num I64 -> "i64"
+        Num I128 -> "i128"
+        Num F32 -> "f32"
+        Num F64 -> "f64"
+        Num Dec -> "roc_std:RocDec"
+        RocDict key value ->
+            keyName = typeName types key
+            valueName = typeName types value
+
+            "roc_std::RocDict<\(keyName), \(valueName)>"
+
+        RocSet elem ->
+            elemName = typeName types elem
+
+            "roc_std::RocSet<\(elemName)>"
+
+        RocList elem ->
+            elemName = typeName types elem
+
+            "roc_std::RocList<\(elemName)>"
+
+        RocBox elem ->
+            elemName = typeName types elem
+
+            "roc_std::RocBox<\(elemName)>"
+
+        RocResult ok err ->
+            okName = typeName types ok
+            errName = typeName types err
+
+            "roc_std::RocResult<\(okName), \(errName)>"
+
+        RecursivePointer content ->
+            typeName types content
+
+        Struct { name } -> escapeKW name
+        TagUnionPayload { name } -> escapeKW name
+        TagUnion (NonRecursive { name }) -> escapeKW name
+        TagUnion (Recursive { name }) -> escapeKW name
+        TagUnion (Enumeration { name }) -> escapeKW name
+        TagUnion (NullableWrapped { name }) -> escapeKW name
+        TagUnion (NullableUnwrapped { name }) -> escapeKW name
+        TagUnion (NonNullableUnwrapped { name }) -> escapeKW name
+        TagUnion (SingleTagStruct { name }) -> escapeKW name
+        Function { functionName } -> escapeKW functionName
+
+getType = \types, id ->
+    when List.get types.types id is
+        Ok type -> type
+        Err _ -> crash "unreachable"
+
+getSizeRoundedToAlignment = \types, id ->
+    alignment = getAlignment types id
+
+    getSizeIgnoringAlignment types id
+    |> roundUpToAlignment alignment
+
+getSizeIgnoringAlignment = \types, id ->
+    when List.get types.sizes id is
+        Ok size -> size
+        Err _ -> crash "unreachable"
+
+getAlignment = \types, id ->
+    when List.get types.aligns id is
+        Ok align -> align
+        Err _ -> crash "unreachable"
+
+roundUpToAlignment = \width, alignment ->
+    when alignment is
+        0 -> width
+        1 -> width
+        _ ->
+            if width % alignment > 0 then
+                width + alignment - (width % alignment)
+            else
+                width
+
+walkWithIndex = \list, originalState, f ->
+    stateWithId =
+        List.walk list { id: 0nat, state: originalState } \{ id, state }, elem ->
+            nextState = f state id elem
+
+            { id: id + 1, state: nextState }
+
+    stateWithId.state
+
+archName = \arch ->
+    when arch is
+        Aarch32 ->
+            "arm"
+
+        Aarch64 ->
+            "aarch64"
+
+        Wasm32 ->
+            "wasm32"
+
+        X86x32 ->
+            "x86"
+
+        X86x64 ->
+            "x86_64"
+
+fileHeader =
+    """
+    // ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
+
+    #![allow(unused_unsafe)]
+    #![allow(dead_code)]
+    #![allow(unused_mut)]
+    #![allow(non_snake_case)]
+    #![allow(non_camel_case_types)]
+    #![allow(non_upper_case_globals)]
+    #![allow(clippy::undocumented_unsafe_blocks)]
+    #![allow(clippy::redundant_static_lifetimes)]
+    #![allow(clippy::unused_unit)]
+    #![allow(clippy::missing_safety_doc)]
+    #![allow(clippy::let_and_return)]
+    #![allow(clippy::missing_safety_doc)]
+    #![allow(clippy::redundant_static_lifetimes)]
+    #![allow(clippy::needless_borrow)]
+    #![allow(clippy::clone_on_copy)]
+
+
+    
+    """
+
+indent = "    "
+discriminantDocComment = "/// Returns which variant this tag union holds. Note that this never includes a payload!"
+
+reservedKeywords = Set.fromList [
+    "try",
+    "abstract",
+    "become",
+    "box",
+    "do",
+    "final",
+    "macro",
+    "override",
+    "priv",
+    "typeof",
+    "unsized",
+    "virtual",
+    "yield",
+    "async",
+    "await",
+    "dyn",
+    "as",
+    "break",
+    "const",
+    "continue",
+    "crate",
+    "else",
+    "enum",
+    "extern",
+    "false",
+    "fn",
+    "for",
+    "if",
+    "impl",
+    "in",
+    "let",
+    "loop",
+    "match",
+    "mod",
+    "move",
+    "mut",
+    "pub",
+    "ref",
+    "return",
+    "self",
+    "Self",
+    "static",
+    "struct",
+    "super",
+    "trait",
+    "true",
+    "type",
+    "unsafe",
+    "use",
+    "where",
+    "while",
+]
+
+escapeKW = \input ->
+    # use a raw identifier for this, to prevent a syntax error due to using a reserved keyword.
+    # https://doc.rust-lang.org/rust-by-example/compatibility/raw_identifiers.html
+    # another design would be to add an underscore after it; this is an experiment!
+    if Set.contains reservedKeywords input then
+        "r#\(input)"
+    else
+        input
+
+# This is a temporary helper until roc_std::roc_dict is update.
+# after that point, Dict will be passed in directly.
+typesWithDict = \{ types, sizes, aligns, typesByName, deps, target } -> {
+    types,
+    sizes,
+    aligns,
+    typesByName: Dict.fromList typesByName,
+    deps: Dict.fromList deps,
+    target,
+}
diff --git a/crates/glue/src/lib.rs b/crates/glue/src/lib.rs
index ade8a7074c..f1d0fbeac2 100644
--- a/crates/glue/src/lib.rs
+++ b/crates/glue/src/lib.rs
@@ -4,6 +4,8 @@
 //! the plan is to support any language via a plugin model.
 pub mod enums;
 pub mod load;
+pub mod roc_helpers;
+pub mod roc_type;
 pub mod rust_glue;
 pub mod structs;
 pub mod types;
diff --git a/crates/glue/src/load.rs b/crates/glue/src/load.rs
index 1cb0108239..7b578fa111 100644
--- a/crates/glue/src/load.rs
+++ b/crates/glue/src/load.rs
@@ -1,14 +1,27 @@
-use crate::rust_glue;
-use crate::types::{Env, Types};
+use crate::roc_type;
+use crate::types::Types;
 use bumpalo::Bump;
+use libloading::Library;
+use roc_build::{
+    link::{LinkType, LinkingStrategy},
+    program::{
+        build_file, handle_error_module, handle_loading_problem, standard_load_config,
+        BuildFileError, BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions,
+    },
+};
+use roc_collections::MutMap;
+use roc_gen_llvm::llvm::build::LlvmBackendMode;
 use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
-use roc_mono::layout::GlobalLayoutInterner;
+use roc_mono::ir::{generate_glue_procs, GlueProc, OptLevel};
+use roc_mono::layout::{GlobalLayoutInterner, LayoutCache, LayoutInterner};
 use roc_packaging::cache::{self, RocCacheDir};
 use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
-use roc_target::{Architecture, OperatingSystem, TargetInfo};
+use roc_target::{Architecture, TargetInfo};
+use roc_types::subs::{Subs, Variable};
 use std::fs::File;
 use std::io::{self, ErrorKind, Write};
-use std::path::{Path, PathBuf};
+use std::mem::ManuallyDrop;
+use std::path::{Component, Path, PathBuf};
 use std::process;
 use strum::IntoEnumIterator;
 use target_lexicon::Triple;
@@ -21,44 +34,165 @@ impl IgnoreErrors {
     const NONE: Self = IgnoreErrors { can: false };
 }
 
-pub fn generate(input_path: &Path, output_path: &Path) -> io::Result {
+pub fn generate(input_path: &Path, output_path: &Path, spec_path: &Path) -> io::Result {
+    // TODO: Add verification around the paths. Make sure they heav the correct file extension and what not.
     match load_types(
         input_path.to_path_buf(),
         Threading::AllAvailable,
         IgnoreErrors::NONE,
     ) {
-        Ok(types_and_targets) => {
-            let mut file = File::create(output_path).unwrap_or_else(|err| {
-                eprintln!(
-                    "Unable to create output file {} - {:?}",
-                    output_path.display(),
-                    err
-                );
+        Ok(types) => {
+            // TODO: we should to modify the app file first before loading it.
+            // Somehow it has to point to the correct platform file which may not exist on the target machine.
+            let triple = Triple::host();
 
-                process::exit(1);
-            });
+            let code_gen_options = CodeGenOptions {
+                // Maybe eventually use dev here or add flag for this.
+                backend: CodeGenBackend::Llvm(LlvmBackendMode::BinaryGlue),
+                opt_level: OptLevel::Development,
+                emit_debug_info: false,
+            };
 
-            let mut buf = std::str::from_utf8(rust_glue::HEADER).unwrap().to_string();
-            let body = rust_glue::emit(&types_and_targets);
-
-            buf.push_str(&body);
-
-            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()
+            let load_config = standard_load_config(
+                &triple,
+                BuildOrdering::BuildIfChecks,
+                Threading::AllAvailable,
             );
 
-            Ok(0)
+            let arena = ManuallyDrop::new(Bump::new());
+            let link_type = LinkType::Dylib;
+            let linking_strategy = if roc_linker::supported(link_type, &triple) {
+                LinkingStrategy::Surgical
+            } else {
+                LinkingStrategy::Legacy
+            };
+
+            let res_binary_path = build_file(
+                &arena,
+                &triple,
+                spec_path.to_path_buf(),
+                code_gen_options,
+                false,
+                link_type,
+                linking_strategy,
+                true,
+                None,
+                RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
+                load_config,
+            );
+
+            match res_binary_path {
+                Ok(BuiltFile {
+                    binary_path,
+                    problems,
+                    total_time,
+                    expect_metadata: _,
+                }) => {
+                    // TODO: Should binary_path be update to deal with extensions?
+                    use target_lexicon::OperatingSystem;
+                    let lib_path = match triple.operating_system {
+                        OperatingSystem::Windows => binary_path.with_extension("dll"),
+                        OperatingSystem::Darwin | OperatingSystem::MacOSX { .. } => {
+                            binary_path.with_extension("dylib")
+                        }
+
+                        _ => binary_path.with_extension("so.1.0"),
+                    };
+
+                    // TODO: Should glue try and run with errors, especially type errors.
+                    // My gut feeling is no or that we should add a flag for it.
+                    // Given glue will generally be run by random end users, I think it should default to full correctness.
+                    debug_assert_eq!(
+                        problems.errors, 0,
+                        "if there are errors, they should have been returned as an error variant"
+                    );
+                    if problems.warnings > 0 {
+                        problems.print_to_stdout(total_time);
+                        println!(
+                            ".\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
+                            "─".repeat(80)
+                        );
+                    }
+
+                    let lib = unsafe { Library::new(lib_path) }.unwrap();
+                    type MakeGlue = unsafe extern "C" fn(
+                        *mut roc_std::RocResult, roc_std::RocStr>,
+                        &roc_std::RocList,
+                    );
+
+                    let make_glue: libloading::Symbol = unsafe {
+                        lib.get("roc__makeGlueForHost_1_exposed_generic".as_bytes())
+                            .unwrap_or_else(|_| panic!("Unable to load glue function"))
+                    };
+                    let roc_types: roc_std::RocList =
+                        types.iter().map(|x| x.into()).collect();
+                    let mut files = roc_std::RocResult::err(roc_std::RocStr::empty());
+                    unsafe { make_glue(&mut files, &roc_types) };
+
+                    // Roc will free data passed into it. So forget that data.
+                    std::mem::forget(roc_types);
+
+                    let files: Result, roc_std::RocStr> =
+                        files.into();
+                    let files = files.unwrap_or_else(|err| {
+                        eprintln!("Glue generation failed: {}", err);
+
+                        process::exit(1);
+                    });
+                    for roc_type::File { name, content } in &files {
+                        let valid_name = PathBuf::from(name.as_str())
+                            .components()
+                            .all(|comp| matches!(comp, Component::CurDir | Component::Normal(_)));
+                        if !valid_name {
+                            eprintln!("File name was invalid: {}", &name);
+
+                            process::exit(1);
+                        }
+                        let full_path = output_path.join(name.as_str());
+                        if let Some(dir_path) = full_path.parent() {
+                            std::fs::create_dir_all(dir_path).unwrap_or_else(|err| {
+                                eprintln!(
+                                    "Unable to create output directory {} - {:?}",
+                                    dir_path.display(),
+                                    err
+                                );
+
+                                process::exit(1);
+                            });
+                        }
+                        let mut file = File::create(&full_path).unwrap_or_else(|err| {
+                            eprintln!(
+                                "Unable to create output file {} - {:?}",
+                                full_path.display(),
+                                err
+                            );
+
+                            process::exit(1);
+                        });
+
+                        file.write_all(content.as_bytes()).unwrap_or_else(|err| {
+                            eprintln!(
+                                "Unable to write bindings to output file {} - {:?}",
+                                full_path.display(),
+                                err
+                            );
+
+                            process::exit(1);
+                        });
+                    }
+
+                    println!(
+                        "🎉 Generated type declarations in:\n\n\t{}",
+                        output_path.display()
+                    );
+
+                    Ok(0)
+                }
+                Err(BuildFileError::ErrorModule { module, total_time }) => {
+                    handle_error_module(module, total_time, spec_path.as_os_str(), true)
+                }
+                Err(BuildFileError::LoadingProblem(problem)) => handle_loading_problem(problem),
+            }
         }
         Err(err) => match err.kind() {
             ErrorKind::NotFound => {
@@ -77,11 +211,125 @@ pub fn generate(input_path: &Path, output_path: &Path) -> io::Result {
     }
 }
 
+fn number_lambda_sets(subs: &Subs, initial: Variable) -> Vec {
+    let mut lambda_sets = vec![];
+    let mut stack = vec![initial];
+
+    macro_rules! var_slice {
+        ($variable_subs_slice:expr) => {{
+            let slice = $variable_subs_slice;
+            subs.variables[slice.indices()].iter().rev()
+        }};
+    }
+
+    while let Some(var) = stack.pop() {
+        use roc_types::subs::Content::*;
+        use roc_types::subs::FlatType::*;
+
+        use roc_types::subs::GetSubsSlice;
+        use roc_types::types::Uls;
+
+        match subs.get_content_without_compacting(var) {
+            RigidVar(_) | RigidAbleVar(_, _) | FlexVar(_) | FlexAbleVar(_, _) | Error => (),
+
+            RecursionVar { .. } => {
+                // we got here, so we've treated this type already
+            }
+
+            Structure(flat_type) => match flat_type {
+                Apply(_, args) => {
+                    stack.extend(var_slice!(*args));
+                }
+
+                Func(arg_vars, closure_var, ret_var) => {
+                    lambda_sets.push(subs.get_root_key_without_compacting(*closure_var));
+
+                    stack.push(*ret_var);
+                    stack.push(*closure_var);
+                    stack.extend(var_slice!(arg_vars));
+                }
+
+                EmptyRecord => (),
+                EmptyTagUnion => (),
+                EmptyTuple => (),
+
+                Record(fields, ext) => {
+                    let fields = *fields;
+                    let ext = *ext;
+
+                    stack.push(ext);
+                    stack.extend(var_slice!(fields.variables()));
+                }
+                Tuple(_, _) => todo!(),
+                TagUnion(tags, ext) => {
+                    let tags = *tags;
+                    let ext = *ext;
+
+                    stack.push(ext.var());
+
+                    for slice_index in tags.variables() {
+                        let slice = subs.variable_slices[slice_index.index as usize];
+                        stack.extend(var_slice!(slice));
+                    }
+                }
+                FunctionOrTagUnion(_, _, ext) => {
+                    stack.push(ext.var());
+                }
+
+                RecursiveTagUnion(rec_var, tags, ext) => {
+                    let tags = *tags;
+                    let ext = *ext;
+                    let rec_var = *rec_var;
+
+                    stack.push(ext.var());
+
+                    for slice_index in tags.variables() {
+                        let slice = subs.variable_slices[slice_index.index as usize];
+                        stack.extend(var_slice!(slice));
+                    }
+
+                    stack.push(rec_var);
+                }
+            },
+            Alias(_, args, var, _) => {
+                let var = *var;
+                let args = *args;
+
+                stack.extend(var_slice!(args.all_variables()));
+
+                stack.push(var);
+            }
+            LambdaSet(roc_types::subs::LambdaSet {
+                solved,
+                recursion_var,
+                unspecialized,
+                ambient_function: _,
+            }) => {
+                for slice_index in solved.variables() {
+                    let slice = subs.variable_slices[slice_index.index as usize];
+                    stack.extend(var_slice!(slice));
+                }
+
+                if let Some(rec_var) = recursion_var.into_variable() {
+                    stack.push(rec_var);
+                }
+
+                for Uls(var, _, _) in subs.get_subs_slice(*unspecialized) {
+                    stack.push(*var);
+                }
+            }
+            &RangedNumber(_) => {}
+        }
+    }
+
+    lambda_sets
+}
+
 pub fn load_types(
     full_file_path: PathBuf,
     threading: Threading,
     ignore_errors: IgnoreErrors,
-) -> Result, io::Error> {
+) -> Result, io::Error> {
     let target_info = (&Triple::host()).into();
     let arena = &Bump::new();
     let LoadedModule {
@@ -91,6 +339,7 @@ pub fn load_types(
         mut declarations_by_id,
         mut solved,
         interns,
+        exposed_to_host,
         ..
     } = roc_load::load_and_typecheck(
         arena,
@@ -129,45 +378,93 @@ pub fn load_types(
         );
     }
 
+    // Get the variables for all the exposed_to_host symbols
     let variables = (0..decls.len()).filter_map(|index| {
-        use roc_can::expr::DeclarationTag::*;
-
-        match decls.declarations[index] {
-            Value | Function(_) | Recursive(_) | TailRecursive(_) => Some(decls.variables[index]),
-            Destructure(_) => {
-                // figure out if we need to export non-identifier defs - when would that
-                // happen?
-                None
-            }
-            MutualRecursion { .. } => {
-                // handled by future iterations
-                None
-            }
-            Expectation | ExpectationFx => {
-                // not publicly visible
-                None
-            }
+        if exposed_to_host.contains_key(&decls.symbols[index].value) {
+            Some(decls.variables[index])
+        } else {
+            None
         }
     });
 
+    let operating_system = target_info.operating_system;
+    let architectures = Architecture::iter();
+    let mut arch_types = Vec::with_capacity(architectures.len());
+
     let layout_interner = GlobalLayoutInterner::with_capacity(128, target_info);
 
-    let architectures = Architecture::iter();
-    let mut types_and_targets = Vec::with_capacity(architectures.len());
-    for arch in architectures {
+    for architecture in architectures {
+        let mut interns = interns.clone(); // TODO there may be a way to avoid this.
         let target_info = TargetInfo {
-            architecture: arch,
-            operating_system: OperatingSystem::Unix,
+            architecture,
+            operating_system,
         };
+        let mut layout_cache = LayoutCache::new(layout_interner.fork(), target_info);
+        let mut glue_procs_by_layout = MutMap::default();
 
-        let types = {
-            let mut env = Env::new(arena, subs, &interns, layout_interner.fork(), target_info);
+        let mut extern_names = MutMap::default();
 
-            env.vars_to_types(variables.clone())
-        };
+        // Populate glue getters/setters for all relevant variables
+        for var in variables.clone() {
+            for (i, v) in number_lambda_sets(subs, var).iter().enumerate() {
+                extern_names.insert(*v, i.to_string());
+            }
 
-        types_and_targets.push((types, target_info));
+            let in_layout = layout_cache
+                .from_var(arena, var, subs)
+                .expect("Something weird ended up in the content");
+
+            let layout = layout_cache.interner.get(in_layout);
+
+            // dbg!(layout);
+
+            if layout.has_varying_stack_size(&layout_cache.interner, arena) {
+                let ident_ids = interns.all_ident_ids.get_mut(&home).unwrap();
+                let answer = generate_glue_procs(
+                    home,
+                    ident_ids,
+                    arena,
+                    &mut layout_interner.fork(),
+                    arena.alloc(layout),
+                );
+
+                // Even though generate_glue_procs does more work than we need it to,
+                // it's important that we use it in order to make sure we get exactly
+                // the same names that mono::ir did for code gen!
+                for (layout, glue_procs) in answer.getters {
+                    let mut names =
+                        bumpalo::collections::Vec::with_capacity_in(glue_procs.len(), arena);
+
+                    // Record all the getter/setter names associated with this layout
+                    for GlueProc { name, .. } in glue_procs {
+                        // Given a struct layout (including lambda sets!) the offsets - and therefore
+                        // getters/setters - are deterministic, so we can use layout as the hash key
+                        // for these getters/setters. We also only need to store the name because
+                        // since they are getters and setters, we can know their types (from a
+                        // TypeId perspective) deterministically based on knowing the types of
+                        // the structs and fields.
+                        //
+                        // Store them as strings, because symbols won't be useful to glue generators!
+                        names.push(name.as_str(&interns).to_string());
+                    }
+
+                    glue_procs_by_layout.insert(layout, names.into_bump_slice());
+                }
+            }
+        }
+
+        let types = Types::new(
+            arena,
+            subs,
+            variables.clone(),
+            arena.alloc(interns),
+            glue_procs_by_layout,
+            layout_cache,
+            target_info,
+        );
+
+        arch_types.push(types);
     }
 
-    Ok(types_and_targets)
+    Ok(arch_types)
 }
diff --git a/crates/glue/src/roc_helpers.rs b/crates/glue/src/roc_helpers.rs
new file mode 100644
index 0000000000..3e117ad667
--- /dev/null
+++ b/crates/glue/src/roc_helpers.rs
@@ -0,0 +1,234 @@
+use libc::{c_char, c_int, c_void, size_t};
+#[cfg(unix)]
+use libc::{c_uint, mode_t, off_t, pid_t};
+
+use roc_std::RocStr;
+
+// These are required to ensure rust adds these functions to the final binary even though they are never used.
+#[used]
+pub static ROC_ALLOC: unsafe extern "C" fn(usize, u32) -> *mut c_void = roc_alloc;
+
+#[used]
+pub static ROC_REALLOC: unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void =
+    roc_realloc;
+
+#[used]
+pub static ROC_DEALLOC: unsafe extern "C" fn(*mut c_void, u32) = roc_dealloc;
+
+#[used]
+pub static ROC_PANIC: unsafe extern "C" fn(&RocStr, u32) = roc_panic;
+
+#[cfg(unix)]
+#[used]
+pub static ROC_GETPPID: unsafe extern "C" fn() -> pid_t = roc_getppid;
+
+#[cfg(unix)]
+#[used]
+pub static ROC_MMAP: unsafe extern "C" fn(
+    *mut c_void,
+    size_t,
+    c_int,
+    c_int,
+    c_int,
+    off_t,
+) -> *mut c_void = roc_mmap;
+
+#[cfg(unix)]
+#[used]
+pub static ROC_SHM_OPEN: unsafe extern "C" fn(*const c_char, c_int, mode_t) -> c_int = roc_shm_open;
+
+#[used]
+pub static ROC_MEMCPY: unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void =
+    roc_memcpy;
+
+#[used]
+pub static ROC_MEMSET: unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void = roc_memset;
+
+/// # Safety
+/// This just delegates to libc::malloc, so it's equally safe.
+#[no_mangle]
+pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
+    libc::malloc(size)
+}
+
+/// # Safety
+/// This just delegates to libc::realloc, so it's equally safe.
+#[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 {
+    libc::realloc(c_ptr, new_size)
+}
+
+/// # Safety
+/// This just delegates to libc::free, so it's equally safe.
+#[no_mangle]
+pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
+    libc::free(c_ptr)
+}
+
+#[no_mangle]
+pub extern "C" fn roc_panic(msg: &RocStr, tag_id: u32) {
+    match tag_id {
+        0 => {
+            eprintln!("Roc crashed with:\n\n\t{}\n", msg.as_str());
+
+            print_backtrace();
+            std::process::exit(1);
+        }
+        1 => {
+            eprintln!("The program crashed with:\n\n\t{}\n", msg.as_str());
+
+            print_backtrace();
+            std::process::exit(1);
+        }
+        code => {
+            eprintln!("Roc crashed with error code:\n\n\t{}\n", code);
+
+            print_backtrace();
+            std::process::exit(1);
+        }
+    }
+}
+
+#[cfg(unix)]
+#[no_mangle]
+pub extern "C" fn roc_getppid() -> pid_t {
+    unsafe { libc::getppid() }
+}
+
+/// # Safety
+/// This just delegates to libc::mmap, and so is equally safe.
+#[cfg(unix)]
+#[no_mangle]
+pub unsafe extern "C" fn roc_mmap(
+    addr: *mut c_void,
+    len: size_t,
+    prot: c_int,
+    flags: c_int,
+    fd: c_int,
+    offset: off_t,
+) -> *mut c_void {
+    libc::mmap(addr, len, prot, flags, fd, offset)
+}
+
+/// # Safety
+/// This just delegates to libc::shm_open, and so is equally safe.
+#[cfg(unix)]
+#[no_mangle]
+pub unsafe extern "C" fn roc_shm_open(name: *const c_char, oflag: c_int, mode: mode_t) -> c_int {
+    libc::shm_open(name, oflag, mode as c_uint)
+}
+
+fn print_backtrace() {
+    eprintln!("Here is the call stack that led to the crash:\n");
+
+    let mut entries = Vec::new();
+
+    #[derive(Default)]
+    struct Entry {
+        pub fn_name: String,
+        pub filename: Option,
+        pub line: Option,
+        pub col: Option,
+    }
+
+    backtrace::trace(|frame| {
+        backtrace::resolve_frame(frame, |symbol| {
+            if let Some(fn_name) = symbol.name() {
+                let fn_name = fn_name.to_string();
+
+                if should_show_in_backtrace(&fn_name) {
+                    let mut entry: Entry = Entry {
+                        fn_name: format_fn_name(&fn_name),
+                        ..Default::default()
+                    };
+
+                    if let Some(path) = symbol.filename() {
+                        entry.filename = Some(path.to_string_lossy().into_owned());
+                    };
+
+                    entry.line = symbol.lineno();
+                    entry.col = symbol.colno();
+
+                    entries.push(entry);
+                }
+            } else {
+                entries.push(Entry {
+                    fn_name: "???".to_string(),
+                    ..Default::default()
+                });
+            }
+        });
+
+        true // keep going to the next frame
+    });
+
+    for entry in entries {
+        eprintln!("\t{}", entry.fn_name);
+
+        if let Some(filename) = entry.filename {
+            eprintln!("\t\t{filename}");
+        }
+    }
+
+    eprintln!("\nOptimizations can make this list inaccurate! If it looks wrong, try running without `--optimize` and with `--linker=legacy`\n");
+}
+
+fn should_show_in_backtrace(fn_name: &str) -> bool {
+    let is_from_rust = fn_name.contains("::");
+    let is_host_fn = fn_name.starts_with("roc_panic")
+        || fn_name.starts_with("_Effect_effect")
+        || fn_name.starts_with("_roc__")
+        || fn_name.starts_with("rust_main")
+        || fn_name == "_main";
+
+    !is_from_rust && !is_host_fn
+}
+
+fn format_fn_name(fn_name: &str) -> String {
+    // e.g. convert "_Num_sub_a0c29024d3ec6e3a16e414af99885fbb44fa6182331a70ab4ca0886f93bad5"
+    // to ["Num", "sub", "a0c29024d3ec6e3a16e414af99885fbb44fa6182331a70ab4ca0886f93bad5"]
+    let mut pieces_iter = fn_name.split('_');
+
+    if let (_, Some(module_name), Some(name)) =
+        (pieces_iter.next(), pieces_iter.next(), pieces_iter.next())
+    {
+        display_roc_fn(module_name, name)
+    } else {
+        "???".to_string()
+    }
+}
+
+fn display_roc_fn(module_name: &str, fn_name: &str) -> String {
+    let module_name = if module_name == "#UserApp" {
+        "app"
+    } else {
+        module_name
+    };
+
+    let fn_name = if fn_name.parse::().is_ok() {
+        "(anonymous function)"
+    } else {
+        fn_name
+    };
+
+    format!("\u{001B}[36m{module_name}\u{001B}[39m.{fn_name}")
+}
+
+/// # Safety
+/// This just delegates to libc::memcpy, so it's equally safe.
+#[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)
+}
+
+/// # Safety
+/// This just delegates to libc::memset, so it's equally safe.
+#[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)
+}
diff --git a/crates/glue/src/roc_type/mod.rs b/crates/glue/src/roc_type/mod.rs
new file mode 100644
index 0000000000..50b73265ee
--- /dev/null
+++ b/crates/glue/src/roc_type/mod.rs
@@ -0,0 +1,3964 @@
+// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
+
+#![allow(unused_unsafe)]
+#![allow(dead_code)]
+#![allow(unused_mut)]
+#![allow(non_snake_case)]
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+#![allow(clippy::undocumented_unsafe_blocks)]
+#![allow(clippy::redundant_static_lifetimes)]
+#![allow(clippy::unused_unit)]
+#![allow(clippy::missing_safety_doc)]
+#![allow(clippy::let_and_return)]
+#![allow(clippy::missing_safety_doc)]
+#![allow(clippy::redundant_static_lifetimes)]
+#![allow(clippy::needless_borrow)]
+#![allow(clippy::clone_on_copy)]
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct File {
+    pub content: roc_std::RocStr,
+    pub name: roc_std::RocStr,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct Types {
+    pub aligns: roc_std::RocList,
+    pub deps: roc_std::RocList,
+    pub sizes: roc_std::RocList,
+    pub types: roc_std::RocList,
+    pub typesByName: roc_std::RocList,
+    pub target: Target,
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[repr(C)]
+#[derive(Clone, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+pub struct Tuple1 {
+    f0: roc_std::RocStr,
+    f1: u32,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum discriminant_RocType {
+    Bool = 0,
+    EmptyTagUnion = 1,
+    Function = 2,
+    Num = 3,
+    RecursivePointer = 4,
+    RocBox = 5,
+    RocDict = 6,
+    RocList = 7,
+    RocResult = 8,
+    RocSet = 9,
+    RocStr = 10,
+    Struct = 11,
+    TagUnion = 12,
+    TagUnionPayload = 13,
+    Unit = 14,
+    Unsized = 15,
+}
+
+impl core::fmt::Debug for discriminant_RocType {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::Bool => f.write_str("discriminant_RocType::Bool"),
+            Self::EmptyTagUnion => f.write_str("discriminant_RocType::EmptyTagUnion"),
+            Self::Function => f.write_str("discriminant_RocType::Function"),
+            Self::Num => f.write_str("discriminant_RocType::Num"),
+            Self::RecursivePointer => f.write_str("discriminant_RocType::RecursivePointer"),
+            Self::RocBox => f.write_str("discriminant_RocType::RocBox"),
+            Self::RocDict => f.write_str("discriminant_RocType::RocDict"),
+            Self::RocList => f.write_str("discriminant_RocType::RocList"),
+            Self::RocResult => f.write_str("discriminant_RocType::RocResult"),
+            Self::RocSet => f.write_str("discriminant_RocType::RocSet"),
+            Self::RocStr => f.write_str("discriminant_RocType::RocStr"),
+            Self::Struct => f.write_str("discriminant_RocType::Struct"),
+            Self::TagUnion => f.write_str("discriminant_RocType::TagUnion"),
+            Self::TagUnionPayload => f.write_str("discriminant_RocType::TagUnionPayload"),
+            Self::Unit => f.write_str("discriminant_RocType::Unit"),
+            Self::Unsized => f.write_str("discriminant_RocType::Unsized"),
+        }
+    }
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[repr(C)]
+pub union RocType {
+    Function: core::mem::ManuallyDrop,
+    Num: RocNum,
+    RecursivePointer: u32,
+    RocBox: u32,
+    RocDict: RocType_RocDict,
+    RocList: u32,
+    RocResult: RocType_RocDict,
+    RocSet: u32,
+    Struct: core::mem::ManuallyDrop,
+    TagUnion: core::mem::ManuallyDrop,
+    TagUnionPayload: core::mem::ManuallyDrop,
+    _sizer: [u8; 96],
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct R16 {
+    pub id: u32,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R8 {
+    pub name: roc_std::RocStr,
+    pub payload: U1,
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R4 {
+    pub id: u32,
+    pub name: roc_std::RocStr,
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R2 {
+    pub accessors: R3,
+    pub id: u32,
+    pub name: roc_std::RocStr,
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[repr(C)]
+#[derive(Clone, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+pub struct Tuple2 {
+    f0: u32,
+    f1: roc_std::RocList,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct Target {
+    pub architecture: Architecture,
+    pub operatingSystem: OperatingSystem,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum OperatingSystem {
+    Unix = 0,
+    Wasi = 1,
+    Windows = 2,
+}
+
+impl core::fmt::Debug for OperatingSystem {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::Unix => f.write_str("OperatingSystem::Unix"),
+            Self::Wasi => f.write_str("OperatingSystem::Wasi"),
+            Self::Windows => f.write_str("OperatingSystem::Windows"),
+        }
+    }
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum Architecture {
+    Aarch32 = 0,
+    Aarch64 = 1,
+    Wasm32 = 2,
+    X86x32 = 3,
+    X86x64 = 4,
+}
+
+impl core::fmt::Debug for Architecture {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::Aarch32 => f.write_str("Architecture::Aarch32"),
+            Self::Aarch64 => f.write_str("Architecture::Aarch64"),
+            Self::Wasm32 => f.write_str("Architecture::Wasm32"),
+            Self::X86x32 => f.write_str("Architecture::X86x32"),
+            Self::X86x64 => f.write_str("Architecture::X86x64"),
+        }
+    }
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum discriminant_RocTagUnion {
+    Enumeration = 0,
+    NonNullableUnwrapped = 1,
+    NonRecursive = 2,
+    NullableUnwrapped = 3,
+    NullableWrapped = 4,
+    Recursive = 5,
+    SingleTagStruct = 6,
+}
+
+impl core::fmt::Debug for discriminant_RocTagUnion {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::Enumeration => f.write_str("discriminant_RocTagUnion::Enumeration"),
+            Self::NonNullableUnwrapped => {
+                f.write_str("discriminant_RocTagUnion::NonNullableUnwrapped")
+            }
+            Self::NonRecursive => f.write_str("discriminant_RocTagUnion::NonRecursive"),
+            Self::NullableUnwrapped => f.write_str("discriminant_RocTagUnion::NullableUnwrapped"),
+            Self::NullableWrapped => f.write_str("discriminant_RocTagUnion::NullableWrapped"),
+            Self::Recursive => f.write_str("discriminant_RocTagUnion::Recursive"),
+            Self::SingleTagStruct => f.write_str("discriminant_RocTagUnion::SingleTagStruct"),
+        }
+    }
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[repr(C)]
+pub union RocTagUnion {
+    Enumeration: core::mem::ManuallyDrop,
+    NonNullableUnwrapped: core::mem::ManuallyDrop,
+    NonRecursive: core::mem::ManuallyDrop,
+    NullableUnwrapped: core::mem::ManuallyDrop,
+    NullableWrapped: core::mem::ManuallyDrop,
+    Recursive: core::mem::ManuallyDrop,
+    SingleTagStruct: core::mem::ManuallyDrop,
+    _sizer: [u8; 88],
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R14 {
+    pub name: roc_std::RocStr,
+    pub payload: RocSingleTagPayload,
+    pub tagName: roc_std::RocStr,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum discriminant_RocSingleTagPayload {
+    HasClosure = 0,
+    HasNoClosure = 1,
+}
+
+impl core::fmt::Debug for discriminant_RocSingleTagPayload {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::HasClosure => f.write_str("discriminant_RocSingleTagPayload::HasClosure"),
+            Self::HasNoClosure => f.write_str("discriminant_RocSingleTagPayload::HasNoClosure"),
+        }
+    }
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[repr(C)]
+pub union RocSingleTagPayload {
+    HasClosure: core::mem::ManuallyDrop>,
+    HasNoClosure: core::mem::ManuallyDrop>,
+    _sizer: [u8; 32],
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R10 {
+    pub discriminantOffset: u32,
+    pub discriminantSize: u32,
+    pub name: roc_std::RocStr,
+    pub tags: roc_std::RocList,
+    pub indexOfNullTag: u16,
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R9 {
+    pub name: roc_std::RocStr,
+    pub nonNullPayload: u32,
+    pub nonNullTag: roc_std::RocStr,
+    pub nullTag: roc_std::RocStr,
+    pub whichTagIsNull: U2,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum U2 {
+    FirstTagIsNull = 0,
+    SecondTagIsNull = 1,
+}
+
+impl core::fmt::Debug for U2 {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::FirstTagIsNull => f.write_str("U2::FirstTagIsNull"),
+            Self::SecondTagIsNull => f.write_str("U2::SecondTagIsNull"),
+        }
+    }
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R7 {
+    pub discriminantOffset: u32,
+    pub discriminantSize: u32,
+    pub name: roc_std::RocStr,
+    pub tags: roc_std::RocList,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum discriminant_U1 {
+    None = 0,
+    Some = 1,
+}
+
+impl core::fmt::Debug for discriminant_U1 {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::None => f.write_str("discriminant_U1::None"),
+            Self::Some => f.write_str("discriminant_U1::Some"),
+        }
+    }
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[repr(C)]
+pub union U1 {
+    Some: u32,
+    _sizer: [u8; 8],
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R6 {
+    pub name: roc_std::RocStr,
+    pub payload: u32,
+    pub tagName: roc_std::RocStr,
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R5 {
+    pub name: roc_std::RocStr,
+    pub size: u32,
+    pub tags: roc_std::RocList,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R1 {
+    pub fields: RocStructFields,
+    pub name: roc_std::RocStr,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum discriminant_RocStructFields {
+    HasClosure = 0,
+    HasNoClosure = 1,
+}
+
+impl core::fmt::Debug for discriminant_RocStructFields {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::HasClosure => f.write_str("discriminant_RocStructFields::HasClosure"),
+            Self::HasNoClosure => f.write_str("discriminant_RocStructFields::HasNoClosure"),
+        }
+    }
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[repr(C)]
+pub union RocStructFields {
+    HasClosure: core::mem::ManuallyDrop>,
+    HasNoClosure: core::mem::ManuallyDrop>,
+    _sizer: [u8; 32],
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct R3 {
+    pub getter: roc_std::RocStr,
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+struct RocType_RocDict {
+    pub f0: u32,
+    pub f1: u32,
+}
+
+#[cfg(any(
+    target_arch = "arm",
+    target_arch = "aarch64",
+    target_arch = "wasm32",
+    target_arch = "x86",
+    target_arch = "x86_64"
+))]
+#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(u8)]
+pub enum RocNum {
+    Dec = 0,
+    F32 = 1,
+    F64 = 2,
+    I128 = 3,
+    I16 = 4,
+    I32 = 5,
+    I64 = 6,
+    I8 = 7,
+    U128 = 8,
+    U16 = 9,
+    U32 = 10,
+    U64 = 11,
+    U8 = 12,
+}
+
+impl core::fmt::Debug for RocNum {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            Self::Dec => f.write_str("RocNum::Dec"),
+            Self::F32 => f.write_str("RocNum::F32"),
+            Self::F64 => f.write_str("RocNum::F64"),
+            Self::I128 => f.write_str("RocNum::I128"),
+            Self::I16 => f.write_str("RocNum::I16"),
+            Self::I32 => f.write_str("RocNum::I32"),
+            Self::I64 => f.write_str("RocNum::I64"),
+            Self::I8 => f.write_str("RocNum::I8"),
+            Self::U128 => f.write_str("RocNum::U128"),
+            Self::U16 => f.write_str("RocNum::U16"),
+            Self::U32 => f.write_str("RocNum::U32"),
+            Self::U64 => f.write_str("RocNum::U64"),
+            Self::U8 => f.write_str("RocNum::U8"),
+        }
+    }
+}
+
+#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct RocFn {
+    pub args: roc_std::RocList,
+    pub externName: roc_std::RocStr,
+    pub functionName: roc_std::RocStr,
+    pub lambdaSet: u32,
+    pub ret: u32,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[repr(C)]
+#[derive(Clone, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+pub struct Tuple1 {
+    f0: roc_std::RocStr,
+    f1: u64,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[repr(C)]
+pub union RocType {
+    Function: core::mem::ManuallyDrop,
+    Num: RocNum,
+    RecursivePointer: u64,
+    RocBox: u64,
+    RocDict: RocType_RocDict,
+    RocList: u64,
+    RocResult: RocType_RocDict,
+    RocSet: u64,
+    Struct: core::mem::ManuallyDrop,
+    TagUnion: core::mem::ManuallyDrop,
+    TagUnionPayload: core::mem::ManuallyDrop,
+    _sizer: [u8; 104],
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct R16 {
+    pub id: u64,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R4 {
+    pub id: u64,
+    pub name: roc_std::RocStr,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R2 {
+    pub accessors: R3,
+    pub id: u64,
+    pub name: roc_std::RocStr,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[repr(C)]
+#[derive(Clone, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+pub struct Tuple2 {
+    f0: u64,
+    f1: roc_std::RocList,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[repr(C)]
+pub union RocTagUnion {
+    Enumeration: core::mem::ManuallyDrop,
+    NonNullableUnwrapped: core::mem::ManuallyDrop,
+    NonRecursive: core::mem::ManuallyDrop,
+    NullableUnwrapped: core::mem::ManuallyDrop,
+    NullableWrapped: core::mem::ManuallyDrop,
+    Recursive: core::mem::ManuallyDrop,
+    SingleTagStruct: core::mem::ManuallyDrop,
+    _sizer: [u8; 96],
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R10 {
+    pub name: roc_std::RocStr,
+    pub tags: roc_std::RocList,
+    pub discriminantOffset: u32,
+    pub discriminantSize: u32,
+    pub indexOfNullTag: u16,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R9 {
+    pub name: roc_std::RocStr,
+    pub nonNullPayload: u64,
+    pub nonNullTag: roc_std::RocStr,
+    pub nullTag: roc_std::RocStr,
+    pub whichTagIsNull: U2,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R7 {
+    pub name: roc_std::RocStr,
+    pub tags: roc_std::RocList,
+    pub discriminantOffset: u32,
+    pub discriminantSize: u32,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[repr(C)]
+pub union U1 {
+    Some: u64,
+    _sizer: [u8; 16],
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R6 {
+    pub name: roc_std::RocStr,
+    pub payload: u64,
+    pub tagName: roc_std::RocStr,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct R5 {
+    pub name: roc_std::RocStr,
+    pub tags: roc_std::RocList,
+    pub size: u32,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+struct RocType_RocDict {
+    pub f0: u64,
+    pub f1: u64,
+}
+
+#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
+#[repr(C)]
+pub struct RocFn {
+    pub args: roc_std::RocList,
+    pub externName: roc_std::RocStr,
+    pub functionName: roc_std::RocStr,
+    pub lambdaSet: u64,
+    pub ret: u64,
+}
+
+impl Tuple1 {
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// A tag named T, with the given payload.
+    pub fn T(f0: roc_std::RocStr, f1: u32) -> Self {
+        Self { f0, f1 }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Since `T` only has one tag (namely, `T`),
+    /// convert it to `T`'s payload.
+    pub fn into_T(self) -> (roc_std::RocStr, u32) {
+        (self.f0, self.f1)
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Since `T` only has one tag (namely, `T`),
+    /// convert it to `T`'s payload.
+    pub fn as_T(&self) -> (&roc_std::RocStr, &u32) {
+        (&self.f0, &self.f1)
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// A tag named T, with the given payload.
+    pub fn T(f0: roc_std::RocStr, f1: u64) -> Self {
+        Self { f0, f1 }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Since `T` only has one tag (namely, `T`),
+    /// convert it to `T`'s payload.
+    pub fn into_T(self) -> (roc_std::RocStr, u64) {
+        (self.f0, self.f1)
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Since `T` only has one tag (namely, `T`),
+    /// convert it to `T`'s payload.
+    pub fn as_T(&self) -> (&roc_std::RocStr, &u64) {
+        (&self.f0, &self.f1)
+    }
+}
+
+impl core::fmt::Debug for Tuple1 {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_tuple("Tuple1::T")
+            .field(&self.f0)
+            .field(&self.f1)
+            .finish()
+    }
+}
+
+impl RocType {
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_RocType {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(48))
+        }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_RocType) {
+        let discriminant_ptr: *mut discriminant_RocType = (self as *mut RocType).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(48)) = discriminant;
+        }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// A tag named Bool, which has no payload.
+    pub const Bool: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[48] = discriminant_RocType::Bool as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `into_` methods return a payload, but since the Bool tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn into_Bool(self) {
+        ()
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `as` methods return a payload, but since the Bool tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn as_Bool(&self) {
+        ()
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// A tag named EmptyTagUnion, which has no payload.
+    pub const EmptyTagUnion: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[48] = discriminant_RocType::EmptyTagUnion as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `into_` methods return a payload, but since the EmptyTagUnion tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn into_EmptyTagUnion(self) {
+        ()
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `as` methods return a payload, but since the EmptyTagUnion tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn as_EmptyTagUnion(&self) {
+        ()
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `Function`, with the appropriate payload
+    pub fn Function(arg0: RocFn) -> Self {
+        let mut answer = Self {
+            Function: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocType::Function);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `Function` and convert it to `Function`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Function`.
+    pub unsafe fn into_Function(mut self) -> RocFn {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::Function);
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.Function,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `Function` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Function`.
+    pub unsafe fn as_Function(&self) -> &RocFn {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::Function);
+        let payload = &self.Function;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `Num`, with the appropriate payload
+    pub fn Num(arg: RocNum) -> Self {
+        let mut answer = Self { Num: arg };
+
+        answer.set_discriminant(discriminant_RocType::Num);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `Num` and convert it to `Num`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Num`.
+    pub unsafe fn into_Num(self) -> RocNum {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::Num);
+        let payload = self.Num;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `Num` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Num`.
+    pub unsafe fn as_Num(&self) -> &RocNum {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::Num);
+        let payload = &self.Num;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Construct a tag named `RecursivePointer`, with the appropriate payload
+    pub fn RecursivePointer(arg: u32) -> Self {
+        let mut answer = Self {
+            RecursivePointer: arg,
+        };
+
+        answer.set_discriminant(discriminant_RocType::RecursivePointer);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RecursivePointer` and convert it to `RecursivePointer`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RecursivePointer`.
+    pub unsafe fn into_RecursivePointer(self) -> u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RecursivePointer);
+        let payload = self.RecursivePointer;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RecursivePointer` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RecursivePointer`.
+    pub unsafe fn as_RecursivePointer(&self) -> &u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RecursivePointer);
+        let payload = &self.RecursivePointer;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Construct a tag named `RocBox`, with the appropriate payload
+    pub fn RocBox(arg: u32) -> Self {
+        let mut answer = Self { RocBox: arg };
+
+        answer.set_discriminant(discriminant_RocType::RocBox);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocBox` and convert it to `RocBox`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocBox`.
+    pub unsafe fn into_RocBox(self) -> u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocBox);
+        let payload = self.RocBox;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocBox` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocBox`.
+    pub unsafe fn as_RocBox(&self) -> &u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocBox);
+        let payload = &self.RocBox;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Construct a tag named `RocDict`, with the appropriate payload
+    pub fn RocDict(arg0: u32, arg1: u32) -> Self {
+        let mut answer = Self {
+            RocDict: RocType_RocDict { f0: arg0, f1: arg1 },
+        };
+
+        answer.set_discriminant(discriminant_RocType::RocDict);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocDict` and convert it to `RocDict`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocDict`.
+    pub unsafe fn into_RocDict(self) -> (u32, u32) {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocDict);
+        let payload = self.RocDict;
+
+        (payload.f0, payload.f1)
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocDict` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocDict`.
+    pub unsafe fn as_RocDict(&self) -> (&u32, &u32) {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocDict);
+        let payload = &self.RocDict;
+
+        (&payload.f0, &payload.f1)
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Construct a tag named `RocList`, with the appropriate payload
+    pub fn RocList(arg: u32) -> Self {
+        let mut answer = Self { RocList: arg };
+
+        answer.set_discriminant(discriminant_RocType::RocList);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocList` and convert it to `RocList`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocList`.
+    pub unsafe fn into_RocList(self) -> u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocList);
+        let payload = self.RocList;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocList` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocList`.
+    pub unsafe fn as_RocList(&self) -> &u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocList);
+        let payload = &self.RocList;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Construct a tag named `RocResult`, with the appropriate payload
+    pub fn RocResult(arg0: u32, arg1: u32) -> Self {
+        let mut answer = Self {
+            RocResult: RocType_RocDict { f0: arg0, f1: arg1 },
+        };
+
+        answer.set_discriminant(discriminant_RocType::RocResult);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocResult` and convert it to `RocResult`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocResult`.
+    pub unsafe fn into_RocResult(self) -> (u32, u32) {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocResult);
+        let payload = self.RocResult;
+
+        (payload.f0, payload.f1)
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocResult` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocResult`.
+    pub unsafe fn as_RocResult(&self) -> (&u32, &u32) {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocResult);
+        let payload = &self.RocResult;
+
+        (&payload.f0, &payload.f1)
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Construct a tag named `RocSet`, with the appropriate payload
+    pub fn RocSet(arg: u32) -> Self {
+        let mut answer = Self { RocSet: arg };
+
+        answer.set_discriminant(discriminant_RocType::RocSet);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocSet` and convert it to `RocSet`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocSet`.
+    pub unsafe fn into_RocSet(self) -> u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocSet);
+        let payload = self.RocSet;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocSet` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocSet`.
+    pub unsafe fn as_RocSet(&self) -> &u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocSet);
+        let payload = &self.RocSet;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// A tag named RocStr, which has no payload.
+    pub const RocStr: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[48] = discriminant_RocType::RocStr as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `into_` methods return a payload, but since the RocStr tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn into_RocStr(self) {
+        ()
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `as` methods return a payload, but since the RocStr tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn as_RocStr(&self) {
+        ()
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `Struct`, with the appropriate payload
+    pub fn Struct(arg0: R1) -> Self {
+        let mut answer = Self {
+            Struct: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocType::Struct);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `Struct` and convert it to `Struct`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Struct`.
+    pub unsafe fn into_Struct(mut self) -> R1 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::Struct);
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.Struct,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `Struct` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Struct`.
+    pub unsafe fn as_Struct(&self) -> &R1 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::Struct);
+        let payload = &self.Struct;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `TagUnion`, with the appropriate payload
+    pub fn TagUnion(arg: RocTagUnion) -> Self {
+        let mut answer = Self {
+            TagUnion: core::mem::ManuallyDrop::new(arg),
+        };
+
+        answer.set_discriminant(discriminant_RocType::TagUnion);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `TagUnion` and convert it to `TagUnion`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `TagUnion`.
+    pub unsafe fn into_TagUnion(mut self) -> RocTagUnion {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::TagUnion);
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.TagUnion,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `TagUnion` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `TagUnion`.
+    pub unsafe fn as_TagUnion(&self) -> &RocTagUnion {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::TagUnion);
+        let payload = &self.TagUnion;
+
+        &payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `TagUnionPayload`, with the appropriate payload
+    pub fn TagUnionPayload(arg0: R1) -> Self {
+        let mut answer = Self {
+            TagUnionPayload: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocType::TagUnionPayload);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `TagUnionPayload` and convert it to `TagUnionPayload`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `TagUnionPayload`.
+    pub unsafe fn into_TagUnionPayload(mut self) -> R1 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::TagUnionPayload);
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.TagUnionPayload,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `TagUnionPayload` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `TagUnionPayload`.
+    pub unsafe fn as_TagUnionPayload(&self) -> &R1 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::TagUnionPayload);
+        let payload = &self.TagUnionPayload;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// A tag named Unit, which has no payload.
+    pub const Unit: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[48] = discriminant_RocType::Unit as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `into_` methods return a payload, but since the Unit tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn into_Unit(self) {
+        ()
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `as` methods return a payload, but since the Unit tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn as_Unit(&self) {
+        ()
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// A tag named Unsized, which has no payload.
+    pub const Unsized: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[48] = discriminant_RocType::Unsized as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `into_` methods return a payload, but since the Unsized tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn into_Unsized(self) {
+        ()
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `as` methods return a payload, but since the Unsized tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn as_Unsized(&self) {
+        ()
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_RocType {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(96))
+        }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_RocType) {
+        let discriminant_ptr: *mut discriminant_RocType = (self as *mut RocType).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(96)) = discriminant;
+        }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// A tag named Bool, which has no payload.
+    pub const Bool: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[96] = discriminant_RocType::Bool as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// A tag named EmptyTagUnion, which has no payload.
+    pub const EmptyTagUnion: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[96] = discriminant_RocType::EmptyTagUnion as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Construct a tag named `RecursivePointer`, with the appropriate payload
+    pub fn RecursivePointer(arg: u64) -> Self {
+        let mut answer = Self {
+            RecursivePointer: arg,
+        };
+
+        answer.set_discriminant(discriminant_RocType::RecursivePointer);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RecursivePointer` and convert it to `RecursivePointer`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RecursivePointer`.
+    pub unsafe fn into_RecursivePointer(self) -> u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RecursivePointer);
+        let payload = self.RecursivePointer;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RecursivePointer` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RecursivePointer`.
+    pub unsafe fn as_RecursivePointer(&self) -> &u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RecursivePointer);
+        let payload = &self.RecursivePointer;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Construct a tag named `RocBox`, with the appropriate payload
+    pub fn RocBox(arg: u64) -> Self {
+        let mut answer = Self { RocBox: arg };
+
+        answer.set_discriminant(discriminant_RocType::RocBox);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocBox` and convert it to `RocBox`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocBox`.
+    pub unsafe fn into_RocBox(self) -> u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocBox);
+        let payload = self.RocBox;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocBox` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocBox`.
+    pub unsafe fn as_RocBox(&self) -> &u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocBox);
+        let payload = &self.RocBox;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Construct a tag named `RocDict`, with the appropriate payload
+    pub fn RocDict(arg0: u64, arg1: u64) -> Self {
+        let mut answer = Self {
+            RocDict: RocType_RocDict { f0: arg0, f1: arg1 },
+        };
+
+        answer.set_discriminant(discriminant_RocType::RocDict);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocDict` and convert it to `RocDict`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocDict`.
+    pub unsafe fn into_RocDict(self) -> (u64, u64) {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocDict);
+        let payload = self.RocDict;
+
+        (payload.f0, payload.f1)
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocDict` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocDict`.
+    pub unsafe fn as_RocDict(&self) -> (&u64, &u64) {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocDict);
+        let payload = &self.RocDict;
+
+        (&payload.f0, &payload.f1)
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Construct a tag named `RocList`, with the appropriate payload
+    pub fn RocList(arg: u64) -> Self {
+        let mut answer = Self { RocList: arg };
+
+        answer.set_discriminant(discriminant_RocType::RocList);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocList` and convert it to `RocList`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocList`.
+    pub unsafe fn into_RocList(self) -> u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocList);
+        let payload = self.RocList;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocList` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocList`.
+    pub unsafe fn as_RocList(&self) -> &u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocList);
+        let payload = &self.RocList;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Construct a tag named `RocResult`, with the appropriate payload
+    pub fn RocResult(arg0: u64, arg1: u64) -> Self {
+        let mut answer = Self {
+            RocResult: RocType_RocDict { f0: arg0, f1: arg1 },
+        };
+
+        answer.set_discriminant(discriminant_RocType::RocResult);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocResult` and convert it to `RocResult`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocResult`.
+    pub unsafe fn into_RocResult(self) -> (u64, u64) {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocResult);
+        let payload = self.RocResult;
+
+        (payload.f0, payload.f1)
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocResult` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocResult`.
+    pub unsafe fn as_RocResult(&self) -> (&u64, &u64) {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocResult);
+        let payload = &self.RocResult;
+
+        (&payload.f0, &payload.f1)
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Construct a tag named `RocSet`, with the appropriate payload
+    pub fn RocSet(arg: u64) -> Self {
+        let mut answer = Self { RocSet: arg };
+
+        answer.set_discriminant(discriminant_RocType::RocSet);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocSet` and convert it to `RocSet`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocSet`.
+    pub unsafe fn into_RocSet(self) -> u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocSet);
+        let payload = self.RocSet;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `RocType` has a `.discriminant()` of `RocSet` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `RocSet`.
+    pub unsafe fn as_RocSet(&self) -> &u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocType::RocSet);
+        let payload = &self.RocSet;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// A tag named RocStr, which has no payload.
+    pub const RocStr: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[96] = discriminant_RocType::RocStr as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// A tag named Unit, which has no payload.
+    pub const Unit: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[96] = discriminant_RocType::Unit as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// A tag named Unsized, which has no payload.
+    pub const Unsized: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[96] = discriminant_RocType::Unsized as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], RocType>(bytes)
+    };
+}
+
+impl Drop for RocType {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn drop(&mut self) {
+        // Drop the payloads
+        match self.discriminant() {
+            discriminant_RocType::Bool => {}
+            discriminant_RocType::EmptyTagUnion => {}
+            discriminant_RocType::Function => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.Function)
+            },
+            discriminant_RocType::Num => {}
+            discriminant_RocType::RecursivePointer => {}
+            discriminant_RocType::RocBox => {}
+            discriminant_RocType::RocDict => {}
+            discriminant_RocType::RocList => {}
+            discriminant_RocType::RocResult => {}
+            discriminant_RocType::RocSet => {}
+            discriminant_RocType::RocStr => {}
+            discriminant_RocType::Struct => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.Struct)
+            },
+            discriminant_RocType::TagUnion => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.TagUnion)
+            },
+            discriminant_RocType::TagUnionPayload => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.TagUnionPayload)
+            },
+            discriminant_RocType::Unit => {}
+            discriminant_RocType::Unsized => {}
+        }
+    }
+}
+
+impl Eq for RocType {}
+
+impl PartialEq for RocType {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn eq(&self, other: &Self) -> bool {
+        if self.discriminant() != other.discriminant() {
+            return false;
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocType::Bool => true,
+                discriminant_RocType::EmptyTagUnion => true,
+                discriminant_RocType::Function => self.Function == other.Function,
+                discriminant_RocType::Num => self.Num == other.Num,
+                discriminant_RocType::RecursivePointer => {
+                    self.RecursivePointer == other.RecursivePointer
+                }
+                discriminant_RocType::RocBox => self.RocBox == other.RocBox,
+                discriminant_RocType::RocDict => self.RocDict == other.RocDict,
+                discriminant_RocType::RocList => self.RocList == other.RocList,
+                discriminant_RocType::RocResult => self.RocResult == other.RocResult,
+                discriminant_RocType::RocSet => self.RocSet == other.RocSet,
+                discriminant_RocType::RocStr => true,
+                discriminant_RocType::Struct => self.Struct == other.Struct,
+                discriminant_RocType::TagUnion => self.TagUnion == other.TagUnion,
+                discriminant_RocType::TagUnionPayload => {
+                    self.TagUnionPayload == other.TagUnionPayload
+                }
+                discriminant_RocType::Unit => true,
+                discriminant_RocType::Unsized => true,
+            }
+        }
+    }
+}
+
+impl PartialOrd for RocType {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn partial_cmp(&self, other: &Self) -> Option {
+        match self.discriminant().partial_cmp(&other.discriminant()) {
+            Some(core::cmp::Ordering::Equal) => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocType::Bool => Some(core::cmp::Ordering::Equal),
+                discriminant_RocType::EmptyTagUnion => Some(core::cmp::Ordering::Equal),
+                discriminant_RocType::Function => self.Function.partial_cmp(&other.Function),
+                discriminant_RocType::Num => self.Num.partial_cmp(&other.Num),
+                discriminant_RocType::RecursivePointer => {
+                    self.RecursivePointer.partial_cmp(&other.RecursivePointer)
+                }
+                discriminant_RocType::RocBox => self.RocBox.partial_cmp(&other.RocBox),
+                discriminant_RocType::RocDict => self.RocDict.partial_cmp(&other.RocDict),
+                discriminant_RocType::RocList => self.RocList.partial_cmp(&other.RocList),
+                discriminant_RocType::RocResult => self.RocResult.partial_cmp(&other.RocResult),
+                discriminant_RocType::RocSet => self.RocSet.partial_cmp(&other.RocSet),
+                discriminant_RocType::RocStr => Some(core::cmp::Ordering::Equal),
+                discriminant_RocType::Struct => self.Struct.partial_cmp(&other.Struct),
+                discriminant_RocType::TagUnion => self.TagUnion.partial_cmp(&other.TagUnion),
+                discriminant_RocType::TagUnionPayload => {
+                    self.TagUnionPayload.partial_cmp(&other.TagUnionPayload)
+                }
+                discriminant_RocType::Unit => Some(core::cmp::Ordering::Equal),
+                discriminant_RocType::Unsized => Some(core::cmp::Ordering::Equal),
+            }
+        }
+    }
+}
+
+impl Ord for RocType {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        match self.discriminant().cmp(&other.discriminant()) {
+            core::cmp::Ordering::Equal => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocType::Bool => core::cmp::Ordering::Equal,
+                discriminant_RocType::EmptyTagUnion => core::cmp::Ordering::Equal,
+                discriminant_RocType::Function => self.Function.cmp(&other.Function),
+                discriminant_RocType::Num => self.Num.cmp(&other.Num),
+                discriminant_RocType::RecursivePointer => {
+                    self.RecursivePointer.cmp(&other.RecursivePointer)
+                }
+                discriminant_RocType::RocBox => self.RocBox.cmp(&other.RocBox),
+                discriminant_RocType::RocDict => self.RocDict.cmp(&other.RocDict),
+                discriminant_RocType::RocList => self.RocList.cmp(&other.RocList),
+                discriminant_RocType::RocResult => self.RocResult.cmp(&other.RocResult),
+                discriminant_RocType::RocSet => self.RocSet.cmp(&other.RocSet),
+                discriminant_RocType::RocStr => core::cmp::Ordering::Equal,
+                discriminant_RocType::Struct => self.Struct.cmp(&other.Struct),
+                discriminant_RocType::TagUnion => self.TagUnion.cmp(&other.TagUnion),
+                discriminant_RocType::TagUnionPayload => {
+                    self.TagUnionPayload.cmp(&other.TagUnionPayload)
+                }
+                discriminant_RocType::Unit => core::cmp::Ordering::Equal,
+                discriminant_RocType::Unsized => core::cmp::Ordering::Equal,
+            }
+        }
+    }
+}
+
+impl Clone for RocType {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn clone(&self) -> Self {
+        let mut answer = unsafe {
+            match self.discriminant() {
+                discriminant_RocType::Bool => core::mem::transmute::<
+                    core::mem::MaybeUninit,
+                    RocType,
+                >(core::mem::MaybeUninit::uninit()),
+                discriminant_RocType::EmptyTagUnion => {
+                    core::mem::transmute::, RocType>(
+                        core::mem::MaybeUninit::uninit(),
+                    )
+                }
+                discriminant_RocType::Function => Self {
+                    Function: self.Function.clone(),
+                },
+                discriminant_RocType::Num => Self {
+                    Num: self.Num.clone(),
+                },
+                discriminant_RocType::RecursivePointer => Self {
+                    RecursivePointer: self.RecursivePointer.clone(),
+                },
+                discriminant_RocType::RocBox => Self {
+                    RocBox: self.RocBox.clone(),
+                },
+                discriminant_RocType::RocDict => Self {
+                    RocDict: self.RocDict.clone(),
+                },
+                discriminant_RocType::RocList => Self {
+                    RocList: self.RocList.clone(),
+                },
+                discriminant_RocType::RocResult => Self {
+                    RocResult: self.RocResult.clone(),
+                },
+                discriminant_RocType::RocSet => Self {
+                    RocSet: self.RocSet.clone(),
+                },
+                discriminant_RocType::RocStr => core::mem::transmute::<
+                    core::mem::MaybeUninit,
+                    RocType,
+                >(core::mem::MaybeUninit::uninit()),
+                discriminant_RocType::Struct => Self {
+                    Struct: self.Struct.clone(),
+                },
+                discriminant_RocType::TagUnion => Self {
+                    TagUnion: self.TagUnion.clone(),
+                },
+                discriminant_RocType::TagUnionPayload => Self {
+                    TagUnionPayload: self.TagUnionPayload.clone(),
+                },
+                discriminant_RocType::Unit => core::mem::transmute::<
+                    core::mem::MaybeUninit,
+                    RocType,
+                >(core::mem::MaybeUninit::uninit()),
+                discriminant_RocType::Unsized => core::mem::transmute::<
+                    core::mem::MaybeUninit,
+                    RocType,
+                >(core::mem::MaybeUninit::uninit()),
+            }
+        };
+
+        answer.set_discriminant(self.discriminant());
+
+        answer
+    }
+}
+
+impl core::hash::Hash for RocType {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn hash(&self, state: &mut H) {
+        match self.discriminant() {
+            discriminant_RocType::Bool => discriminant_RocType::Bool.hash(state),
+            discriminant_RocType::EmptyTagUnion => discriminant_RocType::EmptyTagUnion.hash(state),
+            discriminant_RocType::Function => unsafe {
+                discriminant_RocType::Function.hash(state);
+                self.Function.hash(state);
+            },
+            discriminant_RocType::Num => unsafe {
+                discriminant_RocType::Num.hash(state);
+                self.Num.hash(state);
+            },
+            discriminant_RocType::RecursivePointer => unsafe {
+                discriminant_RocType::RecursivePointer.hash(state);
+                self.RecursivePointer.hash(state);
+            },
+            discriminant_RocType::RocBox => unsafe {
+                discriminant_RocType::RocBox.hash(state);
+                self.RocBox.hash(state);
+            },
+            discriminant_RocType::RocDict => unsafe {
+                discriminant_RocType::RocDict.hash(state);
+                self.RocDict.hash(state);
+            },
+            discriminant_RocType::RocList => unsafe {
+                discriminant_RocType::RocList.hash(state);
+                self.RocList.hash(state);
+            },
+            discriminant_RocType::RocResult => unsafe {
+                discriminant_RocType::RocResult.hash(state);
+                self.RocResult.hash(state);
+            },
+            discriminant_RocType::RocSet => unsafe {
+                discriminant_RocType::RocSet.hash(state);
+                self.RocSet.hash(state);
+            },
+            discriminant_RocType::RocStr => discriminant_RocType::RocStr.hash(state),
+            discriminant_RocType::Struct => unsafe {
+                discriminant_RocType::Struct.hash(state);
+                self.Struct.hash(state);
+            },
+            discriminant_RocType::TagUnion => unsafe {
+                discriminant_RocType::TagUnion.hash(state);
+                self.TagUnion.hash(state);
+            },
+            discriminant_RocType::TagUnionPayload => unsafe {
+                discriminant_RocType::TagUnionPayload.hash(state);
+                self.TagUnionPayload.hash(state);
+            },
+            discriminant_RocType::Unit => discriminant_RocType::Unit.hash(state),
+            discriminant_RocType::Unsized => discriminant_RocType::Unsized.hash(state),
+        }
+    }
+}
+
+impl core::fmt::Debug for RocType {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.write_str("RocType::")?;
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocType::Bool => f.write_str("Bool"),
+                discriminant_RocType::EmptyTagUnion => f.write_str("EmptyTagUnion"),
+                discriminant_RocType::Function => {
+                    f.debug_tuple("Function").field(&*self.Function).finish()
+                }
+                discriminant_RocType::Num => f.debug_tuple("Num").field(&self.Num).finish(),
+                discriminant_RocType::RecursivePointer => f
+                    .debug_tuple("RecursivePointer")
+                    .field(&self.RecursivePointer)
+                    .finish(),
+                discriminant_RocType::RocBox => {
+                    f.debug_tuple("RocBox").field(&self.RocBox).finish()
+                }
+                discriminant_RocType::RocDict => f
+                    .debug_tuple("RocDict")
+                    .field(&(&self.RocDict).f0)
+                    .field(&(&self.RocDict).f1)
+                    .finish(),
+                discriminant_RocType::RocList => {
+                    f.debug_tuple("RocList").field(&self.RocList).finish()
+                }
+                discriminant_RocType::RocResult => f
+                    .debug_tuple("RocResult")
+                    .field(&(&self.RocResult).f0)
+                    .field(&(&self.RocResult).f1)
+                    .finish(),
+                discriminant_RocType::RocSet => {
+                    f.debug_tuple("RocSet").field(&self.RocSet).finish()
+                }
+                discriminant_RocType::RocStr => f.write_str("RocStr"),
+                discriminant_RocType::Struct => {
+                    f.debug_tuple("Struct").field(&*self.Struct).finish()
+                }
+                discriminant_RocType::TagUnion => {
+                    f.debug_tuple("TagUnion").field(&*self.TagUnion).finish()
+                }
+                discriminant_RocType::TagUnionPayload => f
+                    .debug_tuple("TagUnionPayload")
+                    .field(&*self.TagUnionPayload)
+                    .finish(),
+                discriminant_RocType::Unit => f.write_str("Unit"),
+                discriminant_RocType::Unsized => f.write_str("Unsized"),
+            }
+        }
+    }
+}
+
+impl Tuple2 {
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// A tag named T, with the given payload.
+    pub fn T(f0: u32, f1: roc_std::RocList) -> Self {
+        Self { f0, f1 }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Since `T` only has one tag (namely, `T`),
+    /// convert it to `T`'s payload.
+    pub fn into_T(self) -> (u32, roc_std::RocList) {
+        (self.f0, self.f1)
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Since `T` only has one tag (namely, `T`),
+    /// convert it to `T`'s payload.
+    pub fn as_T(&self) -> (&u32, &roc_std::RocList) {
+        (&self.f0, &self.f1)
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// A tag named T, with the given payload.
+    pub fn T(f0: u64, f1: roc_std::RocList) -> Self {
+        Self { f0, f1 }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Since `T` only has one tag (namely, `T`),
+    /// convert it to `T`'s payload.
+    pub fn into_T(self) -> (u64, roc_std::RocList) {
+        (self.f0, self.f1)
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Since `T` only has one tag (namely, `T`),
+    /// convert it to `T`'s payload.
+    pub fn as_T(&self) -> (&u64, &roc_std::RocList) {
+        (&self.f0, &self.f1)
+    }
+}
+
+impl core::fmt::Debug for Tuple2 {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_tuple("Tuple2::T")
+            .field(&self.f0)
+            .field(&self.f1)
+            .finish()
+    }
+}
+
+impl RocTagUnion {
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_RocTagUnion {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(44))
+        }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_RocTagUnion) {
+        let discriminant_ptr: *mut discriminant_RocTagUnion = (self as *mut RocTagUnion).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(44)) = discriminant;
+        }
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `Enumeration`, with the appropriate payload
+    pub fn Enumeration(arg0: R5) -> Self {
+        let mut answer = Self {
+            Enumeration: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocTagUnion::Enumeration);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `Enumeration` and convert it to `Enumeration`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Enumeration`.
+    pub unsafe fn into_Enumeration(mut self) -> R5 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocTagUnion::Enumeration);
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.Enumeration,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `Enumeration` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Enumeration`.
+    pub unsafe fn as_Enumeration(&self) -> &R5 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocTagUnion::Enumeration);
+        let payload = &self.Enumeration;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `NonNullableUnwrapped`, with the appropriate payload
+    pub fn NonNullableUnwrapped(arg0: R6) -> Self {
+        let mut answer = Self {
+            NonNullableUnwrapped: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocTagUnion::NonNullableUnwrapped);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `NonNullableUnwrapped` and convert it to `NonNullableUnwrapped`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `NonNullableUnwrapped`.
+    pub unsafe fn into_NonNullableUnwrapped(mut self) -> R6 {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocTagUnion::NonNullableUnwrapped
+        );
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.NonNullableUnwrapped,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `NonNullableUnwrapped` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `NonNullableUnwrapped`.
+    pub unsafe fn as_NonNullableUnwrapped(&self) -> &R6 {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocTagUnion::NonNullableUnwrapped
+        );
+        let payload = &self.NonNullableUnwrapped;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `NonRecursive`, with the appropriate payload
+    pub fn NonRecursive(arg0: R7) -> Self {
+        let mut answer = Self {
+            NonRecursive: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocTagUnion::NonRecursive);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `NonRecursive` and convert it to `NonRecursive`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `NonRecursive`.
+    pub unsafe fn into_NonRecursive(mut self) -> R7 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocTagUnion::NonRecursive);
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.NonRecursive,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `NonRecursive` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `NonRecursive`.
+    pub unsafe fn as_NonRecursive(&self) -> &R7 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocTagUnion::NonRecursive);
+        let payload = &self.NonRecursive;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `NullableUnwrapped`, with the appropriate payload
+    pub fn NullableUnwrapped(arg0: R9) -> Self {
+        let mut answer = Self {
+            NullableUnwrapped: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocTagUnion::NullableUnwrapped);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `NullableUnwrapped` and convert it to `NullableUnwrapped`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `NullableUnwrapped`.
+    pub unsafe fn into_NullableUnwrapped(mut self) -> R9 {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocTagUnion::NullableUnwrapped
+        );
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.NullableUnwrapped,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `NullableUnwrapped` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `NullableUnwrapped`.
+    pub unsafe fn as_NullableUnwrapped(&self) -> &R9 {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocTagUnion::NullableUnwrapped
+        );
+        let payload = &self.NullableUnwrapped;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `NullableWrapped`, with the appropriate payload
+    pub fn NullableWrapped(arg0: R10) -> Self {
+        let mut answer = Self {
+            NullableWrapped: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocTagUnion::NullableWrapped);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `NullableWrapped` and convert it to `NullableWrapped`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `NullableWrapped`.
+    pub unsafe fn into_NullableWrapped(mut self) -> R10 {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocTagUnion::NullableWrapped
+        );
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.NullableWrapped,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `NullableWrapped` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `NullableWrapped`.
+    pub unsafe fn as_NullableWrapped(&self) -> &R10 {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocTagUnion::NullableWrapped
+        );
+        let payload = &self.NullableWrapped;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `Recursive`, with the appropriate payload
+    pub fn Recursive(arg0: R7) -> Self {
+        let mut answer = Self {
+            Recursive: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocTagUnion::Recursive);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `Recursive` and convert it to `Recursive`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Recursive`.
+    pub unsafe fn into_Recursive(mut self) -> R7 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocTagUnion::Recursive);
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.Recursive,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `Recursive` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Recursive`.
+    pub unsafe fn as_Recursive(&self) -> &R7 {
+        debug_assert_eq!(self.discriminant(), discriminant_RocTagUnion::Recursive);
+        let payload = &self.Recursive;
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `SingleTagStruct`, with the appropriate payload
+    pub fn SingleTagStruct(arg0: R14) -> Self {
+        let mut answer = Self {
+            SingleTagStruct: core::mem::ManuallyDrop::new(arg0),
+        };
+
+        answer.set_discriminant(discriminant_RocTagUnion::SingleTagStruct);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `SingleTagStruct` and convert it to `SingleTagStruct`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `SingleTagStruct`.
+    pub unsafe fn into_SingleTagStruct(mut self) -> R14 {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocTagUnion::SingleTagStruct
+        );
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.SingleTagStruct,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocTagUnion` has a `.discriminant()` of `SingleTagStruct` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `SingleTagStruct`.
+    pub unsafe fn as_SingleTagStruct(&self) -> &R14 {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocTagUnion::SingleTagStruct
+        );
+        let payload = &self.SingleTagStruct;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_RocTagUnion {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(88))
+        }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_RocTagUnion) {
+        let discriminant_ptr: *mut discriminant_RocTagUnion = (self as *mut RocTagUnion).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(88)) = discriminant;
+        }
+    }
+}
+
+impl Drop for RocTagUnion {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn drop(&mut self) {
+        // Drop the payloads
+        match self.discriminant() {
+            discriminant_RocTagUnion::Enumeration => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.Enumeration)
+            },
+            discriminant_RocTagUnion::NonNullableUnwrapped => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.NonNullableUnwrapped)
+            },
+            discriminant_RocTagUnion::NonRecursive => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.NonRecursive)
+            },
+            discriminant_RocTagUnion::NullableUnwrapped => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.NullableUnwrapped)
+            },
+            discriminant_RocTagUnion::NullableWrapped => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.NullableWrapped)
+            },
+            discriminant_RocTagUnion::Recursive => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.Recursive)
+            },
+            discriminant_RocTagUnion::SingleTagStruct => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.SingleTagStruct)
+            },
+        }
+    }
+}
+
+impl Eq for RocTagUnion {}
+
+impl PartialEq for RocTagUnion {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn eq(&self, other: &Self) -> bool {
+        if self.discriminant() != other.discriminant() {
+            return false;
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocTagUnion::Enumeration => self.Enumeration == other.Enumeration,
+                discriminant_RocTagUnion::NonNullableUnwrapped => {
+                    self.NonNullableUnwrapped == other.NonNullableUnwrapped
+                }
+                discriminant_RocTagUnion::NonRecursive => self.NonRecursive == other.NonRecursive,
+                discriminant_RocTagUnion::NullableUnwrapped => {
+                    self.NullableUnwrapped == other.NullableUnwrapped
+                }
+                discriminant_RocTagUnion::NullableWrapped => {
+                    self.NullableWrapped == other.NullableWrapped
+                }
+                discriminant_RocTagUnion::Recursive => self.Recursive == other.Recursive,
+                discriminant_RocTagUnion::SingleTagStruct => {
+                    self.SingleTagStruct == other.SingleTagStruct
+                }
+            }
+        }
+    }
+}
+
+impl PartialOrd for RocTagUnion {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn partial_cmp(&self, other: &Self) -> Option {
+        match self.discriminant().partial_cmp(&other.discriminant()) {
+            Some(core::cmp::Ordering::Equal) => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocTagUnion::Enumeration => {
+                    self.Enumeration.partial_cmp(&other.Enumeration)
+                }
+                discriminant_RocTagUnion::NonNullableUnwrapped => self
+                    .NonNullableUnwrapped
+                    .partial_cmp(&other.NonNullableUnwrapped),
+                discriminant_RocTagUnion::NonRecursive => {
+                    self.NonRecursive.partial_cmp(&other.NonRecursive)
+                }
+                discriminant_RocTagUnion::NullableUnwrapped => {
+                    self.NullableUnwrapped.partial_cmp(&other.NullableUnwrapped)
+                }
+                discriminant_RocTagUnion::NullableWrapped => {
+                    self.NullableWrapped.partial_cmp(&other.NullableWrapped)
+                }
+                discriminant_RocTagUnion::Recursive => self.Recursive.partial_cmp(&other.Recursive),
+                discriminant_RocTagUnion::SingleTagStruct => {
+                    self.SingleTagStruct.partial_cmp(&other.SingleTagStruct)
+                }
+            }
+        }
+    }
+}
+
+impl Ord for RocTagUnion {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        match self.discriminant().cmp(&other.discriminant()) {
+            core::cmp::Ordering::Equal => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocTagUnion::Enumeration => self.Enumeration.cmp(&other.Enumeration),
+                discriminant_RocTagUnion::NonNullableUnwrapped => {
+                    self.NonNullableUnwrapped.cmp(&other.NonNullableUnwrapped)
+                }
+                discriminant_RocTagUnion::NonRecursive => {
+                    self.NonRecursive.cmp(&other.NonRecursive)
+                }
+                discriminant_RocTagUnion::NullableUnwrapped => {
+                    self.NullableUnwrapped.cmp(&other.NullableUnwrapped)
+                }
+                discriminant_RocTagUnion::NullableWrapped => {
+                    self.NullableWrapped.cmp(&other.NullableWrapped)
+                }
+                discriminant_RocTagUnion::Recursive => self.Recursive.cmp(&other.Recursive),
+                discriminant_RocTagUnion::SingleTagStruct => {
+                    self.SingleTagStruct.cmp(&other.SingleTagStruct)
+                }
+            }
+        }
+    }
+}
+
+impl Clone for RocTagUnion {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn clone(&self) -> Self {
+        let mut answer = unsafe {
+            match self.discriminant() {
+                discriminant_RocTagUnion::Enumeration => Self {
+                    Enumeration: self.Enumeration.clone(),
+                },
+                discriminant_RocTagUnion::NonNullableUnwrapped => Self {
+                    NonNullableUnwrapped: self.NonNullableUnwrapped.clone(),
+                },
+                discriminant_RocTagUnion::NonRecursive => Self {
+                    NonRecursive: self.NonRecursive.clone(),
+                },
+                discriminant_RocTagUnion::NullableUnwrapped => Self {
+                    NullableUnwrapped: self.NullableUnwrapped.clone(),
+                },
+                discriminant_RocTagUnion::NullableWrapped => Self {
+                    NullableWrapped: self.NullableWrapped.clone(),
+                },
+                discriminant_RocTagUnion::Recursive => Self {
+                    Recursive: self.Recursive.clone(),
+                },
+                discriminant_RocTagUnion::SingleTagStruct => Self {
+                    SingleTagStruct: self.SingleTagStruct.clone(),
+                },
+            }
+        };
+
+        answer.set_discriminant(self.discriminant());
+
+        answer
+    }
+}
+
+impl core::hash::Hash for RocTagUnion {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn hash(&self, state: &mut H) {
+        match self.discriminant() {
+            discriminant_RocTagUnion::Enumeration => unsafe {
+                discriminant_RocTagUnion::Enumeration.hash(state);
+                self.Enumeration.hash(state);
+            },
+            discriminant_RocTagUnion::NonNullableUnwrapped => unsafe {
+                discriminant_RocTagUnion::NonNullableUnwrapped.hash(state);
+                self.NonNullableUnwrapped.hash(state);
+            },
+            discriminant_RocTagUnion::NonRecursive => unsafe {
+                discriminant_RocTagUnion::NonRecursive.hash(state);
+                self.NonRecursive.hash(state);
+            },
+            discriminant_RocTagUnion::NullableUnwrapped => unsafe {
+                discriminant_RocTagUnion::NullableUnwrapped.hash(state);
+                self.NullableUnwrapped.hash(state);
+            },
+            discriminant_RocTagUnion::NullableWrapped => unsafe {
+                discriminant_RocTagUnion::NullableWrapped.hash(state);
+                self.NullableWrapped.hash(state);
+            },
+            discriminant_RocTagUnion::Recursive => unsafe {
+                discriminant_RocTagUnion::Recursive.hash(state);
+                self.Recursive.hash(state);
+            },
+            discriminant_RocTagUnion::SingleTagStruct => unsafe {
+                discriminant_RocTagUnion::SingleTagStruct.hash(state);
+                self.SingleTagStruct.hash(state);
+            },
+        }
+    }
+}
+
+impl core::fmt::Debug for RocTagUnion {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.write_str("RocTagUnion::")?;
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocTagUnion::Enumeration => f
+                    .debug_tuple("Enumeration")
+                    .field(&*self.Enumeration)
+                    .finish(),
+                discriminant_RocTagUnion::NonNullableUnwrapped => f
+                    .debug_tuple("NonNullableUnwrapped")
+                    .field(&*self.NonNullableUnwrapped)
+                    .finish(),
+                discriminant_RocTagUnion::NonRecursive => f
+                    .debug_tuple("NonRecursive")
+                    .field(&*self.NonRecursive)
+                    .finish(),
+                discriminant_RocTagUnion::NullableUnwrapped => f
+                    .debug_tuple("NullableUnwrapped")
+                    .field(&*self.NullableUnwrapped)
+                    .finish(),
+                discriminant_RocTagUnion::NullableWrapped => f
+                    .debug_tuple("NullableWrapped")
+                    .field(&*self.NullableWrapped)
+                    .finish(),
+                discriminant_RocTagUnion::Recursive => {
+                    f.debug_tuple("Recursive").field(&*self.Recursive).finish()
+                }
+                discriminant_RocTagUnion::SingleTagStruct => f
+                    .debug_tuple("SingleTagStruct")
+                    .field(&*self.SingleTagStruct)
+                    .finish(),
+            }
+        }
+    }
+}
+
+impl RocSingleTagPayload {
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_RocSingleTagPayload {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(12))
+        }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_RocSingleTagPayload) {
+        let discriminant_ptr: *mut discriminant_RocSingleTagPayload =
+            (self as *mut RocSingleTagPayload).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(12)) = discriminant;
+        }
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `HasClosure`, with the appropriate payload
+    pub fn HasClosure(arg: roc_std::RocList) -> Self {
+        let mut answer = Self {
+            HasClosure: core::mem::ManuallyDrop::new(arg),
+        };
+
+        answer.set_discriminant(discriminant_RocSingleTagPayload::HasClosure);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocSingleTagPayload` has a `.discriminant()` of `HasClosure` and convert it to `HasClosure`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `HasClosure`.
+    pub unsafe fn into_HasClosure(mut self) -> roc_std::RocList {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocSingleTagPayload::HasClosure
+        );
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.HasClosure,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocSingleTagPayload` has a `.discriminant()` of `HasClosure` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `HasClosure`.
+    pub unsafe fn as_HasClosure(&self) -> &roc_std::RocList {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocSingleTagPayload::HasClosure
+        );
+        let payload = &self.HasClosure;
+
+        &payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `HasNoClosure`, with the appropriate payload
+    pub fn HasNoClosure(arg: roc_std::RocList) -> Self {
+        let mut answer = Self {
+            HasNoClosure: core::mem::ManuallyDrop::new(arg),
+        };
+
+        answer.set_discriminant(discriminant_RocSingleTagPayload::HasNoClosure);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocSingleTagPayload` has a `.discriminant()` of `HasNoClosure` and convert it to `HasNoClosure`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `HasNoClosure`.
+    pub unsafe fn into_HasNoClosure(mut self) -> roc_std::RocList {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocSingleTagPayload::HasNoClosure
+        );
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.HasNoClosure,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocSingleTagPayload` has a `.discriminant()` of `HasNoClosure` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `HasNoClosure`.
+    pub unsafe fn as_HasNoClosure(&self) -> &roc_std::RocList {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocSingleTagPayload::HasNoClosure
+        );
+        let payload = &self.HasNoClosure;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_RocSingleTagPayload {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(24))
+        }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_RocSingleTagPayload) {
+        let discriminant_ptr: *mut discriminant_RocSingleTagPayload =
+            (self as *mut RocSingleTagPayload).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(24)) = discriminant;
+        }
+    }
+}
+
+impl Drop for RocSingleTagPayload {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn drop(&mut self) {
+        // Drop the payloads
+        match self.discriminant() {
+            discriminant_RocSingleTagPayload::HasClosure => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.HasClosure)
+            },
+            discriminant_RocSingleTagPayload::HasNoClosure => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.HasNoClosure)
+            },
+        }
+    }
+}
+
+impl Eq for RocSingleTagPayload {}
+
+impl PartialEq for RocSingleTagPayload {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn eq(&self, other: &Self) -> bool {
+        if self.discriminant() != other.discriminant() {
+            return false;
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocSingleTagPayload::HasClosure => self.HasClosure == other.HasClosure,
+                discriminant_RocSingleTagPayload::HasNoClosure => {
+                    self.HasNoClosure == other.HasNoClosure
+                }
+            }
+        }
+    }
+}
+
+impl PartialOrd for RocSingleTagPayload {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn partial_cmp(&self, other: &Self) -> Option {
+        match self.discriminant().partial_cmp(&other.discriminant()) {
+            Some(core::cmp::Ordering::Equal) => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocSingleTagPayload::HasClosure => {
+                    self.HasClosure.partial_cmp(&other.HasClosure)
+                }
+                discriminant_RocSingleTagPayload::HasNoClosure => {
+                    self.HasNoClosure.partial_cmp(&other.HasNoClosure)
+                }
+            }
+        }
+    }
+}
+
+impl Ord for RocSingleTagPayload {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        match self.discriminant().cmp(&other.discriminant()) {
+            core::cmp::Ordering::Equal => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocSingleTagPayload::HasClosure => {
+                    self.HasClosure.cmp(&other.HasClosure)
+                }
+                discriminant_RocSingleTagPayload::HasNoClosure => {
+                    self.HasNoClosure.cmp(&other.HasNoClosure)
+                }
+            }
+        }
+    }
+}
+
+impl Clone for RocSingleTagPayload {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn clone(&self) -> Self {
+        let mut answer = unsafe {
+            match self.discriminant() {
+                discriminant_RocSingleTagPayload::HasClosure => Self {
+                    HasClosure: self.HasClosure.clone(),
+                },
+                discriminant_RocSingleTagPayload::HasNoClosure => Self {
+                    HasNoClosure: self.HasNoClosure.clone(),
+                },
+            }
+        };
+
+        answer.set_discriminant(self.discriminant());
+
+        answer
+    }
+}
+
+impl core::hash::Hash for RocSingleTagPayload {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn hash(&self, state: &mut H) {
+        match self.discriminant() {
+            discriminant_RocSingleTagPayload::HasClosure => unsafe {
+                discriminant_RocSingleTagPayload::HasClosure.hash(state);
+                self.HasClosure.hash(state);
+            },
+            discriminant_RocSingleTagPayload::HasNoClosure => unsafe {
+                discriminant_RocSingleTagPayload::HasNoClosure.hash(state);
+                self.HasNoClosure.hash(state);
+            },
+        }
+    }
+}
+
+impl core::fmt::Debug for RocSingleTagPayload {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.write_str("RocSingleTagPayload::")?;
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocSingleTagPayload::HasClosure => f
+                    .debug_tuple("HasClosure")
+                    .field(&*self.HasClosure)
+                    .finish(),
+                discriminant_RocSingleTagPayload::HasNoClosure => f
+                    .debug_tuple("HasNoClosure")
+                    .field(&*self.HasNoClosure)
+                    .finish(),
+            }
+        }
+    }
+}
+
+impl U1 {
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_U1 {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(4))
+        }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_U1) {
+        let discriminant_ptr: *mut discriminant_U1 = (self as *mut U1).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(4)) = discriminant;
+        }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// A tag named None, which has no payload.
+    pub const None: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[4] = discriminant_U1::None as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], U1>(bytes)
+    };
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `into_` methods return a payload, but since the None tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn into_None(self) {
+        ()
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Other `as` methods return a payload, but since the None tag
+    /// has no payload, this does nothing and is only here for completeness.
+    pub fn as_None(&self) {
+        ()
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Construct a tag named `Some`, with the appropriate payload
+    pub fn Some(arg: u32) -> Self {
+        let mut answer = Self { Some: arg };
+
+        answer.set_discriminant(discriminant_U1::Some);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `U1` has a `.discriminant()` of `Some` and convert it to `Some`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Some`.
+    pub unsafe fn into_Some(self) -> u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_U1::Some);
+        let payload = self.Some;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Unsafely assume the given `U1` has a `.discriminant()` of `Some` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Some`.
+    pub unsafe fn as_Some(&self) -> &u32 {
+        debug_assert_eq!(self.discriminant(), discriminant_U1::Some);
+        let payload = &self.Some;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_U1 {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(8))
+        }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_U1) {
+        let discriminant_ptr: *mut discriminant_U1 = (self as *mut U1).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(8)) = discriminant;
+        }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// A tag named None, which has no payload.
+    pub const None: Self = unsafe {
+        let mut bytes = [0; core::mem::size_of::()];
+
+        bytes[8] = discriminant_U1::None as u8;
+
+        core::mem::transmute::<[u8; core::mem::size_of::()], U1>(bytes)
+    };
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Construct a tag named `Some`, with the appropriate payload
+    pub fn Some(arg: u64) -> Self {
+        let mut answer = Self { Some: arg };
+
+        answer.set_discriminant(discriminant_U1::Some);
+
+        answer
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `U1` has a `.discriminant()` of `Some` and convert it to `Some`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Some`.
+    pub unsafe fn into_Some(self) -> u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_U1::Some);
+        let payload = self.Some;
+
+        payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Unsafely assume the given `U1` has a `.discriminant()` of `Some` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `Some`.
+    pub unsafe fn as_Some(&self) -> &u64 {
+        debug_assert_eq!(self.discriminant(), discriminant_U1::Some);
+        let payload = &self.Some;
+
+        &payload
+    }
+}
+
+impl Eq for U1 {}
+
+impl PartialEq for U1 {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn eq(&self, other: &Self) -> bool {
+        if self.discriminant() != other.discriminant() {
+            return false;
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_U1::None => true,
+                discriminant_U1::Some => self.Some == other.Some,
+            }
+        }
+    }
+}
+
+impl PartialOrd for U1 {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn partial_cmp(&self, other: &Self) -> Option {
+        match self.discriminant().partial_cmp(&other.discriminant()) {
+            Some(core::cmp::Ordering::Equal) => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_U1::None => Some(core::cmp::Ordering::Equal),
+                discriminant_U1::Some => self.Some.partial_cmp(&other.Some),
+            }
+        }
+    }
+}
+
+impl Ord for U1 {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        match self.discriminant().cmp(&other.discriminant()) {
+            core::cmp::Ordering::Equal => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_U1::None => core::cmp::Ordering::Equal,
+                discriminant_U1::Some => self.Some.cmp(&other.Some),
+            }
+        }
+    }
+}
+
+impl Copy for U1 {}
+
+impl Clone for U1 {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn clone(&self) -> Self {
+        let mut answer = unsafe {
+            match self.discriminant() {
+                discriminant_U1::None => core::mem::transmute::, U1>(
+                    core::mem::MaybeUninit::uninit(),
+                ),
+                discriminant_U1::Some => Self {
+                    Some: self.Some.clone(),
+                },
+            }
+        };
+
+        answer.set_discriminant(self.discriminant());
+
+        answer
+    }
+}
+
+impl core::hash::Hash for U1 {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn hash(&self, state: &mut H) {
+        match self.discriminant() {
+            discriminant_U1::None => discriminant_U1::None.hash(state),
+            discriminant_U1::Some => unsafe {
+                discriminant_U1::Some.hash(state);
+                self.Some.hash(state);
+            },
+        }
+    }
+}
+
+impl core::fmt::Debug for U1 {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.write_str("U1::")?;
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_U1::None => f.write_str("None"),
+                discriminant_U1::Some => f.debug_tuple("Some").field(&self.Some).finish(),
+            }
+        }
+    }
+}
+
+impl RocStructFields {
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_RocStructFields {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(12))
+        }
+    }
+
+    #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_RocStructFields) {
+        let discriminant_ptr: *mut discriminant_RocStructFields =
+            (self as *mut RocStructFields).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(12)) = discriminant;
+        }
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `HasClosure`, with the appropriate payload
+    pub fn HasClosure(arg: roc_std::RocList) -> Self {
+        let mut answer = Self {
+            HasClosure: core::mem::ManuallyDrop::new(arg),
+        };
+
+        answer.set_discriminant(discriminant_RocStructFields::HasClosure);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocStructFields` has a `.discriminant()` of `HasClosure` and convert it to `HasClosure`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `HasClosure`.
+    pub unsafe fn into_HasClosure(mut self) -> roc_std::RocList {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocStructFields::HasClosure
+        );
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.HasClosure,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocStructFields` has a `.discriminant()` of `HasClosure` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `HasClosure`.
+    pub unsafe fn as_HasClosure(&self) -> &roc_std::RocList {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocStructFields::HasClosure
+        );
+        let payload = &self.HasClosure;
+
+        &payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Construct a tag named `HasNoClosure`, with the appropriate payload
+    pub fn HasNoClosure(arg: roc_std::RocList) -> Self {
+        let mut answer = Self {
+            HasNoClosure: core::mem::ManuallyDrop::new(arg),
+        };
+
+        answer.set_discriminant(discriminant_RocStructFields::HasNoClosure);
+
+        answer
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocStructFields` has a `.discriminant()` of `HasNoClosure` and convert it to `HasNoClosure`'s payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `HasNoClosure`.
+    pub unsafe fn into_HasNoClosure(mut self) -> roc_std::RocList {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocStructFields::HasNoClosure
+        );
+        let payload = {
+            let mut uninitialized = core::mem::MaybeUninit::uninit();
+            let swapped = unsafe {
+                core::mem::replace(
+                    &mut self.HasNoClosure,
+                    core::mem::ManuallyDrop::new(uninitialized.assume_init()),
+                )
+            };
+
+            core::mem::forget(self);
+
+            core::mem::ManuallyDrop::into_inner(swapped)
+        };
+
+        payload
+    }
+
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    /// Unsafely assume the given `RocStructFields` has a `.discriminant()` of `HasNoClosure` and return its payload.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `HasNoClosure`.
+    pub unsafe fn as_HasNoClosure(&self) -> &roc_std::RocList {
+        debug_assert_eq!(
+            self.discriminant(),
+            discriminant_RocStructFields::HasNoClosure
+        );
+        let payload = &self.HasNoClosure;
+
+        &payload
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Returns which variant this tag union holds. Note that this never includes a payload!
+    pub fn discriminant(&self) -> discriminant_RocStructFields {
+        unsafe {
+            let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self);
+
+            core::mem::transmute::(*bytes.as_ptr().add(24))
+        }
+    }
+
+    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
+    /// Internal helper
+    fn set_discriminant(&mut self, discriminant: discriminant_RocStructFields) {
+        let discriminant_ptr: *mut discriminant_RocStructFields =
+            (self as *mut RocStructFields).cast();
+
+        unsafe {
+            *(discriminant_ptr.add(24)) = discriminant;
+        }
+    }
+}
+
+impl Drop for RocStructFields {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn drop(&mut self) {
+        // Drop the payloads
+        match self.discriminant() {
+            discriminant_RocStructFields::HasClosure => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.HasClosure)
+            },
+            discriminant_RocStructFields::HasNoClosure => unsafe {
+                core::mem::ManuallyDrop::drop(&mut self.HasNoClosure)
+            },
+        }
+    }
+}
+
+impl Eq for RocStructFields {}
+
+impl PartialEq for RocStructFields {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn eq(&self, other: &Self) -> bool {
+        if self.discriminant() != other.discriminant() {
+            return false;
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocStructFields::HasClosure => self.HasClosure == other.HasClosure,
+                discriminant_RocStructFields::HasNoClosure => {
+                    self.HasNoClosure == other.HasNoClosure
+                }
+            }
+        }
+    }
+}
+
+impl PartialOrd for RocStructFields {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn partial_cmp(&self, other: &Self) -> Option {
+        match self.discriminant().partial_cmp(&other.discriminant()) {
+            Some(core::cmp::Ordering::Equal) => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocStructFields::HasClosure => {
+                    self.HasClosure.partial_cmp(&other.HasClosure)
+                }
+                discriminant_RocStructFields::HasNoClosure => {
+                    self.HasNoClosure.partial_cmp(&other.HasNoClosure)
+                }
+            }
+        }
+    }
+}
+
+impl Ord for RocStructFields {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+        match self.discriminant().cmp(&other.discriminant()) {
+            core::cmp::Ordering::Equal => {}
+            not_eq => return not_eq,
+        }
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocStructFields::HasClosure => self.HasClosure.cmp(&other.HasClosure),
+                discriminant_RocStructFields::HasNoClosure => {
+                    self.HasNoClosure.cmp(&other.HasNoClosure)
+                }
+            }
+        }
+    }
+}
+
+impl Clone for RocStructFields {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn clone(&self) -> Self {
+        let mut answer = unsafe {
+            match self.discriminant() {
+                discriminant_RocStructFields::HasClosure => Self {
+                    HasClosure: self.HasClosure.clone(),
+                },
+                discriminant_RocStructFields::HasNoClosure => Self {
+                    HasNoClosure: self.HasNoClosure.clone(),
+                },
+            }
+        };
+
+        answer.set_discriminant(self.discriminant());
+
+        answer
+    }
+}
+
+impl core::hash::Hash for RocStructFields {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn hash(&self, state: &mut H) {
+        match self.discriminant() {
+            discriminant_RocStructFields::HasClosure => unsafe {
+                discriminant_RocStructFields::HasClosure.hash(state);
+                self.HasClosure.hash(state);
+            },
+            discriminant_RocStructFields::HasNoClosure => unsafe {
+                discriminant_RocStructFields::HasNoClosure.hash(state);
+                self.HasNoClosure.hash(state);
+            },
+        }
+    }
+}
+
+impl core::fmt::Debug for RocStructFields {
+    #[cfg(any(
+        target_arch = "arm",
+        target_arch = "aarch64",
+        target_arch = "wasm32",
+        target_arch = "x86",
+        target_arch = "x86_64"
+    ))]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.write_str("RocStructFields::")?;
+
+        unsafe {
+            match self.discriminant() {
+                discriminant_RocStructFields::HasClosure => f
+                    .debug_tuple("HasClosure")
+                    .field(&*self.HasClosure)
+                    .finish(),
+                discriminant_RocStructFields::HasNoClosure => f
+                    .debug_tuple("HasNoClosure")
+                    .field(&*self.HasNoClosure)
+                    .finish(),
+            }
+        }
+    }
+}
diff --git a/crates/glue/src/rust_glue.rs b/crates/glue/src/rust_glue.rs
index 28b1ea1597..5a27609cac 100644
--- a/crates/glue/src/rust_glue.rs
+++ b/crates/glue/src/rust_glue.rs
@@ -1,4 +1,7 @@
-use crate::types::{RocNum, RocTagUnion, RocType, TypeId, Types};
+use crate::types::{
+    Accessors, File, RocFn, RocNum, RocSingleTagPayload, RocStructFields, RocTagUnion, RocType,
+    TypeId, Types,
+};
 use indexmap::IndexMap;
 use roc_target::{Architecture, TargetInfo};
 use std::fmt::{Display, Write};
@@ -57,13 +60,13 @@ fn add_decl(impls: &mut Impls, opt_impl: Impl, target_info: TargetInfo, body: St
     targets.push(target_info);
 }
 
-pub fn emit(types_and_targets: &[(Types, TargetInfo)]) -> String {
-    let mut buf = String::new();
+pub fn emit(types: &[Types]) -> Vec {
+    let mut buf = std::str::from_utf8(HEADER).unwrap().to_string();
     let mut impls: Impls = IndexMap::default();
 
-    for (types, target_info) in types_and_targets {
+    for types in types {
         for id in types.sorted_ids() {
-            add_type(*target_info, id, types, &mut impls);
+            add_type(types.target(), id, types, &mut impls);
         }
     }
 
@@ -135,7 +138,10 @@ pub fn emit(types_and_targets: &[(Types, TargetInfo)]) -> String {
         }
     }
 
-    buf
+    vec![crate::types::File {
+        name: "mod.rs".to_string(),
+        content: buf,
+    }]
 }
 
 fn add_type(target_info: TargetInfo, id: TypeId, types: &Types, impls: &mut Impls) {
@@ -248,16 +254,9 @@ fn add_type(target_info: TargetInfo, id: TypeId, types: &Types, impls: &mut Impl
                 RocTagUnion::SingleTagStruct {
                     name,
                     tag_name,
-                    payload_fields,
+                    payload,
                 } => {
-                    add_single_tag_struct(
-                        name,
-                        tag_name,
-                        payload_fields,
-                        types,
-                        impls,
-                        target_info,
-                    );
+                    add_single_tag_struct(name, tag_name, payload, types, impls, target_info);
                 }
                 RocTagUnion::NonNullableUnwrapped {
                     name,
@@ -289,21 +288,20 @@ fn add_type(target_info: TargetInfo, id: TypeId, types: &Types, impls: &mut Impl
         | RocType::RocDict(_, _)
         | RocType::RocSet(_)
         | RocType::RocList(_)
-        | RocType::RocBox(_) => {}
+        | RocType::RocBox(_)
+        | RocType::Unsized => {}
         RocType::RecursivePointer { .. } => {
             // This is recursively pointing to a type that should already have been added,
             // so no extra work needs to happen.
         }
-        RocType::Function { .. } => {
-            // TODO actually generate glue functions!
-        }
+        RocType::Function(roc_fn) => add_function(target_info, roc_fn, types, impls),
     }
 }
 
 fn add_single_tag_struct(
     name: &str,
     tag_name: &str,
-    payload_fields: &[TypeId],
+    payload: &RocSingleTagPayload,
     types: &Types,
     impls: &mut IndexMap, IndexMap>>,
     target_info: TargetInfo,
@@ -314,224 +312,277 @@ fn add_single_tag_struct(
     // because they have only one alternative. However, still
     // offer the usual tag union APIs.
     {
-        let derive = derive_str(
-            &RocType::Struct {
-                // Deriving doesn't depend on the struct's name,
-                // so no need to clone name here.
-                name: String::new(),
-                fields: payload_fields
+        let mut buf = String::new();
+
+        // Make a dummy RocType::Struct so that we can pass it to deriving
+        // and have that work out as normal.
+        let struct_type = match payload {
+            RocSingleTagPayload::HasClosure { payload_getters } => {
+                {
+                    if payload_getters.is_empty() {
+                        // A single tag with no payload is a zero-sized unit type, so
+                        // represent it as a zero-sized struct (e.g. "struct Foo()").
+                        buf.push_str("();\n");
+                    } else {
+                        buf.push_str("{\n");
+
+                        for (_index, _getter_fn) in payload_getters.iter().enumerate() {
+                            // TODO these should be added as separate functions in the impl!
+                            todo!("TODO generate payload getters");
+                        }
+
+                        buf.push_str("}\n");
+                    }
+
+                    let fields = payload_getters
+                        .iter()
+                        .map(|(type_id, getter)| {
+                            (
+                                String::new(),
+                                *type_id,
+                                Accessors {
+                                    getter: getter.clone(),
+                                },
+                            )
+                        })
+                        .collect();
+
+                    RocType::Struct {
+                        // Deriving doesn't depend on the struct's name,
+                        // so no need to clone name here.
+                        name: String::new(),
+                        fields: RocStructFields::HasClosure { fields },
+                    }
+                }
+            }
+            RocSingleTagPayload::HasNoClosure {
+                payload_fields: payloads,
+            } => {
+                if payloads.is_empty() {
+                    // A single tag with no payload is a zero-sized unit type, so
+                    // represent it as a zero-sized struct (e.g. "struct Foo()").
+                    buf.push_str("();\n");
+                } else {
+                    buf.push_str("{\n");
+
+                    for (index, field_id) in payloads.iter().enumerate() {
+                        let field_type = type_name(*field_id, types);
+
+                        // These are all private fields, since this is a tag union.
+                        // ignore returned result, writeln can not fail as it is used here
+                        let _ = writeln!(buf, "{INDENT}f{index}: {field_type},");
+                    }
+
+                    buf.push_str("}\n");
+                }
+
+                let fields = payloads
                     .iter()
                     .map(|type_id| (String::new(), *type_id))
-                    .collect(),
-            },
-            types,
-            false,
-        );
+                    .collect();
 
-        let mut body = format!("#[repr(transparent)]\n{derive}\npub struct {name} ");
-
-        if payload_fields.is_empty() {
-            // A single tag with no payload is a zero-sized unit type, so
-            // represent it as a zero-sized struct (e.g. "struct Foo()").
-            body.push_str("();\n");
-        } else {
-            body.push_str("{\n");
-
-            for (index, field_id) in payload_fields.iter().enumerate() {
-                let field_type = type_name(*field_id, types);
-
-                // These are all private fields, since this is a tag union.
-                // ignore returned result, writeln can not fail as it is used here
-                let _ = writeln!(body, "{INDENT}f{index}: {field_type},");
+                RocType::Struct {
+                    // Deriving doesn't depend on the struct's name,
+                    // so no need to clone name here.
+                    name: String::new(),
+                    fields: RocStructFields::HasNoClosure { fields },
+                }
             }
+        };
+        let derive = derive_str(&struct_type, types, false);
 
-            body.push_str("}\n");
-        }
+        let body = format!("#[repr(transparent)]\n{derive}\npub struct {name} {buf}");
 
         add_decl(impls, None, target_info, body);
     }
 
     // the impl for the single-tag union itself
-    {
-        let opt_impl = Some(format!("impl {name}"));
+    match payload {
+        RocSingleTagPayload::HasNoClosure { payload_fields } => {
+            let opt_impl = Some(format!("impl {name}"));
 
-        if payload_fields.is_empty() {
-            add_decl(
-                impls,
-                opt_impl.clone(),
-                target_info,
-                format!(
-                    r#"/// A tag named {tag_name}, which has no payload.
+            if payload_fields.is_empty() {
+                add_decl(
+                    impls,
+                    opt_impl.clone(),
+                    target_info,
+                    format!(
+                        r#"/// A tag named {tag_name}, which has no payload.
         pub const {tag_name}: Self = Self();"#,
-                ),
-            );
-
-            add_decl(
-                impls,
-                opt_impl.clone(),
-                target_info,
-                format!(
-                    r#"/// Other `into_` methods return a payload, but since the {tag_name} tag
-        /// has no payload, this does nothing and is only here for completeness.
-        pub fn into_{tag_name}(self) {{
-            ()
-        }}"#,
-                ),
-            );
-
-            add_decl(
-                impls,
-                opt_impl,
-                target_info,
-                format!(
-                    r#"/// Other `as` methods return a payload, but since the {tag_name} tag
-        /// has no payload, this does nothing and is only here for completeness.
-        pub fn as_{tag_name}(&self) {{
-            ()
-        }}"#,
-                ),
-            );
-        } else {
-            let mut args: Vec = Vec::with_capacity(payload_fields.len());
-            let mut fields: Vec = Vec::with_capacity(payload_fields.len());
-            let mut field_types: Vec = Vec::with_capacity(payload_fields.len());
-            let mut field_access: Vec = Vec::with_capacity(payload_fields.len());
-
-            for (index, field_id) in payload_fields.iter().enumerate() {
-                let field_type = type_name(*field_id, types);
-
-                field_access.push(format!("self.f{index}"));
-                args.push(format!("f{index}: {field_type}"));
-                fields.push(format!("{INDENT}{INDENT}{INDENT}f{index},"));
-                field_types.push(field_type);
-            }
-
-            let args = args.join(", ");
-            let fields = fields.join("\n");
-
-            add_decl(
-                impls,
-                opt_impl.clone(),
-                target_info,
-                format!(
-                    r#"/// A tag named {tag_name}, with the given payload.
-    pub fn {tag_name}({args}) -> Self {{
-        Self {{
-{fields}
-        }}
-    }}"#,
-                ),
-            );
-
-            {
-                // Return a tuple
-                let ret_type = {
-                    let joined = field_types.join(", ");
-
-                    if field_types.len() == 1 {
-                        joined
-                    } else {
-                        format!("({joined})")
-                    }
-                };
-                let ret_expr = {
-                    let joined = field_access.join(", ");
-
-                    if field_access.len() == 1 {
-                        joined
-                    } else {
-                        format!("({joined})")
-                    }
-                };
+                    ),
+                );
 
                 add_decl(
                     impls,
                     opt_impl.clone(),
                     target_info,
                     format!(
-                        r#"/// Since `{tag_name}` only has one tag (namely, `{tag_name}`),
-    /// convert it to `{tag_name}`'s payload.
-    pub fn into_{tag_name}(self) -> {ret_type} {{
-        {ret_expr}
-    }}"#,
+                        r#"/// Other `into_` methods return a payload, but since the {tag_name} tag
+        /// has no payload, this does nothing and is only here for completeness.
+        pub fn into_{tag_name}(self) {{
+            ()
+        }}"#,
                     ),
                 );
-            }
-
-            {
-                // Return a tuple
-                let ret_type = {
-                    let joined = field_types
-                        .iter()
-                        .map(|field_type| format!("&{field_type}"))
-                        .collect::>()
-                        .join(", ");
-
-                    if field_types.len() == 1 {
-                        joined
-                    } else {
-                        format!("({joined})")
-                    }
-                };
-                let ret_expr = {
-                    let joined = field_access
-                        .iter()
-                        .map(|field| format!("&{field}"))
-                        .collect::>()
-                        .join(", ");
-
-                    if field_access.len() == 1 {
-                        joined
-                    } else {
-                        format!("({joined})")
-                    }
-                };
 
                 add_decl(
                     impls,
                     opt_impl,
                     target_info,
                     format!(
-                        r#"/// Since `{tag_name}` only has one tag (namely, `{tag_name}`),
+                        r#"/// Other `as` methods return a payload, but since the {tag_name} tag
+        /// has no payload, this does nothing and is only here for completeness.
+        pub fn as_{tag_name}(&self) {{
+            ()
+        }}"#,
+                    ),
+                );
+            } else {
+                let mut args: Vec = Vec::with_capacity(payload_fields.len());
+                let mut fields: Vec = Vec::with_capacity(payload_fields.len());
+                let mut field_types: Vec = Vec::with_capacity(payload_fields.len());
+                let mut field_access: Vec = Vec::with_capacity(payload_fields.len());
+
+                for (index, field_id) in payload_fields.iter().enumerate() {
+                    let field_type = type_name(*field_id, types);
+
+                    field_access.push(format!("self.f{index}"));
+                    args.push(format!("f{index}: {field_type}"));
+                    fields.push(format!("{INDENT}{INDENT}{INDENT}f{index},"));
+                    field_types.push(field_type);
+                }
+
+                let args = args.join(", ");
+                let fields = fields.join("\n");
+
+                add_decl(
+                    impls,
+                    opt_impl.clone(),
+                    target_info,
+                    format!(
+                        r#"/// A tag named {tag_name}, with the given payload.
+    pub fn {tag_name}({args}) -> Self {{
+        Self {{
+{fields}
+        }}
+    }}"#,
+                    ),
+                );
+
+                {
+                    // Return a tuple
+                    let ret_type = {
+                        let joined = field_types.join(", ");
+
+                        if field_types.len() == 1 {
+                            joined
+                        } else {
+                            format!("({joined})")
+                        }
+                    };
+                    let ret_expr = {
+                        let joined = field_access.join(", ");
+
+                        if field_access.len() == 1 {
+                            joined
+                        } else {
+                            format!("({joined})")
+                        }
+                    };
+
+                    add_decl(
+                        impls,
+                        opt_impl.clone(),
+                        target_info,
+                        format!(
+                            r#"/// Since `{tag_name}` only has one tag (namely, `{tag_name}`),
+    /// convert it to `{tag_name}`'s payload.
+    pub fn into_{tag_name}(self) -> {ret_type} {{
+        {ret_expr}
+    }}"#,
+                        ),
+                    );
+                }
+
+                {
+                    // Return a tuple
+                    let ret_type = {
+                        let joined = field_types
+                            .iter()
+                            .map(|field_type| format!("&{field_type}"))
+                            .collect::>()
+                            .join(", ");
+
+                        if field_types.len() == 1 {
+                            joined
+                        } else {
+                            format!("({joined})")
+                        }
+                    };
+                    let ret_expr = {
+                        let joined = field_access
+                            .iter()
+                            .map(|field| format!("&{field}"))
+                            .collect::>()
+                            .join(", ");
+
+                        if field_access.len() == 1 {
+                            joined
+                        } else {
+                            format!("({joined})")
+                        }
+                    };
+
+                    add_decl(
+                        impls,
+                        opt_impl,
+                        target_info,
+                        format!(
+                            r#"/// Since `{tag_name}` only has one tag (namely, `{tag_name}`),
     /// convert it to `{tag_name}`'s payload.
     pub fn as_{tag_name}(&self) -> {ret_type} {{
         {ret_expr}
     }}"#,
-                    ),
-                );
+                        ),
+                    );
+                }
             }
         }
+        RocSingleTagPayload::HasClosure { payload_getters: _ } => todo!(),
     }
 
     // The Debug impl for the single-tag union
-    {
-        let opt_impl = Some(format!("impl core::fmt::Debug for {name}"));
+    match payload {
+        RocSingleTagPayload::HasNoClosure { payload_fields } => {
+            let opt_impl = Some(format!("impl core::fmt::Debug for {name}"));
 
-        let mut buf =
-            "fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {".to_string();
+            let mut buf = "fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {"
+                .to_string();
 
-        if payload_fields.is_empty() {
-            // ignore returned result, write can not fail as it is used here
-            let _ = write!(buf, "f.write_str(\"{name}::{tag_name}\")");
-        } else {
-            let _ = write!(
-                buf,
-                "\n{INDENT}{INDENT}{INDENT}f.debug_tuple(\"{name}::{tag_name}\")"
-            );
-
-            for (index, _) in payload_fields.iter().enumerate() {
+            if payload_fields.is_empty() {
+                // ignore returned result, write can not fail as it is used here
+                let _ = write!(buf, "f.write_str(\"{name}::{tag_name}\")");
+            } else {
                 let _ = write!(
                     buf,
-                    "{INDENT}{INDENT}{INDENT}{INDENT}.field(&self.f{index})"
+                    "\n{INDENT}{INDENT}{INDENT}f.debug_tuple(\"{name}::{tag_name}\")"
                 );
+
+                for (index, _) in payload_fields.iter().enumerate() {
+                    let _ = write!(
+                        buf,
+                        "{INDENT}{INDENT}{INDENT}{INDENT}.field(&self.f{index})"
+                    );
+                }
+
+                let _ = write!(buf, "{INDENT}{INDENT}{INDENT}{INDENT}.finish()");
             }
 
-            let _ = write!(buf, "{INDENT}{INDENT}{INDENT}{INDENT}.finish()");
+            buf.push_str("    }\n");
+
+            add_decl(impls, opt_impl, target_info, buf);
         }
-
-        buf.push_str("    }\n");
-
-        add_decl(impls, opt_impl, target_info, buf);
+        RocSingleTagPayload::HasClosure { payload_getters: _ } => todo!(),
     }
 }
 
@@ -958,9 +1009,11 @@ pub struct {name} {{
                             "arg".to_string()
                         };
                     }
-                    RocType::Struct { fields, name } => {
-                        let answer =
-                            tag_union_struct_help(name, fields.iter(), *payload_id, types, false);
+                    RocType::Struct {
+                        fields: RocStructFields::HasNoClosure { fields },
+                        name,
+                    } => {
+                        let answer = tag_union_struct_help(name, fields, *payload_id, types, false);
 
                         owned_ret = answer.owned_ret;
                         borrowed_ret = answer.borrowed_ret;
@@ -969,9 +1022,11 @@ pub struct {name} {{
                         payload_args = answer.payload_args;
                         args_to_payload = answer.args_to_payload;
                     }
-                    RocType::TagUnionPayload { fields, name } => {
-                        let answer =
-                            tag_union_struct_help(name, fields.iter(), *payload_id, types, true);
+                    RocType::TagUnionPayload {
+                        fields: RocStructFields::HasNoClosure { fields },
+                        name,
+                    } => {
+                        let answer = tag_union_struct_help(name, fields, *payload_id, types, true);
 
                         owned_ret = answer.owned_ret;
                         borrowed_ret = answer.borrowed_ret;
@@ -980,7 +1035,102 @@ pub struct {name} {{
                         payload_args = answer.payload_args;
                         args_to_payload = answer.args_to_payload;
                     }
-                    RocType::Function { .. } => todo!(),
+                    RocType::Struct {
+                        fields: RocStructFields::HasClosure { fields: _ },
+                        name: _,
+                    } => {
+                        todo!("struct");
+                    }
+                    RocType::TagUnionPayload {
+                        fields: RocStructFields::HasClosure { fields },
+                        name: _, // TODO call this payload_struct_name and use it to define the struct...or don't define it at all, maybe, since there are only getters and setters?
+                    } => {
+                        // TODO don't generate op.into_StdoutWrite() - only getters/setters instead!
+                        for (field_name, field, accessor) in fields {
+                            let getter_name = &accessor.getter;
+                            let ret = type_name(*field, types);
+                            let returns_via_pointer = true;
+
+                            let body = if let RocType::Function(_) = types.get_type(*field) {
+                                format!(
+                                    r#"
+                                    extern "C" {{
+                                        #[link_name = "{getter_name}_size"]
+                                        fn size() -> usize;
+
+                                        #[link_name = "{getter_name}_generic"]
+                                        fn getter(_: *mut u8, _: *const {name});
+                                    }}
+
+                                    // allocate memory to store this variably-sized value
+                                    // allocates with roc_alloc, but that likely still uses the heap
+                                    let it = std::iter::repeat(0xAAu8).take(size());
+                                    let mut bytes = roc_std::RocList::from_iter(it);
+
+                                    getter(bytes.as_mut_ptr(), self);
+
+                                    {ret} {{
+                                        closure_data: bytes,
+                                    }}
+                                    "#
+                                )
+                            } else if returns_via_pointer {
+                                format!(
+                                    r#"
+                                    extern "C" {{
+                                        #[link_name = "{getter_name}_generic"]
+                                        fn getter(_: *mut {ret}, _: *const {name});
+                                    }}
+
+                                    let mut ret = core::mem::MaybeUninit::uninit();
+                                    getter(ret.as_mut_ptr(), self);
+                                    ret.assume_init()
+                                    "#
+                                )
+                            } else {
+                                format!(
+                                    r#"
+                                    extern "C" {{
+                                        #[link_name = "{getter_name}"]
+                                        fn getter(_: *const {name}) -> {ret};
+                                    }}
+
+                                    getter(self)
+                                    "#
+                                )
+                            };
+
+                            add_decl(
+                                impls,
+                                opt_impl.clone(),
+                                target_info,
+                                format!(
+                                    r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{tag_name}` and return its payload at index {field_name}.
+    /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
+    /// Panics in debug builds if the `.discriminant()` doesn't return `{tag_name}`.
+    pub unsafe fn get_{tag_name}_{field_name}(&self) -> {ret} {{
+        debug_assert_eq!(self.discriminant(), {discriminant_name}::{tag_name});
+
+        {body}
+    }}"#,
+                                ),
+                            );
+                        }
+
+                        // TODO revise these - they're all copy/pasted from somewhere else
+                        owned_ret_type = type_name(*payload_id, types);
+                        borrowed_ret_type = format!("&{}", owned_ret_type);
+                        owned_ret = "payload".to_string();
+                        borrowed_ret = format!("&{owned_ret}");
+                        payload_args = format!("arg: {owned_ret_type}");
+                        args_to_payload = if cannot_derive_copy(payload_type, types) {
+                            "core::mem::ManuallyDrop::new(arg)".to_string()
+                        } else {
+                            "arg".to_string()
+                        };
+                    }
+                    RocType::Unsized => todo!(),
+                    RocType::Function(RocFn { .. }) => todo!(),
                 };
 
                 {
@@ -1047,7 +1197,7 @@ pub struct {name} {{
                         )
                     } else {
                         format!(
-                            r#"/// Unsafely assume the given `{name}` has a `.discriminant()` of `{tag_name}` and convert it to `{tag_name}`'s payload.
+                            r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{tag_name}` and convert it to `{tag_name}`'s payload.
             /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
             /// Panics in debug builds if the `.discriminant()` doesn't return `{tag_name}`.
             pub unsafe fn into_{tag_name}({self_for_into}) -> {owned_ret_type} {{
@@ -1078,7 +1228,7 @@ pub struct {name} {{
                         )
                     } else {
                         format!(
-                            r#"/// Unsafely assume the given `{name}` has a `.discriminant()` of `{tag_name}` and return its payload.
+                            r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{tag_name}` and return its payload.
             /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
             /// Panics in debug builds if the `.discriminant()` doesn't return `{tag_name}`.
             pub unsafe fn as_{tag_name}(&self) -> {borrowed_ret_type} {{
@@ -1579,16 +1729,24 @@ pub struct {name} {{
                             RocType::TagUnionPayload { fields, .. } => {
                                 let mut buf = Vec::new();
 
-                                for (label, _) in fields {
-                                    // Needs an "f" prefix
-                                    buf.push(format!(
+                                match fields {
+                                    RocStructFields::HasNoClosure { fields } => {
+                                        for (label, _) in fields {
+                                            // Needs an "f" prefix
+                                            buf.push(format!(
                                         ".field(&({deref_str}{actual_self}.{tag_name}).f{label})"
                                     ));
+                                        }
+                                    }
+                                    RocStructFields::HasClosure { fields: _ } => {
+                                        buf.push("// TODO HAS CLOSURE".to_string());
+                                    }
                                 }
 
                                 buf.join("\n")
                             }
-                            RocType::Function { .. } => todo!(),
+                            RocType::Unsized => todo!(),
+                            RocType::Function(RocFn { .. }) => todo!(),
                         };
 
                         format!(
@@ -1681,10 +1839,104 @@ fn add_enumeration, S: AsRef + Display>(
     add_decl(impls, None, target_info, buf);
 }
 
-fn add_struct(
+fn add_function(
+    // name: &str,
+    target_info: TargetInfo,
+    roc_fn: &RocFn,
+    types: &Types,
+    impls: &mut Impls,
+) {
+    let name = escape_kw(roc_fn.function_name.to_string());
+    // let derive = derive_str(types.get_type(struct_id), types, true);
+    let derive = "";
+    let pub_str = "pub ";
+    let mut buf;
+
+    let repr = "C";
+    buf = format!("{derive}\n#[repr({repr})]\n{pub_str}struct {name} {{\n");
+
+    let fields = [("closure_data", &roc_fn.lambda_set)];
+
+    for (label, type_id) in fields {
+        let type_str = type_name(*type_id, types);
+
+        // Tag union payloads have numbered fields, so we prefix them
+        // with an "f" because Rust doesn't allow struct fields to be numbers.
+        let label = escape_kw(label.to_string());
+
+        writeln!(buf, "{INDENT}pub {label}: {type_str},",).unwrap();
+    }
+
+    buf.push('}');
+
+    buf.push('\n');
+    buf.push('\n');
+
+    let extern_name = &roc_fn.extern_name;
+
+    let return_type_str = type_name(roc_fn.ret, types);
+
+    writeln!(buf, "impl {name} {{").unwrap();
+
+    write!(buf, "{INDENT}pub fn force_thunk(mut self").unwrap();
+    for (i, argument_type) in roc_fn.args.iter().enumerate() {
+        write!(buf, ", arg_{i}: {}", type_name(*argument_type, types)).unwrap();
+    }
+    writeln!(buf, ") -> {return_type_str} {{").unwrap();
+
+    writeln!(buf, "{INDENT}{INDENT}extern \"C\" {{").unwrap();
+
+    // fn extern_name(output: *mut return_type, arg1: arg1_type, ..., closure_data: *mut u8);
+    write!(buf, "{INDENT}{INDENT}{INDENT} fn {extern_name}(").unwrap();
+
+    for (i, argument_type) in roc_fn.args.iter().enumerate() {
+        write!(buf, "arg_{i}: &{}, ", type_name(*argument_type, types)).unwrap();
+    }
+
+    writeln!(
+        buf,
+        "closure_data: *mut u8, output: *mut {return_type_str});"
+    )
+    .unwrap();
+
+    // {argument_types} "
+
+    writeln!(buf, "{INDENT}{INDENT}}}").unwrap();
+
+    writeln!(buf).unwrap();
+
+    writeln!(
+        buf,
+        "{INDENT}{INDENT}let mut output = std::mem::MaybeUninit::uninit();"
+    )
+    .unwrap();
+
+    writeln!(
+        buf,
+        "{INDENT}{INDENT}let ptr = self.closure_data.as_mut_ptr();"
+    )
+    .unwrap();
+
+    write!(buf, "{INDENT}{INDENT}unsafe {{ {extern_name}(").unwrap();
+
+    for (i, _) in roc_fn.args.iter().enumerate() {
+        write!(buf, "&arg_{i}, ").unwrap();
+    }
+
+    writeln!(buf, "ptr, output.as_mut_ptr()) }};").unwrap();
+
+    writeln!(buf, "{INDENT}{INDENT}unsafe {{ output.assume_init() }}").unwrap();
+
+    writeln!(buf, "{INDENT}}}").unwrap();
+    buf.push('}');
+
+    add_decl(impls, None, target_info, buf);
+}
+
+fn add_struct(
     name: &str,
     target_info: TargetInfo,
-    fields: &[(S, TypeId)],
+    fields: &RocStructFields,
     struct_id: TypeId,
     types: &Types,
     impls: &mut Impls,
@@ -1693,29 +1945,39 @@ fn add_struct(
     let name = escape_kw(name.to_string());
     let derive = derive_str(types.get_type(struct_id), types, true);
     let pub_str = if is_tag_union_payload { "" } else { "pub " };
-    let repr = if fields.len() == 1 {
-        "transparent"
-    } else {
-        "C"
-    };
-    let mut buf = format!("{derive}\n#[repr({repr})]\n{pub_str}struct {name} {{\n");
+    let mut buf;
 
-    for (label, type_id) in fields {
-        let type_str = type_name(*type_id, types);
+    match fields {
+        RocStructFields::HasNoClosure { fields } => {
+            let repr = if fields.len() == 1 {
+                "transparent"
+            } else {
+                "C"
+            };
 
-        // Tag union payloads have numbered fields, so we prefix them
-        // with an "f" because Rust doesn't allow struct fields to be numbers.
-        let label = if is_tag_union_payload {
-            format!("f{label}")
-        } else {
-            escape_kw(label.to_string())
-        };
+            buf = format!("{derive}\n#[repr({repr})]\n{pub_str}struct {name} {{\n");
 
-        writeln!(buf, "{INDENT}pub {label}: {type_str},",).unwrap();
+            for (label, type_id) in fields {
+                let type_str = type_name(*type_id, types);
+
+                // Tag union payloads have numbered fields, so we prefix them
+                // with an "f" because Rust doesn't allow struct fields to be numbers.
+                let label = if is_tag_union_payload {
+                    format!("f{label}")
+                } else {
+                    escape_kw(label.to_string())
+                };
+
+                writeln!(buf, "{INDENT}pub {label}: {type_str},",).unwrap();
+            }
+
+            buf.push('}');
+        }
+        RocStructFields::HasClosure { fields: _ } => {
+            buf = "//TODO HAS CLOSURE 2".to_string();
+        }
     }
 
-    buf.push('}');
-
     add_decl(impls, None, target_info, buf);
 }
 
@@ -1746,6 +2008,7 @@ fn type_name(id: TypeId, types: &Types) -> String {
         RocType::RocSet(elem_id) => format!("roc_std::RocSet<{}>", type_name(*elem_id, types)),
         RocType::RocList(elem_id) => format!("roc_std::RocList<{}>", type_name(*elem_id, types)),
         RocType::RocBox(elem_id) => format!("roc_std::RocBox<{}>", type_name(*elem_id, types)),
+        RocType::Unsized => "roc_std::RocList".to_string(),
         RocType::RocResult(ok_id, err_id) => {
             format!(
                 "roc_std::RocResult<{}, {}>",
@@ -1763,7 +2026,7 @@ fn type_name(id: TypeId, types: &Types) -> String {
         | RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { name, .. })
         | RocType::TagUnion(RocTagUnion::SingleTagStruct { name, .. }) => escape_kw(name.clone()),
         RocType::RecursivePointer(content) => type_name(*content, types),
-        RocType::Function { name, .. } => escape_kw(name.clone()),
+        RocType::Function(RocFn { function_name, .. }) => escape_kw(function_name.clone()),
     }
 }
 
@@ -1771,27 +2034,136 @@ fn type_name(id: TypeId, types: &Types) -> String {
 /// case of a struct that's a payload for a recursive tag union, typ.has_enumeration()
 /// will return true, but actually we want to derive Debug here anyway.
 fn derive_str(typ: &RocType, types: &Types, include_debug: bool) -> String {
-    let mut buf = "#[derive(Clone, ".to_string();
+    let mut derives = vec!["Clone"];
 
     if !cannot_derive_copy(typ, types) {
-        buf.push_str("Copy, ");
+        derives.push("Copy");
     }
 
     if include_debug {
-        buf.push_str("Debug, ");
+        derives.push("Debug");
     }
 
-    if !cannot_derive_default(typ, types) {
-        buf.push_str("Default, ");
+    if !has_functions(typ, types) {
+        derives.push("PartialEq");
+        derives.push("PartialOrd");
+
+        if !has_float(typ, types) {
+            derives.push("Eq");
+            derives.push("Ord");
+            derives.push("Hash");
+        }
+
+        if !cannot_derive_default(typ, types) {
+            derives.push("Default");
+        }
     }
 
-    if !has_float(typ, types) {
-        buf.push_str("Eq, Ord, Hash, ");
+    derives.sort();
+
+    format!("#[derive({})]", derives.join(", "))
+}
+
+fn has_functions(start: &RocType, types: &Types) -> bool {
+    let mut seen: Vec = vec![];
+    let mut stack = vec![start];
+
+    macro_rules! push {
+        ($id:expr) => {{
+            if !seen.contains($id) {
+                seen.push(*$id);
+                stack.push(types.get_type(*$id));
+            }
+        }};
     }
 
-    buf.push_str("PartialEq, PartialOrd)]");
+    while let Some(typ) = stack.pop() {
+        match typ {
+            RocType::RocStr
+            | RocType::Bool
+            | RocType::Unit
+            | RocType::Num(_)
+            | RocType::EmptyTagUnion
+            | RocType::Unsized
+            | RocType::TagUnion(RocTagUnion::Enumeration { .. }) => { /* terminal */ }
 
-    buf
+            RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. })
+            | RocType::TagUnion(RocTagUnion::Recursive { tags, .. })
+            | RocType::TagUnion(RocTagUnion::NullableWrapped { tags, .. }) => {
+                for (_, opt_payload_id) in tags.iter() {
+                    if let Some(payload_id) = opt_payload_id {
+                        push!(payload_id);
+                    }
+                }
+            }
+            RocType::RocBox(type_id)
+            | RocType::RocList(type_id)
+            | RocType::RocSet(type_id)
+            | RocType::TagUnion(RocTagUnion::NullableUnwrapped {
+                non_null_payload: type_id,
+                ..
+            })
+            | RocType::TagUnion(RocTagUnion::NonNullableUnwrapped {
+                payload: type_id, ..
+            }) => {
+                push!(type_id);
+            }
+
+            RocType::TagUnion(RocTagUnion::SingleTagStruct {
+                payload: RocSingleTagPayload::HasNoClosure { payload_fields },
+                ..
+            }) => {
+                for payload_id in payload_fields {
+                    push!(payload_id);
+                }
+            }
+
+            RocType::TagUnion(RocTagUnion::SingleTagStruct {
+                payload: RocSingleTagPayload::HasClosure { payload_getters },
+                ..
+            }) => {
+                for (payload_id, _) in payload_getters {
+                    push!(payload_id);
+                }
+            }
+
+            RocType::TagUnionPayload {
+                fields: RocStructFields::HasNoClosure { fields },
+                ..
+            }
+            | RocType::Struct {
+                fields: RocStructFields::HasNoClosure { fields },
+                ..
+            } => {
+                for (_, type_id) in fields {
+                    push!(type_id);
+                }
+            }
+
+            RocType::TagUnionPayload {
+                fields: RocStructFields::HasClosure { fields },
+                ..
+            }
+            | RocType::Struct {
+                fields: RocStructFields::HasClosure { fields },
+                ..
+            } => {
+                for (_, type_id, _) in fields {
+                    push!(type_id);
+                }
+            }
+            RocType::RecursivePointer(type_id) => {
+                push!(type_id);
+            }
+            RocType::RocResult(id1, id2) | RocType::RocDict(id1, id2) => {
+                push!(id1);
+                push!(id2);
+            }
+            RocType::Function(_) => return true,
+        }
+    }
+
+    false
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -1889,29 +2261,36 @@ pub struct {name} {{
                 owned_ret = "payload".to_string();
                 borrowed_ret = format!("&{owned_ret}");
             }
-            RocType::Struct { fields, name } => {
-                let answer =
-                    tag_union_struct_help(name, fields.iter(), non_null_payload, types, false);
+            RocType::Struct { fields, name } => match fields {
+                RocStructFields::HasNoClosure { fields } => {
+                    let answer =
+                        tag_union_struct_help(name, fields, non_null_payload, types, false);
 
-                payload_args = answer.payload_args;
-                args_to_payload = answer.args_to_payload;
-                owned_ret = answer.owned_ret;
-                borrowed_ret = answer.borrowed_ret;
-                owned_ret_type = answer.owned_ret_type;
-                borrowed_ret_type = answer.borrowed_ret_type;
-            }
-            RocType::TagUnionPayload { fields, name } => {
-                let answer =
-                    tag_union_struct_help(name, fields.iter(), non_null_payload, types, true);
+                    payload_args = answer.payload_args;
+                    args_to_payload = answer.args_to_payload;
+                    owned_ret = answer.owned_ret;
+                    borrowed_ret = answer.borrowed_ret;
+                    owned_ret_type = answer.owned_ret_type;
+                    borrowed_ret_type = answer.borrowed_ret_type;
+                }
 
-                payload_args = answer.payload_args;
-                args_to_payload = answer.args_to_payload;
-                owned_ret = answer.owned_ret;
-                borrowed_ret = answer.borrowed_ret;
-                owned_ret_type = answer.owned_ret_type;
-                borrowed_ret_type = answer.borrowed_ret_type;
-            }
+                RocStructFields::HasClosure { .. } => todo!(),
+            },
+            RocType::TagUnionPayload { fields, name } => match fields {
+                RocStructFields::HasNoClosure { fields } => {
+                    let answer = tag_union_struct_help(name, fields, non_null_payload, types, true);
+
+                    payload_args = answer.payload_args;
+                    args_to_payload = answer.args_to_payload;
+                    owned_ret = answer.owned_ret;
+                    borrowed_ret = answer.borrowed_ret;
+                    owned_ret_type = answer.owned_ret_type;
+                    borrowed_ret_type = answer.borrowed_ret_type;
+                }
+                RocStructFields::HasClosure { .. } => todo!(),
+            },
             RocType::Function { .. } => todo!(),
+            RocType::Unsized => todo!(),
         };
 
         // Add a convenience constructor function for the tag with the payload, e.g.
@@ -1986,7 +2365,7 @@ pub struct {name} {{
                 opt_impl.clone(),
                 target_info,
                 format!(
-                    r#"/// Unsafely assume the given `{name}` has a `.discriminant()` of `{non_null_tag}` and convert it to `{non_null_tag}`'s payload.
+                    r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{non_null_tag}` and convert it to `{non_null_tag}`'s payload.
     /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
     /// Panics in debug builds if the `.discriminant()` doesn't return {non_null_tag}.
     pub unsafe fn into_{non_null_tag}(self) -> {owned_ret_type} {{
@@ -2005,7 +2384,7 @@ pub struct {name} {{
             opt_impl.clone(),
             target_info,
             format!(
-                r#"/// Unsafely assume the given `{name}` has a `.discriminant()` of `{non_null_tag}` and return its payload.
+                r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{non_null_tag}` and return its payload.
     /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
     /// Panics in debug builds if the `.discriminant()` doesn't return `{non_null_tag}`.
     pub unsafe fn as_{non_null_tag}(&self) -> {borrowed_ret_type} {{
@@ -2138,8 +2517,13 @@ pub struct {name} {{
             RocType::Struct { fields, .. } => {
                 let mut buf = Vec::new();
 
-                for (label, _) in fields {
-                    buf.push(format!(".field(&(&*{extra_deref}self.pointer).{label})"));
+                match fields {
+                    RocStructFields::HasNoClosure { fields } => {
+                        for (label, _) in fields {
+                            buf.push(format!(".field(&(&*{extra_deref}self.pointer).{label})"));
+                        }
+                    }
+                    RocStructFields::HasClosure { fields: _ } => todo!(),
                 }
 
                 buf.join(&format!("\n{INDENT}{INDENT}{INDENT}{INDENT}{INDENT}"))
@@ -2147,13 +2531,19 @@ pub struct {name} {{
             RocType::TagUnionPayload { fields, .. } => {
                 let mut buf = Vec::new();
 
-                for (label, _) in fields {
-                    // Needs an "f" prefix
-                    buf.push(format!(".field(&(&*{extra_deref}self.pointer).f{label})"));
+                match fields {
+                    RocStructFields::HasNoClosure { fields } => {
+                        for (label, _) in fields {
+                            // Needs an "f" prefix
+                            buf.push(format!(".field(&(&*{extra_deref}self.pointer).f{label})"));
+                        }
+                    }
+                    RocStructFields::HasClosure { fields: _ } => todo!(),
                 }
 
                 buf.join(&format!("\n{INDENT}{INDENT}{INDENT}{INDENT}{INDENT}"))
             }
+            RocType::Unsized => todo!(),
             RocType::Function { .. } => todo!(),
         };
 
@@ -2221,17 +2611,13 @@ struct StructIngredients {
     borrowed_ret_type: String,
 }
 
-fn tag_union_struct_help<'a, I: Iterator, L: Display + PartialOrd + 'a>(
+fn tag_union_struct_help(
     name: &str,
-    fields: I,
+    fields: &[(String, TypeId)],
     payload_id: TypeId,
     types: &Types,
     is_tag_union_payload: bool,
 ) -> StructIngredients {
-    let mut sorted_fields = fields.collect::>();
-
-    sorted_fields.sort_by(|(label1, _), (label2, _)| label1.partial_cmp(label2).unwrap());
-
     let mut ret_types = if is_tag_union_payload {
         // This will be a tuple we create when iterating over the fields.
         Vec::new()
@@ -2241,13 +2627,13 @@ fn tag_union_struct_help<'a, I: Iterator, L: Display + P
 
     let mut ret_values = Vec::new();
 
-    for (label, type_id) in sorted_fields.iter() {
+    for (label, type_id) in fields.iter() {
         let label = if is_tag_union_payload {
             // Tag union payload fields need "f" prefix
             // because they're numbers
             format!("f{}", label)
         } else {
-            escape_kw(format!("{}", label))
+            escape_kw(label.to_string())
         };
 
         ret_values.push(format!("payload.{label}"));
@@ -2266,7 +2652,7 @@ fn tag_union_struct_help<'a, I: Iterator, L: Display + P
         .collect::>()
         .join(", ");
     let args_to_payload = if is_tag_union_payload {
-        let prefixed_fields = sorted_fields
+        let prefixed_fields = fields
             .iter()
             .enumerate()
             .map(|(index, (label, _))| {
@@ -2361,7 +2747,16 @@ fn cannot_derive_default(roc_type: &RocType, types: &Types) -> bool {
         | RocType::TagUnion { .. }
         | RocType::RocResult(_, _)
         | RocType::RecursivePointer { .. }
-        | RocType::Function { .. } => true,
+        | RocType::Struct {
+            fields: RocStructFields::HasClosure { .. },
+            ..
+        }
+        | RocType::TagUnionPayload {
+            fields: RocStructFields::HasClosure { .. },
+            ..
+        }
+        | RocType::Unsized => true,
+        RocType::Function { .. } => true,
         RocType::RocStr | RocType::Bool | RocType::Num(_) => false,
         RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
             cannot_derive_default(types.get_type(*id), types)
@@ -2370,10 +2765,16 @@ fn cannot_derive_default(roc_type: &RocType, types: &Types) -> bool {
             cannot_derive_default(types.get_type(*key_id), types)
                 || cannot_derive_default(types.get_type(*val_id), types)
         }
-        RocType::Struct { fields, .. } => fields
+        RocType::Struct {
+            fields: RocStructFields::HasNoClosure { fields },
+            ..
+        } => fields
             .iter()
             .any(|(_, type_id)| cannot_derive_default(types.get_type(*type_id), types)),
-        RocType::TagUnionPayload { fields, .. } => fields
+        RocType::TagUnionPayload {
+            fields: RocStructFields::HasNoClosure { fields },
+            ..
+        } => fields
             .iter()
             .any(|(_, type_id)| cannot_derive_default(types.get_type(*type_id), types)),
     }
@@ -2387,6 +2788,7 @@ fn cannot_derive_copy(roc_type: &RocType, types: &Types) -> bool {
         | RocType::Bool
         | RocType::Num(_)
         | RocType::TagUnion(RocTagUnion::Enumeration { .. })
+        | RocType::Unsized
         | RocType::Function { .. } => false,
         RocType::RocStr
         | RocType::RocList(_)
@@ -2398,9 +2800,18 @@ fn cannot_derive_copy(roc_type: &RocType, types: &Types) -> bool {
         | RocType::TagUnion(RocTagUnion::Recursive { .. })
         | RocType::RecursivePointer { .. }
         | RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { .. }) => true,
-        RocType::TagUnion(RocTagUnion::SingleTagStruct { payload_fields, .. }) => payload_fields
+        RocType::TagUnion(RocTagUnion::SingleTagStruct {
+            payload: RocSingleTagPayload::HasNoClosure { payload_fields },
+            ..
+        }) => payload_fields
             .iter()
             .any(|type_id| cannot_derive_copy(types.get_type(*type_id), types)),
+        RocType::TagUnion(RocTagUnion::SingleTagStruct {
+            payload: RocSingleTagPayload::HasClosure { payload_getters },
+            ..
+        }) => payload_getters
+            .iter()
+            .any(|(type_id, _)| cannot_derive_copy(types.get_type(*type_id), types)),
         RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => {
             tags.iter().any(|(_, payloads)| {
                 payloads
@@ -2412,12 +2823,26 @@ fn cannot_derive_copy(roc_type: &RocType, types: &Types) -> bool {
             cannot_derive_copy(types.get_type(*ok_id), types)
                 || cannot_derive_copy(types.get_type(*err_id), types)
         }
-        RocType::Struct { fields, .. } => fields
+        RocType::Struct {
+            fields: RocStructFields::HasNoClosure { fields },
+            ..
+        }
+        | RocType::TagUnionPayload {
+            fields: RocStructFields::HasNoClosure { fields },
+            ..
+        } => fields
             .iter()
             .any(|(_, type_id)| cannot_derive_copy(types.get_type(*type_id), types)),
-        RocType::TagUnionPayload { fields, .. } => fields
+        RocType::Struct {
+            fields: RocStructFields::HasClosure { fields },
+            ..
+        }
+        | RocType::TagUnionPayload {
+            fields: RocStructFields::HasClosure { fields },
+            ..
+        } => fields
             .iter()
-            .any(|(_, type_id)| cannot_derive_copy(types.get_type(*type_id), types)),
+            .any(|(_, type_id, _)| cannot_derive_copy(types.get_type(*type_id), types)),
     }
 }
 
@@ -2441,7 +2866,8 @@ fn has_float_help(roc_type: &RocType, types: &Types, do_not_recurse: &[TypeId])
         | RocType::RocStr
         | RocType::Bool
         | RocType::TagUnion(RocTagUnion::Enumeration { .. })
-        | RocType::Function { .. } => false,
+        | RocType::Function { .. }
+        | RocType::Unsized => false,
         RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
             has_float_help(types.get_type(*id), types, do_not_recurse)
         }
@@ -2453,30 +2879,61 @@ fn has_float_help(roc_type: &RocType, types: &Types, do_not_recurse: &[TypeId])
             has_float_help(types.get_type(*key_id), types, do_not_recurse)
                 || has_float_help(types.get_type(*val_id), types, do_not_recurse)
         }
-        RocType::Struct { fields, .. } => fields
+        RocType::TagUnionPayload {
+            fields: RocStructFields::HasNoClosure { fields },
+            name: _,
+        }
+        | RocType::Struct {
+            fields: RocStructFields::HasNoClosure { fields },
+            name: _,
+        } => fields
             .iter()
             .any(|(_, type_id)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
-        RocType::TagUnionPayload { fields, .. } => fields
+        RocType::TagUnionPayload {
+            fields: RocStructFields::HasClosure { fields },
+            name: _,
+        }
+        | RocType::Struct {
+            fields: RocStructFields::HasClosure { fields },
+            name: _,
+        } => fields
             .iter()
-            .any(|(_, type_id)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
-        RocType::TagUnion(RocTagUnion::SingleTagStruct { payload_fields, .. }) => payload_fields
+            .any(|(_, type_id, _)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
+        RocType::TagUnion(RocTagUnion::SingleTagStruct {
+            payload: RocSingleTagPayload::HasNoClosure { payload_fields },
+            ..
+        }) => payload_fields
             .iter()
             .any(|type_id| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
-        RocType::TagUnion(RocTagUnion::Recursive { tags, .. })
-        | RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => {
-            tags.iter().any(|(_, payloads)| {
-                payloads
-                    .iter()
-                    .any(|id| has_float_help(types.get_type(*id), types, do_not_recurse))
-            })
-        }
-        RocType::TagUnion(RocTagUnion::NullableWrapped { tags, .. }) => {
-            tags.iter().any(|(_, payloads)| {
-                payloads
-                    .iter()
-                    .any(|id| has_float_help(types.get_type(*id), types, do_not_recurse))
-            })
-        }
+        RocType::TagUnion(RocTagUnion::SingleTagStruct {
+            payload: RocSingleTagPayload::HasClosure { payload_getters },
+            ..
+        }) => payload_getters
+            .iter()
+            .any(|(type_id, _)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
+        RocType::TagUnion(RocTagUnion::Recursive {
+            tags,
+            name: _,
+            discriminant_offset: _,
+            discriminant_size: _,
+        })
+        | RocType::TagUnion(RocTagUnion::NonRecursive {
+            tags,
+            name: _,
+            discriminant_offset: _,
+            discriminant_size: _,
+        })
+        | RocType::TagUnion(RocTagUnion::NullableWrapped {
+            tags,
+            name: _,
+            index_of_null_tag: _,
+            discriminant_size: _,
+            discriminant_offset: _,
+        }) => tags.iter().any(|(_, payloads)| {
+            payloads
+                .iter()
+                .any(|id| has_float_help(types.get_type(*id), types, do_not_recurse))
+        }),
         RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { payload, .. })
         | RocType::TagUnion(RocTagUnion::NullableUnwrapped {
             non_null_payload: payload,
diff --git a/crates/glue/src/types.rs b/crates/glue/src/types.rs
index 47fce83389..25ef3dcb6b 100644
--- a/crates/glue/src/types.rs
+++ b/crates/glue/src/types.rs
@@ -1,4 +1,5 @@
 use crate::enums::Enums;
+use crate::roc_type;
 use crate::structs::Structs;
 use bumpalo::Bump;
 use fnv::FnvHashMap;
@@ -6,22 +7,32 @@ use roc_builtins::bitcode::{
     FloatWidth::*,
     IntWidth::{self, *},
 };
-use roc_collections::VecMap;
+use roc_collections::{MutMap, VecMap};
 use roc_module::{
     ident::TagName,
     symbol::{Interns, Symbol},
 };
-use roc_mono::layout::{
-    cmp_fields, ext_var_is_empty_tag_union, round_up_to_alignment, Builtin, Discriminant, InLayout,
-    Layout, LayoutCache, LayoutInterner, TLLayoutInterner, UnionLayout,
+use roc_mono::{
+    ir::LambdaSetId,
+    layout::{
+        cmp_fields, ext_var_is_empty_tag_union, round_up_to_alignment, Builtin, Discriminant,
+        InLayout, Layout, LayoutCache, LayoutInterner, TLLayoutInterner, UnionLayout,
+    },
 };
-use roc_target::TargetInfo;
+use roc_target::{Architecture, OperatingSystem, TargetInfo};
 use roc_types::{
-    subs::{Content, FlatType, GetSubsSlice, Subs, UnionLabels, UnionTags, Variable},
+    subs::{Content, FlatType, GetSubsSlice, Label, Subs, UnionLabels, Variable},
     types::{AliasKind, RecordField},
 };
+use std::convert::From;
 use std::fmt::Display;
 
+#[derive(Debug, PartialEq, Eq)]
+pub struct File {
+    pub name: String,
+    pub content: String,
+}
+
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub struct TypeId(usize);
 
@@ -35,6 +46,8 @@ impl TypeId {
     const MAX: Self = Self(Self::PENDING.0 - 1);
 }
 
+// TODO: remove this and instead generate directly into roc_type::Types
+// Probably want to fix roc_std::RocDict and update roc_type::Types to use it first.
 #[derive(Debug, Clone)]
 pub struct Types {
     // These are all indexed by TypeId
@@ -42,6 +55,8 @@ pub struct Types {
     sizes: Vec,
     aligns: Vec,
 
+    entry_points: Vec<(String, TypeId)>,
+
     // Needed to check for duplicates
     types_by_name: FnvHashMap,
 
@@ -49,19 +64,56 @@ pub struct Types {
     /// 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>,
+    target: TargetInfo,
 }
 
 impl Types {
-    pub fn with_capacity(cap: usize) -> Self {
+    pub fn with_capacity(cap: usize, target_info: TargetInfo) -> Self {
         Self {
+            target: target_info,
             types: Vec::with_capacity(cap),
             types_by_name: FnvHashMap::with_capacity_and_hasher(10, Default::default()),
+            entry_points: Vec::new(),
             sizes: Vec::new(),
             aligns: Vec::new(),
             deps: VecMap::with_capacity(cap),
         }
     }
 
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn new<'a, I: Iterator>(
+        arena: &'a Bump,
+        subs: &'a Subs,
+        variables: I,
+        interns: &'a Interns,
+        glue_procs_by_layout: MutMap, &'a [String]>,
+        layout_cache: LayoutCache<'a>,
+        target: TargetInfo,
+    ) -> Self {
+        let mut types = Self::with_capacity(variables.size_hint().0, target);
+        let mut env = Env::new(
+            arena,
+            subs,
+            interns,
+            layout_cache.interner,
+            glue_procs_by_layout,
+            target,
+        );
+
+        for var in variables {
+            env.lambda_set_ids = env.find_lambda_sets(var);
+            env.add_type(var, &mut types);
+        }
+
+        env.resolve_pending_recursive_types(&mut types);
+
+        types
+    }
+
+    pub fn entry_points(&self) -> &[(String, TypeId)] {
+        self.entry_points.as_slice()
+    }
+
     pub fn is_equivalent(&self, a: &RocType, b: &RocType) -> bool {
         self.is_equivalent_help(RocTypeOrPending::Type(a), RocTypeOrPending::Type(b))
     }
@@ -76,6 +128,7 @@ impl Types {
         };
 
         match (a, b) {
+            (Unsized, Unsized) => true,
             (RocStr, RocStr) | (Bool, Bool) | (EmptyTagUnion, EmptyTagUnion) | (Unit, Unit) => true,
             (RocResult(ok_a, err_a), RocResult(ok_b, err_b)) => {
                 self.is_equivalent_help(
@@ -111,14 +164,14 @@ impl Types {
                         SingleTagStruct {
                             name: _,
                             tag_name: tag_name_a,
-                            payload_fields: payload_fields_a,
+                            payload: payload_a,
                         },
                         SingleTagStruct {
                             name: _,
                             tag_name: tag_name_b,
-                            payload_fields: payload_fields_b,
+                            payload: payload_b,
                         },
-                    ) => tag_name_a == tag_name_b && payload_fields_a == payload_fields_b,
+                    ) => tag_name_a == tag_name_b && payload_a == payload_b,
                     (
                         NonNullableUnwrapped {
                             name: _,
@@ -143,37 +196,30 @@ impl Types {
                     (
                         NonRecursive {
                             tags: tags_a,
-                            discriminant_size: disc_w_a,
-                            discriminant_offset: disc_o_a,
+                            discriminant_size: disc_size_a,
                             ..
                         },
                         NonRecursive {
                             tags: tags_b,
-                            discriminant_size: disc_w_b,
-                            discriminant_offset: disc_o_b,
+                            discriminant_size: disc_size_b,
                             ..
                         },
                     )
                     | (
                         Recursive {
                             tags: tags_a,
-                            discriminant_size: disc_w_a,
-                            discriminant_offset: disc_o_a,
+                            discriminant_size: disc_size_a,
                             ..
                         },
                         Recursive {
                             tags: tags_b,
-                            discriminant_size: disc_w_b,
-                            discriminant_offset: disc_o_b,
+                            discriminant_size: disc_size_b,
                             ..
                         },
                     ) => {
-                        if disc_w_a != disc_w_b
-                            || disc_o_a != disc_o_b
-                            || tags_a.len() != tags_b.len()
-                        {
-                            false
-                        } else {
+                        if disc_size_a == disc_size_b && tags_a.len() == tags_b.len() {
+                            // discriminant offset doesn't matter for equality,
+                            // since it's determined 100% by other fields
                             tags_a.iter().zip(tags_b.iter()).all(
                                 |((name_a, opt_id_a), (name_b, opt_id_b))| {
                                     name_a == name_b
@@ -187,6 +233,8 @@ impl Types {
                                         }
                                 },
                             )
+                        } else {
+                            false
                         }
                     }
                     (
@@ -194,8 +242,6 @@ impl Types {
                         NullableWrapped { tags: tags_b, .. },
                     ) => {
                         if tags_a.len() != tags_b.len() {
-                            false
-                        } else {
                             tags_a.iter().zip(tags_b.iter()).all(
                                 |((name_a, opt_id_a), (name_b, opt_id_b))| {
                                     name_a == name_b
@@ -209,6 +255,8 @@ impl Types {
                                         }
                                 },
                             )
+                        } else {
+                            false
                         }
                     }
                     (
@@ -234,9 +282,7 @@ impl Types {
                     }
                     // These are all listed explicitly so that if we ever add a new variant,
                     // we'll get an exhaustiveness error here.
-                    (SingleTagStruct { .. }, _)
-                    | (_, SingleTagStruct { .. })
-                    | (NonNullableUnwrapped { .. }, _)
+                    (NonNullableUnwrapped { .. }, _)
                     | (_, NonNullableUnwrapped { .. })
                     | (Enumeration { .. }, _)
                     | (_, Enumeration { .. })
@@ -244,16 +290,64 @@ impl Types {
                     | (_, NonRecursive { .. })
                     | (Recursive { .. }, _)
                     | (_, Recursive { .. })
+                    | (SingleTagStruct { .. }, NullableWrapped { .. })
+                    | (NullableWrapped { .. }, SingleTagStruct { .. })
                     | (NullableUnwrapped { .. }, _)
                     | (_, NullableUnwrapped { .. }) => false,
                 }
             }
             (
+                TagUnionPayload {
+                    fields: RocStructFields::HasClosure { fields: fields_a },
+                    name: _,
+                },
+                TagUnionPayload {
+                    fields: RocStructFields::HasClosure { fields: fields_b },
+                    name: _,
+                },
+            )
+            | (
                 Struct {
-                    fields: fields_a, ..
+                    fields: RocStructFields::HasClosure { fields: fields_a },
+                    name: _,
                 },
                 Struct {
-                    fields: fields_b, ..
+                    fields: RocStructFields::HasClosure { fields: fields_b },
+                    name: _,
+                },
+            ) => {
+                if fields_a.len() == fields_b.len() {
+                    fields_a.iter().zip(fields_b.iter()).all(
+                        |((name_a, id_a, _), (name_b, id_b, _))| {
+                            name_a == name_b
+                                && self.is_equivalent_help(
+                                    self.get_type_or_pending(*id_a),
+                                    self.get_type_or_pending(*id_b),
+                                )
+                        },
+                    )
+                } else {
+                    false
+                }
+            }
+            (
+                TagUnionPayload {
+                    fields: RocStructFields::HasNoClosure { fields: fields_a },
+                    name: _,
+                },
+                TagUnionPayload {
+                    fields: RocStructFields::HasNoClosure { fields: fields_b },
+                    name: _,
+                },
+            )
+            | (
+                Struct {
+                    fields: RocStructFields::HasNoClosure { fields: fields_a },
+                    name: _,
+                },
+                Struct {
+                    fields: RocStructFields::HasNoClosure { fields: fields_b },
+                    name: _,
                 },
             ) => {
                 if fields_a.len() == fields_b.len() {
@@ -273,43 +367,69 @@ impl Types {
             }
             (
                 TagUnionPayload {
-                    fields: fields_a, ..
+                    fields: RocStructFields::HasClosure { .. },
+                    name: _,
                 },
                 TagUnionPayload {
-                    fields: fields_b, ..
+                    fields: RocStructFields::HasNoClosure { .. },
+                    name: _,
                 },
-            ) => {
-                if fields_a.len() == fields_b.len() {
-                    fields_a
-                        .iter()
-                        .zip(fields_b.iter())
-                        .all(|((name_a, id_a), (name_b, id_b))| {
-                            name_a == name_b
-                                && self.is_equivalent_help(
-                                    self.get_type_or_pending(*id_a),
-                                    self.get_type_or_pending(*id_b),
-                                )
-                        })
-                } else {
-                    false
-                }
-            }
+            )
+            | (
+                TagUnionPayload {
+                    fields: RocStructFields::HasNoClosure { .. },
+                    name: _,
+                },
+                TagUnionPayload {
+                    fields: RocStructFields::HasClosure { .. },
+                    name: _,
+                },
+            )
+            | (
+                Struct {
+                    fields: RocStructFields::HasNoClosure { .. },
+                    name: _,
+                },
+                Struct {
+                    fields: RocStructFields::HasClosure { .. },
+                    name: _,
+                },
+            )
+            | (
+                Struct {
+                    fields: RocStructFields::HasClosure { .. },
+                    name: _,
+                },
+                Struct {
+                    fields: RocStructFields::HasNoClosure { .. },
+                    name: _,
+                },
+            ) => false,
             (
-                Function {
-                    name: name_a,
+                Function(RocFn {
+                    function_name: name_a,
+                    extern_name: extern_a,
                     args: args_a,
+                    lambda_set: lambda_a,
                     ret: ret_a,
-                },
-                Function {
-                    name: name_b,
+                }),
+                Function(RocFn {
+                    function_name: name_b,
+                    extern_name: extern_b,
                     args: args_b,
+                    lambda_set: lambda_b,
                     ret: ret_b,
-                },
+                }),
             ) => {
                 // for functions, the name is actually important because two functions
                 // with the same type could have completely different implementations!
                 if name_a == name_b
+                    && extern_a == extern_b
                     && args_a.len() == args_b.len()
+                    && self.is_equivalent_help(
+                        self.get_type_or_pending(*lambda_a),
+                        self.get_type_or_pending(*lambda_b),
+                    )
                     && self.is_equivalent_help(
                         self.get_type_or_pending(*ret_a),
                         self.get_type_or_pending(*ret_b),
@@ -354,7 +474,9 @@ impl Types {
             | (RecursivePointer(_), _)
             | (_, RecursivePointer(_))
             | (Function { .. }, _)
-            | (_, Function { .. }) => false,
+            | (_, Function { .. })
+            | (Unsized, _)
+            | (_, Unsized) => false,
         }
     }
 
@@ -486,6 +608,292 @@ impl Types {
             } => unreachable!("Cyclic type definitions: {:?}", nodes_in_cycle),
         }
     }
+
+    pub fn target(&self) -> TargetInfo {
+        self.target
+    }
+}
+
+impl From<&Types> for roc_type::Types {
+    fn from(types: &Types) -> Self {
+        let deps = types
+            .deps
+            .iter()
+            .map(|(k, v)| roc_type::Tuple2::T(k.0 as _, v.iter().map(|x| x.0 as _).collect()))
+            .collect();
+        let types_by_name = types
+            .types_by_name
+            .iter()
+            .map(|(k, v)| roc_type::Tuple1::T(k.as_str().into(), v.0 as _))
+            .collect();
+        roc_type::Types {
+            aligns: types.aligns.as_slice().into(),
+            deps,
+            sizes: types.sizes.as_slice().into(),
+            types: types.types.iter().map(|t| t.into()).collect(),
+            typesByName: types_by_name,
+            target: types.target.into(),
+        }
+    }
+}
+
+impl From<&RocType> for roc_type::RocType {
+    fn from(rc: &RocType) -> Self {
+        match rc {
+            RocType::RocStr => roc_type::RocType::RocStr,
+            RocType::Bool => roc_type::RocType::Bool,
+            RocType::RocResult(ok, err) => roc_type::RocType::RocResult(ok.0 as _, err.0 as _),
+            RocType::Num(num_type) => roc_type::RocType::Num(num_type.into()),
+            RocType::RocList(elem) => roc_type::RocType::RocList(elem.0 as _),
+            RocType::RocDict(k, v) => roc_type::RocType::RocDict(k.0 as _, v.0 as _),
+            RocType::RocSet(elem) => roc_type::RocType::RocSet(elem.0 as _),
+            RocType::RocBox(elem) => roc_type::RocType::RocBox(elem.0 as _),
+            RocType::TagUnion(union) => roc_type::RocType::TagUnion(union.into()),
+            RocType::EmptyTagUnion => roc_type::RocType::EmptyTagUnion,
+            RocType::Struct { name, fields } => roc_type::RocType::Struct(roc_type::R1 {
+                fields: fields.into(),
+                name: name.as_str().into(),
+            }),
+            RocType::TagUnionPayload { name, fields } => {
+                roc_type::RocType::TagUnionPayload(roc_type::R1 {
+                    fields: fields.into(),
+                    name: name.as_str().into(),
+                })
+            }
+            RocType::RecursivePointer(elem) => roc_type::RocType::RecursivePointer(elem.0 as _),
+            RocType::Function(RocFn {
+                function_name,
+                extern_name,
+                args,
+                lambda_set,
+                ret,
+            }) => roc_type::RocType::Function(roc_type::RocFn {
+                args: args.iter().map(|arg| arg.0 as _).collect(),
+                functionName: function_name.as_str().into(),
+                externName: extern_name.as_str().into(),
+                ret: ret.0 as _,
+                lambdaSet: lambda_set.0 as _,
+            }),
+            RocType::Unit => roc_type::RocType::Unit,
+            RocType::Unsized => roc_type::RocType::Unsized,
+        }
+    }
+}
+
+impl From<&RocNum> for roc_type::RocNum {
+    fn from(rn: &RocNum) -> Self {
+        match rn {
+            RocNum::I8 => roc_type::RocNum::I8,
+            RocNum::U8 => roc_type::RocNum::U8,
+            RocNum::I16 => roc_type::RocNum::I16,
+            RocNum::U16 => roc_type::RocNum::U16,
+            RocNum::I32 => roc_type::RocNum::I32,
+            RocNum::U32 => roc_type::RocNum::U32,
+            RocNum::I64 => roc_type::RocNum::I64,
+            RocNum::U64 => roc_type::RocNum::U64,
+            RocNum::I128 => roc_type::RocNum::I128,
+            RocNum::U128 => roc_type::RocNum::U128,
+            RocNum::F32 => roc_type::RocNum::F32,
+            RocNum::F64 => roc_type::RocNum::F64,
+            RocNum::Dec => roc_type::RocNum::Dec,
+        }
+    }
+}
+
+impl From<&RocTagUnion> for roc_type::RocTagUnion {
+    fn from(rtu: &RocTagUnion) -> Self {
+        match rtu {
+            RocTagUnion::Enumeration { name, tags, size } => {
+                roc_type::RocTagUnion::Enumeration(roc_type::R5 {
+                    name: name.as_str().into(),
+                    tags: tags.iter().map(|name| name.as_str().into()).collect(),
+                    size: *size,
+                })
+            }
+            RocTagUnion::NonRecursive {
+                name,
+                tags,
+                discriminant_size,
+                discriminant_offset,
+            } => roc_type::RocTagUnion::NonRecursive(roc_type::R7 {
+                name: name.as_str().into(),
+                tags: tags
+                    .iter()
+                    .map(|(name, payload)| roc_type::R8 {
+                        name: name.as_str().into(),
+                        payload: payload.into(),
+                    })
+                    .collect(),
+                discriminantSize: *discriminant_size,
+                discriminantOffset: *discriminant_offset,
+            }),
+            RocTagUnion::Recursive {
+                name,
+                tags,
+                discriminant_size,
+                discriminant_offset,
+            } => roc_type::RocTagUnion::Recursive(roc_type::R7 {
+                name: name.as_str().into(),
+                tags: tags
+                    .iter()
+                    .map(|(name, payload)| roc_type::R8 {
+                        name: name.as_str().into(),
+                        payload: payload.into(),
+                    })
+                    .collect(),
+                discriminantSize: *discriminant_size,
+                discriminantOffset: *discriminant_offset,
+            }),
+            RocTagUnion::NonNullableUnwrapped {
+                name,
+                tag_name,
+                payload,
+            } => roc_type::RocTagUnion::NonNullableUnwrapped(roc_type::R6 {
+                name: name.as_str().into(),
+                tagName: tag_name.as_str().into(),
+                payload: payload.0 as _,
+            }),
+            RocTagUnion::SingleTagStruct {
+                name,
+                tag_name,
+                payload,
+            } => roc_type::RocTagUnion::SingleTagStruct(roc_type::R14 {
+                name: name.as_str().into(),
+                tagName: tag_name.as_str().into(),
+                payload: payload.into(),
+            }),
+            RocTagUnion::NullableWrapped {
+                name,
+                index_of_null_tag,
+                tags,
+                discriminant_size,
+                discriminant_offset,
+            } => roc_type::RocTagUnion::NullableWrapped(roc_type::R10 {
+                name: name.as_str().into(),
+                indexOfNullTag: *index_of_null_tag,
+                tags: tags
+                    .iter()
+                    .map(|(name, payload)| roc_type::R8 {
+                        name: name.as_str().into(),
+                        payload: payload.into(),
+                    })
+                    .collect(),
+                discriminantSize: *discriminant_size,
+                discriminantOffset: *discriminant_offset,
+            }),
+            RocTagUnion::NullableUnwrapped {
+                name,
+                null_tag,
+                non_null_tag,
+                non_null_payload,
+                null_represents_first_tag,
+            } => roc_type::RocTagUnion::NullableUnwrapped(roc_type::R9 {
+                name: name.as_str().into(),
+                nonNullPayload: non_null_payload.0 as _,
+                nonNullTag: non_null_tag.as_str().into(),
+                nullTag: null_tag.as_str().into(),
+                whichTagIsNull: if *null_represents_first_tag {
+                    roc_type::U2::FirstTagIsNull
+                } else {
+                    roc_type::U2::SecondTagIsNull
+                },
+            }),
+        }
+    }
+}
+
+impl From<&RocStructFields> for roc_type::RocStructFields {
+    fn from(struct_fields: &RocStructFields) -> Self {
+        match struct_fields {
+            RocStructFields::HasNoClosure { fields } => roc_type::RocStructFields::HasNoClosure(
+                fields
+                    .iter()
+                    .map(|(name, id)| roc_type::R4 {
+                        name: name.as_str().into(),
+                        id: id.0 as _,
+                    })
+                    .collect(),
+            ),
+            RocStructFields::HasClosure { fields } => roc_type::RocStructFields::HasClosure(
+                fields
+                    .iter()
+                    .map(|(name, id, accessors)| roc_type::R2 {
+                        name: name.as_str().into(),
+                        id: id.0 as _,
+                        accessors: roc_type::R3 {
+                            getter: accessors.getter.as_str().into(),
+                        },
+                    })
+                    .collect(),
+            ),
+        }
+    }
+}
+
+impl From<&RocSingleTagPayload> for roc_type::RocSingleTagPayload {
+    fn from(struct_fields: &RocSingleTagPayload) -> Self {
+        match struct_fields {
+            RocSingleTagPayload::HasNoClosure { payload_fields } => {
+                roc_type::RocSingleTagPayload::HasNoClosure(
+                    payload_fields
+                        .iter()
+                        .map(|id| roc_type::R16 { id: id.0 as _ })
+                        .collect(),
+                )
+            }
+            RocSingleTagPayload::HasClosure { payload_getters } => {
+                roc_type::RocSingleTagPayload::HasClosure(
+                    payload_getters
+                        .iter()
+                        .map(|(id, name)| roc_type::R4 {
+                            id: id.0 as _,
+                            name: name.as_str().into(),
+                        })
+                        .collect(),
+                )
+            }
+        }
+    }
+}
+
+impl From<&Option> for roc_type::U1 {
+    fn from(opt: &Option) -> Self {
+        match opt {
+            Some(x) => roc_type::U1::Some(x.0 as _),
+            None => roc_type::U1::None,
+        }
+    }
+}
+
+impl From for roc_type::Target {
+    fn from(target: TargetInfo) -> Self {
+        roc_type::Target {
+            architecture: target.architecture.into(),
+            operatingSystem: target.operating_system.into(),
+        }
+    }
+}
+
+impl From for roc_type::Architecture {
+    fn from(arch: Architecture) -> Self {
+        match arch {
+            Architecture::Aarch32 => roc_type::Architecture::Aarch32,
+            Architecture::Aarch64 => roc_type::Architecture::Aarch64,
+            Architecture::Wasm32 => roc_type::Architecture::Wasm32,
+            Architecture::X86_32 => roc_type::Architecture::X86x32,
+            Architecture::X86_64 => roc_type::Architecture::X86x64,
+        }
+    }
+}
+
+impl From for roc_type::OperatingSystem {
+    fn from(os: OperatingSystem) -> Self {
+        match os {
+            OperatingSystem::Windows => roc_type::OperatingSystem::Windows,
+            OperatingSystem::Unix => roc_type::OperatingSystem::Unix,
+            OperatingSystem::Wasi => roc_type::OperatingSystem::Wasi,
+        }
+    }
 }
 
 enum RocTypeOrPending<'a> {
@@ -494,6 +902,46 @@ enum RocTypeOrPending<'a> {
     Pending,
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Accessors {
+    // The name of the extern
+    pub getter: String,
+    // TODO setter
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum RocStructFields {
+    HasNoClosure {
+        fields: Vec<(String, TypeId)>,
+    },
+    HasClosure {
+        fields: Vec<(String, TypeId, Accessors)>,
+        // no struct_size because it's not knowable if there's a closure; must call a size getter!
+    },
+}
+
+impl RocStructFields {
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    pub fn len(&self) -> usize {
+        match self {
+            RocStructFields::HasNoClosure { fields } => fields.len(),
+            RocStructFields::HasClosure { fields } => fields.len(),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct RocFn {
+    pub function_name: String,
+    pub extern_name: String,
+    pub args: Vec,
+    pub lambda_set: TypeId,
+    pub ret: TypeId,
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum RocType {
     RocStr,
@@ -508,23 +956,21 @@ pub enum RocType {
     EmptyTagUnion,
     Struct {
         name: String,
-        fields: Vec<(String, TypeId)>,
+        fields: RocStructFields,
     },
     TagUnionPayload {
         name: String,
-        fields: Vec<(usize, TypeId)>,
+        fields: RocStructFields,
     },
     /// A recursive pointer, e.g. in StrConsList : [Nil, Cons Str StrConsList],
     /// this would be the field of Cons containing the (recursive) StrConsList type,
     /// and the TypeId is the TypeId of StrConsList itself.
     RecursivePointer(TypeId),
-    Function {
-        name: String,
-        args: Vec,
-        ret: TypeId,
-    },
+    Function(RocFn),
     /// A zero-sized type, such as an empty record or a single-tag union with no payload
     Unit,
+    /// A type that has a size that is not statically known
+    Unsized,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
@@ -586,6 +1032,19 @@ impl From for RocNum {
     }
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum RocSingleTagPayload {
+    /// If at least one payload field contains a closure, we have to provide
+    /// field getters and setters because the size and order of those fields can vary based on the
+    /// application's implementation, so those sizes and order are not knowable at host build time.
+    HasClosure {
+        payload_getters: Vec<(TypeId, String)>,
+    },
+    HasNoClosure {
+        payload_fields: Vec,
+    },
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum RocTagUnion {
     Enumeration {
@@ -598,16 +1057,16 @@ pub enum RocTagUnion {
     NonRecursive {
         name: String,
         tags: Vec<(String, Option)>,
-        discriminant_size: u32,
         discriminant_offset: u32,
+        discriminant_size: u32,
     },
     /// A recursive tag union (general case)
     /// e.g. `Expr : [Sym Str, Add Expr Expr]`
     Recursive {
         name: String,
         tags: Vec<(String, Option)>,
-        discriminant_size: u32,
         discriminant_offset: u32,
+        discriminant_size: u32,
     },
     /// Optimization: No need to store a tag ID (the payload is "unwrapped")
     /// e.g. `RoseTree a : [Tree a (List (RoseTree a))]`
@@ -618,10 +1077,13 @@ pub enum RocTagUnion {
     },
     /// Optimization: No need to store a tag ID (the payload is "unwrapped")
     /// e.g. `[Foo Str Bool]`
+    /// Just like a normal struct, if one of the payload fields is a closure, we have to provide
+    /// field getters and setters because the size and order of those fields can vary based on the
+    /// application's implementation, so those sizes and order are not knowable at host build time.
     SingleTagStruct {
         name: String,
         tag_name: String,
-        payload_fields: Vec,
+        payload: RocSingleTagPayload,
     },
     /// A recursive tag union that has an empty variant
     /// Optimization: Represent the empty variant as null pointer => no memory usage & fast comparison
@@ -630,12 +1092,14 @@ pub enum RocTagUnion {
     /// see also: https://youtu.be/ip92VMpf_-A?t=164
     NullableWrapped {
         name: String,
+        /// Which of the tags in .tags is the null pointer.
+        /// Note that this index is *not necessarily* the same as the offset of that tag
+        /// at runtime, which can move around if any of the payloads contain closures!
         index_of_null_tag: u16,
         tags: Vec<(String, Option)>,
         discriminant_size: u32,
         discriminant_offset: u32,
     },
-
     /// A recursive tag union with only two variants, where one is empty.
     /// Optimizations: Use null for the empty variant AND don't store a tag ID for the other variant.
     /// e.g. `ConsList a : [Nil, Cons a (ConsList a)]`
@@ -654,10 +1118,12 @@ pub enum RocTagUnion {
     },
 }
 
-pub struct Env<'a> {
+struct Env<'a> {
     arena: &'a Bump,
     subs: &'a Subs,
     layout_cache: LayoutCache<'a>,
+    glue_procs_by_layout: MutMap, &'a [String]>,
+    lambda_set_ids: MutMap,
     interns: &'a Interns,
     struct_names: Structs,
     enum_names: Enums,
@@ -667,11 +1133,12 @@ pub struct Env<'a> {
 }
 
 impl<'a> Env<'a> {
-    pub fn new(
+    fn new(
         arena: &'a Bump,
         subs: &'a Subs,
         interns: &'a Interns,
         layout_interner: TLLayoutInterner<'a>,
+        glue_procs_by_layout: MutMap, &'a [String]>,
         target: TargetInfo,
     ) -> Self {
         Env {
@@ -682,37 +1149,13 @@ impl<'a> Env<'a> {
             enum_names: Default::default(),
             pending_recursive_types: Default::default(),
             known_recursive_types: Default::default(),
+            glue_procs_by_layout,
+            lambda_set_ids: Default::default(),
             layout_cache: LayoutCache::new(layout_interner, target),
             target,
         }
     }
 
-    pub fn vars_to_types(&mut self, variables: I) -> Types
-    where
-        I: Iterator,
-    {
-        let mut types = Types::with_capacity(variables.size_hint().0);
-
-        for var in variables {
-            self.add_type(var, &mut types);
-        }
-
-        self.resolve_pending_recursive_types(&mut types);
-
-        types
-    }
-
-    fn add_type(&mut self, var: Variable, types: &mut Types) -> TypeId {
-        roc_tracing::debug!(content=?roc_types::subs::SubsFmtContent(self.subs.get_content_without_compacting(var), self.subs), "adding type");
-
-        let layout = self
-            .layout_cache
-            .from_var(self.arena, var, self.subs)
-            .expect("Something weird ended up in the content");
-
-        add_type_help(self, layout, var, None, types)
-    }
-
     fn resolve_pending_recursive_types(&mut self, types: &mut Types) {
         // TODO if VecMap gets a drain() method, use that instead of doing take() and into_iter
         let pending = core::mem::take(&mut self.pending_recursive_types);
@@ -739,6 +1182,95 @@ impl<'a> Env<'a> {
             types.replace(type_id, RocType::RecursivePointer(actual_type_id));
         }
     }
+
+    fn find_lambda_sets(&self, root: Variable) -> MutMap {
+        let mut lambda_set_id = LambdaSetId::default();
+
+        let mut result = MutMap::default();
+        let mut stack = vec![root];
+
+        while let Some(var) = stack.pop() {
+            match self.subs.get_content_without_compacting(var) {
+                Content::RangedNumber(_)
+                | Content::Error
+                | Content::FlexVar(_)
+                | Content::RigidVar(_)
+                | Content::FlexAbleVar(_, _)
+                | Content::RigidAbleVar(_, _)
+                | Content::RecursionVar { .. } => {}
+                Content::Structure(flat_type) => match flat_type {
+                    FlatType::Apply(_, arguments) => {
+                        stack.extend(self.subs.get_subs_slice(*arguments).iter().rev());
+                    }
+                    FlatType::Func(_, lambda_set_var, _) => {
+                        result.insert(*lambda_set_var, lambda_set_id);
+                        lambda_set_id = lambda_set_id.next();
+
+                        // the lambda set itself can contain more lambda sets
+                        stack.push(*lambda_set_var);
+                    }
+                    FlatType::Record(fields, ext) => {
+                        stack.extend(self.subs.get_subs_slice(fields.variables()).iter().rev());
+                        stack.push(*ext);
+                    }
+                    FlatType::Tuple(elements, ext) => {
+                        stack.extend(self.subs.get_subs_slice(elements.variables()).iter().rev());
+                        stack.push(*ext);
+                    }
+                    FlatType::FunctionOrTagUnion(_, _, ext) => {
+                        // just the ext
+                        match ext {
+                            roc_types::subs::TagExt::Openness(var) => stack.push(*var),
+                            roc_types::subs::TagExt::Any(_) => { /* ignore */ }
+                        }
+                    }
+                    FlatType::TagUnion(union_tags, ext)
+                    | FlatType::RecursiveTagUnion(_, union_tags, ext) => {
+                        for tag in union_tags.variables() {
+                            stack.extend(
+                                self.subs
+                                    .get_subs_slice(self.subs.variable_slices[tag.index as usize])
+                                    .iter()
+                                    .rev(),
+                            );
+                        }
+
+                        match ext {
+                            roc_types::subs::TagExt::Openness(var) => stack.push(*var),
+                            roc_types::subs::TagExt::Any(_) => { /* ignore */ }
+                        }
+                    }
+                    FlatType::EmptyRecord => {}
+                    FlatType::EmptyTuple => {}
+                    FlatType::EmptyTagUnion => {}
+                },
+                Content::Alias(_, _, actual, _) => {
+                    stack.push(*actual);
+                }
+                Content::LambdaSet(lambda_set) => {
+                    // the lambda set itself should already be caught by Func above, but the
+                    // capture can itself contain more lambda sets
+                    for index in lambda_set.solved.variables() {
+                        let subs_slice = self.subs.variable_slices[index.index as usize];
+                        stack.extend(self.subs.variables[subs_slice.indices()].iter());
+                    }
+                }
+            }
+        }
+
+        result
+    }
+
+    fn add_type(&mut self, var: Variable, types: &mut Types) -> TypeId {
+        roc_tracing::debug!(content=?roc_types::subs::SubsFmtContent(self.subs.get_content_without_compacting(var), self.subs), "adding type");
+
+        let layout = self
+            .layout_cache
+            .from_var(self.arena, var, self.subs)
+            .expect("Something weird ended up in the content");
+
+        add_type_help(self, layout, var, None, types)
+    }
 }
 
 fn add_type_help<'a>(
@@ -823,6 +1355,21 @@ fn add_type_help<'a>(
             let args = env.subs.get_subs_slice(*args);
             let mut arg_type_ids = Vec::with_capacity(args.len());
 
+            let name = format!("RocFunction_{:?}", closure_var);
+
+            let lambda_set_layout = env
+                .layout_cache
+                .from_var(env.arena, *closure_var, env.subs)
+                .expect("Something weird ended up in the content");
+
+            let _lambda_set = match env.layout_cache.interner.get(lambda_set_layout) {
+                Layout::LambdaSet(lambda_set) => lambda_set,
+                _ => unreachable!(),
+            };
+
+            let id = env.lambda_set_ids.get(closure_var).unwrap();
+            let extern_name = format!("roc__mainForHost_{}_caller", id.0);
+
             for arg_var in args {
                 let arg_layout = env
                     .layout_cache
@@ -832,6 +1379,9 @@ fn add_type_help<'a>(
                 arg_type_ids.push(add_type_help(env, arg_layout, *arg_var, None, types));
             }
 
+            let lambda_set_type_id =
+                add_type_help(env, lambda_set_layout, *closure_var, None, types);
+
             let ret_type_id = {
                 let ret_layout = env
                     .layout_cache
@@ -841,17 +1391,15 @@ fn add_type_help<'a>(
                 add_type_help(env, ret_layout, *ret_var, None, types)
             };
 
-            let name = format!("TODO_roc_function_{:?}", closure_var);
-            let fn_type_id = types.add_named(
-                &env.layout_cache.interner,
-                name.clone(),
-                RocType::Function {
-                    name,
+            let fn_type_id = add_function(env, name, types, layout, |name| {
+                RocType::Function(RocFn {
+                    function_name: name,
+                    extern_name,
                     args: arg_type_ids.clone(),
+                    lambda_set: lambda_set_type_id,
                     ret: ret_type_id,
-                },
-                layout,
-            );
+                })
+            });
 
             types.depends(fn_type_id, ret_type_id);
 
@@ -934,6 +1482,11 @@ fn add_type_help<'a>(
                             }
                         }
                     }
+                    Layout::Struct { .. } if *name == Symbol::RESULT_RESULT => {
+                        // can happen if one or both of a and b in `Result.Result a b` are the
+                        // empty tag union `[]`
+                        add_type_help(env, layout, *real_var, opt_name, types)
+                    }
                     Layout::Struct { .. } if *name == Symbol::DICT_DICT => {
                         let type_vars = env.subs.get_subs_slice(alias_vars.type_variables());
 
@@ -1011,7 +1564,16 @@ fn add_type_help<'a>(
 
             type_id
         }
-        Content::LambdaSet(_) => todo!(),
+        Content::LambdaSet(lambda_set) => {
+            let tags = lambda_set.solved;
+
+            if tags.is_empty() {
+                // this function does not capture anything. Represent that at runtime as a unit value
+                types.add_anonymous(&env.layout_cache.interner, RocType::Unsized, layout)
+            } else {
+                add_tag_union(env, opt_name, &tags, var, types, layout, None)
+            }
+        }
     }
 }
 
@@ -1205,23 +1767,45 @@ fn add_builtin_type<'a>(
     }
 }
 
-fn add_struct<'a, I, L, F>(
+fn add_function<'a, F>(
     env: &mut Env<'a>,
     name: String,
-    fields: I,
     types: &mut Types,
     layout: InLayout<'a>,
     to_type: F,
 ) -> TypeId
+where
+    F: FnOnce(String) -> RocType,
+{
+    // let subs = env.subs;
+    // let arena = env.arena;
+
+    types.add_named(
+        &env.layout_cache.interner,
+        name.clone(),
+        to_type(name),
+        layout,
+    )
+}
+
+fn add_struct<'a, I, L, F>(
+    env: &mut Env<'a>,
+    name: String,
+    fields: I,
+    types: &mut Types,
+    in_layout: InLayout<'a>,
+    to_type: F,
+) -> TypeId
 where
     I: IntoIterator,
     L: Display + Ord,
-    F: FnOnce(String, Vec<(L, TypeId)>) -> RocType,
+    F: FnOnce(String, RocStructFields) -> RocType,
 {
     let subs = env.subs;
+    let arena = env.arena;
     let fields_iter = &mut fields.into_iter();
     let mut sortables =
-        bumpalo::collections::Vec::with_capacity_in(fields_iter.size_hint().0, env.arena);
+        bumpalo::collections::Vec::with_capacity_in(fields_iter.size_hint().0, arena);
 
     for (label, field_var) in fields_iter {
         sortables.push((
@@ -1244,39 +1828,80 @@ where
         )
     });
 
-    let fields = sortables
-        .into_iter()
-        .map(|(label, field_var, field_layout)| {
-            let type_id = add_type_help(env, field_layout, field_var, None, types);
+    // This layout should have an entry in glue_procs_by_layout iff it
+    // contains closures, but we'll double-check that with a debug_assert.
+    let layout = env.layout_cache.interner.get(in_layout);
+    let struct_fields = match env.glue_procs_by_layout.get(&layout) {
+        Some(&glue_procs) => {
+            debug_assert!(layout.has_varying_stack_size(&env.layout_cache.interner, arena));
 
-            (label, type_id)
-        })
-        .collect::>();
+            let fields: Vec<(String, TypeId, Accessors)> = sortables
+                .into_iter()
+                .zip(glue_procs.iter())
+                .map(|((label, field_var, field_layout), getter)| {
+                    let type_id = add_type_help(env, field_layout, field_var, None, types);
+                    let accessors = Accessors {
+                        getter: getter.clone(),
+                    };
+
+                    (format!("{}", label), type_id, accessors)
+                })
+                .collect();
+
+            RocStructFields::HasClosure { fields }
+        }
+        None => {
+            debug_assert!(!layout.has_varying_stack_size(&env.layout_cache.interner, arena));
+
+            let fields: Vec<(String, TypeId)> = sortables
+                .into_iter()
+                .map(|(label, field_var, field_layout)| {
+                    let type_id = add_type_help(env, field_layout, field_var, None, types);
+
+                    (format!("{}", label), type_id)
+                })
+                .collect();
+
+            RocStructFields::HasNoClosure { fields }
+        }
+    };
 
     types.add_named(
         &env.layout_cache.interner,
         name.clone(),
-        to_type(name, fields),
-        layout,
+        to_type(name, struct_fields),
+        in_layout,
     )
 }
 
-fn add_tag_union<'a>(
+trait UnionTag: Label + std::fmt::Debug {
+    fn union_tag_name(&self) -> String;
+}
+
+impl UnionTag for TagName {
+    fn union_tag_name(&self) -> String {
+        self.0.as_str().to_string()
+    }
+}
+
+impl UnionTag for Symbol {
+    fn union_tag_name(&self) -> String {
+        format!("C{:?}_{}", self.module_id(), self.ident_id().index())
+    }
+}
+
+fn tag_union_type_from_layout<'a>(
     env: &mut Env<'a>,
     opt_name: Option,
-    union_tags: &UnionTags,
+    name: String,
+    union_tags: &UnionLabels,
     var: Variable,
     types: &mut Types,
     layout: InLayout<'a>,
-    rec_root: Option,
-) -> TypeId {
+) -> RocTagUnion {
     let subs = env.subs;
-    let name = match opt_name {
-        Some(sym) => sym.as_str(env.interns).to_string(),
-        None => env.enum_names.get_name(var),
-    };
 
-    let tag_union_type = match env.layout_cache.get_in(layout) {
+    match env.layout_cache.get_in(layout) {
         _ if union_tags.is_newtype_wrapper(subs)
             && matches!(
                 subs.get_content_without_compacting(var),
@@ -1286,19 +1911,15 @@ fn add_tag_union<'a>(
                 Content::Structure(FlatType::TagUnion(_, _))
             ) =>
         {
-            let (tag_name, payload_vars) = single_tag_payload(union_tags, subs);
-
-            // A newtype wrapper should always have exactly one payload.
-            debug_assert_eq!(payload_vars.len(), 1);
-
             // A newtype wrapper should always have the same layout as its payload.
             let payload_layout = layout;
-            let payload_id = add_type_help(env, payload_layout, payload_vars[0], None, types);
+            let (tag_name, payload) =
+                single_tag_payload_fields(env, union_tags, subs, layout, &[payload_layout], types);
 
             RocTagUnion::SingleTagStruct {
                 name: name.clone(),
-                tag_name: tag_name.to_string(),
-                payload_fields: vec![payload_id],
+                tag_name,
+                payload,
             }
         }
         Layout::Union(union_layout) => {
@@ -1353,7 +1974,7 @@ fn add_tag_union<'a>(
                     // e.g. `RoseTree a : [Tree a (List (RoseTree a))]`
                     RocTagUnion::NonNullableUnwrapped {
                         name: name.clone(),
-                        tag_name: tag_name.to_string(),
+                        tag_name,
                         payload: opt_payload.unwrap(),
                     }
                 }
@@ -1429,16 +2050,13 @@ fn add_tag_union<'a>(
             add_int_enumeration(union_tags, subs, &name, int_width)
         }
         Layout::Struct { field_layouts, .. } => {
-            let (tag_name, payload_fields) =
-                single_tag_payload_fields(union_tags, subs, field_layouts, env, types);
+            let (tag_name, payload) =
+                single_tag_payload_fields(env, union_tags, subs, layout, field_layouts, types);
 
-            // A recursive tag union with just one constructor
-            // Optimization: No need to store a tag ID (the payload is "unwrapped")
-            // e.g. `RoseTree a : [Tree a (List (RoseTree a))]`
             RocTagUnion::SingleTagStruct {
                 name: name.clone(),
-                tag_name: tag_name.to_string(),
-                payload_fields,
+                tag_name,
+                payload,
             }
         }
         Layout::Builtin(Builtin::Bool) => {
@@ -1453,30 +2071,64 @@ fn add_tag_union<'a>(
 
             RocTagUnion::SingleTagStruct {
                 name: name.clone(),
-                tag_name: tag_name.to_string(),
-                payload_fields: vec![type_id],
+                tag_name,
+                payload: RocSingleTagPayload::HasNoClosure {
+                    // Builtins have no closures
+                    payload_fields: vec![type_id],
+                },
             }
         }
         Layout::Boxed(elem_layout) => {
             let (tag_name, payload_fields) =
-                single_tag_payload_fields(union_tags, subs, &[elem_layout], env, types);
+                single_tag_payload_fields(env, union_tags, subs, layout, &[elem_layout], types);
 
             RocTagUnion::SingleTagStruct {
                 name: name.clone(),
-                tag_name: tag_name.to_string(),
-                payload_fields,
+                tag_name,
+                payload: payload_fields,
             }
         }
-        Layout::LambdaSet(_) => {
-            todo!();
-        }
+        Layout::LambdaSet(lambda_set) => tag_union_type_from_layout(
+            env,
+            opt_name,
+            name,
+            union_tags,
+            var,
+            types,
+            lambda_set.runtime_representation(),
+        ),
         Layout::RecursivePointer(_) => {
             // A single-tag union which only wraps itself is erroneous and should have
             // been turned into an error earlier in the process.
             unreachable!();
         }
+    }
+}
+
+fn add_tag_union<'a>(
+    env: &mut Env<'a>,
+    opt_name: Option,
+    union_tags: &UnionLabels,
+    var: Variable,
+    types: &mut Types,
+    layout: InLayout<'a>,
+    rec_root: Option,
+) -> TypeId {
+    let name = match opt_name {
+        Some(sym) => sym.as_str(env.interns).to_string(),
+        None => env.enum_names.get_name(var),
     };
 
+    let tag_union_type = tag_union_type_from_layout(
+        env,
+        opt_name,
+        name.to_string(),
+        union_tags,
+        var,
+        types,
+        layout,
+    );
+
     let typ = RocType::TagUnion(tag_union_type);
     let type_id = types.add_named(&env.layout_cache.interner, name, typ, layout);
 
@@ -1488,14 +2140,14 @@ fn add_tag_union<'a>(
 }
 
 fn add_int_enumeration(
-    union_tags: &UnionLabels,
+    union_tags: &UnionLabels,
     subs: &Subs,
     name: &str,
     int_width: IntWidth,
 ) -> RocTagUnion {
     let tags: Vec = union_tags
         .iter_from_subs(subs)
-        .map(|(tag_name, _)| tag_name.0.as_str().to_string())
+        .map(|(tag_name, _)| tag_name.union_tag_name())
         .collect();
     RocTagUnion::Enumeration {
         name: name.to_string(),
@@ -1506,7 +2158,7 @@ fn add_int_enumeration(
 
 fn union_tags_to_types<'a>(
     name: &str,
-    union_tags: &UnionLabels,
+    union_tags: &UnionLabels,
     subs: &Subs,
     env: &mut Env<'a>,
     types: &mut Types,
@@ -1516,7 +2168,7 @@ fn union_tags_to_types<'a>(
     let mut tags: Vec<(String, Vec)> = union_tags
         .iter_from_subs(subs)
         .map(|(tag_name, payload_vars)| {
-            let name_str = tag_name.0.as_str().to_string();
+            let name_str = tag_name.union_tag_name();
 
             (name_str, payload_vars.to_vec())
         })
@@ -1541,33 +2193,64 @@ fn union_tags_to_types<'a>(
 }
 
 fn single_tag_payload<'a>(
-    union_tags: &'a UnionLabels,
+    union_tags: &'a UnionLabels,
     subs: &'a Subs,
-) -> (&'a str, &'a [Variable]) {
+) -> (String, &'a [Variable]) {
     let mut iter = union_tags.iter_from_subs(subs);
     let (tag_name, payload_vars) = iter.next().unwrap();
-    // This should be a single-tag union.
-    debug_assert_eq!(iter.next(), None);
 
-    (tag_name.0.as_str(), payload_vars)
+    // This should be a single-tag union, but it could be the remnant of a `Result.Result a []`,
+    // where the `Err` branch is inconsequential, but still part of the type
+
+    (tag_name.union_tag_name(), payload_vars)
 }
 
 fn single_tag_payload_fields<'a, 'b>(
-    union_tags: &'b UnionLabels,
-    subs: &'b Subs,
-    field_layouts: &[InLayout<'a>],
     env: &mut Env<'a>,
+    union_tags: &'b UnionLabels,
+    subs: &'b Subs,
+    in_layout: InLayout<'a>,
+    field_layouts: &[InLayout<'a>],
     types: &mut Types,
-) -> (&'b str, Vec) {
+) -> (String, RocSingleTagPayload) {
+    let layout = env.layout_cache.interner.get(in_layout);
+    // There should be a glue_procs_by_layout entry iff this layout has a closure in it,
+    // so we shouldn't need to separately check that. Howeevr, we still do a debug_assert
+    // anyway just so we have some warning in case that relationship somehow didn't hold!
+    debug_assert_eq!(
+        env.glue_procs_by_layout.get(&layout).is_some(),
+        layout.has_varying_stack_size(&env.layout_cache.interner, env.arena)
+    );
+
     let (tag_name, payload_vars) = single_tag_payload(union_tags, subs);
 
-    let payload_fields: Vec = payload_vars
-        .iter()
-        .zip(field_layouts.iter())
-        .map(|(field_var, field_layout)| add_type_help(env, *field_layout, *field_var, None, types))
-        .collect();
+    let payload = match env.glue_procs_by_layout.get(&layout) {
+        Some(glue_procs) => {
+            let payload_getters = payload_vars
+                .iter()
+                .zip(field_layouts.iter())
+                .zip(glue_procs.iter())
+                .map(|((field_var, field_layout), getter_name)| {
+                    let type_id = add_type_help(env, *field_layout, *field_var, None, types);
 
-    (tag_name, payload_fields)
+                    (type_id, getter_name.to_string())
+                })
+                .collect();
+
+            RocSingleTagPayload::HasClosure { payload_getters }
+        }
+        None => RocSingleTagPayload::HasNoClosure {
+            payload_fields: payload_vars
+                .iter()
+                .zip(field_layouts.iter())
+                .map(|(field_var, field_layout)| {
+                    add_type_help(env, *field_layout, *field_var, None, types)
+                })
+                .collect(),
+        },
+    };
+
+    (tag_name, payload)
 }
 
 fn tag_to_type<'a, D: Display>(
diff --git a/crates/glue/templates/header.rs b/crates/glue/templates/header.rs
index 9f7f9707df..22a435144b 100644
--- a/crates/glue/templates/header.rs
+++ b/crates/glue/templates/header.rs
@@ -1,6 +1,7 @@
 // ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
 
 #![allow(unused_unsafe)]
+#![allow(unused_variables)]
 #![allow(dead_code)]
 #![allow(unused_mut)]
 #![allow(non_snake_case)]
@@ -15,3 +16,8 @@
 #![allow(clippy::redundant_static_lifetimes)]
 #![allow(clippy::needless_borrow)]
 #![allow(clippy::clone_on_copy)]
+
+type Op_StderrWrite = roc_std::RocStr;
+type Op_StdoutWrite = roc_std::RocStr;
+type TODO_roc_function_69 = roc_std::RocStr;
+type TODO_roc_function_70 = roc_std::RocStr;
diff --git a/crates/glue/tests/fixture-templates/rust/Cargo.lock b/crates/glue/tests/fixture-templates/rust/Cargo.lock
deleted file mode 100644
index 8114a43323..0000000000
--- a/crates/glue/tests/fixture-templates/rust/Cargo.lock
+++ /dev/null
@@ -1,37 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "host"
-version = "0.0.1"
-dependencies = [
- "indoc",
- "libc",
- "roc_std",
-]
-
-[[package]]
-name = "indoc"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e"
-
-[[package]]
-name = "libc"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
-
-[[package]]
-name = "roc_std"
-version = "0.0.1"
-dependencies = [
- "static_assertions",
-]
-
-[[package]]
-name = "static_assertions"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7"
diff --git a/crates/glue/tests/fixtures/.gitignore b/crates/glue/tests/fixtures/.gitignore
index a71685876a..5d524b2751 100644
--- a/crates/glue/tests/fixtures/.gitignore
+++ b/crates/glue/tests/fixtures/.gitignore
@@ -2,7 +2,7 @@ Cargo.lock
 Cargo.toml
 build.rs
 host.c
-test_glue.rs
+test_glue
 roc_externs.rs
 main.rs
 app
diff --git a/crates/glue/tests/fixtures/dict/app.roc b/crates/glue/tests/fixtures/dict/app.roc
deleted file mode 100644
index 57837319a2..0000000000
--- a/crates/glue/tests/fixtures/dict/app.roc
+++ /dev/null
@@ -1,10 +0,0 @@
-app "app"
-    packages { pf: "platform.roc" }
-    imports []
-    provides [main] to pf
-
-main =
-    Dict.empty {}
-    |> Dict.insert "foo" "this will be overwritten"
-    |> Dict.insert "baz" "blah"
-    |> Dict.insert "foo" "bar"
diff --git a/crates/glue/tests/fixtures/dict/platform.roc b/crates/glue/tests/fixtures/dict/platform.roc
deleted file mode 100644
index 1a9939cc87..0000000000
--- a/crates/glue/tests/fixtures/dict/platform.roc
+++ /dev/null
@@ -1,9 +0,0 @@
-platform "test-platform"
-    requires {} { main : _ }
-    exposes []
-    packages {}
-    imports []
-    provides [mainForHost]
-
-mainForHost : Dict Str Str
-mainForHost = main
diff --git a/crates/glue/tests/fixtures/dict/src/lib.rs b/crates/glue/tests/fixtures/dict/src/lib.rs
deleted file mode 100644
index b05778186b..0000000000
--- a/crates/glue/tests/fixtures/dict/src/lib.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-mod test_glue;
-
-use roc_std::{RocDict, RocStr};
-
-extern "C" {
-    #[link_name = "roc__mainForHost_1_exposed_generic"]
-    fn roc_main(_: *mut RocDict);
-}
-
-#[no_mangle]
-pub extern "C" fn rust_main() -> i32 {
-    use std::cmp::Ordering;
-    use std::collections::hash_set::HashSet;
-
-    let dict = unsafe {
-        let mut ret: core::mem::MaybeUninit> =
-            core::mem::MaybeUninit::uninit();
-
-        roc_main(ret.as_mut_ptr());
-
-        ret.assume_init()
-    };
-
-    // Verify that it has all the expected traits.
-
-    assert!(dict == dict); // PartialEq
-    assert_eq!(dict.len(), 2); // len
-    assert!(dict.clone() == dict.clone()); // Clone
-
-    assert!(dict.partial_cmp(&dict) == Some(Ordering::Equal)); // PartialOrd
-    assert!(dict.cmp(&dict) == Ordering::Equal); // Ord
-
-    let mut set = HashSet::new();
-
-    set.insert(dict.clone()); // Eq, Hash
-    set.insert(dict.clone());
-
-    assert_eq!(set.len(), 1);
-
-    println!("dict was: {:?}", dict); // Debug
-
-    // Exit code
-    0
-}
-
-// Externs required by roc_std and by the Roc app
-
-use core::ffi::c_void;
-use std::ffi::CStr;
-use std::os::raw::c_char;
-
-#[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)
-}
diff --git a/crates/glue/tests/fixtures/set/app.roc b/crates/glue/tests/fixtures/set/app.roc
deleted file mode 100644
index 6a6946ee8a..0000000000
--- a/crates/glue/tests/fixtures/set/app.roc
+++ /dev/null
@@ -1,11 +0,0 @@
-app "app"
-    packages { pf: "platform.roc" }
-    imports []
-    provides [main] to pf
-
-main =
-    Set.empty {}
-    |> Set.insert "foo"
-    |> Set.insert "bar"
-    |> Set.insert "foo"
-    |> Set.insert "baz"
diff --git a/crates/glue/tests/fixtures/set/platform.roc b/crates/glue/tests/fixtures/set/platform.roc
deleted file mode 100644
index 7b73d49ba5..0000000000
--- a/crates/glue/tests/fixtures/set/platform.roc
+++ /dev/null
@@ -1,9 +0,0 @@
-platform "test-platform"
-    requires {} { main : _ }
-    exposes []
-    packages {}
-    imports []
-    provides [mainForHost]
-
-mainForHost : Set Str
-mainForHost = main
diff --git a/crates/glue/tests/fixtures/set/src/lib.rs b/crates/glue/tests/fixtures/set/src/lib.rs
deleted file mode 100644
index 0c3114737b..0000000000
--- a/crates/glue/tests/fixtures/set/src/lib.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-mod test_glue;
-
-use roc_std::{RocSet, RocStr};
-
-extern "C" {
-    #[link_name = "roc__mainForHost_1_exposed_generic"]
-    fn roc_main(_: *mut RocSet);
-}
-
-#[no_mangle]
-pub extern "C" fn rust_main() -> i32 {
-    use std::cmp::Ordering;
-    use std::collections::hash_set::HashSet;
-
-    let set = unsafe {
-        let mut ret: core::mem::MaybeUninit> = core::mem::MaybeUninit::uninit();
-
-        roc_main(ret.as_mut_ptr());
-
-        ret.assume_init()
-    };
-
-    // Verify that it has all the expected traits.
-
-    assert!(set == set); // PartialEq
-    assert_eq!(set.len(), 3); // len
-    assert!(set.clone() == set.clone()); // Clone
-
-    assert!(set.partial_cmp(&set) == Some(Ordering::Equal)); // PartialOrd
-    assert!(set.cmp(&set) == Ordering::Equal); // Ord
-
-    let mut hash_set = HashSet::new();
-
-    hash_set.insert(set.clone()); // Eq, Hash
-    hash_set.insert(set.clone());
-
-    assert_eq!(hash_set.len(), 1);
-
-    println!("set was: {:?}", set); // Debug
-
-    // Exit code
-    0
-}
-
-// Externs required by roc_std and by the Roc app
-
-use core::ffi::c_void;
-use std::ffi::CStr;
-use std::os::raw::c_char;
-
-#[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)
-}
diff --git a/crates/glue/tests/gen_rs.rs b/crates/glue/tests/gen_rs.rs
index 88ab8d24f7..1202f1b3da 100644
--- a/crates/glue/tests/gen_rs.rs
+++ b/crates/glue/tests/gen_rs.rs
@@ -9,6 +9,8 @@ mod helpers;
 #[cfg(test)]
 mod test_gen_rs {
     use crate::helpers::generate_bindings;
+    use roc_glue::rust_glue::HEADER;
+    use roc_glue::types::File;
 
     #[test]
     fn basic_record_aliased() {
@@ -18,30 +20,33 @@ mod test_gen_rs {
 
             main : MyRcd
             main = { a: 1u64, b: 2i128 }
-        "#
+            "#
         );
 
+        let full_header = std::str::from_utf8(HEADER).unwrap().to_string() + "\n";
         assert_eq!(
-            generate_bindings(module)
-                .strip_prefix('\n')
-                .unwrap_or_default(),
-            indoc!(
-                r#"
-                #[cfg(any(
-                    target_arch = "arm",
-                    target_arch = "aarch64",
-                    target_arch = "wasm32",
-                    target_arch = "x86",
-                    target_arch = "x86_64"
-                ))]
-                #[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
-                #[repr(C)]
-                pub struct MyRcd {
-                    pub b: roc_std::I128,
-                    pub a: u64,
-                }
-            "#
-            )
+            generate_bindings(module),
+            vec![File {
+                name: "mod.rs".to_string(),
+                content: full_header
+                    + indoc!(
+                        r#"
+                    #[cfg(any(
+                        target_arch = "arm",
+                        target_arch = "aarch64",
+                        target_arch = "wasm32",
+                        target_arch = "x86",
+                        target_arch = "x86_64"
+                    ))]
+                    #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+                    #[repr(C)]
+                    pub struct MyRcd {
+                        pub b: roc_std::I128,
+                        pub a: u64,
+                    }
+                    "#
+                    )
+            }]
         );
     }
 
@@ -55,55 +60,58 @@ mod test_gen_rs {
 
             main : Outer
             main = { x: { a: 5, b: 24 }, y: "foo", z: [1, 2] }
-        "#
+            "#
         );
 
+        let full_header = std::str::from_utf8(HEADER).unwrap().to_string() + "\n";
         assert_eq!(
-            generate_bindings(module)
-                .strip_prefix('\n')
-                .unwrap_or_default(),
-            indoc!(
-                r#"
-                #[cfg(any(
-                    target_arch = "arm",
-                    target_arch = "wasm32",
-                    target_arch = "x86"
-                ))]
-                #[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
-                #[repr(C)]
-                pub struct Outer {
-                    pub x: Inner,
-                    pub y: roc_std::RocStr,
-                    pub z: roc_std::RocList,
-                }
+            generate_bindings(module),
+            vec![File {
+                name: "mod.rs".to_string(),
+                content: full_header
+                    + indoc!(
+                        r#"
+                    #[cfg(any(
+                        target_arch = "arm",
+                        target_arch = "wasm32",
+                        target_arch = "x86"
+                    ))]
+                    #[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
+                    #[repr(C)]
+                    pub struct Outer {
+                        pub x: Inner,
+                        pub y: roc_std::RocStr,
+                        pub z: roc_std::RocList,
+                    }
 
-                #[cfg(any(
-                    target_arch = "arm",
-                    target_arch = "aarch64",
-                    target_arch = "wasm32",
-                    target_arch = "x86",
-                    target_arch = "x86_64"
-                ))]
-                #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
-                #[repr(C)]
-                pub struct Inner {
-                    pub b: f32,
-                    pub a: u16,
-                }
+                    #[cfg(any(
+                        target_arch = "arm",
+                        target_arch = "aarch64",
+                        target_arch = "wasm32",
+                        target_arch = "x86",
+                        target_arch = "x86_64"
+                    ))]
+                    #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
+                    #[repr(C)]
+                    pub struct Inner {
+                        pub b: f32,
+                        pub a: u16,
+                    }
 
-                #[cfg(any(
-                    target_arch = "aarch64",
-                    target_arch = "x86_64"
-                ))]
-                #[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
-                #[repr(C)]
-                pub struct Outer {
-                    pub y: roc_std::RocStr,
-                    pub z: roc_std::RocList,
-                    pub x: Inner,
-                }
-            "#
-            )
+                    #[cfg(any(
+                        target_arch = "aarch64",
+                        target_arch = "x86_64"
+                    ))]
+                    #[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
+                    #[repr(C)]
+                    pub struct Outer {
+                        pub y: roc_std::RocStr,
+                        pub z: roc_std::RocList,
+                        pub x: Inner,
+                    }
+                    "#
+                    )
+            }]
         );
     }
 
@@ -111,27 +119,30 @@ mod test_gen_rs {
     fn record_anonymous() {
         let module = "main = { a: 1u64, b: 2u128 }";
 
+        let full_header = std::str::from_utf8(HEADER).unwrap().to_string() + "\n";
         assert_eq!(
-            generate_bindings(module)
-                .strip_prefix('\n')
-                .unwrap_or_default(),
-            indoc!(
-                r#"
-                #[cfg(any(
-                    target_arch = "arm",
-                    target_arch = "aarch64",
-                    target_arch = "wasm32",
-                    target_arch = "x86",
-                    target_arch = "x86_64"
-                ))]
-                #[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
-                #[repr(C)]
-                pub struct R1 {
-                    pub b: roc_std::U128,
-                    pub a: u64,
-                }
-            "#
-            )
+            generate_bindings(module),
+            vec![File {
+                name: "mod.rs".to_string(),
+                content: full_header
+                    + indoc!(
+                        r#"
+                    #[cfg(any(
+                        target_arch = "arm",
+                        target_arch = "aarch64",
+                        target_arch = "wasm32",
+                        target_arch = "x86",
+                        target_arch = "x86_64"
+                    ))]
+                    #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+                    #[repr(C)]
+                    pub struct R1 {
+                        pub b: roc_std::U128,
+                        pub a: u64,
+                    }
+                    "#
+                    )
+            }]
         );
     }
 
@@ -139,52 +150,55 @@ mod test_gen_rs {
     fn nested_record_anonymous() {
         let module = r#"main = { x: { a: 5u16, b: 24f32 }, y: "foo", z: [1u8, 2] }"#;
 
+        let full_header = std::str::from_utf8(HEADER).unwrap().to_string() + "\n";
         assert_eq!(
-            generate_bindings(module)
-                .strip_prefix('\n')
-                .unwrap_or_default(),
-            indoc!(
-                r#"
-                #[cfg(any(
-                    target_arch = "arm",
-                    target_arch = "wasm32",
-                    target_arch = "x86"
-                ))]
-                #[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
-                #[repr(C)]
-                pub struct R1 {
-                    pub x: R2,
-                    pub y: roc_std::RocStr,
-                    pub z: roc_std::RocList,
-                }
+            generate_bindings(module),
+            vec![File {
+                name: "mod.rs".to_string(),
+                content: full_header
+                    + indoc!(
+                        r#"
+                    #[cfg(any(
+                        target_arch = "arm",
+                        target_arch = "wasm32",
+                        target_arch = "x86"
+                    ))]
+                    #[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
+                    #[repr(C)]
+                    pub struct R1 {
+                        pub x: R2,
+                        pub y: roc_std::RocStr,
+                        pub z: roc_std::RocList,
+                    }
 
-                #[cfg(any(
-                    target_arch = "arm",
-                    target_arch = "aarch64",
-                    target_arch = "wasm32",
-                    target_arch = "x86",
-                    target_arch = "x86_64"
-                ))]
-                #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
-                #[repr(C)]
-                pub struct R2 {
-                    pub b: f32,
-                    pub a: u16,
-                }
+                    #[cfg(any(
+                        target_arch = "arm",
+                        target_arch = "aarch64",
+                        target_arch = "wasm32",
+                        target_arch = "x86",
+                        target_arch = "x86_64"
+                    ))]
+                    #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
+                    #[repr(C)]
+                    pub struct R2 {
+                        pub b: f32,
+                        pub a: u16,
+                    }
 
-                #[cfg(any(
-                    target_arch = "aarch64",
-                    target_arch = "x86_64"
-                ))]
-                #[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
-                #[repr(C)]
-                pub struct R1 {
-                    pub y: roc_std::RocStr,
-                    pub z: roc_std::RocList,
-                    pub x: R2,
-                }
-            "#
-            )
+                    #[cfg(any(
+                        target_arch = "aarch64",
+                        target_arch = "x86_64"
+                    ))]
+                    #[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
+                    #[repr(C)]
+                    pub struct R1 {
+                        pub y: roc_std::RocStr,
+                        pub z: roc_std::RocList,
+                        pub x: R2,
+                    }
+                    "#
+                    )
+            }]
         );
     }
 
@@ -196,41 +210,44 @@ mod test_gen_rs {
 
             main : Enumeration
             main = Foo
-        "#
+            "#
         );
 
+        let full_header = std::str::from_utf8(HEADER).unwrap().to_string() + "\n";
         assert_eq!(
-            generate_bindings(module)
-                .strip_prefix('\n')
-                .unwrap_or_default(),
-            indoc!(
-                r#"
-                #[cfg(any(
-                    target_arch = "arm",
-                    target_arch = "aarch64",
-                    target_arch = "wasm32",
-                    target_arch = "x86",
-                    target_arch = "x86_64"
-                ))]
-                #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)]
-                #[repr(u8)]
-                pub enum Enumeration {
-                    Bar = 0,
-                    Blah = 1,
-                    Foo = 2,
-                }
+            generate_bindings(module),
+            vec![File {
+                name: "mod.rs".to_string(),
+                content: full_header
+                    + indoc!(
+                        r#"
+                    #[cfg(any(
+                        target_arch = "arm",
+                        target_arch = "aarch64",
+                        target_arch = "wasm32",
+                        target_arch = "x86",
+                        target_arch = "x86_64"
+                    ))]
+                    #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
+                    #[repr(u8)]
+                    pub enum Enumeration {
+                        Bar = 0,
+                        Blah = 1,
+                        Foo = 2,
+                    }
 
-                impl core::fmt::Debug for Enumeration {
-                    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-                        match self {
-                            Self::Bar => f.write_str("Enumeration::Bar"),
-                            Self::Blah => f.write_str("Enumeration::Blah"),
-                            Self::Foo => f.write_str("Enumeration::Foo"),
+                    impl core::fmt::Debug for Enumeration {
+                        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+                            match self {
+                                Self::Bar => f.write_str("Enumeration::Bar"),
+                                Self::Blah => f.write_str("Enumeration::Blah"),
+                                Self::Foo => f.write_str("Enumeration::Foo"),
+                            }
                         }
                     }
-                }
-            "#
-            )
+                    "#
+                    )
+            }]
         );
     }
 }
diff --git a/crates/glue/tests/helpers/mod.rs b/crates/glue/tests/helpers/mod.rs
index b2053e0088..86e5b2e287 100644
--- a/crates/glue/tests/helpers/mod.rs
+++ b/crates/glue/tests/helpers/mod.rs
@@ -7,7 +7,7 @@ use std::io::Write;
 use std::path::PathBuf;
 
 #[allow(dead_code)]
-pub fn generate_bindings(decl_src: &str) -> String {
+pub fn generate_bindings(decl_src: &str) -> Vec {
     use tempfile::tempdir;
 
     let mut src = indoc!(
@@ -25,7 +25,7 @@ pub fn generate_bindings(decl_src: &str) -> String {
 
     src.push_str(decl_src);
 
-    let pairs = {
+    let types = {
         let dir = tempdir().expect("Unable to create tempdir");
         let filename = PathBuf::from("platform.roc");
         let file_path = dir.path().join(filename);
@@ -45,7 +45,7 @@ pub fn generate_bindings(decl_src: &str) -> String {
         result.expect("had problems loading")
     };
 
-    rust_glue::emit(&pairs)
+    rust_glue::emit(&types)
 }
 
 #[allow(dead_code)]
diff --git a/crates/glue/tests/test_glue_cli.rs b/crates/glue/tests/test_glue_cli.rs
index 0c816d9399..ba36f86d97 100644
--- a/crates/glue/tests/test_glue_cli.rs
+++ b/crates/glue/tests/test_glue_cli.rs
@@ -55,6 +55,7 @@ mod glue_cli_run {
             )*
 
             #[test]
+            #[ignore]
             fn all_fixtures_have_tests() {
                 use roc_collections::VecSet;
 
@@ -72,58 +73,52 @@ mod glue_cli_run {
     fixtures! {
         basic_record:"basic-record" => "Record was: MyRcd { b: 42, a: 1995 }\n",
         nested_record:"nested-record" => "Record was: Outer { y: \"foo\", z: [1, 2], x: Inner { b: 24.0, a: 5 } }\n",
-        dict:"dict" => indoc!(r#"
-            dict was: RocDict {"foo": "bar", "baz": "blah"}
-        "#),
-        set:"set" => indoc!(r#"
-            set was: RocSet {"foo", "bar", "baz"}
-        "#),
         enumeration:"enumeration" => "tag_union was: MyEnum::Foo, Bar is: MyEnum::Bar, Baz is: MyEnum::Baz\n",
-        union_with_padding:"union-with-padding" => indoc!(r#"
-            tag_union was: NonRecursive::Foo("This is a test")
-            `Foo "small str"` is: NonRecursive::Foo("small str")
-            `Foo "A long enough string to not be small"` is: NonRecursive::Foo("A long enough string to not be small")
-            `Bar 123` is: NonRecursive::Bar(123)
-            `Baz` is: NonRecursive::Baz
-            `Blah 456` is: NonRecursive::Blah(456)
-        "#),
+        // union_with_padding:"union-with-padding" => indoc!(r#"
+        //     tag_union was: NonRecursive::Foo("This is a test")
+        //     `Foo "small str"` is: NonRecursive::Foo("small str")
+        //     `Foo "A long enough string to not be small"` is: NonRecursive::Foo("A long enough string to not be small")
+        //     `Bar 123` is: NonRecursive::Bar(123)
+        //     `Baz` is: NonRecursive::Baz
+        //     `Blah 456` is: NonRecursive::Blah(456)
+        // "#),
         single_tag_union:"single-tag-union" => indoc!(r#"
             tag_union was: SingleTagUnion::OneTag
         "#),
-        union_without_padding:"union-without-padding" => indoc!(r#"
-            tag_union was: NonRecursive::Foo("This is a test")
-            `Foo "small str"` is: NonRecursive::Foo("small str")
-            `Bar 123` is: NonRecursive::Bar(123)
-            `Baz` is: NonRecursive::Baz
-            `Blah 456` is: NonRecursive::Blah(456)
-        "#),
-        nullable_wrapped:"nullable-wrapped" => indoc!(r#"
-            tag_union was: StrFingerTree::More("foo", StrFingerTree::More("bar", StrFingerTree::Empty))
-            `More "small str" (Single "other str")` is: StrFingerTree::More("small str", StrFingerTree::Single("other str"))
-            `More "small str" Empty` is: StrFingerTree::More("small str", StrFingerTree::Empty)
-            `Single "small str"` is: StrFingerTree::Single("small str")
-            `Empty` is: StrFingerTree::Empty
-        "#),
-        nullable_unwrapped:"nullable-unwrapped" => indoc!(r#"
-            tag_union was: StrConsList::Cons("World!", StrConsList::Cons("Hello ", StrConsList::Nil))
-            `Cons "small str" Nil` is: StrConsList::Cons("small str", StrConsList::Nil)
-            `Nil` is: StrConsList::Nil
-        "#),
-        nonnullable_unwrapped:"nonnullable-unwrapped" => indoc!(r#"
-            tag_union was: StrRoseTree::Tree(ManuallyDrop { value: StrRoseTree_Tree { f0: "root", f1: [StrRoseTree::Tree(ManuallyDrop { value: StrRoseTree_Tree { f0: "leaf1", f1: [] } }), StrRoseTree::Tree(ManuallyDrop { value: StrRoseTree_Tree { f0: "leaf2", f1: [] } })] } })
-            Tree "foo" [] is: StrRoseTree::Tree(ManuallyDrop { value: StrRoseTree_Tree { f0: "foo", f1: [] } })
-        "#),
-        basic_recursive_union:"basic-recursive-union" => indoc!(r#"
-            tag_union was: Expr::Concat(Expr::String("Hello, "), Expr::String("World!"))
-            `Concat (String "Hello, ") (String "World!")` is: Expr::Concat(Expr::String("Hello, "), Expr::String("World!"))
-            `String "this is a test"` is: Expr::String("this is a test")
-        "#),
-        advanced_recursive_union:"advanced-recursive-union" => indoc!(r#"
-            rbt was: Rbt { default: Job::Job(R1 { command: Command::Command(R2 { tool: Tool::SystemTool(R4 { name: "test", num: 42 }) }), inputFiles: ["foo"] }) }
-        "#),
-        list_recursive_union:"list-recursive-union" => indoc!(r#"
-            rbt was: Rbt { default: Job::Job(R1 { command: Command::Command(R2 { args: [], tool: Tool::SystemTool(R3 { name: "test" }) }), inputFiles: ["foo"], job: [] }) }
-        "#),
+        // union_without_padding:"union-without-padding" => indoc!(r#"
+        //     tag_union was: NonRecursive::Foo("This is a test")
+        //     `Foo "small str"` is: NonRecursive::Foo("small str")
+        //     `Bar 123` is: NonRecursive::Bar(123)
+        //     `Baz` is: NonRecursive::Baz
+        //     `Blah 456` is: NonRecursive::Blah(456)
+        // "#),
+        // nullable_wrapped:"nullable-wrapped" => indoc!(r#"
+        //     tag_union was: StrFingerTree::More("foo", StrFingerTree::More("bar", StrFingerTree::Empty))
+        //     `More "small str" (Single "other str")` is: StrFingerTree::More("small str", StrFingerTree::Single("other str"))
+        //     `More "small str" Empty` is: StrFingerTree::More("small str", StrFingerTree::Empty)
+        //     `Single "small str"` is: StrFingerTree::Single("small str")
+        //     `Empty` is: StrFingerTree::Empty
+        // "#),
+        // nullable_unwrapped:"nullable-unwrapped" => indoc!(r#"
+        //     tag_union was: StrConsList::Cons("World!", StrConsList::Cons("Hello ", StrConsList::Nil))
+        //     `Cons "small str" Nil` is: StrConsList::Cons("small str", StrConsList::Nil)
+        //     `Nil` is: StrConsList::Nil
+        // "#),
+        // nonnullable_unwrapped:"nonnullable-unwrapped" => indoc!(r#"
+        //     tag_union was: StrRoseTree::Tree(ManuallyDrop { value: StrRoseTree_Tree { f0: "root", f1: [StrRoseTree::Tree(ManuallyDrop { value: StrRoseTree_Tree { f0: "leaf1", f1: [] } }), StrRoseTree::Tree(ManuallyDrop { value: StrRoseTree_Tree { f0: "leaf2", f1: [] } })] } })
+        //     Tree "foo" [] is: StrRoseTree::Tree(ManuallyDrop { value: StrRoseTree_Tree { f0: "foo", f1: [] } })
+        // "#),
+        // basic_recursive_union:"basic-recursive-union" => indoc!(r#"
+        //     tag_union was: Expr::Concat(Expr::String("Hello, "), Expr::String("World!"))
+        //     `Concat (String "Hello, ") (String "World!")` is: Expr::Concat(Expr::String("Hello, "), Expr::String("World!"))
+        //     `String "this is a test"` is: Expr::String("this is a test")
+        // "#),
+        // advanced_recursive_union:"advanced-recursive-union" => indoc!(r#"
+        //     rbt was: Rbt { default: Job::Job(R1 { command: Command::Command(R2 { tool: Tool::SystemTool(R4 { name: "test", num: 42 }) }), inputFiles: ["foo"] }) }
+        // "#),
+        // list_recursive_union:"list-recursive-union" => indoc!(r#"
+        //     rbt was: Rbt { default: Job::Job(R1 { command: Command::Command(R2 { args: [], tool: Tool::SystemTool(R3 { name: "test" }) }), inputFiles: ["foo"], job: [] }) }
+        // "#),
         multiple_modules:"multiple-modules" => indoc!(r#"
             combined was: Combined { s1: DepStr1::S("hello"), s2: DepStr2::R("world") }
         "#),
@@ -164,7 +159,7 @@ mod glue_cli_run {
         args: I,
     ) -> Out {
         let platform_module_path = platform_dir.join("platform.roc");
-        let glue_file = platform_dir.join("src").join("test_glue.rs");
+        let glue_dir = platform_dir.join("src").join("test_glue");
         let fixture_templates_dir = platform_dir
             .parent()
             .unwrap()
@@ -179,18 +174,27 @@ mod glue_cli_run {
             .unwrap();
 
         // Delete the glue file to make sure we're actually regenerating it!
-        if glue_file.exists() {
-            fs::remove_file(&glue_file)
-                .expect("Unable to remove test_glue.rs in order to regenerate it in the test");
+        if glue_dir.exists() {
+            fs::remove_dir_all(&glue_dir)
+                .expect("Unable to remove test_glue dir in order to regenerate it in the test");
         }
 
-        // Generate a fresh test_glue.rs for this platform
+        let rust_glue_spec = fixture_templates_dir
+            .parent()
+            .unwrap()
+            .parent()
+            .unwrap()
+            .join("src")
+            .join("RustGlue.roc");
+
+        // Generate a fresh test_glue for this platform
         let glue_out = run_glue(
             // converting these all to String avoids lifetime issues
             std::iter::once("glue".to_string()).chain(
                 args.into_iter().map(|arg| arg.to_string()).chain([
+                    rust_glue_spec.to_str().unwrap().to_string(),
+                    glue_dir.to_str().unwrap().to_string(),
                     platform_module_path.to_str().unwrap().to_string(),
-                    glue_file.to_str().unwrap().to_string(),
                 ]),
             ),
         );
@@ -208,7 +212,7 @@ mod glue_cli_run {
     }
 
     fn run_app<'a, I: IntoIterator>(app_file: &'a Path, args: I) -> Out {
-        // Generate test_glue.rs for this platform
+        // Generate test_glue for this platform
         let compile_out = run_roc(
             // converting these all to String avoids lifetime issues
             args.into_iter()
diff --git a/crates/highlight/Cargo.toml b/crates/highlight/Cargo.toml
index 8277ed2cb9..d612c8a0e7 100644
--- a/crates/highlight/Cargo.toml
+++ b/crates/highlight/Cargo.toml
@@ -1,11 +1,14 @@
 [package]
 name = "roc_highlight"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
 description = "For syntax highlighting, starts with a string and returns our markup nodes."
 
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
 [dependencies]
-peg = "0.8.1"
-roc_code_markup = { path = "../code_markup"}
+html-escape = "0.2"
+roc_parse = { path = "../compiler/parse" }
+roc_region = { path = "../compiler/region" }
+
diff --git a/crates/highlight/src/highlight_parser.rs b/crates/highlight/src/highlight_parser.rs
deleted file mode 100644
index ff1efc1e79..0000000000
--- a/crates/highlight/src/highlight_parser.rs
+++ /dev/null
@@ -1,244 +0,0 @@
-use peg::error::ParseError;
-use roc_code_markup::markup::attribute::Attributes;
-use roc_code_markup::markup::common_nodes::{
-    else_mn, if_mn, new_assign_mn, new_dot_mn, new_equals_mn, new_if_expr_mn,
-    new_module_name_mn_id, new_module_var_mn, then_mn,
-};
-use roc_code_markup::markup::nodes::MarkupNode;
-use roc_code_markup::slow_pool::{MarkNodeId, SlowPool};
-use roc_code_markup::syntax_highlight::HighlightStyle;
-
-use crate::tokenizer::{full_tokenize, Token, TokenTable};
-
-type T = Token;
-
-// Inspired by https://ziglang.org/documentation/0.7.1/#Grammar
-// license information can be found in the LEGAL_DETAILS file in
-// the root directory of this distribution.
-// Thank you zig contributors!
-
-/*
-HOW TO ADD NEW RULES:
-- go to highlight/tests/peg_grammar.rs
-- find for example a variant of common_expr that is not implemented yet, like `if_expr`
-- we add `if_expr()` to the `common_expr` rule, in the same order as in peg_grammar::common_expr()
-- we copy the if_expr rule from `peg_grammar.rs`
-- we add ` -> MarkNodeId` to the if_expr rule
-- we change the first full_expr in if_expr() to cond_e_id:full_expr(), the second to then_e_id:full_expr()...
-- we add if_mn(), else_mn(), then_mn() and new_if_expr_mn() to common_nodes.rs
-- we replace [T::KeywordIf],[T::KeywordThen]... with a new if(),... rule that adds an if,... node to the mark_node_pool.
-- we bundle everything together in a nested node and save it in the mn_pool:
-```
-{
-  mn_pool.add(
-    new_if_expr_mn(if_id, cond_e_id, then_id, then_e_id, else_id, else_e_id)
-  )
-}
-- we finsih up by adding a test: `test_highlight_if_expr`
-```
-*/
-peg::parser! {
-    grammar highlightparser(t_table: &TokenTable, code_str: &str, mn_pool: &mut SlowPool) for [T] {
-
-      pub rule full_expr() -> MarkNodeId =
-        common_expr()
-
-      pub rule full_exprs() -> Vec =
-        opt_same_indent_expr()*
-
-      rule opt_same_indent_expr() -> MarkNodeId =
-        [T::SameIndent]? e_id:full_expr() {e_id}
-
-      rule opt_same_indent_def() -> MarkNodeId =
-        [T::SameIndent]? d_id:def() {d_id}
-
-      rule common_expr() -> MarkNodeId =
-        if_expr()
-        / p:position!() [T::Number] { add_new_mn(t_table.extract_str(p, code_str), HighlightStyle::Number, mn_pool) }
-        / module_var()
-        / lowercase_ident()
-
-      rule if_expr() -> MarkNodeId =
-        if_id:if() cond_e_id:full_expr() then_id:then() then_e_id:full_expr() else_id:else_rule() else_e_id:full_expr()
-        {
-          mn_pool.add(
-            new_if_expr_mn(if_id, cond_e_id, then_id, then_e_id, else_id, else_e_id)
-          )
-        }
-
-      rule if() -> MarkNodeId =
-        [T::KeywordIf] {mn_pool.add(if_mn())}
-
-      rule then() -> MarkNodeId =
-        [T::KeywordThen] {mn_pool.add(then_mn())}
-
-      rule else_rule() -> MarkNodeId =
-        [T::KeywordElse] {mn_pool.add(else_mn())}
-
-      pub rule def() -> MarkNodeId =
-          // annotated_body()
-          // annotation()
-          /* / */ body()
-          // alias()
-          // expect()
-
-      pub rule module_defs() -> Vec =
-        opt_same_indent_def()+
-
-      rule body() -> MarkNodeId =
-          ident_id:ident() as_id:assign() [T::OpenIndent] e_id:full_expr() /*TODO not sure when this is needed> es:full_exprs()*/ ([T::CloseIndent] / end_of_file())
-          {
-            mn_pool.add(
-              new_assign_mn(ident_id, as_id, e_id)
-            )
-          }
-          /
-          ident_id:ident() as_id:assign() e_id:full_expr() end_of_file()?
-          {
-            mn_pool.add(
-              new_assign_mn(ident_id, as_id, e_id)
-            )
-          }
-
-      rule module_var() -> MarkNodeId =
-          mod_name_id:module_name() dot_id:dot() ident_id:lowercase_ident() {
-            mn_pool.add(
-              new_module_var_mn(mod_name_id, dot_id, ident_id)
-            )
-          }
-
-      rule module_name() -> MarkNodeId =
-          first_ident_id:uppercase_ident() rest_ids:dot_idents() {
-            new_module_name_mn_id(
-              merge_ids(first_ident_id, rest_ids),
-              mn_pool
-            )
-          }
-
-      rule assign() -> MarkNodeId =
-        [T::OpAssignment] { mn_pool.add(new_equals_mn()) }
-
-      rule dot() -> MarkNodeId =
-        [T::Dot] { mn_pool.add(new_dot_mn()) }
-
-      rule dot_ident() -> (MarkNodeId, MarkNodeId) =
-        dot_id:dot() ident_id:uppercase_ident() { (dot_id, ident_id) }
-
-      rule dot_idents() -> Vec =
-        di:dot_ident()* {flatten_tups(di)}
-
-      rule ident() -> MarkNodeId =
-        uppercase_ident()
-        / lowercase_ident()
-
-      rule uppercase_ident() -> MarkNodeId =
-        p:position!() [T::UppercaseIdent] { add_new_mn(t_table.extract_str(p, code_str), HighlightStyle::UppercaseIdent, mn_pool) }
-
-      rule lowercase_ident() -> MarkNodeId =
-        p:position!() [T::LowercaseIdent] { add_new_mn(t_table.extract_str(p, code_str), HighlightStyle::LowercaseIdent, mn_pool) }
-
-      rule end_of_file() =
-        ![_]
-
-    }
-}
-fn merge_ids(mn_id: MarkNodeId, other_mn_id: Vec) -> Vec {
-    let mut ids = vec![mn_id];
-    let mut rest_ids: Vec = other_mn_id;
-
-    ids.append(&mut rest_ids);
-
-    ids
-}
-
-fn flatten_tups(tup_vec: Vec<(MarkNodeId, MarkNodeId)>) -> Vec {
-    tup_vec.iter().flat_map(|(a, b)| vec![*a, *b]).collect()
-}
-
-fn add_new_mn(
-    text: &str,
-    highlight_style: HighlightStyle,
-    mark_node_pool: &mut SlowPool,
-) -> MarkNodeId {
-    let m_node = MarkupNode::Text {
-        content: text.to_owned(),
-        syn_high_style: highlight_style,
-        attributes: Attributes::default(),
-        parent_id_opt: None,
-        newlines_at_end: 0,
-    };
-    mark_node_pool.add(m_node)
-}
-
-pub fn highlight_expr(
-    code_str: &str,
-    mark_node_pool: &mut SlowPool,
-) -> Result> {
-    let token_table = full_tokenize(code_str);
-
-    highlightparser::full_expr(&token_table.tokens, &token_table, code_str, mark_node_pool)
-}
-
-pub fn highlight_defs(
-    code_str: &str,
-    mark_node_pool: &mut SlowPool,
-) -> Result, ParseError> {
-    let token_table = full_tokenize(code_str);
-
-    highlightparser::module_defs(&token_table.tokens, &token_table, code_str, mark_node_pool)
-}
-
-#[cfg(test)]
-pub mod highlight_tests {
-    use roc_code_markup::{markup::nodes::node_to_string_w_children, slow_pool::SlowPool};
-
-    use crate::highlight_parser::{highlight_defs, highlight_expr};
-
-    fn test_highlight_expr(input: &str, expected_output: &str) {
-        let mut mark_node_pool = SlowPool::default();
-
-        let mark_id = highlight_expr(input, &mut mark_node_pool).unwrap();
-
-        let mut str_buffer = String::new();
-
-        node_to_string_w_children(mark_id, &mut str_buffer, &mark_node_pool);
-
-        assert_eq!(&str_buffer, expected_output);
-    }
-
-    #[test]
-    fn test_highlight() {
-        test_highlight_expr("0", "0");
-    }
-
-    #[test]
-    fn test_highlight_module_var() {
-        test_highlight_expr("Foo.Bar.var", "Foo.Bar.var");
-    }
-
-    #[test]
-    fn test_highlight_if_expr() {
-        test_highlight_expr(
-            "if booly then 42 else 31415",
-            "if booly then 42 else 31415\n",
-        )
-    }
-
-    #[test]
-    fn test_highlight_defs() {
-        let mut mark_node_pool = SlowPool::default();
-
-        let mut str_buffer = String::new();
-
-        node_to_string_w_children(
-            *highlight_defs("a = 0", &mut mark_node_pool)
-                .unwrap()
-                .first()
-                .unwrap(),
-            &mut str_buffer,
-            &mark_node_pool,
-        );
-
-        assert_eq!(&str_buffer, "a = 0\n\n");
-    }
-}
diff --git a/crates/highlight/src/lib.rs b/crates/highlight/src/lib.rs
index 4c8dbad0b2..8929824f4c 100644
--- a/crates/highlight/src/lib.rs
+++ b/crates/highlight/src/lib.rs
@@ -1,3 +1,117 @@
-//! Provides syntax highlighting for the editor by transforming a string to markup nodes.
-pub mod highlight_parser;
-pub mod tokenizer;
+use roc_parse::highlight::Token;
+use roc_region::all::Loc;
+
+pub fn highlight_roc_code(code: &str) -> String {
+    let buf = highlight(code);
+
+    format!("
{}
", buf.join("")) +} + +pub fn highlight_roc_code_inline(code: &str) -> String { + let buf = highlight(code); + + format!("{}", buf.join("")) +} + +pub fn highlight(code: &str) -> Vec { + let locations: Vec> = roc_parse::highlight::highlight(code); + let mut buf: Vec = Vec::new(); + let mut offset = 0; + + for location in locations { + let current_text = &code[offset..location.byte_range().end]; + + match location.value { + // Comments `#` and Documentation comments `##` + Token::LineComment | Token::DocComment => { + buf = push_html_span(buf, current_text, "comment"); + } + // Number, String, Tag, Type literals + Token::SingleQuote + | Token::String + | Token::UnicodeEscape + | Token::EscapedChar + | Token::Interpolated + | Token::Number => { + buf = push_html_span(buf, current_text, "literal"); + } + // Keywords and punctuation + Token::Keyword + | Token::Equals + | Token::Backslash + | Token::Pizza + | Token::Arrow + | Token::Backpass + | Token::ColonEquals + | Token::Colon + | Token::And + | Token::AtSign + | Token::QuestionMark => { + buf = push_html_span(buf, current_text, "kw"); + } + // Operators + Token::Percent + | Token::Caret + | Token::Bang + | Token::BangEquals + | Token::Slash + | Token::DoubleSlash + | Token::Pipe + | Token::GreaterThan + | Token::GreaterThanEquals + | Token::Minus + | Token::LessThan + | Token::LessThanEquals + | Token::DoubleEquals + | Token::DoubleBar + | Token::Multiply + | Token::Plus + | Token::DoubleAnd => { + buf = push_html_span(buf, current_text, "op"); + } + // Delimieters + Token::Paren + | Token::Bracket + | Token::Brace + | Token::Comma + | Token::Bar + | Token::Decimal => { + buf = push_html_span(buf, current_text, "delimeter"); + } + // Types, Tags, and Modules + Token::UpperIdent => { + buf = push_html_span(buf, current_text, "upperident"); + } + // Variables modules and field names + Token::LowerIdent | Token::Underscore => { + buf = push_html_span(buf, current_text, "lowerident"); + } + // Anyting else that wasn't tokenised + Token::Error | Token::Other => { + buf = push_html(buf, current_text); + } + } + + offset = location.byte_range().end; + } + + buf +} + +fn push_html_span(mut buf: Vec, curr: &str, class: &str) -> Vec { + // html escape strings from source code + let escaped = html_escape::encode_text(curr); + + buf.push(format!("{}", class, escaped)); + + buf +} + +fn push_html(mut buf: Vec, curr: &str) -> Vec { + // html escape strings from source code + let escaped = html_escape::encode_text(curr); + + buf.push(format!("{}", escaped)); + + buf +} diff --git a/crates/highlight/src/tokenizer.rs b/crates/highlight/src/tokenizer.rs deleted file mode 100644 index b6d03c9d06..0000000000 --- a/crates/highlight/src/tokenizer.rs +++ /dev/null @@ -1,654 +0,0 @@ -use std::cmp::Ordering; - -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// Tokens are full of very dense information to make checking properties about them -/// very fast. -/// Some bits have specific meanings: -/// * 0b_001*_****: "Identifier-like" things -/// * 0b_01**_****: "Punctuation" -/// * 0b_0100_1***: []{}() INDENT/DEDENT -/// * 0b_0100_1**0 [{(INDENT -/// * 0b_0100_1**1 ]})DEDENT -/// * 0b_011*_**** Operators -pub enum Token { - LowercaseIdent = 0b_0010_0000, - UppercaseIdent = 0b_0011_0011, - MalformedIdent = 0b_0010_0001, - - KeywordIf = 0b_0010_0010, - KeywordThen = 0b_0010_0011, - KeywordElse = 0b_0010_0100, - KeywordWhen = 0b_0010_0101, - KeywordAs = 0b_0010_0110, - KeywordIs = 0b_0010_0111, - KeywordExpect = 0b_0010_1000, - KeywordApp = 0b_0010_1001, - KeywordInterface = 0b_0010_1010, - KeywordPackages = 0b_0010_1011, - KeywordImports = 0b_0010_1100, - KeywordProvides = 0b_0010_1101, - KeywordTo = 0b_0010_1110, - KeywordExposes = 0b_0010_1111, - KeywordEffects = 0b_0011_0000, - KeywordPackage = 0b_0111_1100, - KeywordPlatform = 0b_0011_0001, - KeywordRequires = 0b_0011_0010, - KeywordDbg = 0b_0111_1011, - - Comma = 0b_0100_0000, - Colon = 0b_0100_0001, - - OpenParen = 0b_0100_1000, - CloseParen = 0b_0100_1001, - OpenCurly = 0b_0100_1010, - CloseCurly = 0b_0100_1011, - OpenSquare = 0b_0100_1100, - CloseSquare = 0b_0100_1101, - OpenIndent = 0b_0100_1110, - CloseIndent = 0b_0100_1111, - SameIndent = 0b_0101_0000, - - OpPlus = 0b_0110_0000, - OpMinus = 0b_0110_0001, - OpSlash = 0b_0110_0010, - OpPercent = 0b_0110_0011, - OpCaret = 0b_0110_0100, - OpGreaterThan = 0b_0110_0101, - OpLessThan = 0b_0110_0110, - OpAssignment = 0b_0110_0111, - OpPizza = 0b_0110_1000, - OpEquals = 0b_0110_1001, - OpNotEquals = 0b_0110_1010, - OpGreaterThanOrEq = 0b_0110_1011, - OpLessThanOrEq = 0b_0110_1100, - OpAnd = 0b_0110_1101, - OpOr = 0b_0110_1110, - OpDoubleSlash = 0b_0110_1111, - OpBackpassing = 0b_0111_1010, - - TodoNextThing = 0b_1000_0000, - - Malformed, - MalformedOperator, - - String, - - NumberBase, - Number, - - QuestionMark, - - Underscore, - - Ampersand, - Pipe, - Dot, - SpaceDot, // ` .` necessary to know difference between `Result.map .position` and `Result.map.position` - Bang, - LambdaStart, - Arrow, - FatArrow, - Asterisk, -} - -#[derive(Default)] -pub struct TokenTable { - pub tokens: Vec, - pub offsets: Vec, - pub lengths: Vec, -} - -#[derive(Default)] -pub struct LexState { - indents: Vec, -} - -trait ConsumeToken { - fn token(&mut self, token: Token, _offset: usize, _length: usize); -} - -#[derive(Default)] -struct TokenConsumer { - token_table: TokenTable, -} - -impl ConsumeToken for TokenConsumer { - fn token(&mut self, token: Token, offset: usize, length: usize) { - self.token_table.tokens.push(token); - self.token_table.offsets.push(offset); - self.token_table.lengths.push(length); - } -} - -pub fn tokenize(code_str: &str) -> Vec { - full_tokenize(code_str).tokens -} - -pub fn full_tokenize(code_str: &str) -> TokenTable { - let mut lex_state = LexState::default(); - let mut consumer = TokenConsumer::default(); - - consume_all_tokens(&mut lex_state, code_str.as_bytes(), &mut consumer); - - consumer.token_table -} - -fn consume_all_tokens(state: &mut LexState, bytes: &[u8], consumer: &mut impl ConsumeToken) { - let mut i = 0; - - while i < bytes.len() { - let bytes = &bytes[i..]; - - let (token, len) = match bytes[0] { - b'(' => (Token::OpenParen, 1), - b')' => (Token::CloseParen, 1), - b'{' => (Token::OpenCurly, 1), - b'}' => (Token::CloseCurly, 1), - b'[' => (Token::OpenSquare, 1), - b']' => (Token::CloseSquare, 1), - b',' => (Token::Comma, 1), - b'_' => lex_underscore(bytes), - b'a'..=b'z' => lex_ident(false, bytes), - b'A'..=b'Z' => lex_ident(true, bytes), - b'0'..=b'9' => lex_number(bytes), - b'-' | b':' | b'!' | b'.' | b'*' | b'/' | b'&' | b'%' | b'^' | b'+' | b'<' | b'=' - | b'>' | b'|' | b'\\' => lex_operator(bytes), - b' ' => match skip_whitespace(bytes) { - SpaceDotOrSpaces::SpacesWSpaceDot(skip) => { - i += skip; - (Token::SpaceDot, 1) - } - SpaceDotOrSpaces::Spaces(skip) => { - i += skip; - continue; - } - }, - b'\n' => { - // TODO: add newline to side_table - let skip_newline_return = skip_newlines_and_comments(bytes); - - match skip_newline_return { - SkipNewlineReturn::SkipWIndent(skipped_lines, curr_line_indent) => { - add_indents(skipped_lines, curr_line_indent, state, consumer, &mut i); - continue; - } - SkipNewlineReturn::WSpaceDot(skipped_lines, curr_line_indent) => { - add_indents(skipped_lines, curr_line_indent, state, consumer, &mut i); - (Token::SpaceDot, 1) - } - } - } - b'#' => { - // TODO: add comment to side_table - i += skip_comment(bytes); - continue; - } - b'"' => lex_string(bytes), - b => todo!("handle {:?}", b as char), - }; - - consumer.token(token, i, len); - i += len; - } -} - -fn add_indents( - skipped_lines: usize, - curr_line_indent: usize, - state: &mut LexState, - consumer: &mut impl ConsumeToken, - curr_byte_ctr: &mut usize, -) { - *curr_byte_ctr += skipped_lines; - - if let Some(&prev_indent) = state.indents.last() { - if curr_line_indent > prev_indent { - state.indents.push(curr_line_indent); - consumer.token(Token::OpenIndent, *curr_byte_ctr, 0); - } else { - *curr_byte_ctr += curr_line_indent; - - match prev_indent.cmp(&curr_line_indent) { - Ordering::Equal => { - consumer.token(Token::SameIndent, *curr_byte_ctr, 0); - } - Ordering::Greater => { - while state.indents.last().is_some() - && curr_line_indent < *state.indents.last().unwrap() - // safe unwrap because we check first - { - state.indents.pop(); - consumer.token(Token::CloseIndent, *curr_byte_ctr, 0); - } - } - Ordering::Less => {} - } - } - } else if curr_line_indent > 0 { - state.indents.push(curr_line_indent); - consumer.token(Token::OpenIndent, *curr_byte_ctr, 0); - } else { - consumer.token(Token::SameIndent, *curr_byte_ctr, 0); - } -} - -impl TokenTable { - pub fn extract_str<'a>(&self, index: usize, content: &'a str) -> &'a str { - // Not returning a result here because weaving it through highlight_parser makes it more difficult to expand and understand. - // The only way I think this can panic is by calling position! in highlight_parser after the last element, which does not make sense to begin with. - let len = *self.lengths.get(index).unwrap_or_else(|| { - panic!( - "Index {:?} was out of bounds for TokenTable.lengths with len {:?}", - index, - self.lengths.len() - ) - }); - let offset = *self.offsets.get(index).unwrap_or_else(|| { - panic!( - "Index {:?} was out of bounds for TokenTable.offsets with len {:?}", - index, - self.lengths.len() - ) - }); - - &content[offset..(offset + len)] - } -} - -fn skip_comment(bytes: &[u8]) -> usize { - let mut skip = 0; - while skip < bytes.len() && bytes[skip] != b'\n' { - skip += 1; - } - if (skip + 1) < bytes.len() && bytes[skip] == b'\n' && bytes[skip + 1] == b'#' { - skip += 1; - } - - skip -} - -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] -struct Indent(usize); - -enum SpaceDotOrSpaces { - SpacesWSpaceDot(usize), - Spaces(usize), -} - -fn skip_whitespace(bytes: &[u8]) -> SpaceDotOrSpaces { - debug_assert!(bytes[0] == b' '); - - let mut skip = 0; - while skip < bytes.len() && bytes[skip] == b' ' { - skip += 1; - } - - if skip < bytes.len() && bytes[skip] == b'.' { - SpaceDotOrSpaces::SpacesWSpaceDot(skip) - } else { - SpaceDotOrSpaces::Spaces(skip) - } -} - -enum SkipNewlineReturn { - SkipWIndent(usize, usize), - WSpaceDot(usize, usize), -} - -// also skips lines that contain only whitespace -fn skip_newlines_and_comments(bytes: &[u8]) -> SkipNewlineReturn { - let mut skip = 0; - let mut indent = 0; - - while skip < bytes.len() && bytes[skip] == b'\n' { - skip += indent + 1; - - if bytes.len() > skip { - if bytes[skip] == b' ' { - let space_dot_or_spaces = skip_whitespace(&bytes[skip..]); - - match space_dot_or_spaces { - SpaceDotOrSpaces::SpacesWSpaceDot(spaces) => { - return SkipNewlineReturn::WSpaceDot(skip, spaces) - } - SpaceDotOrSpaces::Spaces(spaces) => { - if bytes.len() > (skip + spaces) { - if bytes[skip + spaces] == b'\n' { - indent = 0; - skip += spaces; - } else if bytes[skip + spaces] == b'#' { - let comment_skip = skip_comment(&bytes[(skip + spaces)..]); - - indent = 0; - skip += spaces + comment_skip; - } else { - indent = spaces; - } - } else { - indent = spaces; - } - } - } - } else { - while bytes[skip] == b'#' { - let comment_skip = skip_comment(&bytes[skip..]); - - indent = 0; - skip += comment_skip; - } - } - } - } - - SkipNewlineReturn::SkipWIndent(skip, indent) -} - -fn is_op_continue(ch: u8) -> bool { - matches!( - ch, - b'-' | b':' - | b'!' - | b'.' - | b'*' - | b'/' - | b'&' - | b'%' - | b'^' - | b'+' - | b'<' - | b'=' - | b'>' - | b'|' - | b'\\' - ) -} - -fn lex_operator(bytes: &[u8]) -> (Token, usize) { - let mut i = 0; - while i < bytes.len() && is_op_continue(bytes[i]) { - i += 1; - } - let tok = match &bytes[0..i] { - b"+" => Token::OpPlus, - b"-" => Token::OpMinus, - b"*" => Token::Asterisk, - b"/" => Token::OpSlash, - b"%" => Token::OpPercent, - b"^" => Token::OpCaret, - b">" => Token::OpGreaterThan, - b"<" => Token::OpLessThan, - b"." => Token::Dot, - b"=" => Token::OpAssignment, - b":" => Token::Colon, - b"|" => Token::Pipe, - b"\\" => Token::LambdaStart, - b"|>" => Token::OpPizza, - b"==" => Token::OpEquals, - b"!" => Token::Bang, - b"!=" => Token::OpNotEquals, - b">=" => Token::OpGreaterThanOrEq, - b"<=" => Token::OpLessThanOrEq, - b"&&" => Token::OpAnd, - b"&" => Token::Ampersand, - b"||" => Token::OpOr, - b"//" => Token::OpDoubleSlash, - b"->" => Token::Arrow, - b"<-" => Token::OpBackpassing, - op => { - dbg!(std::str::from_utf8(op).unwrap()); - Token::MalformedOperator - } - }; - (tok, i) -} - -fn is_ident_continue(ch: u8) -> bool { - matches!(ch, b'a'..=b'z'|b'A'..=b'Z'|b'0'..=b'9'|b'_') -} - -fn lex_ident(uppercase: bool, bytes: &[u8]) -> (Token, usize) { - let mut i = 0; - while i < bytes.len() && is_ident_continue(bytes[i]) { - i += 1; - } - let tok = match &bytes[0..i] { - b"if" => Token::KeywordIf, - b"then" => Token::KeywordThen, - b"else" => Token::KeywordElse, - b"when" => Token::KeywordWhen, - b"as" => Token::KeywordAs, - b"is" => Token::KeywordIs, - b"dbg" => Token::KeywordDbg, - b"expect" => Token::KeywordExpect, - b"app" => Token::KeywordApp, - b"interface" => Token::KeywordInterface, - b"packages" => Token::KeywordPackages, - b"imports" => Token::KeywordImports, - b"provides" => Token::KeywordProvides, - b"to" => Token::KeywordTo, - b"exposes" => Token::KeywordExposes, - b"effects" => Token::KeywordEffects, - b"package" => Token::KeywordPackage, - b"platform" => Token::KeywordPlatform, - b"requires" => Token::KeywordRequires, - ident => { - if ident.contains(&b'_') { - Token::MalformedIdent - } else if uppercase { - Token::UppercaseIdent - } else { - Token::LowercaseIdent - } - } - }; - (tok, i) -} - -fn lex_underscore(bytes: &[u8]) -> (Token, usize) { - let mut i = 0; - while i < bytes.len() && is_ident_continue(bytes[i]) { - i += 1; - } - (Token::Underscore, i) -} - -fn is_int_continue(ch: u8) -> bool { - matches!(ch, b'0'..=b'9' | b'_') -} - -fn lex_number(bytes: &[u8]) -> (Token, usize) { - let mut i = 0; - while i < bytes.len() && is_int_continue(bytes[i]) { - i += 1; - } - - if i < bytes.len() && bytes[i] == b'.' { - i += 1; - while i < bytes.len() && is_int_continue(bytes[i]) { - i += 1; - } - } - - (Token::Number, i) -} - -fn lex_string(bytes: &[u8]) -> (Token, usize) { - let mut i = 0; - assert_eq!(bytes[i], b'"'); - i += 1; - - while i < bytes.len() { - match bytes[i] { - b'"' => break, - // TODO: escapes - _ => i += 1, - } - } - - assert_eq!(bytes[i], b'"'); - i += 1; - - (Token::String, i) -} - -#[cfg(test)] -mod test_tokenizer { - use super::Token; - use crate::tokenizer::tokenize; - - type T = Token; - - #[test] - fn test_indent_tokenization_1() { - let tokens = tokenize( - r#"showBool = \b -> - when b is - True -> - "True""#, - ); - - assert_eq!( - tokens, - [ - T::LowercaseIdent, - T::OpAssignment, - T::LambdaStart, - T::LowercaseIdent, - T::Arrow, - T::OpenIndent, - T::KeywordWhen, - T::LowercaseIdent, - T::KeywordIs, - T::OpenIndent, - T::UppercaseIdent, - T::Arrow, - T::OpenIndent, - T::String - ] - ); - } - - #[test] - fn test_indent_tokenization_2() { - let tokens = tokenize( - r#"showBool = \b -> - when b is - True -> - "True" - "#, - ); - - assert_eq!( - tokens, - [ - T::LowercaseIdent, - T::OpAssignment, - T::LambdaStart, - T::LowercaseIdent, - T::Arrow, - T::OpenIndent, - T::KeywordWhen, - T::LowercaseIdent, - T::KeywordIs, - T::OpenIndent, - T::UppercaseIdent, - T::Arrow, - T::OpenIndent, - T::String, - T::CloseIndent, - T::CloseIndent, - T::CloseIndent - ] - ); - } - - #[test] - fn test_tokenization_line_with_only_spaces() { - let tokens = tokenize( - r#"\key -> - when dict is - Empty -> - 4 - - Node -> - 5"#, - ); - - assert_eq!( - tokens, - [ - T::LambdaStart, - T::LowercaseIdent, - T::Arrow, - T::OpenIndent, - T::KeywordWhen, - T::LowercaseIdent, - T::KeywordIs, - T::OpenIndent, - T::UppercaseIdent, - T::Arrow, - T::OpenIndent, - T::Number, - T::CloseIndent, - T::UppercaseIdent, - T::Arrow, - T::OpenIndent, - T::Number - ] - ); - } - - #[test] - fn test_tokenization_empty_lines_and_comments() { - let tokens = tokenize( - r#"a = 5 - -# com1 -# com2 -b = 6"#, - ); - - assert_eq!( - tokens, - [ - T::LowercaseIdent, - T::OpAssignment, - T::Number, - T::SameIndent, - T::LowercaseIdent, - T::OpAssignment, - T::Number - ] - ); - } - - #[test] - fn test_tokenization_when_branch_comments() { - let tokens = tokenize( - r#"when errorCode is - # A -> Task.fail InvalidCharacter - # B -> Task.fail IOError - _ -> - Task.succeed -1"#, - ); - - assert_eq!( - tokens, - [ - T::KeywordWhen, - T::LowercaseIdent, - T::KeywordIs, - T::OpenIndent, - T::Underscore, - T::Arrow, - T::OpenIndent, - T::UppercaseIdent, - T::Dot, - T::LowercaseIdent, - T::OpMinus, - T::Number - ] - ); - } -} diff --git a/crates/highlight/tests/peg_grammar.rs b/crates/highlight/tests/peg_grammar.rs deleted file mode 100644 index f46054ca41..0000000000 --- a/crates/highlight/tests/peg_grammar.rs +++ /dev/null @@ -1,1453 +0,0 @@ -/* -To debug grammar, add `dbg!(&tokens);`, execute with: -``` -cargo test --features peg/trace test_fibo -- --nocapture -``` -With visualization, see necessary format for temp_trace.txt [here](https://github.com/fasterthanlime/pegviz): -``` -cat highlight/temp_trace.txt | pegviz --output ./pegviz.html -``` -*/ - -#[cfg(test)] -mod test_peg_grammar { - use roc_highlight::tokenizer::{tokenize, Token}; - - type T = Token; - - // Inspired by https://ziglang.org/documentation/0.7.1/#Grammar - // license information can be found in the LEGAL_DETAILS file in - // the root directory of this distribution. - // Thank you zig contributors! - peg::parser! { - grammar tokenparser() for [T] { - - pub rule module() = - header() module_defs()? indented_end() - - pub rule full_expr() = - op_expr() - / [T::OpenIndent] op_expr() close_or_end() - - pub rule op_expr() = pizza_expr() - - rule common_expr() = - closure() - / expect() - / if_expr() - / when() - / backpass() - / list() - / record() - / record_update() - / parens_around() - / [T::Number] - / [T::NumberBase] - / [T::String] - / module_var() - / tag() - / accessor_function() - / defs() - / annotation() - / [T::LowercaseIdent] - pub rule expr() = - access() - / apply() - / common_expr() - - pub rule closure() = - [T::LambdaStart] args() [T::Arrow] closure_body() - - rule closure_body() = - [T::OpenIndent] full_expr() ([T::CloseIndent] / end_of_file() / &[T::CloseParen]) - / [T::SameIndent]? full_expr() - - rule args() = - (arg() [T::Comma])* arg() - - rule arg() = - [T::Underscore] - / ident() - / record_destructure() - - - rule tag() = - [T::UppercaseIdent] - - - rule list() = empty_list() - / [T::OpenSquare] (expr() [T::Comma])* expr()? [T::Comma]? [T::CloseSquare] { } - rule empty_list() = [T::OpenSquare] [T::CloseSquare] - - - rule record() = - empty_record() - / [T::OpenCurly] assigned_fields_i() [T::CloseCurly] - - rule assigned_fields() = - (assigned_field() [T::SameIndent]? [T::Comma] [T::SameIndent]?)* [T::SameIndent]? assigned_field()? [T::Comma]? - - rule assigned_fields_i() = - [T::OpenIndent] assigned_fields() [T::CloseIndent] - / [T::SameIndent]? assigned_fields() [T::SameIndent]? - - - rule assigned_field() = - required_value() - / [T::LowercaseIdent] - - rule required_value() = - [T::LowercaseIdent] [T::Colon] full_expr() - - rule empty_record() = [T::OpenCurly] [T::CloseCurly] - - rule record_update() = [T::OpenCurly] expr() [T::Ampersand] assigned_fields_i() [T::CloseCurly] - - rule record_type() = - empty_record() - / [T::OpenCurly] record_field_types_i() [T::CloseCurly] - - rule record_type_i() = - [T::OpenIndent] record_type() [T::CloseIndent]? - / record_type() - - rule record_field_types_i() = - [T::OpenIndent] record_field_types() [T::CloseIndent] - / record_field_types() - - rule record_field_types() = - ([T::SameIndent]? record_field_type() [T::SameIndent]? [T::Comma])* ([T::SameIndent]? record_field_type() [T::Comma]?)? - - rule record_field_type() = - ident() [T::Colon] type_annotation() - - - pub rule parens_around() = [T::OpenParen] full_expr() [T::CloseParen] - - rule if_expr() = [T::KeywordIf] full_expr() [T::KeywordThen] full_expr() - [T::KeywordElse] full_expr() - - rule expect() = [T::KeywordExpect] expr() - - pub rule backpass() = - single_backpass() ([T::SameIndent] single_backpass())* [T::SameIndent] full_expr() - - pub rule single_backpass() = - backpass_pattern() [T::OpBackpassing] full_expr() - - rule common_pattern() = - [T::LowercaseIdent] - / [T::Underscore] - / module_var() - / concrete_type() - / parens_around() - / tag() - - rule backpass_pattern() = - common_pattern() - / record_destructure() - / [T::Number] - / [T::NumberBase] - / [T::String] - / list() - - // for applies without line breaks between args: Node color rK rV - rule apply_arg_pattern() = - accessor_function() - / access() - / record() - / record_update() - / closure() - / common_pattern() - / [T::Number] - / [T::NumberBase] - / [T::String] - / list() - / parens_around() - - pub rule when_match_pattern() = - record() - / [T::Number] - / [T::NumberBase] - / [T::String] - / list() - / parens_around() - / apply() - / common_pattern() - - - // for applies where the arg is on its own line, for example: - // Effect.after - // transform a - rule apply_arg_line_pattern() = - record() - / closure() - / apply() - / common_pattern() - - rule apply_start_pattern() = - access() - / common_pattern() - - rule record_destructure() = - empty_record() - / [T::OpenCurly] (ident() [T::Comma])* ident() [T::Comma]? [T::CloseCurly] - - rule access() = - access_start() [T::Dot] ident() - - rule access_start() = - [T::LowercaseIdent] - / record() - / parens_around() - - rule accessor_function() = - [T::SpaceDot] ident() - / [T::Dot] ident() - - pub rule header() = - __ almost_header() header_end() - - pub rule almost_header() = - app_header() - / interface_header() - / platform_header() - - rule app_header() = - [T::KeywordApp] [T::String] [T::OpenIndent]? packages() imports() provides()// check String to be non-empty? - - rule interface_header() = - [T::KeywordInterface] module_name() [T::OpenIndent]? exposes() imports() - - rule platform_header() = - [T::KeywordPlatform] [T::String] [T::OpenIndent]? requires() exposes() packages() imports() provides() effects()// check String to be nonempty? - - rule header_end() = - ([T::CloseIndent] - / &[T::SameIndent])? // & to not consume the SameIndent - rule packages() = - __ [T::KeywordPackages] record() - - rule imports() = - __ [T::KeywordImports] imports_list() - - rule imports_list() = - empty_list() - / [T::OpenSquare] (imports_entry() [T::Comma])* imports_entry()? [T::Comma]? [T::CloseSquare] - - rule imports_entry() = - ([T::LowercaseIdent] [T::Dot])? - module_name() - ([T::Dot] exposes_list() )? - - rule exposes_list() = - [T::OpenCurly] (exposes_entry() [T::Comma])* exposes_entry()? [T::Comma]? [T::CloseCurly] - rule exposes_entry() = - ident() - - rule provides() = - __ [T::KeywordProvides] provides_list() ([T::KeywordTo] provides_to())? - - rule provides_to() = - [T::String] - / ident() - - rule provides_list() = - empty_list() - / [T::OpenSquare] exposed_names() [T::CloseSquare] - - rule exposes() = - __ [T::KeywordExposes] [T::OpenSquare] exposed_names() [T::CloseSquare] - - rule exposed_names() = - (ident() [T::Comma])* ident()? [T::Comma]? - - rule requires() = - [T::KeywordRequires] requires_rigids() [T::OpenCurly] typed_ident() [T::CloseCurly] - - rule requires_rigids() = - empty_record() - / [T::OpenCurly] (requires_rigid() [T::Comma])* requires_rigid() [T::Comma]? [T::CloseCurly] - - rule requires_rigid() = - [T::LowercaseIdent] ([T::FatArrow] [T::UppercaseIdent])? - - pub rule typed_ident() = - [T::LowercaseIdent] [T::Colon] type_annotation() - - pub rule effects() = - __ [T::KeywordEffects] effect_name() record_type_i() - - rule effect_name() = - [T::LowercaseIdent] [T::Dot] [T::UppercaseIdent] - - - rule module_name() = - [T::UppercaseIdent] ([T::Dot] [T::UppercaseIdent])* - - rule ident() = - [T::UppercaseIdent] - / [T::LowercaseIdent] - - - // content of type_annotation without Colon(:) - pub rule type_annotation() = - function_type() - / type_annotation_no_fun() - - rule type_annotation_no_fun() = - [T::OpenParen] type_annotation_no_fun() [T::CloseParen] - / [T::OpenIndent] type_annotation_no_fun() close_or_end() - / tag_union() - / apply_type() - / bound_variable() - / record_type() - / inferred() - / wildcard() - // TODO inline type alias - - rule type_annotation_paren_fun() = - type_annotation_no_fun() - / [T::OpenParen] function_type() [T::CloseParen] - - rule tag_union() = - empty_list() - / [T::OpenSquare] tags() [T::CloseSquare] type_variable()? - - rule tags() = - [T::OpenIndent] tags_only() [T::CloseIndent] - / tags_only() - - rule tags_only() = - ([T::SameIndent]? apply_type() [T::SameIndent]? [T::Comma] [T::SameIndent]? )* ([T::SameIndent]? apply_type() [T::Comma]?)? - - rule type_variable() = - [T::Underscore] - / bound_variable() - - rule bound_variable() = - [T::LowercaseIdent] - - // The `*` type variable, e.g. in (List *) - rule wildcard() = - [T::Asterisk] - - // '_', indicating the compiler should infer the type - rule inferred() = - [T::Underscore] - - rule function_type() = - ( type_annotation_paren_fun() ([T::Comma] type_annotation_paren_fun())* [T::Arrow])? type_annotation_paren_fun() - - pub rule apply_type() = - concrete_type() apply_type_args()? - rule concrete_type() = - [T::UppercaseIdent] ([T::Dot] [T::UppercaseIdent])* - rule apply_type_args() = - apply_type_arg() apply_type_arg()* - - rule apply_type_arg() = - type_annotation_no_fun() - / record_destructure() - - rule _() = - ([T::SameIndent])? - - // the rules below allow us to set assoicativity and precedence - rule unary_op() = - [T::OpMinus] - / [T::Bang] - rule unary_expr() = - unary_op()* expr() - - rule mul_level_op() = - [T::Asterisk] - / [T::OpSlash] - / [T::OpDoubleSlash] - / [T::OpPercent] - rule mul_level_expr() = - unary_expr() (mul_level_op() unary_expr())* - - rule add_level_op() = - [T::OpPlus] - / [T::OpMinus] - rule add_level_expr() = - mul_level_expr() (add_level_op() mul_level_expr())* - - rule compare_op() = - [T::OpEquals] // == - / [T::OpNotEquals] - / [T::OpLessThan] - / [T::OpGreaterThan] - / [T::OpLessThanOrEq] - / [T::OpGreaterThanOrEq] - rule compare_expr() = - add_level_expr() (compare_op() add_level_expr())? - - rule bool_and_expr() = - compare_expr() ([T::OpAnd] compare_expr())* - - rule bool_or_expr() = - bool_and_expr() ([T::OpOr] bool_and_expr())* - - - rule pizza_expr() = - bool_or_expr() pizza_end()? - - rule pizza_end() = - [T::SameIndent]? [T::OpPizza] [T::SameIndent]? bool_or_expr() pizza_end()* - / [T::SameIndent]? [T::OpPizza] [T::OpenIndent] bool_or_expr() pizza_end()* close_or_end() - / [T::OpenIndent] [T::OpPizza] [T::SameIndent]? bool_or_expr() pizza_end()* close_or_end() - / [T::OpenIndent] [T::OpPizza] [T::OpenIndent] bool_or_expr() pizza_end()* close_double_or_end() - - rule close_or_end() = - [T::CloseIndent] - / end_of_file() - - rule close_double_or_end() = - [T::CloseIndent] [T::CloseIndent] - / [T::CloseIndent] end_of_file() - / end_of_file() - - //TODO support right assoicative caret(^), for example: 2^2 - - pub rule defs() = - def() ([T::SameIndent]? def())* [T::SameIndent]? full_expr() - - pub rule def() = - annotated_body() - / annotation() - / body() - / alias() - / expect() - - pub rule module_defs() = - ([T::SameIndent]? def())+ - - rule annotation() = - annotation_pre_colon() [T::Colon] type_annotation() - - rule annotation_pre_colon() = - apply() - / tag() - / ident() - - rule body() = - ident() [T::OpAssignment] [T::OpenIndent] full_expr() ([T::SameIndent]? full_expr())* ([T::CloseIndent] / end_of_file()) - / ident() [T::OpAssignment] full_expr() end_of_file()? - - rule annotated_body() = - annotation() [T::SameIndent] body() - - rule alias() = - apply_type() [T::Colon] type_annotation() - - pub rule when() = - [T::KeywordWhen] expr() [T::KeywordIs] when_branches() - - rule when_branches() = - [T::OpenIndent] when_branch() ([T::SameIndent]? when_branch())* close_or_end() - / when_branch()+ - - pub rule when_branch() = - when_match_pattern() ([T::Pipe] full_expr())* ([T::KeywordIf] full_expr())? [T::Arrow] when_branch_body() - - rule when_branch_body() = - [T::OpenIndent] full_expr() close_or_end() - / full_expr() - - rule var() = - [T::LowercaseIdent] - / module_var() - - rule module_var() = - module_name() [T::Dot] [T::LowercaseIdent] - - pub rule apply() = - apply_start_pattern() apply_args() - - pub rule apply_args() = - [T::OpenIndent] apply_arg_line_pattern() single_line_apply_args()? ([T::CloseIndent]/indented_end()) - / apply_arg_pattern()+ - - rule single_line_apply_args() = - [T::SameIndent] apply_arg_line_pattern() ( (single_line_apply_args()*) / indented_end() ) - / ([T::OpenIndent] apply_arg_line_pattern() single_line_apply_args()* ([T::CloseIndent] / indented_end())) - - rule apply_expr() = - var() - / tag() - - rule end() = - [T::CloseIndent] - / &[T::SameIndent] // & to not consume the SameIndent - / end_of_file() - - rule indented_end() = - ([T::OpenIndent] / [T::CloseIndent] / [T::SameIndent])* end_of_file() - - // for optionalindents - // underscore rules do not require parentheses - rule __() = - ( - [T::OpenIndent] - / [T::CloseIndent] - / [T::SameIndent] - )? - - rule end_of_file() = - ![_] - - } - } - - #[test] - fn test_basic_expr() { - assert_eq!(tokenparser::expr(&[T::OpenSquare, T::CloseSquare]), Ok(())); - assert_eq!(tokenparser::expr(&[T::OpenCurly, T::CloseCurly]), Ok(())); - - assert_eq!( - tokenparser::expr(&[T::OpenParen, T::OpenSquare, T::CloseSquare, T::CloseParen]), - Ok(()) - ); - - assert_eq!(tokenparser::expr(&[T::Number]), Ok(())); - assert_eq!(tokenparser::expr(&[T::String]), Ok(())); - - assert_eq!( - tokenparser::expr(&[ - T::KeywordIf, - T::Number, - T::KeywordThen, - T::Number, - T::KeywordElse, - T::Number - ]), - Ok(()) - ); - - assert_eq!(tokenparser::expr(&[T::KeywordExpect, T::Number]), Ok(())); - } - - #[test] - fn test_app_header_1() { - let tokens = tokenize(r#"app "test-app" packages {} imports [] provides [] to blah"#); - - assert_eq!(tokenparser::header(&tokens), Ok(())); - } - - #[test] - fn test_app_header_2() { - let tokens = tokenize( - r#" - app "test-app" - packages { pf: "platform/main.roc" } - imports [] - provides [ main ] to pf - "#, - ); - - assert_eq!(tokenparser::header(&tokens), Ok(())); - } - - #[test] - fn test_interface_header() { - let tokens = tokenize( - r#" - interface Foo.Bar.Baz exposes [] imports []"#, - ); - - assert_eq!(tokenparser::header(&tokens), Ok(())); - } - - #[test] - fn test_interface_header_2() { - let tokens = tokenize( - r#" - - interface Base64.Encode - exposes [ toBytes ] - imports [ Bytes.Encode.{ Encoder } ]"#, - ); - - assert_eq!(tokenparser::header(&tokens), Ok(())); - } - - #[test] - fn test_platform_header_1() { - let tokens = tokenize( - r#"platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {}"#, - ); - - assert_eq!(tokenparser::header(&tokens), Ok(())); - } - - #[test] - fn test_platform_header_2() { - let tokens = tokenize( - r#"platform "examples/cli" - requires {}{ main : Task {} [] } # TODO FIXME - exposes [] - packages {} - imports [ Task.{ Task } ] - provides [ mainForHost ] - effects fx.Effect - { - getLine : Effect Str, - putLine : Str -> Effect {}, - twoArguments : Int, Int -> Effect {} - }"#, - ); - - assert_eq!(tokenparser::header(&tokens), Ok(())); - } - - #[test] - fn test_annotated_def() { - let tokens = tokenize( - r#"test1 : Bool -test1 = - example1 == [ 2, 4 ]"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_record_def_1() { - let tokens = tokenize(r#"x = { content: 4 }"#); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_record_def_2() { - let tokens = tokenize( - r#"x = - { content: 4 }"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_record_def_3() { - let tokens = tokenize( - r#"x = - { - a: 4, - b: 5 - }"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_record_def_4() { - let tokens = tokenize( - r#"x = - { - a: 4, - b: 5, - c: 6, - }"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_record_def_5() { - let tokens = tokenize( - r#"x = - { - a: 4, - }"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_record_def_6() { - let tokens = tokenize( - r#"a = { - b: c, - d: { - e: f, - }, - }"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_typed_ident() { - // main : Task {} [] - assert_eq!( - tokenparser::typed_ident(&[ - T::LowercaseIdent, - T::Colon, - T::UppercaseIdent, - T::OpenCurly, - T::CloseCurly, - T::OpenSquare, - T::CloseSquare - ]), - Ok(()) - ); - } - - #[test] - fn test_order_of_ops() { - // True || False && True || False - assert_eq!( - tokenparser::full_expr(&[ - T::UppercaseIdent, - T::OpOr, - T::UppercaseIdent, - T::OpAnd, - T::UppercaseIdent, - T::OpOr, - T::UppercaseIdent - ]), - Ok(()) - ); - } - - fn file_to_string(file_path: &str) -> String { - // it's ok to panic in a test - std::fs::read_to_string(file_path).unwrap() - } - - fn example_path(sub_path: &str) -> String { - let examples_dir = "../../examples/".to_string(); - - let file_path = examples_dir + sub_path; - - file_to_string(&file_path) - } - - fn cli_testing_path(sub_path: &str) -> String { - let examples_dir = "../cli_testing_examples/".to_string(); - - let file_path = examples_dir + sub_path; - - file_to_string(&file_path) - } - - #[test] - fn test_hello() { - let tokens = tokenize(&example_path("helloWorld.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_fibo() { - let tokens = tokenize(&cli_testing_path("algorithms/fibonacci.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_annotation() { - let tokens = tokenize(r#"ConsList a : [ Cons a (ConsList a), Nil ]"#); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_apply_type() { - let tokens = tokenize(r#"Cons a (ConsList a)"#); - - assert_eq!(tokenparser::apply_type(&tokens), Ok(())); - } - - #[test] - fn test_apply_expect_fail_1() { - assert!(tokenparser::apply(&[ - T::LowercaseIdent, - T::LowercaseIdent, - T::CloseIndent, - T::UppercaseIdent - ]) - .is_err()); - } - - #[test] - fn test_apply_expect_fail_2() { - let tokens = tokenize( - r#"eval a - b"#, - ); - - assert!(tokenparser::apply(&tokens).is_err()); - } - - #[test] - fn test_when_1() { - let tokens = tokenize( - r#"when list is - Cons _ rest -> - 1 + len rest - - Nil -> - 0"#, - ); - - assert_eq!(tokenparser::when(&tokens), Ok(())); - } - - #[test] - fn test_when_2() { - let tokens = tokenize( - r#"when list is - Nil -> - Cons a - - Nil -> - Nil"#, - ); - - assert_eq!(tokenparser::when(&tokens), Ok(())); - } - - #[test] - fn test_when_3() { - let tokens = tokenize( - r#"when list is - Nil -> Cons a - Nil -> Nil"#, - ); - assert_eq!(tokenparser::when(&tokens), Ok(())); - } - - #[test] - fn test_when_in_defs() { - let tokens = tokenize( - r#"fromBytes = \bytes -> - when bytes is - Ok v -> v - "#, - ); - dbg!(&tokens); - assert_eq!(tokenparser::module_defs(&tokens), Ok(())); - } - - #[test] - fn test_base64() { - let tokens = tokenize(&cli_testing_path("benchmarks/Base64.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_base64_test() { - let tokens = tokenize(&cli_testing_path("benchmarks/TestBase64.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_when_branch() { - let tokens = tokenize(r#"Ok path -> path"#); - - assert_eq!(tokenparser::when_branch(&tokens), Ok(())); - } - - #[test] - fn test_def_in_def() { - let tokens = tokenize( - r#"example = - cost = 1 - - cost"#, - ); - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_backpass_in_def() { - let tokens = tokenize( - r#"main = - lastName <- 4 - Stdout.line "Hi!""#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_astar_test() { - let tokens = tokenize(&cli_testing_path("benchmarks/TestAStar.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_cli_echo() { - let tokens = tokenize(&example_path("cli/echo.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_pizza_1() { - let tokens = tokenize( - r#"closure = \_ -> - Task.succeed {} - |> Task.map (\_ -> x)"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_pizza_one_line() { - let tokens = tokenize(r#"5 |> fun"#); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_same_indent_1() { - let tokens = tokenize( - r#"5 - |> fun"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_same_indent_2() { - let tokens = tokenize( - r#"5 - |> - fun"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_indented_1_a() { - let tokens = tokenize( - r#"5 - |> fun"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_indented_1_b() { - let tokens = tokenize( - r#"5 - |> fun - "#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_indented_2_a() { - let tokens = tokenize( - r#"5 - |> - fun"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_indented_2_b() { - let tokens = tokenize( - r#"5 - |> - fun - "#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_indented_2_c() { - let tokens = tokenize( - r#"5 - |> - fun - - "#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_mixed_indent_1_a() { - let tokens = tokenize( - r#"5 - |> - fun"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_mixed_indent_1_b() { - let tokens = tokenize( - r#"5 - |> - fun - "#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_mixed_indent_2_a() { - let tokens = tokenize( - r#"5 - |> - fun"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_pizza_mixed_indent_2_b() { - let tokens = tokenize( - r#"5 - |> - fun"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_longer_pizza() { - let tokens = tokenize(r#"5 |> fun a |> fun b"#); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_deeper_pizza() { - let tokens = tokenize( - r#"5 - |> fun a - |> fun b"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_deeper_indented_pizza_a() { - let tokens = tokenize( - r#"5 - |> fun a - |> fun b"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_deeper_indented_pizza_b() { - let tokens = tokenize( - r#"5 - |> fun a - |> fun b - "#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_deep_mixed_indent_pizza_a() { - let tokens = tokenize( - r#"5 - |> fun a |> fun b - |> fun c d - |> fun "test" - |> List.map Str.toI64 - |> g (1 + 1)"#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_deep_mixed_indent_pizza_b() { - let tokens = tokenize( - r#"5 - |> fun a |> fun b - |> fun c d - |> fun "test" - |> List.map Str.toI64 - |> g (1 + 1) - "#, - ); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_bool_or() { - let tokens = tokenize(r#"a || True || b || False"#); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - fn test_closure_file() { - let tokens = tokenize(&cli_testing_path("benchmarks/Closure.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_def_with_indents() { - let tokens = tokenize( - r#"main = - Task.after - Task.getInt - \n -> - queens n"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_nqueens() { - let tokens = tokenize(&cli_testing_path("benchmarks/NQueens.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_quicksort_help() { - let tokens = tokenize( - r#"quicksortHelp = \list, low, high -> - if low < high then - when partition low is - Pair -> - partitioned - |> quicksortHelp low - else - list"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_quicksort() { - let tokens = tokenize(&cli_testing_path("benchmarks/Quicksort.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_indented_closure_apply() { - let tokens = tokenize( - r#" - effect - \result -> result"#, - ); - - assert_eq!(tokenparser::apply_args(&tokens), Ok(())); - } - - #[test] - fn test_parens_closure_indent() { - let tokens = tokenize( - r#"(\i -> - i)"#, - ); - assert_eq!(tokenparser::parens_around(&tokens), Ok(())); - } - - #[test] - fn test_task() { - let tokens = tokenize(&cli_testing_path("benchmarks/platform/Task.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_pizza_line() { - let tokens = tokenize( - r#"unoptimized - |> Num.toStr - |> Task.putLine"#, - ); - - assert_eq!(tokenparser::full_expr(&tokens), Ok(())); - } - - #[test] - fn test_defs_w_apply() { - let tokens = tokenize( - r#"unoptimized = eval e - - 42"#, - ); - - assert_eq!(tokenparser::defs(&tokens), Ok(())); - } - - #[test] - fn test_indented_apply_defs() { - let tokens = tokenize( - r#"main = - after - \n -> - e = 5 - - 4 - - Expr : I64"#, - ); - - assert_eq!(tokenparser::module_defs(&tokens), Ok(())); - } - - #[test] - fn test_cfold() { - let tokens = tokenize(&cli_testing_path("benchmarks/CFold.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_apply_with_comment() { - let tokens = tokenize( - r#"main = - Task.after - \n -> - e = mkExpr n 1 # comment - unoptimized = eval e - optimized = eval (constFolding (reassoc e)) - - optimized"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_multi_defs() { - let tokens = tokenize( - r#"main = - tree : I64 - tree = 0 - - tree"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - // TODO fix slow execution; likely a problem with apply - #[test] - fn test_perf_issue() { - let tokens = tokenize( - r#"main = - tree = insert 0 {} Empty - - tree - |> Task.putLine - -nodeInParens : RedBlackTree k v, (k -> Str), (v -> Str) -> Str -nodeInParens = \tree, showKey, showValue -> - when tree is - _ -> - "(\(inner))" - -RedBlackTree k v : [ Node NodeColor k v (RedBlackTree k v) (RedBlackTree k v), Empty ] - -Key k : Num k - -balance = \color -> - when right is - Node Red -> - when left is - _ -> - Node color rK rV (Node Red key value left) - - _ -> - 5"#, - ); - - assert_eq!(tokenparser::module_defs(&tokens), Ok(())); - } - - #[test] - fn test_rbtree_insert() { - let tokens = tokenize(&cli_testing_path("benchmarks/RBTreeInsert.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_closure_1() { - let tokens = tokenize( - r#"\key -> - when dict is - Empty -> - 4 - - Node -> - 5"#, - ); - - assert_eq!(tokenparser::closure(&tokens), Ok(())); - } - - #[test] - fn test_closure_2() { - let tokens = tokenize( - r#"\key -> - when dict is - Empty -> - Node Red - - Node nColor -> - when key is - GT -> - balance nColor"#, - ); - - assert_eq!(tokenparser::closure(&tokens), Ok(())); - } - - #[test] - fn test_nested_apply() { - let tokens = tokenize( - r#"after = \effect -> - Effect.after - transform a - - map : Str"#, - ); - - assert_eq!(tokenparser::module_defs(&tokens), Ok(())); - } - - #[test] - fn test_deep_indented_defs() { - let tokens = tokenize( - r#"after = \effect -> - after - \result -> - transform a - - map : Str"#, - ); - - assert_eq!(tokenparser::module_defs(&tokens), Ok(())); - } - - #[test] - fn test_rbtree_ck() { - let tokens = tokenize(&cli_testing_path("benchmarks/RBTreeCk.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_record_type_def() { - let tokens = tokenize( - r#"Model position : - { - evaluated : Set, - }"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_apply_with_acces() { - let tokens = tokenize(r#"Dict.get model.costs"#); - - assert_eq!(tokenparser::apply(&tokens), Ok(())); - } - - #[test] - fn test_space_dot() { - let tokens = tokenize(r#"Result.map .position"#); - - assert_eq!(tokenparser::op_expr(&tokens), Ok(())); - } - - #[test] - #[ignore = "Does not yet know about ability syntax"] - fn test_astar() { - let tokens = tokenize(&cli_testing_path("benchmarks/AStar.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_backpass_in_def_2() { - let tokens = tokenize( - r#"with = \path -> - handle <- withOpen - - 4"#, - ); - - assert_eq!(tokenparser::def(&tokens), Ok(())); - } - - #[test] - fn test_false_interpreter_context() { - let tokens = tokenize(&example_path("cli/false-interpreter/Context.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } - - #[test] - fn test_when_match_apply() { - let tokens = tokenize(r#"Pair (Val 0) f"#); - - assert_eq!(tokenparser::when_match_pattern(&tokens), Ok(())); - } - - #[test] - fn test_when_match_apply_2() { - let tokens = tokenize(r#"Pair (Val 0) f"#); - - assert_eq!(tokenparser::when_match_pattern(&tokens), Ok(())); - } - - #[test] - fn test_apply_with_closure() { - let tokens = tokenize(r#"Task.after \w -> nestHelp s"#); - - assert_eq!(tokenparser::apply(&tokens), Ok(())); - } - - #[test] - fn test_deriv() { - let tokens = tokenize(&cli_testing_path("benchmarks/Deriv.roc")); - - assert_eq!(tokenparser::module(&tokens), Ok(())); - } -} diff --git a/crates/linker/Cargo.toml b/crates/linker/Cargo.toml index 8eeb8d2b77..ea6d76ea14 100644 --- a/crates/linker/Cargo.toml +++ b/crates/linker/Cargo.toml @@ -1,32 +1,34 @@ [package] name = "roc_linker" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -repository = "https://github.com/roc-lang/roc" -edition = "2021" description = "A surgical linker for Roc" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + [lib] name = "roc_linker" path = "src/lib.rs" [dependencies] -roc_mono = { path = "../compiler/mono" } -roc_build = { path = "../compiler/build" } roc_collections = { path = "../compiler/collections" } roc_error_macros = { path = "../error_macros" } +roc_module = { path = "../compiler/module" } roc_load = { path = "../compiler/load" } +roc_mono = { path = "../compiler/mono" } roc_packaging = { path = "../packaging" } roc_reporting = { path = "../reporting" } +roc_target = { path = "../compiler/roc_target" } +bincode.workspace = true bumpalo.workspace = true iced-x86.workspace = true +mach_object.workspace = true memmap2.workspace = true object.workspace = true -mach_object.workspace = true serde.workspace = true -bincode.workspace = true target-lexicon.workspace = true tempfile.workspace = true @@ -34,3 +36,4 @@ tempfile.workspace = true [dev-dependencies] indoc.workspace = true libc.workspace = true +serial_test.workspace = true diff --git a/crates/linker/src/elf.rs b/crates/linker/src/elf.rs index bb9bfe1475..a512be51e2 100644 --- a/crates/linker/src/elf.rs +++ b/crates/linker/src/elf.rs @@ -1532,7 +1532,16 @@ fn surgery_elf_help( let func_virt_offset = match app_func_vaddr_map.get(func_name) { Some(offset) => *offset as u64, None => { - internal_error!("Function, {}, was not defined by the app", &func_name); + eprintln!("Error:"); + eprintln!("\n\tFunction, {}, was not defined by the app.", &func_name); + eprintln!("\nPotential causes:"); + eprintln!("\n\t- because the platform was built with a non-compatible version of roc compared to the one you are running."); + eprintln!("\n\t\tsolutions:"); + eprintln!("\t\t\t+ Downgrade your roc version to the one that was used to build the platform."); + eprintln!("\t\t\t+ Or ask the platform author to release a new version of the platform using a current roc release."); + eprintln!("\n\t- This can also occur due to a bug in the compiler. In that case, file an issue here: https://github.com/roc-lang/roc/issues/new/choose"); + + std::process::exit(1); } }; if verbose { @@ -1637,8 +1646,8 @@ fn surgery_elf_help( mod tests { use super::*; + use crate::preprocessed_host_filename; use indoc::indoc; - use roc_build::link::preprocessed_host_filename; use target_lexicon::Triple; const ELF64_DYNHOST: &[u8] = include_bytes!("../dynhost_benchmarks_elf64") as &[_]; diff --git a/crates/linker/src/lib.rs b/crates/linker/src/lib.rs index a739993a22..acc5b617ce 100644 --- a/crates/linker/src/lib.rs +++ b/crates/linker/src/lib.rs @@ -5,12 +5,12 @@ //! practical to use a regular linker. use memmap2::{Mmap, MmapMut}; use object::Object; -use roc_build::link::{get_target_triple_str, rebuild_host, LinkType}; use roc_error_macros::internal_error; -use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading}; -use roc_mono::ir::OptLevel; +use roc_load::{EntryPoint, ExecutionMode, ExposedToHost, LoadConfig, Threading}; +use roc_module::symbol::Interns; use roc_packaging::cache::RocCacheDir; use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE}; +use roc_target::get_target_triple_str; use std::cmp::Ordering; use std::mem; use std::path::{Path, PathBuf}; @@ -23,6 +23,14 @@ mod pe; mod generate_dylib; mod metadata; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum LinkType { + // These numbers correspond to the --lib and --no-link flags + Executable = 0, + Dylib = 1, + None = 2, +} + pub fn supported(link_type: LinkType, target: &Triple) -> bool { if let LinkType::Executable = link_type { match target { @@ -54,49 +62,16 @@ pub fn supported(link_type: LinkType, target: &Triple) -> bool { } } -pub fn build_and_preprocess_host( - opt_level: OptLevel, - target: &Triple, - platform_main_roc: &Path, - preprocessed_host_path: &Path, - exposed_to_host: Vec, - exported_closure_types: Vec, -) { - let stub_lib = if let target_lexicon::OperatingSystem::Windows = target.operating_system { - platform_main_roc.with_file_name("libapp.dll") - } else { - platform_main_roc.with_file_name("libapp.so") - }; +pub const PRECOMPILED_HOST_EXT: &str = "rh"; // Short for "roc host" - let dynhost = if let target_lexicon::OperatingSystem::Windows = target.operating_system { - platform_main_roc.with_file_name("dynhost.exe") - } else { - platform_main_roc.with_file_name("dynhost") - }; - - let stub_dll_symbols = make_stub_dll_symbols(exposed_to_host, exported_closure_types); - generate_dynamic_lib(target, &stub_dll_symbols, &stub_lib); - rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib)); - - let metadata = platform_main_roc.with_file_name(metadata_file_name(target)); - // let prehost = host_input_path.with_file_name(preprocessed_host_filename(target).unwrap()); - - preprocess( - target, - &dynhost, - &metadata, - preprocessed_host_path, - &stub_lib, - &stub_dll_symbols, - false, - false, - ) +pub fn preprocessed_host_filename(target: &Triple) -> Option { + roc_target::get_target_triple_str(target).map(|x| format!("{}.{}", x, PRECOMPILED_HOST_EXT)) } fn metadata_file_name(target: &Triple) -> String { let target_triple_str = get_target_triple_str(target); - format!("metadata_{}.rm2", target_triple_str.unwrap_or("unknown")) + format!("metadata_{}.rm", target_triple_str.unwrap_or("unknown")) } pub fn link_preprocessed_host( @@ -137,7 +112,7 @@ pub fn generate_stub_lib( let exposed_to_host = loaded .exposed_to_host - .values + .top_level_values .keys() .map(|x| x.as_str(&loaded.interns).to_string()) .collect(); @@ -155,6 +130,11 @@ pub fn generate_stub_lib( }) .collect(); + let exposed_symbols = ExposedSymbols { + top_level_values: exposed_to_host, + exported_closure_types, + }; + if let EntryPoint::Executable { platform_path, .. } = &loaded.entry_point { let stub_lib = if let target_lexicon::OperatingSystem::Windows = triple.operating_system { platform_path.with_file_name("libapp.obj") @@ -162,7 +142,7 @@ pub fn generate_stub_lib( platform_path.with_file_name("libapp.so") }; - let stub_dll_symbols = make_stub_dll_symbols(exposed_to_host, exported_closure_types); + let stub_dll_symbols = exposed_symbols.stub_dll_symbols(); generate_dynamic_lib(triple, &stub_dll_symbols, &stub_lib); } else { unreachable!(); @@ -170,33 +150,104 @@ pub fn generate_stub_lib( Ok(0) } -fn make_stub_dll_symbols( - exposed_to_host: Vec, - exported_closure_types: Vec, -) -> Vec { - let mut custom_names = Vec::new(); +pub fn generate_stub_lib_from_loaded( + target: &Triple, + platform_main_roc: &Path, + stub_dll_symbols: &[String], +) -> PathBuf { + let stub_lib_path = if let target_lexicon::OperatingSystem::Windows = target.operating_system { + platform_main_roc.with_file_name("libapp.dll") + } else { + platform_main_roc.with_file_name("libapp.so") + }; - for sym in exposed_to_host { - custom_names.extend([ - format!("roc__{}_1_exposed", sym), - format!("roc__{}_1_exposed_generic", sym), - format!("roc__{}_size", sym), - ]); + generate_dynamic_lib(target, stub_dll_symbols, &stub_lib_path); + + stub_lib_path +} + +pub struct ExposedSymbols { + // usually just `mainForhost` + pub top_level_values: Vec, + + // old type exposing mechanism + pub exported_closure_types: Vec, +} + +impl ExposedSymbols { + pub fn from_exposed_to_host(interns: &Interns, exposed_to_host: &ExposedToHost) -> Vec { + let mut custom_names = Vec::new(); + + for x in exposed_to_host.top_level_values.keys() { + let sym = x.as_str(interns); - for closure_type in &exported_closure_types { custom_names.extend([ - format!("roc__{}_1_{}_caller", sym, closure_type), - format!("roc__{}_1_{}_size", sym, closure_type), - format!("roc__{}_1_{}_result_size", sym, closure_type), + format!("roc__{}_1_exposed", sym), + format!("roc__{}_1_exposed_generic", sym), + format!("roc__{}_1_exposed_size", sym), + ]); + + let exported_closure_types = exposed_to_host + .closure_types + .iter() + .map(|x| format!("{}_{}", x.module_string(interns), x.as_str(interns))); + + for (i, _) in exported_closure_types.enumerate() { + custom_names.extend([ + format!("roc__{}_{i}_caller", sym), + format!("roc__{}_{i}_size", sym), + format!("roc__{}_{i}_result_size", sym), + ]); + } + } + + for x in &exposed_to_host.getters { + let sym = x.as_str(interns); + custom_names.extend([ + sym.to_string(), + format!("{sym}_generic"), + format!("{sym}_size"), ]); } + + for (top_level_value, lambda_set_id) in &exposed_to_host.lambda_sets { + let sym = top_level_value.as_str(interns); + let id = lambda_set_id.0; + custom_names.extend([format!("roc__{sym}_{id}_caller")]); + } + + // on windows (PE) binary search is used on the symbols, + // so they must be in alphabetical order + custom_names.sort_unstable(); + + custom_names } - // on windows (PE) binary search is used on the symbols, - // so they must be in alphabetical order - custom_names.sort_unstable(); + pub fn stub_dll_symbols(&self) -> Vec { + let mut custom_names = Vec::new(); - custom_names + for sym in &self.top_level_values { + custom_names.extend([ + format!("roc__{}_1_exposed", sym), + format!("roc__{}_1_exposed_generic", sym), + format!("roc__{}_size", sym), + ]); + + for closure_type in &self.exported_closure_types { + custom_names.extend([ + format!("roc__{}_1_{}_caller", sym, closure_type), + format!("roc__{}_1_{}_size", sym, closure_type), + format!("roc__{}_1_{}_result_size", sym, closure_type), + ]); + } + } + + // on windows (PE) binary search is used on the symbols, + // so they must be in alphabetical order + custom_names.sort_unstable(); + + custom_names + } } fn generate_dynamic_lib(target: &Triple, stub_dll_symbols: &[String], stub_lib_path: &Path) { @@ -332,6 +383,32 @@ fn stub_lib_is_up_to_date(target: &Triple, stub_lib_path: &Path, custom_names: & it1.eq(it2) } +pub fn preprocess_host( + target: &Triple, + platform_main_roc: &Path, + preprocessed_path: &Path, + shared_lib: &Path, + stub_dll_symbols: &[String], +) { + let metadata_path = platform_main_roc.with_file_name(metadata_file_name(target)); + let host_exe_path = if let target_lexicon::OperatingSystem::Windows = target.operating_system { + platform_main_roc.with_file_name("dynhost.exe") + } else { + platform_main_roc.with_file_name("dynhost") + }; + + preprocess( + target, + &host_exe_path, + &metadata_path, + preprocessed_path, + shared_lib, + stub_dll_symbols, + false, + false, + ) +} + /// Constructs a `metadata::Metadata` from a host executable binary, and writes it to disk #[allow(clippy::too_many_arguments)] fn preprocess( diff --git a/crates/linker/src/metadata.rs b/crates/linker/src/metadata.rs index a2125543fb..0f606a40ac 100644 --- a/crates/linker/src/metadata.rs +++ b/crates/linker/src/metadata.rs @@ -55,21 +55,15 @@ impl Metadata { } pub fn read_from_file(metadata_filename: &Path) -> Self { - let input = - std::fs::File::open(metadata_filename) - .unwrap_or_else( - |e| internal_error!(r#" + let input = std::fs::File::open(metadata_filename).unwrap_or_else(|e| { + internal_error!( + r#" Error: - {} - - > This may occur when using a release of roc that relies on a specific metadata format like 'rm2' and the imported platform only has an older metadata format available, like rm1. - The platform you are using can be found in the header of your main.roc: `packages {{ pf: }}`. - You should check if a more recent version of the platform is available. - If not, you should notify the author of the platform about this issue. - -"#, e) - ); + {}\n"#, + e + ) + }); match deserialize_from(BufReader::new(input)) { Ok(data) => data, diff --git a/crates/linker/src/pe.rs b/crates/linker/src/pe.rs index 3c42d3b26e..784c199edc 100644 --- a/crates/linker/src/pe.rs +++ b/crates/linker/src/pe.rs @@ -1357,8 +1357,9 @@ mod test { use object::read::pe::PeFile64; use object::{pe, LittleEndian as LE, Object}; + use crate::preprocessed_host_filename; use indoc::indoc; - use roc_build::link::preprocessed_host_filename; + use serial_test::serial; use target_lexicon::Triple; use super::*; @@ -1834,12 +1835,14 @@ mod test { } #[cfg(windows)] + #[serial(zig_build)] #[test] fn basics_windows() { assert_eq!("Hello, 234567 32 1 3!\n", windows_test(test_basics)) } #[test] + #[serial(zig_build)] #[ignore] fn basics_wine() { assert_eq!("Hello, 234567 32 1 3!\n", wine_test(test_basics)) @@ -1876,13 +1879,15 @@ mod test { } #[cfg(windows)] + #[serial(zig_build)] #[test] fn app_internal_relocations_windows() { assert_eq!("Hello foo\n", windows_test(test_internal_relocations)) } - #[ignore] #[test] + #[serial(zig_build)] + #[ignore] fn app_internal_relocations_wine() { assert_eq!("Hello foo\n", wine_test(test_internal_relocations)) } diff --git a/crates/packaging/Cargo.toml b/crates/packaging/Cargo.toml index c37ff250a8..f0a894dd9a 100644 --- a/crates/packaging/Cargo.toml +++ b/crates/packaging/Cargo.toml @@ -1,33 +1,29 @@ [package] name = "roc_packaging" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -repository = "https://github.com/roc-lang/roc" -edition = "2021" description = "Functionality for packaging Roc source code - e.g. for distribution over the network" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + [dependencies] -roc_parse = { path = "../compiler/parse" } roc_error_macros = { path = "../error_macros" } +roc_parse = { path = "../compiler/parse" } -tar = "0.4.38" # used for `roc build --tar` -brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli -flate2 = "1.0.24" -walkdir = "2.3.2" -blake3 = "1.3.1" -base64-url = "1.4.13" - +base64-url.workspace = true +blake3.workspace = true +brotli.workspace = true # used for decompressing tarballs over HTTPS, if the server supports brotli bumpalo.workspace = true +flate2.workspace = true fs_extra.workspace = true +tar.workspace = true # used for `roc build --tar` tempfile.workspace = true +walkdir.workspace = true [target.'cfg(not(target_family = "wasm"))'.dependencies] -# default-features=false removes libopenssl as a dependency on Linux, which might not be available! -reqwest = { version = "0.11.13", default-features = false, features = [ "blocking", "rustls-tls" ] } +reqwest.workspace = true [dev-dependencies] -pretty_assertions = "1.3.0" -indoc = "1.0.7" - -tempfile.workspace = true \ No newline at end of file +tempfile.workspace = true diff --git a/crates/packaging/src/cache.rs b/crates/packaging/src/cache.rs index ecf2ecf5b7..bf81493f32 100644 --- a/crates/packaging/src/cache.rs +++ b/crates/packaging/src/cache.rs @@ -20,6 +20,41 @@ pub enum RocCacheDir<'a> { Temp(&'a tempfile::TempDir), } +// Errors in case NixOS users try to use a dynamically linked platform +#[cfg(target_os = "linux")] +fn nixos_error_if_dynamic(url: &str, dest_dir: &Path) { + let output = std::process::Command::new("uname") + .arg("-a") + .output() + .expect("uname command failed to start"); + let running_nixos = String::from_utf8_lossy(&output.stdout).contains("NixOS"); + + if running_nixos { + // bash -c is used instead of plain ldd because process::Command escapes its arguments + let ldd_output = std::process::Command::new("bash") + .arg("-c") + .arg(format!("ldd {}/linux-x86_64.rh*", dest_dir.display())) + .output() + .expect("ldd command failed to start"); + let is_dynamic = String::from_utf8_lossy(&ldd_output.stdout).contains("=>"); + + if is_dynamic { + eprintln!("The platform downloaded from the URL {url} is dynamically linked.\n\ + Dynamically linked platforms can't be used on NixOS.\n\n\ + You can:\n\n\t\ + - Download the source of the platform and build it locally, like in this example:\n\t \ + https://github.com/roc-lang/roc/blob/main/examples/platform-switching/rocLovesRust.roc.\n\t \ + When building your roc application, you can use the flag `--prebuilt-platform=true` to prevent the platform from being rebuilt every time.\n\t \ + For some graphical platforms you may need to use https://github.com/guibou/nixGL.\n\n\t\ + - Contact the author of the platform to ask them to statically link their platform.\n\t \ + musl can be used to prevent a dynamic dependency on the systems' libc.\n\t \ + If the platform is dynamically linked to GPU drivers, it can not be statically linked practically. Use the previous suggestion to build locally in this case.\n" + ); + std::process::exit(1); + } + } +} + /// Accepts either a path to the Roc cache dir, or else a TempDir. If a TempDir, always download /// into that dir. If the cache dir on the filesystem, then look into it to see if we already /// have an entry for the given URL. If we do, return its info. If we don't already have it, then: @@ -51,6 +86,12 @@ pub fn install_package<'a>( if dest_dir.exists() { // If the cache dir exists already, we assume it has the correct contents // (it's a cache, after all!) and return without downloading anything. + // + #[cfg(target_os = "linux")] + { + nixos_error_if_dynamic(url, &dest_dir); + } + Ok((dest_dir, root_module_filename)) } else { // Download into a tempdir; only move it to dest_dir if hash verification passes. @@ -93,6 +134,11 @@ pub fn install_package<'a>( .map_err(Problem::FsExtraErr)?; } + #[cfg(target_os = "linux")] + { + nixos_error_if_dynamic(url, &dest_dir); + } + // The package's files are now in the cache. We're done! Ok((dest_dir, root_module_filename)) } else { diff --git a/crates/packaging/src/tarball.rs b/crates/packaging/src/tarball.rs index 7c894584da..460378f8fa 100644 --- a/crates/packaging/src/tarball.rs +++ b/crates/packaging/src/tarball.rs @@ -151,9 +151,9 @@ fn write_archive(path: &Path, writer: W) -> io::Result<()> { if [ // surgical linker format - Some("rh1"), + Some("rh"), // metadata file - Some("rm2"), + Some("rm"), // legacy linker formats Some("o"), Some("obj"), diff --git a/crates/repl_cli/Cargo.toml b/crates/repl_cli/Cargo.toml index 518953831a..34db9dc223 100644 --- a/crates/repl_cli/Cargo.toml +++ b/crates/repl_cli/Cargo.toml @@ -1,11 +1,11 @@ [package] -edition = "2021" name = "roc_repl_cli" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "Command Line Interface(CLI) functionality for the Read-Evaluate-Print-Loop (REPL)." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [features] # pipe target to roc_build @@ -16,30 +16,30 @@ target-x86 = ["roc_build/target-x86"] target-x86_64 = ["roc_build/target-x86_64"] [dependencies] -inkwell.workspace = true +roc_build = { path = "../compiler/build" } +roc_builtins = { path = "../compiler/builtins" } +roc_collections = { path = "../compiler/collections" } +roc_gen_llvm = { path = "../compiler/gen_llvm" } +roc_load = { path = "../compiler/load" } +roc_module = { path = "../compiler/module" } +roc_mono = { path = "../compiler/mono" } +roc_parse = { path = "../compiler/parse" } +roc_region = { path = "../compiler/region" } +roc_repl_eval = { path = "../repl_eval" } +roc_reporting = { path = "../reporting" } +roc_std = { path = "../roc_std" } +roc_target = { path = "../compiler/roc_target" } +roc_types = { path = "../compiler/types" } + bumpalo.workspace = true const_format.workspace = true +inkwell.workspace = true libloading.workspace = true -rustyline.workspace = true rustyline-derive.workspace = true +rustyline.workspace = true target-lexicon.workspace = true unicode-segmentation.workspace = true -roc_build = {path = "../compiler/build"} -roc_builtins = {path = "../compiler/builtins"} -roc_collections = {path = "../compiler/collections"} -roc_gen_llvm = {path = "../compiler/gen_llvm"} -roc_load = {path = "../compiler/load"} -roc_mono = {path = "../compiler/mono"} -roc_parse = {path = "../compiler/parse"} -roc_repl_eval = {path = "../repl_eval"} -roc_reporting = {path = "../reporting"} -roc_std = {path = "../roc_std"} -roc_target = {path = "../compiler/roc_target"} -roc_types = {path = "../compiler/types"} -roc_region = { path = "../compiler/region" } -roc_module = { path = "../compiler/module" } - [lib] name = "roc_repl_cli" path = "src/lib.rs" diff --git a/crates/repl_cli/src/cli_gen.rs b/crates/repl_cli/src/cli_gen.rs index 2ceb98ce0b..8009f12cf7 100644 --- a/crates/repl_cli/src/cli_gen.rs +++ b/crates/repl_cli/src/cli_gen.rs @@ -42,8 +42,13 @@ pub fn gen_and_eval_llvm<'a, I: Iterator>( } }; - debug_assert_eq!(loaded.exposed_to_host.values.len(), 1); - let (main_fn_symbol, main_fn_var) = loaded.exposed_to_host.values.iter().next().unwrap(); + debug_assert_eq!(loaded.exposed_to_host.top_level_values.len(), 1); + let (main_fn_symbol, main_fn_var) = loaded + .exposed_to_host + .top_level_values + .iter() + .next() + .unwrap(); let main_fn_symbol = *main_fn_symbol; let main_fn_var = *main_fn_var; diff --git a/crates/repl_eval/Cargo.toml b/crates/repl_eval/Cargo.toml index d1ffdf93a1..eb14b97b7b 100644 --- a/crates/repl_eval/Cargo.toml +++ b/crates/repl_eval/Cargo.toml @@ -1,28 +1,27 @@ [package] -edition = "2021" name = "roc_repl_eval" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "Provides the functionality for the REPL to evaluate Roc expressions." -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dependencies] -bumpalo.workspace = true +roc_builtins = { path = "../compiler/builtins" } +roc_can = { path = "../compiler/can" } +roc_collections = { path = "../compiler/collections" } +roc_fmt = { path = "../compiler/fmt" } +roc_load = { path = "../compiler/load" } +roc_module = { path = "../compiler/module" } +roc_mono = { path = "../compiler/mono" } +roc_packaging = { path = "../packaging" } +roc_parse = { path = "../compiler/parse" } +roc_problem = { path = "../compiler/problem" } +roc_region = { path = "../compiler/region" } +roc_reporting = { path = "../reporting" } +roc_std = { path = "../roc_std" } +roc_target = { path = "../compiler/roc_target" } +roc_types = { path = "../compiler/types" } -roc_builtins = {path = "../compiler/builtins"} -roc_can = {path = "../compiler/can"} -roc_collections = {path = "../compiler/collections"} -roc_fmt = {path = "../compiler/fmt"} -roc_load = {path = "../compiler/load"} -roc_module = {path = "../compiler/module"} -roc_mono = {path = "../compiler/mono"} -roc_parse = {path = "../compiler/parse"} -roc_problem = {path = "../compiler/problem"} -roc_region = {path = "../compiler/region"} -roc_packaging = {path = "../packaging"} -roc_reporting = {path = "../reporting"} -roc_std = {path = "../roc_std"} -roc_target = {path = "../compiler/roc_target"} -roc_types = {path = "../compiler/types"} +bumpalo.workspace = true diff --git a/crates/repl_eval/src/eval.rs b/crates/repl_eval/src/eval.rs index 3e8412e0b9..caf909b6dd 100644 --- a/crates/repl_eval/src/eval.rs +++ b/crates/repl_eval/src/eval.rs @@ -18,7 +18,7 @@ use roc_region::all::{Loc, Region}; use roc_std::RocDec; use roc_target::TargetInfo; use roc_types::subs::{ - Content, FlatType, GetSubsSlice, RecordFields, Subs, TagExt, UnionTags, Variable, + Content, FlatType, GetSubsSlice, RecordFields, Subs, TagExt, TupleElems, UnionTags, Variable, }; use crate::{ReplApp, ReplAppMemory}; @@ -432,6 +432,9 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Content::Structure(FlatType::EmptyRecord) => { struct_to_ast(env, mem, addr, RecordFields::empty()) } + Content::Structure(FlatType::Tuple(elems, _)) => { + struct_to_ast_tuple(env, mem, addr, *elems) + } Content::Structure(FlatType::TagUnion(tags, _)) => { let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); @@ -596,6 +599,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>( F64 => helper!(deref_f64, f64), } } + (_, Layout::Builtin(Builtin::Decimal)) => { + helper!(deref_dec, RocDec) + } (_, Layout::Builtin(Builtin::List(elem_layout))) => { let elem_addr = mem.deref_usize(addr); let len = mem.deref_usize(addr + env.target_info.ptr_width() as usize); @@ -880,12 +886,6 @@ fn addr_to_ast<'a, M: ReplAppMemory>( (_, Layout::Boxed(_)) => { unreachable!("Box layouts can only be behind a `Box.Box` application") } - other => { - todo!( - "TODO add support for rendering pointer to {:?} in the REPL", - other - ); - } }; apply_newtypes(env, newtype_containers.into_bump_slice(), expr) } @@ -1127,6 +1127,72 @@ fn struct_to_ast<'a, 'env, M: ReplAppMemory>( } } +fn struct_to_ast_tuple<'a, 'env, M: ReplAppMemory>( + env: &mut Env<'a, 'env>, + mem: &'a M, + addr: usize, + tuple_elems: TupleElems, +) -> Expr<'a> { + let arena = env.arena; + let subs = env.subs; + let mut output = Vec::with_capacity_in(tuple_elems.len(), arena); + + debug_assert!(tuple_elems.len() > 1); + + // We'll advance this as we iterate through the fields + let mut field_addr = addr; + + // the type checker stores tuple elements in alphabetical order + let alphabetical_fields: Vec<_> = tuple_elems + .sorted_iterator(subs, Variable::EMPTY_TUPLE) + .map(|(l, elem)| { + let layout = env.layout_cache.from_var(arena, elem, env.subs).unwrap(); + + (l, elem, layout) + }) + .collect_in(arena); + + // but the memory representation sorts first by size (and uses field name as a tie breaker) + let mut in_memory_fields = alphabetical_fields; + in_memory_fields.sort_by(|(label1, _, layout1), (label2, _, layout2)| { + cmp_fields( + &env.layout_cache.interner, + label1, + *layout1, + label2, + *layout2, + env.target_info, + ) + }); + + for (label, elem_var, elem_layout) in in_memory_fields { + let loc_expr = &*arena.alloc(Loc { + value: addr_to_ast( + env, + mem, + field_addr, + elem_layout, + WhenRecursive::Unreachable, + elem_var, + ), + region: Region::zero(), + }); + + output.push((label, loc_expr)); + + // Advance the field pointer to the next field. + field_addr += env.layout_cache.interner.stack_size(elem_layout) as usize; + } + + // to the user we want to present the fields in alphabetical order again, so re-sort + output.sort_by(|a, b| (a.0).cmp(&b.0)); + let output = env + .arena + .alloc_slice_fill_iter(output.into_iter().map(|(_, expr)| expr)); + + Expr::Tuple(Collection::with_items(output)) +} + fn unpack_single_element_tag_union(subs: &Subs, tags: UnionTags) -> (&TagName, &[Variable]) { let (tag_name_index, payload_vars_index) = tags.iter_all().next().unwrap(); diff --git a/crates/repl_expect/Cargo.toml b/crates/repl_expect/Cargo.toml index bae087b61c..305077f466 100644 --- a/crates/repl_expect/Cargo.toml +++ b/crates/repl_expect/Cargo.toml @@ -1,45 +1,45 @@ [package] name = "roc_repl_expect" -version = "0.0.1" -edition = "2021" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "Supports evaluating expect and printing contextual information when they fail." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] +roc_build = { path = "../compiler/build" } +roc_builtins = { path = "../compiler/builtins" } +roc_can = { path = "../compiler/can" } +roc_collections = { path = "../compiler/collections" } +roc_error_macros = { path = "../error_macros" } +roc_gen_llvm = { path = "../compiler/gen_llvm" } +roc_load = { path = "../compiler/load" } +roc_module = { path = "../compiler/module" } +roc_mono = { path = "../compiler/mono" } +roc_packaging = { path = "../packaging" } +roc_parse = { path = "../compiler/parse" } +roc_region = { path = "../compiler/region" } +roc_repl_eval = { path = "../repl_eval" } +roc_reporting = { path = "../reporting" } +roc_std = { path = "../roc_std" } +roc_target = { path = "../compiler/roc_target" } +roc_types = { path = "../compiler/types" } + bumpalo.workspace = true -target-lexicon.workspace = true +inkwell.workspace = true +libc.workspace = true libloading.workspace = true signal-hook.workspace = true -libc.workspace = true -inkwell.workspace = true - -roc_builtins = {path = "../compiler/builtins"} -roc_can = {path = "../compiler/can"} -roc_collections = {path = "../compiler/collections"} -roc_load = {path = "../compiler/load"} -roc_mono = {path = "../compiler/mono"} -roc_parse = {path = "../compiler/parse"} -roc_module = {path = "../compiler/module"} -roc_repl_eval = {path = "../repl_eval"} -roc_packaging = {path = "../packaging"} -roc_reporting = {path = "../reporting"} -roc_std = {path = "../roc_std"} -roc_target = {path = "../compiler/roc_target"} -roc_types = {path = "../compiler/types"} -roc_gen_llvm = {path = "../compiler/gen_llvm"} -roc_region = { path = "../compiler/region" } -roc_build = { path = "../compiler/build" } -roc_error_macros = { path = "../error_macros" } +target-lexicon.workspace = true [dev-dependencies] -test_gen = { path = "../compiler/test_gen" } -roc_build = { path = "../compiler/build", features = ["target-aarch64", "target-x86_64"] } +roc_build = { path = "../compiler/build", features = ["target-aarch64", "target-x86_64"] } -tempfile.workspace = true indoc.workspace = true pretty_assertions.workspace = true strip-ansi-escapes.workspace = true +tempfile.workspace = true [lib] diff --git a/crates/repl_expect/src/lib.rs b/crates/repl_expect/src/lib.rs index 0bd889b352..b0a0aeaa56 100644 --- a/crates/repl_expect/src/lib.rs +++ b/crates/repl_expect/src/lib.rs @@ -202,9 +202,9 @@ mod test { println!("{}", x); } - assert_eq!(x, expected); + assert_eq!(expected, x); } else { - assert_eq!(actual, expected); + assert_eq!(expected, actual); } } @@ -666,10 +666,16 @@ mod test { When it failed, these variables had these values: - a : [Err Str, Ok Str] + a : [ + Err Str, + Ok Str, + ] a = Ok "Astra mortemque praestare gradatim" - b : [Err Str, Ok Str] + b : [ + Err Str, + Ok Str, + ] b = Err "Profundum et fundamentum" "# ), @@ -1099,4 +1105,79 @@ mod test { ), ); } + + #[test] + fn tag_payloads_of_different_size() { + run_expect_test( + indoc!( + r#" + interface Test exposes [] imports [] + + actual : [Leftover (List U8), TooShort] + actual = Leftover [49, 93] + + expect + expected : [Leftover (List U8), TooShort] + expected = TooShort + + actual == expected + "# + ), + indoc!( + r#" + This expectation failed: + + 6│> expect + 7│> expected : [Leftover (List U8), TooShort] + 8│> expected = TooShort + 9│> + 10│> actual == expected + + When it failed, these variables had these values: + + expected : [ + Leftover (List U8), + TooShort, + ] + expected = TooShort + "# + ), + ); + } + + #[test] + fn extra_offset_in_tag_union() { + run_expect_test( + indoc!( + r#" + interface Test exposes [] imports [] + + actual : Result Str U64 + actual = Err 1 + + expect + expected : Result Str U64 + expected = Ok "foobar" + + actual == expected + "# + ), + indoc!( + r#" + This expectation failed: + + 6│> expect + 7│> expected : Result Str U64 + 8│> expected = Ok "foobar" + 9│> + 10│> actual == expected + + When it failed, these variables had these values: + + expected : Result Str U64 + expected = Ok "foobar" + "# + ), + ); + } } diff --git a/crates/repl_test/Cargo.toml b/crates/repl_test/Cargo.toml index 8b94ebbeb4..4858f695a9 100644 --- a/crates/repl_test/Cargo.toml +++ b/crates/repl_test/Cargo.toml @@ -1,37 +1,35 @@ [package] -edition = "2021" name = "repl_test" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "Tests the roc REPL." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [build-dependencies] -roc_cli = {path = "../cli"} +roc_cli = { path = "../cli" } [dev-dependencies] -indoc = "1.0.7" -strip-ansi-escapes = "0.1.1" -bumpalo.workspace = true - roc_build = { path = "../compiler/build" } -roc_repl_cli = {path = "../repl_cli"} -roc_test_utils = {path = "../test_utils"} -roc_wasm_interp = {path = "../wasm_interp"} +roc_repl_cli = { path = "../repl_cli" } +roc_test_utils = { path = "../test_utils" } +roc_wasm_interp = { path = "../wasm_interp" } + +bumpalo.workspace = true +indoc.workspace = true +strip-ansi-escapes.workspace = true [features] default = ["target-aarch64", "target-x86_64", "target-wasm32"] -wasm = ["target-wasm32"] -target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"] target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"] +target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"] +target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"] target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"] target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"] -target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"] +wasm = ["target-wasm32"] -target-all = [ - "target-aarch64", - "target-arm", - "target-x86", - "target-x86_64", - "target-wasm32" -] +target-all = ["target-aarch64", "target-arm", "target-x86", "target-x86_64", "target-wasm32"] + +[package.metadata.cargo-udeps.ignore] +development = ["roc_wasm_interp"] \ No newline at end of file diff --git a/crates/repl_test/src/tests.rs b/crates/repl_test/src/tests.rs index 1fb4c4c4f4..6a3250b97b 100644 --- a/crates/repl_test/src/tests.rs +++ b/crates/repl_test/src/tests.rs @@ -990,7 +990,7 @@ fn issue_2343_complete_mono_with_shadowed_vars() { ^ Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. + one by accident. Give one of them a new name. "# ), ); @@ -1268,3 +1268,27 @@ fn enum_tag_union_in_list() { r#"[E, F, G, H] : List [E, F, G, H]"#, ); } + +#[test] +fn str_to_dec() { + expect_success( + indoc!( + r#" + Str.toDec "1234.1234" + "# + ), + r#"Ok 1234.1234 : Result Dec [InvalidNumStr]"#, + ); +} + +#[test] +fn tuple() { + expect_success( + indoc!( + r#" + ("a", 2u32) + "# + ), + r#"("a", 2) : ( Str, U32 )*"#, + ); +} diff --git a/crates/repl_test/src/wasm.rs b/crates/repl_test/src/wasm.rs index a11d3abbf1..80c664863a 100644 --- a/crates/repl_test/src/wasm.rs +++ b/crates/repl_test/src/wasm.rs @@ -4,7 +4,7 @@ use roc_wasm_interp::{ }; const COMPILER_BYTES: &[u8] = - include_bytes!("../../../target/wasm32-wasi/release/roc_repl_wasm.wasm"); + include_bytes!("../../../target/wasm32-wasi/release-with-lto/roc_repl_wasm.wasm"); struct CompilerDispatcher<'a> { arena: &'a Bump, diff --git a/crates/repl_test/test_wasm.sh b/crates/repl_test/test_wasm.sh index 1e772d367e..1e36648ac1 100755 --- a/crates/repl_test/test_wasm.sh +++ b/crates/repl_test/test_wasm.sh @@ -7,7 +7,9 @@ set -euxo pipefail # We need to clear RUSTFLAGS for this command, as CI sets normally some flags that are specific to CPU targets. # Tests target wasm32-wasi instead of wasm32-unknown-unknown, so that we can debug with println! and dbg! -RUSTFLAGS="" cargo build --locked --release --target wasm32-wasi -p roc_repl_wasm --no-default-features --features wasi_test +# This has to be built with lto for now. If we do not use lto, it will pull in system calls we don't yet support. +# TODO: Add system calls to our wasi interp so that this can just be built in release without lto. +RUSTFLAGS="" cargo build --locked --profile release-with-lto --target wasm32-wasi -p roc_repl_wasm --no-default-features --features wasi_test # Build & run the test code on *native* target, not WebAssembly cargo test --locked --release -p repl_test --features wasm diff --git a/crates/repl_wasm/Cargo.toml b/crates/repl_wasm/Cargo.toml index d977ff74f1..59e245f0f7 100644 --- a/crates/repl_wasm/Cargo.toml +++ b/crates/repl_wasm/Cargo.toml @@ -1,36 +1,39 @@ [package] -edition = "2021" name = "roc_repl_wasm" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "Provides a build of the REPL for the Roc website using WebAssembly." +authors = ["The Roc Contributors"] +edition = "2021" +license = "UPL-1.0" +version = "0.0.1" + [lib] crate-type = ["cdylib"] [build-dependencies] -roc_builtins = {path = "../compiler/builtins"} -roc_utils = {path = "../utils"} +roc_bitcode = { path = "../compiler/builtins/bitcode" } +roc_builtins = { path = "../compiler/builtins" } wasi_libc_sys = { path = "../wasi-libc-sys" } + tempfile.workspace = true [dependencies] bumpalo.workspace = true -console_error_panic_hook = {version = "0.1.7", optional = true} -futures = {version = "0.3.24", optional = true} -js-sys = "0.3.60" -wasm-bindgen = "0.2.79" -wasm-bindgen-futures = "0.4.33" +console_error_panic_hook = { workspace = true, optional = true } +futures = { workspace = true, optional = true } +getrandom = { version = "0.2", features = ["js"] } # not a direct dependency, needed because of https://docs.rs/getrandom/latest/getrandom/#webassembly-support +js-sys.workspace = true +wasm-bindgen-futures.workspace = true +wasm-bindgen.workspace = true -roc_collections = {path = "../compiler/collections"} -roc_gen_wasm = {path = "../compiler/gen_wasm"} -roc_load = {path = "../compiler/load"} -roc_parse = {path = "../compiler/parse"} -roc_repl_eval = {path = "../repl_eval"} -roc_reporting = {path = "../reporting"} -roc_target = {path = "../compiler/roc_target"} -roc_types = {path = "../compiler/types"} +roc_collections = { path = "../compiler/collections" } +roc_gen_wasm = { path = "../compiler/gen_wasm" } +roc_load = { path = "../compiler/load" } +roc_parse = { path = "../compiler/parse" } +roc_repl_eval = { path = "../repl_eval" } +roc_reporting = { path = "../reporting" } +roc_target = { path = "../compiler/roc_target" } +roc_types = { path = "../compiler/types" } [features] wasi_test = ["futures"] diff --git a/crates/repl_wasm/build.rs b/crates/repl_wasm/build.rs index 0e6ed6bb2b..0ed3b1b4dd 100644 --- a/crates/repl_wasm/build.rs +++ b/crates/repl_wasm/build.rs @@ -2,11 +2,16 @@ use std::env; use std::path::PathBuf; use std::process::Command; -use roc_builtins::bitcode; use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH}; const PLATFORM_FILENAME: &str = "repl_platform"; +#[cfg(not(windows))] +const OBJECT_EXTENSION: &str = "o"; + +#[cfg(windows)] +const OBJECT_EXTENSION: &str = "obj"; + fn main() { println!("cargo:rerun-if-changed=build.rs"); let source_path = format!("src/{}.c", PLATFORM_FILENAME); @@ -21,10 +26,10 @@ fn main() { let mut pre_linked_binary_path = PathBuf::from(std::env::var("OUT_DIR").unwrap()); pre_linked_binary_path.extend(["pre_linked_binary"]); - pre_linked_binary_path.set_extension("o"); + pre_linked_binary_path.set_extension(OBJECT_EXTENSION); - let builtins_host_tempfile = - bitcode::host_wasm_tempfile().expect("failed to write host builtins object to tempfile"); + let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile() + .expect("failed to write host builtins object to tempfile"); let output = Command::new(&zig_executable()) .args([ @@ -60,7 +65,7 @@ fn zig_executable() -> String { fn build_wasm_platform(out_dir: &str, source_path: &str) -> PathBuf { let mut platform_obj = PathBuf::from(out_dir).join(PLATFORM_FILENAME); - platform_obj.set_extension("o"); + platform_obj.set_extension(OBJECT_EXTENSION); Command::new(&zig_executable()) .args([ diff --git a/crates/repl_wasm/src/repl.rs b/crates/repl_wasm/src/repl.rs index b3bd03206f..8108e4a9e8 100644 --- a/crates/repl_wasm/src/repl.rs +++ b/crates/repl_wasm/src/repl.rs @@ -164,9 +164,14 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> { } } +#[cfg(not(windows))] const PRE_LINKED_BINARY: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/pre_linked_binary.o")) as &[_]; +#[cfg(windows)] +const PRE_LINKED_BINARY: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/pre_linked_binary.obj")) as &[_]; + pub async fn entrypoint_from_js(src: String) -> Result { #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); @@ -212,8 +217,8 @@ pub async fn entrypoint_from_js(src: String) -> Result { .. } = mono; - debug_assert_eq!(exposed_to_host.values.len(), 1); - let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); + debug_assert_eq!(exposed_to_host.top_level_values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.top_level_values.iter().next().unwrap(); let main_fn_symbol = *main_fn_symbol; let main_fn_var = *main_fn_var; @@ -237,7 +242,7 @@ pub async fn entrypoint_from_js(src: String) -> Result { module_id, stack_bytes: roc_gen_wasm::Env::DEFAULT_STACK_BYTES, exposed_to_host: exposed_to_host - .values + .top_level_values .keys() .copied() .collect::>(), diff --git a/crates/reporting/Cargo.toml b/crates/reporting/Cargo.toml index df2ac5dd73..b2315b965b 100644 --- a/crates/reporting/Cargo.toml +++ b/crates/reporting/Cargo.toml @@ -1,40 +1,43 @@ [package] name = "roc_reporting" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Responsible for generating warning and error messages." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] +roc_can = { path = "../compiler/can" } roc_collections = { path = "../compiler/collections" } roc_error_macros = { path = "../error_macros" } roc_exhaustive = { path = "../compiler/exhaustive" } -roc_region = { path = "../compiler/region" } +roc_fmt = { path = "../compiler/fmt" } roc_module = { path = "../compiler/module" } roc_parse = { path = "../compiler/parse" } roc_problem = { path = "../compiler/problem" } -roc_types = { path = "../compiler/types" } -roc_can = { path = "../compiler/can" } -roc_fmt = { path = "../compiler/fmt" } +roc_region = { path = "../compiler/region" } roc_solve_problem = { path = "../compiler/solve_problem" } roc_std = { path = "../roc_std" } +roc_types = { path = "../compiler/types" } ven_pretty = { path = "../vendor/pretty" } -distance.workspace = true +itertools = "0.10.5" + bumpalo.workspace = true +distance.workspace = true [dev-dependencies] +roc_builtins = { path = "../compiler/builtins" } roc_constrain = { path = "../compiler/constrain" } roc_derive = { path = "../compiler/derive" } -roc_builtins = { path = "../compiler/builtins" } roc_load = { path = "../compiler/load" } -roc_problem = { path = "../compiler/problem" } +roc_packaging = { path = "../packaging" } roc_parse = { path = "../compiler/parse" } +roc_problem = { path = "../compiler/problem" } +roc_solve = { path = "../compiler/solve" } roc_target = { path = "../compiler/roc_target" } roc_test_utils = { path = "../test_utils" } -roc_solve = { path = "../compiler/solve" } -roc_packaging = { path = "../packaging" } -pretty_assertions.workspace = true indoc.workspace = true insta.workspace = true +pretty_assertions.workspace = true diff --git a/crates/reporting/src/error/canonicalize.rs b/crates/reporting/src/error/canonicalize.rs index db626cef12..7468e1d93e 100644 --- a/crates/reporting/src/error/canonicalize.rs +++ b/crates/reporting/src/error/canonicalize.rs @@ -1218,6 +1218,21 @@ fn to_bad_ident_expr_report<'b>( ]), ]) } + QualifiedTupleAccessor(pos) => { + let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); + + alloc.stack([ + alloc.reflow("I am trying to parse a qualified name here:"), + alloc.region_with_subregion(lines.convert_region(surroundings), region), + alloc.concat([ + alloc.reflow("This looks like a tuple accessor on a module or tag name,"), + alloc.reflow(r"but neither modules nor tags can have tuple elements! "), + alloc.reflow(r"Maybe you wanted a qualified name, something like "), + alloc.parser_suggestion("Json.Decode.string"), + alloc.text("."), + ]), + ]) + } QualifiedTag(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); @@ -1358,7 +1373,7 @@ fn to_bad_ident_pattern_report<'b>( ]), ]), - WeirdDotQualified(pos) => { + QualifiedTupleAccessor(pos) | WeirdDotQualified(pos) => { let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); alloc.stack([ @@ -1524,7 +1539,7 @@ fn report_shadowing<'b>( alloc.concat([ alloc.reflow("Since these "), alloc.reflow(what_plural), - alloc.reflow(" have the same name, it's easy to use the wrong one on accident. Give one of them a new name."), + alloc.reflow(" have the same name, it's easy to use the wrong one by accident. Give one of them a new name."), ]), ]) }; diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 41f5ce1c47..3a8dd5d9a5 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -2,6 +2,8 @@ use crate::error::canonicalize::{to_circular_def_doc, CIRCULAR_DEF}; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder}; +use itertools::EitherOrBoth; +use itertools::Itertools; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{HumanIndex, MutSet, SendMap}; use roc_collections::VecMap; @@ -13,14 +15,14 @@ use roc_module::symbol::Symbol; use roc_problem::Severity; use roc_region::all::{LineInfo, Region}; use roc_solve_problem::{ - NotDerivableContext, NotDerivableDecode, NotDerivableEq, TypeError, UnderivableReason, - Unfulfilled, + NotDerivableContext, NotDerivableDecode, NotDerivableEncode, NotDerivableEq, TypeError, + UnderivableReason, Unfulfilled, }; use roc_std::RocDec; use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::types::{ - AbilitySet, AliasKind, Category, ErrorType, PatternCategory, Polarity, Reason, RecordField, - TypeExt, + AbilitySet, AliasKind, Category, ErrorType, IndexOrField, PatternCategory, Polarity, Reason, + RecordField, TypeExt, }; use std::path::PathBuf; use ven_pretty::DocAllocator; @@ -371,7 +373,29 @@ fn underivable_hint<'b>( ])), ]))) } + NotDerivableContext::Encode(reason) => match reason { + NotDerivableEncode::Nat => { + Some(alloc.note("").append(alloc.concat([ + alloc.reflow("Encoding a "), + alloc.type_str("Nat"), + alloc.reflow(" is not supported. Consider using a fixed-sized unsigned integer, like a "), + alloc.type_str("U64"), + alloc.reflow(" instead."), + ]))) + } + }, NotDerivableContext::Decode(reason) => match reason { + NotDerivableDecode::Nat => { + Some(alloc.note("").append(alloc.concat([ + alloc.reflow("Decoding to a "), + alloc.type_str("Nat"), + alloc.reflow(" is not supported. Consider decoding to a fixed-sized unsigned integer, like "), + alloc.type_str("U64"), + alloc.reflow(", then converting to a "), + alloc.type_str("Nat"), + alloc.reflow(" if needed."), + ]))) + } NotDerivableDecode::OptionalRecordField(field) => { Some(alloc.note("").append(alloc.concat([ alloc.reflow("I can't derive decoding for a record with an optional field, which in this case is "), @@ -1683,10 +1707,13 @@ fn format_category<'b>( alloc.text(" of type:"), ), - RecordAccessor(field) => ( + Accessor(field) => ( alloc.concat([ alloc.text(format!("{}his ", t)), - alloc.record_field(field.to_owned()), + match field { + IndexOrField::Index(index) => alloc.tuple_field(*index), + IndexOrField::Field(field) => alloc.record_field(field.to_owned()), + }, alloc.text(" value"), ]), alloc.text(" is a:"), @@ -1704,14 +1731,6 @@ fn format_category<'b>( alloc.text(" of type:"), ), - TupleAccessor(index) => ( - alloc.concat([ - alloc.text(format!("{}his ", t)), - alloc.tuple_field(*index), - alloc.text(" value"), - ]), - alloc.text(" is a:"), - ), TupleAccess(index) => ( alloc.concat([ alloc.text(format!("{}he value at ", t)), @@ -2024,6 +2043,7 @@ fn add_pattern_category<'b>( let rest = match category { Record => alloc.reflow(" record values of type:"), + Tuple => alloc.reflow(" tuple values of type:"), EmptyRecord => alloc.reflow(" an empty record:"), PatternGuard => alloc.reflow(" a pattern guard of type:"), PatternDefault => alloc.reflow(" an optional field of type:"), @@ -2474,6 +2494,8 @@ fn to_doc_help<'b>( .map(|(k, v)| (alloc.tag_name(k), v)) .collect(), tag_ext_to_doc(alloc, pol, gen_usages, ext), + 0, // zero tags omitted, since this isn't a diff + None, ) } @@ -2493,13 +2515,16 @@ fn to_doc_help<'b>( .collect::>(); tags.sort_by(|(a, _), (b, _)| a.cmp(b)); - report_text::recursive_tag_union( + let rec_doc = to_doc_help(ctx, gen_usages, alloc, Parens::Unnecessary, *rec_var); + + report_text::tag_union( alloc, - to_doc_help(ctx, gen_usages, alloc, Parens::Unnecessary, *rec_var), tags.into_iter() .map(|(k, v)| (alloc.tag_name(k), v)) .collect(), tag_ext_to_doc(alloc, pol, gen_usages, ext), + 0, // zero tags omitted, since this isn't a diff + Some(rec_doc), ) } @@ -2867,21 +2892,11 @@ fn to_diff<'b>( } (TagUnion(tags1, ext1, pol), TagUnion(tags2, ext2, _)) => { - diff_tag_union(alloc, pol, &tags1, ext1, &tags2, ext2) + diff_tag_union(alloc, pol, tags1, ext1, None, tags2, ext2, None) } - (RecursiveTagUnion(_rec1, _tags1, _ext1, _), RecursiveTagUnion(_rec2, _tags2, _ext2, _)) => { - // TODO do a better job here - let (left, left_able) = to_doc(alloc, Parens::Unnecessary, type1); - let (right, right_able) = to_doc(alloc, Parens::Unnecessary, type2); - - Diff { - left, - right, - status: Status::Similar, - left_able, - right_able, - } + (RecursiveTagUnion(rec1, tags1, ext1, pol), RecursiveTagUnion(rec2, tags2, ext2, _)) => { + diff_tag_union(alloc, pol, tags1, ext1, Some(*rec1), tags2, ext2, Some(*rec2)) } pair => { @@ -3207,7 +3222,11 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool { .any(|(t1, t2)| should_show_diff(t1, t2)) } (Infinite, Infinite) | (Error, Error) => false, - (FlexVar(v1), FlexVar(v2)) | (RigidVar(v1), RigidVar(v2)) => v1 != v2, + (RigidVar(v1), RigidVar(v2)) => v1 != v2, + (FlexVar(_), _) | (_, FlexVar(_)) => { + // If either is flex, it will unify to the other type; no diff is needed. + false + } (FlexAbleVar(v1, _set1), FlexAbleVar(v2, _set2)) | (RigidAbleVar(v1, _set1), RigidAbleVar(v2, _set2)) => { #[cfg(debug_assertions)] @@ -3225,14 +3244,53 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool { v1 != v2 } (Record(fields1, ext1), Record(fields2, ext2)) => { - if fields1.len() != fields2.len() || ext1 != ext2 { + let is_1_open = matches!(ext1, TypeExt::FlexOpen(_)); + let is_2_open = matches!(ext2, TypeExt::FlexOpen(_)); + + if !is_1_open && !is_2_open && fields1.len() != fields2.len() { return true; } - fields1 - .iter() - .zip(fields2.iter()) - .any(|((name1, f1), (name2, f2))| name1 != name2 || should_show_field_diff(f1, f2)) + // Check for diffs in any of the fields1 fields. + for (name, f1) in fields1.iter() { + match fields2.get(name) { + Some(f2) => { + // If the field is on both records, and the diff should be + // shown, then we should show a diff for the whole record. + if should_show_field_diff(f1, f2) { + return true; + } + + // (If the field is on both records, but no diff should be + // shown between those fields, continue checking other fields.) + } + None => { + // It's fine for 1 to have a field that the other doesn't have, + // so long as the other one is open. + if !is_2_open { + return true; + } + } + } + } + + // At this point we've checked all the fields that are in both records, + // as well as all the fields that are in 1 but not 2. + // All that remains is to check the fields that are in 2 but not 1, + // which we don't care about if 1 is open (because then it's fine + // for the other record to have fields it doesn't). + if !is_1_open { + for name in fields2.keys() { + if !fields1.contains_key(name) { + // fields1 is missing this field, and fields1 is not open, + // therefore this is a relevant diff. + return true; + } + } + } + + // We haven't early-returned true yet, so we didn't find any relevant diffs! + false } (Tuple(elems1, ext1), Tuple(elems2, ext2)) => { if elems1.len() != elems2.len() || ext1 != ext2 { @@ -3343,8 +3401,6 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool { | (_, Error) | (Type(_, _), _) | (_, Type(_, _)) - | (FlexVar(_), _) - | (_, FlexVar(_)) | (RigidVar(_), _) | (_, RigidVar(_)) | (FlexAbleVar(_, _), _) @@ -3411,64 +3467,94 @@ fn should_show_field_diff( fn same_tag_name_overlap_diff<'b>( alloc: &'b RocDocAllocator<'b>, field: TagName, - args1: Vec, - args2: Vec, + payload_vals1: Vec, + payload_vals2: Vec, ) -> Diff<(TagName, RocDocBuilder<'b>, Vec>)> { - if args1.len() == args2.len() { - let diff = diff_args(alloc, Parens::InTypeParam, args1, args2); + // Render ellipses wherever the payload slots have the same type. + let mut left_doc = Vec::with_capacity(payload_vals1.len()); + let mut left_able = Vec::new(); + let mut right_doc = Vec::with_capacity(payload_vals2.len()); + let mut right_able = Vec::new(); - Diff { - left: (field.clone(), alloc.tag_name(field.clone()), diff.left), - right: (field.clone(), alloc.tag_name(field), diff.right), - status: diff.status, - left_able: diff.left_able, - right_able: diff.right_able, - } - } else { - let (left_doc, left_able): (_, Vec) = args1 - .into_iter() - .map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) - .unzip(); - let (right_doc, right_able): (_, Vec) = args2 - .into_iter() - .map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) - .unzip(); + // itertools::zip_longest is a zip that can continue past the end of one Vec. + // If they both have payload values in a given slot, and both are the same type, + // we render ellipsis instead of the actual type - since there's no diff between them. + // If one of them doesn't have a payload value in that slot, we always render its type. + for either_or_both in payload_vals1 + .into_iter() + .zip_longest(payload_vals2.into_iter()) + { + match either_or_both { + // Both tag unions have a payload value in this slot + EitherOrBoth::Both(t1, t2) => { + if should_show_diff(&t1, &t2) { + { + let (doc, able) = to_doc(alloc, Parens::InTypeParam, t1); + left_doc.push(doc); + left_able.extend(able); + } - Diff { - left: (field.clone(), alloc.tag_name(field.clone()), left_doc), - right: (field.clone(), alloc.tag_name(field), right_doc), - status: Status::Similar, - left_able: left_able.into_iter().flatten().collect(), - right_able: right_able.into_iter().flatten().collect(), + { + let (doc, able) = to_doc(alloc, Parens::InTypeParam, t2); + right_doc.push(doc); + right_able.extend(able); + } + } else { + left_doc.push(alloc.ellipsis()); + right_doc.push(alloc.ellipsis()); + } + } + // Only the left tag union has a payload value in this slot + EitherOrBoth::Left(t1) => { + let (doc, able) = to_doc(alloc, Parens::InTypeParam, t1); + left_doc.push(doc); + left_able.extend(able); + } + // Only the right tag union has a payload value in this slot + EitherOrBoth::Right(t2) => { + let (doc, able) = to_doc(alloc, Parens::InTypeParam, t2); + right_doc.push(doc); + right_able.extend(able); + } } } + + Diff { + left: (field.clone(), alloc.tag_name(field.clone()), left_doc), + right: (field.clone(), alloc.tag_name(field), right_doc), + status: Status::Similar, + left_able, + right_able, + } } fn diff_tag_union<'b>( alloc: &'b RocDocAllocator<'b>, pol: Polarity, - fields1: &SendMap>, + tags1: SendMap>, ext1: TypeExt, - fields2: &SendMap>, + rec1: Option, + mut tags2: SendMap>, ext2: TypeExt, + rec2: Option, ) -> Diff> { let gen_usages1 = { let mut usages = VecMap::default(); - count_generated_name_usages(&mut usages, fields1.values().flatten()); + count_generated_name_usages(&mut usages, tags1.values().flatten()); count_generated_name_usages_in_exts(&mut usages, [(&ext1, false)]); usages }; let gen_usages2 = { let mut usages = VecMap::default(); - count_generated_name_usages(&mut usages, fields2.values().flatten()); + count_generated_name_usages(&mut usages, tags2.values().flatten()); count_generated_name_usages_in_exts(&mut usages, [(&ext2, false)]); usages }; - let to_overlap_docs = |(field, (t1, t2)): (TagName, (Vec, Vec))| { - same_tag_name_overlap_diff(alloc, field, t1, t2) + let to_overlap_docs = |(tag_name, (t1, t2)): (TagName, (Vec, Vec))| { + same_tag_name_overlap_diff(alloc, tag_name, t1, t2) }; - let to_unknown_docs = |(field, args): (&TagName, &Vec)| -> ( + let to_unknown_docs = |(tag_name, args): (&TagName, &Vec)| -> ( TagName, RocDocBuilder<'b>, Vec>, @@ -3480,103 +3566,221 @@ fn diff_tag_union<'b>( .map(|arg| to_doc(alloc, Parens::InTypeParam, arg.clone())) .unzip(); ( - field.clone(), - alloc.tag_name(field.clone()), + tag_name.clone(), + alloc.tag_name(tag_name.clone()), args, able.into_iter().flatten().collect(), ) }; - let shared_keys = fields1 - .clone() - .intersection_with(fields2.clone(), |v1, v2| (v1, v2)); + let mut same_tags_different_payloads = VecMap::default(); + let mut tags_in_left_only = Vec::default(); + let mut same_tags_same_payloads = 0; - let left_keys = fields1.clone().relative_complement(fields2.clone()); - let right_keys = fields2.clone().relative_complement(fields1.clone()); + for (k1, v1) in tags1.into_iter() { + match tags2.remove(&k1) { + Some(v2) if should_show_payload_diff(&v1, &v2) => { + // The tag names are the same but the payload types are different + // (or at least should be rendered as different) + same_tags_different_payloads.insert(k1.clone(), (v1.clone(), v2)); + } + Some(_) => { + // They both have the same tag name as well as the same payload types + same_tags_same_payloads += 1; + } + None => { + // Only tags1 has this tag. + tags_in_left_only.push((k1, v1)); + } + } + } - let both = shared_keys.into_iter().map(to_overlap_docs); - let mut left = left_keys.iter().map(to_unknown_docs).peekable(); - let mut right = right_keys.iter().map(to_unknown_docs).peekable(); + // We've removed all the tags that they had in common, so the remaining entries in tags2 + // are ones that appear on the right only. + let tags_in_right_only = tags2; + let no_tags_in_common = same_tags_different_payloads.is_empty() && same_tags_same_payloads == 0; + let both = same_tags_different_payloads + .into_iter() + .map(to_overlap_docs); - let all_fields_shared = left.peek().is_none() && right.peek().is_none(); + let any_tags_on_one_side_only = !tags_in_left_only.is_empty() || !tags_in_right_only.is_empty(); + + let mut left = tags_in_left_only + .iter() + .map(|(k, v)| to_unknown_docs((k, v))) + .peekable(); + let mut right = tags_in_right_only.iter().map(to_unknown_docs).peekable(); let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) { (false, false) => Status::Similar, _ => match (left.peek(), right.peek()) { + // At least one tag appeared only on the left, and also + // at least one tag appeared only on the right. There's a chance this is + // because of a typo, so we'll suggest that as a hint. (Some((f, _, _, _)), Some(_)) => Status::Different(vec![Problem::TagTypo( f.clone(), - fields2.keys().cloned().collect(), + tags_in_right_only.keys().cloned().collect(), )]), - (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 - } + // At least one tag appeared only on the left, but all of the tags + // on the right also appeared on the left. So at least one tag is missing. + (Some(_), None) => Status::Different(vec![Problem::TagsMissing( + left.clone().map(|v| v.0).collect(), + )]), + // At least one tag appeared only on the right, but all of the tags + // on the left also appeared on the right. So at least one tag is missing. (None, Some(_)) => { let status = Status::Different(vec![Problem::TagsMissing(right.map(|v| v.0).collect())]); - right = right_keys.iter().map(to_unknown_docs).peekable(); + right = tags_in_right_only.iter().map(to_unknown_docs).peekable(); status } + // Left and right have the same set of tag names (but may have different payloads). (None, None) => Status::Similar, }, }; + let ext1_is_open = matches!(&ext1, TypeExt::FlexOpen(_)); + let ext2_is_open = matches!(&ext2, TypeExt::FlexOpen(_)); let ext_diff = tag_ext_to_diff(alloc, pol, ext1, ext2, &gen_usages1, &gen_usages2); - let mut fields_diff: Diff, Vec>)>> = Diff { - left: vec![], - right: vec![], + let mut tags_diff: Diff, Vec>)>> = Diff { status: Status::Similar, - left_able: vec![], - right_able: vec![], + left: Vec::new(), + right: Vec::new(), + left_able: Vec::new(), + right_able: Vec::new(), }; for diff in both { - fields_diff.left.push(diff.left); - fields_diff.right.push(diff.right); - fields_diff.status.merge(diff.status); - fields_diff.left_able.extend(diff.left_able); - fields_diff.right_able.extend(diff.right_able); + tags_diff.status.merge(diff.status); + tags_diff.left.push(diff.left); + tags_diff.right.push(diff.right); + tags_diff.left_able.extend(diff.left_able); + tags_diff.right_able.extend(diff.right_able); } - if !all_fields_shared { - for (tag, tag_doc, args, able) in left { - fields_diff.left.push((tag, tag_doc, args)); - fields_diff.left_able.extend(able); + let left_tags_omitted; + let right_tags_omitted; + + if no_tags_in_common { + // If they have no tags in common, we shouldn't omit any tags, + // because that would result in an unhelpful diff of + // […] on one side and another […] on the other side! + + left_tags_omitted = 0; + right_tags_omitted = 0; + + for (tag, tag_doc, payload_vals, able) in left { + tags_diff.left.push((tag, tag_doc, payload_vals)); + tags_diff.left_able.extend(able); } - for (tag, tag_doc, args, able) in right { - fields_diff.right.push((tag, tag_doc, args)); - fields_diff.right_able.extend(able); + + for (tag, tag_doc, payload_vals, able) in right { + tags_diff.right.push((tag, tag_doc, payload_vals)); + tags_diff.right_able.extend(able); } - fields_diff.status.merge(Status::Different(vec![])); + + tags_diff.status.merge(Status::Different(Vec::new())); + } else if any_tags_on_one_side_only { + // If either tag union is open but the other is not, then omit the tags in the other. + // + // In other words, if one tag union is a pattern match which has _ ->, + // don't list the tags which fall under that catch-all pattern because + // they won't be helpful. By omitting them, we'll only show the tags that + // are actually matched. + // + // We shouldn't do this if they're both open though, + // because that would result in an unhelpful diff of + // […] on one side and another […] on the other side! + if ext2_is_open && !ext1_is_open { + left_tags_omitted = same_tags_same_payloads + left.len(); + } else { + left_tags_omitted = same_tags_same_payloads; + + for (tag, tag_doc, args, able) in left { + tags_diff.left.push((tag, tag_doc, args)); + tags_diff.left_able.extend(able); + } + } + + if ext1_is_open && !ext2_is_open { + right_tags_omitted = same_tags_same_payloads + right.len(); + } else { + right_tags_omitted = same_tags_same_payloads; + + for (tag, tag_doc, args, able) in right { + tags_diff.right.push((tag, tag_doc, args)); + tags_diff.right_able.extend(able); + } + } + + tags_diff.status.merge(Status::Different(Vec::new())); + } else { + left_tags_omitted = same_tags_same_payloads; + right_tags_omitted = same_tags_same_payloads; } - fields_diff.left.sort_by(|a, b| a.0.cmp(&b.0)); - fields_diff.right.sort_by(|a, b| a.0.cmp(&b.0)); + tags_diff.left.sort_by(|a, b| a.0.cmp(&b.0)); + tags_diff.right.sort_by(|a, b| a.0.cmp(&b.0)); - let lefts = fields_diff - .left - .into_iter() - .map(|(_, a, b)| (a, b)) - .collect(); - let rights = fields_diff + let lefts = tags_diff.left.into_iter().map(|(_, a, b)| (a, b)).collect(); + let rights = tags_diff .right .into_iter() .map(|(_, a, b)| (a, b)) .collect(); - let doc1 = report_text::tag_union(alloc, lefts, ext_diff.left); - let doc2 = report_text::tag_union(alloc, rights, ext_diff.right); + let doc1 = match rec1 { + None => report_text::tag_union(alloc, lefts, ext_diff.left, left_tags_omitted, None), + Some(rec) => { + let (rec_doc, able) = to_doc(alloc, Parens::Unnecessary, rec); - fields_diff.status.merge(status); + tags_diff.left_able.extend(able); + + report_text::tag_union( + alloc, + lefts, + ext_diff.left, + left_tags_omitted, + Some(rec_doc), + ) + } + }; + let doc2 = match rec2 { + None => report_text::tag_union(alloc, rights, ext_diff.right, right_tags_omitted, None), + Some(rec) => { + let (rec_doc, able) = to_doc(alloc, Parens::Unnecessary, rec); + + tags_diff.right_able.extend(able); + + report_text::tag_union( + alloc, + rights, + ext_diff.right, + right_tags_omitted, + Some(rec_doc), + ) + } + }; + + tags_diff.status.merge(status); Diff { left: doc1, right: doc2, - status: fields_diff.status, - left_able: fields_diff.left_able, - right_able: fields_diff.right_able, + status: tags_diff.status, + left_able: tags_diff.left_able, + right_able: tags_diff.right_able, + } +} + +fn should_show_payload_diff(errs1: &[ErrorType], errs2: &[ErrorType]) -> bool { + if errs1.len() == errs2.len() { + errs1 + .iter() + .zip(errs2.iter()) + .any(|(err1, err2)| should_show_diff(err1, err2)) + } else { + true } } @@ -3597,16 +3801,15 @@ fn tag_ext_to_diff<'b>( left: ext_doc_1, right: ext_doc_2, status, - left_able: vec![], - right_able: vec![], + left_able: Vec::new(), + right_able: Vec::new(), }, Status::Different(_) => Diff { - // NOTE elm colors these differently at this point left: ext_doc_1, right: ext_doc_2, status, - left_able: vec![], - right_able: vec![], + left_able: Vec::new(), + right_able: Vec::new(), }, } } @@ -3927,6 +4130,8 @@ mod report_text { alloc: &'b RocDocAllocator<'b>, entries: Vec<(RocDocBuilder<'b>, Vec>)>, opt_ext: Option>, + tags_omitted: usize, + opt_rec: Option>, ) -> RocDocBuilder<'b> { let ext_doc = if let Some(t) = opt_ext { t @@ -3934,73 +4139,67 @@ mod report_text { alloc.nil() }; - if entries.is_empty() { - alloc.text("[]") - } else { - let entry_to_doc = |(tag_name, arguments): (RocDocBuilder<'b>, Vec<_>)| { - if arguments.is_empty() { - tag_name - } else { - tag_name - .append(alloc.space()) - .append(alloc.intersperse(arguments, alloc.space())) - } - }; - - let starts = - std::iter::once(alloc.reflow("[")).chain(std::iter::repeat(alloc.reflow(", "))); - - let entries_doc = alloc.concat( - entries - .into_iter() - .zip(starts) - .map(|(entry, start)| start.append(entry_to_doc(entry))), - ); - - entries_doc.append(alloc.reflow("]")).append(ext_doc) - } - } - - pub fn recursive_tag_union<'b>( - alloc: &'b RocDocAllocator<'b>, - rec_var: RocDocBuilder<'b>, - entries: Vec<(RocDocBuilder<'b>, Vec>)>, - opt_ext: Option>, - ) -> RocDocBuilder<'b> { - let ext_doc = if let Some(t) = opt_ext { - t - } else { - alloc.nil() + let entry_to_doc = |(tag_name, arguments): (RocDocBuilder<'b>, Vec<_>)| { + if arguments.is_empty() { + tag_name + } else { + tag_name + .append(alloc.space()) + .append(alloc.intersperse(arguments, alloc.space())) + } }; - if entries.is_empty() { - alloc.text("[]") - } else { - let entry_to_doc = |(tag_name, arguments): (RocDocBuilder<'b>, Vec<_>)| { - if arguments.is_empty() { - tag_name + let without_rec = if entries.is_empty() { + if tags_omitted == 0 { + alloc.text("[]") + } else { + alloc + .text("[") + .append(alloc.ellipsis().append(alloc.text("]"))) + } + .append(ext_doc) + } else if entries.len() == 1 { + // Single-tag unions get printed on one line; multi-tag unions get multiple lines + alloc + .text("[") + .append(entry_to_doc(entries.into_iter().next().unwrap())) + .append(if tags_omitted == 0 { + alloc.text("") } else { - tag_name - .append(alloc.space()) - .append(alloc.intersperse(arguments, alloc.space())) - } - }; - - let starts = - std::iter::once(alloc.reflow("[")).chain(std::iter::repeat(alloc.reflow(", "))); - - let entries_doc = alloc.concat( - entries - .into_iter() - .zip(starts) - .map(|(entry, start)| start.append(entry_to_doc(entry))), - ); - - entries_doc - .append(alloc.reflow("]")) + alloc.text(", ").append(alloc.ellipsis()) + }) + .append(alloc.text("]")) .append(ext_doc) - .append(alloc.text(" as ")) - .append(rec_var) + } else { + let ending = if tags_omitted == 0 { + alloc.reflow("]") + } else { + alloc.vcat([ + alloc.ellipsis().indent(super::TAG_INDENT), + alloc.reflow("]"), + ]) + }; + + // Multi-tag unions get printed on multiple lines, as do tag unions with tags omitted. + alloc + .vcat( + std::iter::once(alloc.reflow("[")).chain( + entries + .into_iter() + .map(|entry| { + entry_to_doc(entry) + .indent(super::TAG_INDENT) + .append(alloc.reflow(",")) + }) + .chain(std::iter::once(ending)), + ), + ) + .append(ext_doc) + }; + + match opt_rec { + Some(rec) => without_rec.append(alloc.text(" as ")).append(rec), + None => without_rec, } } @@ -4735,6 +4934,7 @@ fn exhaustive_pattern_to_doc<'b>( } const AFTER_TAG_INDENT: &str = " "; +const TAG_INDENT: usize = 4; const RECORD_FIELD_INDENT: usize = 4; fn pattern_to_doc_help<'b>( @@ -4831,6 +5031,18 @@ fn pattern_to_doc_help<'b>( .append(alloc.intersperse(arg_docs, alloc.reflow(", "))) .append(" }") } + RenderAs::Tuple => { + let mut arg_docs = Vec::with_capacity(args.len()); + + for v in args.into_iter() { + arg_docs.push(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 { diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 0c00ea064f..505c9cfb81 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -543,7 +543,7 @@ mod test_reporting { ^ Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. + one by accident. Give one of them a new name. "### ); @@ -575,7 +575,7 @@ mod test_reporting { ^^^^^^^^^^^^^^^^^^^^^^^^ Since these aliases have the same name, it's easy to use the wrong one - on accident. Give one of them a new name. + by accident. Give one of them a new name. "### ); @@ -957,6 +957,59 @@ mod test_reporting { "### ); + test_report!( + tuple_exhaustiveness_bad, + indoc!( + r#" + Color : [Red, Blue] + + value : (Color, Color) + value = (Red, Red) + + when value is + (Blue, Blue) -> "foo" + (Red, Blue) -> "foo" + (Blue, Red) -> "foo" + #(Red, Red) -> "foo" + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 9│> when value is + 10│> (Blue, Blue) -> "foo" + 11│> (Red, Blue) -> "foo" + 12│> (Blue, Red) -> "foo" + + Other possibilities include: + + ( Red, Red ) + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_report!( + tuple_exhaustiveness_good, + indoc!( + r#" + Color : [Red, Blue] + + value : (Color, Color) + value = (Red, Red) + + when value is + (Blue, Blue) -> "foo" + (Red, Blue) -> "foo" + (Blue, Red) -> "foo" + (Red, Red) -> "foo" + "# + ), + @"" // No error + ); + test_report!( elem_in_list, indoc!( @@ -1408,7 +1461,10 @@ mod test_reporting { But `f` needs its 1st argument to be: - [Green, Red] + [ + Green, + Red, + ] Tip: Seems like a tag typo. Maybe `Blue` should be `Red`? @@ -1442,7 +1498,10 @@ mod test_reporting { But `f` needs its 1st argument to be: - [Green Str, Red (Int *)] + [ + Green Str, + Red (Int *), + ] Tip: Seems like a tag typo. Maybe `Blue` should be `Red`? @@ -2475,11 +2534,11 @@ mod test_reporting { This `a` value is a: - [A] + […] But the type annotation on `f` says it should be: - [A, B] + [B, …] Tip: Looks like a closed tag union does not have the `B` tag. @@ -2509,11 +2568,15 @@ mod test_reporting { This `a` value is a: - [A] + […] But the type annotation on `f` says it should be: - [A, B, C] + [ + B, + C, + … + ] Tip: Looks like a closed tag union does not have the `B` and `C` tags. @@ -2578,11 +2641,11 @@ mod test_reporting { This `x` value is a: - [Left {}, Right Str] + [Right Str, …] But you are trying to use it as: - [Left *] + […] Tip: Looks like a closed tag union does not have the `Right` tag. @@ -3327,11 +3390,23 @@ mod test_reporting { This `Cons` tag application has the type: - [Cons {} [Cons Str [Cons {} a, Nil]b as a, Nil]b, Nil]b + [ + Cons {} [ + Cons Str [ + Cons {} a, + Nil, + ]b as a, + Nil, + ]b, + Nil, + ]b But the type annotation on `x` says it should be: - [Cons {} a, Nil] as a + [ + Cons {} a, + Nil, + ] as a "### ); @@ -3364,12 +3439,29 @@ mod test_reporting { This `ACons` tag application has the type: - [ACons (Int Signed64) [BCons (Int Signed64) [ACons Str [BCons I64 [ACons I64 (BList I64 I64), - ANil]b as ∞, BNil]c, ANil]b, BNil]c, ANil]b + [ + ACons (Int Signed64) [ + BCons (Int Signed64) [ + ACons Str [ + BCons I64 [ + ACons I64 (BList I64 I64), + ANil, + ]b as ∞, + BNil, + ]c, + ANil, + ]b, + BNil, + ]c, + ANil, + ]b But the type annotation on `x` says it should be: - [ACons I64 (BList I64 I64), ANil] as a + [ + ACons I64 (BList I64 I64), + ANil, + ] as a "### ); @@ -5593,25 +5685,6 @@ All branches in an `if` must have the same type! "### ); - test_report!( - part_starts_with_number, - indoc!( - r#" - foo.100 - "# - ), - @r###" - ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ - - I trying to parse a record field access here: - - 4│ foo.100 - ^ - - So I expect to see a lowercase letter next, like .name or .height. - "### - ); - test_report!( closure_underscore_ident, indoc!( @@ -7912,11 +7985,11 @@ In roc, functions are always written as a lambda, like{} This `v` value is a: - F [A, B, C] + F [C, …] But the branch patterns have type: - F [A, B] + F […] The branches must be cases of the `when` condition's type! @@ -8391,7 +8464,7 @@ In roc, functions are always written as a lambda, like{} ^^^^^^^^^ Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. + one by accident. Give one of them a new name. "# ); @@ -8420,7 +8493,7 @@ In roc, functions are always written as a lambda, like{} ^^^^^^^ Since these abilities have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. + one by accident. Give one of them a new name. "# ); @@ -8942,26 +9015,30 @@ In roc, functions are always written as a lambda, like{} foo "# ), - @r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The branches of this `when` expression don't match the condition: + The branches of this `when` expression don't match the condition: - 6│> when bool is - 7│ True -> "true" - 8│ False -> "false" - 9│ Wat -> "surprise!" + 6│> when bool is + 7│ True -> "true" + 8│ False -> "false" + 9│ Wat -> "surprise!" - This `bool` value is a: + This `bool` value is a: - Bool + Bool - But the branch patterns have type: + But the branch patterns have type: - [False, True, Wat] + [ + False, + True, + Wat, + ] - The branches must be cases of the `when` condition's type! - "# + The branches must be cases of the `when` condition's type! + "### ); // from https://github.com/roc-lang/roc/commit/1372737f5e53ee5bb96d7e1b9593985e5537023a @@ -9705,7 +9782,7 @@ In roc, functions are always written as a lambda, like{} ^^^^ Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. + one by accident. Give one of them a new name. ── UNNECESSARY DEFINITION ──────────────────────────────── /code/proj/Main.roc ─ @@ -9942,7 +10019,10 @@ In roc, functions are always written as a lambda, like{} This `lst` value is a: - [Cons {} ∞, Nil] as ∞ + [ + Cons {} ∞, + Nil, + ] as ∞ But the type annotation on `olist` says it should be: @@ -10509,11 +10589,11 @@ I recommend using camelCase. It's the standard style in Roc code! This `u8` value is a: - [Bad [DecodeProblem], Good (List U8)] + [Good …, …] But the branch patterns have type: - [Bad [DecodeProblem], Good (List U8) *] + [Good … *, …] The branches must be cases of the `when` condition's type! "### @@ -11143,6 +11223,55 @@ I recommend using camelCase. It's the standard style in Roc code! "### ); + test_no_problem!( + derive_hash_for_tuple, + indoc!( + r#" + app "test" provides [main] to "./platform" + + foo : a -> {} | a has Hash + + main = foo ("", 1) + "# + ) + ); + + test_report!( + cannot_hash_tuple_with_non_hash_element, + indoc!( + r#" + app "test" provides [main] to "./platform" + + foo : a -> {} | a has Hash + + main = foo ("", \{} -> {}) + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 5│ main = foo ("", \{} -> {}) + ^^^^^^^^^^^^^^^ + + I can't generate an implementation of the `Hash` ability for + + ( + Str, + {}a -> {}, + )a + + In particular, an implementation for + + {}a -> {} + + cannot be generated. + + Note: `Hash` cannot be generated for functions. + "### + ); + test_report!( shift_by_negative, indoc!( @@ -11525,6 +11654,58 @@ I recommend using camelCase. It's the standard style in Roc code! "### ); + test_no_problem!( + derive_eq_for_tuple, + indoc!( + r#" + app "test" provides [main] to "./platform" + + foo : a -> {} | a has Eq + + main = foo ("", 1) + "# + ) + ); + + test_report!( + cannot_eq_tuple_with_non_eq_element, + indoc!( + r#" + app "test" provides [main] to "./platform" + + foo : a -> {} | a has Eq + + main = foo ("", 1.0f64) + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 5│ main = foo ("", 1.0f64) + ^^^^^^^^^^^^ + + I can't generate an implementation of the `Eq` ability for + + ( + Str, + F64, + )a + + In particular, an implementation for + + F64 + + cannot be generated. + + Note: I can't derive `Bool.isEq` for floating-point types. That's + because Roc's floating-point numbers cannot be compared for total + equality - in Roc, `NaN` is never comparable to `NaN`. If a type + doesn't support total equality, it cannot support the `Eq` ability! + "### + ); + test_report!( cannot_import_structural_eq_not_eq, indoc!( @@ -11984,7 +12165,10 @@ I recommend using camelCase. It's the standard style in Roc code! The `when` condition is a list of type: - List [A, B] + List [ + A, + B, + ] But the branch patterns have type: @@ -12967,11 +13151,11 @@ I recommend using camelCase. It's the standard style in Roc code! This `map` call produces: - List [One, Two] + List [Two, …] But the type annotation on `main` says it should be: - List [One] + List […] "### ); @@ -13005,11 +13189,11 @@ I recommend using camelCase. It's the standard style in Roc code! This `map` call produces: - List [One, Two] + List [Two, …] But the type annotation on `main` says it should be: - List [One] + List […] "### ); @@ -13028,4 +13212,209 @@ I recommend using camelCase. It's the standard style in Roc code! "# ) ); + + test_report!( + derive_decoding_for_nat, + indoc!( + r#" + app "test" imports [Decode.{decoder}] provides [main] to "./platform" + + main = + myDecoder : Decoder Nat fmt | fmt has DecoderFormatting + myDecoder = decoder + + myDecoder + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 5│ myDecoder = decoder + ^^^^^^^ + + I can't generate an implementation of the `Decoding` ability for + + Nat + + Note: Decoding to a Nat is not supported. Consider decoding to a + fixed-sized unsigned integer, like U64, then converting to a Nat if + needed. + "### + ); + + test_report!( + derive_encoding_for_nat, + indoc!( + r#" + app "test" imports [] provides [main] to "./platform" + + x : Nat + + main = Encode.toEncoder x + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 5│ main = Encode.toEncoder x + ^ + + I can't generate an implementation of the `Encoding` ability for + + Int Natural + + In particular, an implementation for + + Natural + + cannot be generated. + + Tip: `Natural` does not implement `Encoding`. + "### + ); + + test_no_problem!( + derive_decoding_for_tuple, + indoc!( + r#" + app "test" imports [Decode.{decoder}] provides [main] to "./platform" + + main = + myDecoder : Decoder (U32, Str) fmt | fmt has DecoderFormatting + myDecoder = decoder + + myDecoder + "# + ) + ); + + test_report!( + cannot_decode_tuple_with_non_decode_element, + indoc!( + r#" + app "test" imports [Decode.{decoder}] provides [main] to "./platform" + + main = + myDecoder : Decoder (U32, {} -> {}) fmt | fmt has DecoderFormatting + myDecoder = decoder + + myDecoder + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 5│ myDecoder = decoder + ^^^^^^^ + + I can't generate an implementation of the `Decoding` ability for + + U32, {} -> {} + + Note: `Decoding` cannot be generated for functions. + "### + ); + + test_no_problem!( + derive_encoding_for_tuple, + indoc!( + r#" + app "test" imports [] provides [main] to "./platform" + + x : (U32, Str) + + main = Encode.toEncoder x + "# + ) + ); + + test_report!( + cannot_encode_tuple_with_non_encode_element, + indoc!( + r#" + app "test" imports [] provides [main] to "./platform" + + x : (U32, {} -> {}) + + main = Encode.toEncoder x + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 5│ main = Encode.toEncoder x + ^ + + I can't generate an implementation of the `Encoding` ability for + + U32, {} -> {} + + Note: `Encoding` cannot be generated for functions. + "### + ); + + test_report!( + exhaustiveness_check_function_or_tag_union_issue_4994, + indoc!( + r#" + app "test" provides [main] to "./platform" + + x : U8 + + ifThenCase = + when x is + 0 -> Red + 1 -> Yellow + 2 -> Purple + 3 -> Zulip + _ -> Green + + main = + when ifThenCase is + Red -> "red" + Green -> "green" + Yellow -> "yellow" + Zulip -> "zulip" + "# + ), + @r###" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + + This `when` does not cover all the possibilities: + + 14│> when ifThenCase is + 15│> Red -> "red" + 16│> Green -> "green" + 17│> Yellow -> "yellow" + 18│> Zulip -> "zulip" + + Other possibilities include: + + Purple + _ + + I would have to crash if I saw one of those! Add branches for them! + "### + ); + + test_no_problem!( + openness_constraint_opens_under_tuple, + indoc!( + r#" + x : [A, B, C] + when (x, 1u8) is + (A, _) -> Bool.true + (B, _) -> Bool.true + _ -> Bool.true + "# + ) + ); } diff --git a/crates/roc_std/Cargo.lock b/crates/roc_std/Cargo.lock deleted file mode 100644 index a70041e2d7..0000000000 --- a/crates/roc_std/Cargo.lock +++ /dev/null @@ -1,297 +0,0 @@ -# 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 = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[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 = "itoa" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" - -[[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.0.1" -dependencies = [ - "arrayvec", - "indoc", - "libc", - "pretty_assertions", - "quickcheck", - "quickcheck_macros", - "serde", - "serde_json", - "static_assertions", -] - -[[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "serde" -version = "1.0.143" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" - -[[package]] -name = "serde_json" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[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/crates/roc_std/Cargo.toml b/crates/roc_std/Cargo.toml index 5f0161caa8..8c7b41cb9d 100644 --- a/crates/roc_std/Cargo.toml +++ b/crates/roc_std/Cargo.toml @@ -1,26 +1,29 @@ [package] -authors = ["The Roc Contributors"] +name = "roc_std" description = "Rust representations of Roc data structures" +readme = "README.md" + +authors = ["The Roc Contributors"] edition = "2021" license = "UPL-1.0" -name = "roc_std" -readme = "README.md" repository = "https://github.com/roc-lang/roc" version = "0.0.1" [dependencies] -static_assertions = "1.1.0" arrayvec = "0.7.2" -serde = { version = "1", optional = true } +serde = { version = "1.0.153", optional = true } +static_assertions = "1.1.0" [dev-dependencies] -indoc = "1.0.3" -libc = "0.2.135" -pretty_assertions = "1.0.0" +libc = "0.2.139" +pretty_assertions = "1.3.0" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" -serde_json = "1.0.83" +serde_json = "1.0.94" [features] -std = [] serde = ["dep:serde"] +std = [] + +[package.metadata.cargo-udeps.ignore] +development = ["quickcheck_macros", "serde_json"] \ No newline at end of file diff --git a/crates/roc_std/src/lib.rs b/crates/roc_std/src/lib.rs index 7a4953e508..2b5fbd4aa4 100644 --- a/crates/roc_std/src/lib.rs +++ b/crates/roc_std/src/lib.rs @@ -1,5 +1,5 @@ //! Provides Rust representations of Roc data structures. -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] #![crate_type = "lib"] use arrayvec::ArrayString; diff --git a/crates/roc_std/src/roc_dict.rs b/crates/roc_std/src/roc_dict.rs index 96d17aac58..918c9558c9 100644 --- a/crates/roc_std/src/roc_dict.rs +++ b/crates/roc_std/src/roc_dict.rs @@ -46,7 +46,14 @@ impl RocDict { } impl RocDict { - pub fn from_iter>(src: I) -> Self { + unsafe fn insert_unchecked(&mut self, _key: K, _val: V) { + todo!(); + } +} + +impl FromIterator<(K, V)> for RocDict { + fn from_iter>(into_iter: T) -> Self { + let src = into_iter.into_iter(); let mut ret = Self::with_capacity(src.size_hint().0); for (key, val) in src { @@ -57,16 +64,6 @@ impl RocDict { ret } - - unsafe fn insert_unchecked(&mut self, _key: K, _val: V) { - todo!(); - } -} - -impl<'a, K: Hash, V> FromIterator<(K, V)> for RocDict { - fn from_iter>(into_iter: T) -> Self { - RocDict::from_iter(into_iter.into_iter()) - } } impl<'a, K, V> IntoIterator for &'a RocDict { @@ -132,6 +129,7 @@ impl Debug for RocDict { /// contiguously in memory. If we separate them at some point, we'll need to /// change this implementation drastically! #[derive(Eq)] +#[repr(C)] union RocDictItem { key_first: ManuallyDrop>, value_first: ManuallyDrop>, diff --git a/crates/roc_std/src/roc_list.rs b/crates/roc_std/src/roc_list.rs index 1f6ba03bae..426b50b597 100644 --- a/crates/roc_std/src/roc_list.rs +++ b/crates/roc_std/src/roc_list.rs @@ -9,7 +9,7 @@ use core::{ intrinsics::copy_nonoverlapping, iter::FromIterator, mem::{self, ManuallyDrop}, - ops::Deref, + ops::{Deref, DerefMut}, ptr::{self, NonNull}, }; @@ -28,7 +28,9 @@ use serde::{ pub struct RocList { elements: Option>>, length: usize, - capacity: usize, + // This technically points to directly after the refcount. + // This is an optimization that enables use one code path for regular lists and slices for geting the refcount ptr. + capacity_or_ref_ptr: usize, } impl RocList { @@ -41,7 +43,7 @@ impl RocList { Self { elements: None, length: 0, - capacity: 0, + capacity_or_ref_ptr: 0, } } @@ -51,7 +53,7 @@ impl RocList { Self { elements: Some(Self::elems_with_capacity(num_elems)), length: 0, - capacity: num_elems, + capacity_or_ref_ptr: num_elems, } } @@ -92,11 +94,19 @@ impl RocList { } pub fn len(&self) -> usize { - self.length + self.length & (isize::MAX as usize) + } + + pub fn is_seamless_slice(&self) -> bool { + ((self.length | self.capacity_or_ref_ptr) as isize) < 0 } pub fn capacity(&self) -> usize { - self.capacity + if !self.is_seamless_slice() { + self.capacity_or_ref_ptr + } else { + self.len() + } } pub fn is_empty(&self) -> bool { @@ -121,6 +131,14 @@ impl RocList { } } + pub fn as_mut_ptr(&mut self) -> *mut T { + self.as_mut_slice().as_mut_ptr() + } + + pub fn as_ptr(&self) -> *const T { + self.as_slice().as_ptr() + } + /// Marks a list as readonly. This means that it will be leaked. /// For constants passed in from platform to application, this may be reasonable. /// @@ -151,7 +169,20 @@ impl RocList { /// bytes over - in other words, calling this `as_slice` method and then calling `to_vec` /// on that. pub fn as_slice(&self) -> &[T] { - &*self + self + } + + /// Note that there is no way to convert directly to a Vec. + /// + /// This is because RocList values are not allocated using the system allocator, so + /// handing off any heap-allocated bytes to a Vec would not work because its Drop + /// implementation would try to free those bytes using the wrong allocator. + /// + /// Instead, if you want a Rust Vec, you need to do a fresh allocation and copy the + /// bytes over - in other words, calling this `as_slice` method and then calling `to_vec` + /// on that. + pub fn as_mut_slice(&mut self) -> &mut [T] { + &mut *self } #[inline(always)] @@ -173,10 +204,11 @@ impl RocList { /// Useful for doing memcpy on the underlying allocation. Returns NULL if list is empty. pub(crate) unsafe fn ptr_to_allocation(&self) -> *mut c_void { - unsafe { - self.ptr_to_first_elem() - .cast::() - .sub(Self::alloc_alignment() as usize) as *mut _ + let alignment = Self::alloc_alignment() as usize; + if self.is_seamless_slice() { + ((self.capacity_or_ref_ptr << 1) - alignment) as *mut _ + } else { + unsafe { self.ptr_to_first_elem().cast::().sub(alignment) as *mut _ } } } @@ -223,12 +255,12 @@ where roc_realloc( storage.as_ptr().cast(), Self::alloc_bytes(new_len), - Self::alloc_bytes(self.capacity), + Self::alloc_bytes(self.capacity()), Self::alloc_alignment(), ) }; - self.capacity = new_len; + self.capacity_or_ref_ptr = new_len; Self::elems_from_allocation(NonNull::new(new_ptr).unwrap_or_else(|| { todo!("Reallocation failed"); @@ -241,18 +273,20 @@ where } // Allocate new memory. - self.capacity = slice.len(); + self.capacity_or_ref_ptr = slice.len(); let new_elements = Self::elems_with_capacity(slice.len()); // Copy the old elements to the new allocation. unsafe { - copy_nonoverlapping(elements.as_ptr(), new_elements.as_ptr(), self.length); + copy_nonoverlapping(elements.as_ptr(), new_elements.as_ptr(), self.len()); } + // Clear the seamless slice bit since we now have clear ownership. + self.length = self.len(); new_elements } } else { - self.capacity = slice.len(); + self.capacity_or_ref_ptr = slice.len(); Self::elems_with_capacity(slice.len()) }; @@ -284,7 +318,7 @@ impl RocList { /// /// May return a new RocList, if the provided one was not unique. pub fn reserve(&mut self, num_elems: usize) { - let new_len = num_elems + self.length; + let new_len = num_elems + self.len(); let new_elems; let old_elements_ptr; @@ -298,7 +332,7 @@ impl RocList { let new_alloc = roc_realloc( old_alloc, Self::alloc_bytes(new_len), - Self::alloc_bytes(self.capacity), + Self::alloc_bytes(self.capacity()), Self::alloc_alignment(), ); @@ -327,7 +361,7 @@ impl RocList { unsafe { // Copy the old elements to the new allocation. - copy_nonoverlapping(old_elements_ptr, new_elems.as_ptr(), self.length); + copy_nonoverlapping(old_elements_ptr, new_elems.as_ptr(), self.len()); } // Decrease the current allocation's reference count. @@ -360,8 +394,8 @@ impl RocList { self.update_to(Self { elements: Some(new_elems), - length: self.length, - capacity: new_len, + length: self.len(), + capacity_or_ref_ptr: new_len, }); } @@ -381,7 +415,7 @@ impl Deref for RocList { fn deref(&self) -> &Self::Target { if let Some(elements) = self.elements { - let elements = ptr::slice_from_raw_parts(elements.as_ptr().cast::(), self.length); + let elements = ptr::slice_from_raw_parts(elements.as_ptr().cast::(), self.len()); unsafe { &*elements } } else { @@ -390,6 +424,19 @@ impl Deref for RocList { } } +impl DerefMut for RocList { + fn deref_mut(&mut self) -> &mut Self::Target { + if let Some(elements) = self.elements { + let ptr = elements.as_ptr().cast::(); + let elements = ptr::slice_from_raw_parts_mut(ptr, self.length); + + unsafe { &mut *elements } + } else { + &mut [] + } + } +} + impl Default for RocList { fn default() -> Self { Self::empty() @@ -413,7 +460,7 @@ where { fn partial_cmp(&self, other: &RocList) -> Option { // If one is longer than the other, use that as the ordering. - match self.length.partial_cmp(&other.length) { + match self.len().partial_cmp(&other.len()) { Some(Ordering::Equal) => {} ord => return ord, } @@ -437,7 +484,7 @@ where { fn cmp(&self, other: &Self) -> Ordering { // If one is longer than the other, use that as the ordering. - match self.length.cmp(&other.length) { + match self.len().cmp(&other.len()) { Ordering::Equal => {} ord => return ord, } @@ -479,7 +526,7 @@ impl Clone for RocList { Self { elements: self.elements, length: self.length, - capacity: self.capacity, + capacity_or_ref_ptr: self.capacity_or_ref_ptr, } } } @@ -564,7 +611,7 @@ impl FromIterator for RocList { return Self { elements: Some(Self::elems_with_capacity(count)), length: count, - capacity: count, + capacity_or_ref_ptr: count, }; } @@ -578,8 +625,8 @@ impl FromIterator for RocList { for new_elem in iter { // If the size_hint didn't give us a max, we may need to grow. 1.5x seems to be good, based on: // https://archive.ph/Z2R8w and https://github.com/facebook/folly/blob/1f2706/folly/docs/FBVector.md - if list.length == list.capacity { - list.reserve(list.capacity / 2); + if list.length == list.capacity() { + list.reserve(list.capacity() / 2); elements = list.elements.unwrap().as_ptr(); } diff --git a/crates/roc_std/src/roc_set.rs b/crates/roc_std/src/roc_set.rs index d268c63643..683045b8a4 100644 --- a/crates/roc_std/src/roc_set.rs +++ b/crates/roc_std/src/roc_set.rs @@ -27,10 +27,11 @@ impl RocSet { } } -impl RocSet { - #[allow(unused)] - pub fn from_iter>(src: I) -> Self { - Self(RocDict::from_iter(src.map(|elem| (elem, ())))) +impl FromIterator for RocSet { + fn from_iter>(into_iter: I) -> Self { + Self(RocDict::from_iter( + into_iter.into_iter().map(|elem| (elem, ())), + )) } } diff --git a/crates/roc_std/src/roc_str.rs b/crates/roc_std/src/roc_str.rs index 0face1fe2c..57cfc549a8 100644 --- a/crates/roc_std/src/roc_str.rs +++ b/crates/roc_std/src/roc_str.rs @@ -18,7 +18,7 @@ use core::{ }; #[cfg(feature = "std")] -use core::ffi::{CStr, CString}; +use std::ffi::{CStr, CString}; use crate::RocList; @@ -154,7 +154,7 @@ impl RocStr { /// bytes over - in other words, calling this `as_str` method and then calling `to_string` /// on that. pub fn as_str(&self) -> &str { - &*self + self } /// Create an empty RocStr with enough space preallocated to store @@ -562,8 +562,8 @@ impl Deref for RocStr { fn deref(&self) -> &Self::Target { match self.as_enum_ref() { - RocStrInnerRef::HeapAllocated(h) => unsafe { core::str::from_utf8_unchecked(&*h) }, - RocStrInnerRef::SmallString(s) => &*s, + RocStrInnerRef::HeapAllocated(h) => unsafe { core::str::from_utf8_unchecked(h) }, + RocStrInnerRef::SmallString(s) => s, } } } @@ -698,6 +698,11 @@ impl From for RocStr { #[repr(C)] union RocStrInner { + // TODO: this really should be separated from the List type. + // Due to length specifying seamless slices for Str and capacity for Lists they should not share the same code. + // Currently, there are work arounds in RocList to handle both via removing the highest bit of length in many cases. + // With glue changes, we should probably rewrite these cleanly to match what is in the zig bitcode. + // It is definitely a bit stale now and I think the storage mechanism can be quite confusing with our extra pieces of state. heap_allocated: ManuallyDrop>, small_string: SmallString, } diff --git a/crates/roc_std/tests/test_roc_std.rs b/crates/roc_std/tests/test_roc_std.rs index 4de4e543fe..63aec446a9 100644 --- a/crates/roc_std/tests/test_roc_std.rs +++ b/crates/roc_std/tests/test_roc_std.rs @@ -1,7 +1,7 @@ +#![allow(clippy::missing_safety_doc)] + #[macro_use] extern crate pretty_assertions; -// #[macro_use] -// extern crate indoc; extern crate quickcheck; extern crate roc_std; diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml index fa8caad1ad..1d70df053d 100644 --- a/crates/test_utils/Cargo.toml +++ b/crates/test_utils/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "roc_test_utils" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Utility functions used all over the code base." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -pretty_assertions = "1.3.0" -remove_dir_all = "0.7.0" +pretty_assertions.workspace = true +remove_dir_all.workspace = true [dev-dependencies] diff --git a/crates/tracing/Cargo.toml b/crates/tracing/Cargo.toml index ee153b5aa8..07eb67cb78 100644 --- a/crates/tracing/Cargo.toml +++ b/crates/tracing/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "roc_tracing" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" description = "Utilities for setting up tracing at various executable entry points." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] -tracing = { version = "0.1.36", features = ["release_max_level_off"] } -tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } -tracing-appender = "0.2.2" +tracing-appender.workspace = true +tracing-subscriber.workspace = true +tracing.workspace = true diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml deleted file mode 100644 index 02445e2963..0000000000 --- a/crates/utils/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "roc_utils" -version = "0.0.1" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2021" -description = "Utility functions used all over the code base." - -[dependencies] -snafu = { version = "0.7.1", features = ["backtraces"] } - -[dev-dependencies] diff --git a/crates/utils/command/Cargo.toml b/crates/utils/command/Cargo.toml new file mode 100644 index 0000000000..132b228fb3 --- /dev/null +++ b/crates/utils/command/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "roc_command_utils" +description = "Utility functions used all over the code base to call eternal apps like zig." + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] + +[dev-dependencies] diff --git a/crates/utils/src/lib.rs b/crates/utils/command/src/lib.rs similarity index 66% rename from crates/utils/src/lib.rs rename to crates/utils/command/src/lib.rs index b49eede385..ed76330655 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/command/src/lib.rs @@ -1,105 +1,8 @@ -//! Provides utility functions used all over the code base. -use snafu::OptionExt; use std::{ - collections::HashMap, env::{self, VarError}, path::PathBuf, process::Command, - slice::SliceIndex, }; -use util_error::{IndexOfFailedSnafu, KeyNotFoundSnafu, OutOfBoundsSnafu, UtilResult}; - -pub mod util_error; - -// replace HashMap method that returns Option with one that returns Result and proper Error -pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>( - hash_map: &'a HashMap, - key: &K, -) -> UtilResult<&'a V> { - let value = hash_map.get(key).context(KeyNotFoundSnafu { - key_str: format!("{:?}", key), - })?; - - Ok(value) -} - -pub fn index_of(elt: T, slice: &[T]) -> UtilResult { - let index = slice - .iter() - .position(|slice_elt| *slice_elt == elt) - .with_context(|| { - let elt_str = format!("{:?}", elt); - let collection_str = format!("{:?}", slice); - - IndexOfFailedSnafu { - elt_str, - collection_str, - } - })?; - - Ok(index) -} - -// replaces slice method that return Option with one that return Result and proper Error -pub fn slice_get(index: usize, slice: &[T]) -> UtilResult<&>::Output> { - let elt_ref = slice.get(index).context(OutOfBoundsSnafu { - index, - collection_name: "Slice", - len: slice.len(), - })?; - - Ok(elt_ref) -} - -pub fn slice_get_mut( - index: usize, - slice: &mut [T], -) -> UtilResult<&mut >::Output> { - let slice_len = slice.len(); - - let elt_ref = slice.get_mut(index).context(OutOfBoundsSnafu { - index, - collection_name: "Slice", - len: slice_len, - })?; - - Ok(elt_ref) -} - -// returns the index of the first occurrence of element and index of the last occurrence -pub fn first_last_index_of( - elt: T, - slice: &[T], -) -> UtilResult<(usize, usize)> { - let mut first_index_opt = None; - let mut last_index_opt = None; - - for (index, list_elt) in slice.iter().enumerate() { - if *list_elt == elt { - if first_index_opt.is_none() { - first_index_opt = Some(index); - last_index_opt = Some(index); - } else { - last_index_opt = Some(index) - } - } else if last_index_opt.is_some() { - break; - } - } - - if let (Some(first_index), Some(last_index)) = (first_index_opt, last_index_opt) { - Ok((first_index, last_index)) - } else { - let elt_str = format!("{:?}", elt); - let collection_str = format!("{:?}", slice); - - IndexOfFailedSnafu { - elt_str, - collection_str, - } - .fail() - } -} // get the path of the lib folder // runtime dependencies like zig files, Windows dylib builds, are put in the lib folder diff --git a/crates/utils/error/Cargo.toml b/crates/utils/error/Cargo.toml new file mode 100644 index 0000000000..85b4635dc8 --- /dev/null +++ b/crates/utils/error/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "roc_error_utils" +description = "Utility functions used all over the code base adding better errors to calls." + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] +snafu.workspace = true + +[dev-dependencies] diff --git a/crates/utils/error/src/lib.rs b/crates/utils/error/src/lib.rs new file mode 100644 index 0000000000..9b63f1307c --- /dev/null +++ b/crates/utils/error/src/lib.rs @@ -0,0 +1,127 @@ +//! Provides utility functions used all over the code base. +use snafu::{Backtrace, OptionExt, Snafu}; +use std::{collections::HashMap, slice::SliceIndex}; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum UtilError { + #[snafu(display( + "IndexOfFailed: Element {} was not found in collection {}.", + elt_str, + collection_str + ))] + IndexOfFailed { + elt_str: String, + collection_str: String, + backtrace: Backtrace, + }, + #[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))] + KeyNotFound { + key_str: String, + backtrace: Backtrace, + }, + #[snafu(display( + "OutOfBounds: index {} was out of bounds for {} with length {}.", + index, + collection_name, + len + ))] + OutOfBounds { + index: usize, + collection_name: String, + len: usize, + backtrace: Backtrace, + }, +} + +pub type UtilResult = std::result::Result; + +// replace HashMap method that returns Option with one that returns Result and proper Error +pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>( + hash_map: &'a HashMap, + key: &K, +) -> UtilResult<&'a V> { + let value = hash_map.get(key).context(KeyNotFoundSnafu { + key_str: format!("{:?}", key), + })?; + + Ok(value) +} + +pub fn index_of(elt: T, slice: &[T]) -> UtilResult { + let index = slice + .iter() + .position(|slice_elt| *slice_elt == elt) + .with_context(|| { + let elt_str = format!("{:?}", elt); + let collection_str = format!("{:?}", slice); + + IndexOfFailedSnafu { + elt_str, + collection_str, + } + })?; + + Ok(index) +} + +// replaces slice method that return Option with one that return Result and proper Error +pub fn slice_get(index: usize, slice: &[T]) -> UtilResult<&>::Output> { + let elt_ref = slice.get(index).context(OutOfBoundsSnafu { + index, + collection_name: "Slice", + len: slice.len(), + })?; + + Ok(elt_ref) +} + +pub fn slice_get_mut( + index: usize, + slice: &mut [T], +) -> UtilResult<&mut >::Output> { + let slice_len = slice.len(); + + let elt_ref = slice.get_mut(index).context(OutOfBoundsSnafu { + index, + collection_name: "Slice", + len: slice_len, + })?; + + Ok(elt_ref) +} + +// returns the index of the first occurrence of element and index of the last occurrence +pub fn first_last_index_of( + elt: T, + slice: &[T], +) -> UtilResult<(usize, usize)> { + let mut first_index_opt = None; + let mut last_index_opt = None; + + for (index, list_elt) in slice.iter().enumerate() { + if *list_elt == elt { + if first_index_opt.is_none() { + first_index_opt = Some(index); + last_index_opt = Some(index); + } else { + last_index_opt = Some(index) + } + } else if last_index_opt.is_some() { + break; + } + } + + if let (Some(first_index), Some(last_index)) = (first_index_opt, last_index_opt) { + Ok((first_index, last_index)) + } else { + let elt_str = format!("{:?}", elt); + let collection_str = format!("{:?}", slice); + + IndexOfFailedSnafu { + elt_str, + collection_str, + } + .fail() + } +} diff --git a/crates/utils/src/util_error.rs b/crates/utils/src/util_error.rs deleted file mode 100644 index d19c230f11..0000000000 --- a/crates/utils/src/util_error.rs +++ /dev/null @@ -1,35 +0,0 @@ -use snafu::{Backtrace, Snafu}; - -#[derive(Debug, Snafu)] -#[snafu(visibility(pub))] -pub enum UtilError { - #[snafu(display( - "IndexOfFailed: Element {} was not found in collection {}.", - elt_str, - collection_str - ))] - IndexOfFailed { - elt_str: String, - collection_str: String, - backtrace: Backtrace, - }, - #[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))] - KeyNotFound { - key_str: String, - backtrace: Backtrace, - }, - #[snafu(display( - "OutOfBounds: index {} was out of bounds for {} with length {}.", - index, - collection_name, - len - ))] - OutOfBounds { - index: usize, - collection_name: String, - len: usize, - backtrace: Backtrace, - }, -} - -pub type UtilResult = std::result::Result; diff --git a/crates/valgrind/Cargo.toml b/crates/valgrind/Cargo.toml index eb5271ab46..11799d3d20 100644 --- a/crates/valgrind/Cargo.toml +++ b/crates/valgrind/Cargo.toml @@ -1,20 +1,24 @@ [package] name = "valgrind" -version = "0.1.0" -edition = "2021" -authors = ["The Roc Contributors"] -license = "UPL-1.0" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dev-dependencies] -roc_cli = { path = "../cli" } cli_utils = { path = "../cli_utils" } roc_build = { path = "../compiler/build" } -roc_mono = { path = "../compiler/mono" } -roc_load = { path = "../compiler/load" } -roc_reporting = { path = "../reporting" } roc_linker = { path = "../linker" } +roc_load = { path = "../compiler/load" } +roc_mono = { path = "../compiler/mono" } roc_packaging = { path = "../packaging" } +roc_reporting = { path = "../reporting" } + bumpalo.workspace = true +indoc.workspace = true target-lexicon.workspace = true tempfile.workspace = true -indoc.workspace = true + +[package.metadata.cargo-udeps.ignore] +development = ["roc_build", "roc_linker"] \ No newline at end of file diff --git a/crates/valgrind/src/lib.rs b/crates/valgrind/src/lib.rs index 0a80e8f63f..2a1d6d4aa6 100644 --- a/crates/valgrind/src/lib.rs +++ b/crates/valgrind/src/lib.rs @@ -7,8 +7,8 @@ static BUILD_ONCE: std::sync::Once = std::sync::Once::new(); #[cfg(all(target_os = "linux"))] fn build_host() { - use roc_build::link::preprocessed_host_filename; - use roc_linker::build_and_preprocess_host; + use roc_build::program::build_and_preprocess_host; + use roc_linker::preprocessed_host_filename; let platform_main_roc = std::env::current_dir() .unwrap() @@ -26,8 +26,10 @@ fn build_host() { &target, &platform_main_roc, &preprocessed_host_path, - vec![String::from("mainForHost")], - vec![], + roc_linker::ExposedSymbols { + top_level_values: vec![String::from("mainForHost")], + exported_closure_types: vec![], + }, ); } @@ -45,7 +47,7 @@ fn valgrind_test(source: &str) { #[cfg(target_os = "linux")] fn valgrind_test_linux(source: &str) { - use roc_cli::build::BuiltFile; + use roc_build::program::BuiltFile; // the host is identical for all tests so we only want to build it once BUILD_ONCE.call_once(build_host); @@ -81,7 +83,7 @@ fn valgrind_test_linux(source: &str) { let arena = bumpalo::Bump::new(); let assume_prebuilt = true; - let res_binary_path = roc_cli::build::build_str_test( + let res_binary_path = roc_build::program::build_str_test( &arena, &app_module_path, &app_module_source, @@ -101,7 +103,7 @@ fn valgrind_test_linux(source: &str) { run_with_valgrind(&binary_path); } - Err(roc_cli::build::BuildFileError::LoadingProblem( + Err(roc_build::program::BuildFileError::LoadingProblem( roc_load::LoadingProblem::FormattedReport(report), )) => { eprintln!("{}", report); @@ -294,3 +296,20 @@ fn str_trim_left_capacity() { "# )); } + +#[test] +fn str_concat_later_referencing_empty_list_with_capacity() { + valgrind_test(indoc!( + r#" + ( + a : List U8 + a = List.withCapacity 1 + + List.concat a [58] + |> List.len + |> Num.addWrap (List.len a) + |> Num.toStr + ) + "# + )); +} diff --git a/crates/valgrind/zig-platform/host.zig b/crates/valgrind/zig-platform/host.zig index 7465a1fe0e..e74df0b22c 100644 --- a/crates/valgrind/zig-platform/host.zig +++ b/crates/valgrind/zig-platform/host.zig @@ -130,7 +130,7 @@ pub fn main() u8 { // stdout the result stdout.print("{s}", .{callresult.asSlice()}) catch unreachable; - callresult.deinit(); + callresult.decref(); stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable; diff --git a/crates/vendor/morphic_lib/Cargo.toml b/crates/vendor/morphic_lib/Cargo.toml index 5c950e0799..106b8b6e74 100644 --- a/crates/vendor/morphic_lib/Cargo.toml +++ b/crates/vendor/morphic_lib/Cargo.toml @@ -5,7 +5,8 @@ authors = ["William Brandon", "Wilson Berkow", "Frank Dai", "Benjamin Driscoll"] edition = "2018" [dependencies] -thiserror = "1.0.30" -sha2 = "0.10.2" -smallvec = "1.7.0" -typed-arena = "2.0.1" +roc_collections = {path = "../../compiler/collections"} +thiserror = "1.0.39" +blake3 = "1.3.3" +smallvec = "1.10.0" +typed-arena = "2.0.2" diff --git a/crates/vendor/morphic_lib/src/analyze.rs b/crates/vendor/morphic_lib/src/analyze.rs index 44f58da4ad..1560802350 100644 --- a/crates/vendor/morphic_lib/src/analyze.rs +++ b/crates/vendor/morphic_lib/src/analyze.rs @@ -1,8 +1,11 @@ use smallvec::SmallVec; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::BTreeSet; use std::convert::TryInto; use typed_arena::Arena; +use roc_collections::MutMap as HashMap; +use roc_collections::MutSet as HashSet; + use crate::api; use crate::ir; use crate::name_cache::{EntryPointId, FuncId}; @@ -192,7 +195,7 @@ impl Origin { impl Default for Origin { fn default() -> Self { - Origin::FromArgSlots(Set::new()) + Origin::FromArgSlots(Set::default()) } } @@ -243,7 +246,7 @@ impl<'a> ForwardState<'a> { fn add_heap_cell(&mut self) -> HeapCellId { self.heap_cells.push(ForwardData { origin: Origin::default(), - aliases: Set::new(), + aliases: Set::default(), }) } @@ -256,7 +259,6 @@ impl<'a> ForwardState<'a> { // TODO: Optimize this so that it does not traverse the whole parent chain when the heap // cell is guaranteed to not have any back ref annotations before a certain point (i.e., // when we have some information about when the heap cell was created). - // TODO: Remove query points from back ref sets when they are set to 'DirectTouch'. if back_ref_states[version].overlay.contains_key(&heap_cell) { return back_ref_states[version] .overlay @@ -267,7 +269,7 @@ impl<'a> ForwardState<'a> { &[parent] => Self::back_refs_in_states(back_ref_states, parent, heap_cell).clone(), parents => { let num_parents = parents.len(); - let mut back_refs = HashSet::new(); + let mut back_refs = HashSet::default(); for parent_i in 0..num_parents { let parent = back_ref_states[version].parents[parent_i]; let parent_back_refs = @@ -358,10 +360,9 @@ impl<'a> ForwardState<'a> { fn touch(&mut self, version: BackRefStateVersionId, heap_cell: HeapCellId) { let back_refs = std::mem::take(self.back_refs(version, heap_cell)); - for &query_point in &back_refs { + for query_point in back_refs.into_iter() { self.fates.insert(query_point, Fate::DirectTouch); } - *self.back_refs(version, heap_cell) = back_refs; } fn recursive_touch(&mut self, version: BackRefStateVersionId, heap_cells: &[HeapCellId]) { @@ -429,14 +430,14 @@ impl<'a> ForwardState<'a> { debug_assert_eq!(input_slot_arrs.len(), 1); let arg_slots = input_slot_arrs[0]; // TODO: optimize this entire case! - let mut heap_cell_slots = HashMap::>::new(); + let mut heap_cell_slots = HashMap::>::default(); for (slot_i, &heap_cell) in arg_slots.iter().enumerate() { heap_cell_slots .entry(heap_cell) .or_insert_with(SmallVec::new) .push(slot_i.try_into().unwrap()); } - let mut arg_aliases = HashSet::new(); + let mut arg_aliases = HashSet::default(); for (heap_cell, slot_indices) in &heap_cell_slots { // Wire up to occurrences of the same heap cell in the argument slots for (i, &slot_i) in slot_indices.iter().enumerate() { @@ -832,7 +833,7 @@ impl<'a> ForwardState<'a> { ) { let block_info = graph.blocks().block_info(block); let new_version = self.back_ref_states.push(BackRefState { - overlay: HashMap::new(), + overlay: HashMap::default(), parents: block_info .predecessors .iter() @@ -872,7 +873,7 @@ impl<'a> ForwardState<'a> { graph: &ir::Graph, blocks: impl Iterator, ) -> HeapCellSlotMapping { - let mut heap_cell_to_slots = HashMap::new(); + let mut heap_cell_to_slots = HashMap::default(); for block in blocks { for val_id in block_values_inclusive(graph, block) { for (i, &heap_cell) in self.value_slots[val_id].unwrap().iter().enumerate() { @@ -894,7 +895,7 @@ impl<'a> ForwardState<'a> { heap_cell_slots_inductive: &HeapCellSlotMapping, heap_cell_slots_current: &HeapCellSlotMapping, ) -> ForwardSccSummary { - let mut summary = ForwardSccSummary::new(); + let mut summary = ForwardSccSummary::default(); for block in blocks { for val_id in block_values_inclusive(graph, block) { let block_version = self.block_versions[block].unwrap(); @@ -904,9 +905,9 @@ impl<'a> ForwardState<'a> { .enumerate() .map(|(slot_i, &heap_cell)| { let mut val_summary = ForwardSccSlotSummary { - pre_aliases: Set::new(), - inductive_aliases: Set::new(), - internal_aliases: Set::new(), + pre_aliases: Set::default(), + inductive_aliases: Set::default(), + internal_aliases: Set::default(), back_refs: Self::back_refs_in_states( &mut self.back_ref_states, block_version, @@ -914,35 +915,49 @@ impl<'a> ForwardState<'a> { ) .clone(), }; + let matching_slot: (ir::ValueId, u32) = + (val_id, slot_i.try_into().unwrap()); + + // Iterators and cloning are used here. + // These iterators are small with cheap to clone items so it is fast and ok. let aliased_heap_cells = std::iter::once(heap_cell) .chain(self.heap_cells[heap_cell].aliases.iter().cloned()); - for aliased in aliased_heap_cells { - if aliased < min_new_id { - val_summary.pre_aliases.insert(aliased); - } - for &aliased_slot in heap_cell_slots_current - .get(&aliased) - .iter() - .cloned() - .flatten() - { - if aliased_slot == (val_id, slot_i.try_into().unwrap()) { - continue; - } - val_summary.internal_aliases.insert(aliased_slot); - } - for &aliased_slot in heap_cell_slots_inductive - .get(&aliased) - .iter() - .cloned() - .flatten() - { - if aliased_slot == (val_id, slot_i.try_into().unwrap()) { - continue; - } - val_summary.inductive_aliases.insert(aliased_slot); - } - } + let pre_aliases_iter = aliased_heap_cells + .clone() + .filter(|&aliased| aliased < min_new_id); + let internal_aliases_iter = aliased_heap_cells + .clone() + .flat_map(|aliased| { + heap_cell_slots_current.get(&aliased).into_iter().flatten() + }) + .cloned() + .filter(|&aliased_slot| aliased_slot == matching_slot); + let inductive_aliases_iter = aliased_heap_cells + .flat_map(|aliased| { + heap_cell_slots_inductive + .get(&aliased) + .into_iter() + .flatten() + }) + .cloned() + .filter(|&aliased_slot| aliased_slot == matching_slot); + + // These are all cheap to clone. + // Clone them and count them to avoid expensive resizing of HashSets. + let pre_aliases_count = pre_aliases_iter.clone().count(); + val_summary.pre_aliases.reserve(pre_aliases_count); + val_summary.pre_aliases.extend(pre_aliases_iter); + + let internal_aliases_count = internal_aliases_iter.clone().count(); + val_summary.internal_aliases.reserve(internal_aliases_count); + val_summary.internal_aliases.extend(internal_aliases_iter); + + let inductive_aliases_count = inductive_aliases_iter.clone().count(); + val_summary + .inductive_aliases + .reserve(inductive_aliases_count); + val_summary.inductive_aliases.extend(inductive_aliases_iter); + val_summary }) .collect(); @@ -993,7 +1008,7 @@ impl<'a> ForwardState<'a> { .into_iter() .collect::>(); let init_version = self.back_ref_states.push(BackRefState { - overlay: HashMap::new(), + overlay: HashMap::default(), parents: init_version_parents, }); @@ -1114,11 +1129,11 @@ impl<'a> ForwardState<'a> { .map(|arg_val_id| { let slot_count = id_result_slot_count(sc, graph, arg_val_id); let arg_slots: &[_] = slots_arena.alloc_extend((0..slot_count).map(|i| { - let mut origin_arg_slots = Set::new(); + let mut origin_arg_slots = Set::default(); origin_arg_slots.insert(i); heap_cells.push(ForwardData { origin: Origin::FromArgSlots(origin_arg_slots), - aliases: Set::new(), + aliases: Set::default(), }) })); if let Some(arg_alias) = arg_alias { @@ -1138,7 +1153,7 @@ impl<'a> ForwardState<'a> { .flatten() .enumerate() .map(|(slot_i, &heap_cell)| { - let mut heap_cell_back_refs = HashSet::new(); + let mut heap_cell_back_refs = HashSet::default(); heap_cell_back_refs.insert(QueryPoint::EntryArg(slot_i.try_into().unwrap())); (heap_cell, heap_cell_back_refs) }) @@ -1160,7 +1175,7 @@ impl<'a> ForwardState<'a> { block_versions: IdVec::filled_with(graph.blocks().block_count(), || None), block_versions_inductive: IdVec::filled_with(graph.blocks().block_count(), || None), entry_version, - fates: HashMap::new(), + fates: HashMap::default(), }; for scc_id in graph.sccs().count().iter() { @@ -1168,7 +1183,7 @@ impl<'a> ForwardState<'a> { } let exit_version = state.back_ref_states.push(BackRefState { - overlay: HashMap::new(), + overlay: HashMap::default(), parents: graph .exit_blocks() .iter() @@ -1242,7 +1257,7 @@ impl Default for Fate { fn default() -> Self { Fate::Other { indirect_touch: false, - ret_slots: Set::new(), + ret_slots: Set::default(), } } } @@ -1256,12 +1271,12 @@ fn analyze_func( let slots_arena = Arena::new(); let (forward, ret_slots) = ForwardState::analyze_graph(&slots_arena, sc, ctx, &func_def.graph, arg_alias); - let mut heap_cell_to_arg_slot = HashMap::::new(); + let mut heap_cell_to_arg_slot = HashMap::::default(); for (slot_i, &heap_cell) in forward.arg_slots.unwrap().iter().enumerate() { let existing = heap_cell_to_arg_slot.insert(heap_cell, slot_i.try_into().unwrap()); debug_assert!(existing.is_none()); } - let mut heap_cell_to_ret_slots = HashMap::>::new(); + let mut heap_cell_to_ret_slots = HashMap::>::default(); for (slot_i, &heap_cell) in ret_slots.iter().enumerate() { heap_cell_to_ret_slots .entry(heap_cell) @@ -1281,8 +1296,8 @@ fn analyze_func( .iter() .enumerate() .map(|(this_ret_slot_i, &heap_cell)| { - let mut arg_aliases = Set::new(); - let mut ret_aliases = Set::new(); + let mut arg_aliases = Set::default(); + let mut ret_aliases = Set::default(); for other in std::iter::once(heap_cell) .chain(forward.heap_cells[heap_cell].aliases.iter().cloned()) { @@ -1437,8 +1452,8 @@ impl<'a> GlobalAnalysisContext<'a> { let mut scc_ctx = SccAnalysisContext { global: &mut *self, scc, - prev_iter: HashMap::new(), - curr_iter: HashMap::new(), + prev_iter: HashMap::default(), + curr_iter: HashMap::default(), }; match scc_kind { SccKind::Acyclic => { @@ -1552,21 +1567,20 @@ struct Query { impl Query { fn to_spec(&self, func: FuncId) -> api::FuncSpec { - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - hasher.update(func.0.to_le_bytes()); - hasher.update((self.arg_aliases.len() as u64).to_le_bytes()); + let mut hasher = blake3::Hasher::new(); + hasher.update(&func.0.to_le_bytes()); + hasher.update(&(self.arg_aliases.len() as u64).to_le_bytes()); for arg_alias in &self.arg_aliases { - hasher.update(arg_alias.fst().to_le_bytes()); - hasher.update(arg_alias.snd().to_le_bytes()); + hasher.update(&arg_alias.fst().to_le_bytes()); + hasher.update(&arg_alias.snd().to_le_bytes()); } - hasher.update((self.arg_slots_touched.len() as u64).to_le_bytes()); + hasher.update(&(self.arg_slots_touched.len() as u64).to_le_bytes()); for &arg_touched in &self.arg_slots_touched { - hasher.update([arg_touched as u8]); + hasher.update(&[arg_touched as u8]); } - hasher.update((self.ret_slots_touched.len() as u64).to_le_bytes()); + hasher.update(&(self.ret_slots_touched.len() as u64).to_le_bytes()); for &ret_touched in &self.ret_slots_touched { - hasher.update([ret_touched as u8]); + hasher.update(&[ret_touched as u8]); } api::FuncSpec(hasher.finalize().into()) } @@ -1738,7 +1752,7 @@ pub(crate) fn analyze(tc: TypeCache, program: &ir::Program) -> ProgramSolutions func_defs: &program.funcs, sccs: &func_sccs, func_to_scc: &func_to_scc, - committed: IdVec::filled_with(program.funcs.count(), HashMap::new), + committed: IdVec::filled_with(program.funcs.count(), HashMap::default), }; for (_, &func) in &program.entry_points { @@ -1748,7 +1762,7 @@ pub(crate) fn analyze(tc: TypeCache, program: &ir::Program) -> ProgramSolutions } let mut func_solutions = FuncSolutions { - solutions: IdVec::filled_with(program.funcs.count(), HashMap::new), + solutions: IdVec::filled_with(program.funcs.count(), HashMap::default), }; let entry_point_solutions = program.entry_points.map(|_, &func| { @@ -1773,9 +1787,8 @@ pub(crate) fn analyze(tc: TypeCache, program: &ir::Program) -> ProgramSolutions // specialization and all update modes are `Immutable`: fn hash_func_id_trivial(func_id: FuncId) -> api::FuncSpec { - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - hasher.update(func_id.0.to_le_bytes()); + let mut hasher = blake3::Hasher::new(); + hasher.update(&func_id.0.to_le_bytes()); api::FuncSpec(hasher.finalize().into()) } diff --git a/crates/vendor/morphic_lib/src/api.rs b/crates/vendor/morphic_lib/src/api.rs index bd4f75de14..53b3335c12 100644 --- a/crates/vendor/morphic_lib/src/api.rs +++ b/crates/vendor/morphic_lib/src/api.rs @@ -1,7 +1,6 @@ // https://github.com/morphic-lang/morphic_lib/issues/19 #![allow(clippy::result_large_err)] -use sha2::{digest::Digest, Sha256}; use smallvec::SmallVec; use std::collections::{btree_map::Entry, BTreeMap}; use std::rc::Rc; @@ -1655,16 +1654,16 @@ pub fn solve_trivial(api_program: Program) -> Result { // TODO: Remove this; it's only used in obsolete logic for populating const definitions with trivial // solutions -fn hash_bstr(hasher: &mut Sha256, bstr: &[u8]) { +fn hash_bstr(hasher: &mut blake3::Hasher, bstr: &[u8]) { let header = (bstr.len() as u64).to_le_bytes(); - hasher.update(header); + hasher.update(&header); hasher.update(bstr); } // TODO: Remove this; it's only used in obsolete logic for populating const definitions with trivial // solutions fn hash_func_name(mod_: ModName, func: FuncName) -> FuncSpec { - let mut hasher = Sha256::new(); + let mut hasher = blake3::Hasher::new(); hash_bstr(&mut hasher, mod_.0); hash_bstr(&mut hasher, func.0); FuncSpec(hasher.finalize().into()) diff --git a/crates/vendor/morphic_lib/src/util/id_bi_map.rs b/crates/vendor/morphic_lib/src/util/id_bi_map.rs index 876574c49f..d8e46ef215 100644 --- a/crates/vendor/morphic_lib/src/util/id_bi_map.rs +++ b/crates/vendor/morphic_lib/src/util/id_bi_map.rs @@ -1,7 +1,10 @@ -use std::collections::hash_map::{Entry, HashMap}; +use std::collections::hash_map::Entry; use std::hash::Hash; use std::ops::Deref; +// use std::collections::HashMap; +use roc_collections::MutMap as HashMap; + use crate::util::id_type::Id; use crate::util::id_vec::IdVec; @@ -32,7 +35,7 @@ impl IdBiMap { pub fn new() -> Self { IdBiMap { key_to_val: IdVec::new(), - val_to_key: HashMap::new(), + val_to_key: HashMap::default(), } } diff --git a/crates/vendor/pretty/Cargo.toml b/crates/vendor/pretty/Cargo.toml index 7df7a3b737..78bf73d5b6 100644 --- a/crates/vendor/pretty/Cargo.toml +++ b/crates/vendor/pretty/Cargo.toml @@ -15,5 +15,5 @@ features = ["termcolor"] [dependencies] arrayvec = "0.7.2" -typed-arena = "2.0.1" -termcolor = { version = "1.1.2", optional = true } +typed-arena = "2.0.2" +termcolor = { version = "1.2.0", optional = true } diff --git a/crates/wasi-libc-sys/Cargo.toml b/crates/wasi-libc-sys/Cargo.toml index f7faff97fd..7bc52198fc 100644 --- a/crates/wasi-libc-sys/Cargo.toml +++ b/crates/wasi-libc-sys/Cargo.toml @@ -1,11 +1,12 @@ [package] -authors = ["The Roc Contributors"] description = "Rust wrapper for a WebAssembly test platform built on libc" -edition = "2021" -license = "UPL-1.0" name = "wasi_libc_sys" -repository = "https://github.com/roc-lang/roc" -version = "0.0.1" + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true [build-dependencies] -roc_utils = {path = "../utils"} \ No newline at end of file +roc_command_utils = { path = "../utils/command" } diff --git a/crates/wasi-libc-sys/build.rs b/crates/wasi-libc-sys/build.rs index 2aca3eadd2..1ef3bda1ea 100644 --- a/crates/wasi-libc-sys/build.rs +++ b/crates/wasi-libc-sys/build.rs @@ -1,4 +1,4 @@ -use roc_utils::zig; +use roc_command_utils::zig; use std::env; use std::ffi::OsString; use std::fs; diff --git a/crates/wasm_interp/Cargo.toml b/crates/wasm_interp/Cargo.toml index fa49a8a07d..c4a6c84dd5 100644 --- a/crates/wasm_interp/Cargo.toml +++ b/crates/wasm_interp/Cargo.toml @@ -1,18 +1,20 @@ [package] name = "roc_wasm_interp" -version = "0.1.0" -edition = "2021" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "A WebAssembly interpreter for testing the compiler." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [[bin]] name = "roc_wasm_interp" path = "src/main.rs" [dependencies] roc_wasm_module = { path = "../wasm_module" } -rand = "0.8.4" + bitvec.workspace = true bumpalo.workspace = true clap.workspace = true +rand.workspace = true diff --git a/crates/wasm_module/Cargo.toml b/crates/wasm_module/Cargo.toml index 315b9950d2..0ff4298cdd 100644 --- a/crates/wasm_module/Cargo.toml +++ b/crates/wasm_module/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "roc_wasm_module" -version = "0.0.1" -edition = "2021" -authors = ["The Roc Contributors"] -license = "UPL-1.0" description = "Parse, manipulate, and serialize WebAssembly modules." +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + [dependencies] roc_error_macros = { path = "../error_macros" } diff --git a/default.nix b/default.nix index 759223ab07..d1b8a7c621 100644 --- a/default.nix +++ b/default.nix @@ -22,9 +22,9 @@ rustPlatform.buildRustPackage { cargoLock = { lockFile = ./Cargo.lock; outputHashes = { - "confy-0.5.0" = "sha256-BVTczVbURL1Id/k/5ArlDQTZxLuI3XxQl7BdIx230U4="; + "confy-0.5.1" = "sha256-3PQdz9W/uJd4CaUZdwAd2u3JJ100SFAoKLCFE6THRZI="; "criterion-0.3.5" = "sha256-7REd3phV6PBzqWwKF8hwttw4FTq2tKGxxAAJDpLC50A="; - "inkwell-0.1.0" = "sha256-Sl50CW9H5IXV3k7BoAc0l2mv57lbrzhNxD0ub1AlOxM="; + "inkwell-0.1.0" = "sha256-1kpvY3naS33B99nuu5ZYhb7mdddAyG+DkbUl/RG1Ptg="; "plotters-0.3.1" = "sha256-noy/RSjoEPZZbOJTZw1yxGcX5S+2q/7mxnUrzDyxOFw="; "rustyline-9.1.1" = "sha256-aqQqz6nSp+Qn44gm3jXmmQUO6/fYTx7iLph2tbA24Bs="; }; @@ -65,7 +65,6 @@ rustPlatform.buildRustPackage { cargo makeWrapper # necessary for postBuild wrapProgram ] ++ lib.optionals pkgs.stdenv.isLinux [ - alsa-lib valgrind vulkan-headers vulkan-loader diff --git a/design/editor/design.md b/design/editor/design.md index 075dc9de80..d5b17fc2be 100644 --- a/design/editor/design.md +++ b/design/editor/design.md @@ -20,5 +20,5 @@ Should the editor organize all UI into a tree for easy altering/communication wi - It requires a lot less work to communicate with the compiler because we have a valid AST at all time. - Similarly, we never have to deal with partial expressions that have not been fully typed out yet. - The user never has to fiddle with formatting. -- It allows plugins to work with typed values instead of: a string that is connected with a typed value and where any changes to the typed value would have to produce a string that is sensibly formatted similar the formatting of the original string. +- It allows plugins to work with typed values instead of a string that is connected with a typed value and where any changes to the typed value would have to produce a string that is sensibly formatted similar the formatting of the original string. diff --git a/examples/.gitignore b/examples/.gitignore index 129a6c07dd..29accfafaa 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -2,7 +2,7 @@ libhost.a libapp.so dynhost -*.rm1 -*.rh1 +*.rm +*.rh helloWorld diff --git a/examples/cli/cli-platform/Cargo.lock b/examples/cli/cli-platform/Cargo.lock deleted file mode 100644 index 94e74cf1db..0000000000 --- a/examples/cli/cli-platform/Cargo.lock +++ /dev/null @@ -1,963 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[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 = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bumpalo" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[package]] -name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" - -[[package]] -name = "futures-io" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" - -[[package]] -name = "futures-sink" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" - -[[package]] -name = "futures-task" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" - -[[package]] -name = "futures-util" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" -dependencies = [ - "futures-core", - "futures-io", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "gimli" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" - -[[package]] -name = "h2" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "host" -version = "0.0.1" -dependencies = [ - "backtrace", - "libc", - "reqwest", - "roc_std", -] - -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" -dependencies = [ - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "ipnet" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" - -[[package]] -name = "itoa" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" - -[[package]] -name = "js-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.134" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.36.1", -] - -[[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 = "object" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "proc-macro2" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "reqwest" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "roc_std" -version = "0.0.1" -dependencies = [ - "arrayvec", - "static_assertions", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - -[[package]] -name = "rustls" -version = "0.20.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" -dependencies = [ - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" -dependencies = [ - "base64", -] - -[[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "serde" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" - -[[package]] -name = "serde_json" -version = "1.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -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 = "tokio" -version = "1.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "socket2", - "windows-sys 0.42.0", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls", - "tokio", - "webpki", -] - -[[package]] -name = "tokio-util" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" - -[[package]] -name = "web-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" -dependencies = [ - "webpki", -] - -[[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" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "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-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] diff --git a/examples/cli/cli-platform/Cargo.toml b/examples/cli/cli-platform/Cargo.toml index 294b712a9c..56bd77a63a 100644 --- a/examples/cli/cli-platform/Cargo.toml +++ b/examples/cli/cli-platform/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "host" -version = "0.0.1" authors = ["The Roc Contributors"] -license = "UPL-1.0" edition = "2021" +license = "UPL-1.0" +version = "0.0.1" links = "app" @@ -17,9 +17,9 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../crates/roc_std" } -libc = "0.2" backtrace = "0.3" -reqwest = { version="0.11.11", default-features=false, features=["blocking", "rustls-tls"] } +libc = "0.2" +reqwest = { version = "0.11.11", default-features = false, features = ["blocking", "rustls-tls"] } +roc_std = { path = "../../../crates/roc_std" } [workspace] diff --git a/examples/cli/cli-platform/EnvDecoding.roc b/examples/cli/cli-platform/EnvDecoding.roc index 62b25f3f9d..ccc4a9f2bb 100644 --- a/examples/cli/cli-platform/EnvDecoding.roc +++ b/examples/cli/cli-platform/EnvDecoding.roc @@ -19,6 +19,7 @@ EnvFormat := {} has [ string: envString, list: envList, record: envRecord, + tuple: envTuple, }, ] @@ -93,3 +94,10 @@ envList = \decodeElem -> Decode.custom \bytes, @EnvFormat {} -> envRecord : _, (_, _ -> [Keep (Decoder _ _), Skip]), (_ -> _) -> Decoder _ _ envRecord = \_initialState, _stepField, _finalizer -> Decode.custom \bytes, @EnvFormat {} -> { result: Err TooShort, rest: bytes } + +# TODO: we must currently annotate the arrows here so that the lambda sets are +# exercised, and the solver can find an ambient lambda set for the +# specialization. +envTuple : _, (_, _ -> [Next (Decoder _ _), TooLong]), (_ -> _) -> Decoder _ _ +envTuple = \_initialState, _stepElem, _finalizer -> Decode.custom \bytes, @EnvFormat {} -> + { result: Err TooShort, rest: bytes } diff --git a/examples/cli/cli-platform/src/lib.rs b/examples/cli/cli-platform/src/lib.rs index 257d1926f0..61b2036195 100644 --- a/examples/cli/cli-platform/src/lib.rs +++ b/examples/cli/cli-platform/src/lib.rs @@ -9,10 +9,9 @@ use core::mem::MaybeUninit; use glue::Metadata; use roc_std::{RocDict, RocList, RocResult, RocStr}; use std::borrow::{Borrow, Cow}; -use std::ffi::{CStr, OsStr}; +use std::ffi::{ OsStr}; use std::fs::File; use std::io::Write; -use std::os::raw::c_char; use std::path::Path; use std::time::Duration; @@ -23,17 +22,17 @@ extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] fn roc_main(output: *mut u8); - #[link_name = "roc__mainForHost_size"] + #[link_name = "roc__mainForHost_1_exposed_size"] fn roc_main_size() -> i64; - #[link_name = "roc__mainForHost_1__Fx_caller"] + #[link_name = "roc__mainForHost_0_caller"] fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); #[allow(dead_code)] - #[link_name = "roc__mainForHost_1__Fx_size"] + #[link_name = "roc__mainForHost_0_size"] fn size_Fx() -> i64; - #[link_name = "roc__mainForHost_1__Fx_result_size"] + #[link_name = "roc__mainForHost_0_result_size"] fn size_Fx_result() -> i64; } diff --git a/examples/cli/effects-platform/host.zig b/examples/cli/effects-platform/host.zig index 8920911a66..f28daaf745 100644 --- a/examples/cli/effects-platform/host.zig +++ b/examples/cli/effects-platform/host.zig @@ -25,10 +25,10 @@ const mem = std.mem; const Allocator = mem.Allocator; extern fn roc__mainForHost_1_exposed_generic([*]u8) void; -extern fn roc__mainForHost_size() i64; -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 roc__mainForHost_1_exposed_size() i64; +extern fn roc__mainForHost_0_caller(*const u8, [*]u8, [*]u8) void; +extern fn roc__mainForHost_0_size() i64; +extern fn roc__mainForHost_0_result_size() i64; const Align = 2 * @alignOf(usize); extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; @@ -123,7 +123,7 @@ pub export fn main() u8 { const stderr = std.io.getStdErr().writer(); // NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes - const size = std.math.max(8, @intCast(usize, roc__mainForHost_size())); + const size = std.math.max(8, @intCast(usize, roc__mainForHost_1_exposed_size())); const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; var output = @ptrCast([*]u8, raw_output); @@ -152,7 +152,7 @@ fn to_seconds(tms: std.os.timespec) f64 { fn call_the_closure(closure_data_pointer: [*]u8) void { const allocator = std.heap.page_allocator; - const size = roc__mainForHost_1__Fx_result_size(); + const size = roc__mainForHost_0_result_size(); if (size == 0) { // the function call returns an empty record @@ -160,7 +160,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void { // So it's special-cased const flags: u8 = 0; var result: [1]u8 = .{0}; - roc__mainForHost_1__Fx_caller(&flags, closure_data_pointer, &result); + roc__mainForHost_0_caller(&flags, closure_data_pointer, &result); return; } @@ -173,7 +173,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void { } const flags: u8 = 0; - roc__mainForHost_1__Fx_caller(&flags, closure_data_pointer, output); + roc__mainForHost_0_caller(&flags, closure_data_pointer, output); return; } diff --git a/examples/cli/false-interpreter/platform/Cargo.lock b/examples/cli/false-interpreter/platform/Cargo.lock deleted file mode 100644 index 3c874e3e30..0000000000 --- a/examples/cli/false-interpreter/platform/Cargo.lock +++ /dev/null @@ -1,37 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "host" -version = "0.0.1" -dependencies = [ - "libc", - "roc_std", -] - -[[package]] -name = "libc" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" - -[[package]] -name = "roc_std" -version = "0.0.1" -dependencies = [ - "arrayvec", - "static_assertions", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" diff --git a/examples/cli/false-interpreter/platform/Cargo.toml b/examples/cli/false-interpreter/platform/Cargo.toml index eeeb74f517..1cce894c1d 100644 --- a/examples/cli/false-interpreter/platform/Cargo.toml +++ b/examples/cli/false-interpreter/platform/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "host" -version = "0.0.1" authors = ["The Roc Contributors"] -license = "UPL-1.0" edition = "2021" +license = "UPL-1.0" +version = "0.0.1" links = "app" @@ -17,7 +17,7 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../../crates/roc_std" } libc = "0.2" +roc_std = { path = "../../../../crates/roc_std" } [workspace] diff --git a/examples/cli/false-interpreter/platform/src/lib.rs b/examples/cli/false-interpreter/platform/src/lib.rs index 897eaea4d2..c023206dcb 100644 --- a/examples/cli/false-interpreter/platform/src/lib.rs +++ b/examples/cli/false-interpreter/platform/src/lib.rs @@ -1,6 +1,5 @@ #![allow(non_snake_case)] -use core::alloc::Layout; use core::ffi::c_void; use core::mem::MaybeUninit; use libc; @@ -15,17 +14,17 @@ extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] fn roc_main(output: *mut u8, args: &RocStr); - #[link_name = "roc__mainForHost_size"] + #[link_name = "roc__mainForHost_1_exposed_size"] fn roc_main_size() -> i64; - #[link_name = "roc__mainForHost_1__Fx_caller"] + #[link_name = "roc__mainForHost_0_caller"] fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); #[allow(dead_code)] - #[link_name = "roc__mainForHost_1__Fx_size"] + #[link_name = "roc__mainForHost_0_size"] fn size_Fx() -> i64; - #[link_name = "roc__mainForHost_1__Fx_result_size"] + #[link_name = "roc__mainForHost_0_result_size"] fn size_Fx_result() -> i64; } @@ -109,11 +108,9 @@ pub extern "C" fn rust_main() -> i32 { let arg = RocStr::from(arg.as_str()); let size = unsafe { roc_main_size() } as usize; - let layout = Layout::array::(size).unwrap(); unsafe { - // TODO allocate on the stack if it's under a certain size - let buffer = std::alloc::alloc(layout); + let buffer = roc_alloc(size, 1) as *mut u8; roc_main(buffer, &arg); @@ -123,7 +120,7 @@ pub extern "C" fn rust_main() -> i32 { let result = call_the_closure(buffer); - std::alloc::dealloc(buffer, layout); + roc_dealloc(buffer as _, 1); result }; @@ -134,8 +131,7 @@ pub extern "C" fn rust_main() -> i32 { unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { let size = size_Fx_result() as usize; - let layout = Layout::array::(size).unwrap(); - let buffer = std::alloc::alloc(layout) as *mut u8; + let buffer = roc_alloc(size, 1) as *mut u8; call_Fx( // This flags pointer will never get dereferenced @@ -144,8 +140,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { buffer as *mut u8, ); - std::alloc::dealloc(buffer, layout); - + roc_dealloc(buffer as _, 1); 0 } @@ -176,14 +171,14 @@ pub extern "C" fn roc_fx_getChar() -> u8 { pub extern "C" fn roc_fx_putLine(line: &RocStr) { let string = line.as_str(); println!("{}", string); - std::io::stdout().lock().flush(); + let _ = std::io::stdout().lock().flush(); } #[no_mangle] pub extern "C" fn roc_fx_putRaw(line: &RocStr) { let string = line.as_str(); print!("{}", string); - std::io::stdout().lock().flush(); + let _ = std::io::stdout().lock().flush(); } #[no_mangle] @@ -212,7 +207,8 @@ pub extern "C" fn roc_fx_getFileBytes(br_ptr: *mut BufReader) -> RocList) { unsafe { - Box::from_raw(br_ptr); + let boxed = Box::from_raw(br_ptr); + drop(boxed) } } @@ -232,7 +228,7 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> *mut BufReader { } #[no_mangle] -pub extern "C" fn roc_fx_withFileOpen(name: &RocStr, buffer: *const u8) { +pub extern "C" fn roc_fx_withFileOpen(_name: &RocStr, _buffer: *const u8) { // TODO: figure out accepting a closure in an fx and passing data to it. // let f = File::open(name.as_str()).expect("Unable to open file"); // let mut br = BufReader::new(f); diff --git a/examples/cli/tui-platform/host.zig b/examples/cli/tui-platform/host.zig index 6c9028450f..30dbe4543c 100644 --- a/examples/cli/tui-platform/host.zig +++ b/examples/cli/tui-platform/host.zig @@ -32,12 +32,12 @@ extern fn roc__mainForHost_size() i64; const ConstModel = [*]const u8; const MutModel = [*]u8; -extern fn roc__mainForHost_1__Init_caller([*]u8, [*]u8, MutModel) void; -extern fn roc__mainForHost_1__Init_size() i64; -extern fn roc__mainForHost_1__Init_result_size() i64; +extern fn roc__mainForHost_0_caller([*]u8, [*]u8, MutModel) void; +extern fn roc__mainForHost_0_size() i64; +extern fn roc__mainForHost_0_result_size() i64; fn allocate_model(allocator: *Allocator) MutModel { - const size = roc__mainForHost_1__Init_result_size(); + const size = roc__mainForHost_0_result_size(); const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; var output = @ptrCast([*]u8, raw_output); @@ -48,33 +48,33 @@ fn init(allocator: *Allocator) ConstModel { const closure: [*]u8 = undefined; const output = allocate_model(allocator); - roc__mainForHost_1__Init_caller(closure, closure, output); + roc__mainForHost_0_caller(closure, closure, output); return output; } -extern fn roc__mainForHost_1__Update_caller(ConstModel, *const RocStr, [*]u8, MutModel) void; -extern fn roc__mainForHost_1__Update_size() i64; -extern fn roc__mainForHost_1__Update_result_size() i64; +extern fn roc__mainForHost_1_caller(ConstModel, *const RocStr, [*]u8, MutModel) void; +extern fn roc__mainForHost_1_size() i64; +extern fn roc__mainForHost_1_result_size() i64; fn update(allocator: *Allocator, model: ConstModel, msg: RocStr) ConstModel { const closure: [*]u8 = undefined; const output = allocate_model(allocator); - roc__mainForHost_1__Update_caller(model, &msg, closure, output); + roc__mainForHost_1_caller(model, &msg, closure, output); return output; } -extern fn roc__mainForHost_1__View_caller(ConstModel, [*]u8, *RocStr) void; -extern fn roc__mainForHost_1__View_size() i64; -extern fn roc__mainForHost_1__View_result_size() i64; +extern fn roc__mainForHost_2_caller(ConstModel, [*]u8, *RocStr) void; +extern fn roc__mainForHost_2_size() i64; +extern fn roc__mainForHost_2_result_size() i64; fn view(input: ConstModel) RocStr { const closure: [*]u8 = undefined; var output: RocStr = undefined; - roc__mainForHost_1__View_caller(input, closure, &output); + roc__mainForHost_2_caller(input, closure, &output); return output; } diff --git a/examples/glue/glue.roc b/examples/glue/glue.roc new file mode 100644 index 0000000000..f7515d5570 --- /dev/null +++ b/examples/glue/glue.roc @@ -0,0 +1,9 @@ +app "rocLovesRust" + packages { pf: "rust-platform/main.roc" } + imports [] + provides [main] to pf + +main = + StdoutWrite "Roc <3 Rust!\n" \{} -> + StdoutWrite "Roc <3 Rust!\n" \{} -> + Done diff --git a/examples/glue/rust-platform/.cargo/config b/examples/glue/rust-platform/.cargo/config new file mode 100644 index 0000000000..91cf6cf3f9 --- /dev/null +++ b/examples/glue/rust-platform/.cargo/config @@ -0,0 +1,2 @@ +[target.x86_64-pc-windows-gnu] +linker = "zig-cc" diff --git a/examples/platform-switching/rust-platform/Cargo.lock b/examples/glue/rust-platform/Cargo.lock similarity index 100% rename from examples/platform-switching/rust-platform/Cargo.lock rename to examples/glue/rust-platform/Cargo.lock diff --git a/examples/glue/rust-platform/Cargo.toml b/examples/glue/rust-platform/Cargo.toml new file mode 100644 index 0000000000..8d10ce6eea --- /dev/null +++ b/examples/glue/rust-platform/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "host" +version = "0.0.1" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2021" +links = "app" + +[lib] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" + +[dependencies] +roc_std = { path = "../../../crates/roc_std" } +libc = "0.2" + +[workspace] diff --git a/examples/glue/rust-platform/build.rs b/examples/glue/rust-platform/build.rs new file mode 100644 index 0000000000..47763b34c3 --- /dev/null +++ b/examples/glue/rust-platform/build.rs @@ -0,0 +1,9 @@ +fn main() { + #[cfg(not(windows))] + println!("cargo:rustc-link-lib=dylib=app"); + + #[cfg(windows)] + println!("cargo:rustc-link-lib=dylib=libapp"); + + println!("cargo:rustc-link-search=."); +} diff --git a/examples/glue/rust-platform/host.c b/examples/glue/rust-platform/host.c new file mode 100644 index 0000000000..b9214bcf33 --- /dev/null +++ b/examples/glue/rust-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/glue/rust-platform/main.roc b/examples/glue/rust-platform/main.roc new file mode 100644 index 0000000000..47017d926c --- /dev/null +++ b/examples/glue/rust-platform/main.roc @@ -0,0 +1,17 @@ +platform "echo-in-rust" + requires {} { main : _ } + exposes [] + packages {} + imports [] + provides [mainForHost] + +# mainForHost : [StdoutWrite Str (({} -> Op) as Fx0), StderrWrite Str (({} -> Op) as Fx1), Done] as Op +mainForHost : [StdoutWrite Str ({} -> Op), StderrWrite Str ({} -> Op), Done] as Op +mainForHost = main + +# mainForHost : { x: Str, y: {} -> Str } +# mainForHost = +# y = "foo" +# +# when main is +# _ -> { x: "bar", y: \{} -> y } diff --git a/examples/glue/rust-platform/rocLovesRust b/examples/glue/rust-platform/rocLovesRust new file mode 100755 index 0000000000..f06f59fc33 Binary files /dev/null and b/examples/glue/rust-platform/rocLovesRust differ diff --git a/examples/glue/rust-platform/rust-toolchain.toml b/examples/glue/rust-platform/rust-toolchain.toml new file mode 100644 index 0000000000..1932d9560f --- /dev/null +++ b/examples/glue/rust-platform/rust-toolchain.toml @@ -0,0 +1,9 @@ +[toolchain] +channel = "1.65.0" + +profile = "default" + +components = [ + # for usages of rust-analyzer or similar tools inside `nix develop` + "rust-src" +] \ No newline at end of file diff --git a/examples/glue/rust-platform/src/glue.rs b/examples/glue/rust-platform/src/glue.rs new file mode 100644 index 0000000000..e3e2e524b7 --- /dev/null +++ b/examples/glue/rust-platform/src/glue.rs @@ -0,0 +1,744 @@ +// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command + +#![allow(unused_unsafe)] +#![allow(unused_variables)] +#![allow(dead_code)] +#![allow(unused_mut)] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(clippy::undocumented_unsafe_blocks)] +#![allow(clippy::redundant_static_lifetimes)] +#![allow(clippy::unused_unit)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::let_and_return)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::redundant_static_lifetimes)] +#![allow(clippy::needless_borrow)] +#![allow(clippy::clone_on_copy)] + +type Op_StderrWrite = roc_std::RocStr; +type Op_StdoutWrite = roc_std::RocStr; +type TODO_roc_function_69 = roc_std::RocStr; +type TODO_roc_function_70 = roc_std::RocStr; + +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[repr(u8)] +pub enum discriminant_Op { + Done = 0, + StderrWrite = 1, + StdoutWrite = 2, +} + +impl core::fmt::Debug for discriminant_Op { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Done => f.write_str("discriminant_Op::Done"), + Self::StderrWrite => f.write_str("discriminant_Op::StderrWrite"), + Self::StdoutWrite => f.write_str("discriminant_Op::StdoutWrite"), + } + } +} + +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[repr(transparent)] +pub struct Op { + pointer: *mut union_Op, +} + +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[repr(C)] +union union_Op { + StderrWrite: core::mem::ManuallyDrop, + StdoutWrite: core::mem::ManuallyDrop, + _sizer: [u8; 8], +} + +#[cfg(any( + target_arch = "arm", + target_arch = "arm", + target_arch = "aarch64", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86", + target_arch = "x86_64", + target_arch = "x86_64" +))] +//TODO HAS CLOSURE 2 +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[repr(C)] +pub struct RocFunction_66 { + pub closure_data: roc_std::RocList, +} + +impl RocFunction_66 { + pub fn force_thunk(mut self, arg_0: ()) -> Op { + extern "C" { + fn roc__mainForHost_0_caller(arg_0: &(), closure_data: *mut u8, output: *mut Op); + } + + let mut output = std::mem::MaybeUninit::uninit(); + let ptr = self.closure_data.as_mut_ptr(); + unsafe { roc__mainForHost_0_caller(&arg_0, ptr, output.as_mut_ptr()) }; + unsafe { output.assume_init() } + } +} + +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[repr(C)] +pub struct RocFunction_67 { + pub closure_data: roc_std::RocList, +} + +impl RocFunction_67 { + pub fn force_thunk(mut self, arg_0: ()) -> Op { + extern "C" { + fn roc__mainForHost_1_caller(arg_0: &(), closure_data: *mut u8, output: *mut Op); + } + + let mut output = std::mem::MaybeUninit::uninit(); + let ptr = self.closure_data.as_mut_ptr(); + unsafe { roc__mainForHost_1_caller(&arg_0, ptr, output.as_mut_ptr()) }; + unsafe { output.assume_init() } + } +} + +impl Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + #[inline(always)] + fn storage(&self) -> Option<&core::cell::Cell> { + let mask = match std::mem::size_of::() { + 4 => 0b11, + 8 => 0b111, + _ => unreachable!(), + }; + + // NOTE: pointer provenance is probably lost here + let unmasked_address = (self.pointer as usize) & !mask; + let untagged = unmasked_address as *const core::cell::Cell; + + if untagged.is_null() { + None + } else { + unsafe { Some(&*untagged.sub(1)) } + } + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Returns which variant this tag union holds. Note that this never includes a payload! + pub fn discriminant(&self) -> discriminant_Op { + // The discriminant is stored in the unused bytes at the end of the recursive pointer + unsafe { core::mem::transmute::((self.pointer as u8) & 0b11) } + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Internal helper + fn tag_discriminant(pointer: *mut union_Op, discriminant: discriminant_Op) -> *mut union_Op { + // The discriminant is stored in the unused bytes at the end of the union pointer + let untagged = (pointer as usize) & (!0b11 as usize); + let tagged = untagged | (discriminant as usize); + + tagged as *mut union_Op + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Internal helper + fn union_pointer(&self) -> *mut union_Op { + // The discriminant is stored in the unused bytes at the end of the union pointer + ((self.pointer as usize) & (!0b11 as usize)) as *mut union_Op + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// A tag named Done, which has no payload. + pub const Done: Self = Self { + pointer: core::ptr::null_mut(), + }; + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 0. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn get_StderrWrite_0(&self) -> roc_std::RocStr { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + + extern "C" { + #[link_name = "roc__getter__2_generic"] + fn getter(_: *mut roc_std::RocStr, _: *const Op); + } + + let mut ret = core::mem::MaybeUninit::uninit(); + getter(ret.as_mut_ptr(), self); + ret.assume_init() + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 1. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn get_StderrWrite_1(&self) -> RocFunction_67 { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + + extern "C" { + #[link_name = "roc__getter__3_size"] + fn size() -> usize; + + #[link_name = "roc__getter__3_generic"] + fn getter(_: *mut u8, _: *const Op); + } + + // allocate memory to store this variably-sized value + // allocates with roc_alloc, but that likely still uses the heap + let it = std::iter::repeat(0xAAu8).take(size()); + let mut bytes = roc_std::RocList::from_iter(it); + + getter(bytes.as_mut_ptr(), self); + + RocFunction_67 { + closure_data: bytes, + } + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Construct a tag named `StderrWrite`, with the appropriate payload + pub fn StderrWrite(arg: Op_StderrWrite) -> Self { + let size = core::mem::size_of::(); + let align = core::mem::align_of::() as u32; + + unsafe { + let ptr = roc_std::roc_alloc_refcounted::(); + + *ptr = union_Op { + StderrWrite: core::mem::ManuallyDrop::new(arg), + }; + + Self { + pointer: Self::tag_discriminant(ptr, discriminant_Op::StderrWrite), + } + } + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and convert it to `StderrWrite`'s payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn into_StderrWrite(mut self) -> Op_StderrWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + let payload = { + let ptr = (self.pointer as usize & !0b11) as *mut union_Op; + let mut uninitialized = core::mem::MaybeUninit::uninit(); + let swapped = unsafe { + core::mem::replace( + &mut (*ptr).StderrWrite, + core::mem::ManuallyDrop::new(uninitialized.assume_init()), + ) + }; + + core::mem::forget(self); + + core::mem::ManuallyDrop::into_inner(swapped) + }; + + payload + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn as_StderrWrite(&self) -> &Op_StderrWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + let payload = { + let ptr = (self.pointer as usize & !0b11) as *mut union_Op; + + unsafe { &(*ptr).StderrWrite } + }; + + &payload + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 0. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn get_StdoutWrite_0(&self) -> roc_std::RocStr { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + + extern "C" { + #[link_name = "roc__getter__2_generic"] + fn getter(_: *mut roc_std::RocStr, _: *const Op); + } + + let mut ret = core::mem::MaybeUninit::uninit(); + getter(ret.as_mut_ptr(), self); + ret.assume_init() + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 1. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn get_StdoutWrite_1(&self) -> RocFunction_66 { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + + extern "C" { + #[link_name = "roc__getter__3_size"] + fn size() -> usize; + + #[link_name = "roc__getter__3_generic"] + fn getter(_: *mut u8, _: *const Op); + } + + // allocate memory to store this variably-sized value + // allocates with roc_alloc, but that likely still uses the heap + let it = std::iter::repeat(0xAAu8).take(size()); + let mut bytes = roc_std::RocList::from_iter(it); + + getter(bytes.as_mut_ptr(), self); + + RocFunction_66 { + closure_data: bytes, + } + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Construct a tag named `StdoutWrite`, with the appropriate payload + pub fn StdoutWrite(arg: Op_StdoutWrite) -> Self { + let size = core::mem::size_of::(); + let align = core::mem::align_of::() as u32; + + unsafe { + let ptr = roc_std::roc_alloc_refcounted::(); + + *ptr = union_Op { + StdoutWrite: core::mem::ManuallyDrop::new(arg), + }; + + Self { + pointer: Self::tag_discriminant(ptr, discriminant_Op::StdoutWrite), + } + } + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and convert it to `StdoutWrite`'s payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn into_StdoutWrite(mut self) -> Op_StdoutWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + let payload = { + let ptr = (self.pointer as usize & !0b11) as *mut union_Op; + let mut uninitialized = core::mem::MaybeUninit::uninit(); + let swapped = unsafe { + core::mem::replace( + &mut (*ptr).StdoutWrite, + core::mem::ManuallyDrop::new(uninitialized.assume_init()), + ) + }; + + core::mem::forget(self); + + core::mem::ManuallyDrop::into_inner(swapped) + }; + + payload + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn as_StdoutWrite(&self) -> &Op_StdoutWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + let payload = { + let ptr = (self.pointer as usize & !0b11) as *mut union_Op; + + unsafe { &(*ptr).StdoutWrite } + }; + + &payload + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Returns which variant this tag union holds. Note that this never includes a payload! + pub fn discriminant(&self) -> discriminant_Op { + // The discriminant is stored in the unused bytes at the end of the recursive pointer + unsafe { core::mem::transmute::((self.pointer as u8) & 0b111) } + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Internal helper + fn tag_discriminant(pointer: *mut union_Op, discriminant: discriminant_Op) -> *mut union_Op { + // The discriminant is stored in the unused bytes at the end of the union pointer + let untagged = (pointer as usize) & (!0b111 as usize); + let tagged = untagged | (discriminant as usize); + + tagged as *mut union_Op + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Internal helper + fn union_pointer(&self) -> *mut union_Op { + // The discriminant is stored in the unused bytes at the end of the union pointer + ((self.pointer as usize) & (!0b111 as usize)) as *mut union_Op + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and convert it to `StderrWrite`'s payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn into_StderrWrite(mut self) -> Op_StderrWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + let payload = { + let ptr = (self.pointer as usize & !0b111) as *mut union_Op; + let mut uninitialized = core::mem::MaybeUninit::uninit(); + let swapped = unsafe { + core::mem::replace( + &mut (*ptr).StderrWrite, + core::mem::ManuallyDrop::new(uninitialized.assume_init()), + ) + }; + + core::mem::forget(self); + + core::mem::ManuallyDrop::into_inner(swapped) + }; + + payload + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn as_StderrWrite(&self) -> &Op_StderrWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + let payload = { + let ptr = (self.pointer as usize & !0b111) as *mut union_Op; + + unsafe { &(*ptr).StderrWrite } + }; + + &payload + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and convert it to `StdoutWrite`'s payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn into_StdoutWrite(mut self) -> Op_StdoutWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + let payload = { + let ptr = (self.pointer as usize & !0b111) as *mut union_Op; + let mut uninitialized = core::mem::MaybeUninit::uninit(); + let swapped = unsafe { + core::mem::replace( + &mut (*ptr).StdoutWrite, + core::mem::ManuallyDrop::new(uninitialized.assume_init()), + ) + }; + + core::mem::forget(self); + + core::mem::ManuallyDrop::into_inner(swapped) + }; + + payload + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn as_StdoutWrite(&self) -> &Op_StdoutWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + let payload = { + let ptr = (self.pointer as usize & !0b111) as *mut union_Op; + + unsafe { &(*ptr).StdoutWrite } + }; + + &payload + } +} + +impl Drop for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn drop(&mut self) { + // We only need to do any work if there's actually a heap-allocated payload. + if let Some(storage) = self.storage() { + let mut new_storage = storage.get(); + + // Decrement the refcount + let needs_dealloc = !new_storage.is_readonly() && new_storage.decrease(); + + if needs_dealloc { + // Drop the payload first. + match self.discriminant() { + discriminant_Op::Done => {} + discriminant_Op::StderrWrite => unsafe { + core::mem::ManuallyDrop::drop(&mut (&mut *self.union_pointer()).StderrWrite) + }, + discriminant_Op::StdoutWrite => unsafe { + core::mem::ManuallyDrop::drop(&mut (&mut *self.union_pointer()).StdoutWrite) + }, + } + + // Dealloc the pointer + let alignment = + core::mem::align_of::().max(core::mem::align_of::()); + + unsafe { + crate::roc_dealloc(storage.as_ptr().cast(), alignment as u32); + } + } else { + // Write the storage back. + storage.set(new_storage); + } + } + } +} + +impl Eq for Op {} + +impl PartialEq for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn eq(&self, other: &Self) -> bool { + if self.discriminant() != other.discriminant() { + return false; + } + + unsafe { + match self.discriminant() { + discriminant_Op::Done => true, + discriminant_Op::StderrWrite => { + (&*self.union_pointer()).StderrWrite == (&*other.union_pointer()).StderrWrite + } + discriminant_Op::StdoutWrite => { + (&*self.union_pointer()).StdoutWrite == (&*other.union_pointer()).StdoutWrite + } + } + } + } +} + +impl PartialOrd for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn partial_cmp(&self, other: &Self) -> Option { + match self.discriminant().partial_cmp(&other.discriminant()) { + Some(core::cmp::Ordering::Equal) => {} + not_eq => return not_eq, + } + + unsafe { + match self.discriminant() { + discriminant_Op::Done => Some(core::cmp::Ordering::Equal), + discriminant_Op::StderrWrite => (&*self.union_pointer()) + .StderrWrite + .partial_cmp(&(&*other.union_pointer()).StderrWrite), + discriminant_Op::StdoutWrite => (&*self.union_pointer()) + .StdoutWrite + .partial_cmp(&(&*other.union_pointer()).StdoutWrite), + } + } + } +} + +impl Ord for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + match self.discriminant().cmp(&other.discriminant()) { + core::cmp::Ordering::Equal => {} + not_eq => return not_eq, + } + + unsafe { + match self.discriminant() { + discriminant_Op::Done => core::cmp::Ordering::Equal, + discriminant_Op::StderrWrite => (&*self.union_pointer()) + .StderrWrite + .cmp(&(&*other.union_pointer()).StderrWrite), + discriminant_Op::StdoutWrite => (&*self.union_pointer()) + .StdoutWrite + .cmp(&(&*other.union_pointer()).StdoutWrite), + } + } + } +} + +impl Clone for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn clone(&self) -> Self { + if let Some(storage) = self.storage() { + let mut new_storage = storage.get(); + if !new_storage.is_readonly() { + new_storage.increment_reference_count(); + storage.set(new_storage); + } + } + + Self { + pointer: self.pointer, + } + } +} + +impl core::hash::Hash for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn hash(&self, state: &mut H) { + match self.discriminant() { + discriminant_Op::Done => discriminant_Op::Done.hash(state), + discriminant_Op::StderrWrite => unsafe { + discriminant_Op::StderrWrite.hash(state); + (&*self.union_pointer()).StderrWrite.hash(state); + }, + discriminant_Op::StdoutWrite => unsafe { + discriminant_Op::StdoutWrite.hash(state); + (&*self.union_pointer()).StdoutWrite.hash(state); + }, + } + } +} + +impl core::fmt::Debug for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Op::")?; + + unsafe { + match self.discriminant() { + discriminant_Op::Done => f.write_str("Done"), + discriminant_Op::StderrWrite => f + .debug_tuple("StderrWrite") + // TODO HAS CLOSURE + .finish(), + discriminant_Op::StdoutWrite => f + .debug_tuple("StdoutWrite") + // TODO HAS CLOSURE + .finish(), + } + } + } +} diff --git a/examples/glue/rust-platform/src/lib.rs b/examples/glue/rust-platform/src/lib.rs new file mode 100644 index 0000000000..44b0405a05 --- /dev/null +++ b/examples/glue/rust-platform/src/lib.rs @@ -0,0 +1,132 @@ +#![allow(non_snake_case)] + +mod glue; + +use core::ffi::c_void; +use glue::Op; +use roc_std::RocStr; +use std::ffi::CStr; +use std::io::Write; +use std::mem::MaybeUninit; +use std::os::raw::c_char; + +extern "C" { + #[link_name = "roc__mainForHost_1_exposed_generic"] + fn roc_main(_: *mut Op); +} + +#[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) +} + +#[cfg(unix)] +#[no_mangle] +pub unsafe extern "C" fn roc_getppid() -> libc::pid_t { + libc::getppid() +} + +#[cfg(unix)] +#[no_mangle] +pub unsafe extern "C" fn roc_mmap( + addr: *mut libc::c_void, + len: libc::size_t, + prot: libc::c_int, + flags: libc::c_int, + fd: libc::c_int, + offset: libc::off_t, +) -> *mut libc::c_void { + libc::mmap(addr, len, prot, flags, fd, offset) +} + +#[cfg(unix)] +#[no_mangle] +pub unsafe extern "C" fn roc_shm_open( + name: *const libc::c_char, + oflag: libc::c_int, + mode: libc::mode_t, +) -> libc::c_int { + libc::shm_open(name, oflag, mode as libc::c_uint) +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + use glue::discriminant_Op::*; + + println!("Let's do things!"); + + let mut op: Op = unsafe { + let mut mem = MaybeUninit::uninit(); + + roc_main(mem.as_mut_ptr()); + + mem.assume_init() + }; + + loop { + match dbg!(op.discriminant()) { + StdoutWrite => { + let output: RocStr = unsafe { op.get_StdoutWrite_0() }; + op = unsafe { op.get_StdoutWrite_1().force_thunk(()) }; + + if let Err(e) = std::io::stdout().write_all(output.as_bytes()) { + panic!("Writing to stdout failed! {:?}", e); + } + } + StderrWrite => { + let output: RocStr = unsafe { op.get_StderrWrite_0() }; + op = unsafe { op.get_StderrWrite_1().force_thunk(()) }; + + if let Err(e) = std::io::stderr().write_all(output.as_bytes()) { + panic!("Writing to stdout failed! {:?}", e); + } + } + Done => { + break; + } + } + } + + println!("Done!"); + + // Exit code + 0 +} diff --git a/examples/glue/rust-platform/src/main.rs b/examples/glue/rust-platform/src/main.rs new file mode 100644 index 0000000000..0765384f29 --- /dev/null +++ b/examples/glue/rust-platform/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(host::rust_main() as _); +} diff --git a/examples/gui/breakout/hello.roc b/examples/gui/breakout/hello-gui.roc similarity index 96% rename from examples/gui/breakout/hello.roc rename to examples/gui/breakout/hello-gui.roc index 0e05e50705..86e97a9f4d 100644 --- a/examples/gui/breakout/hello.roc +++ b/examples/gui/breakout/hello-gui.roc @@ -1,4 +1,4 @@ -app "breakout" +app "hello-gui" packages { pf: "platform/main.roc" } imports [pf.Game.{ Bounds, Elem, Event }] provides [program] { Model } to pf diff --git a/examples/gui/breakout/platform/Cargo.lock b/examples/gui/breakout/platform/Cargo.lock deleted file mode 100644 index cc44dde1c7..0000000000 --- a/examples/gui/breakout/platform/Cargo.lock +++ /dev/null @@ -1,2799 +0,0 @@ -# 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 = "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.2", -] - -[[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.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[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.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[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.2", - "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.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" -dependencies = [ - "cfg-if 1.0.0", -] - -[[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 = "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.0.1" -dependencies = [ - "arrayvec", - "bytemuck", - "cgmath", - "colored", - "confy", - "copypasta", - "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 = "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset", -] - -[[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.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[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.0.1" -dependencies = [ - "arrayvec", - "static_assertions", -] - -[[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 = "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 0.1.10", - "rand", - "static_assertions", -] - -[[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/gui/breakout/platform/Cargo.toml b/examples/gui/breakout/platform/Cargo.toml index 5b612df452..8590583a5b 100644 --- a/examples/gui/breakout/platform/Cargo.toml +++ b/examples/gui/breakout/platform/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "host" -version = "0.0.1" authors = ["The Roc Contributors"] -license = "UPL-1.0" edition = "2021" +license = "UPL-1.0" +version = "0.0.1" [lib] name = "host" @@ -15,32 +15,28 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../../crates/roc_std" } -libc = "0.2" arrayvec = "0.7.2" +libc = "0.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" } +roc_std = { path = "../../../../crates/roc_std" } +cgmath = "0.18.0" +colored = "2.0.0" +confy = { git = 'https://github.com/rust-cli/confy', features = ["yaml_conf"], default-features = false } +copypasta = "0.7.1" +fs_extra = "1.2.0" +futures = "0.3.17" glyph_brush = "0.7.2" log = "0.4.14" -futures = "0.3.17" -cgmath = "0.18.0" -snafu = { version = "0.6.10", features = ["backtraces"] } -colored = "2.0.0" +nonempty = "0.7.0" +palette = "0.6.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 +snafu = { version = "0.6.10", features = ["backtraces"] } threadpool = "1.8.1" +wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" } +wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" } +winit = "0.26.1" [package.metadata.cargo-udeps.ignore] # confy is currently unused but should not be removed @@ -50,7 +46,6 @@ normal = ["confy"] [features] default = [] -with_sound = ["rodio"] [dependencies.bytemuck] version = "1.7.2" diff --git a/examples/gui/breakout/platform/src/lib.rs b/examples/gui/breakout/platform/src/lib.rs index 196a65c017..51378f8ddc 100644 --- a/examples/gui/breakout/platform/src/lib.rs +++ b/examples/gui/breakout/platform/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + mod graphics; mod gui; mod roc; diff --git a/examples/gui/breakout/platform/src/roc.rs b/examples/gui/breakout/platform/src/roc.rs index aecfd0427d..a203065c94 100644 --- a/examples/gui/breakout/platform/src/roc.rs +++ b/examples/gui/breakout/platform/src/roc.rs @@ -13,26 +13,26 @@ use winit::event::VirtualKeyCode; extern "C" { // program - #[link_name = "roc__programForHost_1_exposed_generic"] - fn roc_program(); + // #[link_name = "roc__programForHost_1_exposed_generic"] + // fn roc_program(); - #[link_name = "roc__programForHost_size"] - fn roc_program_size() -> i64; + // #[link_name = "roc__programForHost_1_exposed_size"] + // fn roc_program_size() -> i64; // init - #[link_name = "roc__programForHost_1__Init_caller"] + #[link_name = "roc__programForHost_0_caller"] fn call_init(size: *const Bounds, closure_data: *const u8, output: *mut Model); - #[link_name = "roc__programForHost_1__Init_size"] + #[link_name = "roc__programForHost_0_size"] fn init_size() -> i64; - #[link_name = "roc__programForHost_1__Init_result_size"] + #[link_name = "roc__programForHost_0_result_size"] fn init_result_size() -> i64; // update - #[link_name = "roc__programForHost_1__Update_caller"] + #[link_name = "roc__programForHost_1_caller"] fn call_update( model: *const Model, event: *const RocEvent, @@ -40,18 +40,18 @@ extern "C" { output: *mut Model, ); - #[link_name = "roc__programForHost_1__Update_size"] + #[link_name = "roc__programForHost_1_size"] fn update_size() -> i64; - #[link_name = "roc__programForHost_1__Update_result_size"] + #[link_name = "roc__programForHost_1_result_size"] fn update_result_size() -> i64; // render - #[link_name = "roc__programForHost_1__Render_caller"] + #[link_name = "roc__programForHost_2_caller"] fn call_render(model: *const Model, closure_data: *const u8, output: *mut RocList); - #[link_name = "roc__programForHost_1__Render_size"] + #[link_name = "roc__programForHost_2_size"] fn roc_render_size() -> i64; } diff --git a/examples/gui/platform/Cargo.lock b/examples/gui/platform/Cargo.lock deleted file mode 100644 index 453ced75e3..0000000000 --- a/examples/gui/platform/Cargo.lock +++ /dev/null @@ -1,2800 +0,0 @@ -# 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 = "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.2", -] - -[[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.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[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.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" - -[[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.2", - "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 = "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.0.1" -dependencies = [ - "arrayvec", - "bytemuck", - "cgmath", - "colored", - "confy", - "copypasta", - "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 = "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset", -] - -[[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 = [ - "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.0.1" -dependencies = [ - "arrayvec", - "static_assertions", -] - -[[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 = "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 0.1.10", - "rand", - "static_assertions", -] - -[[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/gui/platform/Cargo.toml b/examples/gui/platform/Cargo.toml index ec1ec5fad1..0ac7d2812e 100644 --- a/examples/gui/platform/Cargo.toml +++ b/examples/gui/platform/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "host" -version = "0.0.1" authors = ["The Roc Contributors"] -license = "UPL-1.0" edition = "2021" +license = "UPL-1.0" links = "app" +version = "0.0.1" [lib] name = "host" @@ -16,32 +16,28 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../crates/roc_std" } -libc = "0.2" arrayvec = "0.7.2" +libc = "0.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" } +roc_std = { path = "../../../crates/roc_std" } +cgmath = "0.18.0" +colored = "2.0.0" +confy = { git = 'https://github.com/rust-cli/confy', features = ["yaml_conf"], default-features = false } +copypasta = "0.7.1" +fs_extra = "1.2.0" +futures = "0.3.17" glyph_brush = "0.7.2" log = "0.4.14" -futures = "0.3.17" -cgmath = "0.18.0" -snafu = { version = "0.6.10", features = ["backtraces"] } -colored = "2.0.0" +nonempty = "0.7.0" +palette = "0.6.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 +snafu = { version = "0.6.10", features = ["backtraces"] } threadpool = "1.8.1" +wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" } +wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" } +winit = "0.26.1" [package.metadata.cargo-udeps.ignore] # confy is currently unused but should not be removed @@ -51,20 +47,19 @@ normal = ["confy"] [features] default = [] -with_sound = ["rodio"] [dependencies.bytemuck] -version = "1.7.2" features = ["derive"] +version = "1.7.2" [workspace] # Optimizations based on https://deterministic.space/high-performance-rust.html [profile.release] -lto = "thin" codegen-units = 1 +lto = "thin" # debug = true # enable when profiling [profile.bench] -lto = "thin" codegen-units = 1 +lto = "thin" diff --git a/examples/helloWorld.roc b/examples/helloWorld.roc index 3996a19bb5..0422062cd7 100644 --- a/examples/helloWorld.roc +++ b/examples/helloWorld.roc @@ -1,5 +1,5 @@ app "helloWorld" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" } imports [pf.Stdout] provides [main] to pf diff --git a/examples/jvm-interop/.gitignore b/examples/jvm-interop/.gitignore new file mode 100644 index 0000000000..28b74819c0 --- /dev/null +++ b/examples/jvm-interop/.gitignore @@ -0,0 +1,7 @@ +*.log +*.class +*.o +*.so +*.jar +libhello* +*.h diff --git a/examples/jvm-interop/README.md b/examples/jvm-interop/README.md new file mode 100644 index 0000000000..0a014226cb --- /dev/null +++ b/examples/jvm-interop/README.md @@ -0,0 +1,210 @@ +# JVM interop +This is a demo for calling Roc code from Java, and some other JVM languages. + + +## Prerequisites + +The following was tested on NixOS, with `openjdk 17.0.5` and `clang 13.0.1` but should work with most recent versions of those (jdk>=10) on most modern Linux and MacOS.\ +You're welcome to test on your machine and tell me (via [Zulip](https://roc.zulipchat.com/#narrow/pm-with/583319-dank)) if you ran into any issues or limitations. + +## Goal +We'll make a few examples, showing basic data type convertions and function calls between Roc and Java (and later some other JVM languages): +- A string formatter. +- A Function that multiples an array by a scalar. +- A factorial function that, for the sake of demonstration, throws a RuntimeException for negative integers. + +This will be done with the help of [Java Native Interface](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/). +We will be using C to bridge between Java and Roc. + +## Structure +As the time of writing this post, the following is the current bare bones tree of a jvm-interop: + +``` console +. +├── impl.roc +├── platform.roc +├── bridge.c +└── javaSource + └── Demo.java +``` + +impl.roc is the application where we actually implement our native Roc functions.\ +platform.roc as the name suggests contains platform logic, (but doesn't really have much here, mostly just) exposes functions to the host - bridge.c\ +bridge.c is the JNI bridge, it's the host that implements the Roc functions (e.g roc_alloc) and the JNI functions that act like the bridge between Roc and Java (bridge as in, doing type conversions between the languages, needed jvm boilerplate, etc). + +For each of our native Roc functions, in the application (impl.roc), we have a corresponding `Java_javaSource_Demo_FUNC` C function that handles the "behind the scenes", this includes type conversions between the languages, transforming roc panics into java exceptions and basically all the glue code necessary. + + +Just so you know what to expect, our Roc functions look like this; +``` coffee +interpolateString : Str -> Str +interpolateString = \name -> + "Hello from Roc \(name)!!!🤘🤘🤘" + + +mulArrByScalar : List I32, I32 -> List I32 +mulArrByScalar = \arr, scalar -> + List.map arr \x -> x * scalar + + +factorial : I64 -> I64 +factorial = \n -> + if n < 0 then + # while we get the chance, examplify a roc panic in an interop + crash "No negatives here!!!" + else if n == 0 then + 1 + else + n * (factorial (n - 1)) +``` + +Nothing too crazy. Again, do note how we crash if n < 0, see how this would play out from the Java side. + +Now let's take a quick look on the Java side of things; + +``` java +public class Demo { + + static { + System.loadLibrary("interop"); + } + + public static native String sayHello(String num); + + public static native int[] mulArrByScalar(int[] arr, int scalar); + + public static native long factorial(long n) throws RuntimeException; + + + public static void main(String[] args) { + + // string demo + System.out.println(sayHello("Brendan") + "\n"); + + // array demo + int[] arr = {10, 20, 30, 40}; + int x = 3; + System.out.println(Arrays.toString(arr) + + " multiplied by " + x + + " results in " + Arrays.toString(mulArrByScalar(arr, x)) + + "\n"); + + // number + panic demo + long n = 5; + System.out.println("Factorial of " + n + " is " + factorial(n)); + + } +} +``` +First we load our library - "interop", which is a shared library (`.so` file) that our Roc+C code compiles to.\ +Then, we declare our native functions with suitable types and throws annotation.\ +Finally in main we test it out with some inputs. + +## See it in action +##### For brevity's sake we'll run the build script and omit some of its (intentionally) verbose output: + +```console +[nix-shell:~/dev/roc/examples/jvm-interop]$ ./build.sh && java javaSource.Greeter +Hello from Roc Brendan!!!🤘🤘🤘 + +[10, 20, 30, 40] multiplied by 3 results in [30, 60, 90, 120] + +Factorial of 5 is 120 +``` +That's pretty cool!\ +Let's also see what happens if in the code above we define n to be -1: +``` console +[nix-shell:~/dev/roc/examples/jvm-interop]$ ./build.sh && java javaSource.Greeter +Hello from Roc Brendan!!!🤘🤘🤘 + +[10, 20, 30, 40] multiplied by 3 results in [30, 60, 90, 120] + +Exception in thread "main" java.lang.RuntimeException: No negatives here!!! + at javaSource.Demo.factorial(Native Method) + at javaSource.Demo.main(Demo.java:36) +``` +And as we expected, it runs the first two examples fine, throws a RuntimeException on the third. + +Since we're talking JVM Bytecode, we can pretty much call our native function from any language that speaks JVM Bytecode. + +Note: The JNI code depends on a dynamic lib, containing our native implementation, that now resides in our working directory.\ +So in the following examples, we'll make sure that our working directory is in LD_LIBRARY_PATH.\ +Generally speaking, you'd paobably add your dynamic library to a spot that's already on your path, for convenience sake.\ +So first, we run: + +```console +[nix-shell:~/dev/roc/examples/jvm-interop]$ export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH +``` + +Now, let's try Kotlin! +```console +[nix-shell:~/dev/roc/examples/jvm-interop]$ kotlin +Welcome to Kotlin version 1.7.20 (JRE 17.0.5+8-nixos) +Type :help for help, :quit for quit + +>>> import javaSource.Demo + +>>> Demo.sayHello("Kotlin Users") +res1: kotlin.String = Hello from Roc Kotlin Users!!!🤘🤘🤘 + +>>> Demo.mulArrByScalar(intArrayOf(10, 20, 30, 40), 101).contentToString() +res2: kotlin.String = [1010, 2020, 3030, 4040] + +>>> Demo.factorial(10) +res3: kotlin.Long = 3628800 +``` +And it just works, out of the box! + +Now let's do Scala + +```console +[nix-shell:~/dev/roc/examples/jvm-interop]$ scala +Welcome to Scala 2.13.10 (OpenJDK 64-Bit Server VM, Java 17.0.5). +Type in expressions for evaluation. Or try :help. + +scala> import javaSource.Demo +import javaSource.Demo + +scala> Demo.sayHello("Scala Users") +val res0: String = Hello from Roc Scala Users!!!🤘🤘🤘 + +scala> Demo.mulArrByScalar(Array(10, 20, 30, 40), 1001) +val res1: Array[Int] = Array(10010, 20020, 30030, 40040) + +scala> Demo.factorial(-2023) +java.lang.RuntimeException: No negatives here!!! + at javaSource.Demo.factorial(Native Method) + ... 32 elided +``` +And it also works beautifully. + +Last one - Clojure +Do note that in Clojure you need to add a `-Sdeps '{:paths ["."]}'` flag to add the working directory to paths. +``` console +[nix-shell:~/dev/roc/examples/jvm-interop]$ clj -Sdeps '{:paths ["."]}' +Clojure 1.11.1 +user=> (import 'javaSource.Demo) +javaSource.Demo + +user=> (Demo/sayHello "Clojure Users") +"Hello from Roc Clojure Users!!!🤘🤘🤘" + +user=> (seq (Demo/mulArrByScalar (int-array [10 20 30]) 9)) ; seq to pretty-print +(90 180 270) + +user=> (Demo/factorial 15) +1307674368000 +``` + +Test it out on your favorite JVM lang!\ +And again, if anything goes not according to plan, tell me in the link above and we'll figure it out. + +## Notes on building +The process is basically the following: +1. Build our application + platform .roc files with (`roc build impl.roc --no-link`) into an object file +2. Generate a C header file (for bridge.c's) using java. +3. Bundle up the C bridge together with our object file into a shared object. + +And that's it, use that shared object from your JVM language. Note every JVM language has its own way to declare that native library so you may want to look at it, or do like in the demo and declare it in java and use the binding from anywhere. + +I suggest reading the build script (build.sh) and adjusting according to your setup. diff --git a/examples/jvm-interop/bridge.c b/examples/jvm-interop/bridge.c new file mode 100644 index 0000000000..7049f80d9c --- /dev/null +++ b/examples/jvm-interop/bridge.c @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "javaSource_Demo.h" + +JavaVM* vm; + +#define ERR_MSG_MAX_SIZE 256 + +jmp_buf exception_buffer; +char* err_msg[ERR_MSG_MAX_SIZE] = {0}; + +jint JNI_OnLoad(JavaVM *loadedVM, void *reserved) +{ + // https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html + vm = loadedVM; + return JNI_VERSION_1_2; +} + +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_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); +} + +// Reference counting + +// If the refcount is set to this, that means the allocation is +// stored in readonly memory in the binary, and we must not +// attempt to increment or decrement it; if we do, we'll segfault! +const ssize_t REFCOUNT_READONLY = 0; +const ssize_t REFCOUNT_ONE = (ssize_t)PTRDIFF_MIN; +const size_t MASK = (size_t)PTRDIFF_MIN; + +// Increment reference count, given a pointer to the first element in a collection. +// We don't need to check for overflow because in order to overflow a usize worth of refcounts, +// you'd need to somehow have more pointers in memory than the OS's virtual address space can hold. +void incref(uint8_t* bytes, uint32_t alignment) +{ + ssize_t *refcount_ptr = ((ssize_t *)bytes) - 1; + ssize_t refcount = *refcount_ptr; + + if (refcount != REFCOUNT_READONLY) { + *refcount_ptr = refcount + 1; + } +} + +// Decrement reference count, given a pointer to the first element in a collection. +// Then call roc_dealloc if nothing is referencing this collection anymore. +void decref(uint8_t* bytes, uint32_t alignment) +{ + if (bytes == NULL) { + return; + } + + size_t extra_bytes = (sizeof(size_t) >= (size_t)alignment) ? sizeof(size_t) : (size_t)alignment; + ssize_t *refcount_ptr = ((ssize_t *)bytes) - 1; + ssize_t refcount = *refcount_ptr; + + if (refcount != REFCOUNT_READONLY) { + *refcount_ptr = refcount - 1; + + if (refcount == REFCOUNT_ONE) { + void *original_allocation = (void *)(refcount_ptr - (extra_bytes - sizeof(size_t))); + + roc_dealloc(original_allocation, alignment); + } + } +} + +struct RocListI32 +{ + int32_t *bytes; + size_t len; + size_t capacity; +}; + +struct RocListI32 init_roclist_i32(int32_t *bytes, size_t len) +{ + if (len == 0) + { + struct RocListI32 ret = { + .len = 0, + .bytes = NULL, + .capacity = 0, + }; + + return ret; + } + else + { + size_t refcount_size = sizeof(size_t); + ssize_t* data = (ssize_t*)roc_alloc(len + refcount_size, alignof(size_t)); + data[0] = REFCOUNT_ONE; + int32_t *new_content = (int32_t *)(data + 1); + + struct RocListI32 ret; + + memcpy(new_content, bytes, len * sizeof(int32_t)); + + ret.bytes = new_content; + ret.len = len; + ret.capacity = len; + + return ret; + } +} +// RocListU8 (List U8) + +struct RocListU8 +{ + uint8_t *bytes; + size_t len; + size_t capacity; +}; + +struct RocListU8 init_roclist_u8(uint8_t *bytes, size_t len) +{ + if (len == 0) + { + struct RocListU8 ret = { + .len = 0, + .bytes = NULL, + .capacity = 0, + }; + + return ret; + } + else + { + + size_t refcount_size = sizeof(size_t); + ssize_t* data = (ssize_t*)roc_alloc(len + refcount_size, alignof(size_t)); + data[0] = REFCOUNT_ONE; + uint8_t *new_content = (uint8_t *)(data + 1); + + struct RocListU8 ret; + + memcpy(new_content, bytes, len * sizeof(uint8_t)); + + ret.bytes = new_content; + ret.len = len; + ret.capacity = len; + + return ret; + } +} + +// RocStr + +struct RocStr +{ + uint8_t *bytes; + size_t len; + size_t capacity; +}; + +struct RocStr init_rocstr(uint8_t *bytes, size_t len) +{ + if (len < sizeof(struct RocStr)) + { + // Start out with zeroed memory, so that + // if we end up comparing two small RocStr values + // for equality, we won't risk memory garbage resulting + // in two equal strings appearing unequal. + struct RocStr ret = { + .len = 0, + .bytes = NULL, + .capacity = 0, + }; + + // Copy the bytes into the stack allocation + memcpy(&ret, bytes, len); + + // Record the string's len in the last byte of the stack allocation + ((uint8_t *)&ret)[sizeof(struct RocStr) - 1] = (uint8_t)len | 0b10000000; + + return ret; + } + else + { + // A large RocStr is the same as a List U8 (aka RocListU8) in memory. + struct RocListU8 roc_bytes = init_roclist_u8(bytes, len); + + struct RocStr ret = { + .len = roc_bytes.len, + .bytes = roc_bytes.bytes, + .capacity = roc_bytes.capacity, + }; + + return ret; + } +} + +bool is_small_str(struct RocStr str) +{ + return ((ssize_t)str.capacity) < 0; +} + +bool is_seamless_str_slice(struct RocStr str) +{ + return ((ssize_t)str.len) < 0; +} + +bool is_seamless_listi32_slice(struct RocListI32 list) +{ + return ((ssize_t)list.capacity) < 0; +} + +// Determine the len of the string, taking into +// account the small string optimization +size_t roc_str_len(struct RocStr str) +{ + uint8_t *bytes = (uint8_t *)&str; + uint8_t last_byte = bytes[sizeof(str) - 1]; + uint8_t 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; + } +} + +__attribute__((noreturn)) void roc_panic(struct RocStr *msg, unsigned int tag_id) +{ + char* bytes = is_small_str(*msg) ? (char*)msg : (char*)msg->bytes; + const size_t str_len = roc_str_len(*msg); + + int len = str_len > ERR_MSG_MAX_SIZE ? ERR_MSG_MAX_SIZE : str_len; + strncpy((char*)err_msg, bytes, len); + + // Free the underlying allocation if needed. + if (!is_small_str(*msg)) { + if (is_seamless_str_slice(*msg)){ + decref((uint8_t *)(msg->capacity << 1), alignof(uint8_t *)); + } + else { + decref(msg->bytes, alignof(uint8_t *)); + } + } + + longjmp(exception_buffer, 1); +} + +extern void roc__programForHost_1__InterpolateString_caller(struct RocStr *name, char *closure_data, struct RocStr *ret); + +extern void roc__programForHost_1__MulArrByScalar_caller(struct RocListI32 *arr, int32_t *scalar, char *closure_data, struct RocListI32 *ret); + +extern void roc__programForHost_1__Factorial_caller(int64_t *scalar, char *closure_data, int64_t *ret); + + +JNIEXPORT jstring JNICALL Java_javaSource_Demo_sayHello + (JNIEnv *env, jobject thisObj, jstring name) +{ + const char *jnameChars = (*env)->GetStringUTFChars(env, name, 0); + // we copy just in case the jvm would try to reclaim that mem + uint8_t *cnameChars = (uint8_t *)strdup(jnameChars); + size_t nameLength = (size_t) (*env)->GetStringLength(env, name); + (*env)->ReleaseStringUTFChars(env, name, jnameChars); + + + struct RocStr rocName = init_rocstr(cnameChars, nameLength); + struct RocStr ret = {0}; + + // Call the Roc function to populate `ret`'s bytes. + roc__programForHost_1__InterpolateString_caller(&rocName, 0, &ret); + jbyte *bytes = (jbyte*)(is_small_str(ret) ? (uint8_t*)&ret : ret.bytes); + + // java being java making this a lot harder than it needs to be + // https://stackoverflow.com/questions/32205446/getting-true-utf-8-characters-in-java-jni + // https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16542 + // but as i refuse converting those manually to their correct form, we just let the jvm handle the conversion + // by first making a java byte array then converting the byte array to our final jstring + jbyteArray byteArray = (*env)->NewByteArray(env, ret.len); + (*env)->SetByteArrayRegion(env, byteArray, 0, ret.len, bytes); + + jstring charsetName = (*env)->NewStringUTF(env, "UTF-8"); + jclass stringClass = (*env)->FindClass(env, "java/lang/String"); + // https://docs.oracle.com/javase/7/docs/jdk/api/jpda/jdi/com/sun/jdi/doc-files/signature.html + jmethodID stringConstructor = (*env)->GetMethodID(env, stringClass, "", "([BLjava/lang/String;)V"); + jstring result = (*env)->NewObject(env, stringClass, stringConstructor, byteArray, charsetName); + + // cleanup + if (!is_seamless_str_slice(ret)) { + decref(ret.bytes, alignof(uint8_t *)); + } + + (*env)->DeleteLocalRef(env, charsetName); + (*env)->DeleteLocalRef(env, byteArray); + + free(cnameChars); + + return result; +} + + +JNIEXPORT jintArray JNICALL Java_javaSource_Demo_mulArrByScalar + (JNIEnv *env, jobject thisObj, jintArray arr, jint scalar) +{ + // extract data from jvm types + jint* jarr = (*env)->GetIntArrayElements(env, arr, NULL); + jsize len = (*env)->GetArrayLength(env, arr); + + // pass data to platform + struct RocListI32 originalArray = init_roclist_i32(jarr, len); + incref((void *)&originalArray, alignof(int32_t*)); + struct RocListI32 ret = {0}; + + roc__programForHost_1__MulArrByScalar_caller(&originalArray, &scalar, 0, &ret); + + // create jvm constructs + jintArray multiplied = (*env)->NewIntArray(env, ret.len); + (*env)->SetIntArrayRegion(env, multiplied, 0, ret.len, (jint*) ret.bytes); + + // cleanup + (*env)->ReleaseIntArrayElements(env, arr, jarr, 0); + + if (is_seamless_listi32_slice(ret)) { + decref((void *)(ret.capacity << 1), alignof(uint8_t *)); + } + else { + decref((void *)ret.bytes, alignof(uint8_t *)); + } + + return multiplied; +} + +JNIEXPORT jlong JNICALL Java_javaSource_Demo_factorial + (JNIEnv *env, jobject thisObj, jlong num) +{ + int64_t ret; + // can crash - meaning call roc_panic, so we set a jump here + if (setjmp(exception_buffer)) { + // exception was thrown, handle it + jclass exClass = (*env)->FindClass(env, "java/lang/RuntimeException"); + const char *msg = (const char *)err_msg; + return (*env)->ThrowNew(env, exClass, msg); + } + else { + int64_t n = (int64_t)num; + roc__programForHost_1__Factorial_caller(&n, 0, &ret); + return ret; + } +} diff --git a/examples/jvm-interop/build.sh b/examples/jvm-interop/build.sh new file mode 100755 index 0000000000..08feb805c4 --- /dev/null +++ b/examples/jvm-interop/build.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +set -euxo pipefail + +# don't forget to validate that $JAVA_HOME is defined, the following would not work without it! +# set it either globally or here +# export JAVA_HOME=/your/java/installed/dir +# in nixos, to set it globally, i needed to say `programs.java.enable = true;` in `/etc/nixos/configuration.nix` + + +# if roc is in your path, you could +# roc build impl.roc --no-link +# else, assuming in roc repo and that you ran `cargo run --release` +../../target/release/roc build impl.roc --no-link + + +# make jvm look here to see libinterop.so +export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH + +# needs jdk10 + +# "-h ." is for placing the jni.h header in the cwd. +# the "javaSource" directory may seem redundant (why not just a plain java file), +# but this is the way of java packaging +# we could go without it with an "implicit" package, but that would ache later on, +# especially with other JVM langs +javac -h . javaSource/Demo.java + + +clang \ + -g -Wall \ + -fPIC \ + -I"$JAVA_HOME/include" \ + # -I"$JAVA_HOME/include/darwin" # for macos + -I"$JAVA_HOME/include/linux" \ + # -shared -o libinterop.dylib \ # for macos + -shared -o libinterop.so \ + rocdemo.o bridge.c + + +# then run +java javaSource.Demo diff --git a/examples/jvm-interop/impl.roc b/examples/jvm-interop/impl.roc new file mode 100644 index 0000000000..75c2ba6d94 --- /dev/null +++ b/examples/jvm-interop/impl.roc @@ -0,0 +1,26 @@ +app "rocdemo" + packages { pf: "platform.roc" } + imports [] + provides [program] to pf + +interpolateString : Str -> Str +interpolateString = \name -> + "Hello from Roc \(name)!!!🤘🤘🤘" + +# jint is i32 +mulArrByScalar : List I32, I32 -> List I32 +mulArrByScalar = \arr, scalar -> + List.map arr \x -> x * scalar + +# java doesn't have unsigned numbers so we cope with long +# factorial : I64 -> I64 +factorial = \n -> + if n < 0 then + # while we get the chance, exemplify a roc panic in an interop + crash "No negatives here!!!" + else if n == 0 then + 1 + else + n * (factorial (n - 1)) + +program = { interpolateString, factorial, mulArrByScalar } diff --git a/examples/jvm-interop/javaSource/Demo.java b/examples/jvm-interop/javaSource/Demo.java new file mode 100644 index 0000000000..2b5184e0e7 --- /dev/null +++ b/examples/jvm-interop/javaSource/Demo.java @@ -0,0 +1,39 @@ +package javaSource; + +import java.util.Arrays; + +public class Demo { + + static { + System.loadLibrary("interop"); + } + + public static native String sayHello(String num); + + public static native int[] mulArrByScalar(int[] arr, int scalar); + + public static native long factorial(long n) throws RuntimeException; + + + public static void main(String[] args) { + + // string demo + System.out.println(sayHello("Brendan") + "\n"); + + // array demo + int[] arr = {10, 20, 30, 40}; + int x = 3; + System.out.println(Arrays.toString(arr) + + " multiplied by " + x + + " results in " + Arrays.toString(mulArrByScalar(arr, x)) + + "\n"); + + // number + panic demo + // This can be implemented more peacefully but for sake of demonstration- + // this will panic from the roc side if n is negative + // and in turn will throw a JVM RuntimeException + long n = -1; + System.out.println("Factorial of " + n + " is " + factorial(n)); + + } +} diff --git a/examples/jvm-interop/platform.roc b/examples/jvm-interop/platform.roc new file mode 100644 index 0000000000..8931752ff0 --- /dev/null +++ b/examples/jvm-interop/platform.roc @@ -0,0 +1,13 @@ +platform "jvm-interop" + requires {} { program : _ } + exposes [] + packages {} + imports [] + provides [programForHost] + +programForHost : { + interpolateString : (Str -> Str) as InterpolateString, + mulArrByScalar : (List I32, I32 -> List I32) as MulArrByScalar, + factorial : (I64 -> I64) as Factorial, +} +programForHost = program diff --git a/examples/parser/Parser/Http.roc b/examples/parser/Parser/Http.roc index d9bdb2ff73..472c79231e 100644 --- a/examples/parser/Parser/Http.roc +++ b/examples/parser/Parser/Http.roc @@ -16,13 +16,14 @@ interface Parser.Http codeunitSatisfies, strFromRaw, anyRawString, + digits, }, ] # https://www.ietf.org/rfc/rfc2616.txt Method : [Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch] -HttpVersion : Str +HttpVersion : { major : U8, minor : U8 } Request : { method : Method, @@ -34,7 +35,7 @@ Request : { Response : { httpVersion : HttpVersion, - statusCode : Str, + statusCode : U16, status : Str, headers : List [Header Str Str], body : List U8, @@ -71,18 +72,19 @@ requestUri = sp = codeunit ' ' crlf = string "\r\n" -# TODO: The 'digit' and 'digits' from Parser.Str are causing repl.expect to blow up -digit = codeunitSatisfies \c -> c >= '0' && c <= '9' -digits = digit |> oneOrMore |> map strFromRaw - httpVersion : Parser RawStr HttpVersion httpVersion = - const (\major -> \minor -> "\(major).\(minor)") + const (\major -> \minor -> { major, minor }) |> skip (string "HTTP/") |> keep digits |> skip (codeunit '.') |> keep digits +expect + actual = parseStr httpVersion "HTTP/1.1" + expected = Ok { major: 1, minor: 1 } + actual == expected + Header : [Header Str Str] stringWithoutColon : Parser RawStr Str @@ -139,7 +141,7 @@ expect expected = Ok { method: Get, uri: "/things?id=1", - httpVersion: "1.1", + httpVersion: { major: 1, minor: 1 }, headers: [ Header "Host" "bar.example", Header "Accept-Encoding" "gzip, deflate", @@ -167,7 +169,7 @@ expect expected = Ok { method: Options, uri: "/resources/post-here/", - httpVersion: "1.1", + httpVersion: { major: 1, minor: 1 }, headers: [ Header "Host" "bar.example", Header "Accept" "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", @@ -234,8 +236,8 @@ expect parseStr response responseText expected = Ok { - httpVersion: "1.1", - statusCode: "200", + httpVersion: { major: 1, minor: 1 }, + statusCode: 200, status: "OK", headers: [ Header "Content-Type" "text/html; charset=utf-8", diff --git a/examples/parser/Parser/Str.roc b/examples/parser/Parser/Str.roc index f38be64d70..0d7cdbf02c 100644 --- a/examples/parser/Parser/Str.roc +++ b/examples/parser/Parser/Str.roc @@ -178,10 +178,10 @@ digit : Parser RawStr U8 digit = digitParsers = List.range { start: At '0', end: At '9' } - |> List.map \digitNum -> - digitNum + |> List.map \digitCodeUnit -> + digitCodeUnit |> codeunit - |> map \_ -> digitNum + |> map \_ -> digitCodeUnit - '0' oneOf digitParsers diff --git a/examples/parser/examples/letter-counts.roc b/examples/parser/examples/letter-counts.roc index c8b42c98c1..33c2680268 100644 --- a/examples/parser/examples/letter-counts.roc +++ b/examples/parser/examples/letter-counts.roc @@ -1,6 +1,6 @@ app "example" packages { - cli: "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br", + cli: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br", parser: "../package/main.roc", } imports [ diff --git a/examples/parser/package/main.roc b/examples/parser/package/main.roc index 01054c0a64..9ab47e3047 100644 --- a/examples/parser/package/main.roc +++ b/examples/parser/package/main.roc @@ -5,4 +5,4 @@ package "parser" ParserStr, ParserHttp, ] - packages {} \ No newline at end of file + packages {} diff --git a/examples/parser/parse-movies-csv.roc b/examples/parser/parse-movies-csv.roc deleted file mode 100644 index 46dd00b964..0000000000 --- a/examples/parser/parse-movies-csv.roc +++ /dev/null @@ -1,58 +0,0 @@ -app "parse-movies-csv" - packages { pf: "platform/main.roc" } - imports [Parser.Core.{ Parser, map, keep }, Parser.Str.{ RawStr }, Parser.CSV.{ CSV, record, field, string, nat }] - provides [main] to pf - -input : Str -input = "Airplane!,1980,\"Robert Hays,Julie Hagerty\"\r\nCaddyshack,1980,\"Chevy Chase,Rodney Dangerfield,Ted Knight,Michael O'Keefe,Bill Murray\"" - -main : Str -main = - when Parser.CSV.parseStr movieInfoParser input is - Ok movies -> - moviesString = - movies - |> List.map movieInfoExplanation - |> Str.joinWith ("\n") - nMovies = List.len movies |> Num.toStr - - "\(nMovies) movies were found:\n\n\(moviesString)\n\nParse success!\n" - - Err problem -> - when problem is - ParsingFailure failure -> - "Parsing failure: \(failure)\n" - - ParsingIncomplete leftover -> - leftoverStr = leftover |> List.map Parser.Str.strFromRaw |> List.map (\val -> "\"\(val)\"") |> Str.joinWith ", " - - "Parsing incomplete. Following leftover fields while parsing a record: \(leftoverStr)\n" - - SyntaxError error -> - "Parsing failure. Syntax error in the CSV: \(error)" - -MovieInfo := { title : Str, releaseYear : Nat, actors : List Str } - -movieInfoParser = - record (\title -> \releaseYear -> \actors -> @MovieInfo { title, releaseYear, actors }) - |> keep (field string) - |> keep (field nat) - |> keep (field actorsParser) - -actorsParser = - string - |> map (\val -> Str.split val ",") - -movieInfoExplanation = \@MovieInfo { title, releaseYear, actors } -> - enumeratedActors = enumerate actors - releaseYearStr = Num.toStr releaseYear - - "The movie '\(title)' was released in \(releaseYearStr) and stars \(enumeratedActors)" - -enumerate : List Str -> Str -enumerate = \elements -> - { before: inits, others: last } = List.split elements (List.len elements - 1) - - last - |> List.prepend (inits |> Str.joinWith ", ") - |> Str.joinWith " and " diff --git a/examples/parser/platform/host.zig b/examples/parser/platform/host.zig deleted file mode 100755 index b84d428034..0000000000 --- a/examples/parser/platform/host.zig +++ /dev/null @@ -1,125 +0,0 @@ -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; - -extern fn roc__mainForHost_1_exposed_generic(*RocStr) void; - -pub fn main() u8 { - const stdout = std.io.getStdOut().writer(); - - // actually call roc to populate the callresult - var callresult = RocStr.empty(); - roc__mainForHost_1_exposed_generic(&callresult); - - // stdout the result - stdout.print("{s}", .{callresult.asSlice()}) catch unreachable; - - callresult.deinit(); - - return 0; -} - -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 (builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - -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) ?*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: *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: *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: *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; - 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); -} - -extern fn kill(pid: c_int, sig: c_int) c_int; -extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int; -extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque; -extern fn getppid() c_int; - -fn roc_getppid() callconv(.C) c_int { - return getppid(); -} - -fn roc_getppid_windows_stub() callconv(.C) c_int { - return 0; -} - -fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int { - return shm_open(name, oflag, mode); -} -fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque { - return mmap(addr, length, prot, flags, fd, offset); -} - -comptime { - if (builtin.os.tag == .macos or builtin.os.tag == .linux) { - @export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong }); - @export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong }); - @export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong }); - } - - if (builtin.os.tag == .windows) { - @export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong }); - } -} diff --git a/examples/parser/platform/main.roc b/examples/parser/platform/main.roc deleted file mode 100644 index 175f7070d5..0000000000 --- a/examples/parser/platform/main.roc +++ /dev/null @@ -1,9 +0,0 @@ -platform "hello-world" - requires {} { main : Str } - exposes [] - packages {} - imports [] - provides [mainForHost] - -mainForHost : Str -mainForHost = main diff --git a/examples/platform-switching/rust-platform/Cargo.toml b/examples/platform-switching/rust-platform/Cargo.toml index 8d10ce6eea..b329e6472b 100644 --- a/examples/platform-switching/rust-platform/Cargo.toml +++ b/examples/platform-switching/rust-platform/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "host" -version = "0.0.1" authors = ["The Roc Contributors"] -license = "UPL-1.0" edition = "2021" +license = "UPL-1.0" links = "app" +version = "0.0.1" [lib] name = "host" @@ -16,7 +16,7 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../crates/roc_std" } libc = "0.2" +roc_std = { path = "../../../crates/roc_std" } [workspace] diff --git a/examples/platform-switching/rust-platform/src/glue.rs b/examples/platform-switching/rust-platform/src/glue.rs new file mode 100644 index 0000000000..e3e2e524b7 --- /dev/null +++ b/examples/platform-switching/rust-platform/src/glue.rs @@ -0,0 +1,744 @@ +// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command + +#![allow(unused_unsafe)] +#![allow(unused_variables)] +#![allow(dead_code)] +#![allow(unused_mut)] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(clippy::undocumented_unsafe_blocks)] +#![allow(clippy::redundant_static_lifetimes)] +#![allow(clippy::unused_unit)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::let_and_return)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::redundant_static_lifetimes)] +#![allow(clippy::needless_borrow)] +#![allow(clippy::clone_on_copy)] + +type Op_StderrWrite = roc_std::RocStr; +type Op_StdoutWrite = roc_std::RocStr; +type TODO_roc_function_69 = roc_std::RocStr; +type TODO_roc_function_70 = roc_std::RocStr; + +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[repr(u8)] +pub enum discriminant_Op { + Done = 0, + StderrWrite = 1, + StdoutWrite = 2, +} + +impl core::fmt::Debug for discriminant_Op { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Done => f.write_str("discriminant_Op::Done"), + Self::StderrWrite => f.write_str("discriminant_Op::StderrWrite"), + Self::StdoutWrite => f.write_str("discriminant_Op::StdoutWrite"), + } + } +} + +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[repr(transparent)] +pub struct Op { + pointer: *mut union_Op, +} + +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[repr(C)] +union union_Op { + StderrWrite: core::mem::ManuallyDrop, + StdoutWrite: core::mem::ManuallyDrop, + _sizer: [u8; 8], +} + +#[cfg(any( + target_arch = "arm", + target_arch = "arm", + target_arch = "aarch64", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86", + target_arch = "x86_64", + target_arch = "x86_64" +))] +//TODO HAS CLOSURE 2 +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[repr(C)] +pub struct RocFunction_66 { + pub closure_data: roc_std::RocList, +} + +impl RocFunction_66 { + pub fn force_thunk(mut self, arg_0: ()) -> Op { + extern "C" { + fn roc__mainForHost_0_caller(arg_0: &(), closure_data: *mut u8, output: *mut Op); + } + + let mut output = std::mem::MaybeUninit::uninit(); + let ptr = self.closure_data.as_mut_ptr(); + unsafe { roc__mainForHost_0_caller(&arg_0, ptr, output.as_mut_ptr()) }; + unsafe { output.assume_init() } + } +} + +#[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" +))] +#[repr(C)] +pub struct RocFunction_67 { + pub closure_data: roc_std::RocList, +} + +impl RocFunction_67 { + pub fn force_thunk(mut self, arg_0: ()) -> Op { + extern "C" { + fn roc__mainForHost_1_caller(arg_0: &(), closure_data: *mut u8, output: *mut Op); + } + + let mut output = std::mem::MaybeUninit::uninit(); + let ptr = self.closure_data.as_mut_ptr(); + unsafe { roc__mainForHost_1_caller(&arg_0, ptr, output.as_mut_ptr()) }; + unsafe { output.assume_init() } + } +} + +impl Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + #[inline(always)] + fn storage(&self) -> Option<&core::cell::Cell> { + let mask = match std::mem::size_of::() { + 4 => 0b11, + 8 => 0b111, + _ => unreachable!(), + }; + + // NOTE: pointer provenance is probably lost here + let unmasked_address = (self.pointer as usize) & !mask; + let untagged = unmasked_address as *const core::cell::Cell; + + if untagged.is_null() { + None + } else { + unsafe { Some(&*untagged.sub(1)) } + } + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Returns which variant this tag union holds. Note that this never includes a payload! + pub fn discriminant(&self) -> discriminant_Op { + // The discriminant is stored in the unused bytes at the end of the recursive pointer + unsafe { core::mem::transmute::((self.pointer as u8) & 0b11) } + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Internal helper + fn tag_discriminant(pointer: *mut union_Op, discriminant: discriminant_Op) -> *mut union_Op { + // The discriminant is stored in the unused bytes at the end of the union pointer + let untagged = (pointer as usize) & (!0b11 as usize); + let tagged = untagged | (discriminant as usize); + + tagged as *mut union_Op + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Internal helper + fn union_pointer(&self) -> *mut union_Op { + // The discriminant is stored in the unused bytes at the end of the union pointer + ((self.pointer as usize) & (!0b11 as usize)) as *mut union_Op + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// A tag named Done, which has no payload. + pub const Done: Self = Self { + pointer: core::ptr::null_mut(), + }; + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 0. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn get_StderrWrite_0(&self) -> roc_std::RocStr { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + + extern "C" { + #[link_name = "roc__getter__2_generic"] + fn getter(_: *mut roc_std::RocStr, _: *const Op); + } + + let mut ret = core::mem::MaybeUninit::uninit(); + getter(ret.as_mut_ptr(), self); + ret.assume_init() + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 1. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn get_StderrWrite_1(&self) -> RocFunction_67 { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + + extern "C" { + #[link_name = "roc__getter__3_size"] + fn size() -> usize; + + #[link_name = "roc__getter__3_generic"] + fn getter(_: *mut u8, _: *const Op); + } + + // allocate memory to store this variably-sized value + // allocates with roc_alloc, but that likely still uses the heap + let it = std::iter::repeat(0xAAu8).take(size()); + let mut bytes = roc_std::RocList::from_iter(it); + + getter(bytes.as_mut_ptr(), self); + + RocFunction_67 { + closure_data: bytes, + } + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Construct a tag named `StderrWrite`, with the appropriate payload + pub fn StderrWrite(arg: Op_StderrWrite) -> Self { + let size = core::mem::size_of::(); + let align = core::mem::align_of::() as u32; + + unsafe { + let ptr = roc_std::roc_alloc_refcounted::(); + + *ptr = union_Op { + StderrWrite: core::mem::ManuallyDrop::new(arg), + }; + + Self { + pointer: Self::tag_discriminant(ptr, discriminant_Op::StderrWrite), + } + } + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and convert it to `StderrWrite`'s payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn into_StderrWrite(mut self) -> Op_StderrWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + let payload = { + let ptr = (self.pointer as usize & !0b11) as *mut union_Op; + let mut uninitialized = core::mem::MaybeUninit::uninit(); + let swapped = unsafe { + core::mem::replace( + &mut (*ptr).StderrWrite, + core::mem::ManuallyDrop::new(uninitialized.assume_init()), + ) + }; + + core::mem::forget(self); + + core::mem::ManuallyDrop::into_inner(swapped) + }; + + payload + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn as_StderrWrite(&self) -> &Op_StderrWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + let payload = { + let ptr = (self.pointer as usize & !0b11) as *mut union_Op; + + unsafe { &(*ptr).StderrWrite } + }; + + &payload + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 0. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn get_StdoutWrite_0(&self) -> roc_std::RocStr { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + + extern "C" { + #[link_name = "roc__getter__2_generic"] + fn getter(_: *mut roc_std::RocStr, _: *const Op); + } + + let mut ret = core::mem::MaybeUninit::uninit(); + getter(ret.as_mut_ptr(), self); + ret.assume_init() + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 1. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn get_StdoutWrite_1(&self) -> RocFunction_66 { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + + extern "C" { + #[link_name = "roc__getter__3_size"] + fn size() -> usize; + + #[link_name = "roc__getter__3_generic"] + fn getter(_: *mut u8, _: *const Op); + } + + // allocate memory to store this variably-sized value + // allocates with roc_alloc, but that likely still uses the heap + let it = std::iter::repeat(0xAAu8).take(size()); + let mut bytes = roc_std::RocList::from_iter(it); + + getter(bytes.as_mut_ptr(), self); + + RocFunction_66 { + closure_data: bytes, + } + } + + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + /// Construct a tag named `StdoutWrite`, with the appropriate payload + pub fn StdoutWrite(arg: Op_StdoutWrite) -> Self { + let size = core::mem::size_of::(); + let align = core::mem::align_of::() as u32; + + unsafe { + let ptr = roc_std::roc_alloc_refcounted::(); + + *ptr = union_Op { + StdoutWrite: core::mem::ManuallyDrop::new(arg), + }; + + Self { + pointer: Self::tag_discriminant(ptr, discriminant_Op::StdoutWrite), + } + } + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and convert it to `StdoutWrite`'s payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn into_StdoutWrite(mut self) -> Op_StdoutWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + let payload = { + let ptr = (self.pointer as usize & !0b11) as *mut union_Op; + let mut uninitialized = core::mem::MaybeUninit::uninit(); + let swapped = unsafe { + core::mem::replace( + &mut (*ptr).StdoutWrite, + core::mem::ManuallyDrop::new(uninitialized.assume_init()), + ) + }; + + core::mem::forget(self); + + core::mem::ManuallyDrop::into_inner(swapped) + }; + + payload + } + + #[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn as_StdoutWrite(&self) -> &Op_StdoutWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + let payload = { + let ptr = (self.pointer as usize & !0b11) as *mut union_Op; + + unsafe { &(*ptr).StdoutWrite } + }; + + &payload + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Returns which variant this tag union holds. Note that this never includes a payload! + pub fn discriminant(&self) -> discriminant_Op { + // The discriminant is stored in the unused bytes at the end of the recursive pointer + unsafe { core::mem::transmute::((self.pointer as u8) & 0b111) } + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Internal helper + fn tag_discriminant(pointer: *mut union_Op, discriminant: discriminant_Op) -> *mut union_Op { + // The discriminant is stored in the unused bytes at the end of the union pointer + let untagged = (pointer as usize) & (!0b111 as usize); + let tagged = untagged | (discriminant as usize); + + tagged as *mut union_Op + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Internal helper + fn union_pointer(&self) -> *mut union_Op { + // The discriminant is stored in the unused bytes at the end of the union pointer + ((self.pointer as usize) & (!0b111 as usize)) as *mut union_Op + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and convert it to `StderrWrite`'s payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn into_StderrWrite(mut self) -> Op_StderrWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + let payload = { + let ptr = (self.pointer as usize & !0b111) as *mut union_Op; + let mut uninitialized = core::mem::MaybeUninit::uninit(); + let swapped = unsafe { + core::mem::replace( + &mut (*ptr).StderrWrite, + core::mem::ManuallyDrop::new(uninitialized.assume_init()), + ) + }; + + core::mem::forget(self); + + core::mem::ManuallyDrop::into_inner(swapped) + }; + + payload + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`. + pub unsafe fn as_StderrWrite(&self) -> &Op_StderrWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite); + let payload = { + let ptr = (self.pointer as usize & !0b111) as *mut union_Op; + + unsafe { &(*ptr).StderrWrite } + }; + + &payload + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and convert it to `StdoutWrite`'s payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn into_StdoutWrite(mut self) -> Op_StdoutWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + let payload = { + let ptr = (self.pointer as usize & !0b111) as *mut union_Op; + let mut uninitialized = core::mem::MaybeUninit::uninit(); + let swapped = unsafe { + core::mem::replace( + &mut (*ptr).StdoutWrite, + core::mem::ManuallyDrop::new(uninitialized.assume_init()), + ) + }; + + core::mem::forget(self); + + core::mem::ManuallyDrop::into_inner(swapped) + }; + + payload + } + + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + /// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload. + /// (Always examine `.discriminant()` first to make sure this is the correct variant!) + /// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`. + pub unsafe fn as_StdoutWrite(&self) -> &Op_StdoutWrite { + debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite); + let payload = { + let ptr = (self.pointer as usize & !0b111) as *mut union_Op; + + unsafe { &(*ptr).StdoutWrite } + }; + + &payload + } +} + +impl Drop for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn drop(&mut self) { + // We only need to do any work if there's actually a heap-allocated payload. + if let Some(storage) = self.storage() { + let mut new_storage = storage.get(); + + // Decrement the refcount + let needs_dealloc = !new_storage.is_readonly() && new_storage.decrease(); + + if needs_dealloc { + // Drop the payload first. + match self.discriminant() { + discriminant_Op::Done => {} + discriminant_Op::StderrWrite => unsafe { + core::mem::ManuallyDrop::drop(&mut (&mut *self.union_pointer()).StderrWrite) + }, + discriminant_Op::StdoutWrite => unsafe { + core::mem::ManuallyDrop::drop(&mut (&mut *self.union_pointer()).StdoutWrite) + }, + } + + // Dealloc the pointer + let alignment = + core::mem::align_of::().max(core::mem::align_of::()); + + unsafe { + crate::roc_dealloc(storage.as_ptr().cast(), alignment as u32); + } + } else { + // Write the storage back. + storage.set(new_storage); + } + } + } +} + +impl Eq for Op {} + +impl PartialEq for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn eq(&self, other: &Self) -> bool { + if self.discriminant() != other.discriminant() { + return false; + } + + unsafe { + match self.discriminant() { + discriminant_Op::Done => true, + discriminant_Op::StderrWrite => { + (&*self.union_pointer()).StderrWrite == (&*other.union_pointer()).StderrWrite + } + discriminant_Op::StdoutWrite => { + (&*self.union_pointer()).StdoutWrite == (&*other.union_pointer()).StdoutWrite + } + } + } + } +} + +impl PartialOrd for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn partial_cmp(&self, other: &Self) -> Option { + match self.discriminant().partial_cmp(&other.discriminant()) { + Some(core::cmp::Ordering::Equal) => {} + not_eq => return not_eq, + } + + unsafe { + match self.discriminant() { + discriminant_Op::Done => Some(core::cmp::Ordering::Equal), + discriminant_Op::StderrWrite => (&*self.union_pointer()) + .StderrWrite + .partial_cmp(&(&*other.union_pointer()).StderrWrite), + discriminant_Op::StdoutWrite => (&*self.union_pointer()) + .StdoutWrite + .partial_cmp(&(&*other.union_pointer()).StdoutWrite), + } + } + } +} + +impl Ord for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + match self.discriminant().cmp(&other.discriminant()) { + core::cmp::Ordering::Equal => {} + not_eq => return not_eq, + } + + unsafe { + match self.discriminant() { + discriminant_Op::Done => core::cmp::Ordering::Equal, + discriminant_Op::StderrWrite => (&*self.union_pointer()) + .StderrWrite + .cmp(&(&*other.union_pointer()).StderrWrite), + discriminant_Op::StdoutWrite => (&*self.union_pointer()) + .StdoutWrite + .cmp(&(&*other.union_pointer()).StdoutWrite), + } + } + } +} + +impl Clone for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn clone(&self) -> Self { + if let Some(storage) = self.storage() { + let mut new_storage = storage.get(); + if !new_storage.is_readonly() { + new_storage.increment_reference_count(); + storage.set(new_storage); + } + } + + Self { + pointer: self.pointer, + } + } +} + +impl core::hash::Hash for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn hash(&self, state: &mut H) { + match self.discriminant() { + discriminant_Op::Done => discriminant_Op::Done.hash(state), + discriminant_Op::StderrWrite => unsafe { + discriminant_Op::StderrWrite.hash(state); + (&*self.union_pointer()).StderrWrite.hash(state); + }, + discriminant_Op::StdoutWrite => unsafe { + discriminant_Op::StdoutWrite.hash(state); + (&*self.union_pointer()).StdoutWrite.hash(state); + }, + } + } +} + +impl core::fmt::Debug for Op { + #[cfg(any( + target_arch = "arm", + target_arch = "aarch64", + target_arch = "wasm32", + target_arch = "x86", + target_arch = "x86_64" + ))] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Op::")?; + + unsafe { + match self.discriminant() { + discriminant_Op::Done => f.write_str("Done"), + discriminant_Op::StderrWrite => f + .debug_tuple("StderrWrite") + // TODO HAS CLOSURE + .finish(), + discriminant_Op::StdoutWrite => f + .debug_tuple("StdoutWrite") + // TODO HAS CLOSURE + .finish(), + } + } + } +} diff --git a/examples/platform-switching/web-assembly-platform/host.zig b/examples/platform-switching/web-assembly-platform/host.zig index 53ad360309..3d2133e79c 100644 --- a/examples/platform-switching/web-assembly-platform/host.zig +++ b/examples/platform-switching/web-assembly-platform/host.zig @@ -51,7 +51,7 @@ pub fn main() u8 { // display the result using JavaScript js_display_roc_string(callresult.asU8ptr(), callresult.len()); - callresult.deinit(); + callresult.decref(); return 0; } diff --git a/examples/platform-switching/zig-platform/host.zig b/examples/platform-switching/zig-platform/host.zig index 7465a1fe0e..e74df0b22c 100644 --- a/examples/platform-switching/zig-platform/host.zig +++ b/examples/platform-switching/zig-platform/host.zig @@ -130,7 +130,7 @@ pub fn main() u8 { // stdout the result stdout.print("{s}", .{callresult.asSlice()}) catch unreachable; - callresult.deinit(); + callresult.decref(); stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable; diff --git a/examples/python-interop/.gitignore b/examples/python-interop/.gitignore new file mode 100644 index 0000000000..fbee8bbbfc --- /dev/null +++ b/examples/python-interop/.gitignore @@ -0,0 +1,5 @@ +libhello* +.interop_env/ +build/ +demo.egg-info/ +dist/ diff --git a/examples/python-interop/README.md b/examples/python-interop/README.md new file mode 100644 index 0000000000..4dbbed8f3e --- /dev/null +++ b/examples/python-interop/README.md @@ -0,0 +1,82 @@ +# Python Interop + +This is a demo for calling Roc code from [Python](https://www.python.org/). + +## Installation + +The following was tested on NixOS, with `Python 3.10`, `clang 13.0.1`, `gcc 11.3.0` but should work with most recent versions of those on most modern Linux and MacOS.\ +Of course you're welcome to test on your machine and tell me (via [Zulip](https://roc.zulipchat.com/#narrow/pm-with/583319-dank)) if you ran into any issues or limitations. + +> Because of some rough edges, the linux installation may be a bit more involved (nothing too bad, mostly stuff like renames), so for your convenience I made a small shell script to help out. + +Now in favor of users of all OSs, let's first do a step by step walkthrough on how the build process works, and later, a few key notes on the implementation. + +## Building the Roc library + +First, `cd` into this directory and run this in your terminal: + +```sh +roc build --lib +``` + +This compiles your Roc code into a binary library in the current directory. The library's filename will be `libhello` plus an OS-specific extension (e.g. `libhello.dylib` on macOS). + +## Some Linux Specific Prep Work +As of the time of writing this document, `roc build --lib` generates a shared object with the suffix `.so.1.0`.\ +This `.0` suffix is not needed by neither the application nor Python, so we can simply rename it. + +``` sh +mv libhello.so.1.0 libhello.so.1 +``` +But, one of which does expect plain libhello.so, so we symlink it: + +```sh +ln -sf libhello.so.1 libhello.so +``` + +Also, one thing about dynamically linked applications like this one, is that they need to know where to look for its shared object dependencies, so we need to let CPython know that we hold libhello in this directory, so: + +``` sh +export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH +``` + +That wasn't so bad and we're already done with prep work, all that's left it to build our C extension. +## Building the C Extension +``` sh +# If you want, you can set the environment variable cc, to compile with clang instead of gcc +python -m venv .interop_env +source .interop_env/bin/activate # activate.fish if you like fish +python setup.py install +``` +For cleanness sake, we make virtual environment here, but do note you don't have to if you don't want to. +You can also run `python setup.py build` and grab the output shared object from the `build/lib.*` directory.\ +Shared objects are simply importable in CPython (which is great!), so you can just start up an interpreter in the same directory as your new `demo.so` and get the same result. + +**Note -** after all is said and done, for prolonged use, you may want to move your shared library (lib hello) to somewhere permanent on your `LD_LIBRARY_PATH`, or add your desired directory to `LD_LIBRARY_PATH` in some way (e.g put it in your shell .rc). + +## Try it out! + +Now we can see our work by entering an interactive shell and calling our function! + +```py +❯ python -q +>>> import demo +>>> demo.call_roc(21) +'The number was 21, OH YEAH!!! 🤘🤘' +``` + +## Notes on implementation +The structure of python-interop is very similar to a C-Extension, in fact, it is one.\ +We have: +- [`PyModuleDef demoModule`](https://docs.python.org/3/c-api/module.html#c.PyModuleDef) struct which declares the `demo` python module, +- [`PyMethodDef DemoMethods`](https://docs.python.org/3/c-api/structures.html#c.PyMethodDef) struct which declares `demo`'s methods, +- [`PyMODINIT_FUNC PyInit_demo`](https://docs.python.org/3/extending/extending.html) which is `demo`’s initialization function, and of course, +- [`PyObject * call_roc`] which is our demo function! Getting args and returning our string to the interpreter. The Roc-Python bridge lives here, all the above are merely CPython boilerplate to wrap up a C implementation into a valid Python module. + +The first three are basically the backbone of any C-API extension.\ +In addition, a couple more functions and notes you may want to pay attention to: +- [`void roc_panic`] - When creating such interpreter-dependent code, it is reasonable to make the implementation of this function fire up an interpreter Exception (e.g `RuntimeError` or whatever suits). +- When I first came across another implementation, I was a bit confused about `extern void roc__mainForHost_1_exposed_generic`, so let me clarify - this is an external function, implemented by the application, that goes (on the application side-) by the name `mainForHost`. + +And one last thing - +- When writing such the C glue (here, `demo.c`), you need to pay attention to not only Python's reference counting, but also Roc's, so remember to wear seatbelts and decrement your ref counts. diff --git a/examples/python-interop/build.sh b/examples/python-interop/build.sh new file mode 100755 index 0000000000..b6fcaab081 --- /dev/null +++ b/examples/python-interop/build.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +set -euxo pipefail + +# Could assume roc binary on path but this may be preferable +cargo build --release +../../target/release/roc build --lib + +# Neither the application nor python needs a .0, so we can just rename it +mv libhello.so.1.0 libhello.so.1 +# but one of which does expect plain libhello.so, so we symlink it +ln -sf libhello.so.1 libhello.so + +# For Python to find libhello, it needs it to be in a known library path, so we export +export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH + +# Optional. Flag to indicate CPython which compiler to use +export cc=clang + +# And we're done, now just pack it all together with python's build system +# setup.py will compile our demo.c to a shared library that depends on libhello. +# One of the nice things about CPython is that this demo shared library is simply importable in CPython +python -m venv .interop_env +source .interop_env/bin/activate +python setup.py install +echo "You may now enter your virtual environment. +In bash/zsh, run: source .interop_env/bin/activate +In fish, run: source .interop_env/bin/activate.fish +In csh/tcsh (really?), run: source .interop_env/bin/activate.csh +Then, try entring an interactive python shell, import demo and call demo.call_roc with your number of choice." diff --git a/examples/python-interop/demo.c b/examples/python-interop/demo.c new file mode 100644 index 0000000000..0310107039 --- /dev/null +++ b/examples/python-interop/demo.c @@ -0,0 +1,262 @@ +#define PY_SSIZE_T_CLEAN +#include +#include +#include +#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); } + +__attribute__((noreturn)) void roc_panic(void *ptr, unsigned int alignment) +{ + PyErr_SetString(PyExc_RuntimeError, (char *)ptr); +} + +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); } + +// Reference counting + +// If the refcount is set to this, that means the allocation is +// stored in readonly memory in the binary, and we must not +// attempt to increment or decrement it; if we do, we'll segfault! +const ssize_t REFCOUNT_READONLY = 0; +const ssize_t REFCOUNT_ONE = (ssize_t)PTRDIFF_MIN; +const size_t MASK = (size_t)PTRDIFF_MIN; + +// Increment reference count, given a pointer to the first element in a collection. +// We don't need to check for overflow because in order to overflow a usize worth of refcounts, +// you'd need to somehow have more pointers in memory than the OS's virtual address space can hold. +void incref(uint8_t* bytes, uint32_t alignment) +{ + ssize_t *refcount_ptr = ((ssize_t *)bytes) - 1; + ssize_t refcount = *refcount_ptr; + + if (refcount != REFCOUNT_READONLY) { + *refcount_ptr = refcount + 1; + } +} + +// Decrement reference count, given a pointer to the first element in a collection. +// Then call roc_dealloc if nothing is referencing this collection anymore. +void decref(uint8_t* bytes, uint32_t alignment) +{ + if (bytes == NULL) { + return; + } + + size_t extra_bytes = (sizeof(size_t) >= (size_t)alignment) ? sizeof(size_t) : (size_t)alignment; + ssize_t *refcount_ptr = ((ssize_t *)bytes) - 1; + ssize_t refcount = *refcount_ptr; + + if (refcount != REFCOUNT_READONLY) { + *refcount_ptr = refcount - 1; + + if (refcount == REFCOUNT_ONE) { + void *original_allocation = (void *)(refcount_ptr - (extra_bytes - sizeof(size_t))); + + roc_dealloc(original_allocation, alignment); + } + } +} + +// RocBytes (List U8) + +struct RocBytes +{ + uint8_t *bytes; + size_t len; + size_t capacity; +}; + +struct RocBytes init_rocbytes(uint8_t *bytes, size_t len) +{ + if (len == 0) + { + struct RocBytes ret = { + .len = 0, + .bytes = NULL, + .capacity = 0, + }; + + return ret; + } + else + { + struct RocBytes ret; + size_t refcount_size = sizeof(size_t); + uint8_t *new_content = ((uint8_t *)roc_alloc(len + refcount_size, alignof(size_t))) + refcount_size; + + memcpy(new_content, bytes, len); + + ret.bytes = new_content; + ret.len = len; + ret.capacity = len; + + return ret; + } +} + +// RocStr + +struct RocStr +{ + uint8_t *bytes; + size_t len; + size_t capacity; +}; + +struct RocStr init_rocstr(uint8_t *bytes, size_t len) +{ + if (len == 0) + { + struct RocStr ret = { + .len = 0, + .bytes = NULL, + .capacity = MASK, + }; + + return ret; + } + else if (len < sizeof(struct RocStr)) + { + // Start out with zeroed memory, so that + // if we end up comparing two small RocStr values + // for equality, we won't risk memory garbage resulting + // in two equal strings appearing unequal. + struct RocStr ret = { + .len = 0, + .bytes = NULL, + .capacity = MASK, + }; + + // Copy the bytes into the stack allocation + memcpy(&ret, bytes, len); + + // Record the string's length in the last byte of the stack allocation + ((uint8_t *)&ret)[sizeof(struct RocStr) - 1] = (uint8_t)len | 0b10000000; + + return ret; + } + else + { + // A large RocStr is the same as a List U8 (aka RocBytes) in memory. + struct RocBytes roc_bytes = init_rocbytes(bytes, len); + + struct RocStr ret = { + .len = roc_bytes.len, + .bytes = roc_bytes.bytes, + .capacity = roc_bytes.capacity, + }; + + return ret; + } +} + +bool is_small_str(struct RocStr str) { return ((ssize_t)str.capacity) < 0; } + +// Determine the length of the string, taking into +// account the small string optimization +size_t roc_str_len(struct RocStr str) +{ + uint8_t *bytes = (uint8_t *)&str; + uint8_t last_byte = bytes[sizeof(str) - 1]; + uint8_t 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; + } +} + +extern void roc__mainForHost_1_exposed_generic(struct RocBytes *ret, struct RocBytes *arg); + +// Receive a value from Python, JSON serialized it and pass it to Roc as a List U8 +// (at which point the Roc platform will decode it and crash if it's invalid, +// which roc_panic will translate into a Python exception), then get some JSON back from Roc +// - also as a List U8 - and have Python JSON.parse it into a plain Python value to return. +PyObject * call_roc(PyObject *self, PyObject *args) +{ + int num; + + if(!PyArg_ParseTuple(args, "i", &num)) { + return NULL; + } + + char str_num[256] = {0}; + sprintf(str_num, "%d", num); + + // can also be done with python objects but im not sure what would be the benefit here + // PyObject* py_num_str = PyUnicode_FromFormat("%d", num); + // const char* c_str = PyUnicode_AsUTF8(py_num_str); + // size_t length = (size_t *)PyUnicode_GetLength(py_num_str); + // ...init_rocbytes((uint8_t *)c_str, length); + + // Turn the given Python number into a JSON string. + struct RocBytes arg = init_rocbytes((uint8_t *)str_num, strlen(str_num)); + struct RocBytes ret; + + // Call the Roc function to populate `ret`'s bytes. + roc__mainForHost_1_exposed_generic(&ret, &arg); + + // Create a Python string from the heap-allocated JSON bytes the Roc function returned. + PyObject* json_bytes = PyUnicode_FromStringAndSize((char*)ret.bytes, ret.len); + PyObject* json_module = PyImport_ImportModule("json"); + PyObject* loads_func = PyObject_GetAttrString(json_module, "loads"); + PyObject *loads_args = PyTuple_Pack(1, json_bytes); + PyObject* py_obj = PyObject_CallObject(loads_func, loads_args); + Py_XDECREF(loads_args); + Py_XDECREF(loads_func); + Py_XDECREF(json_module); + Py_XDECREF(json_bytes); + + // Now that we've created py_str, we're no longer referencing the RocBytes. + decref((void *)&ret, alignof(uint8_t *)); + + return py_obj; +} + +static PyMethodDef DemoMethods[] = { + {"call_roc", call_roc, METH_VARARGS, "Calls a Roc function with a number, returns a string interpolated with the number"}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef demoModule = { + PyModuleDef_HEAD_INIT, + "call_roc", + "demo roc call", + -1, + DemoMethods +}; + +PyMODINIT_FUNC PyInit_demo(void) { + return PyModule_Create(&demoModule); +} diff --git a/examples/python-interop/main.roc b/examples/python-interop/main.roc new file mode 100644 index 0000000000..9b9d316dca --- /dev/null +++ b/examples/python-interop/main.roc @@ -0,0 +1,13 @@ +app "libhello" + packages { pf: "platform/main.roc" } + imports [] + provides [main] to pf + +main : U64 -> Str +main = \num -> + if num == 0 then + "I need a positive number here!" + else + str = Num.toStr num + + "The number was \(str), OH YEAH!!! 🤘🤘" diff --git a/examples/python-interop/platform/host.c b/examples/python-interop/platform/host.c new file mode 100644 index 0000000000..abd7d96680 --- /dev/null +++ b/examples/python-interop/platform/host.c @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include +#include +#include // shm_open +#include // for mmap +#include // for kill + +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); } + +int roc_shm_open(char* name, int oflag, int mode) { +#ifdef _WIN32 + return 0; +#else + return shm_open(name, oflag, mode); +#endif +} +void* roc_mmap(void* addr, int length, int prot, int flags, int fd, int offset) { +#ifdef _WIN32 + return addr; +#else + return mmap(addr, length, prot, flags, fd, offset); +#endif +} + +int roc_getppid() { +#ifdef _WIN32 + return 0; +#else + return getppid(); +#endif +} + +struct RocStr { + char* bytes; + size_t len; + size_t capacity; +}; + +bool is_small_str(struct RocStr str) { return ((ssize_t)str.capacity) < 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; + } +} + +extern void roc__mainForHost_1_exposed_generic(struct RocStr *string); + +int main() { + + struct RocStr str; + roc__mainForHost_1_exposed_generic(&str); + + // 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! + + // NOTE: the string is a static string, read from in the binary + // if you make it a heap-allocated string, it'll be leaked here + return 0; + } else { + printf("Error writing to stdout: %s\n", strerror(errno)); + + // NOTE: the string is a static string, read from in the binary + // if you make it a heap-allocated string, it'll be leaked here + return 1; + } +} diff --git a/examples/python-interop/platform/main.roc b/examples/python-interop/platform/main.roc new file mode 100644 index 0000000000..642b8d4340 --- /dev/null +++ b/examples/python-interop/platform/main.roc @@ -0,0 +1,12 @@ +platform "python-interop" + requires {} { main : arg -> ret | arg has Decoding, ret has Encoding } + exposes [] + packages {} + imports [Json] + provides [mainForHost] + +mainForHost : List U8 -> List U8 +mainForHost = \json -> + when Decode.fromBytes json Json.fromUtf8 is + Ok arg -> Encode.toBytes (main arg) Json.toUtf8 + Err _ -> [] diff --git a/examples/python-interop/setup.py b/examples/python-interop/setup.py new file mode 100644 index 0000000000..d825c8414b --- /dev/null +++ b/examples/python-interop/setup.py @@ -0,0 +1,12 @@ +import os +from setuptools import setup, Extension + +def main(): + setup(name="demo", + description="Demo testing Roc function calls from Python", + ext_modules=[ + Extension("demo", sources=["demo.c"], + libraries=["hello"], library_dirs=[os.getcwd()])]) + +if __name__ == "__main__": + main() diff --git a/examples/ruby-interop/demo.c b/examples/ruby-interop/demo.c index b5afb29419..5006eba7cf 100644 --- a/examples/ruby-interop/demo.c +++ b/examples/ruby-interop/demo.c @@ -91,7 +91,7 @@ struct RocBytes init_rocbytes(uint8_t *bytes, size_t len) struct RocBytes ret = { .len = 0, .bytes = NULL, - .capacity = MASK, + .capacity = 0, }; return ret; diff --git a/examples/ruby-interop/platform/host.c b/examples/ruby-interop/platform/host.c index abd7d96680..cfd449e7a7 100644 --- a/examples/ruby-interop/platform/host.c +++ b/examples/ruby-interop/platform/host.c @@ -28,6 +28,10 @@ void* roc_memcpy(void* dest, const void* src, size_t n) { return memcpy(dest, src, n); } +void* roc_memmove(void* dest, const void* src, size_t n){ + return memmove(dest, src, n); +} + void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); } int roc_shm_open(char* name, int oflag, int mode) { diff --git a/examples/static-site-gen/.gitignore b/examples/static-site-gen/.gitignore index a101b1e51e..fdb13757e3 100644 --- a/examples/static-site-gen/.gitignore +++ b/examples/static-site-gen/.gitignore @@ -1,3 +1,4 @@ /output/*.html +/output/subFolder /static-site /bin diff --git a/examples/static-site-gen/README.md b/examples/static-site-gen/README.md index af96a62bfa..f8dd72ab56 100644 --- a/examples/static-site-gen/README.md +++ b/examples/static-site-gen/README.md @@ -9,7 +9,7 @@ To run, `cd` into this directory and run this in your terminal: If `roc` is on your PATH: ```bash -roc run static-site.roc input/ output/ +roc run static-site.roc -- input/ output/ ``` If not, and you're building Roc from source: diff --git a/examples/static-site-gen/input/apple.md b/examples/static-site-gen/input/subFolder/apple.md similarity index 66% rename from examples/static-site-gen/input/apple.md rename to examples/static-site-gen/input/subFolder/apple.md index f4e28c985d..eb853b1edf 100644 --- a/examples/static-site-gen/input/apple.md +++ b/examples/static-site-gen/input/subFolder/apple.md @@ -44,24 +44,22 @@ ingeniis in pugna quadripedis glandes superos. Tanta quam, illo es prole est telis **unus verba** quisquis iuvenci annis. Nec velox sed sacra gaudia vacuos, Herculei undae calcata inmeriti quercus ignes parabant iam. - digitize(undoDhcp(card_record, cad_flash_dot)); - supercomputer(2 - load_type, yobibyteTraceroute - installHibernate, 1); - burnPci.pop_wrap.usbEmulation(hostESmm, processor_impression(4, lanNntp), - -5); - map_camera -= 73; - if (53) { - dacRootkitDrive(publicActivex.bmpNumWhite.wins_pci_firmware(scroll_cell, - 4, tShortcut)); - horse_algorithm_eide -= 51; - flatbed_blob(flat); - } else { - surge.pci -= open_flash_dv(4, 4, usbStation); - led.memory_fsb.matrixBinaryUrl(umlEngineOsd.agp_thick_thin.t(58)); - kindle_cookie(formulaLedVpn, digital_meme); - } +### Example Table + +| Tables | Are | Cool | +| :------------ | :-----------: | ----: | +| col 3 is | right-aligned | $1600 | +| col 2 is | centered | $12 | +| zebra stripes | are neat | $1 | + +### Example Code Blocks + +```sh +# This isn't fenced roc code so its not formatted +# Use a fence like ```roc to format code blocks +``` + +```roc +file:codeExample.roc +``` -Foret inpendere, haec ipse ossa, dolentes das Caystro miscuit iunctoque -spoliantis illae, ex! Bello istis nunc Aegides? Animo caelestia melior, -furoribus optat maior invecta quid harenis [est](http://example.org) sollemnia modo -Phineu. Suarum pectora. Relinquam in labore Medusae sororem Herculis [simillima -corpora](http://example.org) plus regi ignibus, totum domus! diff --git a/examples/static-site-gen/input/subFolder/codeExample.roc b/examples/static-site-gen/input/subFolder/codeExample.roc new file mode 100644 index 0000000000..76633762c6 --- /dev/null +++ b/examples/static-site-gen/input/subFolder/codeExample.roc @@ -0,0 +1,71 @@ +## This is a documentation comment +# This is a comment +app "static-site" + packages { pf: "platform/main.roc" } + imports [ + pf.Html.{ html, head, body, div, text, a, ul, li, link, meta }, + pf.Html.Attributes.{ httpEquiv, content, href, rel, lang, class, title }, + ] + provides [transformFileContent] to pf + +NavLink : { + # this is another comment + url : Str, + title : Str, + text : Str, +} + +navLinks : List NavLink +navLinks = [ + { url: "apple.html", title: "Exempli Gratia Pagina Pomi", text: "Apple" }, + { url: "banana.html", title: "Exempli Gratia Pagina Musa", text: "Banana" }, + { url: "cherry.html", title: "Exempli Pagina Cerasus", text: "Cherry" }, +] + +transformFileContent : Str, Str -> Str +transformFileContent = \currentUrl, htmlContent -> + List.findFirst navLinks (\{ url } -> url == currentUrl) + |> Result.map (\currentNavLink -> view currentNavLink htmlContent) + |> Result.map Html.render + |> Result.withDefault "" + +view : NavLink, Str -> Html.Node +view = \currentNavLink, htmlContent -> + html [lang "en"] [ + head [] [ + meta [httpEquiv "content-type", content "text/html; charset=utf-8"] [], + Html.title [] [text currentNavLink.title], + link [rel "stylesheet", href "style.css"] [], + ], + body [] [ + div [class "main"] [ + div [class "navbar"] [ + viewNavbar currentNavLink, + ], + div [class "article"] [ + # For now `text` is not escaped so we can use it to insert HTML + # We'll probably want something more explicit in the long term though! + text htmlContent, + ], + ], + ], + ] + +viewNavbar : NavLink -> Html.Node +viewNavbar = \currentNavLink -> + ul + [] + (List.map navLinks \nl -> viewNavLink (nl == currentNavLink) nl) + +viewNavLink : Bool, NavLink -> Html.Node +viewNavLink = \isCurrent, navlink -> + if isCurrent then + li [class "nav-link nav-link--current"] [ + text navlink.text, + ] + else + li [class "nav-link"] [ + a + [href navlink.url, title navlink.title] + [text navlink.text], + ] diff --git a/examples/static-site-gen/output/style.css b/examples/static-site-gen/output/style.css index cdc5bf0e97..ba6aa4fa21 100644 --- a/examples/static-site-gen/output/style.css +++ b/examples/static-site-gen/output/style.css @@ -39,7 +39,98 @@ color: #444; } .article pre { - background-color: #222; - color: yellow; + background-color: rgb(241, 241, 241); + color: rgb(27, 27, 27); padding: 16px; } + +pre { + white-space: pre-wrap; +} + +samp .ann { + /* type annotation - purple in the repl */ + color: #f384fd; +} + +samp .autovar .comment { + /* automatic variable names in the repl, e.g. # val1 */ + color: #338545; +} + +samp .kw { + color: #004cc2; +} + +samp .arrow, samp .backslash, samp .bar { + color: #0600c2; +} + +samp .pipe { + color: #0600c2; +} + +samp .op { + color: #0600c2; +} + +samp .assign { + color: #48fd00; +} + +samp .paren, samp .bracket, samp .brace { + color: #ff0000; +} + +samp .comma { + color: #ff00fb; +} + +samp .colon { + color: #9b0098; +} + +samp .number { +/* number literals */ +color: #9669ff; +} + +samp .str { + /* string literals */ + color: #1dbf00; +} + +samp .str-esc, samp .str-interp { + /* escapes inside string literals, e.g. \t */ + color: #3474db; +} + +samp .dim { + opacity: 0.55; +} + +samp .comment { + color: #005a13; +} + + +table { + table-layout: fixed; + width: 100%; + border-collapse: collapse; + border: 3px solid rgb(161, 64, 0); +} + +tbody tr:nth-child(even) { + background-color: #c6f4ff; +} + +th { + background-color: #ffabab; +} + +th, +td { + + padding: 2px; +} \ No newline at end of file diff --git a/examples/static-site-gen/platform/Cargo.lock b/examples/static-site-gen/platform/Cargo.lock deleted file mode 100644 index 0e81055120..0000000000 --- a/examples/static-site-gen/platform/Cargo.lock +++ /dev/null @@ -1,76 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "host" -version = "0.0.1" -dependencies = [ - "libc", - "pulldown-cmark", - "roc_std", -] - -[[package]] -name = "libc" -version = "0.2.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "pulldown-cmark" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - -[[package]] -name = "roc_std" -version = "0.0.1" -dependencies = [ - "arrayvec", - "static_assertions", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/examples/static-site-gen/platform/Cargo.toml b/examples/static-site-gen/platform/Cargo.toml index 9b5becea49..7475e40815 100644 --- a/examples/static-site-gen/platform/Cargo.toml +++ b/examples/static-site-gen/platform/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "host" -version = "0.0.1" authors = ["The Roc Contributors"] -license = "UPL-1.0" edition = "2021" +license = "UPL-1.0" +version = "0.0.1" links = "app" @@ -17,8 +17,11 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "../../../crates/roc_std" } libc = "0.2" +roc_highlight = { path = "../../../crates/highlight" } +roc_std = { path = "../../../crates/roc_std" } + + # Default features include building a binary that we don't need pulldown-cmark = { version = "0.9.2", default-features = false } diff --git a/examples/static-site-gen/platform/src/lib.rs b/examples/static-site-gen/platform/src/lib.rs index 7db426008e..33006038ce 100644 --- a/examples/static-site-gen/platform/src/lib.rs +++ b/examples/static-site-gen/platform/src/lib.rs @@ -116,7 +116,7 @@ fn run(input_dirname: &str, output_dirname: &str) -> Result<(), String> { let output_dir = { let dir = PathBuf::from(output_dirname); if !dir.exists() { - fs::create_dir(&dir).unwrap(); + fs::create_dir_all(&dir).unwrap(); } strip_windows_prefix( dir.canonicalize() @@ -143,18 +143,23 @@ fn run(input_dirname: &str, output_dirname: &str) -> Result<(), String> { let mut num_errors = 0; let mut num_successes = 0; for input_file in input_files { - match process_file(&input_dir, &output_dir, &input_file) { - Ok(()) => { - num_successes += 1; + match input_file.extension() { + Some(s) if s.eq("md".into()) => { + match process_file(&input_dir, &output_dir, &input_file) { + Ok(()) => { + num_successes += 1; + } + Err(e) => { + eprintln!( + "Failed to process file:\n\n ({:?})with error:\n\n {}", + &input_file, e + ); + num_errors += 1; + } + } } - Err(e) => { - eprintln!( - "Failed to process file:\n\n ({:?})with error:\n\n {}", - &input_file, e - ); - num_errors += 1; - } - } + _ => {} + }; } println!( @@ -170,11 +175,6 @@ fn run(input_dirname: &str, output_dirname: &str) -> Result<(), String> { } fn process_file(input_dir: &Path, output_dir: &Path, input_file: &Path) -> Result<(), String> { - match input_file.extension() { - Some(s) if s.eq("md".into()) => {} - _ => return Err("Only .md files are supported".into()), - }; - let input_relpath = input_file .strip_prefix(input_dir) .map_err(|e| e.to_string())? @@ -202,7 +202,74 @@ fn process_file(input_dir: &Path, output_dir: &Path, input_file: &Path) -> Resul options.remove(Options::ENABLE_SMART_PUNCTUATION); let parser = Parser::new_ext(&content_md, options); - html::push_html(&mut content_html, parser); + + // We'll build a new vector of events since we can only consume the parser once + let mut parser_with_highlighting = Vec::new(); + // As we go along, we'll want to highlight code in bundles, not lines + let mut code_to_highlight = String::new(); + // And track a little bit of state + let mut in_code_block = false; + let mut is_roc_code = false; + + for event in parser { + match event { + pulldown_cmark::Event::Code(cow_str) => { + let highlighted_html = + roc_highlight::highlight_roc_code_inline(cow_str.to_string().as_str()); + parser_with_highlighting.push(pulldown_cmark::Event::Html( + pulldown_cmark::CowStr::from(highlighted_html), + )); + } + pulldown_cmark::Event::Start(pulldown_cmark::Tag::CodeBlock(cbk)) => { + in_code_block = true; + is_roc_code = is_roc_code_block(&cbk); + } + pulldown_cmark::Event::End(pulldown_cmark::Tag::CodeBlock(_)) => { + if in_code_block { + match replace_code_with_static_file(&code_to_highlight, input_file) { + None => {} + // Check if the code block is actually just a relative + // path to a static file, if so replace the code with + // the contents of the file. + // ``` + // file:myCodeFile.roc + // ``` + Some(new_code_to_highlight) => { + code_to_highlight = new_code_to_highlight; + } + } + + // Format the whole multi-line code block as HTML all at once + let highlighted_html: String; + if is_roc_code { + highlighted_html = roc_highlight::highlight_roc_code(&code_to_highlight) + } else { + highlighted_html = format!("
{}
", &code_to_highlight) + } + + // And put it into the vector + parser_with_highlighting.push(pulldown_cmark::Event::Html( + pulldown_cmark::CowStr::from(highlighted_html), + )); + code_to_highlight = String::new(); + in_code_block = false; + } + } + pulldown_cmark::Event::Text(t) => { + if in_code_block { + // If we're in a code block, build up the string of text + code_to_highlight.push_str(&t); + } else { + parser_with_highlighting.push(pulldown_cmark::Event::Text(t)) + } + } + e => { + parser_with_highlighting.push(e); + } + } + } + + html::push_html(&mut content_html, parser_with_highlighting.into_iter()); let roc_relpath = RocStr::from(output_relpath.to_str().unwrap()); let roc_content_html = RocStr::from(content_html.as_str()); @@ -214,6 +281,12 @@ fn process_file(input_dir: &Path, output_dir: &Path, input_file: &Path) -> Resul println!("{} -> {}", input_file.display(), output_file.display()); + // Create parent directory if it doesn't exist + let parent_dir = output_file.parent().unwrap(); + if !parent_dir.exists() { + fs::create_dir_all(&parent_dir).unwrap(); + } + fs::write(output_file, rust_output_str).map_err(|e| format!("{}", e)) } @@ -234,9 +307,58 @@ fn find_files(dir: &Path, file_paths: &mut Vec) -> std::io::Result<()> /// and there seems to be no good way to strip it. So we resort to some string manipulation. pub fn strip_windows_prefix(path_buf: PathBuf) -> std::path::PathBuf { #[cfg(not(windows))] - return path_buf; + { + path_buf + } - let path_str = path_buf.display().to_string(); + #[cfg(windows)] + { + let path_str = path_buf.display().to_string(); - std::path::Path::new(path_str.trim_start_matches(r"\\?\")).to_path_buf() + std::path::Path::new(path_str.trim_start_matches(r"\\?\")).to_path_buf() + } +} + +fn is_roc_code_block(cbk: &pulldown_cmark::CodeBlockKind) -> bool { + match cbk { + pulldown_cmark::CodeBlockKind::Indented => false, + pulldown_cmark::CodeBlockKind::Fenced(cow_str) => { + if cow_str.contains("roc") { + true + } else { + false + } + } + } +} + +fn replace_code_with_static_file(code: &str, input_file: &Path) -> Option { + let input_dir = input_file.parent()?; + let trimmed_code = code.trim(); + + // Confirm the code block starts with a `file:` tag + match trimmed_code.strip_prefix("file:") { + None => None, + Some(path) => { + + // File must be located in input folder or sub-directory + if path.contains("../") { + panic!("ERROR File must be located within the input diretory!"); + } + + let file_path = input_dir.join(path); + + // Check file exists before opening + match file_path.try_exists() { + Err(_) | Ok(false) => { + panic!("ERROR File does not exist: \"{}\"", file_path.to_str().unwrap()); + } + Ok(true) => { + let vec_u8 = fs::read(file_path).ok()?; + + String::from_utf8(vec_u8).ok() + } + } + } + } } diff --git a/examples/static-site-gen/static-site.roc b/examples/static-site-gen/static-site.roc index cfc3a5e06b..e98827c39d 100644 --- a/examples/static-site-gen/static-site.roc +++ b/examples/static-site-gen/static-site.roc @@ -14,7 +14,7 @@ NavLink : { navLinks : List NavLink navLinks = [ - { url: "apple.html", title: "Exempli Gratia Pagina Pomi", text: "Apple" }, + { url: "subFolder/apple.html", title: "Exempli Gratia Pagina Pomi", text: "Apple" }, { url: "banana.html", title: "Exempli Gratia Pagina Musa", text: "Banana" }, { url: "cherry.html", title: "Exempli Pagina Cerasus", text: "Cherry" }, ] diff --git a/examples/typescript-interop/.gitignore b/examples/typescript-interop/.gitignore new file mode 100644 index 0000000000..034f0afafb --- /dev/null +++ b/examples/typescript-interop/.gitignore @@ -0,0 +1,4 @@ +build +node_modules +hello.js +libhello diff --git a/examples/typescript-interop/node/README.md b/examples/typescript-interop/node/README.md new file mode 100644 index 0000000000..c6afd426f2 --- /dev/null +++ b/examples/typescript-interop/node/README.md @@ -0,0 +1,64 @@ +# TypeScript Interop + +This is an example of calling Roc code from [TypeScript](https://www.typescriptlang.org/) on [Node.js](https://nodejs.org/en/). + +## Installation + +You'll need to have a C compiler installed, but most operating systems will have one already. +(e.g. macOS has `clang` installed by default, Linux usually has `gcc` by default, etc.) +All of these commands should be run from the same directory as this README file. + + +First, run this to install Node dependencies and generate the Makefile that will be +used by future commands. (You should only need to run this once.) + +``` +npm install +npx node-gyp configure +``` + +## Building the Roc library + +First, `cd` into this directory and run this in your terminal: + +``` +roc build --lib +``` + +This compiles your Roc code into a shared library in the current directory. The library's filename will be libhello plus an OS-specific extension (e.g. libhello.dylib on macOS). + +Next, run this to rebuild the C sources. + +``` +npx node-gyp build +``` + +Finally, run this to copy the generated TypeScript type definitions into the build directory: + +``` +cp addon.d.ts build/Release/ +``` + +You can verify that TypeScript sees the correct types with: + +``` +npx tsc hello.ts +``` + +### Try it out! + +Now that everything is built, you should be able to run the example with: + +``` +npx ts-node hello.ts +``` + +To rebuild after changing either the `demo.c` file or any `.roc` files, run: + +``` +roc build --lib && npx node-gyp build +``` + +## About this example + +This was created by following the [NodeJS addons](https://nodejs.org/dist/latest/docs/api/addons.html) tutorial and switching from C++ to C, then creating the `addon.d.ts` file to add types to the generated native Node module. \ No newline at end of file diff --git a/examples/typescript-interop/node/addon.d.ts b/examples/typescript-interop/node/addon.d.ts new file mode 100644 index 0000000000..513846bfad --- /dev/null +++ b/examples/typescript-interop/node/addon.d.ts @@ -0,0 +1 @@ +export function hello(arg: string): string; diff --git a/examples/typescript-interop/node/binding.gyp b/examples/typescript-interop/node/binding.gyp new file mode 100644 index 0000000000..12882559cd --- /dev/null +++ b/examples/typescript-interop/node/binding.gyp @@ -0,0 +1,12 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "demo.c" ], + "libraries": [ + "-lhello", + "-L<(module_root_dir)" + ] + } + ] +} diff --git a/examples/typescript-interop/node/demo.c b/examples/typescript-interop/node/demo.c new file mode 100644 index 0000000000..7bb1fed74f --- /dev/null +++ b/examples/typescript-interop/node/demo.c @@ -0,0 +1,419 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +napi_env napi_global_env; + +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) +{ + // WARNING: If roc_panic is called before napi_global_env is set, + // the result will be undefined behavior. So never call any Roc + // functions before setting napi_global_env! + napi_throw_error(napi_global_env, NULL, (char *)ptr); +} + +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); } + +// Reference counting + +// If the refcount is set to this, that means the allocation is +// stored in readonly memory in the binary, and we must not +// attempt to increment or decrement it; if we do, we'll segfault! +const ssize_t REFCOUNT_READONLY = 0; +const ssize_t REFCOUNT_ONE = (ssize_t)PTRDIFF_MIN; +const size_t MASK = (size_t)PTRDIFF_MIN; + +// Increment reference count, given a pointer to the first element in a collection. +// We don't need to check for overflow because in order to overflow a usize worth of refcounts, +// you'd need to somehow have more pointers in memory than the OS's virtual address space can hold. +void incref(uint8_t* bytes, uint32_t alignment) +{ + ssize_t *refcount_ptr = ((ssize_t *)bytes) - 1; + ssize_t refcount = *refcount_ptr; + + if (refcount != REFCOUNT_READONLY) { + *refcount_ptr = refcount + 1; + } +} + +// Decrement reference count, given a pointer to the first byte of a collection's elements. +// Then call roc_dealloc if nothing is referencing this collection anymore. +void decref_heap_bytes(uint8_t* bytes, uint32_t alignment) +{ + size_t extra_bytes = (sizeof(size_t) >= (size_t)alignment) ? sizeof(size_t) : (size_t)alignment; + ssize_t *refcount_ptr = ((ssize_t *)bytes) - 1; + ssize_t refcount = *refcount_ptr; + + if (refcount != REFCOUNT_READONLY) { + *refcount_ptr = refcount - 1; + + if (refcount == REFCOUNT_ONE) { + void *original_allocation = (void *)(refcount_ptr - (extra_bytes - sizeof(size_t))); + + roc_dealloc(original_allocation, alignment); + } + } +} + +// RocBytes (List U8) + +struct RocBytes +{ + uint8_t *bytes; + size_t len; + size_t capacity; +}; + +struct RocBytes empty_rocbytes() +{ + struct RocBytes ret = { + .len = 0, + .bytes = NULL, + .capacity = 0, + }; + + return ret; +} + +struct RocBytes init_rocbytes(uint8_t *bytes, size_t len) +{ + if (len == 0) + { + return empty_rocbytes(); + } + else + { + struct RocBytes ret; + size_t refcount_size = sizeof(size_t); + uint8_t *new_refcount = (uint8_t *)roc_alloc(len + refcount_size, __alignof__(size_t)); + uint8_t *new_content = new_refcount + refcount_size; + + ((ssize_t *)new_refcount)[0] = REFCOUNT_ONE; + + memcpy(new_content, bytes, len); + + ret.bytes = new_content; + ret.len = len; + ret.capacity = len; + + return ret; + } +} + +// RocStr + +struct RocStr +{ + uint8_t *bytes; + size_t len; + size_t capacity; +}; + +struct RocStr empty_roc_str() +{ + struct RocStr ret = { + .len = 0, + .bytes = NULL, + .capacity = MASK, + }; + + return ret; +} + +// Record the small string's length in the last byte of the given stack allocation +void write_small_str_len(size_t len, struct RocStr *str) { + ((uint8_t *)str)[sizeof(struct RocStr) - 1] = (uint8_t)len | 0b10000000; +} + +struct RocStr roc_str_init_small(uint8_t *bytes, size_t len) +{ + // Start out with zeroed memory, so that + // if we end up comparing two small RocStr values + // for equality, we won't risk memory garbage resulting + // in two equal strings appearing unequal. + struct RocStr ret = empty_roc_str(); + + // Copy the bytes into the stack allocation + memcpy(&ret, bytes, len); + + write_small_str_len(len, &ret); + + return ret; +} + +struct RocStr roc_str_init_large(uint8_t *bytes, size_t len, size_t capacity) +{ + // A large RocStr is the same as a List U8 (aka RocBytes) in memory. + struct RocBytes roc_bytes = init_rocbytes(bytes, len); + + struct RocStr ret = { + .len = roc_bytes.len, + .bytes = roc_bytes.bytes, + .capacity = roc_bytes.capacity, + }; + + return ret; +} + +bool is_small_str(struct RocStr str) { return ((ssize_t)str.capacity) < 0; } + +// Determine the length of the string, taking into +// account the small string optimization +size_t roc_str_len(struct RocStr str) +{ + uint8_t *bytes = (uint8_t *)&str; + uint8_t last_byte = bytes[sizeof(str) - 1]; + uint8_t last_byte_xored = last_byte ^ 0b10000000; + size_t small_len = (size_t)(last_byte_xored); + size_t big_len = str.len & PTRDIFF_MAX; // Account for seamless slices + + // 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; + } +} + +void decref_large_str(struct RocStr str) +{ + uint8_t* bytes; + + if ((ssize_t)str.len < 0) + { + // This is a seamless slice, so the bytes are located in the capacity slot. + bytes = (uint8_t*)(str.capacity << 1); + } + else + { + bytes = str.bytes; + } + + decref_heap_bytes(bytes, __alignof__(uint8_t)); +} + + +// Turn the given Node string into a RocStr and return it +napi_status node_string_into_roc_str(napi_env env, napi_value node_string, struct RocStr *roc_str) { + size_t len; + napi_status status; + + // Passing NULL for a buffer (and size 0) will make it write the length of the string into `len`. + // https://nodejs.org/api/n-api.html#napi_get_value_string_utf8 + status = napi_get_value_string_utf8(env, node_string, NULL, 0, &len); + + if (status != napi_ok) + { + return status; + } + + // Node's "write a string into this buffer" function always writes a null terminator, + // so capacity will need to be length + 1. + // https://nodejs.org/api/n-api.html#napi_get_value_string_utf8 + size_t capacity = len + 1; + + // Create a RocStr and write it into the out param + if (capacity < sizeof(struct RocStr)) + { + // If it can fit in a small string, use the string itself as the buffer. + // First, zero out those bytes; small strings need to have zeroes for any bytes + // that are not part of the string, or else comparisons between small strings might fail. + *roc_str = empty_roc_str(); + + // This writes the actual number of bytes copied into len. Theoretically they should be the same, + // but it could be different if the buffer was somehow smaller. This way we guarantee that + // the RocStr does not present any memory garbage to the user. + status = napi_get_value_string_utf8(env, node_string, (char*)roc_str, sizeof(struct RocStr), &len); + + if (status != napi_ok) + { + return status; + } + + // We have to write the length into the buffer *after* Node copies its bytes in, + // because Node will have written a null terminator, which we may need to overwrite. + write_small_str_len(len, roc_str); + } + else + { + // capacity was too big for a small string, so make a heap allocation and write into that. + uint8_t *buf = (uint8_t*)roc_alloc(capacity, __alignof__(char)); + + // This writes the actual number of bytes copied into len. Theoretically they should be the same, + // but it could be different if the buffer was somehow smaller. This way we guarantee that + // the RocStr does not present any memory garbage to the user. + status = napi_get_value_string_utf8(env, node_string, (char*)buf, capacity, &len); + + if (status != napi_ok) + { + // Something went wrong, so free the bytes we just allocated before returning. + roc_dealloc((void *)&buf, __alignof__(char *)); + + return status; + } + + *roc_str = roc_str_init_large(buf, len, capacity); + } + + return status; +} + +// Consume the given RocStr (decrement its refcount) after creating a Node string from it. +napi_value roc_str_into_node_string(napi_env env, struct RocStr roc_str) { + bool is_small = is_small_str(roc_str); + char* roc_str_contents; + + if (is_small) + { + // In a small string, the string itself contains its contents. + roc_str_contents = (char*)&roc_str; + } + else + { + roc_str_contents = (char*)roc_str.bytes; + } + + napi_status status; + napi_value answer; + + status = napi_create_string_utf8(env, roc_str_contents, roc_str_len(roc_str), &answer); + + if (status != napi_ok) + { + answer = NULL; + } + + // Decrement the RocStr because we consumed it. + if (!is_small) + { + decref_large_str(roc_str); + } + + return answer; +} + +// Create a Node string from the given RocStr. +// Don't decrement the RocStr's refcount. (To decrement it, use roc_str_into_node_string instead.) +napi_value roc_str_as_node_string(napi_env env, struct RocStr roc_str) { + bool is_small = is_small_str(roc_str); + char* roc_str_contents; + + if (is_small) + { + // In a small string, the string itself contains its contents. + roc_str_contents = (char*)&roc_str; + } + else + { + roc_str_contents = (char*)roc_str.bytes; + } + + napi_status status; + napi_value answer; + + status = napi_create_string_utf8(env, roc_str_contents, roc_str_len(roc_str), &answer); + + if (status != napi_ok) + { + return NULL; + } + + // Do not decrement the RocStr's refcount because we did not consume it. + + return answer; +} + +extern void roc__mainForHost_1_exposed_generic(struct RocStr *ret, struct RocStr *arg); + +// Receive a string value from Node and pass it to Roc as a RocStr, then get a RocStr +// back from Roc and convert it into a Node string. +napi_value call_roc(napi_env env, napi_callback_info info) { + napi_status status; + + // roc_panic needs a napi_env in order to throw a Node exception, so we provide this + // one globally in case roc_panic gets called during the execution of our Roc function. + // + // According do the docs - https://nodejs.org/api/n-api.html#napi_env - + // it's very important that the napi_env that was passed into "the initial + // native function" is the one that's "passed to any subsequent nested Node-API calls," + // so we must override this every time we call this function (as opposed to, say, + // setting it once during init). + napi_global_env = env; + + // Get the argument passed to the Node function + size_t argc = 1; + napi_value argv[1]; + + status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL); + + if (status != napi_ok) + { + return NULL; + } + + napi_value node_arg = argv[0]; + + struct RocStr roc_arg; + + status = node_string_into_roc_str(env, node_arg, &roc_arg); + + if (status != napi_ok) + { + return NULL; + } + + struct RocStr roc_ret; + // Call the Roc function to populate `roc_ret`'s bytes. + roc__mainForHost_1_exposed_generic(&roc_ret, &roc_arg); + + // Consume the RocStr to create the Node string. + return roc_str_into_node_string(env, roc_ret); +} + +napi_value init(napi_env env, napi_value exports) { + napi_status status; + napi_value fn; + + status = napi_create_function(env, NULL, 0, call_roc, NULL, &fn); + + if (status != napi_ok) + { + return NULL; + } + + status = napi_set_named_property(env, exports, "hello", fn); + + if (status != napi_ok) + { + return NULL; + } + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, init) diff --git a/examples/typescript-interop/node/hello.ts b/examples/typescript-interop/node/hello.ts new file mode 100644 index 0000000000..5156435002 --- /dev/null +++ b/examples/typescript-interop/node/hello.ts @@ -0,0 +1,3 @@ +import { hello } from './build/Release/addon' + +console.log("Roc says the following:", hello("Hello from TypeScript")); diff --git a/examples/typescript-interop/node/main.roc b/examples/typescript-interop/node/main.roc new file mode 100644 index 0000000000..158be20c3c --- /dev/null +++ b/examples/typescript-interop/node/main.roc @@ -0,0 +1,8 @@ +app "libhello" + packages { pf: "platform/main.roc" } + imports [] + provides [main] to pf + +main : Str -> Str +main = \message -> + "TypeScript said to Roc: \(message)! 🎉" diff --git a/examples/typescript-interop/node/package-lock.json b/examples/typescript-interop/node/package-lock.json new file mode 100644 index 0000000000..055bd71ea9 --- /dev/null +++ b/examples/typescript-interop/node/package-lock.json @@ -0,0 +1,1246 @@ +{ + "name": "node", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@types/node": "^18.15.3", + "node-gyp": "^9.3.1", + "ts-node": "^10.9.1", + "typescript": "^4.9.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", + "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", + "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", + "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/examples/typescript-interop/node/package.json b/examples/typescript-interop/node/package.json new file mode 100644 index 0000000000..1ef892e6b8 --- /dev/null +++ b/examples/typescript-interop/node/package.json @@ -0,0 +1,8 @@ +{ + "devDependencies": { + "@types/node": "^18.15.3", + "node-gyp": "^9.3.1", + "ts-node": "^10.9.1", + "typescript": "^4.9.5" + } +} diff --git a/examples/typescript-interop/node/platform/main.roc b/examples/typescript-interop/node/platform/main.roc new file mode 100644 index 0000000000..0410c7f9c1 --- /dev/null +++ b/examples/typescript-interop/node/platform/main.roc @@ -0,0 +1,10 @@ +platform "typescript-interop" + requires {} { main : Str -> Str } + exposes [] + packages {} + imports [] + provides [mainForHost] + +mainForHost : Str -> Str +mainForHost = \message -> + main message diff --git a/examples/typescript-interop/wasm/README.md b/examples/typescript-interop/wasm/README.md new file mode 100644 index 0000000000..f87f5c14cb --- /dev/null +++ b/examples/typescript-interop/wasm/README.md @@ -0,0 +1 @@ +# TODO \ No newline at end of file diff --git a/examples/virtual-dom-wip/platform/Html/Internal/Client.roc b/examples/virtual-dom-wip/platform/Html/Internal/Client.roc index 5f9dd16948..608941749c 100644 --- a/examples/virtual-dom-wip/platform/Html/Internal/Client.roc +++ b/examples/virtual-dom-wip/platform/Html/Internal/Client.roc @@ -723,7 +723,7 @@ expect # diff expect - State : { answer : Nat } + State : { answer : U32 } diffStateBefore : DiffState State diffStateBefore = { @@ -778,7 +778,7 @@ expect # initClientAppHelp expect - State : { answer : Nat } + State : { answer : U32 } init = \result -> when result is diff --git a/examples/virtual-dom-wip/platform/Html/Internal/Server.roc b/examples/virtual-dom-wip/platform/Html/Internal/Server.roc index bfcc0ffe74..ad2d64fc67 100644 --- a/examples/virtual-dom-wip/platform/Html/Internal/Server.roc +++ b/examples/virtual-dom-wip/platform/Html/Internal/Server.roc @@ -68,20 +68,20 @@ initServerApp = \app, initData, hostJavaScript -> insertRocScript : Html [], initData, Str, Str -> Result (Html []) [InvalidDocument] | initData has Encoding insertRocScript = \document, initData, wasmUrl, hostJavaScript -> + encode = + \value -> + value + |> Encode.toBytes Json.toUtf8 + |> Str.fromUtf8 + |> Result.withDefault "" + # Convert initData to JSON as a Roc Str, then convert the Roc Str to a JS string. # JSON won't have invalid UTF-8 in it, since it would be escaped as part of JSON encoding. jsInitData = - initData - |> Encode.toBytes Json.toUtf8 - |> Str.fromUtf8 - |> Encode.toBytes Json.toUtf8 - |> Str.fromUtf8 - |> Result.withDefault "" + initData |> encode |> encode + jsWasmUrl = - wasmUrl - |> Encode.toBytes Json.toUtf8 - |> Str.fromUtf8 - |> Result.withDefault "" + encode wasmUrl script : Html [] script = (element "script") [] [ diff --git a/examples/virtual-dom-wip/platform/Html/Internal/Shared.roc b/examples/virtual-dom-wip/platform/Html/Internal/Shared.roc index 757ebfedc7..92188fe5cb 100644 --- a/examples/virtual-dom-wip/platform/Html/Internal/Shared.roc +++ b/examples/virtual-dom-wip/platform/Html/Internal/Shared.roc @@ -43,7 +43,7 @@ Attribute state : [ CyclicStructureAccessor : [ ObjectField Str CyclicStructureAccessor, - ArrayIndex Nat CyclicStructureAccessor, + ArrayIndex U32 CyclicStructureAccessor, SerializableValue, ] diff --git a/examples/virtual-dom-wip/platform/src/server-side/host.zig b/examples/virtual-dom-wip/platform/src/server-side/host.zig index ce7e7a6737..4d89f99c7b 100644 --- a/examples/virtual-dom-wip/platform/src/server-side/host.zig +++ b/examples/virtual-dom-wip/platform/src/server-side/host.zig @@ -51,13 +51,13 @@ const hostJavaScriptBytes = @embedFile("../client-side/host.js"); pub fn main() u8 { const json = RocStr.fromSlice("42"); - defer json.deinit(); + defer json.decref(); const hostJavaScript = RocStr.fromSlice(hostJavaScriptBytes); - defer hostJavaScript.deinit(); + defer hostJavaScript.decref(); const result = roc__main_1_exposed(json, hostJavaScript); - defer result.payload.deinit(); + defer result.payload.decref(); const writer = if (result.isOk) std.io.getStdOut().writer() diff --git a/flake.nix b/flake.nix index 63bf7b5052..f68f74734f 100644 --- a/flake.nix +++ b/flake.nix @@ -44,7 +44,6 @@ xorg.libXrandr xorg.libXi xorg.libxcb - alsa-lib ]; darwinInputs = with pkgs; @@ -106,6 +105,8 @@ rust cargo-criterion # for benchmarks simple-http-server # to view roc website when trying out edits + wasm-pack # for repl_wasm + jq ]); in { diff --git a/getting_started/linux_x86_64.md b/getting_started/linux_x86_64.md index 72c0e73b3f..3bfbaea846 100644 --- a/getting_started/linux_x86_64.md +++ b/getting_started/linux_x86_64.md @@ -3,7 +3,7 @@ ## How to install Roc In order to develop in Roc, you need to install the Roc CLI, -which includes the Roc compiler and various helpful utilities. +which includes the Roc compiler and some helpful utilities. 1. Download the latest nightly from the assets [here](https://github.com/roc-lang/roc/releases). diff --git a/getting_started/macos_apple_silicon.md b/getting_started/macos_apple_silicon.md index 193b6969dd..6a26c534d7 100644 --- a/getting_started/macos_apple_silicon.md +++ b/getting_started/macos_apple_silicon.md @@ -5,7 +5,7 @@ :warning: We do not yet officially support **MacOS 13**. But, as long as you are **not** using a zig or wasm platform most things should work fine. In order to develop in Roc, you need to install the Roc CLI, -which includes the Roc compiler and various helpful utilities. +which includes the Roc compiler and some helpful utilities. 1. Download the latest nightly from the assets [here](https://github.com/roc-lang/roc/releases). @@ -18,7 +18,6 @@ which includes the Roc compiler and various helpful utilities. 1. Untar the archive: ```sh - mkdir roc_nightly-macos_apple_silicon- tar xf roc_nightly-macos_apple_silicon-.tar.gz cd roc_night ``` diff --git a/getting_started/macos_x86_64.md b/getting_started/macos_x86_64.md index bbceb7bf9e..10115d15cb 100644 --- a/getting_started/macos_x86_64.md +++ b/getting_started/macos_x86_64.md @@ -4,10 +4,8 @@ :warning: We do not yet officially support MacOS 13. But, as long as you are not using a zig or wasm platform most things should work fine. -:warning: Macos x86_64 nightly releases lag behind the others due to [unresolved test failures](https://github.com/roc-lang/roc/issues/4655). The `dbg` keyword is not yet supported, as well as importing platforms using URLs. For the platform you can use the path instead, like in [this example](https://github.com/roc-lang/roc/blob/main/examples/helloWorld.roc). - In order to develop in Roc, you need to install the Roc CLI, -which includes the Roc compiler and various helpful utilities. +which includes the Roc compiler and some helpful utilities. 1. Download the latest nightly from the assets [here](https://github.com/roc-lang/roc/releases). @@ -20,7 +18,6 @@ which includes the Roc compiler and various helpful utilities. 1. Untar the archive: ```sh - mkdir roc_nightly-macos_x86_64- tar xf roc_nightly-macos_x86_64-.tar.gz cd roc_night ``` diff --git a/nightly_benches/Cargo.toml b/nightly_benches/Cargo.toml index a79266821d..7bdb94afa6 100644 --- a/nightly_benches/Cargo.toml +++ b/nightly_benches/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "nightly_benches" -version = "0.0.1" -edition = "2021" -authors = ["The Roc Contributors"] -license = "UPL-1.0" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dependencies] [dev-dependencies] -criterion = { git = "https://github.com/Anton-4/criterion.rs"} cli_utils = { path = "../cli/cli_utils" } -criterion-perf-events ={ git = "https://github.com/Anton-4/criterion-perf-events" } -perfcnt = "0.7.1" + +criterion-perf-events.workspace = true +criterion.workspace = true +perfcnt.workspace = true [[bench]] name = "events_bench" diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index b5021b62fa..a79fdec5a5 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -14,6 +14,15 @@ Doc comments begin with `##` instead. Like Python, Roc does not have multiline comment syntax. +## Casing + +Like Elm, Roc uses camelCase for identifiers. Modules and types begin with a capital letter (eg. +Decode), variables (including type variables!) begin with a lower case letter (eg. request). +Acronyms also use camelCase despite being capitalized in English, eg. `xmlHttpRequest` for a +variable and `XmlHttpRequest` for a type. Each word starts with a capital letter, so if acronyms are +only capitals it's harder to see where the words start. eg. `XMLHTTPRequest` is less clear than +`XmlHttpRequest`, unless you already know the acronyms. + ## String Interpolation Roc strings work like Elm strings except that they support string interpolation. @@ -297,7 +306,7 @@ table { defaultConfig | height = 800, width = 600 } This way, as the caller I'm specifying only the `height` and `width` fields, and leaving the others to whatever is inside `defaultConfig`. Perhaps it also -has the fields `x` and `y`. +has the fields `title` and `description`. In Roc, you can do this like so: @@ -305,7 +314,7 @@ In Roc, you can do this like so: table { height: 800, width: 600 } ``` -...and the `table` function will fill in its default values for `x` and `y`. +...and the `table` function will fill in its default values for `title` and `description`. There is no need to use a `defaultConfig` record. Here's how that `table` function would be implemented in Roc: @@ -1304,7 +1313,7 @@ Some differences to note: - `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). - No `Basics`. You use everything from the standard library fully-qualified; e.g. `Bool.not` or `Num.negate` or `Num.ceiling`. There is no `Never` because `[]` already serves that purpose. (Roc's standard library doesn't include an equivalent of `Basics.never`, but it's one line of code and anyone can implement it: `never = \a -> never a`.) -- No `Tuple`. Roc doesn't have tuple syntax. As a convention, `Pair` can be used to represent tuples (e.g. `List.zip : List a, List b -> List [Pair a b]*`), but this comes up infrequently compared to languages that have dedicated syntax for it. +- No `Tuple` module, but there is syntax support for tuples which allows not only destructuring (like in Elm) but also direct field access - which looks like record field access, but with numbered indices instead of named fields. For example, the Elm code `Tuple.first ( "a", "b" )` and `Tuple.second ( "a", "b" )` could be written in Roc as `("a", "b").0` and `("a", "b").1`. Roc tuples can also have more than two fields. - No `Task`. By design, platform authors implement `Task` (or don't; it's up to them) - it's not something that really *could* be usefully present in Roc's standard library. - No `Process`, `Platform`, `Cmd`, or `Sub` - similarly to `Task`, these are things platform authors would include, or not. - No `Debug`. Roc has a [built-in `dbg` keyword](https://www.roc-lang.org/tutorial#debugging) instead of `Debug.log` and a [`crash` keyword](https://www.roc-lang.org/tutorial#crashing) instead of `Debug.todo`. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e5858aa618..8751835c29 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -19,4 +19,5 @@ components = [ targets = [ "wasm32-wasi", # for test_wasm.sh + "wasm32-unknown-unknown", # for repl_wasm ] diff --git a/www/build.sh b/www/build.sh index 197ae2cf06..37701971e1 100755 --- a/www/build.sh +++ b/www/build.sh @@ -58,46 +58,43 @@ find www/build/builtins -type f -name 'index.html' -exec sed -i 's!!
roc_releases.json + echo 'Building tutorial.html from tutorial.md...' + mkdir www/build/tutorial + + cargo run --release --bin roc run www/generate_tutorial/src/tutorial.roc -- www/generate_tutorial/src/input/ www/build/tutorial/ else + echo 'Fetching latest roc nightly...' + + # we assume that we're on a netlify server if GITHUB_TOKEN_READ_ONLY is set curl --request GET \ --url https://api.github.com/repos/roc-lang/roc/releases \ -u $GITHUB_TOKEN_READ_ONLY \ --output roc_releases.json -fi -# get the url of the latest release -if [ "$(uname)" == "Linux" ]; then RELEASE_MACHINE="linux_x86_64" -elif [ "$(uname)" == "Darwin" ] && [ "$(uname -m)" == "arm64" ]; then - RELEASE_MACHINE="macos_apple_silicon" -elif [ "$(uname)" == "Darwin" ] && [ "$(uname -m)" == "x86_64" ]; then - RELEASE_MACHINE="macos_x86_64" -else - RELEASE_MACHINE="UNSUPPORTED_MACHINE" + + export ROC_RELEASE_URL=$(./ci/get_latest_release_url.sh $RELEASE_MACHINE) + # get roc release archive + curl -OL $ROC_RELEASE_URL + # extract archive + ls | grep "roc_nightly" | xargs tar -xzvf + # delete archive + ls | grep "roc_nightly.*tar.gz" | xargs rm + # simplify dir name + mv roc_nightly* roc_nightly + + echo 'Building tutorial.html from tutorial.md...' + mkdir www/build/tutorial + + ./roc_nightly/roc version + ./roc_nightly/roc run www/generate_tutorial/src/tutorial.roc -- www/generate_tutorial/src/input/ www/build/tutorial/ + + # cleanup + rm -rf roc_nightly roc_releases.json fi -export ROC_RELEASE_URL=$(./ci/get_latest_release_url.sh $RELEASE_MACHINE) -# get roc release archive -curl -OL $ROC_RELEASE_URL -# extract archive -ls | grep "roc_nightly" | xargs tar -xzvf -# delete archive -ls | grep "roc_nightly.*tar.gz" | xargs rm -# simplify dir name -mv roc_nightly* roc_nightly - -echo 'Building tutorial.html from tutorial.md...' -mkdir www/build/tutorial -./roc_nightly/roc version -./roc_nightly/roc run www/generate_tutorial/src/tutorial.roc -- www/generate_tutorial/src/input/ www/build/tutorial/ mv www/build/tutorial/tutorial.html www/build/tutorial/index.html -# cleanup -rm -rf roc_nightly roc_releases.json - popd diff --git a/www/generate_tutorial/src/input/tutorial.md b/www/generate_tutorial/src/input/tutorial.md index f5e2df4dc2..1688ec26c9 100644 --- a/www/generate_tutorial/src/input/tutorial.md +++ b/www/generate_tutorial/src/input/tutorial.md @@ -20,7 +20,7 @@ Try typing this in the REPL and pressing Enter: The REPL should cheerfully display the following: -
"Hello, World!" : Str                # val1
+
"Hello, World!" : Str                # val1
Congratulations! You've just written your first Roc code. @@ -38,8 +38,8 @@ You should see the same `"Hello, World!"` line as before. You can also assign specific names to expressions. Try entering these lines: -
greeting = "Hi"
-
audience = "World"
+
greeting = "Hi"
+
audience = "World"
From now until you exit the REPL, you can refer to either `greeting` or `audience` by those names! @@ -47,11 +47,11 @@ From now until you exit the REPL, you can refer to either `greeting` or `audienc You can combine named strings together using _string interpolation_, like so: -
"\(greeting) there, \(audience)!"
+
"\(greeting) there, \(audience)!"
If you put this into the REPL, you should see this output: -
"Hi there, World!" : Str                # val2
+
"Hi there, World!" : Str                # val2
Notice that the REPL printed `# val2` here. This works just like `# val1` did before, but it chose the name `val2` for this expression because `val1` was already taken. As we continue entering more expressions into the REPL, you'll see more and more of these generated names—but they won't be mentioned again in this tutorial, since they're just a convenience. @@ -83,7 +83,7 @@ Remember back in the [string interpolation](#string-interpolation) section when
Str.concat "Hi " "there!"
 
-"Hi there!" : Str
+"Hi there!" : Str
 
Here we're calling the `Str.concat` function and passing two arguments: the string `"Hi "` and the string `"there!"`. This _concatenates_ the two strings together (that is, it puts one after the other) and returns the resulting combined string of `"Hi there!"`. @@ -94,7 +94,7 @@ That said, just like in the arithmetic example above, we can use parentheses to
Str.concat "Birds: " (Num.toStr 42)
 
-"Birds: 42" : Str
+"Birds: 42" : Str
 
This calls `Num.toStr` on the number `42`, which converts it into the string `"42"`, and then passes that string as the second argument to `Str.concat`. @@ -120,20 +120,19 @@ We'll get into more depth about modules later, but for now you can think of a mo ## [Building an Application](#building-an-application) {#building-an-application} -> **For NixOS:** URL imports don't work yet. Instead you'll have to clone [roc-lang/basic-cli](https://github.com/roc-lang/basic-cli) locally and use it like [this](https://github.com/roc-lang/roc/issues/4655#issuecomment-1336215883). - Let's move out of the REPL and create our first Roc application! Make a file named `main.roc` and put this in it: -
app "hello"
-    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" }
-    imports [pf.Stdout]
-    provides [main] to pf
+```roc
+app "hello"
+    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" }
+    imports [pf.Stdout]
+    provides [main] to pf
 
-main =
-    Stdout.line "I'm a Roc application!"
-
+main = + Stdout.line "I'm a Roc application!" +``` Try running this with: @@ -149,17 +148,18 @@ Congratulations, you've written your first Roc application! We'll go over what t Try replacing the `main` line with this: -
birds = 3
+```roc
+birds = 3
 
-iguanas = 2
+iguanas = 2
 
-total = Num.toStr (birds + iguanas)
+total = Num.toStr (birds + iguanas)
 
-main =
-    Stdout.line "There are \(total) animals."
-
+main = + Stdout.line "There are \(total) animals." +``` -Now run `roc dev` again. This time the "Downloading …" message won't appear; the file has been cached from last time, and won't need to be downloaded again. +Now run `roc dev` again. This time the "Downloading ..." message won't appear; the file has been cached from last time, and won't need to be downloaded again. You should see this: @@ -177,45 +177,48 @@ Once we have a def, we can use its name in other expressions. For example, the ` You can name a def using any combination of letters and numbers, but they have to start with a letter. - +**Note:** Defs are constant; they can't be reassigned. We'd get an error if we wrote these two defs in the same scope: -## [Defining Functions](#defining-functions) {#defining-functions} +```roc +birds = 3 +birds = 2 +``` + +### [Defining Functions](#defining-functions) {#defining-functions} So far we've called functions like `Num.toStr`, `Str.concat`, and `Stdout.line`. Next let's try defining a function of our own. -
birds = 3
+```roc
+birds = 3
 
-iguanas = 2
+iguanas = 2
 
-total = Num.toStr (birds + iguanas)
+total = addAndStringify birds iguanas
 
-main =
-    Stdout.line "There are \(total) animals."
+main =
+    Stdout.line "There are \(total) animals."
 
-addAndStringify = \num1, num2 ->
-    Num.toStr (num1 + num2)
-
+addAndStringify = \num1, num2 -> + Num.toStr (num1 + num2) +``` This new `addAndStringify` function we've defined accepts two numbers, adds them, calls `Num.toStr` on the result, and returns that. The `\num1, num2 ->` syntax defines a function's arguments, and the expression after the `->` is the body of the function. Whenever a function gets called, its body expression gets evaluated and returned. -## [if-then-else](#if-then-else) {#if-then-else} +### [if-then-else](#if-then-else) {#if-then-else} Let's modify this function to return an empty string if the numbers add to zero. -
addAndStringify = \num1, num2 ->
-    sum = num1 + num2
+```roc
+addAndStringify = \num1, num2 ->
+    sum = num1 + num2
 
-    if sum == 0 then
-        ""
-    else
-        Num.toStr (num1 + num2)
-
+ if sum == 0 then + "" + else + Num.toStr (num1 + num2) +``` We did two things here: @@ -228,30 +231,32 @@ Every `if` must be accompanied by both `then` and also `else`. Having an `if` wi We can combine `if` and `else` to get `else if`, like so: -
addAndStringify = \num1, num2 ->
-    sum = num1 + num2
+```roc
+addAndStringify = \num1, num2 ->
+    sum = num1 + num2
 
-    if sum == 0 then
-        ""
-    else if sum < 0 then
-        "negative"
-    else
-        Num.toStr (num1 + num2)
-
+ if sum == 0 then + "" + else if sum < 0 then + "negative" + else + Num.toStr (num1 + num2) +``` Note that `else if` is not a separate language keyword! It's just an `if`/`else` where the `else` branch contains another `if`/`else`. This is easier to see with different indentation: -
addAndStringify = \num1, num2 ->
-    sum = num1 + num2
+```roc
+addAndStringify = \num1, num2 ->
+    sum = num1 + num2
 
-    if sum == 0 then
-        ""
-    else
-        if sum < 0 then
-            "negative"
-        else
-            Num.toStr (num1 + num2)
-
+ if sum == 0 then + "" + else + if sum < 0 then + "negative" + else + Num.toStr (num1 + num2) +``` This differently-indented version is equivalent to writing `else if sum < 0 then` on the same line, although the convention is to use the original version's style. @@ -259,7 +264,9 @@ This differently-indented version is equivalent to writing `else if sum < 0 then This is a comment in Roc: -# The `name` field is unused by addAndStringify +```roc +# The 'name' field is unused by addAndStringify +``` Whenever you write `#` it means that the rest of the line is a comment, and will not affect the running program. Roc does not have multiline comment syntax. @@ -268,11 +275,12 @@ running program. Roc does not have multiline comment syntax. Comments that begin with `##` are "doc comments" which will be included in generated documentation (`roc docs`). They can include code blocks by adding five spaces after `##`. -
## This is a comment for documentation, and includes a code block.
+```roc
+## This is a comment for documentation, and includes a code block.
 ##
 ##     x = 2
-##     expect x == 2
-
+## expect x == 2 +``` Like other comments, doc comments do not affect the running program. @@ -280,14 +288,15 @@ Like other comments, doc comments do not affect the running program. [Print debugging](https://en.wikipedia.org/wiki/Debugging#Techniques) is the most common debugging technique in the history of programming, and Roc has a `dbg` keyword to facilitate it. Here's an example of how to use `dbg`: -
pluralize = \singular, plural, count ->
-    dbg count
+```roc
+pluralize = \singular, plural, count ->
+    dbg count
 
-    if count == 1 then
+    if count == 1 then
         singular
-    else
+    else
         plural
-
+``` Whenever this `dbg` line of code is reached, the value of `count` will be printed to [stderr](), along with the source code file and line number where the `dbg` itself was written: @@ -297,11 +306,15 @@ Here, `[pluralize.roc 6:8]` tells us that this `dbg` was written in the file `pl You can give `dbg` any expression you like, for example: -dbg Str.concat singular plural +```roc +dbg Str.concat singular plural +``` An easy way to print multiple values at a time is to wrap them in a tag, for example a concise tag like `T`: -dbg T "the value of `count` is:" count +```roc +dbg T "the value of count is:" count +``` > **Note:** `dbg` is a debugging tool, and is only available when running your program via a `roc` subcommand (for example using `roc dev`, `roc run`, or `roc test`). When you build a standalone application with `roc build`, any uses of `dbg` won't be included! @@ -309,11 +322,12 @@ An easy way to print multiple values at a time is to wrap them in a tag, for exa Currently our `addAndStringify` function takes two arguments. We can instead make it take one argument like so: -
total = addAndStringify { birds: 5, iguanas: 7 }
+```roc
+total = addAndStringify { birds: 5, iguanas: 7 }
 
-addAndStringify = \counts ->
-    Num.toStr (counts.birds + counts.iguanas)
-
+addAndStringify = \counts -> + Num.toStr (counts.birds + counts.iguanas) +``` The function now takes a _record_, which is a group of named values. Records are not [objects](https://en.wikipedia.org/wiki/Object_(computer_science)); they don't have methods or inheritance, they just store information. @@ -329,13 +343,15 @@ When we use [`==`](/builtins/Bool#isEq) on records, it compares all the fields i The `addAndStringify` function will accept any record with at least the fields `birds` and `iguanas`, but it will also accept records with more fields. For example: -
total = addAndStringify { birds: 5, iguanas: 7 }
-
-# The `name` field is unused by addAndStringify
-totalWithNote = addAndStringify { birds: 4, iguanas: 3, name: "Whee!" }
+```roc
+total = addAndStringify { birds: 5, iguanas: 7 }
 
-addAndStringify = \counts ->
-    Num.toStr (counts.birds + counts.iguanas)
+# The `note` field is unused by addAndStringify +totalWithNote = addAndStringify { birds: 4, iguanas: 3, note: "Whee!" } + +addAndStringify = \counts -> + Num.toStr (counts.birds + counts.iguanas) +``` This works because `addAndStringify` only uses `counts.birds` and `counts.iguanas`. If we were to use `counts.note` inside `addAndStringify`, then we would get an error because `total` is calling `addAndStringify` passing a record that doesn't have a `note` field. @@ -345,13 +361,14 @@ Roc has a couple of shorthands you can use to express some record-related operat Instead of writing `\record -> record.x` we can write `.x` and it will evaluate to the same thing: a function that takes a record and returns its `x` field. You can do this with any field you want. For example: -
# returnFoo is a function that takes a record
-# and returns the `foo` field of that record.
-returnFoo = .foo
+```roc
+# returnFoo is a function that takes a record
+# and returns the `foo` field of that record.
+returnFoo = .foo
 
-returnFoo { foo : "hi!", bar : "blah" }
-# returns "hi!"
-
+returnFoo { foo: "hi!", bar: "blah" } +# returns "hi!" +``` Sometimes we assign a def to a field that happens to have the same name—for example, `{ x: x }`. In these cases, we shorten it to writing the name of the def alone—for example, `{ x }`. We can do this with as many fields as we like; here are several different ways to define the same record: @@ -365,30 +382,35 @@ In these cases, we shorten it to writing the name of the def alone—for example We can use _destructuring_ to avoid naming a record in a function argument, instead giving names to its individual fields: -
addAndStringify = \{ birds, iguanas } ->
-    Num.toStr (birds + iguanas)
-
+```roc +addAndStringify = \{ birds, iguanas } -> + Num.toStr (birds + iguanas) +``` Here, we've _destructured_ the record to create a `birds` def that's assigned to its `birds` field, and an `iguanas` def that's assigned to its `iguanas` field. We can customize this if we like: -
addAndStringify = \{ birds, iguanas: lizards } ->
-    Num.toStr (birds + lizards)
-
+```roc +addAndStringify = \{ birds, iguanas: lizards } -> + Num.toStr (birds + lizards) +``` In this version, we created a `lizards` def that's assigned to the record's `iguanas` field. (We could also do something similar with the `birds` field if we like.) Finally, destructuring can be used in defs too: -
{ x, y } = { x : 5, y : 10 }
+```roc +{ x, y } = { x: 5, y: 10 } +``` ### [Making records from other records](#making-records-from-other-records) {#making-records-from-other-records} So far we've only constructed records from scratch, by specifying all of their fields. We can also construct new records by using another record to use as a starting point, and then specifying only the fields we want to be different. For example, here are two ways to get the same record: -
original = { birds: 5, zebras : 2, iguanas : 7, goats : 1 }
-fromScratch = { birds : 4, zebras : 2, iguanas : 3, goats : 1 }
-fromOriginal = { original & birds : 4, iguanas : 3 }
-
+```roc +original = { birds: 5, zebras: 2, iguanas: 7, goats: 1 } +fromScratch = { birds: 4, zebras: 2, iguanas: 3, goats: 1 } +fromOriginal = { original & birds: 4, iguanas: 3 } +``` The `fromScratch` and `fromOriginal` records are equal, although they're defined in different ways. @@ -398,18 +420,70 @@ The `fromScratch` and `fromOriginal` records are equal, although they're defined Note that `&` can't introduce new fields to a record, or change the types of existing fields. (Trying to do either of these will result in an error at build time!) +## [Optional Record Fields](#optional-record-fields) {#optional-record-fields} + +Roc supports optional record fields using the `?` operator. This can be a useful pattern where you pass a function a record of configuration values, some of which you'd like to provide defaults for. + +In Roc you can write a function like: + +```roc +table = \{ + height, + width, + title? "oak", + description? "a wooden table" + } + -> +``` + +This is using *optional field destructuring* to destructure a record while +also providing default values for any fields that might be missing. + +Here's the type of `table`: + +```roc +table : + { + height : Pixels, + width : Pixels, + title ? Str, + description ? Str, + } + -> Table +``` + +This says that `table` takes a record with two *required* fields, `height` and +`width`, and two *optional* fields, `title` and `description`. It also says that +the `height` and `width` fields have the type `Pixels`, a type alias for some +numeric type, and the `title` and `description` fields have the type `Str`. +This means you can choose to omit the `title`, `description`, or both fields, when calling the function... but if you provide them, they must have the type `Str`. + +This is also the type that would have been inferred for `table` if no annotation +had been written. Roc's compiler can tell from the destructuring syntax +`title ? ""` that `title` is an optional field, and that it has the type `Str`. +These default values can reference other expressions in the record destructure; if you wanted, you could write `{ height, width, title ? "", description ? Str.concat "A table called " title }`. + +Destructuring is the only way to implement a record with optional fields. For example, if you write the expression `config.title` and `title` is an +optional field, you'll get a compile error. + +This means it's never possible to end up with an *optional value* that exists +outside a record field. Optionality is a concept that exists only in record +fields, and it's intended for the use case of config records like this. The +ergonomics of destructuring mean this wouldn't be a good fit for data modeling, consider using a `Result` type instead. + ## [Tags](#tags) {#tags} Sometimes we want to represent that something can have one of several values. For example: -
stoplightColor =
-    if something > 0 then
+```roc
+stoplightColor =
+    if something > 0 then
         Red
-    else if something == 0 then
+    else if something == 0 then
         Yellow
-    else
+    else
         Green
-
+``` Here, `stoplightColor` can have one of three values: `Red`, `Yellow`, or `Green`. The capitalization is very important! If these were lowercase (`red`, `yellow`, `green`), then they would refer to defs. However, because they are capitalized, they instead refer to _tags_. @@ -417,74 +491,80 @@ A tag is a literal value just like a number or a string. Similarly to how I can Let's say we wanted to turn `stoplightColor` from a `Red`, `Green`, or `Yellow` into a string. Here's one way we could do that: -
stoplightStr =
-    if stoplightColor == Red then
-        "red"
-    else if stoplightColor == Green then
-        "green"
-    else
-        "yellow"
-
+```roc +stoplightStr = + if stoplightColor == Red then + "red" + else if stoplightColor == Green then + "green" + else + "yellow" +``` We can express this logic more concisely using `when`/`is` instead of `if`/`then`: -
stoplightStr =
-    when stoplightColor is
-        Red -> "red"
-        Green -> "green"
-        Yellow -> "yellow"
-
+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + Green -> "green" + Yellow -> "yellow" +``` This results in the same value for `stoplightStr`. In both the `when` version and the `if` version, we have three conditional branches, and each of them evaluates to a string. The difference is how the conditions are specified; here, we specify between `when` and `is` that we're making comparisons against `stoplightColor`, and then we specify the different things we're comparing it to: `Red`, `Green`, and `Yellow`. Besides being more concise, there are other advantages to using `when` here. 1. We don't have to specify an `else` branch, so the code can be more self-documenting about exactly what all the options are. -2. We get more compiler help. If we try deleting any of these branches, we'll get a compile-time error saying that we forgot to cover a case that could come up. For example, if we delete the `Green ->` branch, the compiler will say that we didn't handle the possibility that `stoplightColor` could be `Green`. It knows this because `Green` is one of the possibilities in our `stoplightColor = if …` definition. +2. We get more compiler help. If we try deleting any of these branches, we'll get a compile-time error saying that we forgot to cover a case that could come up. For example, if we delete the `Green ->` branch, the compiler will say that we didn't handle the possibility that `stoplightColor` could be `Green`. It knows this because `Green` is one of the possibilities in our `stoplightColor = if ...` definition. -We can still have the equivalent of an `else` branch in our `when` if we like. Instead of writing `else`, we write `\_ ->` like so: +We can still have the equivalent of an `else` branch in our `when` if we like. Instead of writing `else`, we write `_ ->` like so: -
stoplightStr =
-    when stoplightColor is
-        Red -> "red"
-        _ -> "not red"
-
+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + _ -> "not red" +``` This lets us more concisely handle multiple cases. However, it has the downside that if we add a new case - for example, if we introduce the possibility of `stoplightColor` being `Orange`, the compiler can no longer tell us we forgot to handle that possibility in our `when`. After all, we are handling it - just maybe not in the way we'd decide to if the compiler had drawn our attention to it! We can make this `when` _exhaustive_ (that is, covering all possibilities) without using `_ ->` by using `|` to specify multiple matching conditions for the same branch: -
stoplightStr =
-    when stoplightColor is
-        Red -> "red"
-        Green | Yellow -> "not red"
-
+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> "not red" +``` You can read `Green | Yellow` as "either `Green` or `Yellow`". By writing it this way, if we introduce the possibility that `stoplightColor` can be `Orange`, we'll get a compiler error telling us we forgot to cover that case in this `when`, and then we can handle it however we think is best. We can also combine `if` and `when` to make branches more specific: -
stoplightStr =
-    when stoplightColor is
-        Red -> "red"
-        Green | Yellow if contrast > 75 -> "not red, but very high contrast"
-        Green | Yellow if contrast > 50 -> "not red, but high contrast"
-        Green | Yellow -> "not red"
-
+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow if contrast > 75 -> "not red, but very high contrast" + Green | Yellow if contrast > 50 -> "not red, but high contrast" + Green | Yellow -> "not red" +``` This will give the same answer for `stoplightStr` as if we had written the following: -
stoplightStr =
-    when stoplightColor is
-        Red -> "red"
-        Green | Yellow ->
-            if contrast > 75 then
-                "not red, but very high contrast"
-            else if contrast > 50 then
-                "not red, but high contrast"
-            else
-                "not red"
-
+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> + if contrast > 75 then + "not red, but very high contrast" + else if contrast > 50 then + "not red, but high contrast" + else + "not red" +``` Either style can be a reasonable choice depending on the circumstances. @@ -492,22 +572,23 @@ Either style can be a reasonable choice depending on the circumstances. Tags can have _payloads_—that is, values inside them. For example: -
stoplightColor =
-    if something > 100 then
+```roc
+stoplightColor =
+    if something > 100 then
         Red
-    else if something > 0 then
+    else if something > 0 then
         Yellow
-    else if something == 0 then
+    else if something == 0 then
         Green
-    else
-        Custom "some other color"
+    else
+        Custom "some other color"
 
-stoplightStr =
-    when stoplightColor is
-        Red -> "red"
-        Green | Yellow -> "not red"
-        Custom description -> description
-
+stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> "not red" + Custom description -> description +``` This makes two changes to our earlier `stoplightColor` / `stoplightStr` example. @@ -524,17 +605,18 @@ We refer to whatever comes before a `->` in a `when` expression as a _pattern_ You can also pattern match on lists, like so: -
when myList is
-    [] -> 0 # the list is empty
-    [Foo, ..] -> 1 # it starts with a Foo tag
-    [_, ..] -> 2 # it contains at least one element, which we ignore
-    [Foo, Bar, ..] -> 3 # it starts with a Foo tag followed by a Bar tag
-    [Foo, Bar, Baz] -> 4 # it has exactly 3 elements: Foo, Bar, and Baz
-    [Foo, a, ..] -> 5 # its first element is Foo, and its second we name `a`
-    [Ok a, ..] -> 6 # it starts with an Ok containing a payload named `a`
-    [.., Foo] -> 7 # it ends with a Foo tag
-    [A, B, .., C, D] -> 8 # it has certain elements at the beginning and end
-
+```roc +when myList is + [] -> 0 # the list is empty + [Foo, ..] -> 1 # it starts with a Foo tag + [_, ..] -> 2 # it contains at least one element, which we ignore + [Foo, Bar, ..] -> 3 # it starts with a Foo tag followed by a Bar tag + [Foo, Bar, Baz] -> 4 # it has exactly 3 elements: Foo, Bar, and Baz + [Foo, a, ..] -> 5 # its first element is Foo, and its second we name `a` + [Ok a, ..] -> 6 # it starts with an Ok containing a payload named `a` + [.., Foo] -> 7 # it ends with a Foo tag + [A, B, .., C, D] -> 8 # it has certain elements at the beginning and end +``` This can be both more concise and more efficient (at runtime) than calling [`List.get`](https://www.roc-lang.org/builtins/List#get) multiple times, since each call to `get` requires a separate conditional to handle the different `Result`s they return. @@ -544,7 +626,7 @@ This can be both more concise and more efficient (at runtime) than calling [`Lis In many programming languages, `true` and `false` are special language keywords that refer to the two [boolean](https://en.wikipedia.org/wiki/Boolean_data_type) values. In Roc, booleans do not get special keywords; instead, they are exposed as the ordinary values `Bool.true` and `Bool.false`. -This design is partly to keep the number of special keywords in the language smaller, but mainly to suggest how booleans are intended be used in Roc: for [_boolean logic_](https://en.wikipedia.org/wiki/Boolean_algebra) (`&&`, `||`, and so on) as opposed to for data modeling. Tags are the preferred choice for data modeling, and having tag values be more concise than boolean values helps make this preference clear. +This design is partly to keep the number of special keywords in the language smaller, but mainly to suggest how booleans are intended to be used in Roc: for [_boolean logic_](https://en.wikipedia.org/wiki/Boolean_algebra) (`&&`, `||`, and so on) as opposed to for data modeling. Tags are the preferred choice for data modeling, and having tag values be more concise than boolean values helps make this preference clear. As an example of why tags are encouraged for data modeling, in many languages it would be common to write a record like `{ name: "Richard", isAdmin: Bool.true }`, but in Roc it would be preferable to write something like `{ name: "Richard", role: Admin }`. At first, the `role` field might only ever be set to `Admin` or `Normal`, but because the data has been modeled using tags instead of booleans, it's much easier to add other alternatives in the future, like `Guest` or `Moderator` - some of which might also want payloads. @@ -552,11 +634,15 @@ As an example of why tags are encouraged for data modeling, in many languages it Another thing we can do in Roc is to make a _list_ of values. Here's an example: -names = ["Sam", "Lee", "Ari"] +```roc +names = ["Sam", "Lee", "Ari"] +``` This is a list with three elements in it, all strings. We can add a fourth element using `List.append` like so: -List.append names "Jess" +```roc +List.append names "Jess" +``` This returns a **new** list with `"Jess"` after `"Ari"`, and doesn't modify the original list at all. All values in Roc (including lists, but also records, strings, numbers, and so on) are immutable, meaning whenever we want to "change" them, we want to instead pass them to a function which returns some variation of what was passed in. @@ -564,8 +650,9 @@ This returns a **new** list with `"Jess"` after `"Ari"`, and doesn't modify the A common way to transform one list into another is to use `List.map`. Here's an example of how to use it: -List.map [1, 2, 3] \num -> num * 2 - +```roc +List.map [1, 2, 3] \num -> num * 2 +``` This returns `[2, 4, 6]`. @@ -578,9 +665,11 @@ It then returns a list which it creates by calling the given function on each el We can also give `List.map` a named function, instead of an anonymous one: -List.map [1, 2, 3] Num.isOdd +```roc +List.map [1, 2, 3] Num.isOdd +``` -This `Num.isOdd` function returns `Bool.true` if it's given an odd number, and `Bool.false` otherwise. So `Num.isOdd 5` returns true and `Num.isOdd 2` returns false. +This `Num.isOdd` function returns `Bool.true` if it's given an odd number, and `Bool.false` otherwise. So `Num.isOdd 5` returns `Bool.true` and `Num.isOdd 2` returns `Bool.false`. As such, calling `List.map [1, 2, 3] Num.isOdd` returns a new list of `[Bool.true, Bool.false, Bool.true]`. @@ -588,22 +677,25 @@ As such, calling `List.map [1, 2, 3] Num.isOdd` returns a new list of `[Bool.tru If we tried to give `List.map` a function that didn't work on the elements in the list, then we'd get an error at compile time. Here's a valid, and then an invalid example: -
# working example
-List.map [-1, 2, 3, -4] Num.isNegative
-# returns [Bool.true, Bool.false, Bool.false, Bool.true]
-
+```roc +# working example +List.map [-1, 2, 3, -4] Num.isNegative +# returns [Bool.true, Bool.false, Bool.false, Bool.true] +``` -
# invalid example
-List.map ["A", "B", "C"] Num.isNegative
-# error: isNegative doesn't work on strings!
-
+```roc +# invalid example +List.map ["A", "B", "C"] Num.isNegative +# error: isNegative doesn't work on strings! +``` Because `Num.isNegative` works on numbers and not strings, calling `List.map` with `Num.isNegative` and a list of numbers works, but doing the same with a list of strings doesn't work. This wouldn't work either: -
List.map ["A", "B", "C", 1, 2, 3] Num.isNegative
-
+```roc +List.map ["A", "B", "C", 1, 2, 3] Num.isNegative +``` Every element in a Roc list has to share the same type. For example, we can have a list of strings like `["Sam", "Lee", "Ari"]`, or a list of numbers like `[1, 2, 3, 4, 5]` but we can't have a list which mixes strings and numbers like `["Sam", 1, "Lee", 2, 3]`, that would be a compile-time error. @@ -613,17 +705,19 @@ Ensuring that all elements in a list share a type eliminates entire categories o We can use tags with payloads to make a list that contains a mixture of different types. For example: -
List.map [StrElem "A", StrElem "b", NumElem 1, StrElem "c", NumElem -3] \elem ->
-    when elem is
-        NumElem num -> Num.isNegative num
-        StrElem str -> Str.isCapitalized str
-# returns [Bool.true, Bool.false, Bool.false, Bool.false, Bool.true]
-
+```roc +List.map [StrElem "A", StrElem "b", NumElem 1, StrElem "c", NumElem -3] \elem -> + when elem is + NumElem num -> Num.isNegative num + StrElem str -> Str.isCapitalized str +# returns [Bool.true, Bool.false, Bool.false, Bool.false, Bool.true] +``` Compare this with the example from earlier, which caused a compile-time error: -
List.map ["A", "B", "C", 1, 2, 3] Num.isNegative
-
+```roc +List.map ["A", "B", "C", 1, 2, 3] Num.isNegative +``` The version that uses tags works because we aren't trying to call `Num.isNegative` on each element. Instead, we're using a `when` to tell when we've got a string or a number, and then calling either `Num.isNegative` or `Str.isCapitalized` depending on which type we have. @@ -633,57 +727,66 @@ We could take this as far as we like, adding more different tags (e.g. `BoolElem Let's say I want to apply a tag to a bunch of elements in a list. For example: -
List.map ["a", "b", "c"] \str -> Foo str
-
+```roc +List.map ["a", "b", "c"] \str -> Foo str +``` This is a perfectly reasonable way to write it, but I can also write it like this: -
List.map ["a", "b", "c"] Foo
-
+```roc +List.map ["a", "b", "c"] Foo +``` These two versions compile to the same thing. As a convenience, Roc lets you specify a tag name where a function is expected; when you do this, the compiler infers that you want a function which uses all of its arguments as the payload to the given tag. -### [`List.any` and `List.all`](#list-any-and-list-all) {#list-any-and-list-all} +### [List.any and List.all](#list-any-and-list-all) {#list-any-and-list-all} -There are several functions that work like `List.map`, they walk through each element of a list and do something with it. Another is `List.any`, which returns `Bool.true` if calling the given function on any element in the list returns `true`: +There are several functions that work like `List.map`, they walk through each element of a list and do something with it. Another is `List.any`, which returns `Bool.true` if calling the given function on any element in the list returns `Bool.true`: -
List.any [1, 2, 3] Num.isOdd
-# returns `Bool.true` because 1 and 3 are odd
-
+```roc +List.any [1, 2, 3] Num.isOdd +# returns `Bool.true` because 1 and 3 are odd +``` -
List.any [1, 2, 3] Num.isNegative
-# returns `Bool.false` because none of these is negative
-
+```roc +List.any [1, 2, 3] Num.isNegative +# returns `Bool.false` because none of these is negative +``` -There's also `List.all` which only returns `true` if all the elements in the list pass the test: +There's also `List.all` which only returns `Bool.true` if all the elements in the list pass the test: -
List.all [1, 2, 3] Num.isOdd
-# returns `Bool.false` because 2 is not odd
-
+```roc +List.all [1, 2, 3] Num.isOdd +# returns `Bool.false` because 2 is not odd +``` -
List.all [1, 2, 3] Num.isPositive
-# returns `Bool.true` because all of these are positive
-
+```roc +List.all [1, 2, 3] Num.isPositive +# returns `Bool.true` because all of these are positive +``` ### [Removing elements from a list](#removing-elements-from-a-list) {#removing-elements-from-a-list} You can also drop elements from a list. One way is `List.dropAt` - for example: -
List.dropAt ["Sam", "Lee", "Ari"] 1
-# drops the element at offset 1 ("Lee") and returns ["Sam", "Ari"]
-
+```roc +List.dropAt ["Sam", "Lee", "Ari"] 1 +# drops the element at offset 1 ("Lee") and returns ["Sam", "Ari"] +``` -Another way is to use `List.keepIf`, which passes each of the list's elements to the given function, and then keeps them only if that function returns `true`. +Another way is to use `List.keepIf`, which passes each of the list's elements to the given function, and then keeps them only if that function returns `Bool.true`. -
List.keepIf [1, 2, 3, 4, 5] Num.isEven
-# returns [2, 4]
-
+```roc +List.keepIf [1, 2, 3, 4, 5] Num.isEven +# returns [2, 4] +``` There's also `List.dropIf`, which does the opposite: -
List.dropIf [1, 2, 3, 4, 5] Num.isEven
-# returns [1, 3, 5]
-
+```roc +List.dropIf [1, 2, 3, 4, 5] Num.isEven +# returns [1, 3, 5] +``` ### [Getting an individual element from a list](#getting-an-individual-element-from-a-list) {#getting-an-individual-element-from-a-list} @@ -691,32 +794,38 @@ Another thing we can do with a list is to get an individual element out of it. ` For example, what do each of these return? -
List.get ["a", "b", "c"] 1
-
-List.get ["a", "b", "c"] 100
-
+```roc +List.get ["a", "b", "c"] 1 +``` + +```roc +List.get ["a", "b", "c"] 100 +``` The answer is that the first one returns `Ok "b"` and the second one returns `Err OutOfBounds`. They both return tags! This is done so that the caller becomes responsible for handling the possibility that the index is outside the bounds of that particular list. Here's how calling `List.get` can look in practice: -
when List.get ["a", "b", "c"] index is
-    Ok str -> "I got this string: \(str)"
-    Err OutOfBounds -> "That index was out of bounds, sorry!"
-
+```roc +when List.get ["a", "b", "c"] index is + Ok str -> "I got this string: \(str)" + Err OutOfBounds -> "That index was out of bounds, sorry!" +``` There's also `List.first`, which always gets the first element, and `List.last` which always gets the last. They return `Err ListWasEmpty` instead of `Err OutOfBounds`, because the only way they can fail is if you pass them an empty list! These functions demonstrate a common pattern in Roc: operations that can fail returning either an `Ok` tag with the answer (if successful), or an `Err` tag with another tag describing what went wrong (if unsuccessful). In fact, it's such a common pattern that there's a whole module called `Result` which deals with these two tags. Here are some examples of `Result` functions: -
Result.withDefault (List.get ["a", "b", "c"] 100) ""
-# returns "" because that's the default we said to use if List.get returned an Err
-
-Result.isOk (List.get ["a", "b", "c"] 1)
-# returns `Bool.true` because `List.get` returned an `Ok` tag. (The payload gets ignored.)
+```roc
+Result.withDefault (List.get ["a", "b", "c"] 100) ""
+# returns "" because that's the default we said to use if List.get returned an Err
+```
+```roc
+Result.isOk (List.get ["a", "b", "c"] 1)
+# returns `Bool.true` because `List.get` returned an `Ok` tag. (The payload gets ignored.)
 
-# Note: There's a Result.isErr function that works similarly.
-
+# Note: There's a Result.isErr function that works similarly. +``` ### [Walking the elements in a list](#walking-the-elements-in-a-list) {#walking-the-elements-in-a-list} @@ -735,114 +844,88 @@ because it's more concise, runs faster, and doesn't give you any `Result`s to de Here's an example: -
List.walk [1, 2, 3, 4, 5] { evens : [], odds : [] } \state, elem ->
-    if Num.isEven elem then
-        { state & evens : List.append state.evens elem }
-    else
-        { state & odds : List.append state.odds elem }
+```roc
+List.walk [1, 2, 3, 4, 5] { evens: [], odds: [] } \state, elem ->
+    if Num.isEven elem then
+        { state & evens: List.append state.evens elem }
+    else
+        { state & odds: List.append state.odds elem }
 
-# returns { evens : [2, 4], odds : [1, 3, 5] }
-
+# returns { evens: [2, 4], odds: [1, 3, 5] } +``` In this example, we walk over the list `[1, 2, 3, 4, 5]` and add each element to either the `evens` or `odds` field of a `state` record: `{ evens, odds }`. By the end, that record has a list of all the even numbers in the list and a list of all the odd numbers. `List.walk` takes a few ingredients: 1. A list. (`[1, 2, 3, 4, 5]`) -2. An initial `state` value. (`{ evens : [], odds : [] }`) -3. A function which takes the current `state` and element, and returns a new `state`. (`\state, elem -> …`) +2. An initial `state` value. (`{ evens: [], odds: [] }`) +3. A function which takes the current `state` and element, and returns a new `state`. (`\state, elem -> ...`) It then proceeds to walk over each element in the list and call that function. Each time, the state that function returns becomes the argument to the next function call. Here are the arguments the function will receive, and what it will return, as `List.walk` walks over the list `[1, 2, 3, 4, 5]`: -
-
-
-
-    
-    
-    
-
-
-
-
-    
-    
-    
-
-
-    
-    
-    
-
-
-    
-    
-    
-
-
-    
-    
-    
-
-
-    
-    
-    
-
-
-
stateelementreturn value
{ evens : [], odds : [] }1{ evens : [], odds : [1] }
{ evens : [], odds : [1] }2{ evens : [2], odds : [1] }
{ evens : [2], odds : [1] }3{ evens : [2], odds : [1, 3] }
{ evens : [2], odds : [1, 3] }4{ evens : [2, 4], odds : [1, 3] }
{ evens : [2, 4], odds : [1, 3] }4{ evens : [2, 4], odds : [1, 3, 5] }
-
+| State | Element | Return Value | +| --------------------------------- | ------- | ------------------------------------ | +| `{ evens: [], odds: [] }` | `1` | `{ evens: [], odds: [1] }` | +| `{ evens: [], odds: [1] }` | `2` | `{ evens: [2], odds: [1] }` | +| `{ evens: [2], odds: [1] }` | `3` | `{ evens: [2], odds: [1, 3] }` | +| `{ evens: [2], odds: [1, 3] }` | `4` | `{ evens: [2, 4], odds: [1, 3] }` | +| `{ evens: [2, 4], odds: [1, 3] }` | `5` | `{ evens: [2, 4], odds: [1, 3, 5] }` | Note that the initial `state` argument is `{ evens: [], odds: [] }` because that's the argument we passed `List.walk` for its initial state. From then on, each `state` argument is whatever the previous function call returned. -Once the list has run out of elements, `List.walk` retunrs whatever the final function call returned—in this case, `{ evens : [2, 4], odds : [1, 3, 5] }`. (If the list was empty, the function never gets called and `List.walk` returns the initial state.) +Once the list has run out of elements, `List.walk` returns whatever the final function call returned—in this case, `{ evens: [2, 4], odds: [1, 3, 5] }`. (If the list was empty, the function never gets called and `List.walk` returns the initial state.) Note that the state doesn't have to be a record; it can be anything you want. For example, if you made it a `Bool`, you could implement `List.any` using `List.walk`. You could also make the state be a list, and implement `List.map`, `List.keepIf`, or `List.dropIf`. There are a lot of things you can do with `List.walk`! A helpful way to remember the argument order for `List.walk` is that that its arguments follow the same pattern as what we've seen with `List.map`, `List.any`, `List.keepIf`, and `List.dropIf`: the first argument is a list, and the last argument is a function. The difference here is that `List.walk` has one more argument than those other functions; the only place it could go while preserving that pattern is in the middle! -> **Note:** Other languages give this operation different names, such as `fold`, `reduce`, `accumulate`, `aggregate`, `compress`, and `inject`. Some languages also have operations like `forEach` or `for…in` syntax, which walk across every element and perform potentially side-effecting operations on them; `List.walk` can be used to replace these too, if you include a `Task` in the state. We'll talk about tasks, and how to use them with `List.walk`, later on. +> **Note:** Other languages give this operation different names, such as `fold`, `reduce`, `accumulate`, `aggregate`, `compress`, and `inject`. Some languages also have operations like `forEach` or `for...in` syntax, which walk across every element and perform potentially side-effecting operations on them; `List.walk` can be used to replace these too, if you include a `Task` in the state. We'll talk about tasks, and how to use them with `List.walk`, later on. ### [The pipe operator](#the-pipe-operator) {#the-pipe-operator} When you have nested function calls, sometimes it can be clearer to write them in a "pipelined" style using the `|>` operator. Here are three examples of writing the same expression; they all compile to exactly the same thing, but two of them use the `|>` operator to change how the calls look. -
Result.withDefault (List.get ["a", "b", "c"] 1) ""
-
-List.get ["a", "b", "c"] 1
-|> Result.withDefault ""
-
+```roc +Result.withDefault (List.get ["a", "b", "c"] 1) "" +``` +```roc +List.get ["a", "b", "c"] 1 +|> Result.withDefault "" +``` The `|>` operator takes the value that comes before the `|>` and passes it as the first argument to whatever comes after the `|>`. So in the example above, the `|>` takes `List.get ["a", "b", "c"] 1` and passes that value as the first argument to `Result.withDefault`, making `""` the second argument to `Result.withDefault`. We can take this a step further like so: -
["a", "b", "c"]
-|> List.get 1
-|> Result.withDefault ""
-
+```roc +["a", "b", "c"] +|> List.get 1 +|> Result.withDefault "" +``` This is still equivalent to the first expression. Since `|>` is known as the "pipe operator," we can read this as "start with `["a", "b", "c"]`, then pipe it to `List.get`, then pipe it to `Result.withDefault`." One reason the `|>` operator injects the value as the first argument is to make it work better with functions where argument order matters. For example, these two uses of `List.append` are equivalent: -
List.append ["a", "b", "c"] "d"
-
-["a", "b", "c"]
-|> List.append "d"
-
+```roc +List.append ["a", "b", "c"] "d" +``` +```roc +["a", "b", "c"] +|> List.append "d" +``` Another example is `Num.div`. All three of the following do the same thing, because `a / b` in Roc is syntax sugar for `Num.div a b`: -
first / second
-
-Num.div first second
-
-first
-|> Num.div second
-
+```roc +first / second +``` +```roc +Num.div first second +``` All operators in Roc are syntax sugar for normal function calls. See the [Operator Desugaring Table](https://www.roc-lang.org/tutorial#operator-desugaring-table) at the end of this tutorial for a complete list of them. @@ -850,10 +933,11 @@ All operators in Roc are syntax sugar for normal function calls. See the [Operat Sometimes you may want to document the type of a definition. For example, you might write: -
# Takes a firstName string and a lastName string, and returns a string
-fullName = \firstName, lastName ->
-    "\(firstName) \(lastName)"
-
+```roc +# Takes a firstName string and a lastName string, and returns a string +fullName = \firstName, lastName -> + "\(firstName) \(lastName)" +``` Comments can be valuable documentation, but they can also get out of date and become misleading. If someone changes this function and forgets to update the comment, it will no longer be accurate. @@ -861,10 +945,11 @@ Comments can be valuable documentation, but they can also get out of date and be Here's another way to document this function's type, which doesn't have that problem: -
fullName : Str, Str -> Str
-fullName = \firstName, lastName ->
-    "\(firstName) \(lastName)"
-
+```roc +fullName : Str, Str -> Str +fullName = \firstName, lastName -> + "\(firstName) \(lastName)" +``` The `fullName :` line is a _type annotation_. It's a strictly optional piece of metadata we can add above a def to describe its type. Unlike a comment, the Roc compiler will check type annotations for accuracy. If the annotation ever doesn't fit with the implementation, we'll get a compile-time error. @@ -872,36 +957,39 @@ The annotation `fullName : Str, Str -> Str` says "`fullName` is a function that We can give type annotations to any value, not just functions. For example: -
firstName : Str
-firstName = "Amy"
+```roc
+firstName : Str
+firstName = "Amy"
 
-lastName : Str
-lastName = "Lee"
-
+lastName : Str +lastName = "Lee" +``` These annotations say that both `firstName` and `lastName` have the type `Str`. We can annotate records similarly. For example, we could move `firstName` and `lastName` into a record like so: -
amy : { firstName : Str, lastName : Str }
-amy = { firstName : "Amy", lastName : "Lee" }
+```roc
+amy : { firstName : Str, lastName : Str }
+amy = { firstName: "Amy", lastName: "Lee" }
 
-jen : { firstName : Str, lastName : Str }
-jen = { firstName : "Jen", lastName : "Majura" }
-
+jen : { firstName : Str, lastName : Str } +jen = { firstName: "Jen", lastName: "Majura" } +``` ### [Type Aliases](#type-aliases) {#type-aliases} When we have a recurring type annotation like this, it can be nice to give it its own name. We do this like so: -
Musician : { firstName : Str, lastName : Str }
+```roc
+Musician : { firstName : Str, lastName : Str }
 
-amy : Musician
-amy = { firstName: "Amy", lastName: "Lee" }
+amy : Musician
+amy = { firstName: "Amy", lastName: "Lee" }
 
-simone : Musician
-simone = { firstName: "Simone", lastName: "Simons" }
-
+simone : Musician +simone = { firstName: "Simone", lastName: "Simons" } +``` Here, `Musician` is a _type alias_. A type alias is like a def, except it gives a name to a type instead of to a value. Just like how you can read `name : Str` as "`name` has the type `Str`," you can also read `Musician : { firstName : Str, lastName : Str }` as "`Musician` has the type `{ firstName : Str, lastName : Str }`." @@ -909,9 +997,10 @@ Here, `Musician` is a _type alias_. A type alias is like a def, except it gives Annotations for lists must specify what type the list's elements have: -
names : List Str
-names = ["Amy", "Simone", "Tarja"]
-
+```roc +names : List Str +names = ["Amy", "Simone", "Tarja"] +``` You can read `List Str` as "a list of strings." Here, `Str` is a _type parameter_ that tells us what type of `List` we're dealing with. `List` is a _parameterized type_, which means it's a type that requires a type parameter. There's no way to give something a type of `List` without a type parameter. You have to specify what type of list it is, such as `List Str` or `List Bool` or `List { firstName : Str, lastName : Str }`. @@ -919,8 +1008,9 @@ You can read `List Str` as "a list of strings." Here, `Str` is a _type parameter There are some functions that work on any list, regardless of its type parameter. For example, `List.isEmpty` has this type: -
isEmpty : List * -> Bool
-
+```roc +isEmpty : List * -> Bool +``` The `*` is a _wildcard type_; a type that's compatible with any other type. `List *` is compatible with any type of `List` like `List Str`, `List Bool`, and so on. So you can call `List.isEmpty ["I am a List Str"]` as well as `List.isEmpty [Bool.true]`, and they will both work fine. @@ -930,12 +1020,13 @@ The wildcard type also comes up with empty lists. Suppose we have one function t `List.reverse` works similarly to `List.isEmpty`, but with an important distinction. As with `isEmpty`, we can call `List.reverse` on any list, regardless of its type parameter. However, consider these calls: -
strings : List Str
-strings = List.reverse ["a", "b"]
+```roc
+strings : List Str
+strings = List.reverse ["a", "b"]
 
-bools : List Bool
-bools = List.reverse [Bool.true, Bool.false]
-
+bools : List Bool +bools = List.reverse [Bool.true, Bool.false] +``` In the `strings` example, we have `List.reverse` returning a `List Str`. In the `bools` example, it's returning a `List Bool`. So what's the type of `List.reverse`? @@ -943,12 +1034,17 @@ We saw that `List.isEmpty` has the type `List * -> Bool`, so we might think the What we want is something like one of these: -
reverse : List elem -> List elem
-
-reverse : List value -> List value
-
-reverse : List a -> List a
-
+```roc +reverse : List elem -> List elem +``` + +```roc +reverse : List value -> List value +``` + +```roc +reverse : List a -> List a +``` Any of these will work, because `elem`, `value`, and `a` are all _type variables_. A type variable connects two or more types in the same annotation. So you can read `List elem -> List elem` as "takes a list and returns a list that has **the same element type**." Just like `List.reverse` does! @@ -962,50 +1058,82 @@ Similarly, the only way to have a function whose type is `a -> a` is if the func We can also annotate types that include tags: -
colorFromStr : Str -> [Red, Green, Yellow]
-colorFromStr = \string ->
-    when string is
-        "red" -> Red
-        "green" -> Green
-        _ -> Yellow
-
+```roc +colorFromStr : Str -> [Red, Green, Yellow] +colorFromStr = \string -> + when string is + "red" -> Red + "green" -> Green + _ -> Yellow +``` You can read the type `[Red, Green, Yellow]` as "a tag union of the tags `Red`, `Green`, and `Yellow`." Some tag unions have only one tag in them. For example: -
redTag : [Red]
-redTag = Red
-
+```roc +redTag : [Red] +redTag = Red +``` ### [Accumulating Tag Types](#accumulating-tag-types) {#accumulating-tag-types} Tag union types can accumulate more tags based on how they're used. Consider this `if` expression: -
\str ->
-    if Str.isEmpty str then
-        Ok "it was empty"
-    else
-        Err ["it was not empty"]
-
+```roc +\str -> + if Str.isEmpty str then + Ok "it was empty" + else + Err ["it was not empty"] +``` Here, Roc sees that the first branch has the type `[Ok Str]` and that the `else` branch has the type `[Err (List Str)]`, so it concludes that the whole `if` expression evaluates to the combination of those two tag unions: `[Ok Str, Err (List Str)]`. -This means this entire `\str -> …` function has the type `Str -> [Ok Str, Err (List Str)]`. However, it would be most common to annotate it as `Result Str (List Str)` instead, because the `Result` type (for operations like `Result.withDefault`, which we saw earlier) is a type alias for a tag union with `Ok` and `Err` tags that each have one payload: +This means this entire `\str -> ...` function has the type `Str -> [Ok Str, Err (List Str)]`. However, it would be most common to annotate it as `Result Str (List Str)` instead, because the `Result` type (for operations like `Result.withDefault`, which we saw earlier) is a type alias for a tag union with `Ok` and `Err` tags that each have one payload: -
Result ok err : [Ok ok, Err err]
-
+```roc +Result ok err : [Ok ok, Err err] +``` We just saw how tag unions get combined when different branches of a conditional return different tags. Another way tag unions can get combined is through pattern matching. For example: -
when color is
-    Red -> "red"
-    Yellow -> "yellow"
-    Green -> "green"
-
+```roc +when color is + Red -> "red" + Yellow -> "yellow" + Green -> "green" +``` Here, Roc's compiler will infer that `color`'s type is `[Red, Yellow, Green]`, because those are the three possibilities this `when` handles. +### [Opaque Types](#opaque-types) {#opaque-types} + +A type can be defined to be opaque to hide its internal structure. This is a lot more amazing than it may seem. It can make your code more modular, robust, and easier to read: +- If a type is opaque you can modify its internal structure and be certain that no dependencies need to be updated. +- You can prevent that data needs to be checked multiple times. For example, you can create an opaque `NonEmptyList` from a `List` after you've checked it. Now all functions that you pass this `NonEmptyList` to do not need to handle the empty list case. +- Having the type `Username` in a type signature gives you more context compared to `Str`. Even if the `Username` is an opaque type for `Str`. + +You can create an opaque type with the `:=` operator. Let's make one called `Username`: + +```roc +Username := Str + +fromStr : Str -> Username +fromStr = \str -> + @Username str + +toStr : Username -> Str +toStr = \@Username str -> + str +``` + +The `fromStr` function turns a string into a `Username` by calling `@Username` on that string. The `toStr` function turns a `Username` back into a string by pattern matching `@Username str` to unwrap the string from the `Username` opaque type. + +Now we can expose the `Username` opaque type so that other modules can use it in type annotations. However, other modules can't use the `@Username` syntax to wrap or unwrap `Username` values. That operation is only available in the same scope where `Username` itself was defined; trying to use it outside that scope will give an error. + +Note that if we define `Username := Str` inside another module (e.g. `Main`) and also use `@Username`, this will compile, however the new `Username` type in main would not be equal to the one defined in the `Username` module. Although both opaque types have the name `Username`, they were defined in different modules and so they are type-incompatible with each other, and even attempting to use `==` to compare them would be a type mismatch. + ## [Numeric types](#numeric-types) {#numeric-types} Roc has different numeric types that each have different tradeoffs. They can all be broken down into two categories: [fractions](https://en.wikipedia.org/wiki/Fraction), and [integers](https://en.wikipedia.org/wiki/Integer). In Roc we call these `Frac` and `Int` for short. @@ -1032,57 +1160,18 @@ Choosing a size depends on your performance needs and the range of numbers you w Here are the different fixed-size integer types that Roc supports: -

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
RangeType
-128
127
I8
0
255
U8
-32_768
32_767
I16
0
65_535
U16
-2_147_483_648
2_147_483_647
I32
0
(over 4 billion) 4_294_967_295
U32
-9_223_372_036_854_775_808
9_223_372_036_854_775_807
I64
0
(over 18 quintillion)18_446_744_073_709_551_615
U64
-170_141_183_460_469_231_731_687_303_715_884_105_728
170_141_183_460_469_231_731_687_303_715_884_105_727
I128
0
(over 340 undecillion)340_282_366_920_938_463_463_374_607_431_768_211_455
U128
- +| Range | Type | +|-------------------------------------------------------------------------------------------------------------------|--------| +| `-128`
`127` | `I8` | +| `0`
`255` | `U8` | +| `-32_768`
`32_767` | `I16` | +| `0`
`65_535` | `U16` | +| `-2_147_483_648`
`2_147_483_647` | `I32` | +| `0`
(over 4 billion) `4_294_967_295` | `U32` | +| `-9_223_372_036_854_775_808`
`9_223_372_036_854_775_807` | `I64` | +| `0`
_(over 18 quintillion)_`18_446_744_073_709_551_615` | `U64` | +| `-170_141_183_460_469_231_731_687_303_715_884_105_728`
`170_141_183_460_469_231_731_687_303_715_884_105_727` | `I128` | +| `0`
_(over 340 undecillion)_`340_282_366_920_938_463_463_374_607_431_768_211_455` | `U128` | Roc also has one variable-size integer type: `Nat` (short for "natural number"). 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` works the same way as `U64`. When compiling for a 32-bit system, it works the same way as `U32`. Most popular computing devices today are 64-bit, so `Nat` is usually the same as `U64`, but Web Assembly is typically 32-bit - so when running a Roc program built for Web Assembly, `Nat` will work like a `U32` in that program. @@ -1116,18 +1205,20 @@ There are some use cases where `F64` and `F32` can be better choices than `Dec` Some operations work on specific numeric types - such as `I64` or `Dec` - but operations support multiple numeric types. For example, the `Num.abs` function works on any number, since you can take the [absolute value](https://en.wikipedia.org/wiki/Absolute_value) of integers and fractions alike. Its type is: -
abs : Num a -> Num a
-
+```roc +abs : Num a -> Num a +``` This type says `abs` takes a number and then returns a number of the same type. Remember that we can see the type of number is the same because the [type variable](#type-variables) `a` is used on both sides. That's because the `Num` type is compatible with both integers and fractions. There's also an `Int` type which is only compatible with integers, and a `Frac` type which is only compatible with fractions. For example: -
Num.xor : Int a, Int a -> Int a
-
-Num.cos : Frac a -> Frac a
-
- +```roc +Num.xor : Int a, Int a -> Int a +``` +```roc +Num.cos : Frac a -> Frac a +``` When you write a number literal in Roc, it has the type `Num *`. So you could call `Num.xor 1 1` and also `Num.cos 1` and have them all work as expected; the number literal `1` has the type `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`. ### [Number Literals](#number-literals) {#number-literals} @@ -1156,12 +1247,13 @@ Crashes in Roc are not like [try/catch exceptions](https://en.wikipedia.org/wiki You can intentionally crash a Roc program, for example inside a conditional branch that you believe is unreachable. Suppose you're certain that a particular `List U8` contains valid UTF-8 bytes, which means when you call `Str.fromUtf8` on it, the `Result` it returns will always be `Ok`. In that scenario, you can use the `crash` keyword to handle the `Err` case like so: -
answer : Str
-answer =
-    when Str.fromUtf8 definitelyValidUtf8 is
-        Ok str -> str
-        Err _ -> crash "This should never happen!"
-
+```roc +answer : Str +answer = + when Str.fromUtf8 definitelyValidUtf8 is + Ok str -> str + Err _ -> crash "This should never happen!" +``` If the unthinkable happens, and somehow the program reaches this `Err` branch even though that was thought to be impossible, then it will crash - just like if the system had run out of memory. The string passed to `crash` will be provided to the platform as context; each platform may do something different with it. @@ -1171,11 +1263,12 @@ If the unthinkable happens, and somehow the program reaches this `Err` branch ev Another use for `crash` is as a TODO marker when you're in the middle of building something: -
if x > y then
-    transmogrify (x * 2)
-else
-    crash "TODO handle the x <= y case"
-
+```roc +if x > y then + transmogrify (x * 2) +else + crash "TODO handle the x <= y case" +``` This lets you do things like write tests for the non-`crash` branch, and then come back and finish the other branch later. @@ -1191,43 +1284,46 @@ Errors that are recoverable should be represented using normal Roc types (like [ You can write automated tests for your Roc code like so: -
pluralize = \singular, plural, count ->
-    countStr = Num.toStr count
+```roc
+pluralize = \singular, plural, count ->
+    countStr = Num.toStr count
 
-    if count == 1 then
-        "\(countStr) \(singular)"
-    else
-        "\(countStr) \(plural)"
+    if count == 1 then
+        "\(countStr) \(singular)"
+    else
+        "\(countStr) \(plural)"
 
-expect pluralize "cactus" "cacti" 1 == "1 cactus"
+expect pluralize "cactus" "cacti" 1 == "1 cactus"
 
-expect pluralize "cactus" "cacti" 2 == "2 cacti"
-
+expect pluralize "cactus" "cacti" 2 == "2 cacti" +``` -If you put this in a file named `main.roc` and run `roc test`, Roc will execute the two `expect` expressions (that is, the two `pluralize` calls) and report any that returned `false`. +If you put this in a file named `main.roc` and run `roc test`, Roc will execute the two `expect` expressions (that is, the two `pluralize` calls) and report any that returned `Bool.false`. If a test fails, it will not show the actual value that differs from the expected value. To show the actual value, you can write the expect like this: -
expect
-    funcOut = pluralize "cactus" "cacti" 1
+```roc
+expect
+    funcOut = pluralize "cactus" "cacti" 1
 
-    funcOut == "2 cactus"
-
+ funcOut == "2 cactus" +``` ### [Inline Expectations](#inline-expects) {#inline-expects} Expects do not have to be at the top level: -
pluralize = \singular, plural, count ->
-    countStr = Num.toStr count
+```roc
+pluralize = \singular, plural, count ->
+    countStr = Num.toStr count
 
-    if count == 1 then
-        "\(countStr) \(singular)"
-    else
-        expect count > 0
+    if count == 1 then
+        "\(countStr) \(singular)"
+    else
+        expect count > 0
 
-        "\(countStr) \(plural)"
-
+ "\(countStr) \(plural)" +``` This `expect` will fail if you call `pluralize` passing a count of 0. @@ -1236,13 +1332,16 @@ So you'll want to use `roc dev` or `roc test` to get the output for `expect`. ## [Modules](#modules) {#modules} -\[This part of the tutorial has not been written yet. Coming soon!\] +Each `.roc` file is a separate module and contains Roc code for different purposes. Here are all of the different types of modules that Roc suppports; -## [Interface modules](#interface-modules) {#interface-modules} +- **Builtins** provide functions that are automatically imported into every module. +- **Applications** are combined with a platform and compiled into an executable. +- **Interfaces** provide functions which can be imported into other modules. +- **Packages** organise modules to share functionality across applications and platforms. +- **Platforms** provide effects such as IO to interface with the outside world. +- **Hosted** *note this module type is likely to be deprecated soon*. -\[This part of the tutorial has not been written yet. Coming soon!\] - -## [Builtin modules](#builtin-modules) {#builtin-modules} +### [Builtin Modules](#builtin-modules) {#builtin-modules} There are several modules that are built into the Roc compiler, which are imported automatically into every Roc module. They are: @@ -1263,15 +1362,16 @@ Besides being built into the compiler, the builtin modules are different from ot - They are always imported. You never need to add them to `imports`. - All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [Num.{ Nat }]` (the same is true for all the other types in the `Num` module. -## [The app module header](#the-app-module-header) {#the-app-module-header} +### [App Module Header](#app-module-header) {#app-module-header} Let's take a closer look at the part of `main.roc` above the `main` def: -
app "hello"
-    packages { pf : "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" }
-    imports [pf.Stdout]
-    provides main to pf
-
+```roc +app "hello" + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" } + imports [pf.Stdout] + provides main to pf +``` This is known as a _module header_. Every `.roc` file is a _module_, and there are different types of modules. We know this particular one is an _application module_ because it begins with the `app` keyword. @@ -1279,14 +1379,15 @@ The line `app "hello"` states that this module defines a Roc application, and th The remaining lines all involve the [platform](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform) this application is built on: -
packages { pf : "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" }
-imports [pf.Stdout]
-provides [main] to pf
-
+```roc +packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" } + imports [pf.Stdout] + provides [main] to pf +``` -The `packages { pf: "https://…tar.br" }` part says three things: +The `packages { pf: "https://...tar.br" }` part says three things: -- We're going to be using a _package_ (a collection of modules) that can be downloaded from the URL `"https://…tar.br"` +- We're going to be using a _package_ (a collection of modules) that can be downloaded from the URL `"https://...tar.br"` - That package's [base64](https://en.wikipedia.org/wiki/Base64#URL_applications)\-encoded [BLAKE3]() cryptographic hash is the long string at the end (before the `.tar.br` file extension). Once the file has been downloaded, its contents will be verified against this hash, and it will only be installed if they match. This way, you can be confident the download was neither corrupted nor changed since it was originally published. - We're going to name that package `pf` so we can refer to it more concisely in the future. @@ -1294,20 +1395,60 @@ The `imports [pf.Stdout]` line says that we want to import the `Stdout` module f This import has a direct interaction with our definition of `main`. Let's look at that again: -
main = Stdout.line "I'm a Roc application!"
-
+```roc +main = Stdout.line "I'm a Roc application!" +``` Here, `main` is calling a function called `Stdout.line`. More specifically, it's calling a function named `line` which is exposed by a module named `Stdout`. -When we write `imports [pf.Stdout]`, it specifies that the `Stdout` module comes from the package we named `pf` in the `packages { pf: … }` section. +When we write `imports [pf.Stdout]`, it specifies that the `Stdout` module comes from the package we named `pf` in the `packages { pf: ... }` section. If we would like to include other modules in our application, say `AdditionalModule.roc` and `AnotherModule.roc`, then they can be imported directly in `imports` like this: -
imports [pf.Stdout, AdditionalModule, AnotherModule]
-
+```roc +imports [pf.Stdout, AdditionalModule, AnotherModule] +``` You can find documentation for the `Stdout.line` function in the [Stdout](https://www.roc-lang.org/packages/basic-cli/Stdout#line) module documentation. +### [Package Modules](#interface-modules) {#interface-modules} + +Package modules enable Roc code to be easily re-used and shared. This is achieved by organizing code into different Interface modules and then including these in the `exposes` field of the package file structure, `package "name" exposes [ MyInterface ] packages {}`. The modules that are listed in the `exposes` field are then available for use in applications, platforms, or other packages. Internal modules that are not listed will be unavailable for use outside of the package. + +See [Parser Package](https://github.com/roc-lang/roc/tree/main/examples/parser/package) for an example. + +Package documentation can be generated using the Roc cli with `roc docs /package/*.roc`. + +Build a package for distribution with `roc build --bundle .tar.br /package/main.roc`. This will create a single tarball that can then be easily shared online using a URL. + +You can import a package that is available either locally, or from a URL into a Roc application or platform. This is achieved by specifying the package in the `packages` section of the application or platform file structure. For example, `packages { .., parser: "" }` is an example that imports a parser module from a URL. + +How does the Roc cli import and download a package from a URL? + +1. First it checks to see whether the relevant folder already exists in the local filesystem and if not, creates it. If there is a package already downloaded then there is no need to download or extract anything. Packages are cached in a directory, typically `~/.cache/roc` on UNIX, and `%APPDATA%\\Roc` on Windows. +2. It then downloads the file at that URL and verifies that the hash of the file matches the hash at the end of the URL. +3. If the hash of the file matches the hash in the URL, then decompress and extract its contents into the cache folder so that it can be used. + +Why is a Roc package URL so long? + +Including the hash solves a number of problems: + +1. The package at the URL can not suddenly change and cause different behavior. +2. Because of 1. there is no need to check the URL on every compilation to see if we have the latest version. +3. If the domain of the URL expires, a malicious actor can change the package but the hash will not match so the roc cli will reject it. + +### [Interface Modules](#interface-modules) {#interface-modules} + +\[This part of the tutorial has not been written yet. Coming soon!\] + +See [Html Interface](https://github.com/roc-lang/roc/blob/main/examples/virtual-dom-wip/platform/Html.roc) for an example. + +### [Platform Modules](#interface-modules) {#interface-modules} + +\[This part of the tutorial has not been written yet. Coming soon!\] + +See [Platform Switching Rust](https://github.com/roc-lang/roc/blob/main/examples/platform-switching/rust-platform/main.roc) for an example. + ## [Tasks](#tasks) {#tasks} Tasks are technically not part of the Roc language, but they're very common in platforms. Let's continue using the [basic-cli](https://github.com/roc-lang/basic-cli) platform we've been using up to this point as an example! @@ -1323,19 +1464,21 @@ We'll use these four operations to learn about tasks. Let's start with a basic "Hello World" program. -
app "cli-tutorial"
-    packages { pf : "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" }
-    imports [pf.Stdout]
-    provides [main] to pf
+```roc
+app "cli-tutorial"
+    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" }
+    imports [pf.Stdout]
+    provides [main] to pf
 
-main =
-    Stdout.line "Hello, World!"
-
+main = + Stdout.line "Hello, World!" +``` The `Stdout.line` function takes a `Str` and writes it to [standard output](). It has this type: -
Stdout.line : Str -> Task {} *
-
+```roc +Stdout.line : Str -> Task {} * +``` A `Task` represents an _effect_; an interaction with state outside your Roc program, such as the terminal's standard output, or a file. @@ -1345,20 +1488,22 @@ When we set `main` to be a `Task`, the task will get run when we run our program In contrast, `Stdin.line` produces a `Str` when it finishes reading from [standard input](). That `Str` is reflected in its type: -
Stdin.line : Task Str *
-
+```roc +Stdin.line : Task Str * +``` Let's change `main` to read a line from `stdin`, and then print it back out again: -
app "cli-tutorial"
-    packages { pf : "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" }
-    imports [pf.Stdout, pf.Stdin, pf.Task]
-    provides [main] to pf
+```roc
+app "cli-tutorial"
+    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" }
+    imports [pf.Stdout, pf.Stdin, pf.Task]
+    provides [main] to pf
 
-main =
-    Task.await Stdin.line \text ->
-        Stdout.line "You just entered: \(text)"
-
+main = + Task.await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` If you run this program, at first it won't do anything. It's waiting for you to type something in and press Enter! Once you do, it should print back out what you entered. @@ -1366,37 +1511,41 @@ The `Task.await` function combines two tasks into one bigger `Task` which first The type of `Task.await` is: -
Task.await : Task a err, (a -> Task b err) -> Task b err
-
+```roc +Task.await : Task a err, (a -> Task b err) -> Task b err +``` -The second argument to `Task.await` is a "callback function" which runs after the first task completes. This callback function receives the output of that first task, and then returns the second task. This means the second task can make use of output from the first task, like we did in our `\text -> …` callback function here: +The second argument to `Task.await` is a "callback function" which runs after the first task completes. This callback function receives the output of that first task, and then returns the second task. This means the second task can make use of output from the first task, like we did in our `\text -> ...` callback function here: -
\text ->
-    Stdout.line "You just entered: \(text)"
-
+```roc +\text -> + Stdout.line "You just entered: \(text)" +``` Notice that, just like before, we're still building `main` from a single `Task`. This is how we'll always do it! We'll keep building up bigger and bigger `Task`s out of smaller tasks, and then setting `main` to be that one big `Task`. For example, we can print a prompt before we pause to read from `stdin`, so it no longer looks like the program isn't doing anything when we start it up: -
task =
-    Task.await (Stdout.line "Type something press Enter:") \_ ->
-        Task.await Stdin.line \text ->
-            Stdout.line "You just entered: \(text)"
-
+```roc +task = + Task.await (Stdout.line "Type something press Enter:") \_ -> + Task.await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` This works, but we can make it a little nicer to read. Let's change it to the following: -
app "cli-tutorial"
-    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" }
-    imports [pf.Stdout, pf.Stdin, pf.Task.{ await }]
-    provides [main] to pf
+```roc
+app "cli-tutorial"
+    packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3.1/97mY3sUwo433-pcnEQUlMhn-sWiIf_J9bPhcAFZoqY4.tar.br" }
+    imports [pf.Stdout, pf.Stdin, pf.Task.{ await }]
+    provides [main] to pf
 
-main =
-    await (Stdout.line "Type something press Enter:") \_ ->
-        await Stdin.line \text ->
-            Stdout.line "You just entered: \(text)"
-
+main = + await (Stdout.line "Type something press Enter:") \_ -> + await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` Here we've changed how we're importing the `Task` module. Before it was `pf.Task` and now it's `pf.Task.{ await }`. The difference is that we're importing `await` in an _unqualified_ way, meaning that whenever we write `await` in this module, it will refer to `Task.await`. Now we no longer need to write `Task.` every time we want to use `await`. @@ -1404,27 +1553,30 @@ It's most common in Roc to call functions from other modules in a _qualified_ wa Speaking of calling `await` repeatedly, if we keep calling it more and more on this code, we'll end up doing a lot of indenting. If we'd rather not indent so much, we can rewrite `task` into this style which looks different but does the same thing: -
task =
-    _ <- await (Stdout.line "Type something press Enter:")
-    text <- await Stdin.line
+```roc
+task =
+    _ <- await (Stdout.line "Type something press Enter:")
+    text <- await Stdin.line
 
-    Stdout.line "You just entered: \(text)"
-
+ Stdout.line "You just entered: \(text)" +``` -This `<-` syntax is called _backpassing_. The `<-` is a way to define an anonymous function, just like `\ … ->` is. +This `<-` syntax is called _backpassing_. The `<-` is a way to define an anonymous function, just like `\ ... ->` is. Here, we're using backpassing to define two anonymous functions. Here's one of them: -
text <-
+```roc
+text <-
 
-Stdout.line "You just entered: \(text)"
-
+Stdout.line "You just entered: \(text)" +``` It may not look like it, but this code is defining an anonymous function! You might remember it as the anonymous function we previously defined like this: -
\text ->
-    Stdout.line "You just entered: \(text)"
-
+```roc +\text -> + Stdout.line "You just entered: \(text)" +``` These two anonymous functions are the same, just defined using different syntax. @@ -1434,43 +1586,48 @@ Let's look at these two complete expressions side by side. They are both saying Here's the original: -
await Stdin.line \text ->
-    Stdout.line "You just entered: \(text)"
-
+```roc +await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` And here's the equivalent expression with backpassing syntax: -
text <- await Stdin.line
+```roc
+text <- await Stdin.line
 
-Stdout.line "You just entered: \(text)"
-
+Stdout.line "You just entered: \(text)" +``` Here's the other function we're defining with backpassing: -
_ <-
-text <- await Stdin.line
+```roc
+_ <-
+text <- await Stdin.line
 
-Stdout.line "You just entered: \(text)"
-
+Stdout.line "You just entered: \(text)" +``` We could also have written that function this way if we preferred: -
_ <-
+```roc
+_ <-
 
-await Stdin.line \text ->
-    Stdout.line "You just entered: \(text)"
-
+await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` This is using a mix of a backpassing function `_ <-` and a normal function `\text ->`, which is totally allowed! Since backpassing is nothing more than syntax sugar for defining a function and passing back as an argument to another function, there's no reason we can't mix and match if we like. That said, the typical style in which this `task` would be written in Roc is using backpassing for all the `await` calls, like we had above: -
task =
-    _ <- await (Stdout.line "Type something press Enter:")
-    text <- await Stdin.line
+```roc
+task =
+    _ <- await (Stdout.line "Type something press Enter:")
+    text <- await Stdin.line
 
-    Stdout.line "You just entered: \(text)"
-
+ Stdout.line "You just entered: \(text)" +``` This way, it reads like a series of instructions: @@ -1496,9 +1653,10 @@ Here are some concepts you likely won't need as a beginner, but may want to know Let's say I write a function which takes a record with a `firstName` and `lastName` field, and puts them together with a space in between: -
fullName = \user ->
-    "\(user.firstName) \(user.lastName)"
-
+```roc +fullName = \user -> + "\(user.firstName) \(user.lastName)" +``` I can pass this function a record that has more fields than just `firstName` and `lastName`, as long as it has _at least_ both of those fields (and both of them are strings). So any of these calls would work: @@ -1512,20 +1670,23 @@ In contrast, a _closed record_ is one that requires an exact set of fields (and If we add a type annotation to this `fullName` function, we can choose to have it accept either an open record or a closed record: -
# Closed record
-fullName : { firstName : Str, lastName : Str } -> Str
-fullName = \user ->
-    "\(user.firstName) \(user.lastName)"
-
-# Open record (because of the `*`)
-fullName : { firstName : Str, lastName : Str }* -> Str
-fullName = \user ->
-    "\(user.firstName) \(user.lastName)"
-
+```roc +# Closed record +fullName : { firstName : Str, lastName : Str } -> Str +fullName = \user -> + "\(user.firstName) \(user.lastName)" +``` + +```roc +# Open record (because of the `*`) +fullName : { firstName : Str, lastName : Str }* -> Str +fullName = \user -> + "\(user.firstName) \(user.lastName)" +``` The `*` in the type `{ firstName : Str, lastName : Str }*` is what makes it an open record type. This `*` is the _wildcard type_ we saw earlier with empty lists. (An empty list has the type `List *`, in contrast to something like `List Str` which is a list of strings.) -This is because record types can optionally end in a type variable. Just like how we can have `List *` or `List a -> List a`, we can also have `{ first : Str, last : Str }*` or `{ first : Str, last : Str }a -> { first: Str, last : Str }a`. The differences are that in `List a`, the type variable is required and appears with a space after `List`; in a record, the type variable is optional, and appears (with no space) immediately after `}`. +This is because record types can optionally end in a type variable. Just like how we can have `List *` or `List a -> List a`, we can also have `{ first : Str, last : Str }*` or `{ first : Str, last : Str }a -> { first : Str, last : Str }a`. The differences are that in `List a`, the type variable is required and appears with a space after `List`; in a record, the type variable is optional, and appears (with no space) immediately after `}`. If the type variable in a record type is a `*` (such as in `{ first : Str, last : Str }*`), then it's an open record. If the type variable is missing, then it's a closed record. You can also specify a closed record by putting a `{}` as the type variable (so for example, `{ email : Str }{}` is another way to write `{ email : Str }`). In practice, closed records are basically always written without the `{}` on the end, but later on we'll see a situation where putting types other than `*` in that spot can be useful. @@ -1533,10 +1694,11 @@ If the type variable in a record type is a `*` (such as in `{ first : Str, last The type variable can also be a named type variable, like so: -
addHttps : { url  : Str }a -> { url  : Str }a
-addHttps = \record ->
-    { record & url : "https://\(record.url)" }
-
+```roc +addHttps : { url : Str }a -> { url : Str }a +addHttps = \record -> + { record & url: "https://\(record.url)" } +``` This function uses _constrained records_ in its type. The annotation is saying: @@ -1544,7 +1706,7 @@ This function uses _constrained records_ in its type. The annotation is saying: - That `url` field has the type `Str` - It returns a record of exactly the same type as the one it was given -So if we give this function a record with five fields, it will return a record with those same five fields. The only requirement is that one of those fields must be `url : Str`. +So if we give this function a record with five fields, it will return a record with those same five fields. The only requirement is that one of those fields must be `url: Str`. In practice, constrained records appear in type annotations much less often than open or closed records do. @@ -1564,53 +1726,61 @@ You can add type annotations to make record types less flexible than what the co If you like, you can always annotate your functions as accepting open records. However, in practice this may not always be the nicest choice. For example, let's say you have a `User` type alias, like so: -
User : {
-    email : Str,
-    firstName : Str,
-    lastName : Str,
+```roc
+User : {
+    email : Str,
+    firstName : Str,
+    lastName : Str,
 }
-
+``` This defines `User` to be a closed record, which in practice is the most common way records named `User` tend to be defined. If you want to have a function take a `User`, you might write its type like so: -
isValid : User -> Bool
+```roc +isValid : User -> Bool +``` If you want to have a function return a `User`, you might write its type like so: -
userFromEmail : Str -> User
-
+```roc +userFromEmail : Str -> User +``` A function which takes a user and returns a user might look like this: -
capitalizeNames : User -> User
-
+```roc +capitalizeNames : User -> User +``` This is a perfectly reasonable way to write all of these functions. However, I might decide that I really want the `isValid` function to take an open record; a record with _at least_ the fields of this `User` record, but possibly others as well. Since open records have a type variable (like `*` in `{ email : Str }*` or `a` in `{ email : Str }a -> { email : Str }a`), in order to do this I'd need to add a type variable to the `User` type alias: -
User a : {
-    email : Str
-    firstName : Str
-    lastName : Str
+```roc
+User a : {
+    email : Str
+    firstName : Str
+    lastName : Str
 }a
-
+``` Notice that the `a` type variable appears not only in `User a` but also in `}a` at the end of the record type! Using `User a` type alias, I can still write the same three functions, but now their types need to look different. This is what the first one would look like: -
isValid : User * -> Bool
-
+```roc +isValid : User * -> Bool +``` -Here, the `User *` type alias substitutes `*` for the type variable `a` in the type alias, which takes it from `{ email : Str, … }a` to `{ email : Str, … }*`. Now I can pass it any record that has at least the fields in `User`, and possibly others as well, which was my goal. +Here, the `User *` type alias substitutes `*` for the type variable `a` in the type alias, which takes it from `{ email : Str, ... }a` to `{ email : Str, ... }*`. Now I can pass it any record that has at least the fields in `User`, and possibly others as well, which was my goal. -
userFromEmail : Str -> User {}
-
+```roc +userFromEmail : Str -> User {} +``` -Here, the `User {}` type alias substitutes `{}` for the type variable `a` in the type alias, which takes it from `{ email : Str, … }a` to `{ email : Str, … }{}`. As noted earlier, this is another way to specify a closed record: putting a `{}` after it, in the same place that you'd find a `*` in an open record. +Here, the `User {}` type alias substitutes `{}` for the type variable `a` in the type alias, which takes it from `{ email : Str, ... }a` to `{ email : Str, ... }{}`. As noted earlier, this is another way to specify a closed record: putting a `{}` after it, in the same place that you'd find a `*` in an open record. > **Aside:** This works because you can form new record types by replacing the type variable with other record types. For example, `{ a : Str, b : Str }` can also be written `{ a : Str }{ b : Str }`. You can chain these more than once, e.g. `{ a : Str }{ b : Str }{ c : Str, d : Str }`. This is more useful when used with type annotations; for example, `{ a : Str, b : Str }User` describes a closed record consisting of all the fields in the closed record `User`, plus `a : Str` and `b : Str`. @@ -1618,15 +1788,17 @@ This function still returns the same record as it always did, it just needs to b The third function might need to use a named type variable: -
capitalizeNames : User a -> User a
-
+```roc +capitalizeNames : User a -> User a +``` If this function does a record update on the given user, and returns that - for example, if its definition were `capitalizeNames = \user -> { user & email: "blah" }` - then it needs to use the same named type variable for both the argument and return value. However, if returns a new `User` that it created from scratch, then its type could instead be: -
capitalizeNames : User * -> User {}
-
+```roc +capitalizeNames : User * -> User {} +``` This says that it takes a record with at least the fields specified in the `User` type alias, and possibly others...and then returns a record with exactly the fields specified in the `User` type alias, and no others. @@ -1642,22 +1814,24 @@ The _open tag union_ (or _open union_ for short) `[Foo Str, Bar Bool]*` represen Because an open union represents possibilities that are impossible to know ahead of time, any `when` I use on a `[Foo Str, Bar Bool]*` value must include a catch-all `_ ->` branch. Otherwise, if one of those unknown tags were to come up, the `when` would not know what to do with it! For example: -
example : [Foo Str, Bar Bool]* -> Bool
-example = \tag ->
-    when tag is
-        Foo str -> Str.isEmpty str
-        Bar bool -> bool
-        _ -> Bool.false
-
+```roc +example : [Foo Str, Bar Bool]* -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool + _ -> Bool.false +``` In contrast, a _closed tag union_ (or _closed union_) like `[Foo Str, Bar Bool]` (without the `*`) represents the set of all possible tags. If I use a `when` on one of these, I can match on `Foo` only and then on `Bar` only, with no need for a catch-all branch. For example: -
example : [Foo Str, Bar Bool] -> Bool
-example = \tag ->
-    when tag is
-        Foo str -> Str.isEmpty str
-        Bar bool -> bool
-
+```roc +example : [Foo Str, Bar Bool] -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool +``` If we were to remove the type annotations from the previous two code examples, Roc would infer the same types for them anyway. @@ -1675,19 +1849,21 @@ When we make a new record, it's inferred to be a closed record. For example, in This is because open unions can accumulate additional tags based on how they're used in the program, whereas closed unions cannot. For example, let's look at this conditional: -
if x > 5 then
-    "foo"
-else
-    7
-
+```roc +if x > 5 then + "foo" +else + 7 +``` This will be a type mismatch because the two branches have incompatible types. Strings and numbers are not type-compatible! Now let's look at another example: -
if x > 5 then
-    Ok "foo"
-else
-    Err "bar"
-
+```roc +if x > 5 then + Ok "foo" +else + Err "bar" +``` This shouldn't be a type mismatch, because we can see that the two branches are compatible; they are both tags that could easily coexist in the same tag union. But if the compiler inferred the type of `Ok "foo"` to be the closed union `[Ok Str]`, and likewise for `Err "bar"` and `[Err Str]`, then this would have to be a type mismatch - because those two closed unions are incompatible. @@ -1697,14 +1873,16 @@ Earlier we saw how a function which accepts an open union must account for more So if I have an `[Ok Str]*` value, I can pass it to functions with any of these types (among others): -- `[Ok Str]* -> Bool` -- `[Ok Str] -> Bool` -- `[Ok Str, Err Bool]* -> Bool` -- `[Ok Str, Err Bool] -> Bool` -- `[Ok Str, Err Bool, Whatever]* -> Bool` -- `[Ok Str, Err Bool, Whatever] -> Bool` -- `Result Str Bool -> Bool` -- `[Err Bool, Whatever]* -> Bool` +| Function Type | Can it receive `[Ok Str]*`? | +| --------------------------------------- | --------------------------- | +| `[Ok Str]* -> Bool` | Yes | +| `[Ok Str] -> Bool` | Yes | +| `[Ok Str, Err Bool]* -> Bool` | Yes | +| `[Ok Str, Err Bool] -> Bool` | Yes | +| `[Ok Str, Err Bool, Whatever]* -> Bool` | Yes | +| `[Ok Str, Err Bool, Whatever] -> Bool` | Yes | +| `Result Str Bool -> Bool` | Yes | +| `[Err Bool, Whatever]* -> Bool` | Yes | That last one works because a function accepting an open union can accept any unrecognized tag (including `Ok Str`) even though it is not mentioned as one of the tags in `[Err Bool, Whatever]*`! Remember, when a function accepts an open tag union, any `when` branches on that union must include a catch-all `_ ->` branch, which is the branch that will end up handling the `Ok Str` value we pass in. @@ -1725,29 +1903,33 @@ In summary, here's a way to think about the difference between open unions in a Earlier we saw these two examples, one with an open tag union and the other with a closed one: -
example : [Foo Str, Bar Bool]* -> Bool
-example = \tag ->
-    when tag is
-        Foo str -> Str.isEmpty str
-        Bar bool -> bool
-        _ -> Bool.false
-
-example : [Foo Str, Bar Bool] -> Bool
-example = \tag ->
-    when tag is
-        Foo str -> Str.isEmpty str
-        Bar bool -> bool
-
+```roc +example : [Foo Str, Bar Bool]* -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool + _ -> Bool.false +``` + +```roc +example : [Foo Str, Bar Bool] -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool +``` Similarly to how there are open records with a `*`, closed records with nothing, and constrained records with a named type variable, we can also have _constrained tag unions_ with a named type variable. Here's an example: -
example : [Foo Str, Bar Bool]a -> [Foo Str, Bar Bool]a
-example = \tag ->
-    when tag is
-        Foo str -> Bar (Str.isEmpty str)
-        Bar bool -> Bar Bool.false
-        other -> other
-
+```roc +example : [Foo Str, Bar Bool]a -> [Foo Str, Bar Bool]a +example = \tag -> + when tag is + Foo str -> Bar (Str.isEmpty str) + Bar bool -> Bar Bool.false + other -> other +``` This type says that the `example` function will take either a `Foo Str` tag, or a `Bar Bool` tag, or possibly another tag we don't know about at compile time and it also says that the function's return type is the same as the type of its argument. @@ -1774,91 +1956,25 @@ For this reason, any time you see a function that only runs a `when` on its only Here are various Roc expressions involving operators, and what they desugar to. -
-
-
-
-    
-    
-
-
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-    
-
-
-    
-
-
-    
-    
-
-
-
ExpressionDesugars to
a + bNum.add a b
a - bNum.sub a b
a * bNum.mul a b
a / bNum.div a b
a // bNum.divTrunc a b
a ^ bNum.pow a b
a % bNum.rem a b
a >> bNum.shr a b
a << bNum.shl a b
-aNum.neg a
-f x yNum.neg (f x y)
a == bBool.isEq a b
a != bBool.isNotEq a b
a && bBool.and a b
a || bBool.or a b
!aBool.not a
!f x yBool.not (f x y)
a |> b - b a
a b c |> f x yf (a b c) x y
-
+| Expression | Desugars To | +| ----------------------------- | ------------------ | +| `a + b` | `Num.add a b` | +| `a - b` | `Num.sub a b` | +| `a * b` | `Num.mul a b` | +| `a / b` | `Num.div a b` | +| `a // b` | `Num.divTrunc a b` | +| `a ^ b` | `Num.pow a b` | +| `a % b` | `Num.rem a b` | +| `a >> b` | `Num.shr a b` | +| `a << b` | `Num.shl a b` | +| `-a` | `Num.neg a` | +| `-f x y` | `Num.neg (f x y)` | +| `a == b` | `Bool.isEq a b` | +| `a != b` | `Bool.isNotEq a b` | +| `a && b` | `Bool.and a b` | +| a \|\| b | `Bool.or a b` | +| `!a` | `Bool.not a` | +| `!f x y` | `Bool.not (f x y)` | +| a \|> b | `b a` | +| a b c \|> f x y | `f (a b c) x y` | + diff --git a/www/generate_tutorial/src/tutorial.roc b/www/generate_tutorial/src/tutorial.roc index ec44b484fb..f8bc3117ef 100644 --- a/www/generate_tutorial/src/tutorial.roc +++ b/www/generate_tutorial/src/tutorial.roc @@ -67,12 +67,13 @@ tocLinks = { tag: "#building-an-application", value: "Building an Application" }, { tag: "#defining-functions", value: "Defining Functions" }, { tag: "#if-then-else", value: "if-then-else" }, - { tag: "#records", value: "Records" }, { tag: "#debugging", value: "Debugging" }, + { tag: "#records", value: "Records" }, { tag: "#tags", value: "Tags & Pattern Matching" }, { tag: "#booleans", value: "Booleans" }, { tag: "#lists", value: "Lists" }, { tag: "#types", value: "Types" }, + { tag: "#numeric-types", value: "Numeric Types" }, { tag: "#crashing", value: "Crashing" }, { tag: "#tests-and-expectations", value: "Tests and Expectations" }, { tag: "#modules", value: "Modules" }, @@ -94,7 +95,7 @@ tutorialIntro = label [id "tutorial-toc-toggle-label", for "tutorial-toc-toggle"] [text "contents"], ], p [] [text "Welcome to Roc!"], - p [] [text "This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and much more!"], + p [] [text "This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and more!"], ], section [] [ h2 [ id "installation" ] [ diff --git a/www/public/index.html b/www/public/index.html index 6e8b12f7fb..2c07a1d281 100644 --- a/www/public/index.html +++ b/www/public/index.html @@ -149,7 +149,7 @@ for people to come together through shared experiences, to teach and to learn from one another, and to make new friends.

-

No communtiy is perfect, but a community where people show kindness to each another by default +

No community is perfect, but a community where people show kindness to each another by default can be a true joy to participate in. That all starts with friendliness, especially towards beginners, and including towards people who prefer other programming languages. After all, languages are tools people use to create software, and there's no need for us @@ -233,7 +233,7 @@ longer to run than generating unoptimized machine code directly.)

on Zulip! Separately, we're also very interested in fuzzing the compiler, even though we already have a sizable list of known bugs there.)

-

On the communtiy side, so far the community is a friendly bunch, and we want to keep it that way +

On the community side, so far the community is a friendly bunch, and we want to keep it that way as it grows! We hope to do that by encouraging a culture of kindness and helping one another out, especially by being welcoming towards beginners.

diff --git a/www/public/site.css b/www/public/site.css index c9f2d9128e..aca6a59509 100644 --- a/www/public/site.css +++ b/www/public/site.css @@ -1,23 +1,31 @@ :root { + /* WCAG AAA Compliant colors */ + --code-bg: #f4f8f9; + --gray: #717171; + --orange: #BF5000; + --green: #0B8400; + --cyan: #067C94; + --blue: #05006d; + --magenta: #a20031; + --body-max-width: 900px; --text-color: #121212; --top-bar-bg: #222; --top-bar-fg: #eee; - --top-bar-logo-hover: hsl(258, 73%, 70%); - --header-link-color: #17b9b0; + --top-bar-logo-hover: #8055E4; + --header-link-color: #107F79; --header-link-hover: #222; - --link-color: hsl(258, 73%, 58%); - --h1-color: hsl(258, 73%, 70%); + --link-color: #7546e2; + --h1-color: #8055E4; --repl-prompt: #0064ff; --body-bg: #fff; - --code-bg: #e7e7e7; - --code-snippet-bg: #fcfcfc; - --code-snippet-border: #bbb; + --code-color: #303030; - --toc-background: #f3f3f3; - --toc-border: #ddd; + --toc-background: var(--code-bg); + --toc-border: var(--gray); --toc-search-bg: #fcfcfc; - --toc-search-border: #bbb; + --toc-search-border: var(--gray); + --font-mono: "Source Code Pro", monospace; } html { @@ -26,11 +34,13 @@ html { color: var(--text-color); } -html, #tutorial-toc-toggle-label { - font-family: 'Lato', sans-serif; +html, +#tutorial-toc-toggle-label { + font-family: "Lato", sans-serif; } -html, body { +html, +body { margin: 0; padding: 0; } @@ -104,32 +114,51 @@ main { padding: 0 12px; } -code { - margin: 0 0.2rem; - background: var(--code-bg); - padding: 0.1rem 0.5rem; - border-radius: 4px; -} - -code, samp { - font-family: 'Source Code Pro', monospace; +code, +samp { + font-family: var(--font-mono); color: var(--code-color); + background-color: var(--code-bg); + display: inline-block; } -.code-snippet, samp { - display: block; - overflow: auto; - white-space: pre; - padding: 10px 16px; - background-color: var(--code-snippet-bg); - margin-bottom: 16px; - font-size: 1.2rem; - line-height: 1.76rem; - border: 1px solid var(--code-snippet-border); +p code, +td code, +li code, +th code, +samp { + padding: 0 8px; +} + +code a, +a code { + text-decoration: none; + color: var(--code-link-color); + background: none; + padding: 0; +} + +code a:visited, +a:visited code { + color: var(--code-link-color); } pre { - white-space: pre-wrap; + position: relative; + margin-bottom: 16px; + padding: 8px 16px; + box-sizing: border-box; + background-color: var(--code-bg); + overflow-x: hidden; + word-wrap: normal; + font-size: 1.2rem; + line-height: 1.76em; + white-space: pre; +} + +pre>samp { + overflow-x: auto; + display: block; } .repl-prompt:before { @@ -139,50 +168,7 @@ pre { } .repl-err { - color: #c20000; -} - -samp .ann { - /* type annotation - purple in the repl */ - color: #f384fd; -} - -samp .autovar { - /* automatic variable names in the repl, e.g. # val1 */ - color: #338545; -} - -samp .kw { - /* language keywords, e.g. `if`*/ - color: #004cc2; -} - -samp .op, samp .paren, samp .brace, samp .comma, samp .colon { - /* operators, e.g. `+` */ - color: #c20000; -} - -samp .number { - /* number literals */ - color: #158086; -} - -samp .str { - /* string literals */ - color: #158086; -} - -samp .str-esc, samp .str-interp { - /* escapes inside string literals, e.g. \t */ - color: #3474db; -} - -samp .dim { - opacity: 0.55; -} - -samp .comment { - color: #338545; + color: var(--magenta); } /* Tables */ @@ -192,22 +178,27 @@ table { overflow-x: auto; border: 2px solid #f0f0f0; } + thead { border: none; } + tbody { border: none; } + tr { border: none; border-top: 2px solid #f0f0f0; } + th, td { border: none; border-right: 2px solid #f0f0f0; padding: 12px; } + th:last-child, td:last-child { border-right: none; @@ -217,7 +208,8 @@ td:last-child { width: 100%; } -#integer-types th:first-of-type, #integer-types td:first-of-type { +#integer-types th:first-of-type, +#integer-types td:first-of-type { text-align: right; } @@ -225,16 +217,11 @@ td:last-child { background-color: inherit; } -#integer-types th:last-of-type, #integer-types td:last-of-type { +#integer-types th:last-of-type, +#integer-types td:last-of-type { text-align: left; } -@media (prefers-color-scheme: dark) { - table, tr, th, td { - border-color: #3b3f47; - } -} - /* Tutorial Specific */ #tutorial-start { @@ -248,8 +235,7 @@ td:last-child { #tutorial-toc { margin-top: 18px; - background: var(--toc-background); - border: 1px solid var(--toc-border); + background: var(--code-bg); padding: 12px 24px; margin-left: 64px; } @@ -270,7 +256,6 @@ td:last-child { } #tutorial-toc h2 { - color: #686868; font-family: inherit; font-size: 2em; text-shadow: none; @@ -290,11 +275,15 @@ td:last-child { font-size: inherit; } -#tutorial-toc-toggle, #tutorial-toc-toggle-label, #close-tutorial-toc { - display: none; /* This may be overridden on mobile-friendly screen widths */ +#tutorial-toc-toggle, +#tutorial-toc-toggle-label, +#close-tutorial-toc { + display: none; + /* This may be overridden on mobile-friendly screen widths */ } -#tutorial-toc-toggle, #tutorial-toc-toggle-label { +#tutorial-toc-toggle, +#tutorial-toc-toggle-label { font-size: 1.1rem; float: right; } @@ -307,28 +296,48 @@ td:last-child { padding: 12px 24px; } -p, aside, li, footer { +p, +aside, +li, +footer { font-size: 1.2rem; line-height: 1.85rem; } /* Mobile-friendly screen width */ @media only screen and (max-device-width: 480px) and (orientation: portrait) { - p, aside, li, footer, code, samp, .code-snippet { + + p, + aside, + li, + footer, + code, + samp, + .code-snippet { font-size: 16px; } - h1 code, h2 code, h3 code, h4 code, h5 code { + h1 code, + h2 code, + h3 code, + h4 code, + h5 code { font-size: inherit; } + code { + white-space: normal; + } + #tutorial-toc-toggle-label, #close-tutorial-toc { display: block; } - #tutorial-toc-toggle:checked + #tutorial-toc { + + #tutorial-toc-toggle:checked+#tutorial-toc { display: block; } + #tutorial-toc { display: none; position: fixed; @@ -341,13 +350,27 @@ p, aside, li, footer { padding-right: 120px; border: 0; } - h1, h2, h3, h4, h5, h6, p, code { + + h1, + h2, + h3, + h4, + h5, + h6, + p, + code { word-break: break-word !important; } - h1, h2, h3, h4, h5 { + + h1, + h2, + h3, + h4, + h5 { line-height: 1.2em !important; font-size: 2rem !important; } + #top-bar-links a, #top-bar-links label { padding: 12px 8px; @@ -357,12 +380,14 @@ p, aside, li, footer { /* Used on on the different-names page. */ -th, td { +th, +td { text-align: left; padding-right: 24px; } -#different-names-body a, #different-names-body li { +#different-names-body a, +#different-names-body li { font-family: monospace; font-size: 16px; } @@ -381,32 +406,50 @@ th, td { list-style-type: none; } -h1, h2, h3, h4, h5 { - font-family: 'Permanent Marker'; +h1, +h2, +h3, +h4, +h5 { + font-family: "Permanent Marker"; line-height: 1rem; margin-top: 1.75rem; margin-bottom: 0; } -#tutorial-toc-toggle-label, #close-tutorial-toc { +#tutorial-toc-toggle-label, +#close-tutorial-toc { color: var(--header-link-color); } -#tutorial-toc-toggle-label:hover, #close-tutorial-toc:hover { +#tutorial-toc-toggle-label:hover, +#close-tutorial-toc:hover { color: var(--header-link-hover); cursor: pointer; } -h1 a, h2 a, h3 a, h4 a, h5 a { +h1 a, +h2 a, +h3 a, +h4 a, +h5 a { color: var(--header-link-color); } -h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover { +h1 a:hover, +h2 a:hover, +h3 a:hover, +h4 a:hover, +h5 a:hover { text-decoration: none; color: var(--header-link-hover); } -h1 code, h2 code, h3 code, h4 code, h5 code { +h1 code, +h2 code, +h3 code, +h4 code, +h5 code { color: inherit; background-color: inherit; padding: 0; @@ -440,112 +483,140 @@ h4 { } @font-face { - font-family: 'Permanent Marker'; - font-style: normal; - font-weight: 400; - src: url('/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff2') format('woff2'), - url('/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff') format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + font-family: "Permanent Marker"; + font-style: normal; + font-weight: 400; + src: url("/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff2") format("woff2"), + url("/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff") format("woff"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; } /* latin-ext */ @font-face { - font-family: 'Merriweather'; - font-style: normal; - font-weight: 400; - src: url('/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff2') format('woff2'), - url('/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff') format('woff'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; + font-family: "Merriweather"; + font-style: normal; + font-weight: 400; + src: url("/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff2") format("woff2"), + url("/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff") format("woff"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { - font-family: 'Merriweather'; - font-style: normal; - font-weight: 400; - src: url('/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff2') format('woff2'), - url('/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff') format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + font-family: "Merriweather"; + font-style: normal; + font-weight: 400; + src: url("/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff2") format("woff2"), + url("/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff") format("woff"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; } /* latin-ext */ @font-face { - font-family: 'Merriweather Sans'; - font-style: normal; - font-weight: 400; - src: url('/fonts/merriweather-sans-v22-latin-ext_latin/merriweather-sans-v22-latin-ext_latin-regular.woff2') format('woff2'), - url('/fonts/merriweather-sans-v22-latin-ext_latin/merriweather-sans-v22-latin-ext_latin-regular.woff') format('woff'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; + font-family: "Merriweather Sans"; + font-style: normal; + font-weight: 400; + src: url("/fonts/merriweather-sans-v22-latin-ext_latin/merriweather-sans-v22-latin-ext_latin-regular.woff2") format("woff2"), + url("/fonts/merriweather-sans-v22-latin-ext_latin/merriweather-sans-v22-latin-ext_latin-regular.woff") format("woff"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } + /* latin */ @font-face { - font-family: 'Merriweather Sans'; - font-style: normal; - font-weight: 400; - src: url('/fonts/merriweather-sans-v22-latin/merriweather-sans-v22-latin-regular.woff2') format('woff2'), - url('/fonts/merriweather-sans-v22-latin/merriweather-sans-v22-latin-regular.woff') format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + font-family: "Merriweather Sans"; + font-style: normal; + font-weight: 400; + src: url("/fonts/merriweather-sans-v22-latin/merriweather-sans-v22-latin-regular.woff2") format("woff2"), + url("/fonts/merriweather-sans-v22-latin/merriweather-sans-v22-latin-regular.woff") format("woff"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; } /* latin-ext */ @font-face { - font-family: 'Lato'; - font-style: normal; - font-weight: 400; - src: url('/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff2') format('woff2'), - url('/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff') format('woff'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; + font-family: "Lato"; + font-style: normal; + font-weight: 400; + src: url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff2") format("woff2"), + url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff") format("woff"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } + /* latin */ @font-face { - font-family: 'Lato'; - font-style: normal; - font-weight: 400; - src: url('/fonts/lato-v23-latin/lato-v23-latin-regular.woff2') format('woff2'), - url('/fonts/lato-v23-latin/lato-v23-latin-regular.woff') format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + font-family: "Lato"; + font-style: normal; + font-weight: 400; + src: url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff2") format("woff2"), + url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff") format("woff"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; } /* latin-ext */ @font-face { - font-family: 'Source Code Pro'; - font-style: normal; - font-weight: 400; - src: url('/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff2') format('woff2'), - url('/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff') format('woff'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; + font-family: "Source Code Pro"; + font-style: normal; + font-weight: 400; + src: url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff2") format("woff2"), + url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff") format("woff"); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } + /* latin */ @font-face { - font-family: 'Source Code Pro'; - font-style: normal; - font-weight: 400; - src: url('/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2') format('woff2'), - url('/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff') format('woff'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; + font-family: "Source Code Pro"; + font-style: normal; + font-weight: 400; + src: url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2") format("woff2"), + url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff") format("woff"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, + U+2215, U+FEFF, U+FFFD; } @media (prefers-color-scheme: dark) { :root { + /* WCAG AAA Compliant colors */ + --code-bg: #202746; + --gray: #b6b6b6; + --orange: #fd6e08; + --green: #8ecc88; + --cyan: #12c9be; + --blue: #b1afdf; + --magenta: #f39bac; + --text-color: #cdcdcd; --top-bar-bg: #2a2a2a; - --header-link-color: hsl(258, 73%, 70%); + --header-link-color: #9C7CEA; --header-link-hover: #ddd; --h1-color: #1bc6bd; --link-color: #1bc6bd; - --repl-prompt: var(--link-color); + --repl-prompt: #1bc6bd; --body-bg: #0e0e0f; - --code-bg: #303030; - --code-snippet-bg: #1a1a1a; --code-snippet-border: #444; --code-color: #cdcdcd; - --toc-background: var(--code-snippet-bg); + --toc-background: var(--code-bg); --toc-border: var(--code-snippet-border); --toc-search-bg: #333; - --toc-search-border: #555; + --toc-search-border: var(--gray); } - h1, h2, h3, h4, h5 { + h1, + h2, + h3, + h4, + h5 { text-shadow: none; } @@ -553,32 +624,77 @@ h4 { scrollbar-color: #444444 #2f2f2f; } - samp .kw { - /* language keywords, e.g. `if` */ - color: #00c3ff; - } - - samp .colon, samp .op, samp .paren, samp .brace, samp .comma, .repl-err { - /* operators, e.g. `+` */ - color: #ff3966; - } - - samp .str { - /* string literals */ - color: var(--link-color); - } - - code .str { - /* string literals */ - color: var(--link-color); - } - - /* autovar = automatic variable names in the repl, e.g. # val1 */ - samp .comment, samp .autovar { - color: #4ed86c; - } - - samp .number { - color: #00c3ff; + table, + tr, + th, + td { + border-color: var(--gray); } } + +/* Comments `#` and Documentation comments `##` */ +samp .comment, +code .comment { + color: var(--green); +} + +/* Number, String, Tag, Type literals */ +samp .literal, +code .literal { + color: var(--cyan); +} + +/* Keywords and punctuation */ +samp .kw, +code .kw { + color: var(--magenta); +} + +/* Operators */ +samp .op, +code .op { + color: var(--orange); +} + +/* Delimieters */ +samp .delimeter, +code .delimeter { + color: var(--gray); +} + +/* Variables modules and field names */ +samp .lowerident, +code .lowerident { + color: var(--blue); +} + +/* Types, Tags, and Modules */ +samp .upperident, +code .upperident { + color: var(--green); +} + +samp .dim, +code .dim { + opacity: 0.55; +} + +.button-container { + position: absolute; + top: 0; + right: 0; +} + +.copy-button { + background: var(--code-bg); + border: 1px solid var(--magenta); + color: var(--magenta); + display: inline-block; + cursor: pointer; + margin: 8px; +} + +.copy-button:hover { + border-color: var(--green); + color: var(--green); +} \ No newline at end of file diff --git a/www/public/site.js b/www/public/site.js index be7414982c..89da8ce7b5 100644 --- a/www/public/site.js +++ b/www/public/site.js @@ -12,4 +12,61 @@ document.addEventListener("keydown", (event) => { if (event.key == "Escape") { tutorialTocToggle.checked = false; } +}); + +const isTouchSupported = () => { + try{ document.createEvent("TouchEvent"); return true; } + catch(e){ return false; } +} + +// Select all elements that are children of
 elements
+const codeBlocks = document.querySelectorAll("pre > samp");
+
+// Iterate over each code block
+codeBlocks.forEach((codeBlock) => {
+  // Create a "Copy" button
+  const copyButton = document.createElement("button");
+  copyButton.classList.add("copy-button");
+  copyButton.textContent = "Copy";
+
+  // Add event listener to copy button
+  copyButton.addEventListener("click", () => {
+    const codeText = codeBlock.innerText;
+    navigator.clipboard.writeText(codeText);
+    copyButton.textContent = "Copied!";
+    copyButton.classList.add("copy-button-copied");
+    copyButton.addEventListener("mouseleave", () => {
+        copyButton.textContent = "Copy";
+        copyButton.classList.remove('copy-button-copied');
+    });
+  });
+
+  // Create a container for the copy button and append it to the document
+  const buttonContainer = document.createElement("div");
+  buttonContainer.classList.add("button-container");
+  buttonContainer.appendChild(copyButton);
+  codeBlock.parentNode.insertBefore(buttonContainer, codeBlock);
+
+  // Hide the button container by default
+  buttonContainer.style.display = "none";
+
+  if (isTouchSupported()) {
+    // Show the button container on click for touch support (e.g. mobile)
+    document.addEventListener("click", (event) => {
+      if (event.target.closest("pre > samp") !== codeBlock) {
+        buttonContainer.style.display = "none";
+      } else {
+        buttonContainer.style.display = "block";
+      }
+    });
+  } else {
+    // Show the button container on hover for non-touch support (e.g. desktop)
+    codeBlock.parentNode.addEventListener("mouseenter", () => {
+      buttonContainer.style.display = "block";
+    });
+
+    codeBlock.parentNode.addEventListener("mouseleave", () => {
+      buttonContainer.style.display = "none";
+    });  
+  }
 });
\ No newline at end of file