Merge remote-tracking branch 'origin/main' into abilities-syntax

This commit is contained in:
Richard Feldman 2023-08-10 20:29:27 -04:00
commit 2da41be29f
No known key found for this signature in database
GPG key ID: F1F21AA5B1D9E43B
524 changed files with 47536 additions and 15089 deletions

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Benchmarks

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Macos x86-64 rust tests

View file

@ -0,0 +1,46 @@
on:
workflow_dispatch:
schedule:
- cron: '0 9 * * *'
name: Nightly Release Linux arm64/aarch64
jobs:
build:
name: build and package nightly release
runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- name: create version.txt
run: ./ci/write_version.sh
- name: build release with lto
run: cargo build --profile=release-with-lto --locked --bin roc
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-linux_arm64-$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 }}
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View file

@ -7,7 +7,7 @@ name: Nightly Release Linux x86_64
jobs:
build:
name: Rust tests, build and package nightly release
name: build and package nightly release
runs-on: [self-hosted, i7-6700K]
timeout-minutes: 90
steps:

View file

@ -0,0 +1,38 @@
on:
workflow_dispatch:
schedule:
- cron: '0 9 * * *'
name: Nightly Release Old Linux arm64 using Earthly
jobs:
build:
name: build and package nightly release
runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 180
steps:
- uses: actions/checkout@v3
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-old_linux_arm64-$DATE-$SHA" >> $GITHUB_ENV
- run: earthly --version
- name: build release with earthly
run: earthly +build-nightly-release --RELEASE_FOLDER_NAME=${{ env.RELEASE_FOLDER_NAME }} --ZIG_ARCH=aarch64
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View file

@ -0,0 +1,41 @@
on:
workflow_dispatch:
schedule:
- cron: '0 9 * * *'
name: Nightly Release Old Linux x86_64 using Earthly
jobs:
build:
name: build and package nightly release
runs-on: [ubuntu-20.04]
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
- name: get date
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
- name: build file name
env:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-old_linux_x86_64-$DATE-$SHA" >> $GITHUB_ENV
- name: install earthly
run: sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly && /usr/local/bin/earthly bootstrap --with-autocomplete'
- run: earthly --version
- name: build release with earthly
run: earthly +build-nightly-release --RELEASE_FOLDER_NAME=${{ env.RELEASE_FOLDER_NAME }} --RUSTFLAGS="-C target-cpu=x86-64"
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
path: ${{ env.RELEASE_FOLDER_NAME }}.tar.gz
retention-days: 4

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Nix linux x86_64 cargo test

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Nix apple silicon cargo test

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: Nix macOS x86_64 cargo test
@ -19,7 +20,7 @@ jobs:
with:
clean: "true"
- uses: cachix/install-nix-action@v20
- uses: cachix/install-nix-action@v22
- 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

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: SpellCheck

View file

@ -1,11 +1,9 @@
on:
#pull_request:
workflow_dispatch:
name: Test latest nightly release for macOS, ubu 20.04, ubu 22.04 x86_64
env:
ZIG_VERSION: 0.9.1
jobs:
test-nightly:
name: test nightly macos 11, macos 12, ubu 20.04, ubu 22.04
@ -17,6 +15,9 @@ jobs:
timeout-minutes: 90
steps:
- uses: actions/checkout@v3
- uses: goto-bus-stop/setup-zig@v2
with:
version: 0.9.1
- name: get the latest release archive for linux (x86_64)
if: startsWith(matrix.os, 'ubuntu')
@ -26,44 +27,19 @@ jobs:
if: startsWith(matrix.os, 'macos')
run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz
- name: remove everything in this dir except the tar # we want to test like a user who would have downloaded the release, so we clean up all files from the repo checkout
run: ls | grep -v "roc_nightly.*tar\.gz" | xargs rm -rf
- run: zig version
- name: decompress the tar
run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
- name: delete tar
run: ls | grep "roc_nightly.*tar\.gz" | xargs rm -rf
- name: rename nightly folder
run: mv roc_nightly* roc_nightly
- name: test roc hello world
run: cd roc_nightly && ./roc examples/helloWorld.roc
- name: test platform switching rust
run: cd roc_nightly && ./roc examples/platform-switching/rocLovesRust.roc
- name: get OS to use for zig download
if: startsWith(matrix.os, 'ubuntu')
run: echo "OS_TYPE=linux" >> $GITHUB_ENV
- name: get OS to use for zig download
if: startsWith(matrix.os, 'macos')
run: echo "OS_TYPE=macos" >> $GITHUB_ENV
- name: Install zig
- name: prep and run basic tests
run: |
curl -fL -o zig.tar.xz https://ziglang.org/download/${ZIG_VERSION}/zig-${{env.OS_TYPE}}-x86_64-${ZIG_VERSION}.tar.xz && tar -xf zig.tar.xz
echo "${GITHUB_WORKSPACE}/zig-${{env.OS_TYPE}}-x86_64-${ZIG_VERSION}" >> $GITHUB_PATH
- name: zig version
run: zig version
./ci/basic_nightly_test.sh
- name: clean up, get old linux release (x86_64), run tests
if: startsWith(matrix.os, 'ubuntu')
run: |
rm -rf roc_nightly
curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-old_linux_x86_64-latest.tar.gz
./ci/basic_nightly_test.sh
- name: test platform switching zig
run: cd roc_nightly && ./roc examples/platform-switching/rocLovesZig.roc
- name: test platform switching c
run: cd roc_nightly && ./roc examples/platform-switching/rocLovesC.roc

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: CI
@ -61,7 +62,6 @@ jobs:
run: ./ci/www-repl.sh && sccache --show-stats
#TODO i386 (32-bit linux) cli tests
#TODO verify-no-git-changes
- name: test website build script

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: windows - release build
@ -32,8 +33,8 @@ jobs:
- name: zig version
run: zig version
- name: install rust nightly 1.66
run: rustup install nightly-2022-10-30
- name: install rust nightly 1.70.0
run: rustup install nightly-2023-04-15
- name: set up llvm 13
run: |

View file

@ -1,4 +1,5 @@
on: [pull_request]
on:
pull_request:
name: windows - subset of tests
@ -36,8 +37,8 @@ jobs:
- name: zig version
run: zig version
- name: install rust nightly 1.66
run: rustup install nightly-2022-10-30
- name: install rust nightly 1.70.0
run: rustup install nightly-2023-04-15
- name: set up llvm 13
run: |
@ -49,7 +50,11 @@ jobs:
# Why are these tests not build with previous command? => fingerprint error. Use `CARGO_LOG=cargo::core::compiler::fingerprint=info` to investigate
- name: Build specific tests without running. Twice for zig lld-link error.
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli || cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli
run: cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli -p test_gen || cargo test --locked --release --no-run -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli -p test_gen
- name: Test setjmp/longjmp logic
run: cargo test-gen-dev --locked --release nat_alias && cargo test-gen-dev --locked --release a_crash
- name: Actually run the tests.
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli
run: cargo test --locked --release -p roc_ident -p roc_region -p roc_collections -p roc_can -p roc_types -p roc_solve -p roc_mono -p roc_gen_dev -p roc_gen_wasm -p roc_serialize -p roc_editor -p roc_linker -p roc_cli

6
.gitignore vendored
View file

@ -82,3 +82,9 @@ www/src/roc-tutorial
# 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
# snapshot tests temp file
*.pending-snap
# checkmate
checkmate_*.json

182
Cargo.lock generated
View file

@ -88,6 +88,21 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.3.2"
@ -473,6 +488,21 @@ dependencies = [
"num-traits",
]
[[package]]
name = "chrono"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"time 0.1.45",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -1094,6 +1124,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "dyn-clone"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272"
[[package]]
name = "either"
version = "1.8.1"
@ -1394,7 +1430,7 @@ dependencies = [
"cfg-if 1.0.0",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
@ -1653,6 +1689,29 @@ dependencies = [
"tokio-rustls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [
"android_system_properties",
"core-foundation-sys 0.8.4",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "iced-x86"
version = "1.18.0"
@ -1975,7 +2034,7 @@ dependencies = [
"libc",
"log",
"thiserror",
"time",
"time 0.3.21",
"uuid",
]
@ -2106,7 +2165,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.45.0",
]
@ -3095,6 +3154,7 @@ dependencies = [
"page_size",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_error_macros",
"roc_load",
@ -3105,6 +3165,7 @@ dependencies = [
"roc_region",
"roc_reporting",
"roc_solve",
"roc_solve_schema",
"roc_target",
"roc_types",
"roc_unify",
@ -3203,6 +3264,27 @@ dependencies = [
"ven_pretty",
]
[[package]]
name = "roc_checkmate"
version = "0.0.1"
dependencies = [
"chrono",
"roc_checkmate_schema",
"roc_module",
"roc_solve_schema",
"roc_types",
"serde_json",
]
[[package]]
name = "roc_checkmate_schema"
version = "0.0.1"
dependencies = [
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "roc_cli"
version = "0.0.1"
@ -3311,11 +3393,13 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_derive_key",
"roc_error_macros",
"roc_module",
"roc_region",
"roc_solve_schema",
"roc_types",
"roc_unify",
]
@ -3351,6 +3435,7 @@ dependencies = [
"roc_parse",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
"snafu",
@ -3563,11 +3648,13 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_derive",
"roc_error_macros",
"roc_module",
"roc_solve",
"roc_solve_schema",
"roc_types",
"roc_unify",
]
@ -3591,6 +3678,7 @@ dependencies = [
"roc_mono",
"roc_packaging",
"roc_reporting",
"roc_solve",
"roc_target",
"serde",
"serial_test",
@ -3611,6 +3699,7 @@ dependencies = [
"roc_module",
"roc_packaging",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
]
@ -3627,6 +3716,7 @@ dependencies = [
"pretty_assertions",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_constrain",
"roc_debug_flags",
@ -3669,6 +3759,7 @@ dependencies = [
name = "roc_mono"
version = "0.0.1"
dependencies = [
"arrayvec 0.7.2",
"bitvec",
"bumpalo",
"hashbrown 0.13.2",
@ -3685,10 +3776,12 @@ dependencies = [
"roc_module",
"roc_problem",
"roc_region",
"roc_solve_schema",
"roc_std",
"roc_target",
"roc_tracing",
"roc_types",
"roc_unify",
"static_assertions",
"ven_pretty",
]
@ -3758,6 +3851,7 @@ dependencies = [
"roc_build",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_gen_llvm",
"roc_load",
"roc_module",
@ -3792,6 +3886,7 @@ dependencies = [
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_std",
"roc_target",
"roc_types",
@ -3847,6 +3942,7 @@ dependencies = [
"roc_parse",
"roc_repl_eval",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
"tempfile",
@ -3910,6 +4006,7 @@ dependencies = [
"regex",
"roc_builtins",
"roc_can",
"roc_checkmate",
"roc_collections",
"roc_debug_flags",
"roc_derive",
@ -3925,6 +4022,7 @@ dependencies = [
"roc_reporting",
"roc_solve",
"roc_solve_problem",
"roc_solve_schema",
"roc_target",
"roc_types",
"roc_unify",
@ -3945,6 +4043,13 @@ dependencies = [
"roc_types",
]
[[package]]
name = "roc_solve_schema"
version = "0.0.1"
dependencies = [
"bitflags",
]
[[package]]
name = "roc_std"
version = "0.0.1"
@ -4005,11 +4110,12 @@ dependencies = [
name = "roc_unify"
version = "0.0.1"
dependencies = [
"bitflags",
"roc_checkmate",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_module",
"roc_solve_schema",
"roc_tracing",
"roc_types",
]
@ -4155,6 +4261,30 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schemars"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 1.0.109",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@ -4243,6 +4373,17 @@ dependencies = [
"syn 2.0.16",
]
[[package]]
name = "serde_derive_internals"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "serde_json"
version = "1.0.96"
@ -4644,7 +4785,6 @@ dependencies = [
"criterion",
"indoc",
"inkwell",
"lazy_static",
"libc",
"libloading",
"roc_bitcode",
@ -4797,6 +4937,17 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.21"
@ -4941,7 +5092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
dependencies = [
"crossbeam-channel",
"time",
"time 0.3.21",
"tracing-subscriber",
]
@ -5251,6 +5402,12 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -5575,6 +5732,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
@ -5859,9 +6025,9 @@ checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a"
[[package]]
name = "xml-rs"
version = "0.8.11"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1690519550bfa95525229b9ca2350c63043a4857b3b0013811b2ccf4a2420b01"
checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1"
[[package]]
name = "yaml-rust"

View file

@ -82,6 +82,7 @@ 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"
chrono = "0.4.26"
clap = { version = "4.2.7", default-features = false, features = ["std", "color", "suggestions", "help", "usage", "error-context"] }
colored = "2.0.0"
console_error_panic_hook = "0.1.7"
@ -141,6 +142,7 @@ reqwest = { version = "0.11.14", default-features = false, features = ["blocking
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" }
schemars = "0.8.12"
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

60
Earthfile Normal file
View file

@ -0,0 +1,60 @@
VERSION 0.6
FROM rust:1.70.0-slim-buster # make sure to update rust-toolchain.toml too so that everything uses the same rust version
WORKDIR /earthbuild
prep-debian:
RUN apt -y update
install-other-libs:
FROM +prep-debian
RUN apt -y install wget git
RUN apt -y install libunwind-dev pkg-config zlib1g-dev
RUN apt -y install unzip # for www/build.sh
install-zig-llvm:
ARG ZIG_ARCH
FROM +install-other-libs
# zig
RUN wget -c https://ziglang.org/download/0.9.1/zig-linux-$ZIG_ARCH-0.9.1.tar.xz --no-check-certificate
RUN tar -xf zig-linux-$ZIG_ARCH-0.9.1.tar.xz
RUN ln -s /earthbuild/zig-linux-$ZIG_ARCH-0.9.1/zig /bin/zig
# zig builtins wasm tests
RUN apt -y install build-essential
# llvm
RUN apt -y install lsb-release software-properties-common gnupg
RUN wget https://apt.llvm.org/llvm.sh
RUN chmod +x llvm.sh
RUN ./llvm.sh 13
RUN ln -s /usr/bin/clang-13 /usr/bin/clang
# use lld as linker
RUN ln -s /usr/bin/lld-13 /usr/bin/ld.lld
ENV RUSTFLAGS="-C link-arg=-fuse-ld=lld -C target-cpu=native"
RUN apt -y install libssl-dev
RUN OPENSSL_NO_VENDOR=1 cargo install wasm-pack
# sccache
RUN cargo install sccache
RUN sccache -V
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ENV SCCACHE_DIR=/earthbuild/sccache_dir
ENV CARGO_INCREMENTAL=0 # no need to recompile package when using new function
copy-dirs:
ARG ZIG_ARCH
FROM +install-zig-llvm --ZIG_ARCH=$ZIG_ARCH
COPY --dir crates examples Cargo.toml Cargo.lock version.txt .cargo www rust-toolchain.toml ./
build-nightly-release:
ARG RELEASE_FOLDER_NAME
ARG RUSTFLAGS
ARG ZIG_ARCH=x86_64
FROM +copy-dirs --ZIG_ARCH=$ZIG_ARCH
COPY --dir .git LICENSE LEGAL_DETAILS ci ./
# version.txt is used by the CLI: roc --version
RUN ./ci/write_version.sh
RUN RUSTFLAGS=$RUSTFLAGS cargo build --profile=release-with-lto --locked --bin roc
# strip debug info
RUN strip ./target/release-with-lto/roc
RUN ./ci/package_release.sh $RELEASE_FOLDER_NAME
RUN ls
SAVE ARTIFACT ./$RELEASE_FOLDER_NAME.tar.gz AS LOCAL $RELEASE_FOLDER_NAME.tar.gz

View file

@ -4,15 +4,20 @@ Roc is not ready for a 0.1 release yet, but we do have:
- [**installation** guide](https://github.com/roc-lang/roc/tree/main/getting_started)
- [**tutorial**](https://roc-lang.org/tutorial)
- [**docs** for the standard library](https://www.roc-lang.org/builtins/Str)
- [frequently asked questions](https://github.com/roc-lang/roc/blob/main/FAQ.md)
- [Zulip chat](https://roc.zulipchat.com) for help, questions and discussions
- [**docs** for the standard library](https://www.roc-lang.org/builtins)
- [**examples**](https://github.com/roc-lang/examples/tree/main/examples)
- [**faq**: frequently asked questions](https://github.com/roc-lang/roc/blob/main/FAQ.md)
- [**group chat**](https://roc.zulipchat.com) for help, questions and discussions
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!
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 [group chat](https://roc.zulipchat.com), we're friendly!
## Sponsors
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).
You can 💜 **sponsor** 💜 Roc on:
- [GitHub](https://github.com/sponsors/roc-lang)
- [Liberapay](https://liberapay.com/roc_lang)
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):
[<img src="https://user-images.githubusercontent.com/1094080/223597445-81755626-a080-4299-a38c-3c92e7548489.png" height="60" alt="Vendr logo"/>](https://www.vendr.com)
&nbsp;&nbsp;&nbsp;&nbsp;
@ -24,6 +29,7 @@ If you would like your company to become a corporate sponsor of Roc's developmen
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:
* [Ivo Balbaert](https://github.com/Ivo-Balbaert)
* [Lucas Rosa](https://github.com/rvcas)
* [Jonas Schell](https://github.com/Ocupe)
* [Christopher Dolan](https://github.com/cdolan)

40
ci/basic_nightly_test.sh Executable file
View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
# if to prevent unset vars errror
if [ -n "$(ls | grep -v "roc_nightly.*tar\.gz" | grep -v "^ci$")" ]; then
# Remove everything in this dir except the tar and ci folder.
# We want to test like a user who would have downloaded the release, so we clean up all files from the repo checkout.
to_delete=$(ls | grep -v "roc_nightly.*tar\.gz" | grep -v "^ci$")
for file_or_dir in $to_delete
do
echo "Removing: $file_or_dir"
rm -rf "$file_or_dir"
done
fi
# decompress the tar
ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
# delete tar
ls | grep "roc_nightly.*tar\.gz" | xargs rm -rf
# rename nightly folder
mv roc_nightly* roc_nightly
cd roc_nightly
# test roc hello world
./roc examples/helloWorld.roc
./roc examples/platform-switching/rocLovesRust.roc
./roc examples/platform-switching/rocLovesZig.roc
./roc examples/platform-switching/rocLovesC.roc
cd ..

View file

@ -10,6 +10,7 @@ version.workspace = true
[dependencies]
roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" }
roc_checkmate = { path = "../compiler/checkmate" }
roc_collections = { path = "../compiler/collections" }
roc_error_macros = { path = "../error_macros" }
roc_load = { path = "../compiler/load" }
@ -20,6 +21,7 @@ roc_problem = { path = "../compiler/problem" }
roc_region = { path = "../compiler/region" }
roc_reporting = { path = "../reporting" }
roc_solve = { path = "../compiler/solve" }
roc_solve_schema = { path = "../compiler/solve_schema" }
roc_target = { path = "../compiler/roc_target" }
roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify" }

View file

@ -59,7 +59,7 @@ impl From<ModuleError> for ASTError {
impl From<(Region, Loc<Ident>)> for ASTError {
fn from(ident_exists_err: (Region, Loc<Ident>)) -> Self {
Self::IdentExistsError {
msg: format!("{:?}", ident_exists_err),
msg: format!("{ident_exists_err:?}"),
}
}
}
@ -67,7 +67,7 @@ impl From<(Region, Loc<Ident>)> for ASTError {
impl<'a> From<SyntaxError<'a>> for ASTError {
fn from(syntax_err: SyntaxError) -> Self {
Self::SyntaxErrorNoBacktrace {
msg: format!("{:?}", syntax_err),
msg: format!("{syntax_err:?}"),
}
}
}

View file

@ -94,8 +94,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
let mut add_alias = |symbol, alias| {
debug_assert!(
!aliases.contains_key(&symbol),
"Duplicate alias definition for {:?}",
symbol
"Duplicate alias definition for {symbol:?}"
);
// TODO instead of using Region::zero for all of these,

View file

@ -2065,7 +2065,7 @@ pub mod test_constrain {
assert_eq!(actual_str, expected_str);
}
Err(e) => panic!("syntax error {:?}", e),
Err(e) => panic!("syntax error {e:?}"),
}
}

View file

@ -130,7 +130,7 @@ fn expr2_to_string_helper(
);
}
Expr2::Call { .. } => {
let _ = write!(out_string, "Call({:?})", expr2);
let _ = write!(out_string, "Call({expr2:?})");
}
Expr2::Closure { args, .. } => {
out_string.push_str("Closure:\n");
@ -148,7 +148,7 @@ fn expr2_to_string_helper(
}
}
&Expr2::Var { .. } => {
let _ = write!(out_string, "{:?}", expr2);
let _ = write!(out_string, "{expr2:?}");
}
Expr2::RuntimeError { .. } => {
out_string.push_str("RuntimeError\n");

View file

@ -665,37 +665,25 @@ pub fn expr_to_expr2<'a>(
ident,
} => canonicalize_lookup(env, scope, module_name, ident, region),
ParensAround(sub_expr) => expr_to_expr2(env, scope, sub_expr, region),
// Below this point, we shouln't see any of these nodes anymore because
// operator desugaring should have removed them!
bad_expr @ ParensAround(_) => {
panic!(
"A ParensAround did not get removed during operator desugaring somehow: {:#?}",
bad_expr
);
}
bad_expr @ SpaceBefore(_, _) => {
panic!(
"A SpaceBefore did not get removed during operator desugaring somehow: {:#?}",
bad_expr
"A SpaceBefore did not get removed during operator desugaring somehow: {bad_expr:#?}"
);
}
bad_expr @ SpaceAfter(_, _) => {
panic!(
"A SpaceAfter did not get removed during operator desugaring somehow: {:#?}",
bad_expr
"A SpaceAfter did not get removed during operator desugaring somehow: {bad_expr:#?}"
);
}
bad_expr @ BinOps { .. } => {
panic!(
"A binary operator chain did not get desugared somehow: {:#?}",
bad_expr
);
panic!("A binary operator chain did not get desugared somehow: {bad_expr:#?}");
}
bad_expr @ UnaryOp(_, _) => {
panic!(
"A unary operator did not get desugared somehow: {:#?}",
bad_expr
);
panic!("A unary operator did not get desugared somehow: {bad_expr:#?}");
}
rest => todo!("not yet implemented {:?}", rest),

View file

@ -227,7 +227,7 @@ pub fn update_str_expr(
Expr2::Str(old_pool_str) => Either::OldPoolStr(*old_pool_str),
other => UnexpectedASTNodeSnafu {
required_node_type: "SmallStr or Str",
encountered_node_type: format!("{:?}", other),
encountered_node_type: format!("{other:?}"),
}
.fail()?,
};

View file

@ -102,8 +102,7 @@ impl<'a> Env<'a> {
) -> Result<Symbol, RuntimeError> {
debug_assert!(
!module_name.is_empty(),
"Called env.qualified_lookup with an unqualified ident: {:?}",
ident
"Called env.qualified_lookup with an unqualified ident: {ident:?}"
);
let module_name: ModuleName = module_name.into();

View file

@ -68,7 +68,7 @@ impl<T> Clone for NodeId<T> {
fn clone(&self) -> Self {
NodeId {
index: self.index,
_phantom: PhantomData::default(),
_phantom: PhantomData,
}
}
}
@ -176,7 +176,7 @@ impl Pool {
NodeId {
index,
_phantom: PhantomData::default(),
_phantom: PhantomData,
}
} else {
todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows.");

View file

@ -45,7 +45,7 @@ impl PoolStr {
PoolStr {
first_node_id: NodeId {
index: 0,
_phantom: PhantomData::default(),
_phantom: PhantomData,
},
len: 0,
}

View file

@ -72,7 +72,7 @@ impl<'a, T: 'a + Sized> PoolVec<T> {
PoolVec {
first_node_id: NodeId {
index: 0,
_phantom: PhantomData::default(),
_phantom: PhantomData,
},
len: 0,
}
@ -179,7 +179,7 @@ where
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
_phantom: PhantomData,
};
self.len_remaining = len_remaining - 1;
@ -237,7 +237,7 @@ where
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
_phantom: PhantomData,
};
self.len_remaining = len_remaining - 1;
@ -288,7 +288,7 @@ impl<T> Iterator for PoolVecIterNodeIds<T> {
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
_phantom: PhantomData,
};
self.len_remaining = len_remaining - 1;

View file

@ -12,6 +12,7 @@ pub fn load_module(
) -> LoadedModule {
let load_config = LoadConfig {
target_info: TargetInfo::default_x86_64(), // editor only needs type info, so this is unused
function_kind: roc_solve::FunctionKind::LambdaSet, // TODO the editor may need to dynamically change this
render: roc_reporting::report::RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
@ -25,14 +26,8 @@ pub fn load_module(
match loaded {
Ok(x) => x,
Err(roc_load::LoadingProblem::FormattedReport(report)) => {
panic!(
"Failed to load module from src_file: {:?}. Report: {}",
src_file, report
);
panic!("Failed to load module from src_file: {src_file:?}. Report: {report}");
}
Err(e) => panic!(
"Failed to load module from src_file {:?}: {:?}",
src_file, e
),
Err(e) => panic!("Failed to load module from src_file {src_file:?}: {e:?}"),
}
}

View file

@ -2,12 +2,14 @@
#![allow(dead_code)]
use bumpalo::Bump;
use roc_can::expected::{Expected, PExpected};
use roc_checkmate::with_checkmate;
use roc_collections::all::{BumpMap, BumpMapDefault, MutMap};
use roc_error_macros::internal_error;
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_solve::module::Solved;
use roc_solve_schema::UnificationMode;
use roc_types::subs::{
self, AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields,
Subs, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
@ -17,9 +19,8 @@ use roc_types::types::{
RecordField,
};
use roc_unify::unify::unify;
use roc_unify::unify::Env as UEnv;
use roc_unify::unify::Mode;
use roc_unify::unify::Unified::*;
use roc_unify::Env as UEnv;
use crate::constrain::{Constraint, PresenceConstraint};
use crate::lang::core::types::Type2;
@ -228,10 +229,13 @@ fn solve<'a>(
);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -326,10 +330,13 @@ fn solve<'a>(
);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_VALUE,
) {
Success {
@ -402,10 +409,13 @@ fn solve<'a>(
// TODO(ayazhafiz): presence constraints for Expr2/Type2
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
expected,
Mode::EQ,
UnificationMode::EQ,
Polarity::OF_PATTERN,
) {
Success {
@ -606,7 +616,10 @@ fn solve<'a>(
let result = offenders.len();
if result > 0 {
dbg!(&subs, &offenders, &let_con.def_types);
eprintln!(
"subs: {:?}\n\noffenders: {:?}\n\nlet_con.def_types: {:?}\n",
&subs, &offenders, &let_con.def_types
);
}
result
@ -715,10 +728,13 @@ fn solve<'a>(
let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty);
match unify(
&mut UEnv::new(subs),
&mut with_checkmate!({
on => UEnv::new(subs, None),
off => UEnv::new(subs),
}),
actual,
includes,
Mode::PRESENT,
UnificationMode::PRESENT,
Polarity::OF_PATTERN,
) {
Success {
@ -1486,6 +1502,8 @@ fn adjust_rank_content(
rank
}
ErasedLambda => group_rank,
RangedNumber(_vars) => group_rank,
}
}
@ -1666,6 +1684,7 @@ fn instantiate_rigids_help(
}
}
ErasedLambda => {}
RangedNumber(_vars) => {}
}
@ -1978,6 +1997,12 @@ fn deep_copy_var_help(
copy
}
ErasedLambda => {
subs.set(copy, make_descriptor(ErasedLambda));
copy
}
RangedNumber(vars) => {
let new_content = RangedNumber(vars);

View file

@ -93,18 +93,18 @@ pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), Str
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
// I don't have the patience to debug this right now, so let's leave it for another day...
// TODO: fix PartialEq impl on ast types
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
if format!("{ast_normalized:?}") != format!("{reparsed_ast_normalized:?}") {
let mut fail_file = file.clone();
fail_file.set_extension("roc-format-failed");
std::fs::write(&fail_file, buf.as_str()).unwrap();
let mut before_file = file.clone();
before_file.set_extension("roc-format-failed-ast-before");
std::fs::write(&before_file, format!("{:#?}\n", ast_normalized)).unwrap();
std::fs::write(&before_file, format!("{ast_normalized:#?}\n")).unwrap();
let mut after_file = file.clone();
after_file.set_extension("roc-format-failed-ast-after");
std::fs::write(&after_file, format!("{:#?}\n", reparsed_ast_normalized)).unwrap();
std::fs::write(&after_file, format!("{reparsed_ast_normalized:#?}\n")).unwrap();
internal_error!(
"Formatting bug; formatting didn't reparse as the same tree\n\n\

View file

@ -20,6 +20,7 @@ use roc_load::{ExpectMetadata, Threading};
use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir;
use roc_packaging::tarball::Compression;
use roc_target::Target;
use std::env;
use std::ffi::{CString, OsStr, OsString};
use std::io;
@ -28,11 +29,8 @@ use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf};
use std::process;
use std::time::Instant;
use strum::{EnumIter, IntoEnumIterator, IntoStaticStr};
use target_lexicon::BinaryFormat;
use target_lexicon::{
Architecture, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture,
};
use strum::IntoEnumIterator;
use target_lexicon::{Architecture, Triple};
#[cfg(not(target_os = "linux"))]
use tempfile::TempDir;
@ -387,7 +385,7 @@ pub fn test(_matches: &ArgMatches, _triple: Triple) -> io::Result<i32> {
#[cfg(not(windows))]
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
use roc_build::program::report_problems_monomorphized;
use roc_load::{ExecutionMode, LoadConfig, LoadMonomorphizedError};
use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadMonomorphizedError};
use roc_packaging::cache;
use roc_target::TargetInfo;
@ -416,12 +414,10 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
match matches.value_source(ROC_FILE) {
Some(ValueSource::DefaultValue) => {
eprintln!(
"\nThe current directory ({}) does not contain a {} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n",
current_dir_string,
DEFAULT_ROC_FILENAME
"\nThe current directory ({current_dir_string}) does not contain a {DEFAULT_ROC_FILENAME} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n"
)
}
_ => eprintln!("\nThis file was not found: {}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n", expected_file_path_string),
_ => eprintln!("\nThis file was not found: {expected_file_path_string}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n"),
}
process::exit(1);
@ -431,10 +427,13 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let target = &triple;
let opt_level = opt_level;
let target_info = TargetInfo::from(target);
// TODO may need to determine this dynamically based on dev builds.
let function_kind = FunctionKind::LambdaSet;
// Step 1: compile the app and generate the .o file
let load_config = LoadConfig {
target_info,
function_kind,
// TODO: expose this from CLI?
render: roc_reporting::report::RenderTarget::ColorTerminal,
palette: roc_reporting::report::DEFAULT_PALETTE,
@ -565,16 +564,13 @@ pub fn build(
match matches.value_source(ROC_FILE) {
Some(ValueSource::DefaultValue) => {
eprintln!(
"\nThe current directory ({}) does not contain a {} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n",
current_dir_string,
DEFAULT_ROC_FILENAME
"\nThe current directory ({current_dir_string}) does not contain a {DEFAULT_ROC_FILENAME} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n"
)
}
_ => {
let mut error_lines = Vec::new();
error_lines.push(format!(
"This file was not found: {}",
expected_file_path_string
"This file was not found: {expected_file_path_string}"
));
// Add some additional hints if run as `roc [FILENAME]`.
if matches.subcommand().is_none() {
@ -584,8 +580,7 @@ pub fn build(
nearest_match(possible_typo, subcommands)
{
error_lines.push(format!(
"Did you mean to use the {} subcommand?",
nearest_command
"Did you mean to use the {nearest_command} subcommand?"
));
}
}
@ -1144,7 +1139,7 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
);
}
let path = PathBuf::from(format!("/proc/self/fd/{}", fd));
let path = PathBuf::from(format!("/proc/self/fd/{fd}"));
std::fs::write(&path, binary_bytes)?;
@ -1177,7 +1172,7 @@ fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<Executab
Ok(ExecutableFile::OnDisk(temp_dir, app_path_buf))
}
#[cfg(all(target_family = "windows"))]
#[cfg(target_family = "windows")]
fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<ExecutableFile> {
use std::fs::OpenOptions;
use std::io::Write;
@ -1275,83 +1270,3 @@ fn run_wasm<I: Iterator<Item = S>, S: AsRef<[u8]>>(wasm_path: &std::path::Path,
fn run_wasm<I: Iterator<Item = S>, S: AsRef<[u8]>>(_wasm_path: &std::path::Path, _args: I) {
println!("Running wasm files is not supported on this target.");
}
#[derive(Debug, Copy, Clone, EnumIter, IntoStaticStr, PartialEq, Eq, Default)]
pub enum Target {
#[strum(serialize = "system")]
#[default]
System,
#[strum(serialize = "linux32")]
Linux32,
#[strum(serialize = "linux64")]
Linux64,
#[strum(serialize = "windows64")]
Windows64,
#[strum(serialize = "wasm32")]
Wasm32,
}
impl Target {
pub fn to_triple(self) -> Triple {
use Target::*;
match self {
System => Triple::host(),
Linux32 => Triple {
architecture: Architecture::X86_32(X86_32Architecture::I386),
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
binary_format: BinaryFormat::Elf,
},
Linux64 => Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
binary_format: BinaryFormat::Elf,
},
Windows64 => Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Windows,
environment: Environment::Gnu,
binary_format: BinaryFormat::Coff,
},
Wasm32 => Triple {
architecture: Architecture::Wasm32,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Wasi,
environment: Environment::Unknown,
binary_format: BinaryFormat::Wasm,
},
}
}
}
impl From<&Target> for Triple {
fn from(target: &Target) -> Self {
target.to_triple()
}
}
impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", Into::<&'static str>::into(self))
}
}
impl std::str::FromStr for Target {
type Err = String;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"system" => Ok(Target::System),
"linux32" => Ok(Target::Linux32),
"linux64" => Ok(Target::Linux64),
"windows64" => Ok(Target::Windows64),
"wasm32" => Ok(Target::Wasm32),
_ => Err(format!("Roc does not know how to compile to {}", string)),
}
}
}

View file

@ -2,17 +2,18 @@
use roc_build::link::LinkType;
use roc_build::program::{check_file, CodeGenBackend};
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_DEV, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET,
FLAG_TIME, GLUE_DIR, GLUE_SPEC, ROC_FILE,
build_app, format, test, BuildConfig, FormatMode, 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_DEV, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME,
GLUE_DIR, GLUE_SPEC, ROC_FILE,
};
use roc_docs::generate_docs_html;
use roc_error_macros::user_error;
use roc_gen_dev::AssemblyBackendMode;
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{LoadingProblem, Threading};
use roc_load::{FunctionKind, LoadingProblem, Threading};
use roc_packaging::cache::{self, RocCacheDir};
use roc_target::Target;
use std::fs::{self, FileType};
use std::io;
use std::path::{Path, PathBuf};
@ -122,10 +123,12 @@ fn main() -> io::Result<()> {
.get_one::<String>(FLAG_TARGET)
.and_then(|s| Target::from_str(s).ok())
.unwrap_or_default();
let function_kind = FunctionKind::LambdaSet;
roc_linker::generate_stub_lib(
input_path,
RocCacheDir::Persistent(cache::roc_cache_dir().as_path()),
&target.to_triple(),
function_kind,
)
}
Some((CMD_BUILD, matches)) => {
@ -200,12 +203,12 @@ fn main() -> io::Result<()> {
}
Err(LoadingProblem::FormattedReport(report)) => {
print!("{}", report);
print!("{report}");
Ok(1)
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
panic!("build_file failed with error:\n{other:?}");
}
}
}
@ -272,7 +275,7 @@ fn main() -> io::Result<()> {
let format_exit_code = match format(roc_files, format_mode) {
Ok(_) => 0,
Err(message) => {
eprintln!("{}", message);
eprintln!("{message}");
1
}
};

View file

@ -90,7 +90,7 @@ mod cli_run {
// e.g. "1 error and 0 warnings found in 123 ms."
let (before_first_digit, _) = err.split_at(err.rfind("found in ").unwrap());
let err = format!("{}found in <ignored for test> ms.", before_first_digit);
let err = format!("{before_first_digit}found in <ignored for test> ms.");
// make paths consistent
let err = err.replace('\\', "/");
@ -230,7 +230,7 @@ mod cli_run {
what: _,
xwhat,
} = error;
println!("Valgrind Error: {}\n", kind);
println!("Valgrind Error: {kind}\n");
if let Some(ValgrindErrorXWhat {
text,
@ -238,14 +238,14 @@ mod cli_run {
leakedblocks: _,
}) = xwhat
{
println!(" {}", text);
println!(" {text}");
}
}
panic!("Valgrind reported memory errors");
}
} else {
let exit_code = match valgrind_out.status.code() {
Some(code) => format!("exit code {}", code),
Some(code) => format!("exit code {code}"),
None => "no exit code".to_string(),
};
@ -301,7 +301,7 @@ mod cli_run {
// e.g. "1 failed and 0 passed in 123 ms."
if let Some(split) = actual.rfind("passed in ") {
let (before_first_digit, _) = actual.split_at(split);
actual = format!("{}passed in <ignored for test> ms.", before_first_digit);
actual = format!("{before_first_digit}passed in <ignored for test> ms.");
}
let self_path = file.display().to_string();
@ -397,8 +397,7 @@ mod cli_run {
"swiftui" | "rocLovesSwift" => {
if cfg!(not(target_os = "macos")) {
eprintln!(
"WARNING: skipping testing example {} because it only works on MacOS.",
roc_filename
"WARNING: skipping testing example {roc_filename} because it only works on MacOS."
);
return;
} else {
@ -409,8 +408,7 @@ mod cli_run {
"rocLovesWebAssembly" => {
// this is a web assembly example, but we don't test with JS at the moment
eprintln!(
"WARNING: skipping testing example {} because it only works in a browser!",
roc_filename
"WARNING: skipping testing example {roc_filename} because it only works in a browser!"
);
return;
}
@ -664,7 +662,7 @@ mod cli_run {
fn hello_gui() {
test_roc_app_slim(
"examples/gui",
"hello.roc",
"helloBROKEN.roc",
"hello-gui",
"",
UseValgrind::No,
@ -677,7 +675,7 @@ mod cli_run {
fn breakout() {
test_roc_app_slim(
"examples/gui/breakout",
"breakout.roc",
"breakoutBROKEN.roc",
"breakout",
"",
UseValgrind::No,
@ -689,7 +687,7 @@ mod cli_run {
fn breakout_hello_gui() {
test_roc_app_slim(
"examples/gui/breakout",
"hello-gui.roc",
"hello-guiBROKEN.roc",
"hello-gui",
"",
UseValgrind::No,
@ -965,16 +963,14 @@ mod cli_run {
match roc_filename {
"QuicksortApp.roc" => {
eprintln!(
"WARNING: skipping testing benchmark {} because the test is broken right now!",
roc_filename
"WARNING: skipping testing benchmark {roc_filename} because the test is broken right now!"
);
return;
}
"TestAStar.roc" => {
if cfg!(feature = "wasm32-cli-run") {
eprintln!(
"WARNING: skipping testing benchmark {} because it currently does not work on wasm32 due to dictionaries.",
roc_filename
"WARNING: skipping testing benchmark {roc_filename} because it currently does not work on wasm32 due to dictionaries."
);
return;
}

View file

@ -30,7 +30,7 @@ mod editor_launch_test {
// 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));
.unwrap_or_else(|_| panic!("Failed to set current dir to {root_dir:?}"));
let roc_binary_path = build_roc_bin(&["--features", "editor"]);

View file

@ -6,20 +6,6 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
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 mem = std.mem;
const Allocator = mem.Allocator;

View file

@ -6,20 +6,6 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
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 mem = std.mem;
const Allocator = mem.Allocator;

View file

@ -6,20 +6,6 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
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 mem = std.mem;
const Allocator = mem.Allocator;

View file

@ -5,20 +5,6 @@ const expectEqual = testing.expectEqual;
const expect = testing.expect;
const maxInt = std.math.maxInt;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// 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 mem = std.mem;
const Allocator = mem.Allocator;

View file

@ -6,20 +6,6 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
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 mem = std.mem;
const Allocator = mem.Allocator;

View file

@ -21,7 +21,7 @@ main =
|> Task.putLine
Err GetIntError ->
Task.putLine "Error: Failed to get Integer from stdin."
Task.putLine "Error: Failed to get Integer from stdin."
Expr : [
Add Expr Expr,
@ -100,35 +100,27 @@ constFolding = \e ->
x1 = constFolding e1
x2 = constFolding e2
when Pair x1 x2 is
Pair (Val a) (Val b) ->
Val (a + b)
when x1 is
Val a ->
when x2 is
Val b -> Val (a + b)
Add (Val b) x | Add x (Val b) -> Add (Val (a + b)) x
_ -> Add x1 x2
Pair (Val a) (Add (Val b) x) ->
Add (Val (a + b)) x
Pair (Val a) (Add x (Val b)) ->
Add (Val (a + b)) x
Pair y1 y2 ->
Add y1 y2
_ -> Add x1 x2
Mul e1 e2 ->
x1 = constFolding e1
x2 = constFolding e2
when Pair x1 x2 is
Pair (Val a) (Val b) ->
Val (a * b)
when x1 is
Val a ->
when x2 is
Val b -> Val (a * b)
Mul (Val b) x | Mul x (Val b) -> Mul (Val (a * b)) x
_ -> Mul x1 x2
Pair (Val a) (Mul (Val b) x) ->
Mul (Val (a * b)) x
Pair (Val a) (Mul x (Val b)) ->
Mul (Val (a * b)) x
Pair y1 y2 ->
Add y1 y2
_ -> Mul x1 x2
_ ->
e

View file

@ -23,21 +23,16 @@ main =
Err GetIntError ->
Task.putLine "Error: Failed to get Integer from stdin."
nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr
nestHelp = \s, f, m, x -> when m is
0 -> Task.succeed x
_ ->
w <- Task.after (f (s - m) x)
nestHelp s f (m - 1) w
nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr
nest = \f, n, e -> Task.loop { s: n, f, m: n, x: e } nestHelp
State : { s : I64, f : I64, Expr -> IO Expr, m : I64, x : Expr }
nestHelp : State -> IO [Step State, Done Expr]
nestHelp = \{ s, f, m, x } ->
when m is
0 -> Task.succeed (Done x)
_ ->
w <- Task.after (f (s - m) x)
Task.succeed (Step { s, f, m: (m - 1), x: w })
nest = \f, n, e -> nestHelp n f n e
Expr : [Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr]

View file

@ -10,8 +10,8 @@ main =
when inputResult is
Ok n ->
queens n # original koka 13
|> Num.toStr
|> Task.putLine
|> Num.toStr
|> Task.putLine
Err GetIntError ->
Task.putLine "Error: Failed to get Integer from stdin."
@ -21,7 +21,8 @@ ConsList a : [Nil, Cons a (ConsList a)]
queens = \n -> length (findSolutions n n)
findSolutions = \n, k ->
if k <= 0 then # should we use U64 as input type here instead?
if k <= 0 then
# should we use U64 as input type here instead?
Cons Nil Nil
else
extend n Nil (findSolutions n (k - 1))
@ -40,18 +41,18 @@ appendSafe = \k, soln, solns ->
else
appendSafe (k - 1) soln solns
safe : I64, I64, ConsList I64 -> Bool
safe = \queen, diagonal, xs ->
when xs is
Nil -> Bool.true
Cons q t ->
queen != q && queen != q + diagonal && queen != q - diagonal && safe queen (diagonal + 1) t
if queen != q && queen != q + diagonal && queen != q - diagonal then
safe queen (diagonal + 1) t
else
Bool.false
length : ConsList a -> I64
length = \xs ->
dbg "length"
lengthHelp xs 0
lengthHelp : ConsList a, I64 -> I64

View file

@ -23,7 +23,6 @@ main =
Err GetIntError ->
Task.putLine "Error: Failed to get Integer from stdin."
sort : List I64 -> List I64
sort = \list ->

View file

@ -93,9 +93,15 @@ ins = \tree, kx, vx ->
Node Black a ky vy b ->
if lt kx ky then
(if isRed a then balance1 (Node Black Leaf ky vy b) (ins a kx vx) else Node Black (ins a kx vx) ky vy b)
if isRed a then
balance1 (Node Black Leaf ky vy b) (ins a kx vx)
else
Node Black (ins a kx vx) ky vy b
else if lt ky kx then
(if isRed b then balance2 (Node Black a ky vy Leaf) (ins b kx vx) else Node Black a ky vy (ins b kx vx))
if isRed b then
balance2 (Node Black a ky vy Leaf) (ins b kx vx)
else
Node Black a ky vy (ins b kx vx)
else
Node Black a kx vx b

View file

@ -26,7 +26,6 @@ main =
Err GetIntError ->
Task.putLine "Error: Failed to get Integer from stdin."
boom : Str -> a
boom = \_ -> boom ""

View file

@ -7,20 +7,6 @@ const expectEqual = testing.expectEqual;
const expect = testing.expect;
const maxInt = std.math.maxInt;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// 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 mem = std.mem;
const Allocator = mem.Allocator;

View file

@ -5,5 +5,5 @@ platform "benchmarks"
imports [Task.{ Task }]
provides [mainForHost]
mainForHost : Task {} [] as Fx
mainForHost : Task {} []
mainForHost = main

View file

@ -6,20 +6,6 @@ const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
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;

View file

@ -25,8 +25,7 @@ fn exec_bench_w_input<T: Measurement>(
assert!(
compile_out.status.success(),
"build ended with bad status {:?}",
compile_out
"build ended with bad status {compile_out:?}"
);
check_cmd_output(file, stdin_str, executable_filename, expected_ending);
@ -58,10 +57,7 @@ fn check_cmd_output(
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?}",
expected_ending, out
);
panic!("expected output to end with {expected_ending:?} but instead got {out:#?}");
}
assert!(out.status.success());
}
@ -96,7 +92,7 @@ fn bench_cmd<T: Measurement>(
}
if let Some(bench_group) = bench_group_opt {
bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| {
bench_group.bench_function(&format!("Benchmarking {executable_filename:?}"), |b| {
b.iter(|| run_cmd(black_box(&cmd_str), black_box([stdin_str]), &[], []))
});
} else {

View file

@ -11,7 +11,6 @@ use std::env;
use std::ffi::{OsStr, OsString};
use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use std::sync::Mutex;
@ -30,68 +29,13 @@ where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let roc_binary_path = build_roc_bin_cached();
run_roc_with_stdin_and_env(&roc_binary_path, args, stdin_vals, extra_env)
}
// If we don't already have a /target/release/roc, build it!
pub fn build_roc_bin_cached() -> PathBuf {
let roc_binary_path = path_to_roc_binary();
if !roc_binary_path.exists() {
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
run_roc_with_stdin_and_env(args, stdin_vals, extra_env)
}
// 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.
// TODO: In the future, look into compiling the shared library once and then caching it.
static GLUE_LOCK: Mutex<()> = Mutex::new(());
pub fn run_glue<I, S>(args: I) -> Out
@ -101,7 +45,7 @@ where
{
let _guard = GLUE_LOCK.lock().unwrap();
run_roc_with_stdin(&path_to_roc_binary(), args, &[])
run_roc_with_stdin(args, &[])
}
pub fn has_error(stderr: &str) -> bool {
@ -170,16 +114,15 @@ pub fn strip_colors(str: &str) -> String {
.replace(ANSI_STYLE_CODES.color_reset, "")
}
pub fn run_roc_with_stdin<I, S>(path: &Path, args: I, stdin_vals: &[&str]) -> Out
pub fn run_roc_with_stdin<I, S>(args: I, stdin_vals: &[&str]) -> Out
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
run_roc_with_stdin_and_env(path, args, stdin_vals, &[])
run_roc_with_stdin_and_env(args, stdin_vals, &[])
}
pub fn run_roc_with_stdin_and_env<I, S>(
roc_path: &Path,
args: I,
stdin_vals: &[&str],
extra_env: &[(&str, &str)],
@ -188,6 +131,7 @@ where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let roc_path = build_roc_bin_cached();
let mut roc_cmd = Command::new(roc_path);
for arg in args {
@ -235,6 +179,59 @@ where
}
}
// If we don't already have a /target/release/roc, build it!
pub fn build_roc_bin_cached() -> PathBuf {
let roc_binary_path = path_to_roc_binary();
if !roc_binary_path.exists() {
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
}
pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a str, &'a str)>>(
cmd_name: &str,
stdin_vals: I,
@ -258,7 +255,7 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|_| panic!("failed to execute cmd `{}` in CLI test", cmd_name));
.unwrap_or_else(|_| panic!("failed to execute cmd `{cmd_name}` in CLI test"));
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
@ -272,7 +269,7 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>, E: IntoIterator<Item = (&'a
let output = child
.wait_with_output()
.unwrap_or_else(|_| panic!("failed to execute cmd `{}` in CLI test", cmd_name));
.unwrap_or_else(|_| panic!("failed to execute cmd `{cmd_name}` in CLI test"));
Out {
cmd_str,

View file

@ -376,7 +376,7 @@ pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> St
let node = mark_node_pool.get(root_node_id);
writeln!(full_string, "{} mn_id {}\n", node, root_node_id).unwrap();
writeln!(full_string, "{node} mn_id {root_node_id}\n").unwrap();
tree_as_string_helper(node, 1, &mut full_string, mark_node_pool);
@ -399,7 +399,7 @@ fn tree_as_string_helper(
.join("")
.to_owned();
writeln!(full_str, "{} mn_id {}", child_str, child_id).unwrap();
writeln!(full_str, "{child_str} mn_id {child_id}").unwrap();
tree_string.push_str(&full_str);

View file

@ -56,7 +56,7 @@ pub type MarkResult<T, E = MarkError> = std::result::Result<T, E>;
impl From<UtilError> for MarkError {
fn from(util_err: UtilError) -> Self {
let msg = format!("{}", util_err);
let msg = format!("{util_err}");
// hack to handle MarkError derive
let dummy_res: Result<(), NoneError> = Err(NoneError {});

View file

@ -39,8 +39,8 @@ impl SlowPool {
for (mark_node_id, node) in self.nodes.iter().enumerate() {
let ast_node_id_str = match mark_id_ast_id_map.get(mark_node_id) {
Ok(ast_id) => format!("{:?}", ast_id),
Err(err) => format!("{:?}", err),
Ok(ast_id) => format!("{ast_id:?}"),
Err(err) => format!("{err:?}"),
};
let ast_node_id: String = ast_node_id_str
.chars()
@ -52,7 +52,7 @@ impl SlowPool {
let node_children = node.get_children_ids();
if !node_children.is_empty() {
child_str = format!("children: {:?}", node_children);
child_str = format!("children: {node_children:?}");
}
write!(

View file

@ -14,8 +14,8 @@ use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_mono::ir::{
Call, CallType, EntryPoint, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement,
Literal, ModifyRc, OptLevel, Proc, ProcLayout, SingleEntryPoint, Stmt,
Call, CallType, EntryPoint, ErasedField, Expr, HigherOrderLowLevel, HostExposedLambdaSet,
ListLiteralElement, Literal, ModifyRc, OptLevel, Proc, ProcLayout, SingleEntryPoint, Stmt,
};
use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutInterner, LayoutRepr, Niche, RawFunctionLayout,
@ -117,7 +117,7 @@ where
}
if debug() {
for (i, c) in (format!("{:?}", symbol)).chars().take(25).enumerate() {
for (i, c) in (format!("{symbol:?}")).chars().take(25).enumerate() {
name_bytes[25 + i] = c as u8;
}
}
@ -131,21 +131,23 @@ fn bytes_as_ascii(bytes: &[u8]) -> String {
let mut buf = String::new();
for byte in bytes {
write!(buf, "{:02X}", byte).unwrap();
write!(buf, "{byte:02X}").unwrap();
}
buf
}
pub fn spec_program<'a, 'r, I>(
pub fn spec_program<'a, 'r, I1, I2>(
arena: &'a Bump,
interner: &'r mut STLayoutInterner<'a>,
interner: &'r STLayoutInterner<'a>,
opt_level: OptLevel,
entry_point: roc_mono::ir::EntryPoint<'a>,
procs: I,
procs: I1,
hels: I2,
) -> Result<morphic_lib::Solutions>
where
I: Iterator<Item = &'r Proc<'a>>,
I1: Iterator<Item = &'r Proc<'a>>,
I2: Iterator<Item = &'r HostExposedLambdaSet<'a>>,
{
let main_module = {
let mut m = ModDefBuilder::new();
@ -181,40 +183,38 @@ where
let mut type_definitions = MutSet::default();
let mut host_exposed_functions = Vec::new();
let mut erased_functions = Vec::new();
for hels in hels {
match hels.raw_function_layout {
RawFunctionLayout::Function(_, _, _) => {
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, hels.proc_layout.arguments));
}
RawFunctionLayout::ErasedFunction(..) => {
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, hels.proc_layout.arguments));
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
let bytes =
func_name_bytes_help(hels.symbol, [], Niche::NONE, hels.proc_layout.result);
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
}
}
}
// all other functions
for proc in procs {
let bytes = func_name_bytes(proc);
let func_name = FuncName(&bytes);
if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts {
for (_, hels) in aliases {
match hels.raw_function_layout {
RawFunctionLayout::Function(_, _, _) => {
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, hels.proc_layout.arguments));
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
let bytes = func_name_bytes_help(
hels.symbol,
[],
Niche::NONE,
hels.proc_layout.result,
);
host_exposed_functions.push((bytes, hels.proc_layout.arguments));
}
}
}
}
if debug() {
eprintln!(
"{:?}: {:?} with {:?} args",
@ -226,6 +226,11 @@ where
let (spec, type_names) = proc_spec(arena, interner, proc)?;
if proc.is_erased {
let args = &*arena.alloc_slice_fill_iter(proc.args.iter().map(|(lay, _)| *lay));
erased_functions.push((bytes, args));
}
type_definitions.extend(type_names);
m.add_func(func_name, spec)?;
@ -253,6 +258,7 @@ where
entry_point_layout,
Some(roc_main),
&host_exposed_functions,
&erased_functions,
)?;
type_definitions.extend(env.type_names);
@ -279,8 +285,14 @@ where
.collect();
let mut env = Env::new();
let entry_point_function =
build_entry_point(&mut env, interner, layout, None, &host_exposed)?;
let entry_point_function = build_entry_point(
&mut env,
interner,
layout,
None,
&host_exposed,
&erased_functions,
)?;
type_definitions.extend(env.type_names);
@ -360,10 +372,11 @@ fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId)
fn build_entry_point<'a>(
env: &mut Env<'a>,
interner: &mut STLayoutInterner<'a>,
interner: &STLayoutInterner<'a>,
layout: roc_mono::ir::ProcLayout<'a>,
entry_point_function: Option<FuncName>,
host_exposed_functions: &[([u8; SIZE], &'a [InLayout<'a>])],
erased_functions: &[([u8; SIZE], &'a [InLayout<'a>])],
) -> Result<FuncDef> {
let mut builder = FuncDefBuilder::new();
let outer_block = builder.add_block();
@ -394,7 +407,7 @@ fn build_entry_point<'a>(
}
// add fake calls to host-exposed functions so they are specialized
for (name_bytes, layouts) in host_exposed_functions {
for (name_bytes, layouts) in host_exposed_functions.iter().chain(erased_functions) {
let host_exposed_func_name = FuncName(name_bytes);
if Some(host_exposed_func_name) == entry_point_function {
@ -403,7 +416,7 @@ fn build_entry_point<'a>(
let block = builder.add_block();
let struct_layout = interner.insert_direct_no_semantic(LayoutRepr::struct_(layouts));
let struct_layout = LayoutRepr::struct_(layouts);
let type_id = layout_spec(env, &mut builder, interner, struct_layout)?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
@ -433,7 +446,7 @@ fn build_entry_point<'a>(
fn proc_spec<'a>(
arena: &'a Bump,
interner: &mut STLayoutInterner<'a>,
interner: &STLayoutInterner<'a>,
proc: &Proc<'a>,
) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)> {
let mut builder = FuncDefBuilder::new();
@ -460,10 +473,14 @@ fn proc_spec<'a>(
)?;
let root = BlockExpr(block, value_id);
let args_struct_layout =
interner.insert_direct_no_semantic(LayoutRepr::struct_(argument_layouts.into_bump_slice()));
let args_struct_layout = LayoutRepr::struct_(argument_layouts.into_bump_slice());
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 ret_type_id = layout_spec(
&mut env,
&mut builder,
interner,
interner.get_repr(proc.ret_layout),
)?;
let spec = builder.build(arg_type_id, ret_type_id, root)?;
@ -506,6 +523,12 @@ fn apply_refcount_operation(
builder.add_recursive_touch(block, argument)?;
}
ModifyRc::DecRef(symbol) => {
// this is almost certainly suboptimal, but not incorrect
let argument = env.symbols[symbol];
builder.add_recursive_touch(block, argument)?;
}
ModifyRc::Free(symbol) => {
// this is almost certainly suboptimal, but not incorrect
let argument = env.symbols[symbol];
builder.add_recursive_touch(block, argument)?;
}
@ -516,7 +539,7 @@ fn apply_refcount_operation(
fn stmt_spec<'a>(
builder: &mut FuncDefBuilder,
interner: &mut STLayoutInterner<'a>,
interner: &STLayoutInterner<'a>,
env: &mut Env<'a>,
block: BlockId,
layout: InLayout<'a>,
@ -601,10 +624,15 @@ 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)?);
type_ids.push(layout_spec(
env,
builder,
interner,
interner.get_repr(p.layout),
)?);
}
let ret_type_id = layout_spec(env, builder, interner, layout)?;
let ret_type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
@ -645,7 +673,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)?;
let ret_type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
let argument = build_tuple_value(builder, env, block, symbols)?;
let jpid = env.join_points[id];
@ -654,7 +682,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)?;
let result_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
builder.add_unknown_with(block, &[env.symbols[msg]], result_type)
}
@ -692,7 +720,7 @@ fn build_recursive_tuple_type<'a>(
let mut field_types = Vec::new();
for field in layouts.iter() {
let type_id = layout_spec_help(env, builder, interner, *field)?;
let type_id = layout_spec_help(env, builder, interner, interner.get_repr(*field))?;
field_types.push(type_id);
}
@ -708,7 +736,12 @@ fn build_tuple_type<'a>(
let mut field_types = Vec::new();
for field in layouts.iter() {
field_types.push(layout_spec(env, builder, interner, *field)?);
field_types.push(layout_spec(
env,
builder,
interner,
interner.get_repr(*field),
)?);
}
builder.add_tuple_type(&field_types)
@ -742,7 +775,7 @@ fn add_loop(
fn call_spec<'a>(
builder: &mut FuncDefBuilder,
interner: &mut STLayoutInterner<'a>,
interner: &STLayoutInterner<'a>,
env: &mut Env<'a>,
block: BlockId,
layout: InLayout<'a>,
@ -768,6 +801,16 @@ fn call_spec<'a>(
let module = MOD_APP;
builder.add_call(block, spec_var, module, name, arg_value_id)
}
ByPointer {
pointer,
ret_layout,
arg_layouts: _,
} => {
let result_type = layout_spec(env, builder, interner, interner.get_repr(*ret_layout))?;
let fnptr = env.symbols[pointer];
let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?;
builder.add_unknown_with(block, &[fnptr, arg_value_id], result_type)
}
Foreign {
foreign_symbol: _,
ret_layout,
@ -778,7 +821,7 @@ fn call_spec<'a>(
.map(|symbol| env.symbols[symbol])
.collect();
let result_type = layout_spec(env, builder, interner, *ret_layout)?;
let result_type = layout_spec(env, builder, interner, interner.get_repr(*ret_layout))?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -849,11 +892,10 @@ 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)?;
let output_element_type =
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
let state_layout = interner.insert_direct_no_semantic(LayoutRepr::Builtin(
Builtin::List(*return_layout),
));
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -880,8 +922,7 @@ fn call_spec<'a>(
let arg0_layout = argument_layouts[0];
let state_layout = interner
.insert_direct_no_semantic(LayoutRepr::Builtin(Builtin::List(arg0_layout)));
let state_layout = LayoutRepr::Builtin(Builtin::List(arg0_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = list;
@ -906,11 +947,10 @@ 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)?;
let output_element_type =
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
let state_layout = interner.insert_direct_no_semantic(LayoutRepr::Builtin(
Builtin::List(*return_layout),
));
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -941,11 +981,10 @@ 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)?;
let output_element_type =
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
let state_layout = interner.insert_direct_no_semantic(LayoutRepr::Builtin(
Builtin::List(*return_layout),
));
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -982,11 +1021,10 @@ 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)?;
let output_element_type =
layout_spec(env, builder, interner, interner.get_repr(*return_layout))?;
let state_layout = interner.insert_direct_no_semantic(LayoutRepr::Builtin(
Builtin::List(*return_layout),
));
let state_layout = LayoutRepr::Builtin(Builtin::List(*return_layout));
let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@ -1042,7 +1080,7 @@ fn lowlevel_spec<'a>(
) -> Result<ValueId> {
use LowLevel::*;
let type_id = layout_spec(env, builder, interner, layout)?;
let type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
let mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
@ -1150,7 +1188,8 @@ fn lowlevel_spec<'a>(
match interner.get_repr(layout) {
LayoutRepr::Builtin(Builtin::List(element_layout)) => {
let type_id = layout_spec(env, builder, interner, element_layout)?;
let type_id =
layout_spec(env, builder, interner, interner.get_repr(element_layout))?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
@ -1198,7 +1237,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)?;
let result_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
builder.add_unknown_with(block, &arguments, result_type)
}
@ -1283,7 +1322,7 @@ fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> {
fn expr_spec<'a>(
builder: &mut FuncDefBuilder,
interner: &mut STLayoutInterner<'a>,
interner: &STLayoutInterner<'a>,
env: &mut Env<'a>,
block: BlockId,
layout: InLayout<'a>,
@ -1294,21 +1333,16 @@ fn expr_spec<'a>(
match expr {
Literal(literal) => literal_spec(builder, block, literal),
NullPointer => {
let pointer_type = layout_spec(env, builder, interner, layout)?;
let pointer_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
builder.add_unknown_with(block, &[], pointer_type)
}
Call(call) => call_spec(builder, interner, env, block, layout, call),
Reuse {
tag_layout,
tag_id,
arguments,
..
}
| Tag {
Tag {
tag_layout,
tag_id,
arguments,
reuse: _,
} => {
let data_id = build_tuple_value(builder, env, block, arguments)?;
@ -1347,16 +1381,6 @@ fn expr_spec<'a>(
builder.add_make_named(block, MOD_APP, type_name, tag_value_id)
}
ExprBox { symbol } => {
let value_id = env.symbols[symbol];
with_new_heap_cell(builder, block, value_id)
}
ExprUnbox { symbol } => {
let tuple_id = env.symbols[symbol];
builder.add_get_tuple_field(block, tuple_id, BOX_VALUE_INDEX)
}
Struct(fields) => build_tuple_value(builder, env, block, fields),
UnionAtIndex {
index,
@ -1412,6 +1436,38 @@ fn expr_spec<'a>(
builder.add_get_tuple_field(block, variant_id, index)
}
},
UnionFieldPtrAtIndex {
index,
tag_id,
structure,
union_layout,
} => {
let index = (*index) as u32;
let tag_value_id = env.symbols[structure];
let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes);
// unwrap the named wrapper
let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
// now we have a tuple (cell, union { ... }); decompose
let heap_cell = builder.add_get_tuple_field(block, union_id, TAG_CELL_INDEX)?;
let union_data = builder.add_get_tuple_field(block, union_id, TAG_DATA_INDEX)?;
// we're reading from this value, so touch the heap cell
builder.add_touch(block, heap_cell)?;
// next, unwrap the union at the tag id that we've got
let variant_id = builder.add_unwrap_union(block, union_data, *tag_id as u32)?;
let value = builder.add_get_tuple_field(block, variant_id, index)?;
// construct the box. Here the heap_cell of the tag is re-used, I'm hoping that that
// conveys to morphic that we're borrowing into the existing tag?!
builder.add_make_tuple(block, &[heap_cell, value])
}
StructAtIndex {
index, structure, ..
} => {
@ -1419,7 +1475,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)?;
let type_id = layout_spec(env, builder, interner, interner.get_repr(*elem_layout))?;
let list = new_list(builder, block, type_id)?;
@ -1446,7 +1502,8 @@ fn expr_spec<'a>(
EmptyArray => match interner.get_repr(layout) {
LayoutRepr::Builtin(Builtin::List(element_layout)) => {
let type_id = layout_spec(env, builder, interner, element_layout)?;
let type_id =
layout_spec(env, builder, interner, interner.get_repr(element_layout))?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
@ -1483,8 +1540,30 @@ fn expr_spec<'a>(
let value = with_new_heap_cell(builder, block, union_data)?;
builder.add_make_named(block, MOD_APP, type_name, value)
}
FunctionPointer { .. } => {
let pointer_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
builder.add_unknown_with(block, &[], pointer_type)
}
ErasedMake { callee, value } => {
let value = match value {
Some(v) => box_erasure_value_unknown(builder, block, env.symbols[v]),
// model nullptr
None => box_erasure_value_unknown_nullptr(builder, block),
}?;
let callee = env.symbols[callee];
erasure_make(builder, block, value, callee)
}
ErasedLoad { symbol, field } => {
let value = env.symbols[symbol];
let loaded_type = layout_spec(env, builder, interner, interner.get_repr(layout))?;
erasure_load(builder, block, value, *field, loaded_type)
}
RuntimeErrorFunction(_) => {
let type_id = layout_spec(env, builder, interner, layout)?;
let type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
builder.add_terminate(block, type_id)
}
@ -1493,6 +1572,16 @@ fn expr_spec<'a>(
builder.add_make_tuple(block, &[])
}
Alloca { initializer, .. } => {
let initializer = &initializer.as_ref().map(|s| env.symbols[s]);
let values = match initializer {
Some(initializer) => std::slice::from_ref(initializer),
None => &[],
};
let type_id = layout_spec(env, builder, interner, interner.get_repr(layout))?;
builder.add_unknown_with(block, values, type_id)
}
}
}
@ -1515,7 +1604,7 @@ fn layout_spec<'a>(
env: &mut Env<'a>,
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
layout: LayoutRepr<'a>,
) -> Result<TypeId> {
layout_spec_help(env, builder, interner, layout)
}
@ -1539,16 +1628,19 @@ fn layout_spec_help<'a>(
env: &mut Env<'a>,
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
layout: LayoutRepr<'a>,
) -> Result<TypeId> {
use LayoutRepr::*;
match interner.get_repr(layout) {
match layout {
Builtin(builtin) => builtin_spec(env, builder, interner, &builtin),
Struct(field_layouts) => 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,
interner.get_repr(lambda_set.runtime_representation()),
),
Union(union_layout) => {
match union_layout {
UnionLayout::NonRecursive(&[]) => {
@ -1575,12 +1667,14 @@ fn layout_spec_help<'a>(
}
}
Boxed(inner_layout) => {
let inner_type = layout_spec_help(env, builder, interner, inner_layout)?;
Ptr(inner_layout) => {
let inner_type =
layout_spec_help(env, builder, interner, interner.get_repr(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(union_layout) => match interner.get_repr(union_layout) {
LayoutRepr::Union(union_layout) => {
@ -1592,6 +1686,9 @@ fn layout_spec_help<'a>(
}
_ => internal_error!("somehow, a non-recursive layout is under a recursive pointer"),
},
FunctionPointer(_) => function_pointer_type(builder),
Erased(_) => erasure_type(builder),
}
}
@ -1608,7 +1705,8 @@ 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)?;
let element_type =
layout_spec_help(env, builder, interner, interner.get_repr(*element_layout))?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
@ -1634,10 +1732,6 @@ fn static_list_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
const LIST_CELL_INDEX: u32 = 0;
const LIST_BAG_INDEX: u32 = 1;
#[allow(dead_code)]
const BOX_CELL_INDEX: u32 = LIST_CELL_INDEX;
const BOX_VALUE_INDEX: u32 = LIST_BAG_INDEX;
const TAG_CELL_INDEX: u32 = 0;
const TAG_DATA_INDEX: u32 = 1;
@ -1671,3 +1765,78 @@ fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// we model all our numbers as unit values
builder.add_make_tuple(block, &[])
}
fn function_pointer_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
builder.add_tuple_type(&[])
}
const ERASURE_CALEE_INDEX: u32 = 0;
const ERASURE_VALUE_INDEX: u32 = 1;
/// Erasure type modeled as
///
/// ```text
/// Tuple(callee: FnPtr, value: HeapCell)
/// ```
fn erasure_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
let value_cell_id = builder.add_heap_cell_type();
let callee_id = function_pointer_type(builder)?;
builder.add_tuple_type(&[value_cell_id, callee_id])
}
fn erasure_box_value_type<TC: TypeContext>(builder: &mut TC) -> TypeId {
builder.add_heap_cell_type()
}
fn box_erasure_value_unknown(
builder: &mut FuncDefBuilder,
block: BlockId,
value: ValueId,
) -> Result<ValueId> {
let heap_cell = erasure_box_value_type(builder);
builder.add_unknown_with(block, &[value], heap_cell)
}
fn box_erasure_value_unknown_nullptr(
builder: &mut FuncDefBuilder,
block: BlockId,
) -> Result<ValueId> {
let heap_cell = erasure_box_value_type(builder);
builder.add_unknown_with(block, &[], heap_cell)
}
/// Erasure value modeled as
///
/// ```text
/// callee = make_tuple(&[])
/// value = unknown(make_tuple(...captures))
///
/// x : Tuple(callee: FnPtr, value: HeapCell)
/// x = make_tuple(callee, value)
/// ```
fn erasure_make(
builder: &mut FuncDefBuilder,
block: BlockId,
value: ValueId,
callee: ValueId,
) -> Result<ValueId> {
builder.add_make_tuple(block, &[value, callee])
}
fn erasure_load(
builder: &mut FuncDefBuilder,
block: BlockId,
value: ValueId,
field: ErasedField,
loaded_type: TypeId,
) -> Result<ValueId> {
match field {
ErasedField::Callee => builder.add_get_tuple_field(block, value, ERASURE_CALEE_INDEX),
ErasedField::Value | ErasedField::ValuePtr => {
let unknown_heap_cell_value =
builder.add_get_tuple_field(block, value, ERASURE_VALUE_INDEX)?;
// Cast the unknown cell to the wanted type
builder.add_unknown_with(block, &[unknown_heap_cell_value], loaded_type)
}
}
}

View file

@ -126,7 +126,7 @@ fn find_wasi_libc_path() -> PathBuf {
internal_error!("cannot find `wasi-libc.a`")
}
#[cfg(all(unix, not(target_os = "macos")))]
#[cfg(unix)]
#[allow(clippy::too_many_arguments)]
pub fn build_zig_host_native(
env_path: &str,
@ -161,7 +161,7 @@ pub fn build_zig_host_native(
zig_cmd.args([
zig_host_src,
&format!("-femit-bin={}", emit_bin),
&format!("-femit-bin={emit_bin}"),
"--pkg-begin",
"glue",
find_zig_glue_path().to_str().unwrap(),
@ -257,99 +257,6 @@ pub fn build_zig_host_native(
zig_cmd
}
#[cfg(target_os = "macos")]
#[allow(clippy::too_many_arguments)]
pub fn build_zig_host_native(
env_path: &str,
env_home: &str,
emit_bin: &str,
zig_host_src: &str,
_target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
builtins_host_path: &Path,
// For compatibility with the non-macOS def above. Keep these in sync.
) -> Command {
use serde_json::Value;
// Run `zig env` to find the location of zig's std/ directory
let zig_env_output = zig().args(["env"]).output().unwrap();
let zig_env_json = if zig_env_output.status.success() {
std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| {
internal_error!(
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
utf8_err
);
})
} else {
match std::str::from_utf8(&zig_env_output.stderr) {
Ok(stderr) => internal_error!("`zig env` failed - stderr output was: {:?}", stderr),
Err(utf8_err) => internal_error!(
"`zig env` failed; its stderr output was invalid utf8 ({:?})",
utf8_err
),
}
};
let mut zig_compiler_rt_path = match serde_json::from_str(zig_env_json) {
Ok(Value::Object(map)) => match map.get("std_dir") {
Some(Value::String(std_dir)) => PathBuf::from(Path::new(std_dir)),
_ => {
internal_error!("Expected JSON containing a `std_dir` String field from `zig env`, but got: {:?}", zig_env_json);
}
},
_ => {
internal_error!(
"Expected JSON containing a `std_dir` field from `zig env`, but got: {:?}",
zig_env_json
);
}
};
zig_compiler_rt_path.push("special");
zig_compiler_rt_path.push("compiler_rt.zig");
let mut zig_cmd = zig();
zig_cmd
.env_clear()
.env("PATH", env_path)
.env("HOME", env_home);
if let Some(shared_lib_path) = shared_lib_path {
zig_cmd.args([
"build-exe",
"-fPIE",
shared_lib_path.to_str().unwrap(),
builtins_host_path.to_str().unwrap(),
]);
} else {
zig_cmd.args(["build-obj"]);
}
zig_cmd.args([
zig_host_src,
&format!("-femit-bin={}", emit_bin),
"--pkg-begin",
"glue",
find_zig_glue_path().to_str().unwrap(),
"--pkg-end",
// include the zig runtime
"--pkg-begin",
"compiler_rt",
zig_compiler_rt_path.to_str().unwrap(),
"--pkg-end",
// include libc
"--library",
"c",
]);
if matches!(opt_level, OptLevel::Optimize) {
zig_cmd.args(["-O", "ReleaseSafe"]);
} else if matches!(opt_level, OptLevel::Size) {
zig_cmd.args(["-O", "ReleaseSmall"]);
}
zig_cmd
}
pub fn build_zig_host_wasm32(
env_path: &str,
env_home: &str,
@ -492,7 +399,7 @@ pub fn build_swift_host_native(
match arch {
Architecture::Aarch64(_) => command.arg("-arm64"),
_ => command.arg(format!("-{}", arch)),
_ => command.arg(format!("-{arch}")),
};
command
@ -623,7 +530,7 @@ pub fn rebuild_host(
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
let mut cmd = rustup();
cmd.args(["run", "nightly-2022-10-30", "cargo"]);
cmd.args(["run", "nightly-2023-04-15", "cargo"]);
cmd
} else {
@ -1021,7 +928,7 @@ fn link_linux(
.map(|segments| segments.join("/"))
.collect::<Vec<String>>()
.join("\n");
eprintln!("We looked in the following directories:\n{}", dirs);
eprintln!("We looked in the following directories:\n{dirs}");
process::exit(1);
}
};
@ -1171,6 +1078,12 @@ fn link_macos(
// "--gc-sections",
"-arch",
&arch,
// Suppress warnings, because otherwise it prints:
//
// ld: warning: -undefined dynamic_lookup may not work with chained fixups
//
// We can't disable that option without breaking either x64 mac or ARM mac
"-w",
"-macos_version_min",
&get_macos_version(),
])
@ -1178,8 +1091,8 @@ fn link_macos(
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
if Path::new(sdk_path).exists() {
ld_command.arg(format!("-L{}", sdk_path));
ld_command.arg(format!("-L{}/swift", sdk_path));
ld_command.arg(format!("-L{sdk_path}"));
ld_command.arg(format!("-L{sdk_path}/swift"));
};
let roc_link_flags = match env::var("ROC_LINK_FLAGS") {
@ -1381,9 +1294,7 @@ pub fn llvm_module_to_dylib(
assert!(
exit_status.success(),
"\n___________\nLinking command failed with status {:?}:\n\n {:?}\n___________\n",
exit_status,
child
"\n___________\nLinking command failed with status {exit_status:?}:\n\n {child:?}\n___________\n"
);
// Load the dylib

View file

@ -8,8 +8,8 @@ use roc_gen_dev::AssemblyBackendMode;
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::{
EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, MonomorphizedModule, Threading,
EntryPoint, ExecutionMode, ExpectMetadata, FunctionKind, LoadConfig, LoadMonomorphizedError,
LoadedModule, LoadingProblem, MonomorphizedModule, Threading,
};
use roc_mono::ir::{OptLevel, SingleEntryPoint};
use roc_packaging::cache::RocCacheDir;
@ -133,7 +133,7 @@ pub fn gen_from_mono_module<'a>(
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
fn gen_from_mono_module_llvm<'a>(
arena: &'a bumpalo::Bump,
mut loaded: MonomorphizedModule<'a>,
loaded: MonomorphizedModule<'a>,
roc_file_path: &Path,
target: &target_lexicon::Triple,
opt_level: OptLevel,
@ -166,7 +166,7 @@ fn gen_from_mono_module_llvm<'a>(
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let enum_attr = context.create_enum_attribute(kind_id, 1);
let enum_attr = context.create_enum_attribute(kind_id, 0);
for function in module.get_functions() {
let name = function.get_name().to_str().unwrap();
@ -232,9 +232,10 @@ fn gen_from_mono_module_llvm<'a>(
roc_gen_llvm::llvm::build::build_procedures(
&env,
&mut loaded.layout_interner,
&loaded.layout_interner,
opt_level,
loaded.procedures,
loaded.host_exposed_lambda_sets,
entry_point,
Some(&app_ll_file),
&loaded.glue_layouts,
@ -320,10 +321,10 @@ fn gen_from_mono_module_llvm<'a>(
if !unrecognized.is_empty() {
let out = unrecognized
.iter()
.map(|x| format!("{:?}", x))
.map(|x| format!("{x:?}"))
.collect::<Vec<String>>()
.join(", ");
eprintln!("Unrecognized sanitizer: {}\nSupported options are \"address\", \"memory\", \"thread\", \"cargo-fuzz\", and \"afl.rs\".", out);
eprintln!("Unrecognized sanitizer: {out}\nSupported options are \"address\", \"memory\", \"thread\", \"cargo-fuzz\", and \"afl.rs\".");
eprintln!("Note: \"cargo-fuzz\" and \"afl.rs\" both enable sanitizer coverage for fuzzing. They just use different parameters to match the respective libraries.")
}
@ -340,7 +341,7 @@ fn gen_from_mono_module_llvm<'a>(
}
let opt = opt.output().unwrap();
assert!(opt.stderr.is_empty(), "{:#?}", opt);
assert!(opt.stderr.is_empty(), "{opt:#?}");
// write the .o file. Note that this builds the .o for the local machine,
// and ignores the `target_machine` entirely.
@ -358,7 +359,7 @@ fn gen_from_mono_module_llvm<'a>(
.output()
.unwrap();
assert!(bc_to_object.status.success(), "{:#?}", bc_to_object);
assert!(bc_to_object.status.success(), "{bc_to_object:#?}");
MemoryBuffer::create_from_file(&app_o_file).expect("memory buffer creation works")
} else if emit_debug_info {
@ -414,7 +415,7 @@ fn gen_from_mono_module_llvm<'a>(
.output()
.unwrap();
assert!(ll_to_object.stderr.is_empty(), "{:#?}", ll_to_object);
assert!(ll_to_object.stderr.is_empty(), "{ll_to_object:#?}");
}
_ => unreachable!(),
}
@ -716,13 +717,13 @@ pub fn handle_error_module(
pub fn handle_loading_problem(problem: LoadingProblem) -> std::io::Result<i32> {
match problem {
LoadingProblem::FormattedReport(report) => {
print!("{}", 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);
print!("Failed with error: {problem:?}");
Ok(1)
}
}
@ -740,8 +741,20 @@ pub fn standard_load_config(
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
};
// UNSTABLE(lambda-erasure)
let function_kind = if cfg!(debug_assertions) {
if std::env::var("EXPERIMENTAL_ROC_ERASE").is_ok() {
FunctionKind::Erased
} else {
FunctionKind::LambdaSet
}
} else {
FunctionKind::LambdaSet
};
LoadConfig {
target_info,
function_kind,
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
threading,
@ -889,7 +902,7 @@ fn build_loaded_file<'a>(
buf.push('\n');
use std::fmt::Write;
write!(buf, "{}", module_timing).unwrap();
write!(buf, "{module_timing}").unwrap();
if it.peek().is_some() {
buf.push('\n');
@ -914,10 +927,7 @@ fn build_loaded_file<'a>(
.expect("Failed to (re)build platform.");
if emit_timings && !is_platform_prebuilt {
println!(
"Finished rebuilding the platform in {} ms\n",
rebuild_duration
);
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
}
Some(HostRebuildTiming::BeforeApp(rebuild_duration))
@ -957,8 +967,7 @@ fn build_loaded_file<'a>(
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{buf}"
);
println!(
@ -972,10 +981,7 @@ fn build_loaded_file<'a>(
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
);
println!("Finished rebuilding the platform in {rebuild_duration} ms\n");
}
}
@ -1007,7 +1013,7 @@ fn build_loaded_file<'a>(
};
let app_o_file = tempfile::Builder::new()
.prefix("roc_app")
.suffix(&format!(".{}", extension))
.suffix(&format!(".{extension}"))
.tempfile()
.map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
let app_o_file = app_o_file.path();
@ -1212,6 +1218,8 @@ pub fn check_file<'a>(
let load_config = LoadConfig {
target_info,
// TODO: we may not want this for just checking.
function_kind: FunctionKind::LambdaSet,
// TODO: expose this from CLI?
render: RenderTarget::ColorTerminal,
palette: DEFAULT_PALETTE,
@ -1257,8 +1265,7 @@ pub fn check_file<'a>(
if emit_timings {
println!(
"\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
buf
"\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(),);

View file

@ -87,12 +87,12 @@ pub fn target_zig_str(target: &Triple) -> &'static str {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Darwin,
..
} => "x86_64-apple-darwin",
} => "x86_64-macos-gnu",
Triple {
architecture: Architecture::Aarch64(_),
operating_system: OperatingSystem::Darwin,
..
} => "aarch64-apple-darwin",
} => "aarch64-macos-gnu",
_ => internal_error!("TODO gracefully handle unsupported target: {:?}", target),
}
}
@ -149,20 +149,24 @@ pub fn target_machine(
init_arch(target);
let code_model = match target.architecture {
// LLVM 12 will not compile our programs without a large code model.
// The reason is not totally clear to me, but my guess is a few special-cases in
// llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (instructions)
// llvm/lib/Target/AArch64/AArch64Subtarget.cpp (GoT tables)
// Revisit when upgrading to LLVM 13.
Architecture::Aarch64(..) => CodeModel::Large,
_ => CodeModel::Default,
};
// We used to have a problem that LLVM 12 would not compile our programs without a large code model.
// The reason was not totally clear to us, but one guess is a few special-cases in
// llvm/lib/Target/AArch64/AArch64ISelLowering.cpp (instructions)
// llvm/lib/Target/AArch64/AArch64Subtarget.cpp (GoT tables)
// Revisit when upgrading to LLVM 13.
//
// Most recently, we seem to only see this problem on macOS ARM64; removing this
// failed macOS CI here: https://github.com/roc-lang/roc/pull/5644
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
let code_model = CodeModel::Large;
#[cfg(not(all(target_arch = "aarch64", target_os = "macos")))]
let code_model = CodeModel::Default;
Target::from_name(arch).unwrap().create_target_machine(
&TargetTriple::create(target_triple_str(target)),
"generic",
"", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features.
"",
opt,
reloc,
code_model,

View file

@ -35,6 +35,7 @@ fn main() {
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-aarch64", "builtins-aarch64");
generate_bc_file(
&bitcode_path,
"ir-windows-x86_64",
@ -61,12 +62,12 @@ fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
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);
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);
println!("Compiling 64-bit bitcode to: {dest_bc_64bit}");
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
@ -104,7 +105,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
Err(_) => format!("Failed to run \"{command_str}\""),
};
// Flaky test errors that only occur sometimes on MacOS ci server.

View file

@ -66,7 +66,7 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
let src_obj_path = bitcode_path.join(object_file_name);
let src_obj = src_obj_path.to_str().expect("Invalid src object path");
println!("Compiling zig object `{}` to: {}", zig_object, src_obj);
println!("Compiling zig object `{zig_object}` to: {src_obj}");
if !DEBUG {
let mut zig_cmd = zig();
@ -77,7 +77,7 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
run_command(zig_cmd, 0);
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
println!("Moving zig object `{zig_object}` to: {dest_obj}");
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
fs::copy(src_obj, dest_obj).unwrap_or_else(|err| {
@ -167,7 +167,7 @@ fn run_command(mut command: Command, flaky_fail_counter: usize) {
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command_str),
Err(_) => format!("Failed to run \"{command_str}\""),
};
// Flaky test errors that only occur sometimes on MacOS ci server.

View file

@ -25,18 +25,19 @@ pub fn build(b: *Builder) void {
const host_target = b.standardTargetOptions(.{
.default_target = CrossTarget{
.cpu_model = .baseline,
// TODO allow for native target for maximum speed
},
});
const linux32_target = makeLinux32Target();
const linux64_target = makeLinux64Target();
const linux_x64_target = makeLinuxX64Target();
const linux_aarch64_target = makeLinuxAarch64Target();
const windows64_target = makeWindows64Target();
const wasm32_target = makeWasm32Target();
// LLVM IR
generateLlvmIrFile(b, mode, host_target, main_path, "ir", "builtins-host");
generateLlvmIrFile(b, mode, linux32_target, main_path, "ir-i386", "builtins-i386");
generateLlvmIrFile(b, mode, linux64_target, main_path, "ir-x86_64", "builtins-x86_64");
generateLlvmIrFile(b, mode, linux_x64_target, main_path, "ir-x86_64", "builtins-x86_64");
generateLlvmIrFile(b, mode, linux_aarch64_target, main_path, "ir-aarch64", "builtins-aarch64");
generateLlvmIrFile(b, mode, windows64_target, main_path, "ir-windows-x86_64", "builtins-windows-x86_64");
generateLlvmIrFile(b, mode, wasm32_target, main_path, "ir-wasm32", "builtins-wasm32");
@ -89,6 +90,7 @@ fn generateObjectFile(
obj.strip = true;
obj.target = target;
obj.link_function_sections = true;
obj.force_pic = true;
const obj_step = b.step(step_name, "Build object file for linking");
obj_step.dependOn(&obj.step);
}
@ -103,7 +105,17 @@ fn makeLinux32Target() CrossTarget {
return target;
}
fn makeLinux64Target() CrossTarget {
fn makeLinuxAarch64Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.aarch64;
target.os_tag = std.Target.Os.Tag.linux;
target.abi = std.Target.Abi.musl;
return target;
}
fn makeLinuxX64Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.x86_64;

View file

@ -18,6 +18,7 @@ const v2u64 = @Vector(2, u64);
// Export it as weak incase it is already linked in by something else.
comptime {
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
@export(__lshrti3, .{ .name = "__lshrti3", .linkage = .Weak });
if (want_windows_v2u64_abi) {
@export(__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = .Weak });
@export(__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = .Weak });
@ -440,3 +441,47 @@ pub inline fn floatFractionalBits(comptime T: type) comptime_int {
else => @compileError("unknown floating point type " ++ @typeName(T)),
};
}
pub fn __lshrti3(a: i128, b: i32) callconv(.C) i128 {
return lshrXi3(i128, a, b);
}
// Logical shift right: shift in 0 from left to right
// Precondition: 0 <= b < T.bit_count
inline fn lshrXi3(comptime T: type, a: T, b: i32) T {
const word_t = HalveInt(T, false);
const S = std.math.Log2Int(word_t.HalfT);
const input = word_t{ .all = a };
var output: word_t = undefined;
if (b >= word_t.bits) {
output.s.high = 0;
output.s.low = input.s.high >> @intCast(S, b - word_t.bits);
} else if (b == 0) {
return a;
} else {
output.s.high = input.s.high >> @intCast(S, b);
output.s.low = input.s.high << @intCast(S, word_t.bits - b);
output.s.low |= input.s.low >> @intCast(S, b);
}
return output.all;
}
/// Allows to access underlying bits as two equally sized lower and higher
/// signed or unsigned integers.
fn HalveInt(comptime T: type, comptime signed_half: bool) type {
return extern union {
pub const bits = @divExact(@typeInfo(T).Int.bits, 2);
pub const HalfTU = std.meta.Int(.unsigned, bits);
pub const HalfTS = std.meta.Int(.signed, bits);
pub const HalfT = if (signed_half) HalfTS else HalfTU;
all: T,
s: if (native_endian == .Little)
extern struct { low: HalfT, high: HalfT }
else
extern struct { high: HalfT, low: HalfT },
};
}

View file

@ -171,8 +171,8 @@ comptime {
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
exportStrFn(str.repeat, "repeat");
exportStrFn(str.strTrim, "trim");
exportStrFn(str.strTrimLeft, "trim_left");
exportStrFn(str.strTrimRight, "trim_right");
exportStrFn(str.strTrimStart, "trim_start");
exportStrFn(str.strTrimEnd, "trim_end");
exportStrFn(str.strCloneTo, "clone_to");
exportStrFn(str.withCapacity, "with_capacity");
exportStrFn(str.strGraphemes, "graphemes");
@ -195,8 +195,10 @@ comptime {
exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.increfRcPtrC, "incref_rc_ptr");
exportUtilsFn(utils.decrefRcPtrC, "decref_rc_ptr");
exportUtilsFn(utils.freeRcPtrC, "free_rc_ptr");
exportUtilsFn(utils.increfDataPtrC, "incref_data_ptr");
exportUtilsFn(utils.decrefDataPtrC, "decref_data_ptr");
exportUtilsFn(utils.freeDataPtrC, "free_data_ptr");
exportUtilsFn(utils.isUnique, "is_unique");
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");

View file

@ -2302,7 +2302,7 @@ pub fn strTrim(input_string: RocStr) callconv(.C) RocStr {
}
}
pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr {
pub fn strTrimStart(input_string: RocStr) callconv(.C) RocStr {
var string = input_string;
if (string.isEmpty()) {
@ -2350,7 +2350,7 @@ pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr {
}
}
pub fn strTrimRight(input_string: RocStr) callconv(.C) RocStr {
pub fn strTrimEnd(input_string: RocStr) callconv(.C) RocStr {
var string = input_string;
if (string.isEmpty()) {
@ -2583,22 +2583,22 @@ test "strTrim: small to small" {
try expect(trimmed.isSmallStr());
}
test "strTrimLeft: empty" {
const trimmedEmpty = strTrimLeft(RocStr.empty());
test "strTrimStart: empty" {
const trimmedEmpty = strTrimStart(RocStr.empty());
try expect(trimmedEmpty.eq(RocStr.empty()));
}
test "strTrimLeft: blank" {
test "strTrimStart: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.decref();
const trimmed = strTrimLeft(original);
const trimmed = strTrimStart(original);
try expect(trimmed.eq(RocStr.empty()));
}
test "strTrimLeft: large to large" {
test "strTrimStart: large to large" {
const original_bytes = " hello even more giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.decref();
@ -2611,12 +2611,12 @@ test "strTrimLeft: large to large" {
try expect(!expected.isSmallStr());
const trimmed = strTrimLeft(original);
const trimmed = strTrimStart(original);
try expect(trimmed.eq(expected));
}
test "strTrimLeft: large to small" {
test "strTrimStart: large to small" {
// `original` will be consumed by the concat; do not free explicitly
const original_bytes = " hello ";
const original = RocStr.init(original_bytes, original_bytes.len);
@ -2629,14 +2629,14 @@ test "strTrimLeft: large to small" {
try expect(expected.isSmallStr());
const trimmed = strTrimLeft(original);
const trimmed = strTrimStart(original);
defer trimmed.decref();
try expect(trimmed.eq(expected));
try expect(!trimmed.isSmallStr());
}
test "strTrimLeft: small to small" {
test "strTrimStart: small to small" {
const original_bytes = " hello ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.decref();
@ -2649,28 +2649,28 @@ test "strTrimLeft: small to small" {
try expect(expected.isSmallStr());
const trimmed = strTrimLeft(original);
const trimmed = strTrimStart(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "strTrimRight: empty" {
const trimmedEmpty = strTrimRight(RocStr.empty());
test "strTrimEnd: empty" {
const trimmedEmpty = strTrimEnd(RocStr.empty());
try expect(trimmedEmpty.eq(RocStr.empty()));
}
test "strTrimRight: blank" {
test "strTrimEnd: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.decref();
const trimmed = strTrimRight(original);
const trimmed = strTrimEnd(original);
try expect(trimmed.eq(RocStr.empty()));
}
test "strTrimRight: large to large" {
test "strTrimEnd: large to large" {
const original_bytes = " hello even more giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.decref();
@ -2683,12 +2683,12 @@ test "strTrimRight: large to large" {
try expect(!expected.isSmallStr());
const trimmed = strTrimRight(original);
const trimmed = strTrimEnd(original);
try expect(trimmed.eq(expected));
}
test "strTrimRight: large to small" {
test "strTrimEnd: large to small" {
// `original` will be consumed by the concat; do not free explicitly
const original_bytes = " hello ";
const original = RocStr.init(original_bytes, original_bytes.len);
@ -2701,14 +2701,14 @@ test "strTrimRight: large to small" {
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
const trimmed = strTrimEnd(original);
defer trimmed.decref();
try expect(trimmed.eq(expected));
try expect(!trimmed.isSmallStr());
}
test "strTrimRight: small to small" {
test "strTrimEnd: small to small" {
const original_bytes = " hello ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.decref();
@ -2721,7 +2721,7 @@ test "strTrimRight: small to small" {
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
const trimmed = strTrimEnd(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());

View file

@ -220,6 +220,29 @@ pub fn increfDataPtrC(
return increfRcPtrC(isizes, inc_amount);
}
pub fn freeDataPtrC(
bytes_or_null: ?[*]isize,
alignment: u32,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
const ptr = @ptrToInt(bytes);
const tag_mask: usize = if (@sizeOf(usize) == 8) 0b111 else 0b11;
const masked_ptr = ptr & ~tag_mask;
const isizes: [*]isize = @intToPtr([*]isize, masked_ptr);
return freeRcPtrC(isizes - 1, alignment);
}
pub fn freeRcPtrC(
bytes_or_null: ?[*]isize,
alignment: u32,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
return free_ptr_to_refcount(bytes, alignment);
}
pub fn decref(
bytes_or_null: ?[*]u8,
data_bytes: usize,
@ -236,13 +259,23 @@ pub fn decref(
decref_ptr_to_refcount(isizes - 1, alignment);
}
inline fn decref_ptr_to_refcount(
inline fn free_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,
) void {
if (RC_TYPE == Refcount.none) return;
const extra_bytes = std.math.max(alignment, @sizeOf(usize));
// NOTE: we don't even check whether the refcount is "infinity" here!
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
}
inline fn decref_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,
) void {
if (RC_TYPE == Refcount.none) return;
if (DEBUG_INCDEC and builtin.target.cpu.arch != .wasm32) {
std.debug.print("| decrement {*}: ", .{refcount_ptr});
}
@ -264,13 +297,13 @@ inline fn decref_ptr_to_refcount(
}
if (refcount == REFCOUNT_ONE_ISIZE) {
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
free_ptr_to_refcount(refcount_ptr, alignment);
}
},
Refcount.atomic => {
var last = @atomicRmw(isize, &refcount_ptr[0], std.builtin.AtomicRmwOp.Sub, 1, Monotonic);
if (last == REFCOUNT_ONE_ISIZE) {
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
free_ptr_to_refcount(refcount_ptr, alignment);
}
},
Refcount.none => unreachable,

View file

@ -1,3 +1,7 @@
## Most users won't need Box, it is used for:
## - Holding unknown Roc types when developing [platforms](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform).
## - To improve performance in rare cases.
##
interface Box
exposes [box, unbox]
imports []

View file

@ -7,6 +7,7 @@ interface Dict
clear,
capacity,
len,
isEmpty,
get,
contains,
insert,
@ -21,6 +22,8 @@ interface Dict
insertAll,
keepShared,
removeAll,
map,
joinMap,
]
imports [
Bool.{ Bool, Eq },
@ -98,14 +101,14 @@ Dict k v := {
data : List (k, v),
size : Nat,
} where k implements Hash & Eq
implements [
Eq {
isEq,
},
Hash {
hash: hashDict,
},
]
implements [
Eq {
isEq,
},
Hash {
hash: hashDict,
},
]
isEq : Dict k v, Dict k v -> Bool where k implements Hash & Eq, v implements Eq
isEq = \xs, ys ->
@ -139,12 +142,12 @@ empty = \{} ->
## Returns the max number of elements the dictionary can hold before requiring a rehash.
## ```
## foodDict =
## Dict.empty {}
## |> Dict.insert "apple" "fruit"
## Dict.empty {}
## |> Dict.insert "apple" "fruit"
##
## capacityOfDict = Dict.capacity foodDict
## ```
capacity : Dict k v -> Nat where k implements Hash & Eq
capacity : Dict * * -> Nat
capacity = \@Dict { dataIndices } ->
cap = List.len dataIndices
@ -192,10 +195,20 @@ fromList = \data ->
## |> Dict.len
## |> Bool.isEq 3
## ```
len : Dict k v -> Nat where k implements Hash & Eq
len : Dict * * -> Nat
len = \@Dict { size } ->
size
## Check if the dictinoary is empty.
## ```
## Dict.isEmpty (Dict.empty {} |> Dict.insert "key" 42)
##
## Dict.isEmpty (Dict.empty {})
## ```
isEmpty : Dict * * -> Bool
isEmpty = \@Dict { size } ->
size == 0
## Clears all elements from a dictionary keeping around the allocation if it isn't huge.
## ```
## songs =
@ -225,6 +238,30 @@ clear = \@Dict { metadata, dataIndices, data } ->
size: 0,
}
## Convert each value in the dictionary to something new, by calling a conversion
## function on each of them which receives both the key and the old value. Then return a
## new dictionary containing the same keys and the converted values.
map : Dict k a, (k, a -> b) -> Dict k b
where k implements Hash & Eq, b implements Hash & Eq
map = \dict, transform ->
init = withCapacity (capacity dict)
walk dict init \answer, k, v ->
insert answer k (transform k v)
## Like [Dict.map], except the transformation function wraps the return value
## in a dictionary. At the end, all the dictionaries get joined together
## (using [Dict.insertAll]) into one dictionary.
##
## You may know a similar function named `concatMap` in other languages.
joinMap : Dict a b, (a, b -> Dict x y) -> Dict x y
where a implements Hash & Eq, x implements Hash & Eq
joinMap = \dict, transform ->
init = withCapacity (capacity dict) # Might be a pessimization
walk dict init \answer, k, v ->
insertAll answer (transform k v)
## 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.
@ -976,16 +1013,16 @@ expect
# TODO: Add a builtin to distinguish big endian systems and change loading orders.
# TODO: Switch out Wymum on systems with slow 128bit multiplication.
LowLevelHasher := { originalSeed : U64, state : U64 } implements [
Hasher {
addBytes,
addU8,
addU16,
addU32,
addU64,
addU128,
complete,
},
]
Hasher {
addBytes,
addU8,
addU16,
addU32,
addU64,
addU128,
complete,
},
]
# unsafe primitive that does not perform a bounds check
# TODO hide behind an InternalList.roc module

View file

@ -208,6 +208,9 @@ interface List
## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all
## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations.
## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood!
# separator so List.isEmpty doesn't absorb the above into its doc comment
## Check if the list is empty.
## ```
## List.isEmpty [1, 2, 3]
@ -222,6 +225,13 @@ isEmpty = \list ->
# but will cause a reference count increment on the value it got out of the list
getUnsafe : List a, Nat -> a
## Returns an element from a list at the given index.
##
## Returns `Err OutOfBounds` if the given index exceeds the List's length
## ```
## expect List.get [100, 200, 300] 1 == Ok 200
## expect List.get [100, 200, 300] 5 == Err OutOfBounds
## ```
get : List a, Nat -> Result a [OutOfBounds]
get = \list, index ->
if index < List.len list then
@ -352,6 +362,10 @@ releaseExcessCapacity : List a -> List a
concat : List a, List a -> List a
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
## ```
## expect List.last [1, 2, 3] == Ok 3
## expect List.last [] == Err ListWasEmpty
## ```
last : List a -> Result a [ListWasEmpty]
last = \list ->
when List.get list (Num.subSaturated (List.len list) 1) is
@ -383,7 +397,7 @@ repeatHelp = \value, count, accum ->
## Returns the list with its elements reversed.
## ```
## List.reverse [1, 2, 3]
## expect List.reverse [1, 2, 3] == [3, 2, 1]
## ```
reverse : List a -> List a
reverse = \list ->
@ -397,9 +411,9 @@ reverseHelp = \list, left, right ->
## Join the given lists together into one list.
## ```
## List.join [[1, 2, 3], [4, 5], [], [6, 7]]
## List.join [[], []]
## List.join []
## expect List.join [[1], [2, 3], [], [4, 5]] == [1, 2, 3, 4, 5]
## expect List.join [[], []] == []
## expect List.join [] == []
## ```
join : List (List a) -> List a
join = \lists ->
@ -597,6 +611,10 @@ dropIf = \list, predicate ->
## Run the given function on each element of a list, and return the
## number of elements for which the function returned `Bool.true`.
## ```
## expect List.countIf [1, -2, -3] Num.isNegative == 2
## expect List.countIf [1, 2, 3] (\num -> num > 1 ) == 2
## ```
countIf : List a, (a -> Bool) -> Nat
countIf = \list, predicate ->
walkState = \state, elem ->
@ -610,11 +628,12 @@ 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
## expect List.keepOks ["1", "Two", "23", "Bird"] Str.toI32 == [1, 23]
##
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
## expect List.keepOks [["a", "b"], [], ["c", "d", "e"], [] ] List.first == ["a", "c"]
##
## List.keepOks ["", "a", "bc", "", "d", "ef", ""]
## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok str
## expect List.keepOks ["", "a", "bc", "", "d", "ef", ""] fn == ["a", "bc", "d", "ef"]
## ```
keepOks : List before, (before -> Result after *) -> List after
keepOks = \list, toResult ->
@ -646,9 +665,9 @@ 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)
## expect List.map [1, 2, 3] (\num -> num + 1) == [2, 3, 4]
##
## List.map ["", "a", "bc"] Str.isEmpty
## expect List.map ["", "a", "bc"] Str.isEmpty == [Bool.true, Bool.false, Bool.false]
## ```
map : List a, (a -> b) -> List b
@ -675,6 +694,9 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
## ```
## expect List.mapWithIndex [10, 20, 30] (\num, index -> num + index) == [10, 21, 32]
## ```
mapWithIndex : List a, (a, Nat -> b) -> List b
mapWithIndex = \src, func ->
length = len src
@ -1160,8 +1182,17 @@ mapTry = \list, toResult ->
Result.map (toResult elem) \ok ->
List.append state ok
## This is the same as `iterate` but with [Result] instead of `[Continue, Break]`.
## Using `Result` saves a conditional in `mapTry`.
## Same as [List.walk], except you can stop walking early by returning `Err`.
##
## ## Performance Details
##
## Compared to [List.walk], this can potentially visit fewer elements (which can
## improve performance) at the cost of making each step take longer.
## However, the added cost to each step is extremely small, and can easily
## be outweighed if it results in skipping even a small number of elements.
##
## As such, it is typically better for performance to use this over [List.walk]
## if returning `Break` earlier than the last element is expected to be common.
walkTry : List elem, state, (state, elem -> Result state err) -> Result state err
walkTry = \list, init, func ->
walkTryHelp list init func 0 (List.len list)

View file

@ -33,12 +33,17 @@ interface Num
Decimal,
Binary32,
Binary64,
e,
pi,
tau,
abs,
absDiff,
neg,
add,
sub,
mul,
min,
max,
isLt,
isLte,
isGt,
@ -81,6 +86,7 @@ interface Num
bitwiseAnd,
bitwiseXor,
bitwiseOr,
bitwiseNot,
shiftLeftBy,
shiftRightBy,
shiftRightZfBy,
@ -492,6 +498,18 @@ F32 : Num (FloatingPoint Binary32)
## [sqrt] and trigonometry are massively slower with [Dec] than with [F64].
Dec : Num (FloatingPoint Decimal)
## Euler's number (e)
e : Frac *
e = 2.71828182845904523536028747135266249775724709369995
## Archimedes' constant (π)
pi : Frac *
pi = 3.14159265358979323846264338327950288419716939937510
## Circle constant (τ)
tau : Frac *
tau = 2 * pi
# ------- Functions
## Convert a number to a [Str].
##
@ -777,6 +795,34 @@ sub : Num a, Num a -> Num a
## ∞ or -∞. For all other number types, overflow results in a panic.
mul : Num a, Num a -> Num a
## Obtains the smaller between two numbers of the same type.
##
## ```
## Num.min 100 0
##
## Num.min 3.0 -3.0
## ```
min : Num a, Num a -> Num a
min = \a, b ->
if a < b then
a
else
b
## Obtains the greater between two numbers of the same type.
##
## ```
## Num.max 100 0
##
## Num.max 3.0 -3.0
## ```
max : Num a, Num a -> Num a
max = \a, b ->
if a > b then
a
else
b
sin : Frac a -> Frac a
cos : Frac a -> Frac a
@ -930,10 +976,25 @@ remChecked = \a, b ->
isMultipleOf : Int a, Int a -> Bool
## Does a "bitwise and". Each bit of the output is 1 if the corresponding bit
## of x AND of y is 1, otherwise it's 0.
bitwiseAnd : Int a, Int a -> Int a
## Does a "bitwise exclusive or". Each bit of the output is the same as the
## corresponding bit in x if that bit in y is 0, and it's the complement of
## the bit in x if that bit in y is 1.
bitwiseXor : Int a, Int a -> Int a
## Does a "bitwise or". Each bit of the output is 0 if the corresponding bit
## of x OR of y is 0, otherwise it's 1.
bitwiseOr : Int a, Int a -> Int a
## Returns the complement of x - the number you get by switching each 1 for a
## 0 and each 0 for a 1. This is the same as -x - 1.
bitwiseNot : Int a -> Int a
bitwiseNot = \n ->
bitwiseXor n (subWrap 0 1)
## Bitwise left shift of a number by another
##
## The least significant bits always become 0. This means that shifting left is

View file

@ -42,8 +42,8 @@ withDefault = \result, default ->
## function on it. Then returns 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
## Result.map (Ok 12) Num.neg
## Result.map (Err "yipes!") Num.neg
## ```
##
## Functions like `map` are common in Roc; see for example [List.map],

View file

@ -7,6 +7,8 @@ interface Set
walkUntil,
insert,
len,
isEmpty,
capacity,
remove,
contains,
toList,
@ -14,6 +16,8 @@ interface Set
union,
intersection,
difference,
map,
joinMap,
]
imports [
List,
@ -26,14 +30,14 @@ interface Set
## 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 {} where k implements Hash & Eq
implements [
Eq {
isEq,
},
Hash {
hash: hashSet,
},
]
implements [
Eq {
isEq,
},
Hash {
hash: hashSet,
},
]
isEq : Set k, Set k -> Bool where k implements Hash & Eq
isEq = \xs, ys ->
@ -59,6 +63,13 @@ hashSet = \hasher, @Set inner -> Hash.hash hasher inner
empty : {} -> Set k where k implements Hash & Eq
empty = \{} -> @Set (Dict.empty {})
## Return a dictionary with space allocated for a number of entries. This
## may provide a performance optimization if you know how many entries will be
## inserted.
withCapacity : Nat -> Set k where k implements Hash & Eq
withCapacity = \cap ->
@Set (Dict.withCapacity cap)
## Creates a new `Set` with a single value.
## ```
## singleItemSet = Set.single "Apple"
@ -115,10 +126,32 @@ expect
##
## expect countValues == 3
## ```
len : Set k -> Nat where k implements Hash & Eq
len : Set * -> Nat
len = \@Set dict ->
Dict.len dict
## Returns the max number of elements the set can hold before requiring a rehash.
## ```
## foodSet =
## Set.empty {}
## |> Set.insert "apple"
##
## capacityOfSet = Set.capacity foodSet
## ```
capacity : Set * -> Nat
capacity = \@Set dict ->
Dict.capacity dict
## Check if the set is empty.
## ```
## Set.isEmpty (Set.empty {} |> Set.insert 42)
##
## Set.isEmpty (Set.empty {})
## ```
isEmpty : Set * -> Bool
isEmpty = \@Set dict ->
Dict.isEmpty dict
# Inserting a duplicate key has no effect on length.
expect
actual =
@ -261,6 +294,28 @@ walk : Set k, state, (state, k -> state) -> state where k implements Hash & Eq
walk = \@Set dict, state, step ->
Dict.walk dict state (\s, k, _ -> step s k)
## Convert each value in the set to something new, by calling a conversion
## function on each of them which receives the old value. Then return a
## new set containing the converted values.
map : Set a, (a -> b) -> Set b where a implements Hash & Eq, b implements Hash & Eq
map = \set, transform ->
init = withCapacity (capacity set)
walk set init \answer, k ->
insert answer (transform k)
## Like [Set.map], except the transformation function wraps the return value
## in a set. At the end, all the sets get joined together
## (using [Set.union]) into one set.
##
## You may know a similar function named `concatMap` in other languages.
joinMap : Set a, (a -> Set b) -> Set b where a implements Hash & Eq, b implements Hash & Eq
joinMap = \set, transform ->
init = withCapacity (capacity set) # Might be a pessimization
walk set init \answer, k ->
union answer (transform k)
## Iterate through the values of a given `Set` and build a value, can stop
## iterating part way through the collection.
## ```

View file

@ -103,8 +103,8 @@ interface Str
startsWith,
endsWith,
trim,
trimLeft,
trimRight,
trimStart,
trimEnd,
toDec,
toF64,
toF32,
@ -448,15 +448,15 @@ trim : Str -> Str
## Return the [Str] with all whitespace removed from the beginning.
## ```
## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
## expect Str.trimStart " Hello \n\n" == "Hello \n\n"
## ```
trimLeft : Str -> Str
trimStart : Str -> Str
## Return the [Str] with all whitespace removed from the end.
## ```
## expect Str.trimRight " Hello \n\n" == " Hello"
## expect Str.trimEnd " Hello \n\n" == " Hello"
## ```
trimRight : Str -> Str
trimEnd : 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).
@ -638,12 +638,13 @@ countUtf8Bytes : Str -> Nat
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.
## If the substring is not found, returns the original string.
##
## ```
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
## expect Str.replaceEach "foo/bar/baz" "/" "_" == "foo_bar_baz"
## expect Str.replaceEach "not here" "/" "_" == "not here"
## ```
replaceEach : Str, Str, Str -> Result Str [NotFound]
replaceEach : Str, Str, Str -> Str
replaceEach = \haystack, needle, flower ->
when splitFirst haystack needle is
Ok { before, after } ->
@ -653,9 +654,8 @@ replaceEach = \haystack, needle, flower ->
|> Str.concat before
|> Str.concat flower
|> replaceEachHelp after needle flower
|> Ok
Err err -> Err err
Err NotFound -> haystack
replaceEachHelp : Str, Str, Str, Str -> Str
replaceEachHelp = \buf, haystack, needle, flower ->
@ -668,39 +668,44 @@ replaceEachHelp = \buf, haystack, needle, flower ->
Err NotFound -> Str.concat buf haystack
expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
expect Str.replaceEach "abXdeXghi" "X" "_" == "ab_de_ghi"
expect Str.replaceEach "abcdefg" "nothing" "_" == "abcdefg"
## Returns the given [Str] with the first occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
## If the substring is not found, returns the original string.
##
## ```
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == "foo_bar/baz"
## expect Str.replaceFirst "no slashes here" "/" "_" == "no slashes here"
## ```
replaceFirst : Str, Str, Str -> Result Str [NotFound]
replaceFirst : Str, Str, Str -> Str
replaceFirst = \haystack, needle, flower ->
when splitFirst haystack needle is
Ok { before, after } ->
Ok "\(before)\(flower)\(after)"
"\(before)\(flower)\(after)"
Err err -> Err err
Err NotFound -> haystack
expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
expect Str.replaceFirst "abXdeXghi" "X" "_" == "ab_deXghi"
expect Str.replaceFirst "abcdefg" "nothing" "_" == "abcdefg"
## Returns the given [Str] with the last occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
## If the substring is not found, returns the original string.
##
## ```
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
## expect Str.replaceLast "foo/bar/baz" "/" "_" == "foo/bar_baz"
## expect Str.replaceLast "no slashes here" "/" "_" == "no slashes here"
## ```
replaceLast : Str, Str, Str -> Result Str [NotFound]
replaceLast : Str, Str, Str -> Str
replaceLast = \haystack, needle, flower ->
when splitLast haystack needle is
Ok { before, after } ->
Ok "\(before)\(flower)\(after)"
"\(before)\(flower)\(after)"
Err err -> Err err
Err NotFound -> haystack
expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
expect Str.replaceLast "abXdeXghi" "X" "_" == "abXde_ghi"
expect Str.replaceLast "abcdefg" "nothing" "_" == "abcdefg"
## 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.

View file

@ -44,49 +44,49 @@ interface TotallyNotJson
## An opaque type with the `EncoderFormatting` and
## `DecoderFormatting` abilities.
Json := { fieldNameMapping : FieldNameMapping }
implements [
EncoderFormatting {
u8: encodeU8,
u16: encodeU16,
u32: encodeU32,
u64: encodeU64,
u128: encodeU128,
i8: encodeI8,
i16: encodeI16,
i32: encodeI32,
i64: encodeI64,
i128: encodeI128,
f32: encodeF32,
f64: encodeF64,
dec: encodeDec,
bool: encodeBool,
string: encodeString,
list: encodeList,
record: encodeRecord,
tuple: encodeTuple,
tag: encodeTag,
},
DecoderFormatting {
u8: decodeU8,
u16: decodeU16,
u32: decodeU32,
u64: decodeU64,
u128: decodeU128,
i8: decodeI8,
i16: decodeI16,
i32: decodeI32,
i64: decodeI64,
i128: decodeI128,
f32: decodeF32,
f64: decodeF64,
dec: decodeDec,
bool: decodeBool,
string: decodeString,
list: decodeList,
record: decodeRecord,
tuple: decodeTuple,
},
]
implements [
EncoderFormatting {
u8: encodeU8,
u16: encodeU16,
u32: encodeU32,
u64: encodeU64,
u128: encodeU128,
i8: encodeI8,
i16: encodeI16,
i32: encodeI32,
i64: encodeI64,
i128: encodeI128,
f32: encodeF32,
f64: encodeF64,
dec: encodeDec,
bool: encodeBool,
string: encodeString,
list: encodeList,
record: encodeRecord,
tuple: encodeTuple,
tag: encodeTag,
},
DecoderFormatting {
u8: decodeU8,
u16: decodeU16,
u32: decodeU32,
u64: decodeU64,
u128: decodeU128,
i8: decodeI8,
i16: decodeI16,
i32: decodeI32,
i64: decodeI64,
i128: decodeI128,
f32: decodeF32,
f64: decodeF64,
dec: decodeDec,
bool: decodeBool,
string: decodeString,
list: decodeList,
record: decodeRecord,
tuple: decodeTuple,
},
]
## Returns a JSON `Encoder` and `Decoder`
json = @Json { fieldNameMapping: Default }

View file

@ -339,8 +339,8 @@ pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim";
pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left";
pub const STR_TRIM_RIGHT: &str = "roc_builtins.str.trim_right";
pub const STR_TRIM_START: &str = "roc_builtins.str.trim_start";
pub const STR_TRIM_END: &str = "roc_builtins.str.trim_end";
pub const STR_GET_UNSAFE: &str = "roc_builtins.str.get_unsafe";
pub const STR_RESERVE: &str = "roc_builtins.str.reserve";
pub const STR_APPEND_SCALAR: &str = "roc_builtins.str.append_scalar";
@ -393,8 +393,10 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
pub const UTILS_ALLOCATE_WITH_REFCOUNT: &str = "roc_builtins.utils.allocate_with_refcount";
pub const UTILS_INCREF_RC_PTR: &str = "roc_builtins.utils.incref_rc_ptr";
pub const UTILS_DECREF_RC_PTR: &str = "roc_builtins.utils.decref_rc_ptr";
pub const UTILS_FREE_RC_PTR: &str = "roc_builtins.utils.free_rc_ptr";
pub const UTILS_INCREF_DATA_PTR: &str = "roc_builtins.utils.incref_data_ptr";
pub const UTILS_DECREF_DATA_PTR: &str = "roc_builtins.utils.decref_data_ptr";
pub const UTILS_FREE_DATA_PTR: &str = "roc_builtins.utils.free_data_ptr";
pub const UTILS_IS_UNIQUE: &str = "roc_builtins.utils.is_unique";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
pub const UTILS_DICT_PSEUDO_SEED: &str = "roc_builtins.utils.dict_pseudo_seed";

View file

@ -469,8 +469,7 @@ impl IAbilitiesStore<Resolved> {
debug_assert!(
old_specialization.is_none(),
"Existing resolution: {:?}",
old_specialization
"Existing resolution: {old_specialization:?}"
);
}

View file

@ -847,29 +847,17 @@ fn can_annotation_help(
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
if vars.is_empty() && env.home == symbol.module_id() {
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
Type::HostExposedAlias {
name: symbol,
type_arguments: vars,
lambda_set_variables: alias.lambda_set_variables.clone(),
actual: Box::new(alias.typ.clone()),
actual_var,
}
} else {
Type::Alias {
symbol,
type_arguments: vars.into_iter().map(OptAbleType::unbound).collect(),
lambda_set_variables: alias.lambda_set_variables.clone(),
infer_ext_in_output_types: alias
.infer_ext_in_output_variables
.iter()
.map(|v| Type::Variable(*v))
.collect(),
actual: Box::new(alias.typ.clone()),
kind: alias.kind,
}
Type::Alias {
symbol,
type_arguments: vars.into_iter().map(OptAbleType::unbound).collect(),
lambda_set_variables: alias.lambda_set_variables.clone(),
infer_ext_in_output_types: alias
.infer_ext_in_output_variables
.iter()
.map(|v| Type::Variable(*v))
.collect(),
actual: Box::new(alias.typ.clone()),
kind: alias.kind,
}
}

View file

@ -85,13 +85,19 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
// these are used internally and not tied to a symbol
LowLevel::Hash => unimplemented!(),
LowLevel::PtrCast => unimplemented!(),
LowLevel::PtrWrite => unimplemented!(),
LowLevel::PtrStore => unimplemented!(),
LowLevel::PtrLoad => unimplemented!(),
LowLevel::PtrClearTagId => unimplemented!(),
LowLevel::RefCountIncRcPtr => unimplemented!(),
LowLevel::RefCountDecRcPtr=> unimplemented!(),
LowLevel::RefCountIncDataPtr => unimplemented!(),
LowLevel::RefCountDecDataPtr=> unimplemented!(),
LowLevel::RefCountIsUnique => unimplemented!(),
LowLevel::SetJmp => unimplemented!(),
LowLevel::LongJmp => unimplemented!(),
LowLevel::SetLongJmpBuffer => unimplemented!(),
// these are not implemented, not sure why
LowLevel::StrFromInt => unimplemented!(),
LowLevel::StrFromFloat => unimplemented!(),
@ -118,8 +124,8 @@ map_symbol_to_lowlevel_and_arity! {
StrToUtf8; STR_TO_UTF8; 1,
StrRepeat; STR_REPEAT; 2,
StrTrim; STR_TRIM; 1,
StrTrimLeft; STR_TRIM_LEFT; 1,
StrTrimRight; STR_TRIM_RIGHT; 1,
StrTrimStart; STR_TRIM_START; 1,
StrTrimEnd; STR_TRIM_END; 1,
StrToScalars; STR_TO_SCALARS; 1,
StrGetUnsafe; STR_GET_UNSAFE; 2,
StrSubstringUnsafe; STR_SUBSTRING_UNSAFE; 3,

View file

@ -176,7 +176,7 @@ impl Constraints {
let mut buf = String::new();
writeln!(buf, "Constraints statistics for module {:?}:", module_id)?;
writeln!(buf, "Constraints statistics for module {module_id:?}:")?;
writeln!(buf, " constraints length: {}:", self.constraints.len())?;
writeln!(
@ -833,16 +833,16 @@ impl std::fmt::Debug for Constraint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Eq(Eq(arg0, arg1, arg2, arg3)) => {
write!(f, "Eq({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
write!(f, "Eq({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
}
Self::Store(arg0, arg1, arg2, arg3) => {
write!(f, "Store({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
write!(f, "Store({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
}
Self::Lookup(arg0, arg1, arg2) => {
write!(f, "Lookup({:?}, {:?}, {:?})", arg0, arg1, arg2)
write!(f, "Lookup({arg0:?}, {arg1:?}, {arg2:?})")
}
Self::Pattern(arg0, arg1, arg2, arg3) => {
write!(f, "Pattern({:?}, {:?}, {:?}, {:?})", arg0, arg1, arg2, arg3)
write!(f, "Pattern({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
}
Self::True => write!(f, "True"),
Self::SaveTheEnvironment => write!(f, "SaveTheEnvironment"),
@ -851,27 +851,19 @@ impl std::fmt::Debug for Constraint {
Self::IsOpenType(arg0) => f.debug_tuple("IsOpenType").field(arg0).finish(),
Self::IncludesTag(arg0) => f.debug_tuple("IncludesTag").field(arg0).finish(),
Self::PatternPresence(arg0, arg1, arg2, arg3) => {
write!(
f,
"PatternPresence({:?}, {:?}, {:?}, {:?})",
arg0, arg1, arg2, arg3
)
write!(f, "PatternPresence({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
}
Self::Exhaustive(arg0, arg1, arg2, arg3) => {
write!(
f,
"Exhaustive({:?}, {:?}, {:?}, {:?})",
arg0, arg1, arg2, arg3
)
write!(f, "Exhaustive({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
}
Self::Resolve(arg0) => {
write!(f, "Resolve({:?})", arg0)
write!(f, "Resolve({arg0:?})")
}
Self::CheckCycle(arg0, arg1) => {
write!(f, "CheckCycle({:?}, {:?})", arg0, arg1)
write!(f, "CheckCycle({arg0:?}, {arg1:?})")
}
Self::IngestedFile(arg0, arg1, arg2) => {
write!(f, "IngestedFile({:?}, {:?}, {:?})", arg0, arg1, arg2)
write!(f, "IngestedFile({arg0:?}, {arg1:?}, {arg2:?})")
}
}
}

View file

@ -24,7 +24,7 @@ trait CopyEnv {
if descriptor.copy.into_variable().is_some() {
descriptor.copy = OptVariable::NONE;
} else {
debug_assert!(false, "{:?} marked as copied but it wasn't", var);
debug_assert!(false, "{var:?} marked as copied but it wasn't");
}
})
}
@ -1148,6 +1148,7 @@ fn deep_copy_type_vars<C: CopyEnv>(
})
})
}
ErasedLambda => ErasedLambda,
RangedNumber(range) => {
perform_clone!(RangedNumber(range))
@ -1320,7 +1321,7 @@ mod test {
FlexVar(Some(name)) => {
assert_eq!(subs[*name].as_str(), "a");
}
it => panic!("{:?}", it),
it => panic!("{it:?}"),
}
assert_eq!(var, variant_var);
assert!(matches!(
@ -1337,7 +1338,7 @@ mod test {
FlexVar(Some(name)) => {
assert_eq!(subs[*name].as_str(), "b");
}
it => panic!("{:?}", it),
it => panic!("{it:?}"),
}
match arg.value {
@ -1355,10 +1356,10 @@ mod test {
assert_eq!(name.0.as_str(), "G");
assert_eq!(arguments.len(), 0);
}
e => panic!("{:?}", e),
e => panic!("{e:?}"),
}
}
e => panic!("{:?}", e),
e => panic!("{e:?}"),
}
}
@ -1403,7 +1404,7 @@ mod test {
FlexVar(Some(name)) => {
assert_eq!(target[*name].as_str(), "a");
}
it => panic!("{:?}", it),
it => panic!("{it:?}"),
}
assert_eq!(var, variant_var);
assert!(matches!(
@ -1418,7 +1419,7 @@ mod test {
FlexVar(Some(name)) => {
assert_eq!(target[*name].as_str(), "b");
}
it => panic!("{:?}", it),
it => panic!("{it:?}"),
}
match arg.value {
@ -1436,10 +1437,10 @@ mod test {
assert_eq!(name.0.as_str(), "G");
assert_eq!(arguments.len(), 0);
}
e => panic!("{:?}", e),
e => panic!("{e:?}"),
}
}
e => panic!("{:?}", e),
e => panic!("{e:?}"),
}
}

View file

@ -88,6 +88,23 @@ pub struct Annotation {
pub region: Region,
}
impl Annotation {
fn freshen(mut self, var_store: &mut VarStore) -> Self {
let mut substitutions = MutMap::default();
for v in self.introduced_variables.lambda_sets.iter_mut() {
let new = var_store.fresh();
substitutions.insert(*v, new);
*v = new;
}
self.signature.substitute_variables(&substitutions);
self
}
}
#[derive(Debug)]
pub(crate) struct CanDefs {
defs: Vec<Option<Def>>,
@ -1069,8 +1086,7 @@ fn canonicalize_value_defs<'a>(
debug_assert_eq!(env.home, s.module_id());
debug_assert!(
!symbol_to_index.iter().any(|(id, _)| *id == s.ident_id()),
"{:?}",
s
"{s:?}"
);
symbol_to_index.push((s.ident_id(), def_index as u32));
@ -1639,6 +1655,14 @@ pub(crate) fn sort_can_defs_new(
}
};
let host_annotation = if exposed_symbols.contains(&symbol) {
def.annotation
.clone()
.map(|a| (var_store.fresh(), a.freshen(var_store)))
} else {
None
};
if is_initial && !exposed_symbols.contains(&symbol) {
env.problem(Problem::DefsOnlyUsedInRecursion(1, def.region()));
}
@ -1650,6 +1674,7 @@ pub(crate) fn sort_can_defs_new(
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
host_annotation,
specializes,
);
}
@ -1659,55 +1684,80 @@ pub(crate) fn sort_can_defs_new(
def.loc_expr,
def.expr_var,
def.annotation,
host_annotation,
specializes,
);
}
}
} else {
match def.loc_pattern.value {
Pattern::Identifier(symbol) => match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_function_def(
Loc::at(def.loc_pattern.region, symbol),
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
None,
);
Pattern::Identifier(symbol) => {
let host_annotation = if exposed_symbols.contains(&symbol) {
def.annotation
.clone()
.map(|a| (var_store.fresh(), a.freshen(var_store)))
} else {
None
};
match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_function_def(
Loc::at(def.loc_pattern.region, symbol),
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
host_annotation,
None,
);
}
_ => {
declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol),
def.loc_expr,
def.expr_var,
def.annotation,
host_annotation,
None,
);
}
}
_ => {
declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol),
def.loc_expr,
def.expr_var,
def.annotation,
None,
);
}
},
}
Pattern::AbilityMemberSpecialization {
ident: symbol,
specializes,
} => match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_function_def(
Loc::at(def.loc_pattern.region, symbol),
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
Some(specializes),
);
} => {
let host_annotation = if exposed_symbols.contains(&symbol) {
def.annotation
.clone()
.map(|a| (var_store.fresh(), a.freshen(var_store)))
} else {
None
};
match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_function_def(
Loc::at(def.loc_pattern.region, symbol),
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
host_annotation,
Some(specializes),
);
}
_ => {
declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol),
def.loc_expr,
def.expr_var,
def.annotation,
host_annotation,
Some(specializes),
);
}
}
_ => {
declarations.push_value_def(
Loc::at(def.loc_pattern.region, symbol),
def.loc_expr,
def.expr_var,
def.annotation,
Some(specializes),
);
}
},
}
_ => {
declarations.push_destructure_def(
def.loc_pattern,
@ -1750,6 +1800,14 @@ pub(crate) fn sort_can_defs_new(
Some(r) => Some(Region::span_across(&r, &def.region())),
};
let host_annotation = if exposed_symbols.contains(&symbol) {
def.annotation
.clone()
.map(|a| (var_store.fresh(), a.freshen(var_store)))
} else {
None
};
match def.loc_expr.value {
Closure(closure_data) => {
declarations.push_recursive_def(
@ -1757,6 +1815,7 @@ pub(crate) fn sort_can_defs_new(
Loc::at(def.loc_expr.region, closure_data),
def.expr_var,
def.annotation,
host_annotation,
specializes,
);
}
@ -1766,6 +1825,7 @@ pub(crate) fn sort_can_defs_new(
def.loc_expr,
def.expr_var,
def.annotation,
host_annotation,
specializes,
);
}
@ -1838,7 +1898,7 @@ pub(crate) fn sort_can_defs(
);
let declaration = if def_ordering.references.get_row_col(index, index) {
debug_assert!(!is_specialization, "Self-recursive specializations can only be determined during solving - but it was determined for {:?} now, that's a bug!", def);
debug_assert!(!is_specialization, "Self-recursive specializations can only be determined during solving - but it was determined for {def:?} now, that's a bug!");
if is_initial
&& !def
@ -2272,12 +2332,18 @@ fn canonicalize_pending_body<'a>(
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
) -> DefOutput {
let mut loc_value = &loc_expr.value;
while let ast::Expr::ParensAround(value) = loc_value {
loc_value = value;
}
// We treat closure definitions `foo = \a, b -> ...` differently from other body expressions,
// because they need more bookkeeping (for tail calls, closure captures, etc.)
//
// Only defs of the form `foo = ...` can be closure declarations or self tail calls.
let (loc_can_expr, def_references) = {
match (&loc_can_pattern.value, &loc_expr.value) {
match (&loc_can_pattern.value, &loc_value) {
(
Pattern::Identifier(defined_symbol)
| Pattern::AbilityMemberSpecialization {

View file

@ -222,16 +222,16 @@ pub(crate) fn synthesize_member_impl<'a>(
ability_member: Symbol,
) -> (Symbol, Loc<Pattern>, &'a Loc<ast::Expr<'a>>) {
// @Opaq
let at_opaque = env.arena.alloc_str(&format!("@{}", opaque_name));
let at_opaque = env.arena.alloc_str(&format!("@{opaque_name}"));
let (impl_name, def_body): (String, ast::Expr<'a>) = match ability_member {
Symbol::ENCODE_TO_ENCODER => (
format!("#{}_toEncoder", opaque_name),
format!("#{opaque_name}_toEncoder"),
to_encoder(env, at_opaque),
),
Symbol::DECODE_DECODER => (format!("#{}_decoder", opaque_name), decoder(env, at_opaque)),
Symbol::HASH_HASH => (format!("#{}_hash", opaque_name), hash(env, at_opaque)),
Symbol::BOOL_IS_EQ => (format!("#{}_isEq", opaque_name), is_eq(env, at_opaque)),
Symbol::DECODE_DECODER => (format!("#{opaque_name}_decoder"), decoder(env, at_opaque)),
Symbol::HASH_HASH => (format!("#{opaque_name}_hash"), hash(env, at_opaque)),
Symbol::BOOL_IS_EQ => (format!("#{opaque_name}_isEq"), is_eq(env, at_opaque)),
other => internal_error!("{:?} is not a derivable ability member!", other),
};

View file

@ -1362,7 +1362,7 @@ pub fn build_host_exposed_def(
match typ.shallow_structural_dealias() {
Type::Function(args, _, _) => {
for i in 0..args.len() {
let name = format!("closure_arg_{}_{}", ident, i);
let name = format!("closure_arg_{ident}_{i}");
let arg_symbol = {
let ident = name.clone().into();
@ -1381,7 +1381,7 @@ pub fn build_host_exposed_def(
linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol, arg_var)));
}
let foreign_symbol_name = format!("roc_fx_{}", ident);
let foreign_symbol_name = format!("roc_fx_{ident}");
let low_level_call = Expr::ForeignCall {
foreign_symbol: foreign_symbol_name.into(),
args: linked_symbol_arguments,
@ -1389,7 +1389,7 @@ pub fn build_host_exposed_def(
};
let effect_closure_symbol = {
let name = format!("effect_closure_{}", ident);
let name = format!("effect_closure_{ident}");
let ident = name.into();
scope.introduce(ident, Region::zero()).unwrap()
@ -1435,7 +1435,7 @@ pub fn build_host_exposed_def(
_ => {
// not a function
let foreign_symbol_name = format!("roc_fx_{}", ident);
let foreign_symbol_name = format!("roc_fx_{ident}");
let low_level_call = Expr::ForeignCall {
foreign_symbol: foreign_symbol_name.into(),
args: linked_symbol_arguments,
@ -1443,7 +1443,7 @@ pub fn build_host_exposed_def(
};
let effect_closure_symbol = {
let name = format!("effect_closure_{}", ident);
let name = format!("effect_closure_{ident}");
let ident = name.into();
scope.introduce(ident, Region::zero()).unwrap()

View file

@ -67,8 +67,7 @@ impl<'a> Env<'a> {
) -> Result<Symbol, RuntimeError> {
debug_assert!(
!module_name_str.is_empty(),
"Called env.qualified_lookup with an unqualified ident: {:?}",
ident
"Called env.qualified_lookup with an unqualified ident: {ident:?}"
);
let module_name = ModuleName::from(module_name_str);

View file

@ -140,6 +140,7 @@ fn index_var(
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _)
| Content::LambdaSet(_)
| Content::ErasedLambda
| Content::RangedNumber(..) => return Err(TypeError),
Content::Error => return Err(TypeError),
Content::RecursionVar {

View file

@ -18,7 +18,7 @@ use roc_module::called_via::CalledVia;
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::ast::{self, Defs, PrecedenceConflict, StrLiteral};
use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
@ -1403,14 +1403,13 @@ pub fn canonicalize_expr<'a>(
(answer, Output::default())
}
&ast::Expr::ParensAround(sub_expr) => {
let (loc_expr, output) = canonicalize_expr(env, var_store, scope, region, sub_expr);
(loc_expr.value, output)
}
// Below this point, we shouln't see any of these nodes anymore because
// operator desugaring should have removed them!
bad_expr @ ast::Expr::ParensAround(_) => {
internal_error!(
"A ParensAround did not get removed during operator desugaring somehow: {:#?}",
bad_expr
);
}
bad_expr @ ast::Expr::SpaceBefore(_, _) => {
internal_error!(
"A SpaceBefore did not get removed during operator desugaring somehow: {:#?}",
@ -2377,11 +2376,115 @@ fn flatten_str_literal<'a>(
}
}
/// Comments, newlines, and nested interpolation are disallowed inside interpolation
pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
match expr {
ast::Expr::Var { .. } => true,
ast::Expr::RecordAccess(sub_expr, _) => is_valid_interpolation(sub_expr),
_ => false,
// These definitely contain neither comments nor newlines, so they are valid
ast::Expr::Var { .. }
| ast::Expr::SingleQuote(_)
| ast::Expr::Str(StrLiteral::PlainLine(_))
| ast::Expr::Float(_)
| ast::Expr::Num(_)
| ast::Expr::NonBase10Int { .. }
| ast::Expr::AccessorFunction(_)
| ast::Expr::Crash
| ast::Expr::Underscore(_)
| ast::Expr::MalformedIdent(_, _)
| ast::Expr::Tag(_)
| ast::Expr::OpaqueRef(_)
| ast::Expr::MalformedClosure => true,
// Newlines are disallowed inside interpolation, and these all require newlines
ast::Expr::Dbg(_, _)
| ast::Expr::Defs(_, _)
| ast::Expr::Expect(_, _)
| ast::Expr::When(_, _)
| ast::Expr::Backpassing(_, _, _)
| ast::Expr::IngestedFile(_, _)
| ast::Expr::SpaceBefore(_, _)
| ast::Expr::Str(StrLiteral::Block(_))
| ast::Expr::SpaceAfter(_, _) => false,
// These can contain subexpressions, so we need to recursively check those
ast::Expr::Str(StrLiteral::Line(segments)) => {
segments.iter().all(|segment| match segment {
ast::StrSegment::EscapedChar(_)
| ast::StrSegment::Unicode(_)
| ast::StrSegment::Plaintext(_) => true,
// Disallow nested interpolation. Alternatively, we could allow it but require
// a comment above it apologizing to the next person who has to read the code.
ast::StrSegment::Interpolated(_) => false,
})
}
ast::Expr::Record(fields) => fields.iter().all(|loc_field| match loc_field.value {
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
}
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
ast::AssignedField::SpaceBefore(_, _) | ast::AssignedField::SpaceAfter(_, _) => false,
}),
ast::Expr::Tuple(fields) => fields
.iter()
.all(|loc_field| is_valid_interpolation(&loc_field.value)),
ast::Expr::MultipleRecordBuilders(loc_expr)
| ast::Expr::UnappliedRecordBuilder(loc_expr)
| ast::Expr::PrecedenceConflict(PrecedenceConflict { expr: loc_expr, .. })
| ast::Expr::UnaryOp(loc_expr, _)
| ast::Expr::Closure(_, loc_expr) => is_valid_interpolation(&loc_expr.value),
ast::Expr::TupleAccess(sub_expr, _)
| ast::Expr::ParensAround(sub_expr)
| ast::Expr::RecordAccess(sub_expr, _) => is_valid_interpolation(sub_expr),
ast::Expr::Apply(loc_expr, args, _called_via) => {
is_valid_interpolation(&loc_expr.value)
&& args
.iter()
.all(|loc_arg| is_valid_interpolation(&loc_arg.value))
}
ast::Expr::BinOps(loc_exprs, loc_expr) => {
is_valid_interpolation(&loc_expr.value)
&& loc_exprs
.iter()
.all(|(loc_expr, _binop)| is_valid_interpolation(&loc_expr.value))
}
ast::Expr::If(branches, final_branch) => {
is_valid_interpolation(&final_branch.value)
&& branches.iter().all(|(loc_before, loc_after)| {
is_valid_interpolation(&loc_before.value)
&& is_valid_interpolation(&loc_after.value)
})
}
ast::Expr::List(elems) => elems
.iter()
.all(|loc_expr| is_valid_interpolation(&loc_expr.value)),
ast::Expr::RecordUpdate { update, fields } => {
is_valid_interpolation(&update.value)
&& fields.iter().all(|loc_field| match loc_field.value {
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
| ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
}
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
ast::AssignedField::SpaceBefore(_, _)
| ast::AssignedField::SpaceAfter(_, _) => false,
})
}
ast::Expr::RecordBuilder(fields) => fields.iter().all(|loc_field| match loc_field.value {
ast::RecordBuilderField::Value(_label, comments, loc_expr) => {
comments.is_empty() && is_valid_interpolation(&loc_expr.value)
}
ast::RecordBuilderField::ApplyValue(
_label,
comments_before,
comments_after,
loc_expr,
) => {
comments_before.is_empty()
&& comments_after.is_empty()
&& is_valid_interpolation(&loc_expr.value)
}
ast::RecordBuilderField::Malformed(_) | ast::RecordBuilderField::LabelOnly(_) => true,
ast::RecordBuilderField::SpaceBefore(_, _)
| ast::RecordBuilderField::SpaceAfter(_, _) => false,
}),
}
}
@ -2536,6 +2639,8 @@ pub struct Declarations {
// used for ability member specializatons.
pub specializes: VecMap<usize, Symbol>,
pub host_exposed_annotations: VecMap<usize, (Variable, crate::def::Annotation)>,
pub function_bodies: Vec<Loc<FunctionDef>>,
pub expressions: Vec<Loc<Expr>>,
pub destructs: Vec<DestructureDef>,
@ -2557,6 +2662,7 @@ impl Declarations {
variables: Vec::with_capacity(capacity),
symbols: Vec::with_capacity(capacity),
annotations: Vec::with_capacity(capacity),
host_exposed_annotations: VecMap::new(),
function_bodies: Vec::with_capacity(capacity),
expressions: Vec::with_capacity(capacity),
specializes: VecMap::default(), // number of specializations is probably low
@ -2587,6 +2693,7 @@ impl Declarations {
loc_closure_data: Loc<ClosureData>,
expr_var: Variable,
annotation: Option<Annotation>,
host_annotation: Option<(Variable, Annotation)>,
specializes: Option<Symbol>,
) -> usize {
let index = self.declarations.len();
@ -2609,6 +2716,11 @@ impl Declarations {
Recursive::TailRecursive => DeclarationTag::TailRecursive(function_def_index),
};
if let Some(annotation) = host_annotation {
self.host_exposed_annotations
.insert(self.declarations.len(), annotation);
}
self.declarations.push(tag);
self.variables.push(expr_var);
self.symbols.push(symbol);
@ -2629,6 +2741,7 @@ impl Declarations {
loc_closure_data: Loc<ClosureData>,
expr_var: Variable,
annotation: Option<Annotation>,
host_annotation: Option<(Variable, Annotation)>,
specializes: Option<Symbol>,
) -> usize {
let index = self.declarations.len();
@ -2644,6 +2757,11 @@ impl Declarations {
let function_def_index = Index::push_new(&mut self.function_bodies, loc_function_def);
if let Some(annotation) = host_annotation {
self.host_exposed_annotations
.insert(self.declarations.len(), annotation);
}
self.declarations
.push(DeclarationTag::Function(function_def_index));
self.variables.push(expr_var);
@ -2701,10 +2819,16 @@ impl Declarations {
loc_expr: Loc<Expr>,
expr_var: Variable,
annotation: Option<Annotation>,
host_annotation: Option<(Variable, Annotation)>,
specializes: Option<Symbol>,
) -> usize {
let index = self.declarations.len();
if let Some(annotation) = host_annotation {
self.host_exposed_annotations
.insert(self.declarations.len(), annotation);
}
self.declarations.push(DeclarationTag::Value);
self.variables.push(expr_var);
self.symbols.push(symbol);
@ -2759,6 +2883,7 @@ impl Declarations {
def.expr_var,
def.annotation,
None,
None,
);
}
@ -2769,6 +2894,7 @@ impl Declarations {
def.expr_var,
def.annotation,
None,
None,
);
}
},
@ -2779,6 +2905,7 @@ impl Declarations {
def.expr_var,
def.annotation,
None,
None,
);
}
},

View file

@ -347,9 +347,7 @@ pub fn canonicalize_module_defs<'a>(
// the symbol should already be added to the scope when this module is canonicalized
debug_assert!(
scope.contains_alias(symbol) || scope.abilities_store.is_ability(symbol),
"The {:?} is not a type alias or ability known in {:?}",
symbol,
home
"The {symbol:?} is not a type alias or ability known in {home:?}"
);
// but now we know this symbol by a different identifier, so we still need to add it to

View file

@ -220,8 +220,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind
assert!(
(2..=36).contains(&radix),
"from_str_radix_int: must lie in the range `[2, 36]` - found {}",
radix
"from_str_radix_int: must lie in the range `[2, 36]` - found {radix}"
);
let (opt_exact_bound, src) = parse_literal_suffix(src);

View file

@ -7,7 +7,9 @@ use roc_module::called_via::BinOp::Pizza;
use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Collection, RecordBuilderField, ValueDef, WhenBranch};
use roc_parse::ast::{
AssignedField, Collection, RecordBuilderField, StrLiteral, StrSegment, ValueDef, WhenBranch,
};
use roc_region::all::{Loc, Region};
// BinOp precedence logic adapted from Gluon by Markus Westerlind
@ -129,7 +131,6 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
Float(..)
| Num(..)
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
| AccessorFunction(_)
| Var { .. }
@ -144,6 +145,28 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| IngestedFile(_, _)
| Crash => loc_expr,
Str(str_literal) => match str_literal {
StrLiteral::PlainLine(_) => loc_expr,
StrLiteral::Line(segments) => {
let region = loc_expr.region;
let value = Str(StrLiteral::Line(desugar_str_segments(arena, segments)));
arena.alloc(Loc { region, value })
}
StrLiteral::Block(lines) => {
let region = loc_expr.region;
let new_lines = Vec::from_iter_in(
lines
.iter()
.map(|segments| desugar_str_segments(arena, segments)),
arena,
);
let value = Str(StrLiteral::Block(new_lines.into_bump_slice()));
arena.alloc(Loc { region, value })
}
},
TupleAccess(sub_expr, paths) => {
let region = loc_expr.region;
let loc_sub_expr = Loc {
@ -288,7 +311,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
break builder_arg.closure;
}
SpaceBefore(expr, _) | SpaceAfter(expr, _) | ParensAround(expr) => {
SpaceBefore(expr, _) | SpaceAfter(expr, _) => {
current = *expr;
}
_ => break loc_arg,
@ -382,7 +405,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
region: loc_expr.region,
})
}
SpaceBefore(expr, _) | SpaceAfter(expr, _) | ParensAround(expr) => {
SpaceBefore(expr, _) | SpaceAfter(expr, _) => {
// Since we've already begun canonicalization, spaces and parens
// are no longer needed and should be dropped.
desugar_expr(
@ -393,6 +416,20 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
}),
)
}
ParensAround(expr) => {
let desugared = desugar_expr(
arena,
arena.alloc(Loc {
value: **expr,
region: loc_expr.region,
}),
);
arena.alloc(Loc {
value: ParensAround(&desugared.value),
region: loc_expr.region,
})
}
If(if_thens, final_else_branch) => {
// If does not get desugared into `when` so we can give more targeted error messages during type checking.
let desugared_final_else = &*arena.alloc(desugar_expr(arena, final_else_branch));
@ -430,6 +467,34 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
}
}
fn desugar_str_segments<'a>(
arena: &'a Bump,
segments: &'a [StrSegment<'a>],
) -> &'a [StrSegment<'a>] {
Vec::from_iter_in(
segments.iter().map(|segment| match segment {
StrSegment::Plaintext(_) | StrSegment::Unicode(_) | StrSegment::EscapedChar(_) => {
*segment
}
StrSegment::Interpolated(loc_expr) => {
let loc_desugared = desugar_expr(
arena,
arena.alloc(Loc {
region: loc_expr.region,
value: *loc_expr.value,
}),
);
StrSegment::Interpolated(Loc {
region: loc_desugared.region,
value: arena.alloc(loc_desugared.value),
})
}
}),
arena,
)
.into_bump_slice()
}
fn desugar_field<'a>(
arena: &'a Bump,
field: &'a AssignedField<'a, Expr<'a>>,

View file

@ -531,7 +531,7 @@ pub fn canonicalize_pattern<'a>(
use std::ops::Neg;
let sign_str = if is_negative { "-" } else { "" };
let int_str = format!("{}{}", sign_str, int).into_boxed_str();
let int_str = format!("{sign_str}{int}").into_boxed_str();
let i = match int {
// Safety: this is fine because I128::MAX = |I128::MIN| - 1
IntValue::I128(n) if is_negative => {

View file

@ -37,8 +37,7 @@ pub struct CanExprOut {
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = roc_parse::test_helpers::parse_loc_with(arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{expr_str:?} {e:?}"
)
});

View file

@ -422,7 +422,7 @@ mod test_can {
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 2);
println!("{:#?}", problems);
println!("{problems:#?}");
assert!(problems.iter().any(|problem| matches!(
problem,
Problem::RuntimeError(RuntimeError::Shadowing { .. })

View file

@ -0,0 +1,19 @@
[package]
name = "roc_checkmate"
description = "A framework for debugging the solver."
authors.workspace = true
edition.workspace = true
license.workspace = true
version.workspace = true
[dependencies]
roc_checkmate_schema = { path = "../checkmate_schema" }
roc_module = { path = "../module" }
roc_solve_schema = { path = "../solve_schema" }
roc_types = { path = "../types" }
chrono.workspace = true
[build-dependencies]
roc_checkmate_schema = { path = "../checkmate_schema" }
serde_json.workspace = true

View file

@ -0,0 +1,5 @@
# `checkmate`
A tool to debug the solver (checker + inference + specialization engine).
See [the document](https://rwx.notion.site/Type-debugging-tools-de42260060784cacbaf08ea4d61e0eb9?pvs=4).

View file

@ -0,0 +1,13 @@
use std::fs;
use roc_checkmate_schema::AllEvents;
fn main() {
println!("cargo:rerun-if-changed=../checkmate_schema");
let schema = AllEvents::schema();
fs::write(
"schema.json",
serde_json::to_string_pretty(&schema).unwrap(),
)
.unwrap();
}

View file

@ -0,0 +1,907 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "AllEvents",
"type": "array",
"items": {
"$ref": "#/definitions/Event"
},
"definitions": {
"AliasKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Structural"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Opaque"
]
}
}
}
]
},
"AliasTypeVariables": {
"type": "object",
"required": [
"infer_ext_in_output_position_variables",
"lambda_set_variables",
"type_variables"
],
"properties": {
"infer_ext_in_output_position_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"lambda_set_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"type_variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
}
},
"ClosureType": {
"type": "object",
"required": [
"environment",
"function"
],
"properties": {
"environment": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"function": {
"$ref": "#/definitions/Symbol"
}
}
},
"Content": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"name": {
"type": [
"string",
"null"
]
},
"type": {
"type": "string",
"enum": [
"Flex"
]
}
}
},
{
"type": "object",
"required": [
"name",
"type"
],
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"Rigid"
]
}
}
},
{
"type": "object",
"required": [
"abilities",
"type"
],
"properties": {
"abilities": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"name": {
"type": [
"string",
"null"
]
},
"type": {
"type": "string",
"enum": [
"FlexAble"
]
}
}
},
{
"type": "object",
"required": [
"abilities",
"name",
"type"
],
"properties": {
"abilities": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"RigidAble"
]
}
}
},
{
"type": "object",
"required": [
"structure",
"type"
],
"properties": {
"name": {
"type": [
"string",
"null"
]
},
"structure": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Recursive"
]
}
}
},
{
"type": "object",
"required": [
"ambient_function",
"solved",
"type",
"unspecialized"
],
"properties": {
"ambient_function": {
"$ref": "#/definitions/Variable"
},
"recursion_var": {
"anyOf": [
{
"$ref": "#/definitions/Variable"
},
{
"type": "null"
}
]
},
"solved": {
"type": "array",
"items": {
"$ref": "#/definitions/ClosureType"
}
},
"type": {
"type": "string",
"enum": [
"LambdaSet"
]
},
"unspecialized": {
"type": "array",
"items": {
"$ref": "#/definitions/UnspecializedClosureType"
}
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"ErasedLambda"
]
}
}
},
{
"type": "object",
"required": [
"kind",
"name",
"real_variable",
"type",
"variables"
],
"properties": {
"kind": {
"$ref": "#/definitions/AliasKind"
},
"name": {
"$ref": "#/definitions/Symbol"
},
"real_variable": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Alias"
]
},
"variables": {
"$ref": "#/definitions/AliasTypeVariables"
}
}
},
{
"type": "object",
"required": [
"symbol",
"type",
"variables"
],
"properties": {
"symbol": {
"$ref": "#/definitions/Symbol"
},
"type": {
"type": "string",
"enum": [
"Apply"
]
},
"variables": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
}
},
{
"type": "object",
"required": [
"arguments",
"lambda_type",
"ret",
"type"
],
"properties": {
"arguments": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
},
"lambda_type": {
"$ref": "#/definitions/Variable"
},
"ret": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Function"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"fields",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/Variable"
},
"fields": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/RecordField"
}
},
"type": {
"type": "string",
"enum": [
"Record"
]
}
}
},
{
"type": "object",
"required": [
"elements",
"extension",
"type"
],
"properties": {
"elements": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Variable"
}
},
"extension": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"Tuple"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
},
"type": {
"type": "string",
"enum": [
"TagUnion"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"functions",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"functions": {
"type": "array",
"items": {
"$ref": "#/definitions/Symbol"
}
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"type": {
"type": "string",
"enum": [
"FunctionOrTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"extension",
"recursion_var",
"tags",
"type"
],
"properties": {
"extension": {
"$ref": "#/definitions/TagUnionExtension"
},
"recursion_var": {
"$ref": "#/definitions/Variable"
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/Variable"
}
}
},
"type": {
"type": "string",
"enum": [
"RecursiveTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyRecord"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyTuple"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EmptyTagUnion"
]
}
}
},
{
"type": "object",
"required": [
"range",
"type"
],
"properties": {
"range": {
"$ref": "#/definitions/NumericRange"
},
"type": {
"type": "string",
"enum": [
"RangedNumber"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Error"
]
}
}
}
]
},
"Event": {
"oneOf": [
{
"type": "object",
"required": [
"left",
"mode",
"right",
"subevents",
"type"
],
"properties": {
"left": {
"$ref": "#/definitions/Variable"
},
"mode": {
"$ref": "#/definitions/UnificationMode"
},
"right": {
"$ref": "#/definitions/Variable"
},
"subevents": {
"type": "array",
"items": {
"$ref": "#/definitions/Event"
}
},
"success": {
"type": [
"boolean",
"null"
]
},
"type": {
"type": "string",
"enum": [
"Unification"
]
}
}
},
{
"type": "object",
"required": [
"from",
"to",
"type"
],
"properties": {
"from": {
"$ref": "#/definitions/Variable"
},
"to": {
"$ref": "#/definitions/Variable"
},
"type": {
"type": "string",
"enum": [
"VariableUnified"
]
}
}
},
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"content": {
"anyOf": [
{
"$ref": "#/definitions/Content"
},
{
"type": "null"
}
]
},
"rank": {
"anyOf": [
{
"$ref": "#/definitions/Rank"
},
{
"type": "null"
}
]
},
"type": {
"type": "string",
"enum": [
"VariableSetDescriptor"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
}
]
},
"NumericRange": {
"type": "object",
"required": [
"kind",
"min_width",
"signed"
],
"properties": {
"kind": {
"$ref": "#/definitions/NumericRangeKind"
},
"min_width": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"signed": {
"type": "boolean"
}
}
},
"NumericRangeKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Int"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"AnyNum"
]
}
}
}
]
},
"Rank": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"RecordField": {
"type": "object",
"required": [
"field_type",
"kind"
],
"properties": {
"field_type": {
"$ref": "#/definitions/Variable"
},
"kind": {
"$ref": "#/definitions/RecordFieldKind"
}
}
},
"RecordFieldKind": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Demanded"
]
}
}
},
{
"type": "object",
"required": [
"rigid",
"type"
],
"properties": {
"rigid": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": [
"Required"
]
}
}
},
{
"type": "object",
"required": [
"rigid",
"type"
],
"properties": {
"rigid": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": [
"Optional"
]
}
}
}
]
},
"Symbol": {
"type": "string"
},
"TagUnionExtension": {
"oneOf": [
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Openness"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
},
{
"type": "object",
"required": [
"type",
"variable"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Any"
]
},
"variable": {
"$ref": "#/definitions/Variable"
}
}
}
]
},
"UnificationMode": {
"oneOf": [
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Eq"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Present"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"LambdaSetSpecialization"
]
}
}
}
]
},
"UnspecializedClosureType": {
"type": "object",
"required": [
"ability_member",
"lambda_set_region",
"specialization"
],
"properties": {
"ability_member": {
"$ref": "#/definitions/Symbol"
},
"lambda_set_region": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"specialization": {
"$ref": "#/definitions/Variable"
}
}
},
"Variable": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
}

View file

@ -0,0 +1,175 @@
use std::error::Error;
use roc_checkmate_schema::{AllEvents, Event};
use roc_types::subs as s;
use crate::convert::AsSchema;
#[derive(Debug)]
pub struct Collector {
events: AllEvents,
current_event_path: Vec<usize>,
}
impl Default for Collector {
fn default() -> Self {
Self::new()
}
}
impl Collector {
pub fn new() -> Self {
Self {
events: AllEvents(Vec::new()),
current_event_path: Vec::new(),
}
}
pub fn unify(&mut self, subs: &s::Subs, from: s::Variable, to: s::Variable) {
let to = to.as_schema(subs);
let from = from.as_schema(subs);
self.add_event(Event::VariableUnified { to, from });
}
pub fn set_content(&mut self, subs: &s::Subs, var: s::Variable, content: s::Content) {
let variable = var.as_schema(subs);
let content = content.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
content: Some(content),
rank: None,
});
}
pub fn set_rank(&mut self, subs: &s::Subs, var: s::Variable, rank: s::Rank) {
let variable = var.as_schema(subs);
let rank = rank.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
rank: Some(rank),
content: None,
});
}
pub fn set_descriptor(&mut self, subs: &s::Subs, var: s::Variable, descriptor: s::Descriptor) {
let variable = var.as_schema(subs);
let rank = descriptor.rank.as_schema(subs);
let content = descriptor.content.as_schema(subs);
self.add_event(Event::VariableSetDescriptor {
variable,
rank: Some(rank),
content: Some(content),
});
}
pub fn start_unification(
&mut self,
subs: &s::Subs,
left: s::Variable,
right: s::Variable,
mode: roc_solve_schema::UnificationMode,
) {
let left = left.as_schema(subs);
let right = right.as_schema(subs);
let mode = mode.as_schema(subs);
let subevents = Vec::new();
self.add_event(Event::Unification {
left,
right,
mode,
subevents,
success: None,
});
}
pub fn end_unification(
&mut self,
subs: &s::Subs,
left: s::Variable,
right: s::Variable,
success: bool,
) {
let current_event = self.get_path_event();
match current_event {
EventW::Sub(Event::Unification {
left: l,
right: r,
success: s,
..
}) => {
assert_eq!(left.as_schema(subs), *l);
assert_eq!(right.as_schema(subs), *r);
assert!(s.is_none());
*s = Some(success);
}
_ => panic!("end_unification called when not in a unification"),
}
self.current_event_path.pop();
}
pub fn write(&self, writer: impl std::io::Write) -> Result<(), Box<dyn Error>> {
self.events.write(writer)?;
Ok(())
}
fn add_event(&mut self, event: impl Into<Event>) {
let mut event = event.into();
let is_appendable = EventW::Sub(&mut event).appendable();
let event = event;
let path_event = self.get_path_event();
let new_event_index = path_event.append(event);
if is_appendable {
self.current_event_path.push(new_event_index);
}
}
fn get_path_event(&mut self) -> EventW {
let mut event = EventW::Top(&mut self.events);
for i in &self.current_event_path {
event = event.index(*i);
}
event
}
}
enum EventW<'a> {
Top(&'a mut AllEvents),
Sub(&'a mut Event),
}
impl<'a> EventW<'a> {
fn append(self, event: Event) -> usize {
let list = self.subevents_mut().unwrap();
let index = list.len();
list.push(event);
index
}
fn appendable(self) -> bool {
self.subevents().is_some()
}
fn index(self, index: usize) -> EventW<'a> {
Self::Sub(&mut self.subevents_mut().unwrap()[index])
}
}
impl<'a> EventW<'a> {
fn subevents(self) -> Option<&'a Vec<Event>> {
use EventW::*;
match self {
Top(events) => Some(&events.0),
Sub(Event::Unification { subevents, .. }) => Some(subevents),
Sub(Event::VariableUnified { .. } | Event::VariableSetDescriptor { .. }) => None,
}
}
fn subevents_mut(self) -> Option<&'a mut Vec<Event>> {
use EventW::*;
match self {
Top(events) => Some(&mut events.0),
Sub(Event::Unification { subevents, .. }) => Some(subevents),
Sub(Event::VariableUnified { .. } | Event::VariableSetDescriptor { .. }) => None,
}
}
}

View file

@ -0,0 +1,323 @@
use std::collections::HashMap;
use roc_module::{ident, symbol};
use roc_types::{
num,
subs::{self, GetSubsSlice, Subs, SubsIndex, SubsSlice, UnionLabels},
types,
};
use roc_checkmate_schema::{
AliasKind, AliasTypeVariables, ClosureType, Content, NumericRange, NumericRangeKind, Rank,
RecordField, RecordFieldKind, Symbol, TagUnionExtension, UnificationMode,
UnspecializedClosureType, Variable,
};
pub trait AsSchema<T> {
fn as_schema(&self, subs: &Subs) -> T;
}
impl<T, U> AsSchema<Option<U>> for Option<T>
where
T: AsSchema<U>,
T: Copy,
{
fn as_schema(&self, subs: &Subs) -> Option<U> {
self.map(|i| i.as_schema(subs))
}
}
impl<T, U> AsSchema<Vec<U>> for &[T]
where
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> Vec<U> {
self.iter().map(|i| i.as_schema(subs)).collect()
}
}
impl<T, U> AsSchema<U> for SubsIndex<T>
where
Subs: std::ops::Index<SubsIndex<T>, Output = T>,
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> U {
subs[*self].as_schema(subs)
}
}
impl<T, U> AsSchema<Vec<U>> for SubsSlice<T>
where
Subs: GetSubsSlice<T>,
T: AsSchema<U>,
{
fn as_schema(&self, subs: &Subs) -> Vec<U> {
subs.get_subs_slice(*self)
.iter()
.map(|i| i.as_schema(subs))
.collect()
}
}
impl AsSchema<Content> for subs::Content {
fn as_schema(&self, subs: &Subs) -> Content {
use {subs::Content as A, Content as B};
match self {
A::FlexVar(name) => B::Flex(name.as_schema(subs)),
A::RigidVar(name) => B::Rigid(name.as_schema(subs)),
A::FlexAbleVar(name, abilities) => {
B::FlexAble(name.as_schema(subs), abilities.as_schema(subs))
}
A::RigidAbleVar(name, abilities) => {
B::RigidAble(name.as_schema(subs), abilities.as_schema(subs))
}
A::RecursionVar {
structure,
opt_name,
} => B::Recursive(opt_name.as_schema(subs), structure.as_schema(subs)),
A::LambdaSet(lambda_set) => lambda_set.as_schema(subs),
A::ErasedLambda => B::ErasedLambda(),
A::Structure(flat_type) => flat_type.as_schema(subs),
A::Alias(name, type_vars, real_var, kind) => B::Alias(
name.as_schema(subs),
type_vars.as_schema(subs),
real_var.as_schema(subs),
kind.as_schema(subs),
),
A::RangedNumber(range) => B::RangedNumber(range.as_schema(subs)),
A::Error => B::Error(),
}
}
}
impl AsSchema<Content> for subs::FlatType {
fn as_schema(&self, subs: &Subs) -> Content {
match self {
subs::FlatType::Apply(symbol, variables) => {
Content::Apply(symbol.as_schema(subs), variables.as_schema(subs))
}
subs::FlatType::Func(arguments, closure, ret) => Content::Function(
arguments.as_schema(subs),
closure.as_schema(subs),
ret.as_schema(subs),
),
subs::FlatType::Record(fields, ext) => {
Content::Record(fields.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::Tuple(elems, ext) => {
Content::Tuple(elems.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::TagUnion(tags, ext) => {
Content::TagUnion(tags.as_schema(subs), ext.as_schema(subs))
}
subs::FlatType::FunctionOrTagUnion(tags, functions, ext) => {
Content::FunctionOrTagUnion(
functions.as_schema(subs),
tags.as_schema(subs),
ext.as_schema(subs),
)
}
subs::FlatType::RecursiveTagUnion(rec_var, tags, ext) => Content::RecursiveTagUnion(
rec_var.as_schema(subs),
tags.as_schema(subs),
ext.as_schema(subs),
),
subs::FlatType::EmptyRecord => Content::EmptyRecord(),
subs::FlatType::EmptyTuple => Content::EmptyTuple(),
subs::FlatType::EmptyTagUnion => Content::EmptyTagUnion(),
}
}
}
impl AsSchema<Content> for subs::LambdaSet {
fn as_schema(&self, subs: &Subs) -> Content {
let subs::LambdaSet {
solved,
unspecialized,
recursion_var,
ambient_function,
} = self;
Content::LambdaSet(
solved.as_schema(subs),
unspecialized.as_schema(subs),
recursion_var.as_schema(subs),
ambient_function.as_schema(subs),
)
}
}
impl AsSchema<String> for ident::Lowercase {
fn as_schema(&self, _subs: &Subs) -> String {
self.to_string()
}
}
impl AsSchema<Symbol> for symbol::Symbol {
fn as_schema(&self, _subs: &Subs) -> Symbol {
Symbol(format!("{:#?}", self))
}
}
impl AsSchema<Variable> for subs::Variable {
fn as_schema(&self, _subs: &Subs) -> Variable {
Variable(self.index())
}
}
impl AsSchema<Option<Variable>> for subs::OptVariable {
fn as_schema(&self, _subs: &Subs) -> Option<Variable> {
self.into_variable().map(|i| i.as_schema(_subs))
}
}
impl AsSchema<Vec<ClosureType>> for UnionLabels<symbol::Symbol> {
fn as_schema(&self, subs: &Subs) -> Vec<ClosureType> {
self.iter_from_subs(subs)
.map(|(function, environment)| ClosureType {
function: function.as_schema(subs),
environment: environment.as_schema(subs),
})
.collect()
}
}
impl AsSchema<UnspecializedClosureType> for types::Uls {
fn as_schema(&self, subs: &Subs) -> UnspecializedClosureType {
let types::Uls(specialization, ability_member, lambda_set_region) = self;
UnspecializedClosureType {
specialization: specialization.as_schema(subs),
ability_member: ability_member.as_schema(subs),
lambda_set_region: *lambda_set_region,
}
}
}
impl AsSchema<AliasTypeVariables> for subs::AliasVariables {
fn as_schema(&self, subs: &Subs) -> AliasTypeVariables {
let type_variables = self.type_variables().as_schema(subs);
let lambda_set_variables = self.lambda_set_variables().as_schema(subs);
let infer_ext_in_output_position_variables =
self.infer_ext_in_output_variables().as_schema(subs);
AliasTypeVariables {
type_variables,
lambda_set_variables,
infer_ext_in_output_position_variables,
}
}
}
impl AsSchema<AliasKind> for types::AliasKind {
fn as_schema(&self, _subs: &Subs) -> AliasKind {
match self {
types::AliasKind::Structural => AliasKind::Structural,
types::AliasKind::Opaque => AliasKind::Opaque,
}
}
}
impl AsSchema<HashMap<String, RecordField>> for subs::RecordFields {
fn as_schema(&self, subs: &Subs) -> HashMap<String, RecordField> {
let mut map = HashMap::new();
for (name, var, field) in self.iter_all() {
let name = name.as_schema(subs);
let field_type = var.as_schema(subs);
let kind = field.as_schema(subs);
map.insert(name, RecordField { field_type, kind });
}
map
}
}
impl AsSchema<RecordFieldKind> for types::RecordField<()> {
fn as_schema(&self, _subs: &Subs) -> RecordFieldKind {
match self {
types::RecordField::Demanded(_) => RecordFieldKind::Demanded,
types::RecordField::Required(_) => RecordFieldKind::Required { rigid: false },
types::RecordField::Optional(_) => RecordFieldKind::Optional { rigid: false },
types::RecordField::RigidRequired(_) => RecordFieldKind::Required { rigid: true },
types::RecordField::RigidOptional(_) => RecordFieldKind::Optional { rigid: true },
}
}
}
impl AsSchema<HashMap<u32, Variable>> for subs::TupleElems {
fn as_schema(&self, subs: &Subs) -> HashMap<u32, Variable> {
let mut map = HashMap::new();
for (index, var) in self.iter_all() {
let name = subs[index] as _;
let var = var.as_schema(subs);
map.insert(name, var);
}
map
}
}
impl AsSchema<HashMap<String, Vec<Variable>>> for subs::UnionTags {
fn as_schema(&self, subs: &Subs) -> HashMap<String, Vec<Variable>> {
let mut map = HashMap::new();
for (tag, payloads) in self.iter_from_subs(subs) {
map.insert(tag.as_schema(subs), payloads.as_schema(subs));
}
map
}
}
impl AsSchema<TagUnionExtension> for subs::TagExt {
fn as_schema(&self, subs: &Subs) -> TagUnionExtension {
match self {
subs::TagExt::Openness(var) => TagUnionExtension::Openness(var.as_schema(subs)),
subs::TagExt::Any(var) => TagUnionExtension::Any(var.as_schema(subs)),
}
}
}
impl AsSchema<NumericRange> for num::NumericRange {
fn as_schema(&self, _subs: &Subs) -> NumericRange {
let kind =
match self {
num::NumericRange::IntAtLeastSigned(_)
| num::NumericRange::IntAtLeastEitherSign(_) => NumericRangeKind::Int,
num::NumericRange::NumAtLeastSigned(_)
| num::NumericRange::NumAtLeastEitherSign(_) => NumericRangeKind::AnyNum,
};
let min_width = self.min_width();
let (signedness, width) = min_width.signedness_and_width();
let signed = signedness.is_signed();
NumericRange {
kind,
signed,
min_width: width,
}
}
}
impl AsSchema<String> for ident::TagName {
fn as_schema(&self, _subs: &Subs) -> String {
self.0.to_string()
}
}
impl AsSchema<Rank> for subs::Rank {
fn as_schema(&self, _subs: &Subs) -> Rank {
Rank(self.into_usize() as _)
}
}
impl AsSchema<UnificationMode> for roc_solve_schema::UnificationMode {
fn as_schema(&self, _subs: &Subs) -> UnificationMode {
if self.is_eq() {
UnificationMode::Eq
} else if self.is_present() {
UnificationMode::Present
} else if self.is_lambda_set_specialization() {
UnificationMode::LambdaSetSpecialization
} else {
unreachable!()
}
}
}

Some files were not shown because too many files have changed in this diff Show more