mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-13 09:49:11 +03:00
Merge branch 'main' of github.com:roc-lang/roc into editor_launch_fix
This commit is contained in:
commit
8dff853830
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: roc-lang
|
30
.github/workflows/markdown_link_check.yml
vendored
Normal file
30
.github/workflows/markdown_link_check.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
on:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 9 * * *' # 9=9am utc+0
|
||||
|
||||
name: Check Markdown links
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
use-quiet-mode: 'yes'
|
||||
use-verbose-mode: 'yes'
|
||||
base-branch: 'main'
|
||||
check-modified-files-only: 'yes'
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
use-quiet-mode: 'yes'
|
||||
use-verbose-mode: 'yes'
|
||||
base-branch: 'main'
|
||||
check-modified-files-only: 'no'
|
||||
if: ${{ github.event_name == 'schedule' }}
|
@ -29,7 +29,7 @@ jobs:
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_apple_silicon-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_12_apple_silicon-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
|
||||
- name: write version to file
|
||||
run: ./ci/write_version.sh
|
||||
|
27
.github/workflows/nightly_macos_x86_64.yml
vendored
27
.github/workflows/nightly_macos_x86_64.yml
vendored
@ -11,7 +11,10 @@ env:
|
||||
jobs:
|
||||
test-build-upload:
|
||||
name: build, test, package and upload nightly release
|
||||
runs-on: [macos-12]
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-11, macos-12 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -35,23 +38,41 @@ jobs:
|
||||
command: build
|
||||
args: --release --locked
|
||||
|
||||
- name: execute rust tests
|
||||
- name: execute rust tests if macos 12
|
||||
if: endsWith(matrix.os, '12')
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --release --locked -- --skip opaque_wrap_function --skip bool_list_literal
|
||||
|
||||
- name: execute rust tests if macos 11
|
||||
if: endsWith(matrix.os, '11')
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --release --locked -- --skip opaque_wrap_function --skip bool_list_literal --skip platform_switching_swift --skip swift_ui
|
||||
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
|
||||
# this issue may be caused by using older versions of XCode
|
||||
|
||||
- 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: get macos version if 11
|
||||
if: endsWith(matrix.os, '11')
|
||||
run: echo "MACOSVERSION=11" >> $GITHUB_ENV
|
||||
|
||||
- name: get macos version if 12
|
||||
if: endsWith(matrix.os, '12')
|
||||
run: echo "MACOSVERSION=12" >> $GITHUB_ENV
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_x86_64-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_${MACOSVERSION}_x86_64-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_TAR_FILENAME }}
|
||||
|
16
.github/workflows/nightly_netlify_build_deploy.yml
vendored
Normal file
16
.github/workflows/nightly_netlify_build_deploy.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 9 * * *'
|
||||
|
||||
name: Nightly netlify build and deploy
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: trigger netlify build and deploy
|
||||
env:
|
||||
HOOK: ${{ secrets.NETLIFY_BUILD_HOOK }}
|
||||
run: |
|
||||
curl -X POST -d {} https://api.netlify.com/build_hooks/${HOOK}
|
3
.github/workflows/nix_linux_x86_64.yml
vendored
3
.github/workflows/nix_linux_x86_64.yml
vendored
@ -21,3 +21,6 @@ jobs:
|
||||
|
||||
- name: execute tests with --release
|
||||
run: /home/big-ci-user/.nix-profile/bin/nix develop -c cargo test --locked --release
|
||||
|
||||
- name: test wasm32 cli_run
|
||||
run: /home/big-ci-user/.nix-profile/bin/nix develop -c cargo test --locked --release --features="wasm32-cli-run"
|
||||
|
16
.github/workflows/test_nightly_many_os.yml
vendored
16
.github/workflows/test_nightly_many_os.yml
vendored
@ -6,10 +6,10 @@ name: Test latest nightly release for macOS, ubu 20.04, ubu 22.04 x86_64
|
||||
|
||||
jobs:
|
||||
test-nightly:
|
||||
name: test nightly macos, ubu 20.04, ubu 22.04
|
||||
name: test nightly macos 11, macos 12, ubu 20.04, ubu 22.04
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-12, ubuntu-20.04, ubuntu-22.04 ]
|
||||
os: [ macos-11, macos-12, ubuntu-20.04, ubuntu-22.04 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
@ -23,13 +23,17 @@ jobs:
|
||||
--header 'content-type: application/json' \
|
||||
--output roc_releases.json
|
||||
|
||||
- name: get the url of today`s release for linux x86_64
|
||||
- name: get the url of today`s release for linux (x86_64)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: echo "RELEASE_URL=$(./ci/get_latest_release_url.sh linux_x86_64)" >> $GITHUB_ENV
|
||||
|
||||
- name: get the url of today`s release for macos x86_64
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
run: echo "RELEASE_URL=$(./ci/get_latest_release_url.sh macos_x86_64)" >> $GITHUB_ENV
|
||||
- name: get the url of today`s release for macos 11 (x86_64)
|
||||
if: startsWith(matrix.os, 'macos-11')
|
||||
run: echo "RELEASE_URL=$(./ci/get_latest_release_url.sh macos_11_x86_64)" >> $GITHUB_ENV
|
||||
|
||||
- name: get the url of today`s release for macos 12 (x86_64)
|
||||
if: startsWith(matrix.os, 'macos-12')
|
||||
run: echo "RELEASE_URL=$(./ci/get_latest_release_url.sh macos_12_x86_64)" >> $GITHUB_ENV
|
||||
|
||||
- name: get the archive from the url
|
||||
run: curl -OL ${{ env.RELEASE_URL }}
|
||||
|
6
.github/workflows/ubuntu_x86_64.yml
vendored
6
.github/workflows/ubuntu_x86_64.yml
vendored
@ -26,6 +26,9 @@ jobs:
|
||||
|
||||
- name: zig fmt check, zig tests
|
||||
run: cd crates/compiler/builtins/bitcode && ./run-tests.sh
|
||||
|
||||
- name: roc format check on builtins
|
||||
run: cargo run --locked --release format --check crates/compiler/builtins/roc
|
||||
|
||||
- name: zig wasm tests
|
||||
run: cd crates/compiler/builtins/bitcode && ./run-wasm-tests.sh
|
||||
@ -45,6 +48,9 @@ jobs:
|
||||
- name: run `roc test` on Str builtins
|
||||
run: cargo run --locked --release -- test crates/compiler/builtins/roc/Str.roc && sccache --show-stats
|
||||
|
||||
- name: run `roc test` on Dict builtins
|
||||
run: cargo run --locked --release -- test crates/compiler/builtins/roc/Dict.roc && sccache --show-stats
|
||||
|
||||
#TODO pass --locked into the script here as well, this avoids rebuilding dependencies unnecessarily
|
||||
- name: wasm repl test
|
||||
run: crates/repl_test/test_wasm.sh && sccache --show-stats
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
We are committed to providing a friendly, safe and welcoming environment for all. Make sure to take a look at the [Code of Conduct](CodeOfConduct.md)!
|
||||
We are committed to providing a friendly, safe and welcoming environment for all. Make sure to take a look at the [Code of Conduct](CODE_OF_CONDUCT.md)!
|
||||
|
||||
## How to contribute
|
||||
|
||||
@ -23,7 +23,7 @@ Check [Building from source](BUILDING_FROM_SOURCE.md) for instructions.
|
||||
|
||||
## Running Tests
|
||||
|
||||
Most contributors execute the following commands befor pushing their code:
|
||||
Most contributors execute the following commands before pushing their code:
|
||||
|
||||
```sh
|
||||
cargo test
|
||||
@ -40,7 +40,7 @@ Execute `cargo fmt --all` to fix the formatting.
|
||||
- You can find good first issues [here][good-first-issues].
|
||||
- [Fork](https://github.com/roc-lang/roc/fork) the repo so that you can apply your changes first on your own copy of the roc repo.
|
||||
- It's a good idea to open a draft pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Click the button "ready for review" when it's ready.
|
||||
- All your commits need to be signed to prevent impersonation:
|
||||
- All your commits need to be signed [to prevent impersonation](https://dev.to/martiliones/how-i-got-linus-torvalds-in-my-contributors-on-github-3k4g):
|
||||
1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below.
|
||||
2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key).
|
||||
3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
|
||||
|
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -96,6 +96,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "arena-pool"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"roc_error_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
@ -1958,9 +1961,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.133"
|
||||
version = "0.2.135"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
|
||||
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@ -2558,9 +2561,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@ -3286,6 +3289,7 @@ dependencies = [
|
||||
"morphic_lib",
|
||||
"roc_collections",
|
||||
"roc_debug_flags",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
]
|
||||
@ -3381,6 +3385,7 @@ dependencies = [
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_serialize",
|
||||
"roc_types",
|
||||
"static_assertions",
|
||||
]
|
||||
@ -4030,6 +4035,13 @@ dependencies = [
|
||||
"ven_pretty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_serialize"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"roc_collections",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_solve"
|
||||
version = "0.0.1"
|
||||
@ -4120,6 +4132,7 @@ dependencies = [
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
"roc_serialize",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
@ -5115,7 +5128,7 @@ version = "1.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if 1.0.0",
|
||||
"rand",
|
||||
"static_assertions",
|
||||
]
|
||||
|
@ -32,6 +32,7 @@ members = [
|
||||
"crates/compiler/test_gen",
|
||||
"crates/compiler/roc_target",
|
||||
"crates/compiler/debug_flags",
|
||||
"crates/compiler/serialize",
|
||||
"crates/vendor/inkwell",
|
||||
"crates/vendor/pathfinding",
|
||||
"crates/vendor/pretty",
|
||||
|
79
TUTORIAL.md
79
TUTORIAL.md
@ -1239,6 +1239,77 @@ each bit. `0b0000_1000` evaluates to decimal `8`
|
||||
The integer type can be specified as a suffix to the binary literal,
|
||||
so `0b0100u8` evaluates to decimal `4` as an unsigned 8-bit integer.
|
||||
|
||||
## Tests and expectations
|
||||
|
||||
You can write automated tests for your Roc code like so:
|
||||
|
||||
```swift
|
||||
pluralize = \singular, plural, count ->
|
||||
countStr = Num.toStr count
|
||||
|
||||
if count == 1 then
|
||||
"\(countStr) \(singular)"
|
||||
else
|
||||
"\(countStr) \(plural)"
|
||||
|
||||
expect pluralize "cactus" "cacti" 1 == "1 cactus"
|
||||
|
||||
expect pluralize "cactus" "cacti" 2 == "2 cacti"
|
||||
```
|
||||
|
||||
If you put this in a file named `main.roc` and run `roc test`, Roc will execute the two `expect`
|
||||
expressions (that is, the two `pluralize` calls) and report any that returned `false`.
|
||||
|
||||
### Inline `expect`s
|
||||
|
||||
For example:
|
||||
|
||||
```swift
|
||||
if count == 1 then
|
||||
"\(countStr) \(singular)"
|
||||
else
|
||||
expect count > 0
|
||||
|
||||
"\(countStr) \(plural)"
|
||||
```
|
||||
|
||||
This `expect` will fail if you call `pluralize` passing a count of 0, and it will fail
|
||||
regardless of whether the inline `expect` is reached when running your program via `roc dev`
|
||||
or in the course of running a test (with `roc test`).
|
||||
|
||||
So for example, if we added this top-level `expect`...
|
||||
|
||||
```swift
|
||||
expect pluralize "cactus" "cacti" 0 == "0 cacti"
|
||||
```
|
||||
|
||||
...it would hit the inline `expect count > 0`, which would then fail the test.
|
||||
|
||||
Note that inline `expect`s do not halt the program! They are designed to inform, not to affect
|
||||
control flow. In fact, if you do `roc build`, they are not even included in the final binary.
|
||||
|
||||
If you try this code out, you may note that when an `expect` fails (either a top-level or inline
|
||||
one), the failure message includes the values of any named variables - such as `count` here.
|
||||
This leads to a useful technique, which we will see next.
|
||||
|
||||
### Quick debugging with inline `expect`s
|
||||
|
||||
An age-old debugging technique is printing out a variable to the terminal. In Roc you can use
|
||||
`expect` to do this. Here's an example:
|
||||
|
||||
```elm
|
||||
\arg ->
|
||||
x = arg - 1
|
||||
|
||||
# Reports the value of `x` without stopping the program
|
||||
expect x != x
|
||||
|
||||
Num.abs x
|
||||
```
|
||||
|
||||
The failure output will include both the value of `x` as well as the comment immediately above it,
|
||||
which lets you use that comment for extra context in your output.
|
||||
|
||||
## Interface modules
|
||||
|
||||
[This part of the tutorial has not been written yet. Coming soon!]
|
||||
@ -1326,6 +1397,14 @@ this `imports` line tells the Roc compiler that when we call `Stdout.line`, it
|
||||
should look for that `line` function in the `Stdout` module of the
|
||||
`examples/cli/cli-platform/main.roc` package.
|
||||
|
||||
If we would like to include other modules in our application, say `AdditionalModule.roc` and `AnotherModule.roc`, then they can be imported directly in `imports` like this:
|
||||
|
||||
```coffee
|
||||
packages { pf: "examples/cli/cli-platform/main.roc" }
|
||||
imports [pf.Stdout, pf.Program, AdditionalModule, AnotherModule]
|
||||
provides main to pf
|
||||
```
|
||||
|
||||
## Tasks
|
||||
|
||||
Tasks are technically not part of the Roc language, but they're very common in
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
cp target/release/roc ./roc # to be able to exclude "target" later in the tar command
|
||||
cp -r target/release/lib ./lib
|
||||
tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/helloWorld.roc examples/cli crates/roc_std
|
||||
tar -czvf $1 --exclude="target" --exclude="zig-cache" roc lib LICENSE LEGAL_DETAILS examples/helloWorld.roc examples/platform-switching examples/cli crates/roc_std
|
||||
|
@ -26,7 +26,7 @@ bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
page_size = "0.4.2"
|
||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||
ven_graph = { path = "../vendor/pathfinding" }
|
||||
libc = "0.2.133"
|
||||
libc = "0.2.135"
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.7"
|
||||
|
@ -64,7 +64,7 @@ clap = { version = "3.2.20", default-features = false, features = ["std", "color
|
||||
const_format = { version = "0.2.23", features = ["const_generics"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
mimalloc = { version = "0.1.26", default-features = false }
|
||||
libc = "0.2.133"
|
||||
libc = "0.2.135"
|
||||
errno = "0.2.8"
|
||||
ven_pretty = { path = "../vendor/pretty" }
|
||||
|
||||
@ -100,7 +100,7 @@ indoc = "1.0.7"
|
||||
serial_test = "0.9.0"
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
|
||||
cli_utils = { path = "../cli_utils" }
|
||||
once_cell = "1.14.0"
|
||||
once_cell = "1.15.0"
|
||||
parking_lot = "0.12"
|
||||
|
||||
# Wasmer singlepass compiler only works on x86_64.
|
||||
|
@ -1118,7 +1118,7 @@ impl Target {
|
||||
Wasm32 => Triple {
|
||||
architecture: Architecture::Wasm32,
|
||||
vendor: Vendor::Unknown,
|
||||
operating_system: OperatingSystem::Unknown,
|
||||
operating_system: OperatingSystem::Wasi,
|
||||
environment: Environment::Unknown,
|
||||
binary_format: BinaryFormat::Wasm,
|
||||
},
|
||||
|
@ -401,7 +401,7 @@ mod cli_run {
|
||||
// uses C platform
|
||||
fn platform_switching_main() {
|
||||
test_roc_app_slim(
|
||||
"crates/cli_testing_examples/platform-switching",
|
||||
"examples/platform-switching",
|
||||
"main.roc",
|
||||
"rocLovesPlatforms",
|
||||
"Which platform am I running on now?\n",
|
||||
@ -416,7 +416,7 @@ mod cli_run {
|
||||
#[test]
|
||||
fn platform_switching_rust() {
|
||||
test_roc_app_slim(
|
||||
"crates/cli_testing_examples/platform-switching",
|
||||
"examples/platform-switching",
|
||||
"rocLovesRust.roc",
|
||||
"rocLovesRust",
|
||||
"Roc <3 Rust!\n",
|
||||
@ -427,7 +427,7 @@ mod cli_run {
|
||||
#[test]
|
||||
fn platform_switching_zig() {
|
||||
test_roc_app_slim(
|
||||
"crates/cli_testing_examples/platform-switching",
|
||||
"examples/platform-switching",
|
||||
"rocLovesZig.roc",
|
||||
"rocLovesZig",
|
||||
"Roc <3 Zig!\n",
|
||||
@ -438,7 +438,7 @@ mod cli_run {
|
||||
#[test]
|
||||
fn platform_switching_wasm() {
|
||||
test_roc_app_slim(
|
||||
"crates/cli_testing_examples/platform-switching",
|
||||
"examples/platform-switching",
|
||||
"rocLovesWebAssembly.roc",
|
||||
"rocLovesWebAssembly",
|
||||
"Roc <3 Web Assembly!\n",
|
||||
@ -449,7 +449,7 @@ mod cli_run {
|
||||
#[test]
|
||||
fn platform_switching_swift() {
|
||||
test_roc_app_slim(
|
||||
"crates/cli_testing_examples/platform-switching",
|
||||
"examples/platform-switching",
|
||||
"rocLovesSwift.roc",
|
||||
"rocLovesSwift",
|
||||
"Roc <3 Swift!\n",
|
||||
@ -763,6 +763,8 @@ mod cli_run {
|
||||
flags: &[&str],
|
||||
expected_ending: &str,
|
||||
) {
|
||||
use super::{concatcp, run_roc, CMD_BUILD, TARGET_FLAG};
|
||||
|
||||
let mut flags = flags.to_vec();
|
||||
flags.push(concatcp!(TARGET_FLAG, "=wasm32"));
|
||||
|
||||
@ -771,14 +773,14 @@ mod cli_run {
|
||||
.iter()
|
||||
.chain(flags.as_slice()),
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
if !compile_out.stderr.is_empty() {
|
||||
panic!("{}", compile_out.stderr);
|
||||
}
|
||||
|
||||
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
|
||||
|
||||
let path = file.with_file_name(executable_filename);
|
||||
let mut path = file.with_file_name(executable_filename);
|
||||
path.set_extension("wasm");
|
||||
|
||||
let stdout = crate::run_with_wasmer(&path, stdin);
|
||||
|
||||
if !stdout.ends_with(expected_ending) {
|
||||
|
@ -20,7 +20,7 @@ initialModel = \start -> {
|
||||
cameFrom: Dict.empty,
|
||||
}
|
||||
|
||||
cheapestOpen : (position -> F64), Model position -> Result position {}
|
||||
cheapestOpen : (position -> F64), Model position -> Result position {} | position has Eq
|
||||
cheapestOpen = \costFn, model ->
|
||||
model.openSet
|
||||
|> Set.toList
|
||||
@ -35,13 +35,13 @@ cheapestOpen = \costFn, model ->
|
||||
|> Result.map .position
|
||||
|> Result.mapErr (\_ -> {})
|
||||
|
||||
reconstructPath : Dict position position, position -> List position
|
||||
reconstructPath : Dict position position, position -> List position | position has Eq
|
||||
reconstructPath = \cameFrom, goal ->
|
||||
when Dict.get cameFrom goal is
|
||||
Err _ -> []
|
||||
Ok next -> List.append (reconstructPath cameFrom next) goal
|
||||
|
||||
updateCost : position, position, Model position -> Model position
|
||||
updateCost : position, position, Model position -> Model position | position has Eq
|
||||
updateCost = \current, neighbor, model ->
|
||||
newCameFrom =
|
||||
Dict.insert model.cameFrom neighbor current
|
||||
@ -70,7 +70,7 @@ updateCost = \current, neighbor, model ->
|
||||
else
|
||||
model
|
||||
|
||||
astar : (position, position -> F64), (position -> Set position), position, Model position -> Result (List position) {}
|
||||
astar : (position, position -> F64), (position -> Set position), position, Model position -> Result (List position) {} | position has Eq
|
||||
astar = \costFn, moveFn, goal, model ->
|
||||
when cheapestOpen (\source -> costFn source goal) model is
|
||||
Err {} -> Err {}
|
||||
|
@ -189,14 +189,14 @@ fn roc_fx_getInt_64bit() callconv(.C) GetInt {
|
||||
|
||||
fn roc_fx_getInt_32bit(output: *GetInt) callconv(.C) void {
|
||||
if (roc_fx_getInt_help()) |value| {
|
||||
const get_int = GetInt{ .is_error = false, .value = value, .error_code = false };
|
||||
const get_int = GetInt{ .is_error = false, .value = value };
|
||||
output.* = get_int;
|
||||
} else |err| switch (err) {
|
||||
error.InvalidCharacter => {
|
||||
output.* = GetInt{ .is_error = true, .value = 0, .error_code = false };
|
||||
output.* = GetInt{ .is_error = true, .value = 0 };
|
||||
},
|
||||
else => {
|
||||
output.* = GetInt{ .is_error = true, .value = 0, .error_code = true };
|
||||
output.* = GetInt{ .is_error = true, .value = 0 };
|
||||
},
|
||||
}
|
||||
|
||||
|
4301
crates/cli_utils/Cargo.lock
generated
4301
crates/cli_utils/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -11,3 +11,4 @@ roc_collections = {path = "../collections"}
|
||||
roc_module = {path = "../module"}
|
||||
roc_mono = {path = "../mono"}
|
||||
roc_debug_flags = {path = "../debug_flags"}
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
|
@ -16,6 +16,8 @@ use roc_mono::layout::{
|
||||
Builtin, CapturesNiche, Layout, RawFunctionLayout, STLayoutInterner, UnionLayout,
|
||||
};
|
||||
|
||||
use roc_error_macros::internal_error;
|
||||
|
||||
// just using one module for now
|
||||
pub const MOD_APP: ModName = ModName(b"UserApp");
|
||||
|
||||
@ -603,9 +605,10 @@ fn build_tuple_value(
|
||||
|
||||
for field in symbols.iter() {
|
||||
let value_id = match env.symbols.get(field) {
|
||||
None => panic!(
|
||||
None => internal_error!(
|
||||
"Symbol {:?} is not defined in environment {:?}",
|
||||
field, &env.symbols
|
||||
field,
|
||||
&env.symbols
|
||||
),
|
||||
Some(x) => *x,
|
||||
};
|
||||
|
@ -6,3 +6,6 @@ license = "UPL-1.0"
|
||||
repository = "https://github.com/roc-lang/roc"
|
||||
edition = "2021"
|
||||
description = "A CLI for Roc"
|
||||
|
||||
[dependencies]
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
|
@ -1,3 +1,4 @@
|
||||
use roc_error_macros::internal_error;
|
||||
use std::marker::PhantomPinned;
|
||||
use std::ptr::{copy_nonoverlapping, NonNull};
|
||||
|
||||
@ -391,6 +392,6 @@ fn verify_ownership<T>(
|
||||
|
||||
// The address wasn't within any of our chunks' bounds.
|
||||
// Panic to avoid use-after-free errors!
|
||||
panic!("Pointer ownership verification failed.");
|
||||
internal_error!("Pointer ownership verification failed.");
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ pub fn link(
|
||||
operating_system: OperatingSystem::Windows,
|
||||
..
|
||||
} => link_windows(target, output_path, input_paths, link_type),
|
||||
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
_ => internal_error!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ fn find_zig_str_path() -> PathBuf {
|
||||
return zig_str_path;
|
||||
}
|
||||
|
||||
panic!("cannot find `str.zig`. Check the source code in find_zig_str_path() to show all the paths I tried.")
|
||||
internal_error!("cannot find `str.zig`. Check the source code in find_zig_str_path() to show all the paths I tried.")
|
||||
}
|
||||
|
||||
fn find_wasi_libc_path() -> PathBuf {
|
||||
@ -99,7 +99,7 @@ fn find_wasi_libc_path() -> PathBuf {
|
||||
return wasi_libc_pathbuf;
|
||||
}
|
||||
|
||||
panic!("cannot find `wasi-libc.a`")
|
||||
internal_error!("cannot find `wasi-libc.a`")
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
@ -258,15 +258,15 @@ pub fn build_zig_host_native(
|
||||
|
||||
let zig_env_json = if zig_env_output.status.success() {
|
||||
std::str::from_utf8(&zig_env_output.stdout).unwrap_or_else(|utf8_err| {
|
||||
panic!(
|
||||
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) => panic!("`zig env` failed - stderr output was: {:?}", stderr),
|
||||
Err(utf8_err) => panic!(
|
||||
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
|
||||
),
|
||||
@ -277,11 +277,11 @@ pub fn build_zig_host_native(
|
||||
Ok(Value::Object(map)) => match map.get("std_dir") {
|
||||
Some(Value::String(std_dir)) => PathBuf::from(Path::new(std_dir)),
|
||||
_ => {
|
||||
panic!("Expected JSON containing a `std_dir` String field from `zig env`, but got: {:?}", zig_env_json);
|
||||
internal_error!("Expected JSON containing a `std_dir` String field from `zig env`, but got: {:?}", zig_env_json);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
panic!(
|
||||
internal_error!(
|
||||
"Expected JSON containing a `std_dir` field from `zig env`, but got: {:?}",
|
||||
zig_env_json
|
||||
);
|
||||
@ -628,7 +628,7 @@ pub fn rebuild_host(
|
||||
shared_lib_path,
|
||||
)
|
||||
}
|
||||
_ => panic!("Unsupported architecture {:?}", target.architecture),
|
||||
_ => internal_error!("Unsupported architecture {:?}", target.architecture),
|
||||
};
|
||||
|
||||
validate_output("host.zig", &zig_executable(), output)
|
||||
@ -962,7 +962,7 @@ fn link_linux(
|
||||
}
|
||||
}
|
||||
Architecture::Aarch64(_) => library_path(["/lib", "ld-linux-aarch64.so.1"]),
|
||||
_ => panic!(
|
||||
_ => internal_error!(
|
||||
"TODO gracefully handle unsupported linux architecture: {:?}",
|
||||
target.architecture
|
||||
),
|
||||
@ -1370,13 +1370,17 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
|
||||
fn validate_output(file_name: &str, cmd_name: &str, output: Output) {
|
||||
if !output.status.success() {
|
||||
match std::str::from_utf8(&output.stderr) {
|
||||
Ok(stderr) => panic!(
|
||||
Ok(stderr) => internal_error!(
|
||||
"Failed to rebuild {} - stderr of the `{}` command was:\n{}",
|
||||
file_name, cmd_name, stderr
|
||||
file_name,
|
||||
cmd_name,
|
||||
stderr
|
||||
),
|
||||
Err(utf8_err) => panic!(
|
||||
Err(utf8_err) => internal_error!(
|
||||
"Failed to rebuild {} - stderr of the `{}` command was invalid utf8 ({:?})",
|
||||
file_name, cmd_name, utf8_err
|
||||
file_name,
|
||||
cmd_name,
|
||||
utf8_err
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use inkwell::memory_buffer::MemoryBuffer;
|
||||
use roc_error_macros::internal_error;
|
||||
pub use roc_gen_llvm::llvm::build::FunctionIterator;
|
||||
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
@ -315,7 +316,7 @@ fn gen_from_mono_module_llvm(
|
||||
// write the ll code to a file, so we can modify it
|
||||
env.module.print_to_file(&app_ll_file).unwrap();
|
||||
|
||||
panic!(
|
||||
internal_error!(
|
||||
"😱 LLVM errors when defining module; I wrote the full LLVM IR to {:?}\n\n {}",
|
||||
app_ll_file,
|
||||
errors.to_string(),
|
||||
@ -353,10 +354,10 @@ fn gen_from_mono_module_llvm(
|
||||
Err(error) => {
|
||||
use std::io::ErrorKind;
|
||||
match error.kind() {
|
||||
ErrorKind::NotFound => panic!(
|
||||
ErrorKind::NotFound => internal_error!(
|
||||
r"I could not find the `debugir` tool on the PATH, install it from https://github.com/vaivaswatha/debugir"
|
||||
),
|
||||
_ => panic!("{:?}", error),
|
||||
_ => internal_error!("{:?}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -417,7 +418,7 @@ fn gen_from_mono_module_llvm(
|
||||
// module.print_to_file(app_ll_file);
|
||||
module.write_bitcode_to_memory()
|
||||
}
|
||||
_ => panic!(
|
||||
_ => internal_error!(
|
||||
"TODO gracefully handle unsupported architecture: {:?}",
|
||||
target.architecture
|
||||
),
|
||||
@ -506,14 +507,14 @@ fn gen_from_mono_module_dev_wasm32(
|
||||
};
|
||||
|
||||
let host_bytes = std::fs::read(preprocessed_host_path).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
internal_error!(
|
||||
"Failed to read host object file {}! Try setting --prebuilt-platform=false",
|
||||
preprocessed_host_path.display()
|
||||
)
|
||||
});
|
||||
|
||||
let host_module = roc_gen_wasm::parse_host(arena, &host_bytes).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
internal_error!(
|
||||
"I ran into a problem with the host object file, {} at offset 0x{:x}:\n{}",
|
||||
preprocessed_host_path.display(),
|
||||
e.offset,
|
||||
|
@ -2,6 +2,7 @@ use inkwell::{
|
||||
targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple},
|
||||
OptimizationLevel,
|
||||
};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
|
||||
|
||||
@ -44,7 +45,7 @@ pub fn target_triple_str(target: &Triple) -> &'static str {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
..
|
||||
} => "x86_64-pc-windows-gnu",
|
||||
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
_ => internal_error!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +93,7 @@ pub fn target_zig_str(target: &Triple) -> &'static str {
|
||||
operating_system: OperatingSystem::Darwin,
|
||||
..
|
||||
} => "aarch64-apple-darwin",
|
||||
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
_ => internal_error!("TODO gracefully handle unsupported target: {:?}", target),
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +113,7 @@ pub fn init_arch(target: &Triple) {
|
||||
Architecture::Wasm32 if cfg!(feature = "target-wasm32") => {
|
||||
Target::initialize_webassembly(&InitializationConfig::default());
|
||||
}
|
||||
_ => panic!(
|
||||
_ => internal_error!(
|
||||
"TODO gracefully handle unsupported target architecture: {:?}",
|
||||
target.architecture
|
||||
),
|
||||
@ -132,7 +133,7 @@ pub fn arch_str(target: &Triple) -> &'static str {
|
||||
Architecture::Aarch64(_) if cfg!(feature = "target-aarch64") => "aarch64",
|
||||
Architecture::Arm(_) if cfg!(feature = "target-arm") => "arm",
|
||||
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => "wasm32",
|
||||
_ => panic!(
|
||||
_ => internal_error!(
|
||||
"TODO gracefully handle unsupported target architecture: {:?}",
|
||||
target.architecture
|
||||
),
|
||||
|
@ -1,6 +1,5 @@
|
||||
const std = @import("std");
|
||||
const utils = @import("utils.zig");
|
||||
const RocResult = utils.RocResult;
|
||||
const UpdateMode = utils.UpdateMode;
|
||||
const mem = std.mem;
|
||||
const math = std.math;
|
||||
@ -126,53 +125,6 @@ pub const RocList = extern struct {
|
||||
return new_list;
|
||||
}
|
||||
|
||||
// We follow roughly the [fbvector](https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md) when it comes to growing a RocList.
|
||||
// Here is [their growth strategy](https://github.com/facebook/folly/blob/3e0525988fd444201b19b76b390a5927c15cb697/folly/FBVector.h#L1128) for push_back:
|
||||
//
|
||||
// (1) initial size
|
||||
// Instead of growing to size 1 from empty, fbvector allocates at least
|
||||
// 64 bytes. You may still use reserve to reserve a lesser amount of
|
||||
// memory.
|
||||
// (2) 1.5x
|
||||
// For medium-sized vectors, the growth strategy is 1.5x. See the docs
|
||||
// for details.
|
||||
// This does not apply to very small or very large fbvectors. This is a
|
||||
// heuristic.
|
||||
//
|
||||
// In our case, we exposed allocate and reallocate, which will use a smart growth stategy.
|
||||
// We also expose allocateExact and reallocateExact for case where a specific number of elements is requested.
|
||||
|
||||
// calculateCapacity should only be called in cases the list will be growing.
|
||||
// requested_length should always be greater than old_capacity.
|
||||
inline fn calculateCapacity(
|
||||
old_capacity: usize,
|
||||
requested_length: usize,
|
||||
element_width: usize,
|
||||
) usize {
|
||||
// TODO: there are two adjustments that would likely lead to better results for Roc.
|
||||
// 1. Deal with the fact we allocate an extra u64 for refcount.
|
||||
// This may lead to allocating page size + 8 bytes.
|
||||
// That could mean allocating an entire page for 8 bytes of data which isn't great.
|
||||
// 2. Deal with the fact that we can request more than 1 element at a time.
|
||||
// fbvector assumes just appending 1 element at a time when using this algorithm.
|
||||
// As such, they will generally grow in a way that should better match certain memory multiple.
|
||||
// This is also the normal case for roc, but we could also grow by a much larger amount.
|
||||
// We may want to round to multiples of 2 or something similar.
|
||||
var new_capacity: usize = 0;
|
||||
if (element_width == 0) {
|
||||
return requested_length;
|
||||
} else if (old_capacity == 0) {
|
||||
new_capacity = 64 / element_width;
|
||||
} else if (old_capacity < 4096 / element_width) {
|
||||
new_capacity = old_capacity * 2;
|
||||
} else if (old_capacity > 4096 * 32 / element_width) {
|
||||
new_capacity = old_capacity * 2;
|
||||
} else {
|
||||
new_capacity = (old_capacity * 3 + 1) / 2;
|
||||
}
|
||||
return @maximum(new_capacity, requested_length);
|
||||
}
|
||||
|
||||
pub fn allocate(
|
||||
alignment: u32,
|
||||
length: usize,
|
||||
@ -182,7 +134,7 @@ pub const RocList = extern struct {
|
||||
return empty();
|
||||
}
|
||||
|
||||
const capacity = calculateCapacity(0, length, element_width);
|
||||
const capacity = utils.calculateCapacity(0, length, element_width);
|
||||
const data_bytes = capacity * element_width;
|
||||
return RocList{
|
||||
.bytes = utils.allocateWithRefcount(data_bytes, alignment),
|
||||
@ -191,23 +143,6 @@ pub const RocList = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn allocateExact(
|
||||
alignment: u32,
|
||||
length: usize,
|
||||
element_width: usize,
|
||||
) RocList {
|
||||
if (length == 0) {
|
||||
return empty();
|
||||
}
|
||||
|
||||
const data_bytes = length * element_width;
|
||||
return RocList{
|
||||
.bytes = utils.allocateWithRefcount(data_bytes, alignment),
|
||||
.length = length,
|
||||
.capacity = length,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reallocate(
|
||||
self: RocList,
|
||||
alignment: u32,
|
||||
@ -219,67 +154,36 @@ pub const RocList = extern struct {
|
||||
if (self.capacity >= new_length) {
|
||||
return RocList{ .bytes = self.bytes, .length = new_length, .capacity = self.capacity };
|
||||
} else {
|
||||
const new_capacity = calculateCapacity(self.capacity, new_length, element_width);
|
||||
const new_capacity = utils.calculateCapacity(self.capacity, new_length, element_width);
|
||||
const new_source = utils.unsafeReallocate(source_ptr, alignment, self.len(), new_capacity, element_width);
|
||||
return RocList{ .bytes = new_source, .length = new_length, .capacity = new_capacity };
|
||||
}
|
||||
}
|
||||
// TODO: Investigate the performance of this.
|
||||
// Maybe we should just always reallocate to the new_length instead of expanding capacity?
|
||||
const new_capacity = if (self.capacity >= new_length) self.capacity else calculateCapacity(self.capacity, new_length, element_width);
|
||||
return self.reallocateFresh(alignment, new_length, new_capacity, element_width);
|
||||
return self.reallocateFresh(alignment, new_length, element_width);
|
||||
}
|
||||
return RocList.allocate(alignment, new_length, element_width);
|
||||
}
|
||||
|
||||
pub fn reallocateExact(
|
||||
self: RocList,
|
||||
alignment: u32,
|
||||
new_length: usize,
|
||||
element_width: usize,
|
||||
) RocList {
|
||||
if (self.bytes) |source_ptr| {
|
||||
if (self.isUnique()) {
|
||||
if (self.capacity >= new_length) {
|
||||
return RocList{ .bytes = self.bytes, .length = new_length, .capacity = self.capacity };
|
||||
} else {
|
||||
const new_source = utils.unsafeReallocate(source_ptr, alignment, self.len(), new_length, element_width);
|
||||
return RocList{ .bytes = new_source, .length = new_length, .capacity = new_length };
|
||||
}
|
||||
}
|
||||
return self.reallocateFresh(alignment, new_length, new_length, element_width);
|
||||
}
|
||||
return RocList.allocateExact(alignment, new_length, element_width);
|
||||
}
|
||||
|
||||
/// reallocate by explicitly making a new allocation and copying elements over
|
||||
fn reallocateFresh(
|
||||
self: RocList,
|
||||
alignment: u32,
|
||||
new_length: usize,
|
||||
new_capacity: usize,
|
||||
element_width: usize,
|
||||
) RocList {
|
||||
const old_length = self.length;
|
||||
const delta_length = new_length - old_length;
|
||||
|
||||
const data_bytes = new_capacity * element_width;
|
||||
const first_slot = utils.allocateWithRefcount(data_bytes, alignment);
|
||||
const result = RocList.allocate(alignment, new_length, element_width);
|
||||
|
||||
// transfer the memory
|
||||
if (self.bytes) |source_ptr| {
|
||||
const dest_ptr = first_slot;
|
||||
const dest_ptr = result.bytes orelse unreachable;
|
||||
|
||||
@memcpy(dest_ptr, source_ptr, old_length * element_width);
|
||||
@memset(dest_ptr + old_length * element_width, 0, delta_length * element_width);
|
||||
}
|
||||
|
||||
const result = RocList{
|
||||
.bytes = first_slot,
|
||||
.length = new_length,
|
||||
.capacity = new_capacity,
|
||||
};
|
||||
|
||||
utils.decref(self.bytes, old_length * element_width, alignment);
|
||||
|
||||
return result;
|
||||
@ -504,7 +408,7 @@ pub fn listWithCapacity(
|
||||
alignment: u32,
|
||||
element_width: usize,
|
||||
) callconv(.C) RocList {
|
||||
var output = RocList.allocateExact(alignment, capacity, element_width);
|
||||
var output = RocList.allocate(alignment, capacity, element_width);
|
||||
output.length = 0;
|
||||
return output;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ pub const RocStr = extern struct {
|
||||
// This clones the pointed-to bytes if they won't fit in a
|
||||
// small string, and returns a (pointer, len) tuple which points to them.
|
||||
pub fn init(bytes_ptr: [*]const u8, length: usize) RocStr {
|
||||
var result = RocStr.allocate(length, length);
|
||||
var result = RocStr.allocate(length);
|
||||
@memcpy(result.asU8ptr(), bytes_ptr, length);
|
||||
|
||||
return result;
|
||||
@ -70,11 +70,14 @@ pub const RocStr = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
// allocate space for a (big or small) RocStr, but put nothing in it yet
|
||||
pub fn allocate(length: usize, capacity: usize) RocStr {
|
||||
const result_is_big = capacity >= SMALL_STRING_SIZE;
|
||||
// allocate space for a (big or small) RocStr, but put nothing in it yet.
|
||||
// May have a larger capacity than the length.
|
||||
pub fn allocate(length: usize) RocStr {
|
||||
const element_width = 1;
|
||||
const result_is_big = length >= SMALL_STRING_SIZE;
|
||||
|
||||
if (result_is_big) {
|
||||
const capacity = utils.calculateCapacity(0, length, element_width);
|
||||
return RocStr.allocateBig(length, capacity);
|
||||
} else {
|
||||
var string = RocStr.empty();
|
||||
@ -91,25 +94,6 @@ pub const RocStr = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
// This takes ownership of the pointed-to bytes if they won't fit in a
|
||||
// small string, and returns a (pointer, len) tuple which points to them.
|
||||
pub fn withCapacity(length: usize) RocStr {
|
||||
const roc_str_size = @sizeOf(RocStr);
|
||||
|
||||
if (length < roc_str_size) {
|
||||
return RocStr.empty();
|
||||
} else {
|
||||
var new_bytes = utils.alloc(length, RocStr.alignment) catch unreachable;
|
||||
|
||||
var new_bytes_ptr: [*]u8 = @ptrCast([*]u8, &new_bytes);
|
||||
|
||||
return RocStr{
|
||||
.str_bytes = new_bytes_ptr,
|
||||
.str_len = length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq(self: RocStr, other: RocStr) bool {
|
||||
// If they are byte-for-byte equal, they're definitely equal!
|
||||
if (self.str_bytes == other.str_bytes and self.str_len == other.str_len and self.str_capacity == other.str_capacity) {
|
||||
@ -169,38 +153,43 @@ pub const RocStr = extern struct {
|
||||
pub fn reallocate(
|
||||
self: RocStr,
|
||||
new_length: usize,
|
||||
new_capacity: usize,
|
||||
) RocStr {
|
||||
const element_width = 1;
|
||||
const old_capacity = self.getCapacity();
|
||||
|
||||
if (self.str_bytes) |source_ptr| {
|
||||
if (self.isUnique() and !self.isSmallStr()) {
|
||||
const new_source = utils.unsafeReallocate(
|
||||
source_ptr,
|
||||
RocStr.alignment,
|
||||
old_capacity,
|
||||
new_capacity,
|
||||
element_width,
|
||||
);
|
||||
|
||||
return RocStr{ .str_bytes = new_source, .str_len = new_length, .str_capacity = new_capacity };
|
||||
}
|
||||
if (self.isSmallStr() or !self.isUnique()) {
|
||||
return self.reallocateFresh(new_length);
|
||||
}
|
||||
|
||||
return self.reallocateFresh(new_length, new_capacity);
|
||||
if (self.str_bytes) |source_ptr| {
|
||||
if (old_capacity > new_length) {
|
||||
var output = self;
|
||||
output.setLen(new_length);
|
||||
return output;
|
||||
}
|
||||
const new_capacity = utils.calculateCapacity(old_capacity, new_length, element_width);
|
||||
const new_source = utils.unsafeReallocate(
|
||||
source_ptr,
|
||||
RocStr.alignment,
|
||||
old_capacity,
|
||||
new_capacity,
|
||||
element_width,
|
||||
);
|
||||
|
||||
return RocStr{ .str_bytes = new_source, .str_len = new_length, .str_capacity = new_capacity };
|
||||
}
|
||||
return self.reallocateFresh(new_length);
|
||||
}
|
||||
|
||||
/// reallocate by explicitly making a new allocation and copying elements over
|
||||
pub fn reallocateFresh(
|
||||
fn reallocateFresh(
|
||||
self: RocStr,
|
||||
new_length: usize,
|
||||
new_capacity: usize,
|
||||
) RocStr {
|
||||
const old_length = self.len();
|
||||
const delta_length = new_length - old_length;
|
||||
|
||||
const result = RocStr.allocate(new_length, new_capacity);
|
||||
const result = RocStr.allocate(new_length);
|
||||
|
||||
// transfer the memory
|
||||
|
||||
@ -238,6 +227,14 @@ pub const RocStr = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setLen(self: *RocStr, length: usize) void {
|
||||
if (self.isSmallStr()) {
|
||||
self.asU8ptr()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000;
|
||||
} else {
|
||||
self.str_len = length;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getCapacity(self: RocStr) usize {
|
||||
if (self.isSmallStr()) {
|
||||
return SMALL_STR_MAX_LENGTH;
|
||||
@ -1387,7 +1384,7 @@ pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr {
|
||||
const bytes_len = string.len();
|
||||
const bytes_ptr = string.asU8ptr();
|
||||
|
||||
var ret_string = RocStr.allocate(count * bytes_len, count * bytes_len);
|
||||
var ret_string = RocStr.allocate(count * bytes_len);
|
||||
var ret_string_ptr = ret_string.asU8ptr();
|
||||
|
||||
var i: usize = 0;
|
||||
@ -1528,7 +1525,7 @@ fn strConcat(arg1: RocStr, arg2: RocStr) RocStr {
|
||||
} else {
|
||||
const combined_length = arg1.len() + arg2.len();
|
||||
|
||||
const result = arg1.reallocate(combined_length, combined_length);
|
||||
const result = arg1.reallocate(combined_length);
|
||||
|
||||
@memcpy(result.asU8ptr() + arg1.len(), arg2.asU8ptr(), arg2.len());
|
||||
|
||||
@ -1600,7 +1597,7 @@ fn strJoinWith(list: RocListStr, separator: RocStr) RocStr {
|
||||
// include size of the separator
|
||||
total_size += separator.len() * (len - 1);
|
||||
|
||||
var result = RocStr.allocate(total_size, total_size);
|
||||
var result = RocStr.allocate(total_size);
|
||||
var result_ptr = result.asU8ptr();
|
||||
|
||||
var offset: usize = 0;
|
||||
@ -2512,14 +2509,14 @@ test "capacity: big string" {
|
||||
var data = RocStr.init(data_bytes, data_bytes.len);
|
||||
defer data.deinit();
|
||||
|
||||
try expectEqual(data.getCapacity(), data_bytes.len);
|
||||
try expect(data.getCapacity() >= data_bytes.len);
|
||||
}
|
||||
|
||||
pub fn appendScalar(string: RocStr, scalar_u32: u32) callconv(.C) RocStr {
|
||||
const scalar = @intCast(u21, scalar_u32);
|
||||
const width = std.unicode.utf8CodepointSequenceLength(scalar) catch unreachable;
|
||||
|
||||
var output = string.reallocate(string.len() + width, string.len() + width);
|
||||
var output = string.reallocate(string.len() + width);
|
||||
var slice = output.asSliceWithCapacity();
|
||||
|
||||
_ = std.unicode.utf8Encode(scalar, slice[string.len() .. string.len() + width]) catch unreachable;
|
||||
@ -2587,17 +2584,21 @@ test "appendScalar: big 😀" {
|
||||
try expect(actual.eq(expected));
|
||||
}
|
||||
|
||||
pub fn reserve(string: RocStr, capacity: usize) callconv(.C) RocStr {
|
||||
if (capacity > string.getCapacity()) {
|
||||
// expand allocation but keep string length the same
|
||||
return string.reallocate(string.len(), capacity);
|
||||
} else {
|
||||
pub fn reserve(string: RocStr, spare: usize) callconv(.C) RocStr {
|
||||
const old_length = string.len();
|
||||
if (string.getCapacity() >= old_length + spare) {
|
||||
return string;
|
||||
} else {
|
||||
var output = string.reallocate(old_length + spare);
|
||||
output.setLen(old_length);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn withCapacity(capacity: usize) callconv(.C) RocStr {
|
||||
return RocStr.allocate(0, capacity);
|
||||
var str = RocStr.allocate(capacity);
|
||||
str.setLen(0);
|
||||
return str;
|
||||
}
|
||||
|
||||
pub fn getScalarUnsafe(string: RocStr, index: usize) callconv(.C) extern struct { bytesParsed: usize, scalar: u32 } {
|
||||
|
@ -213,6 +213,53 @@ inline fn decref_ptr_to_refcount(
|
||||
}
|
||||
}
|
||||
|
||||
// We follow roughly the [fbvector](https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md) when it comes to growing a RocList.
|
||||
// Here is [their growth strategy](https://github.com/facebook/folly/blob/3e0525988fd444201b19b76b390a5927c15cb697/folly/FBVector.h#L1128) for push_back:
|
||||
//
|
||||
// (1) initial size
|
||||
// Instead of growing to size 1 from empty, fbvector allocates at least
|
||||
// 64 bytes. You may still use reserve to reserve a lesser amount of
|
||||
// memory.
|
||||
// (2) 1.5x
|
||||
// For medium-sized vectors, the growth strategy is 1.5x. See the docs
|
||||
// for details.
|
||||
// This does not apply to very small or very large fbvectors. This is a
|
||||
// heuristic.
|
||||
//
|
||||
// In our case, we exposed allocate and reallocate, which will use a smart growth stategy.
|
||||
// We also expose allocateExact and reallocateExact for case where a specific number of elements is requested.
|
||||
|
||||
// calculateCapacity should only be called in cases the list will be growing.
|
||||
// requested_length should always be greater than old_capacity.
|
||||
pub inline fn calculateCapacity(
|
||||
old_capacity: usize,
|
||||
requested_length: usize,
|
||||
element_width: usize,
|
||||
) usize {
|
||||
// TODO: there are two adjustments that would likely lead to better results for Roc.
|
||||
// 1. Deal with the fact we allocate an extra u64 for refcount.
|
||||
// This may lead to allocating page size + 8 bytes.
|
||||
// That could mean allocating an entire page for 8 bytes of data which isn't great.
|
||||
// 2. Deal with the fact that we can request more than 1 element at a time.
|
||||
// fbvector assumes just appending 1 element at a time when using this algorithm.
|
||||
// As such, they will generally grow in a way that should better match certain memory multiple.
|
||||
// This is also the normal case for roc, but we could also grow by a much larger amount.
|
||||
// We may want to round to multiples of 2 or something similar.
|
||||
var new_capacity: usize = 0;
|
||||
if (element_width == 0) {
|
||||
return requested_length;
|
||||
} else if (old_capacity == 0) {
|
||||
new_capacity = 64 / element_width;
|
||||
} else if (old_capacity < 4096 / element_width) {
|
||||
new_capacity = old_capacity * 2;
|
||||
} else if (old_capacity > 4096 * 32 / element_width) {
|
||||
new_capacity = old_capacity * 2;
|
||||
} else {
|
||||
new_capacity = (old_capacity * 3 + 1) / 2;
|
||||
}
|
||||
return @maximum(new_capacity, requested_length);
|
||||
}
|
||||
|
||||
pub fn allocateWithRefcountC(
|
||||
data_bytes: usize,
|
||||
element_alignment: u32,
|
||||
@ -267,25 +314,6 @@ pub fn unsafeReallocate(
|
||||
return new_source;
|
||||
}
|
||||
|
||||
pub const RocResult = extern struct {
|
||||
bytes: ?[*]u8,
|
||||
|
||||
pub fn isOk(self: RocResult) bool {
|
||||
// assumptions
|
||||
//
|
||||
// - the tag is the first field
|
||||
// - the tag is usize bytes wide
|
||||
// - Ok has tag_id 1, because Err < Ok
|
||||
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.bytes));
|
||||
|
||||
return usizes[0] == 1;
|
||||
}
|
||||
|
||||
pub fn isErr(self: RocResult) bool {
|
||||
return !self.isOk();
|
||||
}
|
||||
};
|
||||
|
||||
pub const Ordering = enum(u8) {
|
||||
EQ = 0,
|
||||
GT = 1,
|
||||
|
@ -1,8 +1,34 @@
|
||||
interface Bool
|
||||
exposes [Bool, true, false, and, or, not, isEq, isNotEq]
|
||||
exposes [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
|
||||
imports []
|
||||
|
||||
Bool := [True, False]
|
||||
## A type that can be compared for total equality.
|
||||
##
|
||||
## Total equality means that all values of the type can be compared to each
|
||||
## other, and two values `a`, `b` are identical if and only if `isEq a b` is
|
||||
## `Bool.true`.
|
||||
##
|
||||
## Not all types support total equality. For example, an [F32] or [F64] can
|
||||
## be a `NaN` ([not a number](https://en.wikipedia.org/wiki/NaN)), and the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
## floating point standard specifies that two `NaN`s are never equal to each other.
|
||||
Eq has
|
||||
## Returns `Bool.true` if the two values are equal, and `Bool.false` otherwise.
|
||||
##
|
||||
## `a == b` is shorthand for `Bool.isEq a b`.
|
||||
##
|
||||
## When `isEq` is derived by the Roc compiler, values are compared via
|
||||
## structural equality. Structural equality works as follows:
|
||||
##
|
||||
## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal.
|
||||
## 2. Records are equal if all their fields are equal.
|
||||
## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
|
||||
## 4. [Num](Num#Num) values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `Bool.false`. See `Num.isNaN` for more about *NaN*.
|
||||
## 5. Functions can never be compared for structural equality. Roc cannot derive `isEq` for types that contain functions!
|
||||
isEq : a, a -> Bool | a has Eq
|
||||
|
||||
Bool := [True, False] has [Eq { isEq: boolIsEq }]
|
||||
|
||||
boolIsEq = \@Bool b1, @Bool b2 -> structuralEq b1 b2
|
||||
|
||||
## The boolean true value.
|
||||
true : Bool
|
||||
@ -67,25 +93,19 @@ or : Bool, Bool -> Bool
|
||||
## Returns `Bool.false` when given `Bool.true`, and vice versa.
|
||||
not : Bool -> Bool
|
||||
|
||||
## Returns `Bool.true` if the two values are *structurally equal*, and `Bool.false` otherwise.
|
||||
##
|
||||
## `a == b` is shorthand for `Bool.isEq a b`
|
||||
##
|
||||
## Structural equality works as follows:
|
||||
##
|
||||
## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal.
|
||||
## 2. Records are equal if all their fields are equal.
|
||||
## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
|
||||
## 4. [Num](Num#Num) values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `Bool.false`. See `Num.isNaN` for more about *NaN*.
|
||||
##
|
||||
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
|
||||
## accept arguments whose types contain functions.
|
||||
isEq : a, a -> Bool
|
||||
|
||||
## Calls [isEq] on the given values, then calls [not] on the result.
|
||||
##
|
||||
## `a != b` is shorthand for `Bool.isNotEq a b`
|
||||
##
|
||||
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
|
||||
## accept arguments whose types contain functions.
|
||||
isNotEq : a, a -> Bool
|
||||
isNotEq : a, a -> Bool | a has Eq
|
||||
isNotEq = \a, b -> structuralNotEq a b
|
||||
|
||||
# INTERNAL COMPILER USE ONLY: used to lower calls to `isEq` to structural
|
||||
# equality via the `Eq` low-level for derived types.
|
||||
structuralEq : a, a -> Bool
|
||||
|
||||
# INTERNAL COMPILER USE ONLY: used to lower calls to `isNotEq` to structural
|
||||
# inequality via the `NotEq` low-level for derived types.
|
||||
structuralNotEq : a, a -> Bool
|
||||
|
@ -18,10 +18,12 @@ interface Dict
|
||||
removeAll,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
Bool.{ Bool, Eq },
|
||||
Result.{ Result },
|
||||
List,
|
||||
Num.{ Nat },
|
||||
Str,
|
||||
Num.{ Nat, U64, U8 },
|
||||
Hash.{ Hasher },
|
||||
]
|
||||
|
||||
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
|
||||
@ -72,7 +74,9 @@ interface Dict
|
||||
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
|
||||
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
|
||||
## `fn dict1 == fn dict2` also being `Bool.true`, even if `fn` relies on the dictionary's ordering.
|
||||
Dict k v := List [Pair k v]
|
||||
Dict k v := List [Pair k v] has [Eq { isEq: dictEq }]
|
||||
|
||||
dictEq = \@Dict l1, @Dict l2 -> l1 == l2
|
||||
|
||||
## An empty dictionary.
|
||||
empty : Dict k v
|
||||
@ -81,7 +85,7 @@ empty = @Dict []
|
||||
withCapacity : Nat -> Dict k v
|
||||
withCapacity = \n -> @Dict (List.withCapacity n)
|
||||
|
||||
get : Dict k v, k -> Result v [KeyNotFound]*
|
||||
get : Dict k v, k -> Result v [KeyNotFound]* | k has Eq
|
||||
get = \@Dict list, needle ->
|
||||
when List.findFirst list (\Pair key _ -> key == needle) is
|
||||
Ok (Pair _ v) ->
|
||||
@ -94,7 +98,7 @@ walk : Dict k v, state, (state, k, v -> state) -> state
|
||||
walk = \@Dict list, initialState, transform ->
|
||||
List.walk list initialState (\state, Pair k v -> transform state k v)
|
||||
|
||||
insert : Dict k v, k, v -> Dict k v
|
||||
insert : Dict k v, k, v -> Dict k v | k has Eq
|
||||
insert = \@Dict list, k, v ->
|
||||
when List.findFirstIndex list (\Pair key _ -> key == k) is
|
||||
Err NotFound ->
|
||||
@ -109,7 +113,7 @@ len : Dict k v -> Nat
|
||||
len = \@Dict list ->
|
||||
List.len list
|
||||
|
||||
remove : Dict k v, k -> Dict k v
|
||||
remove : Dict k v, k -> Dict k v | k has Eq
|
||||
remove = \@Dict list, key ->
|
||||
when List.findFirstIndex list (\Pair k _ -> k == key) is
|
||||
Err NotFound ->
|
||||
@ -124,11 +128,7 @@ remove = \@Dict list, key ->
|
||||
|> @Dict
|
||||
|
||||
## Insert or remove a value in a Dict based on its presence
|
||||
update :
|
||||
Dict k v,
|
||||
k,
|
||||
([Present v, Missing] -> [Present v, Missing])
|
||||
-> Dict k v
|
||||
update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v | k has Eq
|
||||
update = \dict, key, alter ->
|
||||
possibleValue =
|
||||
get dict key
|
||||
@ -151,7 +151,7 @@ expect update empty "a" alterValue == single "a" Bool.false
|
||||
expect update (single "a" Bool.false) "a" alterValue == single "a" Bool.true
|
||||
expect update (single "a" Bool.true) "a" alterValue == empty
|
||||
|
||||
contains : Dict k v, k -> Bool
|
||||
contains : Dict k v, k -> Bool | k has Eq
|
||||
contains = \@Dict list, needle ->
|
||||
step = \_, Pair key _val ->
|
||||
if key == needle then
|
||||
@ -178,18 +178,18 @@ values = \@Dict list ->
|
||||
List.map list (\Pair _ v -> v)
|
||||
|
||||
# union : Dict k v, Dict k v -> Dict k v
|
||||
insertAll : Dict k v, Dict k v -> Dict k v
|
||||
insertAll : Dict k v, Dict k v -> Dict k v | k has Eq
|
||||
insertAll = \xs, @Dict ys ->
|
||||
List.walk ys xs (\state, Pair k v -> Dict.insertIfVacant state k v)
|
||||
|
||||
# intersection : Dict k v, Dict k v -> Dict k v
|
||||
keepShared : Dict k v, Dict k v -> Dict k v
|
||||
keepShared : Dict k v, Dict k v -> Dict k v | k has Eq
|
||||
keepShared = \@Dict xs, ys ->
|
||||
List.keepIf xs (\Pair k _ -> Dict.contains ys k)
|
||||
|> @Dict
|
||||
|
||||
# difference : Dict k v, Dict k v -> Dict k v
|
||||
removeAll : Dict k v, Dict k v -> Dict k v
|
||||
removeAll : Dict k v, Dict k v -> Dict k v | k has Eq
|
||||
removeAll = \xs, @Dict ys ->
|
||||
List.walk ys xs (\state, Pair k _ -> Dict.remove state k)
|
||||
|
||||
@ -202,9 +202,418 @@ insertFresh = \@Dict list, k, v ->
|
||||
|> List.append (Pair k v)
|
||||
|> @Dict
|
||||
|
||||
insertIfVacant : Dict k v, k, v -> Dict k v
|
||||
insertIfVacant : Dict k v, k, v -> Dict k v | k has Eq
|
||||
insertIfVacant = \dict, key, value ->
|
||||
if Dict.contains dict key then
|
||||
dict
|
||||
else
|
||||
Dict.insert dict key value
|
||||
|
||||
# We have decided not to expose the standard roc hashing algorithm.
|
||||
# This is to avoid external dependence and the need for versioning.
|
||||
# The current implementation is a form of [Wyhash final3](https://github.com/wangyi-fudan/wyhash/blob/a5995b98ebfa7bd38bfadc0919326d2e7aabb805/wyhash.h).
|
||||
# It is 64bit and little endian specific currently.
|
||||
# TODO: wyhash is slow for large keys, use something like cityhash if the keys are too long.
|
||||
# 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 } has [
|
||||
Hasher {
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
addI8,
|
||||
addI16,
|
||||
addI32,
|
||||
addI64,
|
||||
addI128,
|
||||
complete,
|
||||
},
|
||||
]
|
||||
|
||||
# unsafe primitive that does not perform a bounds check
|
||||
# TODO hide behind an InternalList.roc module
|
||||
listGetUnsafe : List a, Nat -> a
|
||||
|
||||
createLowLevelHasher : { seed ?U64 } -> LowLevelHasher
|
||||
createLowLevelHasher = \{ seed ? 0x526F_6352_616E_643F } ->
|
||||
@LowLevelHasher { originalSeed: seed, state: seed }
|
||||
|
||||
combineState : LowLevelHasher, { a : U64, b : U64, seed : U64, length : U64 } -> LowLevelHasher
|
||||
combineState = \@LowLevelHasher { originalSeed, state }, { a, b, seed, length } ->
|
||||
tmp = wymix (Num.bitwiseXor wyp1 a) (Num.bitwiseXor seed b)
|
||||
hash = wymix (Num.bitwiseXor wyp1 length) tmp
|
||||
|
||||
@LowLevelHasher { originalSeed, state: wymix state hash }
|
||||
|
||||
complete = \@LowLevelHasher { state } -> state
|
||||
|
||||
addI8 = \hasher, i8 ->
|
||||
addU8 hasher (Num.toU8 i8)
|
||||
addI16 = \hasher, i16 ->
|
||||
addU16 hasher (Num.toU16 i16)
|
||||
addI32 = \hasher, i32 ->
|
||||
addU32 hasher (Num.toU32 i32)
|
||||
addI64 = \hasher, i64 ->
|
||||
addU64 hasher (Num.toU64 i64)
|
||||
addI128 = \hasher, i128 ->
|
||||
addU128 hasher (Num.toU128 i128)
|
||||
|
||||
# These implementations hash each value individually with the seed and then mix
|
||||
# the resulting hash with the state. There are other options that may be faster
|
||||
# like using the output of the last hash as the seed to the current hash.
|
||||
# I am simply not sure the tradeoffs here. Theoretically this method is more sound.
|
||||
# Either way, the performance will be similar and we can change this later.
|
||||
addU8 = \@LowLevelHasher { originalSeed, state }, u8 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
p0 = Num.toU64 u8
|
||||
a =
|
||||
Num.shiftLeftBy p0 16
|
||||
|> Num.bitwiseOr (Num.shiftLeftBy p0 8)
|
||||
|> Num.bitwiseOr p0
|
||||
b = 0
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 1 }
|
||||
|
||||
addU16 = \@LowLevelHasher { originalSeed, state }, u16 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
p0 = Num.bitwiseAnd u16 0xFF |> Num.toU64
|
||||
p1 = Num.shiftRightZfBy u16 8 |> Num.toU64
|
||||
a =
|
||||
Num.shiftLeftBy p0 16
|
||||
|> Num.bitwiseOr (Num.shiftLeftBy p1 8)
|
||||
|> Num.bitwiseOr p1
|
||||
b = 0
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 2 }
|
||||
|
||||
addU32 = \@LowLevelHasher { originalSeed, state }, u32 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
p0 = Num.toU64 u32
|
||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p0
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b: a, seed, length: 4 }
|
||||
|
||||
addU64 = \@LowLevelHasher { originalSeed, state }, u64 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
p0 = Num.bitwiseAnd 0xFFFF_FFFF u64
|
||||
p1 = Num.shiftRightZfBy u64 32
|
||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p1
|
||||
b = Num.shiftLeftBy p1 32 |> Num.bitwiseOr p0
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 8 }
|
||||
|
||||
addU128 = \@LowLevelHasher { originalSeed, state }, u128 ->
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
lower = u128 |> Num.toU64
|
||||
upper = Num.shiftRightZfBy u128 64 |> Num.toU64
|
||||
p0 = Num.bitwiseAnd 0xFFFF_FFFF lower
|
||||
p1 = Num.shiftRightZfBy lower 32 |> Num.bitwiseAnd 0xFFFF_FFFF
|
||||
p2 = Num.bitwiseAnd 0xFFFF_FFFF upper
|
||||
p3 = Num.shiftRightZfBy upper 32 |> Num.bitwiseAnd 0xFFFF_FFFF
|
||||
a = Num.shiftLeftBy p0 32 |> Num.bitwiseOr p2
|
||||
b = Num.shiftLeftBy p3 32 |> Num.bitwiseOr p1
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a, b, seed, length: 16 }
|
||||
|
||||
addBytes : LowLevelHasher, List U8 -> LowLevelHasher
|
||||
addBytes = \@LowLevelHasher { originalSeed, state }, list ->
|
||||
length = List.len list
|
||||
seed = Num.bitwiseXor originalSeed wyp0
|
||||
abs =
|
||||
if length <= 16 then
|
||||
if length >= 4 then
|
||||
x = Num.shiftRightZfBy length 3 |> Num.shiftLeftBy 2
|
||||
a = Num.bitwiseOr (wyr4 list 0 |> Num.shiftLeftBy 32) (wyr4 list x)
|
||||
b =
|
||||
(wyr4 list (Num.subWrap length 4) |> Num.shiftLeftBy 32)
|
||||
|> Num.bitwiseOr (wyr4 list (Num.subWrap length 4 |> Num.subWrap x))
|
||||
|
||||
{ a, b, seed }
|
||||
else if length > 0 then
|
||||
{ a: wyr3 list 0 length, b: 0, seed }
|
||||
else
|
||||
{ a: 0, b: 0, seed }
|
||||
else if length <= 48 then
|
||||
hashBytesHelper16 seed list 0 length
|
||||
else
|
||||
hashBytesHelper48 seed seed seed list 0 length
|
||||
|
||||
combineState (@LowLevelHasher { originalSeed, state }) { a: abs.a, b: abs.b, seed: abs.seed, length: Num.toU64 length }
|
||||
|
||||
hashBytesHelper48 : U64, U64, U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
|
||||
hashBytesHelper48 = \seed, see1, see2, list, index, remaining ->
|
||||
newSeed = wymix (Num.bitwiseXor (wyr8 list index) wyp1) (Num.bitwiseXor (wyr8 list (Num.addWrap index 8)) seed)
|
||||
newSee1 = wymix (Num.bitwiseXor (wyr8 list (Num.addWrap index 16)) wyp2) (Num.bitwiseXor (wyr8 list (Num.addWrap index 24)) see1)
|
||||
newSee2 = wymix (Num.bitwiseXor (wyr8 list (Num.addWrap index 32)) wyp3) (Num.bitwiseXor (wyr8 list (Num.addWrap index 40)) see2)
|
||||
newRemaining = Num.subWrap remaining 48
|
||||
newIndex = Num.addWrap index 48
|
||||
|
||||
if newRemaining > 48 then
|
||||
hashBytesHelper48 newSeed newSee1 newSee2 list newIndex newRemaining
|
||||
else if newRemaining > 16 then
|
||||
finalSeed = Num.bitwiseXor newSee2 (Num.bitwiseXor newSee1 newSeed)
|
||||
|
||||
hashBytesHelper16 finalSeed list newIndex newRemaining
|
||||
else
|
||||
finalSeed = Num.bitwiseXor newSee2 (Num.bitwiseXor newSee1 newSeed)
|
||||
|
||||
{ a: wyr8 list (Num.subWrap newRemaining 16 |> Num.addWrap newIndex), b: wyr8 list (Num.subWrap newRemaining 8 |> Num.addWrap newIndex), seed: finalSeed }
|
||||
|
||||
hashBytesHelper16 : U64, List U8, Nat, Nat -> { a : U64, b : U64, seed : U64 }
|
||||
hashBytesHelper16 = \seed, list, index, remaining ->
|
||||
newSeed = wymix (Num.bitwiseXor (wyr8 list index) wyp1) (Num.bitwiseXor (wyr8 list (Num.addWrap index 8)) seed)
|
||||
newRemaining = Num.subWrap remaining 16
|
||||
newIndex = Num.addWrap index 16
|
||||
|
||||
if newRemaining <= 16 then
|
||||
{ a: wyr8 list (Num.subWrap newRemaining 16 |> Num.addWrap newIndex), b: wyr8 list (Num.subWrap newRemaining 8 |> Num.addWrap newIndex), seed: newSeed }
|
||||
else
|
||||
hashBytesHelper16 newSeed list newIndex newRemaining
|
||||
|
||||
wyp0 : U64
|
||||
wyp0 = 0xa0761d6478bd642f
|
||||
wyp1 : U64
|
||||
wyp1 = 0xe7037ed1a0b428db
|
||||
wyp2 : U64
|
||||
wyp2 = 0x8ebc6af09c88c6e3
|
||||
wyp3 : U64
|
||||
wyp3 = 0x589965cc75374cc3
|
||||
|
||||
wymix : U64, U64 -> U64
|
||||
wymix = \a, b ->
|
||||
{ lower, upper } = wymum a b
|
||||
|
||||
Num.bitwiseXor lower upper
|
||||
|
||||
wymum : U64, U64 -> { lower : U64, upper : U64 }
|
||||
wymum = \a, b ->
|
||||
r = Num.toU128 a * Num.toU128 b
|
||||
lower = Num.toU64 r
|
||||
upper = Num.shiftRightZfBy r 64 |> Num.toU64
|
||||
|
||||
# This is the more robust form.
|
||||
# { lower: Num.bitwiseXor a lower, upper: Num.bitwiseXor b upper }
|
||||
{ lower, upper }
|
||||
|
||||
# Get the next 8 bytes as a U64
|
||||
wyr8 : List U8, Nat -> U64
|
||||
wyr8 = \list, index ->
|
||||
# With seamless slices and Num.fromBytes, this should be possible to make faster and nicer.
|
||||
# It would also deal with the fact that on big endian systems we want to invert the order here.
|
||||
# Without seamless slices, we would need fromBytes to take an index.
|
||||
p1 = listGetUnsafe list index |> Num.toU64
|
||||
p2 = listGetUnsafe list (Num.addWrap index 1) |> Num.toU64
|
||||
p3 = listGetUnsafe list (Num.addWrap index 2) |> Num.toU64
|
||||
p4 = listGetUnsafe list (Num.addWrap index 3) |> Num.toU64
|
||||
p5 = listGetUnsafe list (Num.addWrap index 4) |> Num.toU64
|
||||
p6 = listGetUnsafe list (Num.addWrap index 5) |> Num.toU64
|
||||
p7 = listGetUnsafe list (Num.addWrap index 6) |> Num.toU64
|
||||
p8 = listGetUnsafe list (Num.addWrap index 7) |> Num.toU64
|
||||
a = Num.bitwiseOr p1 (Num.shiftLeftBy p2 8)
|
||||
b = Num.bitwiseOr (Num.shiftLeftBy p3 16) (Num.shiftLeftBy p4 24)
|
||||
c = Num.bitwiseOr (Num.shiftLeftBy p5 32) (Num.shiftLeftBy p6 40)
|
||||
d = Num.bitwiseOr (Num.shiftLeftBy p7 48) (Num.shiftLeftBy p8 56)
|
||||
|
||||
Num.bitwiseOr (Num.bitwiseOr a b) (Num.bitwiseOr c d)
|
||||
|
||||
# Get the next 4 bytes as a U64 with some shifting.
|
||||
wyr4 : List U8, Nat -> U64
|
||||
wyr4 = \list, index ->
|
||||
p1 = listGetUnsafe list index |> Num.toU64
|
||||
p2 = listGetUnsafe list (Num.addWrap index 1) |> Num.toU64
|
||||
p3 = listGetUnsafe list (Num.addWrap index 2) |> Num.toU64
|
||||
p4 = listGetUnsafe list (Num.addWrap index 3) |> Num.toU64
|
||||
a = Num.bitwiseOr p1 (Num.shiftLeftBy p2 8)
|
||||
b = Num.bitwiseOr (Num.shiftLeftBy p3 16) (Num.shiftLeftBy p4 24)
|
||||
|
||||
Num.bitwiseOr a b
|
||||
|
||||
# Get the next K bytes with some shifting.
|
||||
# K must be 3 or less.
|
||||
wyr3 : List U8, Nat, Nat -> U64
|
||||
wyr3 = \list, index, k ->
|
||||
# ((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1]
|
||||
p1 = listGetUnsafe list index |> Num.toU64
|
||||
p2 = listGetUnsafe list (Num.shiftRightZfBy k 1 |> Num.addWrap index) |> Num.toU64
|
||||
p3 = listGetUnsafe list (Num.subWrap k 1 |> Num.addWrap index) |> Num.toU64
|
||||
a = Num.bitwiseOr (Num.shiftLeftBy p1 16) (Num.shiftLeftBy p2 8)
|
||||
|
||||
Num.bitwiseOr a p3
|
||||
|
||||
# TODO: would be great to have table driven expects for this.
|
||||
# Would also be great to have some sort of property based hasher
|
||||
# where we can compare `addU*` functions to the `addBytes` function.
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes []
|
||||
|> complete
|
||||
|
||||
hash == 0x1C3F_F8BF_07F9_B0B3
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0x42]
|
||||
|> complete
|
||||
|
||||
hash == 0x8F9F_0A1E_E06F_0D52
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU8 0x42
|
||||
|> complete
|
||||
|
||||
hash == 0x8F9F_0A1E_E06F_0D52
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0xFF, 0xFF]
|
||||
|> complete
|
||||
|
||||
hash == 0x86CC_8B71_563F_F084
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU16 0xFFFF
|
||||
|> complete
|
||||
|
||||
hash == 0x86CC_8B71_563F_F084
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0x36, 0xA7]
|
||||
|> complete
|
||||
|
||||
hash == 0xD1A5_0F24_2536_84F8
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU16 0xA736
|
||||
|> complete
|
||||
|
||||
hash == 0xD1A5_0F24_2536_84F8
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0x00, 0x00, 0x00, 0x00]
|
||||
|> complete
|
||||
|
||||
hash == 0x3762_ACB1_7604_B541
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU32 0x0000_0000
|
||||
|> complete
|
||||
|
||||
hash == 0x3762_ACB1_7604_B541
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0xA9, 0x2F, 0xEE, 0x21]
|
||||
|> complete
|
||||
|
||||
hash == 0x20F3_3FD7_D32E_C7A9
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU32 0x21EE_2FA9
|
||||
|> complete
|
||||
|
||||
hash == 0x20F3_3FD7_D32E_C7A9
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes [0x5D, 0x66, 0xB1, 0x8F, 0x68, 0x44, 0xC7, 0x03, 0xE1, 0xDD, 0x23, 0x34, 0xBB, 0x9A, 0x42, 0xA7]
|
||||
|> complete
|
||||
|
||||
hash == 0xA16F_DDAA_C167_74C7
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addU128 0xA742_9ABB_3423_DDE1_03C7_4468_8FB1_665D
|
||||
|> complete
|
||||
|
||||
hash == 0xA16F_DDAA_C167_74C7
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashStrBytes "abcdefghijklmnopqrstuvwxyz"
|
||||
|> complete
|
||||
|
||||
hash == 0xBEE0_A8FD_E990_D285
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashStrBytes "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|> complete
|
||||
|
||||
hash == 0xB3C5_8528_9D82_A6EF
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashStrBytes "1234567890123456789012345678901234567890123456789012345678901234567890"
|
||||
|> complete
|
||||
|
||||
hash == 0xDB6B_7997_7A55_BA03
|
||||
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> addBytes (List.repeat 0x77 100)
|
||||
|> complete
|
||||
|
||||
hash == 0x171F_EEE2_B764_8E5E
|
||||
|
||||
# Note, had to specify u8 in the lists below to avoid ability type resolution error.
|
||||
# Apparently it won't pick the default integer.
|
||||
expect
|
||||
hash =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered [8u8, 82u8, 3u8, 8u8, 24u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash == 0x999F_B530_3529_F17D
|
||||
|
||||
expect
|
||||
hash1 =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered ([0u8, 1u8, 2u8, 3u8, 4u8]) List.walk
|
||||
|> complete
|
||||
|
||||
hash2 =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered [4u8, 3u8, 2u8, 1u8, 0u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash1 == hash2
|
||||
|
||||
expect
|
||||
hash1 =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered [0u8, 1u8, 2u8, 3u8, 4u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash2 =
|
||||
createLowLevelHasher {}
|
||||
|> Hash.hashUnordered [4u8, 3u8, 2u8, 1u8, 0u8, 0u8] List.walk
|
||||
|> complete
|
||||
|
||||
hash1 != hash2
|
||||
|
@ -17,6 +17,7 @@ interface Hash
|
||||
complete,
|
||||
hashStrBytes,
|
||||
hashList,
|
||||
hashUnordered,
|
||||
] imports [
|
||||
List,
|
||||
Str,
|
||||
@ -75,10 +76,33 @@ Hasher has
|
||||
|
||||
## Adds a string into a [Hasher] by hashing its UTF-8 bytes.
|
||||
hashStrBytes = \hasher, s ->
|
||||
Str.walkUtf8WithIndex s hasher \accumHasher, byte, _ ->
|
||||
addU8 accumHasher byte
|
||||
addBytes hasher (Str.toUtf8 s)
|
||||
|
||||
## Adds a list of [Hash]able elements to a [Hasher] by hashing each element.
|
||||
hashList = \hasher, lst ->
|
||||
List.walk lst hasher \accumHasher, elem ->
|
||||
hash accumHasher elem
|
||||
|
||||
## Adds a container of [Hash]able elements to a [Hasher] by hashing each element.
|
||||
## The container is iterated using the walk method passed in.
|
||||
## The order of the elements does not affect the final hash.
|
||||
hashUnordered = \hasher, container, walk ->
|
||||
walk
|
||||
container
|
||||
0
|
||||
(\accum, elem ->
|
||||
x =
|
||||
# Note, we intentionally copy the hasher in every iteration.
|
||||
# Having the same base state is required for unordered hashing.
|
||||
hasher
|
||||
|> hash elem
|
||||
|> complete
|
||||
nextAccum = Num.addWrap accum x
|
||||
|
||||
if nextAccum < accum then
|
||||
# we don't want to lose a bit of entropy on overflow, so add it back in.
|
||||
Num.addWrap nextAccum 1
|
||||
else
|
||||
nextAccum
|
||||
)
|
||||
|> \accum -> addU64 hasher accum
|
||||
|
@ -33,7 +33,7 @@ interface Json
|
||||
F64,
|
||||
Dec,
|
||||
},
|
||||
Bool.{ Bool },
|
||||
Bool.{ Bool, Eq },
|
||||
Result,
|
||||
]
|
||||
|
||||
|
@ -29,6 +29,8 @@ interface List
|
||||
map3,
|
||||
product,
|
||||
walkUntil,
|
||||
walkFrom,
|
||||
walkFromUntil,
|
||||
range,
|
||||
sortWith,
|
||||
drop,
|
||||
@ -62,9 +64,10 @@ interface List
|
||||
sortDesc,
|
||||
reserve,
|
||||
walkBackwardsUntil,
|
||||
countIf,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
Bool.{ Bool, Eq },
|
||||
Result.{ Result },
|
||||
Num.{ Nat, Num, Int },
|
||||
]
|
||||
@ -353,7 +356,7 @@ join = \lists ->
|
||||
|
||||
List.walk lists (List.withCapacity totalLength) (\state, list -> List.concat state list)
|
||||
|
||||
contains : List a, a -> Bool
|
||||
contains : List a, a -> Bool | a has Eq
|
||||
contains = \list, needle ->
|
||||
List.any list (\x -> x == needle)
|
||||
|
||||
@ -442,6 +445,22 @@ walkBackwardsUntil = \list, initial, func ->
|
||||
Continue new -> new
|
||||
Break new -> new
|
||||
|
||||
## Walks to the end of the list from a specified starting index
|
||||
walkFrom : List elem, Nat, state, (state, elem -> state) -> state
|
||||
walkFrom = \list, index, state, func ->
|
||||
walkHelp : _, _ -> [Continue _, Break []]
|
||||
walkHelp = \currentState, element -> Continue (func currentState element)
|
||||
|
||||
when List.iterHelp list state walkHelp index (List.len list) is
|
||||
Continue new -> new
|
||||
|
||||
## A combination of [List.walkFrom] and [List.walkUntil]
|
||||
walkFromUntil : List elem, Nat, state, (state, elem -> [Continue state, Break state]) -> state
|
||||
walkFromUntil = \list, index, state, func ->
|
||||
when List.iterHelp list state func index (List.len list) is
|
||||
Continue new -> new
|
||||
Break new -> new
|
||||
|
||||
sum : List (Num a) -> Num a
|
||||
sum = \list ->
|
||||
List.walk list 0 Num.add
|
||||
@ -528,6 +547,18 @@ dropIf : List a, (a -> Bool) -> List a
|
||||
dropIf = \list, predicate ->
|
||||
List.keepIf list (\e -> Bool.not (predicate e))
|
||||
|
||||
## Run the given function on each element of a list, and return the
|
||||
## number of elements for which the function returned `Bool.true`.
|
||||
countIf : List a, (a -> Bool) -> Nat
|
||||
countIf = \list, predicate ->
|
||||
walkState = \state, elem ->
|
||||
if predicate elem then
|
||||
state + 1
|
||||
else
|
||||
state
|
||||
|
||||
List.walk list 0 walkState
|
||||
|
||||
## 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.
|
||||
##
|
||||
@ -890,7 +921,7 @@ intersperse = \list, sep ->
|
||||
## is considered to "start with" an empty list.
|
||||
##
|
||||
## If the first list is empty, this only returns `Bool.true` if the second list is empty.
|
||||
startsWith : List elem, List elem -> Bool
|
||||
startsWith : List elem, List elem -> Bool | elem has Eq
|
||||
startsWith = \list, prefix ->
|
||||
# TODO once we have seamless slices, verify that this wouldn't
|
||||
# have better performance with a function like List.compareSublists
|
||||
@ -902,7 +933,7 @@ startsWith = \list, prefix ->
|
||||
## is considered to "end with" an empty list.
|
||||
##
|
||||
## If the first list is empty, this only returns `Bool.true` if the second list is empty.
|
||||
endsWith : List elem, List elem -> Bool
|
||||
endsWith : List elem, List elem -> Bool | elem has Eq
|
||||
endsWith = \list, suffix ->
|
||||
# TODO once we have seamless slices, verify that this wouldn't
|
||||
# have better performance with a function like List.compareSublists
|
||||
@ -931,7 +962,7 @@ split = \elements, userSplitIndex ->
|
||||
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
##
|
||||
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] }
|
||||
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]*
|
||||
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* | elem has Eq
|
||||
splitFirst = \list, delimiter ->
|
||||
when List.findFirstIndex list (\elem -> elem == delimiter) is
|
||||
Ok index ->
|
||||
@ -946,7 +977,7 @@ splitFirst = \list, delimiter ->
|
||||
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
##
|
||||
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] }
|
||||
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]*
|
||||
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* | elem has Eq
|
||||
splitLast = \list, delimiter ->
|
||||
when List.findLastIndex list (\elem -> elem == delimiter) is
|
||||
Ok index ->
|
||||
|
@ -793,7 +793,7 @@ div : Frac a, Frac a -> Frac a
|
||||
|
||||
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]*
|
||||
divChecked = \a, b ->
|
||||
if b == 0 then
|
||||
if Num.isZero b then
|
||||
Err DivByZero
|
||||
else
|
||||
Ok (Num.div a b)
|
||||
@ -802,7 +802,7 @@ divCeil : Int a, Int a -> Int a
|
||||
|
||||
divCeilChecked : Int a, Int a -> Result (Int a) [DivByZero]*
|
||||
divCeilChecked = \a, b ->
|
||||
if b == 0 then
|
||||
if Num.isZero b then
|
||||
Err DivByZero
|
||||
else
|
||||
Ok (Num.divCeil a b)
|
||||
@ -827,7 +827,7 @@ divTrunc : Int a, Int a -> Int a
|
||||
|
||||
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]*
|
||||
divTruncChecked = \a, b ->
|
||||
if b == 0 then
|
||||
if Num.isZero b then
|
||||
Err DivByZero
|
||||
else
|
||||
Ok (Num.divTrunc a b)
|
||||
@ -847,7 +847,7 @@ rem : Int a, Int a -> Int a
|
||||
|
||||
remChecked : Int a, Int a -> Result (Int a) [DivByZero]*
|
||||
remChecked = \a, b ->
|
||||
if b == 0 then
|
||||
if Num.isZero b then
|
||||
Err DivByZero
|
||||
else
|
||||
Ok (Num.rem a b)
|
||||
@ -1223,19 +1223,19 @@ toU64 : Int * -> U64
|
||||
toU128 : Int * -> U128
|
||||
|
||||
## Convert an [Int] to a [Nat]. If the given number doesn't fit in [Nat], it will be truncated.
|
||||
## Since #Nat has a different maximum number depending on the system you're building
|
||||
## Since [Nat] has a different maximum number depending on the system you're building
|
||||
## for, this may give a different answer on different systems.
|
||||
##
|
||||
## For example, on a 32-bit system, #Num.maxNat will return the same answer as
|
||||
## [Num.maxU32]. This means that calling `Num.toNat 9_000_000_000` on a 32-bit
|
||||
## system will return [Num.maxU32] instead of 9 billion, because 9 billion is
|
||||
## higher than [Num.maxU32] and will not fit in a [Nat] on a 32-bit system.
|
||||
## For example, on a 32-bit system, `Num.maxNat` will return the same answer as
|
||||
## `Num.maxU32`. This means that calling `Num.toNat 9_000_000_000` on a 32-bit
|
||||
## system will return `Num.maxU32` instead of 9 billion, because 9 billion is
|
||||
## higher than `Num.maxU32` and will not fit in a [Nat] on a 32-bit system.
|
||||
##
|
||||
## However, calling `Num.toNat 9_000_000_000` on a 64-bit system will return
|
||||
## the #Nat value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
|
||||
## hold up to [Num.maxU64], and 9_000_000_000 is lower than [Num.maxU64].
|
||||
## the [Nat] value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
|
||||
## hold up to `Num.maxU64`, and 9_000_000_000 is lower than `Num.maxU64`.
|
||||
##
|
||||
## To convert a [Frac] to a [Nat], first call either #Num.round, #Num.ceil, or [Num.floor]
|
||||
## To convert a [Frac] to a [Nat], first call either `Num.round`, `Num.ceil`, or `Num.floor`
|
||||
## on it, then call this on the resulting [Int].
|
||||
toNat : Int * -> Nat
|
||||
|
||||
|
@ -14,9 +14,11 @@ interface Set
|
||||
intersection,
|
||||
difference,
|
||||
]
|
||||
imports [List, Bool.{ Bool }, Dict.{ Dict }, Num.{ Nat }]
|
||||
imports [List, Bool.{ Bool, Eq }, Dict.{ Dict }, Num.{ Nat }]
|
||||
|
||||
Set k := Dict.Dict k {}
|
||||
Set k := Dict.Dict k {} has [Eq { isEq: setEq }]
|
||||
|
||||
setEq = \@Set d1, @Set d2 -> d1 == d2
|
||||
|
||||
fromDict : Dict k {} -> Set k
|
||||
fromDict = \dict -> @Set dict
|
||||
@ -35,7 +37,7 @@ single = \key ->
|
||||
## Make sure never to insert a *NaN* to a [Set]! Because *NaN* is defined to be
|
||||
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
|
||||
## retrieved or removed from the [Set].
|
||||
insert : Set k, k -> Set k
|
||||
insert : Set k, k -> Set k | k has Eq
|
||||
insert = \@Set dict, key ->
|
||||
dict
|
||||
|> Dict.insert key {}
|
||||
@ -75,11 +77,11 @@ expect
|
||||
actual == 3
|
||||
|
||||
## Drops the given element from the set.
|
||||
remove : Set k, k -> Set k
|
||||
remove : Set k, k -> Set k | k has Eq
|
||||
remove = \@Set dict, key ->
|
||||
@Set (Dict.remove dict key)
|
||||
|
||||
contains : Set k, k -> Bool
|
||||
contains : Set k, k -> Bool | k has Eq
|
||||
contains = \set, key ->
|
||||
set
|
||||
|> Set.toDict
|
||||
@ -89,21 +91,21 @@ toList : Set k -> List k
|
||||
toList = \@Set dict ->
|
||||
Dict.keys dict
|
||||
|
||||
fromList : List k -> Set k
|
||||
fromList : List k -> Set k | k has Eq
|
||||
fromList = \list ->
|
||||
initial = @Set (Dict.withCapacity (List.len list))
|
||||
|
||||
List.walk list initial \set, key -> Set.insert set key
|
||||
|
||||
union : Set k, Set k -> Set k
|
||||
union : Set k, Set k -> Set k | k has Eq
|
||||
union = \@Set dict1, @Set dict2 ->
|
||||
@Set (Dict.insertAll dict1 dict2)
|
||||
|
||||
intersection : Set k, Set k -> Set k
|
||||
intersection : Set k, Set k -> Set k | k has Eq
|
||||
intersection = \@Set dict1, @Set dict2 ->
|
||||
@Set (Dict.keepShared dict1 dict2)
|
||||
|
||||
difference : Set k, Set k -> Set k
|
||||
difference : Set k, Set k -> Set k | k has Eq
|
||||
difference = \@Set dict1, @Set dict2 ->
|
||||
@Set (Dict.removeAll dict1 dict2)
|
||||
|
||||
|
@ -47,7 +47,7 @@ interface Str
|
||||
withPrefix,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
Bool.{ Bool, Eq },
|
||||
Result.{ Result },
|
||||
List,
|
||||
Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
|
||||
@ -74,7 +74,7 @@ interface Str
|
||||
## programming, and "extended grapheme cluster" is a mouthful, in Roc we use the
|
||||
## term "grapheme" as a shorthand for the more precise "extended grapheme cluster."
|
||||
##
|
||||
## You can get the number of graphemes in a string by calling [Str.countGraphemes] on it:
|
||||
## You can get the number of graphemes in a string by calling `Str.countGraphemes` on it:
|
||||
##
|
||||
## Str.countGraphemes "Roc!"
|
||||
## Str.countGraphemes "折り紙"
|
||||
@ -140,40 +140,44 @@ Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
|
||||
|
||||
## Returns `Bool.true` if the string is empty, and `Bool.false` otherwise.
|
||||
##
|
||||
## >>> Str.isEmpty "hi!"
|
||||
##
|
||||
## >>> Str.isEmpty ""
|
||||
## expect Str.isEmpty "hi!" == Bool.false
|
||||
## expect Str.isEmpty "" == Bool.true
|
||||
isEmpty : Str -> Bool
|
||||
|
||||
## Concatenate two [Str] values together.
|
||||
##
|
||||
## expect Str.concat "Hello" "World" == "HelloWorld"
|
||||
concat : Str, Str -> Str
|
||||
|
||||
## Returns a string of the specified capacity without any content
|
||||
## Returns a [Str] of the specified capacity [Num] without any content
|
||||
withCapacity : Nat -> Str
|
||||
|
||||
## Combine a list of strings into a single string, with a separator
|
||||
## string in between each.
|
||||
## Combine a [List] of [Str] into a single [Str], with a separator
|
||||
## [Str] in between each.
|
||||
##
|
||||
## >>> Str.joinWith ["one", "two", "three"] ", "
|
||||
## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
|
||||
## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
|
||||
joinWith : List Str, Str -> Str
|
||||
|
||||
## Split a string around a separator.
|
||||
## Split a [Str] around a separator. Passing `""` for the separator is not
|
||||
## useful; it returns the original string wrapped in a list. To split a string
|
||||
## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
|
||||
##
|
||||
## >>> Str.split "1,2,3" ","
|
||||
##
|
||||
## Passing `""` for the separator is not useful; it returns the original string
|
||||
## wrapped in a list.
|
||||
##
|
||||
## >>> Str.split "1,2,3" ""
|
||||
##
|
||||
## To split a string into its individual graphemes, use `Str.graphemes`
|
||||
## expect Str.split "1,2,3" "," == ["1","2","3"]
|
||||
## expect Str.split "1,2,3" "" == ["1,2,3"]
|
||||
split : Str, Str -> List Str
|
||||
|
||||
## Repeat a given [Str] value [Nat] times.
|
||||
##
|
||||
## expect Str.repeat ">" 3 == ">>>"
|
||||
repeat : Str, Nat -> Str
|
||||
|
||||
## Count the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
|
||||
## in the string.
|
||||
##
|
||||
## Str.countGraphemes "Roc!" # 4
|
||||
## Str.countGraphemes "七巧板" # 3
|
||||
## Str.countGraphemes "üïä" # 1
|
||||
## expect Str.countGraphemes "Roc!" == 4
|
||||
## expect Str.countGraphemes "七巧板" == 9
|
||||
## expect Str.countGraphemes "üïä" == 4
|
||||
countGraphemes : Str -> Nat
|
||||
|
||||
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
|
||||
@ -182,7 +186,7 @@ countGraphemes : Str -> Nat
|
||||
## If the given [Str] is empty, or if the given [U32] is not a valid
|
||||
## code point, this will return `Bool.false`.
|
||||
##
|
||||
## **Performance Note:** This runs slightly faster than [Str.startsWith], so
|
||||
## **Performance Note:** This runs slightly faster than `Str.startsWith`, so
|
||||
## if you want to check whether a string begins with something that's representable
|
||||
## in a single code point, you can use (for example) `Str.startsWithScalar '鹏'`
|
||||
## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.)
|
||||
@ -193,25 +197,25 @@ countGraphemes : Str -> Nat
|
||||
startsWithScalar : Str, U32 -> Bool
|
||||
|
||||
## Return a [List] of the [unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value)
|
||||
## in the given string.
|
||||
## in the given string. Strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
|
||||
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).
|
||||
##
|
||||
## (Strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
|
||||
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).)
|
||||
## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
|
||||
toScalars : Str -> List U32
|
||||
|
||||
## Return a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
|
||||
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
|
||||
## see [Str.split].)
|
||||
## To split the string into a [List] of smaller [Str] values instead of [U8] values,
|
||||
## see `Str.split`.
|
||||
##
|
||||
## >>> Str.toUtf8 "👩👩👦👦"
|
||||
##
|
||||
## >>> Str.toUtf8 "Roc"
|
||||
##
|
||||
## >>> Str.toUtf8 "鹏"
|
||||
##
|
||||
## >>> Str.toUtf8 "🐦"
|
||||
## expect Str.toUtf8 "鹏" == [233, 185, 143]
|
||||
## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
|
||||
toUtf8 : Str -> List U8
|
||||
|
||||
## Encode a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
|
||||
## into a [Str]
|
||||
##
|
||||
## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
|
||||
## expect Str.fromUtf8 [0xb0] == Err (BadUtf8 InvalidStartByte 0)
|
||||
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]*
|
||||
fromUtf8 = \bytes ->
|
||||
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
|
||||
@ -221,6 +225,10 @@ fromUtf8 = \bytes ->
|
||||
else
|
||||
Err (BadUtf8 result.dProblemCode result.aByteIndex)
|
||||
|
||||
## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
|
||||
## into a [Str]
|
||||
##
|
||||
## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi"
|
||||
fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]*
|
||||
fromUtf8Range = \bytes, config ->
|
||||
if config.start + config.count <= List.len bytes then
|
||||
@ -242,64 +250,210 @@ FromUtf8Result : {
|
||||
|
||||
fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result
|
||||
|
||||
## Check if the given [Str] starts with a value.
|
||||
##
|
||||
## expect Str.startsWith "ABC" "A" == Bool.true
|
||||
## expect Str.startsWith "ABC" "X" == Bool.false
|
||||
startsWith : Str, Str -> Bool
|
||||
|
||||
## Check if the given [Str] ends with a value.
|
||||
##
|
||||
## expect Str.endsWith "ABC" "C" == Bool.true
|
||||
## expect Str.endsWith "ABC" "X" == Bool.false
|
||||
endsWith : Str, Str -> Bool
|
||||
|
||||
## Return the string with any blank spaces removed from both the beginning
|
||||
## Return the [Str] with all whitespace removed from both the beginning
|
||||
## as well as the end.
|
||||
##
|
||||
## expect Str.trim " Hello \n\n" == "Hello"
|
||||
trim : Str -> Str
|
||||
|
||||
## Return the [Str] with all whitespace removed from the beginning.
|
||||
##
|
||||
## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
|
||||
trimLeft : Str -> Str
|
||||
|
||||
## Return the [Str] with all whitespace removed from the end.
|
||||
##
|
||||
## expect Str.trimRight " Hello \n\n" == " Hello"
|
||||
trimRight : Str -> Str
|
||||
|
||||
## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal
|
||||
## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
|
||||
##
|
||||
## expect Str.toDec "10" == Ok 10dec
|
||||
## expect Str.toDec "-0.25" == Ok -0.25dec
|
||||
## expect Str.toDec "not a number" == Err InvalidNumStr
|
||||
toDec : Str -> Result Dec [InvalidNumStr]*
|
||||
toDec = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a [F64]. A [F64] value is a 64-bit
|
||||
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
|
||||
## specified with a `f64` suffix.
|
||||
##
|
||||
## expect Str.toF64 "0.10" == Ok 0.10f64
|
||||
## expect Str.toF64 "not a number" == Err InvalidNumStr
|
||||
toF64 : Str -> Result F64 [InvalidNumStr]*
|
||||
toF64 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a [F32].A [F32] value is a 32-bit
|
||||
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
|
||||
## specified with a `f32` suffix.
|
||||
##
|
||||
## expect Str.toF32 "0.10" == Ok 0.10f32
|
||||
## expect Str.toF32 "not a number" == Err InvalidNumStr
|
||||
toF32 : Str -> Result F32 [InvalidNumStr]*
|
||||
toF32 = \string -> strToNumHelp string
|
||||
|
||||
## Convert a [Str] to a [Nat]. If the given number doesn't fit in [Nat], it will be [truncated](https://www.ualberta.ca/computing-science/media-library/teaching-resources/java/truncation-rounding.html).
|
||||
## [Nat] has a different maximum number depending on the system you're building
|
||||
## for, so this may give a different answer on different systems.
|
||||
##
|
||||
## For example, on a 32-bit system, `Num.maxNat` will return the same answer as
|
||||
## `Num.maxU32`. This means that calling `Str.toNat "9_000_000_000"` on a 32-bit
|
||||
## system will return `Num.maxU32` instead of 9 billion, because 9 billion is
|
||||
## larger than `Num.maxU32` and will not fit in a [Nat] on a 32-bit system.
|
||||
##
|
||||
## Calling `Str.toNat "9_000_000_000"` on a 64-bit system will return
|
||||
## the [Nat] value of 9_000_000_000. This is because on a 64-bit system, [Nat] can
|
||||
## hold up to `Num.maxU64`, and 9_000_000_000 is smaller than `Num.maxU64`.
|
||||
##
|
||||
## expect Str.toNat "9_000_000_000" == Ok 9000000000
|
||||
## expect Str.toNat "not a number" == Err InvalidNumStr
|
||||
toNat : Str -> Result Nat [InvalidNumStr]*
|
||||
toNat = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers
|
||||
## from `0` to `340_282_366_920_938_463_463_374_607_431_768_211_455` (over
|
||||
## 340 undecillion). It can be specified with a u128 suffix.
|
||||
##
|
||||
## expect Str.toU128 "1500" == Ok 1500u128
|
||||
## expect Str.toU128 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU128 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU128 "not a number" == Err InvalidNumStr
|
||||
toU128 : Str -> Result U128 [InvalidNumStr]*
|
||||
toU128 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a signed [I128] integer. A [I128] value can hold numbers
|
||||
## from `-170_141_183_460_469_231_731_687_303_715_884_105_728` to
|
||||
## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified
|
||||
## with a i128 suffix.
|
||||
##
|
||||
## expect Str.toI128 "1500" == Ok 1500i128
|
||||
## expect Str.toI128 "-1" == Ok -1i128
|
||||
## expect Str.toI128 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI128 "not a number" == Err InvalidNumStr
|
||||
toI128 : Str -> Result I128 [InvalidNumStr]*
|
||||
toI128 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers
|
||||
## from `0` to `18_446_744_073_709_551_615` (over 18 quintillion). It
|
||||
## can be specified with a u64 suffix.
|
||||
##
|
||||
## expect Str.toU64 "1500" == Ok 1500u64
|
||||
## expect Str.toU64 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU64 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU64 "not a number" == Err InvalidNumStr
|
||||
toU64 : Str -> Result U64 [InvalidNumStr]*
|
||||
toU64 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers
|
||||
## from `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807`. It can be
|
||||
## specified with a i64 suffix.
|
||||
##
|
||||
## expect Str.toI64 "1500" == Ok 1500i64
|
||||
## expect Str.toI64 "-1" == Ok -1i64
|
||||
## expect Str.toI64 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI64 "not a number" == Err InvalidNumStr
|
||||
toI64 : Str -> Result I64 [InvalidNumStr]*
|
||||
toI64 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers
|
||||
## from `0` to `4_294_967_295` (over 4 billion). It can be specified with
|
||||
## a u32 suffix.
|
||||
##
|
||||
## expect Str.toU32 "1500" == Ok 1500u32
|
||||
## expect Str.toU32 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU32 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU32 "not a number" == Err InvalidNumStr
|
||||
toU32 : Str -> Result U32 [InvalidNumStr]*
|
||||
toU32 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers
|
||||
## from `-2_147_483_648` to `2_147_483_647`. It can be
|
||||
## specified with a i32 suffix.
|
||||
##
|
||||
## expect Str.toI32 "1500" == Ok 1500i32
|
||||
## expect Str.toI32 "-1" == Ok -1i32
|
||||
## expect Str.toI32 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI32 "not a number" == Err InvalidNumStr
|
||||
toI32 : Str -> Result I32 [InvalidNumStr]*
|
||||
toI32 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers
|
||||
## from `0` to `65_535`. It can be specified with a u16 suffix.
|
||||
##
|
||||
## expect Str.toU16 "1500" == Ok 1500u16
|
||||
## expect Str.toU16 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toU16 "-1" == Err InvalidNumStr
|
||||
## expect Str.toU16 "not a number" == Err InvalidNumStr
|
||||
toU16 : Str -> Result U16 [InvalidNumStr]*
|
||||
toU16 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers
|
||||
## from `-32_768` to `32_767`. It can be
|
||||
## specified with a i16 suffix.
|
||||
##
|
||||
## expect Str.toI16 "1500" == Ok 1500i16
|
||||
## expect Str.toI16 "-1" == Ok -1i16
|
||||
## expect Str.toI16 "0.1" == Err InvalidNumStr
|
||||
## expect Str.toI16 "not a number" == Err InvalidNumStr
|
||||
toI16 : Str -> Result I16 [InvalidNumStr]*
|
||||
toI16 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers
|
||||
## from `0` to `255`. It can be specified with a u8 suffix.
|
||||
##
|
||||
## expect Str.toU8 "250" == Ok 250u8
|
||||
## expect Str.toU8 "-0.1" == Err InvalidNumStr
|
||||
## expect Str.toU8 "not a number" == Err InvalidNumStr
|
||||
## expect Str.toU8 "1500" == Err InvalidNumStr
|
||||
toU8 : Str -> Result U8 [InvalidNumStr]*
|
||||
toU8 = \string -> strToNumHelp string
|
||||
|
||||
## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers
|
||||
## from `-128` to `127`. It can be
|
||||
## specified with a i8 suffix.
|
||||
##
|
||||
## expect Str.toI8 "-15" == Ok -15i8
|
||||
## expect Str.toI8 "150.00" == Err InvalidNumStr
|
||||
## expect Str.toI8 "not a number" == Err InvalidNumStr
|
||||
toI8 : Str -> Result I8 [InvalidNumStr]*
|
||||
toI8 = \string -> strToNumHelp string
|
||||
|
||||
## Gets the byte at the given index, without performing a bounds check
|
||||
## Get the byte at the given index, without performing a bounds check.
|
||||
getUnsafe : Str, Nat -> U8
|
||||
|
||||
## gives the number of string bytes
|
||||
## Gives the number of bytes in a [Str] value.
|
||||
##
|
||||
## expect Str.countUtf8Bytes "Hello World" == 11
|
||||
countUtf8Bytes : Str -> Nat
|
||||
|
||||
## string slice that does not do bounds checking or utf-8 verification
|
||||
substringUnsafe : Str, Nat, Nat -> Str
|
||||
|
||||
## Returns the string with each occurrence of a substring replaced with a replacement.
|
||||
## If the substring is not found, returns `Err NotFound`.
|
||||
## Returns the given [Str] with each occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
##
|
||||
## Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
|
||||
## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
|
||||
## expect Str.replaceEach "not here" "/" "_" == Err NotFound
|
||||
replaceEach : Str, Str, Str -> Result Str [NotFound]*
|
||||
replaceEach = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
# We found at least one needle, so start the buffer off with
|
||||
# `before` followed by the first replacement flower.
|
||||
Str.reserve "" (Str.countUtf8Bytes haystack)
|
||||
Str.withCapacity (Str.countUtf8Bytes haystack)
|
||||
|> Str.concat before
|
||||
|> Str.concat flower
|
||||
|> replaceEachHelp after needle flower
|
||||
@ -320,10 +474,11 @@ replaceEachHelp = \buf, haystack, needle, flower ->
|
||||
|
||||
expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
|
||||
|
||||
## Returns the string with the first occurrence of a substring replaced with a replacement.
|
||||
## If the substring is not found, returns `Err NotFound`.
|
||||
## Returns the given [Str] with the first occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
##
|
||||
## Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
|
||||
## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
|
||||
## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
|
||||
replaceFirst : Str, Str, Str -> Result Str [NotFound]*
|
||||
replaceFirst = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
@ -334,10 +489,11 @@ replaceFirst = \haystack, needle, flower ->
|
||||
|
||||
expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
|
||||
|
||||
## Returns the string with the last occurrence of a substring replaced with a replacement.
|
||||
## If the substring is not found, returns `Err NotFound`.
|
||||
## Returns the given [Str] with the last occurrence of a substring replaced.
|
||||
## Returns [Err NotFound] if the substring is not found.
|
||||
##
|
||||
## Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
|
||||
## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
|
||||
## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
|
||||
replaceLast : Str, Str, Str -> Result Str [NotFound]*
|
||||
replaceLast = \haystack, needle, flower ->
|
||||
when splitLast haystack needle is
|
||||
@ -348,10 +504,12 @@ replaceLast = \haystack, needle, flower ->
|
||||
|
||||
expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
|
||||
|
||||
## Returns the string before the first occurrence of a delimiter, as well as the
|
||||
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
|
||||
## as the rest of the string after that occurrence.
|
||||
## Returns [ Err NotFound] if the delimiter is not found.
|
||||
##
|
||||
## Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||
## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||
## expect Str.splitFirst "no slashes here" "/" == Err NotFound
|
||||
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]*
|
||||
splitFirst = \haystack, needle ->
|
||||
when firstMatch haystack needle is
|
||||
@ -399,10 +557,12 @@ firstMatchHelp = \haystack, needle, index, lastPossible ->
|
||||
else
|
||||
None
|
||||
|
||||
## Returns the string before the last occurrence of a delimiter, as well as the
|
||||
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
## Returns the given [Str] before the last occurrence of a delimiter, as well as
|
||||
## the rest of the string after that occurrence.
|
||||
## Returns [Err NotFound] if the delimiter is not found.
|
||||
##
|
||||
## Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
|
||||
## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
|
||||
## expect Str.splitLast "no slashes here" "/" == Err NotFound
|
||||
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]*
|
||||
splitLast = \haystack, needle ->
|
||||
when lastMatch haystack needle is
|
||||
@ -488,8 +648,13 @@ matchesAtHelp = \state ->
|
||||
|
||||
doesThisMatch && doesRestMatch
|
||||
|
||||
## Walks over the string's UTF-8 bytes, calling a function which updates a state using each
|
||||
## UTF-8 `U8` byte as well as the index of that byte within the string.
|
||||
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
|
||||
## state for each byte. The index for that byte in the string is provided
|
||||
## to the update function.
|
||||
##
|
||||
## f : List U8, U8, Nat -> List U8
|
||||
## f = \state, byte, _ -> List.append state byte
|
||||
## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
|
||||
walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state
|
||||
walkUtf8WithIndex = \string, state, step ->
|
||||
walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string)
|
||||
@ -504,12 +669,17 @@ walkUtf8WithIndexHelp = \string, state, step, index, length ->
|
||||
else
|
||||
state
|
||||
|
||||
## Make sure at least some number of bytes fit in this string without reallocating
|
||||
## Enlarge the given [Str] for at least capacity additional bytes.
|
||||
reserve : Str, Nat -> Str
|
||||
|
||||
## is UB when the scalar is invalid
|
||||
appendScalarUnsafe : Str, U32 -> Str
|
||||
|
||||
## Append a [U32] scalar to the given [Str]. If the given scalar is not a valid
|
||||
## unicode value, it will return [Err InvalidScalar].
|
||||
##
|
||||
## expect Str.appendScalar "H" 105 == Ok "Hi"
|
||||
## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
|
||||
appendScalar : Str, U32 -> Result Str [InvalidScalar]*
|
||||
appendScalar = \string, scalar ->
|
||||
if isValidScalar scalar then
|
||||
@ -523,6 +693,12 @@ isValidScalar = \scalar ->
|
||||
|
||||
getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
|
||||
|
||||
## Walks over the unicode [U32] values for the given [Str] and calls a function
|
||||
## to update state for each.
|
||||
##
|
||||
## f : List U32, U32 -> List U32
|
||||
## f = \state, scalar -> List.append state scalar
|
||||
## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
|
||||
walkScalars : Str, state, (state, U32 -> state) -> state
|
||||
walkScalars = \string, init, step ->
|
||||
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
|
||||
@ -537,6 +713,18 @@ walkScalarsHelp = \string, state, step, index, length ->
|
||||
else
|
||||
state
|
||||
|
||||
## Walks over the unicode [U32] values for the given [Str] and calls a function
|
||||
## to update state for each.
|
||||
##
|
||||
## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
|
||||
## f = \state, scalar ->
|
||||
## check = 66
|
||||
## if scalar == check then
|
||||
## Break [check]
|
||||
## else
|
||||
## Continue (List.append state scalar)
|
||||
## expect Str.walkScalarsUntil "ABC" [] f == [66]
|
||||
## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
|
||||
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
|
||||
walkScalarsUntil = \string, init, step ->
|
||||
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
|
||||
@ -567,6 +755,8 @@ strToNumHelp = \string ->
|
||||
else
|
||||
Err InvalidNumStr
|
||||
|
||||
## Adds the specified prefix to the string, like a reversed Str.concat
|
||||
## Adds a prefix to the given [Str].
|
||||
##
|
||||
## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome"
|
||||
withPrefix : Str, Str -> Str
|
||||
withPrefix = \str, prefix -> Str.concat prefix str
|
||||
|
@ -14,6 +14,7 @@ roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_serialize = { path = "../serialize" }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
static_assertions = "1.1.0"
|
||||
bitvec = "1"
|
||||
|
@ -9,6 +9,14 @@ use roc_types::{
|
||||
types::{MemberImpl, Type},
|
||||
};
|
||||
|
||||
/// During type solving and monomorphization, a module must know how its imported ability
|
||||
/// implementations are resolved - are they derived, or have a concrete implementation?
|
||||
///
|
||||
/// Unfortunately we cannot keep this information opaque, as it's important for properly
|
||||
/// restoring specialization lambda sets. As such, we need to export implementation information,
|
||||
/// which is the job of this structure.
|
||||
pub type ResolvedImplementations = VecMap<ImplKey, ResolvedImpl>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MemberVariables {
|
||||
pub able_vars: Vec<Variable>,
|
||||
@ -20,7 +28,7 @@ pub struct MemberVariables {
|
||||
|
||||
/// The member and its signature is defined locally, in the module the store is created for.
|
||||
/// We need to instantiate and introduce this during solving.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ResolvedMemberType(Variable);
|
||||
|
||||
/// Member type information that needs to be resolved from imports.
|
||||
@ -48,7 +56,7 @@ impl ResolvePhase for Pending {
|
||||
type MemberType = PendingMemberType;
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Resolved;
|
||||
impl ResolvePhase for Resolved {
|
||||
type MemberType = ResolvedMemberType;
|
||||
@ -57,7 +65,7 @@ impl ResolvePhase for Resolved {
|
||||
/// Stores information about an ability member definition, including the parent ability, the
|
||||
/// defining type, and what type variables need to be instantiated with instances of the ability.
|
||||
// TODO: SoA and put me in an arena
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AbilityMemberData<Phase: ResolvePhase> {
|
||||
pub parent_ability: Symbol,
|
||||
pub region: Region,
|
||||
@ -82,7 +90,7 @@ impl AbilityMemberData<Resolved> {
|
||||
pub type SpecializationLambdaSets = VecMap<u8, Variable>;
|
||||
|
||||
/// A particular specialization of an ability member.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MemberSpecializationInfo<Phase: ResolvePhase> {
|
||||
_phase: std::marker::PhantomData<Phase>,
|
||||
pub symbol: Symbol,
|
||||
@ -100,6 +108,7 @@ impl MemberSpecializationInfo<Resolved> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct SpecializationId(NonZeroU32);
|
||||
|
||||
static_assertions::assert_eq_size!(SpecializationId, Option<SpecializationId>);
|
||||
@ -469,8 +478,19 @@ impl IAbilitiesStore<Resolved> {
|
||||
pub fn get_resolved(&self, id: SpecializationId) -> Option<Symbol> {
|
||||
self.resolved_specializations.get(&id).copied()
|
||||
}
|
||||
|
||||
pub fn serialize(&self, writer: &mut impl std::io::Write) -> std::io::Result<usize> {
|
||||
serialize::serialize(self, writer)
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: &[u8]) -> (Self, usize) {
|
||||
serialize::deserialize(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
pub use serialize::deserialize_solved_implementations;
|
||||
pub use serialize::serialize_solved_implementations;
|
||||
|
||||
impl IAbilitiesStore<Pending> {
|
||||
pub fn import_implementation(&mut self, impl_key: ImplKey, resolved_impl: &ResolvedImpl) {
|
||||
let member_impl = match resolved_impl {
|
||||
@ -484,8 +504,12 @@ impl IAbilitiesStore<Pending> {
|
||||
|
||||
let old_declared_impl = self.declared_implementations.insert(impl_key, member_impl);
|
||||
debug_assert!(
|
||||
old_declared_impl.is_none(),
|
||||
"Replacing existing declared impl!"
|
||||
old_declared_impl.is_none() ||
|
||||
// Can happen between we import declared implementations during canonicalization, but
|
||||
// implementation information only after solving
|
||||
old_declared_impl.unwrap() == member_impl,
|
||||
"Replacing existing declared impl: {:?}",
|
||||
(impl_key, old_declared_impl)
|
||||
);
|
||||
}
|
||||
|
||||
@ -660,3 +684,700 @@ impl IAbilitiesStore<Pending> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod serialize {
|
||||
use roc_collections::{MutMap, VecMap};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_serialize::bytes;
|
||||
use roc_types::{
|
||||
subs::{SubsSlice, Variable},
|
||||
types::MemberImpl,
|
||||
};
|
||||
|
||||
use super::{
|
||||
AbilitiesStore, AbilityMemberData, ImplKey, MemberSpecializationInfo, Resolved,
|
||||
ResolvedImpl, ResolvedImplementations, ResolvedMemberType, SpecializationId,
|
||||
};
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Header {
|
||||
members_of_ability: u64,
|
||||
specialization_to_root: u64,
|
||||
ability_members: u64,
|
||||
declared_implementations: u64,
|
||||
specializations: u64,
|
||||
next_specialization_id: u64,
|
||||
resolved_specializations: u64,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
fn from_store(store: &AbilitiesStore) -> Self {
|
||||
let AbilitiesStore {
|
||||
members_of_ability,
|
||||
specialization_to_root,
|
||||
ability_members,
|
||||
declared_implementations,
|
||||
specializations,
|
||||
next_specialization_id,
|
||||
resolved_specializations,
|
||||
} = store;
|
||||
|
||||
Self {
|
||||
members_of_ability: members_of_ability.len() as _,
|
||||
specialization_to_root: specialization_to_root.len() as _,
|
||||
ability_members: ability_members.len() as _,
|
||||
declared_implementations: declared_implementations.len() as _,
|
||||
specializations: specializations.len() as _,
|
||||
next_specialization_id: next_specialization_id.get() as _,
|
||||
resolved_specializations: resolved_specializations.len() as _,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_array(self) -> [u8; std::mem::size_of::<Self>()] {
|
||||
// Safety: With repr(c) all fields are in order and properly aligned without padding.
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
|
||||
fn from_array(array: [u8; std::mem::size_of::<Self>()]) -> Self {
|
||||
// Safety: With repr(c) all fields are in order and properly aligned without padding.
|
||||
unsafe { std::mem::transmute(array) }
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn serialize(store: &AbilitiesStore, writer: &mut impl Write) -> io::Result<usize> {
|
||||
let header = Header::from_store(store).to_array();
|
||||
let written = header.len();
|
||||
writer.write_all(&header)?;
|
||||
|
||||
let AbilitiesStore {
|
||||
members_of_ability,
|
||||
specialization_to_root,
|
||||
ability_members,
|
||||
declared_implementations,
|
||||
specializations,
|
||||
next_specialization_id: _, // written in the header
|
||||
resolved_specializations,
|
||||
} = store;
|
||||
|
||||
let written = serialize_members_of_ability(members_of_ability, writer, written)?;
|
||||
let written = serialize_specializations_to_root(specialization_to_root, writer, written)?;
|
||||
let written = serialize_ability_members(ability_members, writer, written)?;
|
||||
let written =
|
||||
serialize_declared_implementations(declared_implementations, writer, written)?;
|
||||
let written = serialize_specializations(specializations, writer, written)?;
|
||||
let written =
|
||||
serialize_resolved_specializations(resolved_specializations, writer, written)?;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
pub(super) fn deserialize(bytes: &[u8]) -> (AbilitiesStore, usize) {
|
||||
let mut offset = 0;
|
||||
|
||||
let header_slice = &bytes[..std::mem::size_of::<Header>()];
|
||||
offset += header_slice.len();
|
||||
let header = Header::from_array(header_slice.try_into().unwrap());
|
||||
|
||||
let (members_of_ability, offset) =
|
||||
deserialize_members_of_ability(bytes, header.members_of_ability as _, offset);
|
||||
let (specialization_to_root, offset) =
|
||||
deserialize_specialization_to_root(bytes, header.specialization_to_root as _, offset);
|
||||
let (ability_members, offset) =
|
||||
deserialize_ability_members(bytes, header.ability_members as _, offset);
|
||||
let (declared_implementations, offset) = deserialize_declared_implementations(
|
||||
bytes,
|
||||
header.declared_implementations as _,
|
||||
offset,
|
||||
);
|
||||
let (specializations, offset) =
|
||||
deserialize_specializations(bytes, header.specializations as _, offset);
|
||||
let (resolved_specializations, offset) = deserialize_resolved_specializations(
|
||||
bytes,
|
||||
header.resolved_specializations as _,
|
||||
offset,
|
||||
);
|
||||
|
||||
(
|
||||
AbilitiesStore {
|
||||
members_of_ability,
|
||||
specialization_to_root,
|
||||
ability_members,
|
||||
declared_implementations,
|
||||
specializations,
|
||||
next_specialization_id: (header.next_specialization_id as u32).try_into().unwrap(),
|
||||
resolved_specializations,
|
||||
},
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
fn serialize_members_of_ability(
|
||||
members_of_ability: &MutMap<Symbol, Vec<Symbol>>,
|
||||
writer: &mut impl Write,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
bytes::serialize_map(
|
||||
members_of_ability,
|
||||
bytes::serialize_slice,
|
||||
bytes::serialize_slice_of_slices,
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_members_of_ability(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (MutMap<Symbol, Vec<Symbol>>, usize) {
|
||||
bytes::deserialize_map(
|
||||
bytes,
|
||||
bytes::deserialize_vec,
|
||||
bytes::deserialize_slice_of_slices,
|
||||
length,
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct SerImplKey(Symbol, Symbol);
|
||||
impl From<&ImplKey> for SerImplKey {
|
||||
fn from(k: &ImplKey) -> Self {
|
||||
Self(k.opaque, k.ability_member)
|
||||
}
|
||||
}
|
||||
impl From<&SerImplKey> for ImplKey {
|
||||
fn from(k: &SerImplKey) -> Self {
|
||||
Self {
|
||||
opaque: k.0,
|
||||
ability_member: k.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_specializations_to_root(
|
||||
specialization_to_root: &MutMap<Symbol, ImplKey>,
|
||||
writer: &mut impl Write,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
bytes::serialize_map(
|
||||
specialization_to_root,
|
||||
bytes::serialize_slice,
|
||||
|keys, writer, written| {
|
||||
bytes::serialize_slice(
|
||||
&keys.iter().map(SerImplKey::from).collect::<Vec<_>>(),
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
},
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_specialization_to_root(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (MutMap<Symbol, ImplKey>, usize) {
|
||||
bytes::deserialize_map(
|
||||
bytes,
|
||||
bytes::deserialize_vec,
|
||||
|bytes, length, offset| {
|
||||
let (slice, offset) = bytes::deserialize_slice::<SerImplKey>(bytes, length, offset);
|
||||
(slice.iter().map(ImplKey::from).collect(), offset)
|
||||
},
|
||||
length,
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct SerMemberData(Symbol, Region, Variable);
|
||||
impl From<&AbilityMemberData<Resolved>> for SerMemberData {
|
||||
fn from(k: &AbilityMemberData<Resolved>) -> Self {
|
||||
Self(k.parent_ability, k.region, k.typ.0)
|
||||
}
|
||||
}
|
||||
impl From<&SerMemberData> for AbilityMemberData<Resolved> {
|
||||
fn from(k: &SerMemberData) -> Self {
|
||||
Self {
|
||||
parent_ability: k.0,
|
||||
region: k.1,
|
||||
typ: ResolvedMemberType(k.2),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_ability_members(
|
||||
ability_members: &MutMap<Symbol, AbilityMemberData<Resolved>>,
|
||||
writer: &mut impl Write,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
bytes::serialize_map(
|
||||
ability_members,
|
||||
bytes::serialize_slice,
|
||||
|keys, writer, written| {
|
||||
bytes::serialize_slice(
|
||||
&keys.iter().map(SerMemberData::from).collect::<Vec<_>>(),
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
},
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_ability_members(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (MutMap<Symbol, AbilityMemberData<Resolved>>, usize) {
|
||||
bytes::deserialize_map(
|
||||
bytes,
|
||||
bytes::deserialize_vec,
|
||||
|bytes, length, offset| {
|
||||
let (slice, offset) =
|
||||
bytes::deserialize_slice::<SerMemberData>(bytes, length, offset);
|
||||
(slice.iter().map(AbilityMemberData::from).collect(), offset)
|
||||
},
|
||||
length,
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
enum SerMemberImpl {
|
||||
Impl(Symbol),
|
||||
Derived,
|
||||
Error,
|
||||
}
|
||||
impl From<&MemberImpl> for SerMemberImpl {
|
||||
fn from(k: &MemberImpl) -> Self {
|
||||
match k {
|
||||
MemberImpl::Impl(s) => Self::Impl(*s),
|
||||
MemberImpl::Derived => Self::Derived,
|
||||
MemberImpl::Error => Self::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&SerMemberImpl> for MemberImpl {
|
||||
fn from(k: &SerMemberImpl) -> Self {
|
||||
match k {
|
||||
SerMemberImpl::Impl(s) => Self::Impl(*s),
|
||||
SerMemberImpl::Derived => Self::Derived,
|
||||
SerMemberImpl::Error => Self::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_declared_implementations(
|
||||
declared_implementations: &MutMap<ImplKey, MemberImpl>,
|
||||
writer: &mut impl Write,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
bytes::serialize_map(
|
||||
declared_implementations,
|
||||
bytes::serialize_slice,
|
||||
|keys, writer, written| {
|
||||
bytes::serialize_slice(
|
||||
&keys.iter().map(SerMemberImpl::from).collect::<Vec<_>>(),
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
},
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_declared_implementations(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (MutMap<ImplKey, MemberImpl>, usize) {
|
||||
bytes::deserialize_map(
|
||||
bytes,
|
||||
bytes::deserialize_vec,
|
||||
|bytes, length, offset| {
|
||||
let (slice, offset) =
|
||||
bytes::deserialize_slice::<SerMemberImpl>(bytes, length, offset);
|
||||
(slice.iter().map(MemberImpl::from).collect(), offset)
|
||||
},
|
||||
length,
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct SerMemberSpecInfo(Symbol, SubsSlice<u8>, SubsSlice<Variable>);
|
||||
|
||||
fn serialize_specializations(
|
||||
specializations: &MutMap<Symbol, MemberSpecializationInfo<Resolved>>,
|
||||
writer: &mut impl Write,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
bytes::serialize_map(
|
||||
specializations,
|
||||
bytes::serialize_slice,
|
||||
|spec_info, writer, written| {
|
||||
let mut spec_lambda_sets_regions: Vec<u8> = Vec::new();
|
||||
let mut spec_lambda_sets_vars: Vec<Variable> = Vec::new();
|
||||
let mut ser_member_spec_infos: Vec<SerMemberSpecInfo> = Vec::new();
|
||||
|
||||
for MemberSpecializationInfo {
|
||||
_phase: _,
|
||||
symbol,
|
||||
specialization_lambda_sets,
|
||||
} in spec_info
|
||||
{
|
||||
let regions = SubsSlice::extend_new(
|
||||
&mut spec_lambda_sets_regions,
|
||||
specialization_lambda_sets.keys().copied(),
|
||||
);
|
||||
let vars = SubsSlice::extend_new(
|
||||
&mut spec_lambda_sets_vars,
|
||||
specialization_lambda_sets.values().copied(),
|
||||
);
|
||||
ser_member_spec_infos.push(SerMemberSpecInfo(*symbol, regions, vars));
|
||||
}
|
||||
|
||||
let written = bytes::serialize_slice(&ser_member_spec_infos, writer, written)?;
|
||||
let written = bytes::serialize_slice(&spec_lambda_sets_regions, writer, written)?;
|
||||
let written = bytes::serialize_slice(&spec_lambda_sets_vars, writer, written)?;
|
||||
|
||||
Ok(written)
|
||||
},
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_specializations(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (MutMap<Symbol, MemberSpecializationInfo<Resolved>>, usize) {
|
||||
bytes::deserialize_map(
|
||||
bytes,
|
||||
bytes::deserialize_vec,
|
||||
|bytes, length, offset| {
|
||||
let (serialized_slices, offset) =
|
||||
bytes::deserialize_slice::<SerMemberSpecInfo>(bytes, length, offset);
|
||||
|
||||
let (regions_slice, offset) = {
|
||||
let total_items = serialized_slices.iter().map(|s| s.1.len()).sum();
|
||||
bytes::deserialize_slice::<u8>(bytes, total_items, offset)
|
||||
};
|
||||
|
||||
let (vars_slice, offset) = {
|
||||
let total_items = serialized_slices.iter().map(|s| s.2.len()).sum();
|
||||
bytes::deserialize_slice::<Variable>(bytes, total_items, offset)
|
||||
};
|
||||
|
||||
let mut spec_infos: Vec<MemberSpecializationInfo<Resolved>> =
|
||||
Vec::with_capacity(length);
|
||||
for SerMemberSpecInfo(symbol, regions, vars) in serialized_slices {
|
||||
let regions = regions_slice[regions.indices()].to_vec();
|
||||
let lset_vars = vars_slice[vars.indices()].to_vec();
|
||||
let spec_info = MemberSpecializationInfo::new(*symbol, unsafe {
|
||||
VecMap::zip(regions, lset_vars)
|
||||
});
|
||||
spec_infos.push(spec_info)
|
||||
}
|
||||
|
||||
(spec_infos, offset)
|
||||
},
|
||||
length,
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
fn serialize_resolved_specializations(
|
||||
resolved_specializations: &MutMap<SpecializationId, Symbol>,
|
||||
writer: &mut impl Write,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
bytes::serialize_map(
|
||||
resolved_specializations,
|
||||
bytes::serialize_slice,
|
||||
bytes::serialize_slice,
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_resolved_specializations(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (MutMap<SpecializationId, Symbol>, usize) {
|
||||
bytes::deserialize_map(
|
||||
bytes,
|
||||
bytes::deserialize_vec,
|
||||
bytes::deserialize_vec,
|
||||
length,
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
enum SerResolvedImpl {
|
||||
Impl(SerMemberSpecInfo),
|
||||
Derived,
|
||||
Error,
|
||||
}
|
||||
impl SerResolvedImpl {
|
||||
fn num_regions(&self) -> usize {
|
||||
match self {
|
||||
SerResolvedImpl::Impl(spec) => spec.1.len(),
|
||||
SerResolvedImpl::Derived => 0,
|
||||
SerResolvedImpl::Error => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_solved_implementations(
|
||||
solved_impls: &ResolvedImplementations,
|
||||
writer: &mut impl std::io::Write,
|
||||
) -> std::io::Result<usize> {
|
||||
let len = solved_impls.len() as u64;
|
||||
|
||||
let written = bytes::serialize_slice(&[len], writer, 0)?;
|
||||
|
||||
bytes::serialize_vec_map(
|
||||
solved_impls,
|
||||
|keys, writer, written| {
|
||||
bytes::serialize_slice(
|
||||
&keys.iter().map(SerImplKey::from).collect::<Vec<_>>(),
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
},
|
||||
|resolved_impls, writer, written| {
|
||||
let mut spec_lambda_sets_regions: Vec<u8> = Vec::new();
|
||||
let mut spec_lambda_sets_vars: Vec<Variable> = Vec::new();
|
||||
let mut ser_resolved_impls: Vec<SerResolvedImpl> = Vec::new();
|
||||
|
||||
for resolved_impl in resolved_impls {
|
||||
let ser_resolved_impl = match resolved_impl {
|
||||
ResolvedImpl::Impl(MemberSpecializationInfo {
|
||||
_phase: _,
|
||||
symbol,
|
||||
specialization_lambda_sets,
|
||||
}) => {
|
||||
let regions = SubsSlice::extend_new(
|
||||
&mut spec_lambda_sets_regions,
|
||||
specialization_lambda_sets.keys().copied(),
|
||||
);
|
||||
let vars = SubsSlice::extend_new(
|
||||
&mut spec_lambda_sets_vars,
|
||||
specialization_lambda_sets.values().copied(),
|
||||
);
|
||||
SerResolvedImpl::Impl(SerMemberSpecInfo(*symbol, regions, vars))
|
||||
}
|
||||
ResolvedImpl::Derived => SerResolvedImpl::Derived,
|
||||
ResolvedImpl::Error => SerResolvedImpl::Error,
|
||||
};
|
||||
|
||||
ser_resolved_impls.push(ser_resolved_impl);
|
||||
}
|
||||
|
||||
let written = bytes::serialize_slice(&ser_resolved_impls, writer, written)?;
|
||||
let written = bytes::serialize_slice(&spec_lambda_sets_regions, writer, written)?;
|
||||
let written = bytes::serialize_slice(&spec_lambda_sets_vars, writer, written)?;
|
||||
|
||||
Ok(written)
|
||||
},
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn deserialize_solved_implementations(bytes: &[u8]) -> (ResolvedImplementations, usize) {
|
||||
let (len_slice, offset) = bytes::deserialize_slice::<u64>(bytes, 1, 0);
|
||||
let length = len_slice[0];
|
||||
|
||||
bytes::deserialize_vec_map(
|
||||
bytes,
|
||||
|bytes, length, offset| {
|
||||
let (slice, offset) = bytes::deserialize_slice::<SerImplKey>(bytes, length, offset);
|
||||
(slice.iter().map(ImplKey::from).collect(), offset)
|
||||
},
|
||||
|bytes, length, offset| {
|
||||
let (serialized_slices, offset) =
|
||||
bytes::deserialize_slice::<SerResolvedImpl>(bytes, length, offset);
|
||||
|
||||
let total_num_regions = serialized_slices.iter().map(|s| s.num_regions()).sum();
|
||||
|
||||
let (regions_slice, offset) =
|
||||
bytes::deserialize_slice::<u8>(bytes, total_num_regions, offset);
|
||||
|
||||
let (vars_slice, offset) =
|
||||
{ bytes::deserialize_slice::<Variable>(bytes, total_num_regions, offset) };
|
||||
|
||||
let mut all_resolved: Vec<ResolvedImpl> = Vec::with_capacity(length);
|
||||
for ser_resolved in serialized_slices {
|
||||
let resolved = match ser_resolved {
|
||||
SerResolvedImpl::Impl(SerMemberSpecInfo(symbol, regions, vars)) => {
|
||||
let regions = regions_slice[regions.indices()].to_vec();
|
||||
let lset_vars = vars_slice[vars.indices()].to_vec();
|
||||
let spec_info = MemberSpecializationInfo::new(*symbol, unsafe {
|
||||
VecMap::zip(regions, lset_vars)
|
||||
});
|
||||
ResolvedImpl::Impl(spec_info)
|
||||
}
|
||||
SerResolvedImpl::Derived => ResolvedImpl::Derived,
|
||||
SerResolvedImpl::Error => ResolvedImpl::Error,
|
||||
};
|
||||
|
||||
all_resolved.push(resolved);
|
||||
}
|
||||
|
||||
(all_resolved, offset)
|
||||
},
|
||||
length as _,
|
||||
offset,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use roc_collections::VecMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::{subs::Variable, types::MemberImpl};
|
||||
|
||||
use super::{
|
||||
AbilitiesStore, AbilityMemberData, ImplKey, MemberSpecializationInfo, ResolvedMemberType,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn serde_abilities_store() {
|
||||
let store = {
|
||||
let mut store = AbilitiesStore::default();
|
||||
store.register_ability(
|
||||
Symbol::ARG_1,
|
||||
[
|
||||
(
|
||||
Symbol::ARG_2,
|
||||
AbilityMemberData {
|
||||
parent_ability: Symbol::ARG_1,
|
||||
region: Region::zero(),
|
||||
typ: ResolvedMemberType(Variable::BOOL),
|
||||
},
|
||||
),
|
||||
(
|
||||
Symbol::ARG_3,
|
||||
AbilityMemberData {
|
||||
parent_ability: Symbol::ARG_1,
|
||||
region: Region::zero(),
|
||||
typ: ResolvedMemberType(Variable::BOOL),
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
store.register_ability(
|
||||
Symbol::ARG_4,
|
||||
[(
|
||||
Symbol::ARG_5,
|
||||
AbilityMemberData {
|
||||
parent_ability: Symbol::ARG_4,
|
||||
region: Region::zero(),
|
||||
typ: ResolvedMemberType(Variable::BOOL),
|
||||
},
|
||||
)],
|
||||
);
|
||||
|
||||
store.register_declared_implementations(
|
||||
Symbol::ATTR_ATTR,
|
||||
[
|
||||
(Symbol::ARG_2, MemberImpl::Impl(Symbol::ATTR_INVALID)),
|
||||
(Symbol::ARG_3, MemberImpl::Impl(Symbol::ARG_CLOSURE)),
|
||||
],
|
||||
);
|
||||
|
||||
store.register_declared_implementations(
|
||||
Symbol::ATTR_ATTR,
|
||||
[(Symbol::ARG_5, MemberImpl::Derived)],
|
||||
);
|
||||
|
||||
store
|
||||
.mark_implementation(
|
||||
ImplKey {
|
||||
opaque: Symbol::ATTR_ATTR,
|
||||
ability_member: Symbol::ARG_2,
|
||||
},
|
||||
Ok(MemberSpecializationInfo::new(Symbol::UNDERSCORE, {
|
||||
let mut map = VecMap::default();
|
||||
map.insert(1, Variable::BOOL);
|
||||
map.insert(2, Variable::U8);
|
||||
map
|
||||
})),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
store
|
||||
.mark_implementation(
|
||||
ImplKey {
|
||||
opaque: Symbol::ATTR_ATTR,
|
||||
ability_member: Symbol::ARG_3,
|
||||
},
|
||||
Ok(MemberSpecializationInfo::new(Symbol::UNDERSCORE, {
|
||||
let mut map = VecMap::default();
|
||||
map.insert(1, Variable::BOOL);
|
||||
map.insert(2, Variable::U8);
|
||||
map.insert(3, Variable::U32);
|
||||
map.insert(4, Variable::U64);
|
||||
map
|
||||
})),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let spec_id1 = store.fresh_specialization_id();
|
||||
let spec_id2 = store.fresh_specialization_id();
|
||||
|
||||
store.insert_resolved(spec_id1, Symbol::ARG_2);
|
||||
store.insert_resolved(spec_id2, Symbol::ARG_3);
|
||||
|
||||
store
|
||||
};
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
let written = store.serialize(&mut bytes).unwrap();
|
||||
assert_eq!(bytes.len(), written);
|
||||
|
||||
let AbilitiesStore {
|
||||
members_of_ability,
|
||||
specialization_to_root,
|
||||
ability_members,
|
||||
declared_implementations,
|
||||
specializations,
|
||||
next_specialization_id,
|
||||
resolved_specializations,
|
||||
} = store;
|
||||
|
||||
let (de_store, offset) = AbilitiesStore::deserialize(&bytes);
|
||||
|
||||
assert_eq!(bytes.len(), offset);
|
||||
|
||||
assert_eq!(members_of_ability, de_store.members_of_ability);
|
||||
assert_eq!(specialization_to_root, de_store.specialization_to_root);
|
||||
assert_eq!(ability_members, de_store.ability_members);
|
||||
assert_eq!(declared_implementations, de_store.declared_implementations);
|
||||
assert_eq!(specializations, de_store.specializations);
|
||||
assert_eq!(next_specialization_id, de_store.next_specialization_id);
|
||||
assert_eq!(resolved_specializations, de_store.resolved_specializations);
|
||||
}
|
||||
}
|
||||
|
@ -553,7 +553,7 @@ fn can_annotation_help(
|
||||
references,
|
||||
);
|
||||
|
||||
args.push(arg_ann);
|
||||
args.push(Loc::at(arg.region, arg_ann));
|
||||
}
|
||||
|
||||
match scope.lookup_alias(symbol) {
|
||||
@ -573,8 +573,14 @@ fn can_annotation_help(
|
||||
|
||||
let mut type_var_to_arg = Vec::new();
|
||||
|
||||
for (_, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
type_var_to_arg.push(arg_ann);
|
||||
for (alias_arg, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
type_var_to_arg.push(Loc::at(
|
||||
arg_ann.region,
|
||||
OptAbleType {
|
||||
typ: arg_ann.value,
|
||||
opt_ability: alias_arg.value.opt_bound_ability,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let mut lambda_set_variables =
|
||||
|
@ -70,6 +70,7 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
|
||||
// Below, we explicitly handle some exceptions to the pattern where a lowlevel maps
|
||||
// directly to a symbol. If you are unsure if your lowlevel is an exception, assume
|
||||
// that it isn't and just see if that works.
|
||||
#[allow(unreachable_patterns)] // multiple symbols can map to one low-level
|
||||
match lowlevel {
|
||||
$(
|
||||
LowLevel::$lowlevel => Symbol::$symbol,
|
||||
@ -144,6 +145,8 @@ map_symbol_to_lowlevel_and_arity! {
|
||||
ListSwap; LIST_SWAP; 3,
|
||||
ListGetCapacity; LIST_CAPACITY; 1,
|
||||
|
||||
ListGetUnsafe; DICT_LIST_GET_UNSAFE; 2,
|
||||
|
||||
NumAdd; NUM_ADD; 2,
|
||||
NumAddWrap; NUM_ADD_WRAP; 2,
|
||||
NumAddChecked; NUM_ADD_CHECKED_LOWLEVEL; 2,
|
||||
@ -191,8 +194,8 @@ map_symbol_to_lowlevel_and_arity! {
|
||||
NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2,
|
||||
NumToStr; NUM_TO_STR; 1,
|
||||
|
||||
Eq; BOOL_EQ; 2,
|
||||
NotEq; BOOL_NEQ; 2,
|
||||
Eq; BOOL_STRUCTURAL_EQ; 2,
|
||||
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
|
||||
And; BOOL_AND; 2,
|
||||
Or; BOOL_OR; 2,
|
||||
Not; BOOL_NOT; 1,
|
||||
|
@ -829,7 +829,15 @@ fn canonicalize_opaque<'a>(
|
||||
type_arguments: alias
|
||||
.type_variables
|
||||
.iter()
|
||||
.map(|_| Type::Variable(var_store.fresh()))
|
||||
.map(|alias_var| {
|
||||
Loc::at(
|
||||
alias_var.region,
|
||||
OptAbleType {
|
||||
typ: Type::Variable(var_store.fresh()),
|
||||
opt_ability: alias_var.value.opt_bound_ability,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
lambda_set_variables: alias
|
||||
.lambda_set_variables
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::abilities::{ImplKey, PendingAbilitiesStore, ResolvedImpl};
|
||||
use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl};
|
||||
use crate::annotation::canonicalize_annotation;
|
||||
use crate::def::{canonicalize_defs, Def};
|
||||
use crate::effect_module::HostedGeneratedFunctions;
|
||||
@ -17,7 +17,7 @@ use roc_parse::header::HeaderFor;
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{ExposedTypesStorageSubs, VarStore, Variable};
|
||||
use roc_types::subs::{ExposedTypesStorageSubs, Subs, VarStore, Variable};
|
||||
use roc_types::types::{Alias, AliasKind, AliasVar, Type};
|
||||
|
||||
/// The types of all exposed values/functions of a collection of modules
|
||||
@ -374,6 +374,7 @@ pub fn canonicalize_module_defs<'a>(
|
||||
if !output.references.has_type_or_value_lookup(symbol)
|
||||
&& !exposed_symbols.contains(&symbol)
|
||||
&& !scope.abilities_store.is_specialization_name(symbol)
|
||||
&& !symbol.is_exposed_for_builtin_derivers()
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
@ -1179,3 +1180,53 @@ fn fix_values_captured_in_closure_expr(
|
||||
OpaqueWrapFunction(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type state for a single module.
|
||||
#[derive(Debug)]
|
||||
pub struct TypeState {
|
||||
pub subs: Subs,
|
||||
pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
pub abilities: AbilitiesStore,
|
||||
pub solved_implementations: ResolvedImplementations,
|
||||
}
|
||||
|
||||
impl TypeState {
|
||||
pub fn serialize(&self, writer: &mut impl std::io::Write) -> std::io::Result<usize> {
|
||||
let Self {
|
||||
subs,
|
||||
exposed_vars_by_symbol,
|
||||
abilities,
|
||||
solved_implementations,
|
||||
} = self;
|
||||
|
||||
let written_subs = subs.serialize(exposed_vars_by_symbol, writer)?;
|
||||
let written_ab = abilities.serialize(writer)?;
|
||||
let written_solved_impls =
|
||||
crate::abilities::serialize_solved_implementations(solved_implementations, writer)?;
|
||||
|
||||
Ok(written_subs + written_ab + written_solved_impls)
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: &[u8]) -> (Self, usize) {
|
||||
let ((subs, exposed_vars_by_symbol), len_subs) = Subs::deserialize(bytes);
|
||||
let bytes = &bytes[len_subs..];
|
||||
|
||||
let (abilities, len_abilities) = AbilitiesStore::deserialize(bytes);
|
||||
let bytes = &bytes[len_abilities..];
|
||||
|
||||
let (solved_implementations, len_solved_impls) =
|
||||
crate::abilities::deserialize_solved_implementations(bytes);
|
||||
|
||||
let total_offset = len_subs + len_abilities + len_solved_impls;
|
||||
|
||||
(
|
||||
Self {
|
||||
subs,
|
||||
exposed_vars_by_symbol: exposed_vars_by_symbol.to_vec(),
|
||||
abilities,
|
||||
solved_implementations,
|
||||
},
|
||||
total_offset,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,14 @@ impl<K, V> VecMap<K, V> {
|
||||
|
||||
(k, v)
|
||||
}
|
||||
|
||||
pub fn unzip(self) -> (Vec<K>, Vec<V>) {
|
||||
(self.keys, self.values)
|
||||
}
|
||||
|
||||
pub fn unzip_slices(&self) -> (&[K], &[V]) {
|
||||
(&self.keys, &self.values)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: PartialEq, V> VecMap<K, V> {
|
||||
@ -114,14 +122,6 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
||||
self.values.truncate(len);
|
||||
}
|
||||
|
||||
pub fn unzip(self) -> (Vec<K>, Vec<V>) {
|
||||
(self.keys, self.values)
|
||||
}
|
||||
|
||||
pub fn unzip_slices(&self) -> (&[K], &[V]) {
|
||||
(&self.keys, &self.values)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// keys and values must have the same length, and there must not
|
||||
@ -250,6 +250,31 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> PartialEq for VecMap<K, V>
|
||||
where
|
||||
K: PartialEq,
|
||||
V: PartialEq,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.len() != other.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (k, v) in self.iter() {
|
||||
match other.get(k) {
|
||||
Some(v1) => {
|
||||
if v != v1 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_drain_filter {
|
||||
use crate::VecMap;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::iter::FromIterator;
|
||||
use std::{borrow::Borrow, iter::FromIterator};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct VecSet<T> {
|
||||
@ -131,3 +131,17 @@ impl<T> IntoIterator for VecSet<T> {
|
||||
self.elements.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Borrow<[T]> for VecSet<T> {
|
||||
fn borrow(&self) -> &[T] {
|
||||
&self.elements
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Vec<T>> for VecSet<T> {
|
||||
fn from(elements: Vec<T>) -> Self {
|
||||
// Not totally safe, but good enough for our purposes - also, duplicates in the VecSet are
|
||||
// fine, just inefficient.
|
||||
Self { elements }
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use roc_can::constraint::{Constraint, Constraints};
|
||||
use roc_can::expected::Expected::{self, *};
|
||||
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::num::{NumericRange, SingleQuoteBound};
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::Type::{self, *};
|
||||
@ -198,7 +198,11 @@ pub fn num_literal(
|
||||
|
||||
#[inline(always)]
|
||||
pub fn builtin_type(symbol: Symbol, args: Vec<Type>) -> Type {
|
||||
Type::Apply(symbol, args, Region::zero())
|
||||
Type::Apply(
|
||||
symbol,
|
||||
args.into_iter().map(Loc::at_zero).collect(),
|
||||
Region::zero(),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -1510,8 +1510,12 @@ fn constrain_function_def(
|
||||
loc_symbol.value,
|
||||
Loc {
|
||||
region: loc_function_def.region,
|
||||
// todo can we use Type::Variable(expr_var) here?
|
||||
value: signature.clone(),
|
||||
// NOTE: we MUST use `expr_var` here so that the correct type variable is
|
||||
// associated with the function. We prefer this to the annotation type, because the
|
||||
// annotation type may be instantiated into a fresh type variable that is
|
||||
// disassociated fromt the rest of the program.
|
||||
// Below, we'll check that the function actually matches the annotation.
|
||||
value: Type::Variable(expr_var),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -3,16 +3,23 @@
|
||||
use std::iter::once;
|
||||
|
||||
use roc_can::{
|
||||
expr::{AnnotatedMark, ClosureData, Expr, Recursive},
|
||||
expr::{AnnotatedMark, ClosureData, Expr, IntValue, Recursive, WhenBranch, WhenBranchPattern},
|
||||
num::{IntBound, IntLitWidth},
|
||||
pattern::Pattern,
|
||||
};
|
||||
use roc_derive_key::hash::FlatHashKey;
|
||||
use roc_module::{called_via::CalledVia, ident::Lowercase, symbol::Symbol};
|
||||
use roc_region::all::Loc;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::{
|
||||
called_via::CalledVia,
|
||||
ident::{Lowercase, TagName},
|
||||
symbol::Symbol,
|
||||
};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::{
|
||||
num::int_lit_width_to_variable,
|
||||
subs::{
|
||||
Content, FlatType, LambdaSet, OptVariable, RecordFields, SubsSlice, UnionLambdas, Variable,
|
||||
VariableSubsSlice,
|
||||
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
|
||||
RedundantMark, SubsIndex, SubsSlice, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
|
||||
},
|
||||
types::RecordField,
|
||||
};
|
||||
@ -20,8 +27,15 @@ use roc_types::{
|
||||
use crate::{synth_var, util::Env, DerivedBody};
|
||||
|
||||
pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody {
|
||||
let (body, body_type) = match key {
|
||||
let (body_type, body) = match key {
|
||||
FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields),
|
||||
FlatHashKey::TagUnion(tags) => {
|
||||
if tags.len() == 1 {
|
||||
hash_newtype_tag_union(env, def_symbol, tags.into_iter().next().unwrap())
|
||||
} else {
|
||||
hash_tag_union(env, def_symbol, tags)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let specialization_lambda_sets =
|
||||
@ -34,7 +48,7 @@ pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbo
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (Expr, Variable) {
|
||||
fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (Variable, Expr) {
|
||||
// Suppose rcd = { f1, ..., fn }.
|
||||
// Build a generalized type t_rcd = { f1: t1, ..., fn: tn }, with fresh t1, ..., tn,
|
||||
// so that we can re-use the derived impl for many records of the same fields.
|
||||
@ -75,9 +89,9 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (E
|
||||
let hasher_sym = env.new_symbol("hasher");
|
||||
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER));
|
||||
|
||||
let (body, body_var) = record_fields.iter_all().fold(
|
||||
(Expr::Var(hasher_sym), hasher_var),
|
||||
|(body, body_var), (field_name, field_var, _)| {
|
||||
let (body_var, body) = record_fields.iter_all().fold(
|
||||
(hasher_var, Expr::Var(hasher_sym)),
|
||||
|total_hasher, (field_name, field_var, _)| {
|
||||
let field_name = env.subs[field_name].clone();
|
||||
let field_var = env.subs[field_var];
|
||||
|
||||
@ -89,54 +103,326 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (E
|
||||
field: field_name,
|
||||
};
|
||||
|
||||
let (hash_fn_data, returned_hasher_var) = {
|
||||
// build `Hash.hash ...` function type
|
||||
//
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
let exposed_hash_fn_var = env.import_builtin_symbol_var(Symbol::HASH_HASH);
|
||||
|
||||
// (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
let this_arguments_slice =
|
||||
VariableSubsSlice::insert_into_subs(env.subs, [body_var, field_var]);
|
||||
let this_hash_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_hasher_result_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_hash_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_arguments_slice,
|
||||
this_hash_clos_var,
|
||||
this_hasher_result_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
// ~ (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
env.unify(exposed_hash_fn_var, this_hash_fn_var);
|
||||
|
||||
// Hash.hash : hasher, (typeof field) -[clos]-> hasher | hasher has Hasher, (typeof field) has Hash
|
||||
let hash_fn_head = Expr::AbilityMember(Symbol::HASH_HASH, None, this_hash_fn_var);
|
||||
let hash_fn_data = Box::new((
|
||||
this_hash_fn_var,
|
||||
Loc::at_zero(hash_fn_head),
|
||||
this_hash_clos_var,
|
||||
this_hasher_result_var,
|
||||
));
|
||||
|
||||
(hash_fn_data, this_hasher_result_var)
|
||||
};
|
||||
|
||||
let hash_arguments = vec![
|
||||
(body_var, Loc::at_zero(body)),
|
||||
(field_var, Loc::at_zero(field_access)),
|
||||
];
|
||||
let call_hash = Expr::Call(hash_fn_data, hash_arguments, CalledVia::Space);
|
||||
|
||||
(call_hash, returned_hasher_var)
|
||||
call_hash_hash(env, total_hasher, (field_var, field_access))
|
||||
},
|
||||
);
|
||||
|
||||
// Finally, build the closure
|
||||
// \hasher, rcd -> body
|
||||
build_outer_derived_closure(
|
||||
env,
|
||||
fn_name,
|
||||
(hasher_var, hasher_sym),
|
||||
(record_var, Pattern::Identifier(rcd_sym)),
|
||||
(body_var, body),
|
||||
)
|
||||
}
|
||||
|
||||
/// Build a `hash` implementation for a non-singleton tag union.
|
||||
fn hash_tag_union(
|
||||
env: &mut Env<'_>,
|
||||
fn_name: Symbol,
|
||||
tags: Vec<(TagName, u16)>,
|
||||
) -> (Variable, Expr) {
|
||||
// Suppose tags = [ A p11 .. p1n, ..., Q pq1 .. pqm ]
|
||||
// Build a generalized type t_tags = [ A t11 .. t1n, ..., Q tq1 .. tqm ],
|
||||
// with fresh t1, ..., tqm, so that we can re-use the derived impl for many
|
||||
// unions of the same tags and payloads.
|
||||
let (union_var, union_tags) = {
|
||||
let flex_tag_labels = tags
|
||||
.into_iter()
|
||||
.map(|(label, arity)| {
|
||||
let variables_slice = VariableSubsSlice::reserve_into_subs(env.subs, arity.into());
|
||||
for var_index in variables_slice {
|
||||
env.subs[var_index] = env.subs.fresh_unnamed_flex_var();
|
||||
}
|
||||
(label, variables_slice)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let union_tags = UnionTags::insert_slices_into_subs(env.subs, flex_tag_labels);
|
||||
let tag_union_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::TagUnion(union_tags, Variable::EMPTY_TAG_UNION)),
|
||||
);
|
||||
|
||||
(tag_union_var, union_tags)
|
||||
};
|
||||
|
||||
// Now, a hasher for this tag union is
|
||||
//
|
||||
// hash_union : hasher, [ A t11 .. t1n, ..., Q tq1 .. tqm ] -> hasher | hasher has Hasher
|
||||
// hash_union = \hasher, union ->
|
||||
// when union is
|
||||
// A x11 .. x1n -> Hash.hash (... (Hash.hash (Hash.uN hasher 0) x11) ...) x1n
|
||||
// ...
|
||||
// Q xq1 .. xqm -> Hash.hash (... (Hash.hash (Hash.uN hasher (q - 1)) xq1) ...) xqm
|
||||
//
|
||||
// where `Hash.uN` is the appropriate hasher for the discriminant value - typically a `u8`, but
|
||||
// if there are more than `u8::MAX` tags, we use `u16`, and so on.
|
||||
let union_sym = env.new_symbol("union");
|
||||
|
||||
let hasher_sym = env.new_symbol("hasher");
|
||||
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER));
|
||||
|
||||
let (discr_width, discr_precision_var, hash_discr_member) = if union_tags.len() > u64::MAX as _
|
||||
{
|
||||
// Should never happen, `usize` isn't more than 64 bits on most machines, but who knows?
|
||||
// Maybe someday soon someone will try to compile a huge Roc program on a 128-bit one.
|
||||
internal_error!("new record unlocked: you fit more than 18 billion, billion tags in a Roc program, and the compiler didn't fall over! But it will now. 🤯")
|
||||
} else if union_tags.len() > u32::MAX as _ {
|
||||
(IntLitWidth::U64, Variable::UNSIGNED64, Symbol::HASH_ADD_U64)
|
||||
} else if union_tags.len() > u16::MAX as _ {
|
||||
(IntLitWidth::U32, Variable::UNSIGNED32, Symbol::HASH_ADD_U32)
|
||||
} else if union_tags.len() > u8::MAX as _ {
|
||||
(IntLitWidth::U16, Variable::UNSIGNED16, Symbol::HASH_ADD_U16)
|
||||
} else {
|
||||
(IntLitWidth::U8, Variable::UNSIGNED8, Symbol::HASH_ADD_U8)
|
||||
};
|
||||
let discr_num_var = int_lit_width_to_variable(discr_width);
|
||||
|
||||
// Build the branches of the body
|
||||
let whole_hasher_var = env.subs.fresh_unnamed_flex_var();
|
||||
let branches = union_tags
|
||||
.iter_all()
|
||||
.enumerate()
|
||||
.map(|(discr_n, (tag, payloads))| {
|
||||
// A
|
||||
let tag_name = env.subs[tag].clone();
|
||||
// t11 .. t1n
|
||||
let payload_vars = env.subs.get_subs_slice(env.subs[payloads]).to_vec();
|
||||
// x11 .. x1n
|
||||
let payload_syms: Vec<_> = std::iter::repeat_with(|| env.unique_symbol())
|
||||
.take(payload_vars.len())
|
||||
.collect();
|
||||
|
||||
// `A x1 .. x1n` pattern
|
||||
let pattern = Pattern::AppliedTag {
|
||||
whole_var: union_var,
|
||||
tag_name,
|
||||
ext_var: Variable::EMPTY_TAG_UNION,
|
||||
// (t1, v1) (t2, v2)
|
||||
arguments: (payload_vars.iter())
|
||||
.zip(payload_syms.iter())
|
||||
.map(|(var, sym)| (*var, Loc::at_zero(Pattern::Identifier(*sym))))
|
||||
.collect(),
|
||||
};
|
||||
let branch_pattern = WhenBranchPattern {
|
||||
pattern: Loc::at_zero(pattern),
|
||||
degenerate: false,
|
||||
};
|
||||
|
||||
// discrHasher = (Hash.uN hasher n)
|
||||
let (discr_hasher_var, disc_hasher_expr) = call_hash_ability_member(
|
||||
env,
|
||||
hash_discr_member,
|
||||
(hasher_var, Expr::Var(hasher_sym)),
|
||||
(
|
||||
discr_num_var,
|
||||
Expr::Int(
|
||||
discr_num_var,
|
||||
discr_precision_var,
|
||||
format!("{}", discr_n).into_boxed_str(),
|
||||
IntValue::I128((discr_n as i128).to_ne_bytes()),
|
||||
IntBound::Exact(discr_width),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Fold up `Hash.hash (... (Hash.hash discrHasher x11) ...) x1n`
|
||||
let (body_var, body_expr) = (payload_vars.into_iter()).zip(payload_syms).fold(
|
||||
(discr_hasher_var, disc_hasher_expr),
|
||||
|total_hasher, (payload_var, payload_sym)| {
|
||||
call_hash_hash(env, total_hasher, (payload_var, Expr::Var(payload_sym)))
|
||||
},
|
||||
);
|
||||
|
||||
env.unify(whole_hasher_var, body_var);
|
||||
|
||||
WhenBranch {
|
||||
patterns: vec![branch_pattern],
|
||||
value: Loc::at_zero(body_expr),
|
||||
guard: None,
|
||||
redundant: RedundantMark::known_non_redundant(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// when union is
|
||||
// ...
|
||||
let when_var = whole_hasher_var;
|
||||
let when_expr = Expr::When {
|
||||
loc_cond: Box::new(Loc::at_zero(Expr::Var(union_sym))),
|
||||
cond_var: union_var,
|
||||
expr_var: when_var,
|
||||
region: Region::zero(),
|
||||
branches,
|
||||
branches_cond_var: union_var,
|
||||
exhaustive: ExhaustiveMark::known_exhaustive(),
|
||||
};
|
||||
|
||||
// Finally, build the closure
|
||||
// \hasher, rcd -> body
|
||||
build_outer_derived_closure(
|
||||
env,
|
||||
fn_name,
|
||||
(hasher_var, hasher_sym),
|
||||
(union_var, Pattern::Identifier(union_sym)),
|
||||
(when_var, when_expr),
|
||||
)
|
||||
}
|
||||
|
||||
/// Build a `hash` implementation for a newtype (singleton) tag union.
|
||||
/// If a tag union is a newtype, we do not need to hash its discriminant.
|
||||
fn hash_newtype_tag_union(
|
||||
env: &mut Env<'_>,
|
||||
fn_name: Symbol,
|
||||
tag: (TagName, u16),
|
||||
) -> (Variable, Expr) {
|
||||
// Suppose tags = [ A p1 .. pn ]
|
||||
// Build a generalized type t_tags = [ A t1 .. tn ],
|
||||
// with fresh t1, ..., tn, so that we can re-use the derived impl for many
|
||||
// unions of the same tag and payload arity.
|
||||
let (union_var, tag_name, payload_variables) = {
|
||||
let (label, arity) = tag;
|
||||
|
||||
let variables_slice = VariableSubsSlice::reserve_into_subs(env.subs, arity.into());
|
||||
for var_index in variables_slice {
|
||||
env.subs[var_index] = env.subs.fresh_unnamed_flex_var();
|
||||
}
|
||||
|
||||
let variables_slices_slice =
|
||||
SubsSlice::extend_new(&mut env.subs.variable_slices, [variables_slice]);
|
||||
let tag_name_index = SubsIndex::push_new(&mut env.subs.tag_names, label.clone());
|
||||
|
||||
let union_tags = UnionTags::from_slices(tag_name_index.as_slice(), variables_slices_slice);
|
||||
let tag_union_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::TagUnion(union_tags, Variable::EMPTY_TAG_UNION)),
|
||||
);
|
||||
|
||||
(
|
||||
tag_union_var,
|
||||
label,
|
||||
env.subs.get_subs_slice(variables_slice).to_vec(),
|
||||
)
|
||||
};
|
||||
|
||||
// Now, a hasher for this tag union is
|
||||
//
|
||||
// hash_union : hasher, [ A t1 .. tn ] -> hasher | hasher has Hasher
|
||||
// hash_union = \hasher, A x1 .. xn ->
|
||||
// Hash.hash (... (Hash.hash discrHasher x1) ...) xn
|
||||
let hasher_sym = env.new_symbol("hasher");
|
||||
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER));
|
||||
|
||||
// A
|
||||
let tag_name = tag_name;
|
||||
// t1 .. tn
|
||||
let payload_vars = payload_variables;
|
||||
// x11 .. x1n
|
||||
let payload_syms: Vec<_> = std::iter::repeat_with(|| env.unique_symbol())
|
||||
.take(payload_vars.len())
|
||||
.collect();
|
||||
|
||||
// `A x1 .. x1n` pattern
|
||||
let pattern = Pattern::AppliedTag {
|
||||
whole_var: union_var,
|
||||
tag_name,
|
||||
ext_var: Variable::EMPTY_TAG_UNION,
|
||||
// (t1, v1) (t2, v2)
|
||||
arguments: (payload_vars.iter())
|
||||
.zip(payload_syms.iter())
|
||||
.map(|(var, sym)| (*var, Loc::at_zero(Pattern::Identifier(*sym))))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// Fold up `Hash.hash (... (Hash.hash discrHasher x11) ...) x1n`
|
||||
let (body_var, body_expr) = (payload_vars.into_iter()).zip(payload_syms).fold(
|
||||
(hasher_var, Expr::Var(hasher_sym)),
|
||||
|total_hasher, (payload_var, payload_sym)| {
|
||||
call_hash_hash(env, total_hasher, (payload_var, Expr::Var(payload_sym)))
|
||||
},
|
||||
);
|
||||
|
||||
// Finally, build the closure
|
||||
// \hasher, rcd -> body
|
||||
build_outer_derived_closure(
|
||||
env,
|
||||
fn_name,
|
||||
(hasher_var, hasher_sym),
|
||||
(union_var, pattern),
|
||||
(body_var, body_expr),
|
||||
)
|
||||
}
|
||||
|
||||
fn call_hash_hash(
|
||||
env: &mut Env<'_>,
|
||||
hasher: (Variable, Expr),
|
||||
val: (Variable, Expr),
|
||||
) -> (Variable, Expr) {
|
||||
call_hash_ability_member(env, Symbol::HASH_HASH, hasher, val)
|
||||
}
|
||||
|
||||
fn call_hash_ability_member(
|
||||
env: &mut Env<'_>,
|
||||
member: Symbol,
|
||||
hasher: (Variable, Expr),
|
||||
val: (Variable, Expr),
|
||||
) -> (Variable, Expr) {
|
||||
let (in_hasher_var, in_hasher_expr) = hasher;
|
||||
let (in_val_var, in_val_expr) = val;
|
||||
|
||||
// build `member ...` function type. `member` here is `Hash.hash` or `Hash.addU16`.
|
||||
//
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
let exposed_hash_fn_var = env.import_builtin_symbol_var(member);
|
||||
|
||||
// (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
let this_arguments_slice =
|
||||
VariableSubsSlice::insert_into_subs(env.subs, [in_hasher_var, in_val_var]);
|
||||
let this_hash_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_out_hasher_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_hash_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_arguments_slice,
|
||||
this_hash_clos_var,
|
||||
this_out_hasher_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
// ~ (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
env.unify(exposed_hash_fn_var, this_hash_fn_var);
|
||||
|
||||
// Hash.hash : hasher, (typeof field) -[clos]-> hasher | hasher has Hasher, (typeof field) has Hash
|
||||
let hash_fn_head = Expr::AbilityMember(member, None, this_hash_fn_var);
|
||||
let hash_fn_data = Box::new((
|
||||
this_hash_fn_var,
|
||||
Loc::at_zero(hash_fn_head),
|
||||
this_hash_clos_var,
|
||||
this_out_hasher_var,
|
||||
));
|
||||
|
||||
let hash_arguments = vec![
|
||||
(in_hasher_var, Loc::at_zero(in_hasher_expr)),
|
||||
(in_val_var, Loc::at_zero(in_val_expr)),
|
||||
];
|
||||
let call_hash = Expr::Call(hash_fn_data, hash_arguments, CalledVia::Space);
|
||||
|
||||
(this_out_hasher_var, call_hash)
|
||||
}
|
||||
|
||||
fn build_outer_derived_closure(
|
||||
env: &mut Env<'_>,
|
||||
fn_name: Symbol,
|
||||
hasher: (Variable, Symbol),
|
||||
val: (Variable, Pattern),
|
||||
body: (Variable, Expr),
|
||||
) -> (Variable, Expr) {
|
||||
let (hasher_var, hasher_sym) = hasher;
|
||||
let (val_var, val_pattern) = val;
|
||||
let (body_var, body_expr) = body;
|
||||
|
||||
let (fn_var, fn_clos_var) = {
|
||||
// Create fn_var for ambient capture; we fix it up below.
|
||||
@ -156,7 +442,7 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (E
|
||||
);
|
||||
|
||||
// hasher, rcd_var -[fn_name]-> (hasher = body_var)
|
||||
let args_slice = SubsSlice::insert_into_subs(env.subs, [hasher_var, record_var]);
|
||||
let args_slice = SubsSlice::insert_into_subs(env.subs, [hasher_var, val_var]);
|
||||
env.subs.set_content(
|
||||
fn_var,
|
||||
Content::Structure(FlatType::Func(args_slice, fn_clos_var, body_var)),
|
||||
@ -179,13 +465,13 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (E
|
||||
Loc::at_zero(Pattern::Identifier(hasher_sym)),
|
||||
),
|
||||
(
|
||||
record_var,
|
||||
val_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(rcd_sym)),
|
||||
Loc::at_zero(val_pattern),
|
||||
),
|
||||
],
|
||||
loc_body: Box::new(Loc::at_zero(body)),
|
||||
loc_body: Box::new(Loc::at_zero(body_expr)),
|
||||
});
|
||||
|
||||
(clos_expr, fn_var)
|
||||
(fn_var, clos_expr)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use roc_module::{
|
||||
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
|
||||
|
||||
use crate::{
|
||||
util::{check_derivable_ext_var, debug_name_record},
|
||||
util::{check_derivable_ext_var, debug_name_record, debug_name_tag},
|
||||
DeriveError,
|
||||
};
|
||||
|
||||
@ -32,19 +32,7 @@ impl FlatEncodableKey {
|
||||
FlatEncodableKey::Set() => "set".to_string(),
|
||||
FlatEncodableKey::Dict() => "dict".to_string(),
|
||||
FlatEncodableKey::Record(fields) => debug_name_record(fields),
|
||||
FlatEncodableKey::TagUnion(tags) => {
|
||||
let mut str = String::from('[');
|
||||
tags.iter().enumerate().for_each(|(i, (tag, arity))| {
|
||||
if i > 0 {
|
||||
str.push(',');
|
||||
}
|
||||
str.push_str(tag.0.as_str());
|
||||
str.push(' ');
|
||||
str.push_str(&arity.to_string());
|
||||
});
|
||||
str.push(']');
|
||||
str
|
||||
}
|
||||
FlatEncodableKey::TagUnion(tags) => debug_name_tag(tags),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
use roc_module::{ident::Lowercase, symbol::Symbol};
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
use roc_module::{
|
||||
ident::{Lowercase, TagName},
|
||||
symbol::Symbol,
|
||||
};
|
||||
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
|
||||
|
||||
use crate::{
|
||||
util::{check_derivable_ext_var, debug_name_record},
|
||||
util::{check_derivable_ext_var, debug_name_record, debug_name_tag},
|
||||
DeriveError,
|
||||
};
|
||||
|
||||
@ -18,12 +21,14 @@ pub enum FlatHash {
|
||||
pub enum FlatHashKey {
|
||||
// Unfortunate that we must allocate here, c'est la vie
|
||||
Record(Vec<Lowercase>),
|
||||
TagUnion(Vec<(TagName, u16)>),
|
||||
}
|
||||
|
||||
impl FlatHashKey {
|
||||
pub(crate) fn debug_name(&self) -> String {
|
||||
match self {
|
||||
FlatHashKey::Record(fields) => debug_name_record(fields),
|
||||
FlatHashKey::TagUnion(tags) => debug_name_tag(tags),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -60,16 +65,43 @@ impl FlatHash {
|
||||
|
||||
Ok(Key(FlatHashKey::Record(field_names)))
|
||||
}
|
||||
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(_name_index, _, _) => {
|
||||
Err(Underivable) // yet
|
||||
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
|
||||
// The recursion var doesn't matter, because the derived implementation will only
|
||||
// look on the surface of the tag union type, and more over the payloads of the
|
||||
// arguments will be left generic for the monomorphizer to fill in with the
|
||||
// appropriate type. That is,
|
||||
// [ A t1, B t1 t2 ]
|
||||
// and
|
||||
// [ A t1, B t1 t2 ] as R
|
||||
// look the same on the surface, because `R` is only somewhere inside of the
|
||||
// `t`-prefixed payload types.
|
||||
let (tags_iter, ext) = tags.unsorted_tags_and_ext(subs, ext);
|
||||
|
||||
check_derivable_ext_var(subs, ext, |ext| {
|
||||
matches!(ext, Content::Structure(FlatType::EmptyTagUnion))
|
||||
})?;
|
||||
|
||||
let mut tag_names_and_payload_sizes: Vec<_> = tags_iter
|
||||
.tags
|
||||
.into_iter()
|
||||
.map(|(name, payload_slice)| {
|
||||
let payload_size = payload_slice.len();
|
||||
(name.clone(), payload_size as _)
|
||||
})
|
||||
.collect();
|
||||
|
||||
tag_names_and_payload_sizes.sort_by(|(t1, _), (t2, _)| t1.cmp(t2));
|
||||
|
||||
Ok(Key(FlatHashKey::TagUnion(tag_names_and_payload_sizes)))
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(names_index, _, _) => Ok(Key(FlatHashKey::TagUnion(
|
||||
subs.get_subs_slice(names_index)
|
||||
.iter()
|
||||
.map(|t| (t.clone(), 0))
|
||||
.collect(),
|
||||
))),
|
||||
FlatType::EmptyRecord => Ok(Key(FlatHashKey::Record(vec![]))),
|
||||
FlatType::EmptyTagUnion => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::EmptyTagUnion => Ok(Key(FlatHashKey::TagUnion(vec![]))),
|
||||
//
|
||||
FlatType::Erroneous(_) => Err(Underivable),
|
||||
FlatType::Func(..) => Err(Underivable),
|
||||
@ -111,7 +143,8 @@ impl FlatHash {
|
||||
},
|
||||
Content::RangedNumber(_) => Err(Underivable),
|
||||
//
|
||||
Content::RecursionVar { .. } => Err(Underivable),
|
||||
Content::RecursionVar { structure, .. } => Self::from_var(subs, structure),
|
||||
//
|
||||
Content::Error => Err(Underivable),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
|
@ -76,6 +76,7 @@ pub enum DeriveBuiltin {
|
||||
ToEncoder,
|
||||
Decoder,
|
||||
Hash,
|
||||
IsEq,
|
||||
}
|
||||
|
||||
impl TryFrom<Symbol> for DeriveBuiltin {
|
||||
@ -86,6 +87,7 @@ impl TryFrom<Symbol> for DeriveBuiltin {
|
||||
Symbol::ENCODE_TO_ENCODER => Ok(DeriveBuiltin::ToEncoder),
|
||||
Symbol::DECODE_DECODER => Ok(DeriveBuiltin::Decoder),
|
||||
Symbol::HASH_HASH => Ok(DeriveBuiltin::Hash),
|
||||
Symbol::BOOL_IS_EQ => Ok(DeriveBuiltin::IsEq),
|
||||
_ => Err(value),
|
||||
}
|
||||
}
|
||||
@ -112,6 +114,13 @@ impl Derived {
|
||||
}
|
||||
FlatHash::Key(repr) => Ok(Derived::Key(DeriveKey::Hash(repr))),
|
||||
},
|
||||
DeriveBuiltin::IsEq => {
|
||||
// If obligation checking passes, we always lower derived implementations of `isEq`
|
||||
// to the `Eq` low-level, to be fulfilled by the backends.
|
||||
Ok(Derived::SingleLambdaSetImmediate(
|
||||
Symbol::BOOL_STRUCTURAL_EQ,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_types::subs::{Content, Subs, Variable};
|
||||
|
||||
use crate::DeriveError;
|
||||
@ -42,3 +42,17 @@ pub(crate) fn debug_name_record(fields: &[Lowercase]) -> String {
|
||||
str.push('}');
|
||||
str
|
||||
}
|
||||
|
||||
pub(crate) fn debug_name_tag(tags: &[(TagName, u16)]) -> String {
|
||||
let mut str = String::from('[');
|
||||
tags.iter().enumerate().for_each(|(i, (tag, arity))| {
|
||||
if i > 0 {
|
||||
str.push(',');
|
||||
}
|
||||
str.push_str(tag.0.as_str());
|
||||
str.push(' ');
|
||||
str.push_str(&arity.to_string());
|
||||
});
|
||||
str.push(']');
|
||||
str
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ This is the general procedure I follow with some helpful links:
|
||||
|
||||
## Helpful Resources
|
||||
|
||||
- [Compiler Explorer](godbolt.org) -
|
||||
- [Compiler Explorer](https://godbolt.org/) -
|
||||
Generates assembly from most languages.
|
||||
Really good for getting a reference for what is required to do something.
|
||||
Can answer questions like "how would x be implemented in arm assembly?"
|
||||
|
@ -6850,7 +6850,7 @@ pub fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'
|
||||
return_size >= 2 * env.target_info.ptr_width() as u32
|
||||
}
|
||||
roc_target::OperatingSystem::Unix => return_size > 2 * env.target_info.ptr_width() as u32,
|
||||
roc_target::OperatingSystem::Wasi => unreachable!(),
|
||||
roc_target::OperatingSystem::Wasi => return_size > 2 * env.target_info.ptr_width() as u32,
|
||||
};
|
||||
|
||||
if return_size == 0 {
|
||||
|
@ -50,6 +50,7 @@ impl From<Layout<'_>> for CodeGenNumType {
|
||||
|| internal_error!("Tried to perform a Num low-level operation on {:?}", layout);
|
||||
match layout {
|
||||
Layout::Builtin(builtin) => match builtin {
|
||||
Builtin::Bool => I32,
|
||||
Builtin::Int(int_width) => match int_width {
|
||||
IntWidth::U8 => I32,
|
||||
IntWidth::U16 => I32,
|
||||
@ -1724,6 +1725,7 @@ impl<'a> LowLevelCall<'a> {
|
||||
let arg_type = CodeGenNumType::from(arg_layout);
|
||||
let arg_width = match arg_layout {
|
||||
Layout::Builtin(Builtin::Int(w)) => w,
|
||||
Layout::Builtin(Builtin::Bool) => IntWidth::U8,
|
||||
x => internal_error!("Num.intCast is not defined for {:?}", x),
|
||||
};
|
||||
|
||||
|
@ -20,6 +20,7 @@ roc_builtins = { path = "../builtins" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_can = { path = "../can" }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
|
||||
[target.'cfg(not(windows))'.build-dependencies]
|
||||
|
@ -11,14 +11,15 @@ const SKIP_SUBS_CACHE: bool = {
|
||||
}
|
||||
};
|
||||
|
||||
// IFTTT: crates/compiler/load/src/lib.rs
|
||||
const MODULES: &[(ModuleId, &str)] = &[
|
||||
(ModuleId::BOOL, "Bool.roc"),
|
||||
(ModuleId::DICT, "Dict.roc"),
|
||||
(ModuleId::SET, "Set.roc"),
|
||||
(ModuleId::RESULT, "Result.roc"),
|
||||
(ModuleId::NUM, "Num.roc"),
|
||||
(ModuleId::LIST, "List.roc"),
|
||||
(ModuleId::STR, "Str.roc"),
|
||||
(ModuleId::DICT, "Dict.roc"),
|
||||
(ModuleId::SET, "Set.roc"),
|
||||
(ModuleId::BOX, "Box.roc"),
|
||||
(ModuleId::ENCODE, "Encode.roc"),
|
||||
(ModuleId::DECODE, "Decode.roc"),
|
||||
@ -46,26 +47,27 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
|
||||
|
||||
#[cfg(not(windows))]
|
||||
if SKIP_SUBS_CACHE {
|
||||
write_subs_for_module_dummy(&output_path)
|
||||
write_types_for_module_dummy(&output_path)
|
||||
} else {
|
||||
write_subs_for_module_real(module_id, filename, &output_path)
|
||||
write_types_for_module_real(module_id, filename, &output_path)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = SKIP_SUBS_CACHE;
|
||||
let _ = module_id;
|
||||
write_subs_for_module_dummy(&output_path)
|
||||
write_types_for_module_dummy(&output_path)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_subs_for_module_dummy(output_path: &Path) {
|
||||
fn write_types_for_module_dummy(output_path: &Path) {
|
||||
// write out a dummy file
|
||||
std::fs::write(output_path, &[]).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn write_subs_for_module_real(module_id: ModuleId, filename: &str, output_path: &Path) {
|
||||
fn write_types_for_module_real(module_id: ModuleId, filename: &str, output_path: &Path) {
|
||||
use roc_can::module::TypeState;
|
||||
use roc_load_internal::file::{LoadingProblem, Threading};
|
||||
|
||||
let arena = Bump::new();
|
||||
@ -94,9 +96,19 @@ fn write_subs_for_module_real(module_id: ModuleId, filename: &str, output_path:
|
||||
}
|
||||
};
|
||||
|
||||
let subs = module.solved.inner();
|
||||
let subs = module.solved.into_inner();
|
||||
let exposed_vars_by_symbol: Vec<_> = module.exposed_to_host.into_iter().collect();
|
||||
let abilities = module.abilities_store;
|
||||
let solved_implementations = module.resolved_implementations;
|
||||
|
||||
let mut file = std::fs::File::create(&output_path).unwrap();
|
||||
subs.serialize(&exposed_vars_by_symbol, &mut file).unwrap();
|
||||
|
||||
let type_state = TypeState {
|
||||
subs,
|
||||
exposed_vars_by_symbol,
|
||||
abilities,
|
||||
solved_implementations,
|
||||
};
|
||||
|
||||
type_state.serialize(&mut file).unwrap();
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
use bumpalo::Bump;
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_can::module::{ExposedByModule, TypeState};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
use roc_module::symbol::ModuleId;
|
||||
use roc_reporting::report::RenderTarget;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::subs::{Subs, Variable};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const SKIP_SUBS_CACHE: bool = {
|
||||
@ -27,9 +26,9 @@ fn load<'a>(
|
||||
exposed_types: ExposedByModule,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let cached_subs = read_cached_subs();
|
||||
let cached_types = read_cached_types();
|
||||
|
||||
roc_load_internal::file::load(arena, load_start, exposed_types, cached_subs, load_config)
|
||||
roc_load_internal::file::load(arena, load_start, exposed_types, cached_types, load_config)
|
||||
}
|
||||
|
||||
/// Load using only a single thread; used when compiling to webassembly
|
||||
@ -41,7 +40,7 @@ pub fn load_single_threaded<'a>(
|
||||
render: RenderTarget,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let cached_subs = read_cached_subs();
|
||||
let cached_subs = read_cached_types();
|
||||
|
||||
roc_load_internal::file::load_single_threaded(
|
||||
arena,
|
||||
@ -157,37 +156,48 @@ pub fn load_and_typecheck_str<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
// IFTTT: crates/compiler/load/build.rs
|
||||
const BOOL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Bool.dat")) as &[_];
|
||||
const RESULT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Result.dat")) as &[_];
|
||||
const LIST: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/List.dat")) as &[_];
|
||||
const STR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Str.dat")) as &[_];
|
||||
const DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Dict.dat")) as &[_];
|
||||
const SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Set.dat")) as &[_];
|
||||
const BOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Box.dat")) as &[_];
|
||||
const RESULT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Result.dat")) as &[_];
|
||||
const NUM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Num.dat")) as &[_];
|
||||
const LIST: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/List.dat")) as &[_];
|
||||
const STR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Str.dat")) as &[_];
|
||||
const BOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Box.dat")) as &[_];
|
||||
const ENCODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Encode.dat")) as &[_];
|
||||
const DECODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Decode.dat")) as &[_];
|
||||
const HASH: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Hash.dat")) as &[_];
|
||||
|
||||
fn deserialize_help(bytes: &[u8]) -> (Subs, Vec<(Symbol, Variable)>) {
|
||||
let (subs, slice) = Subs::deserialize(bytes);
|
||||
fn deserialize_help(bytes: &[u8]) -> TypeState {
|
||||
let (state, _offset) = TypeState::deserialize(bytes);
|
||||
debug_assert_eq!(bytes.len(), _offset);
|
||||
|
||||
(subs, slice.to_vec())
|
||||
state
|
||||
}
|
||||
|
||||
fn read_cached_subs() -> MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)> {
|
||||
fn read_cached_types() -> MutMap<ModuleId, TypeState> {
|
||||
let mut output = MutMap::default();
|
||||
|
||||
// Wasm seems to re-order definitions between build time and runtime, but only in release mode.
|
||||
// That is very strange, but we can solve it separately
|
||||
if !cfg!(target_family = "wasm") && !cfg!(windows) && !SKIP_SUBS_CACHE {
|
||||
output.insert(ModuleId::BOOL, deserialize_help(BOOL));
|
||||
|
||||
output.insert(ModuleId::RESULT, deserialize_help(RESULT));
|
||||
output.insert(ModuleId::NUM, deserialize_help(NUM));
|
||||
|
||||
output.insert(ModuleId::LIST, deserialize_help(LIST));
|
||||
output.insert(ModuleId::STR, deserialize_help(STR));
|
||||
output.insert(ModuleId::DICT, deserialize_help(DICT));
|
||||
|
||||
output.insert(ModuleId::SET, deserialize_help(SET));
|
||||
output.insert(ModuleId::BOX, deserialize_help(BOX));
|
||||
|
||||
output.insert(ModuleId::DICT, deserialize_help(DICT));
|
||||
output.insert(ModuleId::SET, deserialize_help(SET));
|
||||
|
||||
output.insert(ModuleId::ENCODE, deserialize_help(ENCODE));
|
||||
output.insert(ModuleId::DECODE, deserialize_help(DECODE));
|
||||
|
||||
output.insert(ModuleId::HASH, deserialize_help(HASH));
|
||||
}
|
||||
|
||||
output
|
||||
|
@ -11,7 +11,7 @@ use roc_can::expr::Declarations;
|
||||
use roc_can::expr::PendingDerives;
|
||||
use roc_can::module::{
|
||||
canonicalize_module_defs, ExposedByModule, ExposedForModule, ExposedModuleTypes, Module,
|
||||
ResolvedImplementations,
|
||||
ResolvedImplementations, TypeState,
|
||||
};
|
||||
use roc_collections::{default_hasher, BumpMap, MutMap, MutSet, VecMap, VecSet};
|
||||
use roc_constrain::module::constrain_module;
|
||||
@ -344,6 +344,9 @@ fn start_phase<'a>(
|
||||
)
|
||||
});
|
||||
|
||||
// Add the declared abilities from the modules we import;
|
||||
// we may not know all their types yet since type-solving happens in
|
||||
// parallel, but we'll fill that in during type-checking our module.
|
||||
abilities_store
|
||||
.union(import_store.closure_from_imported(exposed_symbols));
|
||||
}
|
||||
@ -353,7 +356,7 @@ fn start_phase<'a>(
|
||||
let skip_constraint_gen = {
|
||||
// Give this its own scope to make sure that the Guard from the lock() is dropped
|
||||
// immediately after contains_key returns
|
||||
state.cached_subs.lock().contains_key(&module_id)
|
||||
state.cached_types.lock().contains_key(&module_id)
|
||||
};
|
||||
|
||||
BuildTask::CanonicalizeAndConstrain {
|
||||
@ -398,7 +401,7 @@ fn start_phase<'a>(
|
||||
&state.exposed_types,
|
||||
dep_idents,
|
||||
declarations,
|
||||
state.cached_subs.clone(),
|
||||
state.cached_types.clone(),
|
||||
derived_module,
|
||||
)
|
||||
}
|
||||
@ -927,13 +930,13 @@ struct State<'a> {
|
||||
|
||||
make_specializations_pass: MakeSpecializationsPass,
|
||||
|
||||
// cached subs (used for builtin modules, could include packages in the future too)
|
||||
cached_subs: CachedSubs,
|
||||
// cached types (used for builtin modules, could include packages in the future too)
|
||||
cached_types: CachedTypeState,
|
||||
|
||||
layout_interner: Arc<GlobalInterner<'a, Layout<'a>>>,
|
||||
}
|
||||
|
||||
type CachedSubs = Arc<Mutex<MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>>>;
|
||||
type CachedTypeState = Arc<Mutex<MutMap<ModuleId, TypeState>>>;
|
||||
|
||||
impl<'a> State<'a> {
|
||||
fn goal_phase(&self) -> Phase {
|
||||
@ -947,7 +950,7 @@ impl<'a> State<'a> {
|
||||
exposed_types: ExposedByModule,
|
||||
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
cached_types: MutMap<ModuleId, TypeState>,
|
||||
render: RenderTarget,
|
||||
number_of_workers: usize,
|
||||
exec_mode: ExecutionMode,
|
||||
@ -978,7 +981,7 @@ impl<'a> State<'a> {
|
||||
exposed_symbols_by_module: MutMap::default(),
|
||||
timings: MutMap::default(),
|
||||
layout_caches: std::vec::Vec::with_capacity(number_of_workers),
|
||||
cached_subs: Arc::new(Mutex::new(cached_subs)),
|
||||
cached_types: Arc::new(Mutex::new(cached_types)),
|
||||
render,
|
||||
exec_mode,
|
||||
make_specializations_pass: MakeSpecializationsPass::Pass(1),
|
||||
@ -1091,7 +1094,7 @@ enum BuildTask<'a> {
|
||||
var_store: VarStore,
|
||||
declarations: Declarations,
|
||||
dep_idents: IdentIdsByModule,
|
||||
cached_subs: CachedSubs,
|
||||
cached_subs: CachedTypeState,
|
||||
derived_module: SharedDerivedModule,
|
||||
},
|
||||
BuildPendingSpecializations {
|
||||
@ -1456,7 +1459,7 @@ pub fn load<'a>(
|
||||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
cached_types: MutMap<ModuleId, TypeState>,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
enum Threads {
|
||||
@ -1489,7 +1492,7 @@ pub fn load<'a>(
|
||||
load_start,
|
||||
exposed_types,
|
||||
load_config.target_info,
|
||||
cached_subs,
|
||||
cached_types,
|
||||
load_config.render,
|
||||
load_config.exec_mode,
|
||||
),
|
||||
@ -1498,7 +1501,7 @@ pub fn load<'a>(
|
||||
load_start,
|
||||
exposed_types,
|
||||
load_config.target_info,
|
||||
cached_subs,
|
||||
cached_types,
|
||||
load_config.render,
|
||||
threads,
|
||||
load_config.exec_mode,
|
||||
@ -1513,7 +1516,7 @@ pub fn load_single_threaded<'a>(
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
cached_types: MutMap<ModuleId, TypeState>,
|
||||
render: RenderTarget,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
@ -1539,7 +1542,7 @@ pub fn load_single_threaded<'a>(
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
cached_types,
|
||||
render,
|
||||
number_of_workers,
|
||||
exec_mode,
|
||||
@ -1761,7 +1764,7 @@ fn load_multi_threaded<'a>(
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
cached_types: MutMap<ModuleId, TypeState>,
|
||||
render: RenderTarget,
|
||||
available_threads: usize,
|
||||
exec_mode: ExecutionMode,
|
||||
@ -1803,7 +1806,7 @@ fn load_multi_threaded<'a>(
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
cached_types,
|
||||
render,
|
||||
num_workers,
|
||||
exec_mode,
|
||||
@ -4192,7 +4195,7 @@ impl<'a> BuildTask<'a> {
|
||||
exposed_types: &ExposedByModule,
|
||||
dep_idents: IdentIdsByModule,
|
||||
declarations: Declarations,
|
||||
cached_subs: CachedSubs,
|
||||
cached_subs: CachedTypeState,
|
||||
derived_module: SharedDerivedModule,
|
||||
) -> Self {
|
||||
let exposed_by_module = exposed_types.retain_modules(imported_modules.keys());
|
||||
@ -4332,6 +4335,9 @@ pub fn add_imports(
|
||||
import_variables.push(list_len_type);
|
||||
}
|
||||
|
||||
// Fill in the implementation information of the abilities from the modules we import, which we
|
||||
// now know because all imported modules should be solved by now.
|
||||
//
|
||||
// TODO: see if we can reduce the amount of specializations we need to import.
|
||||
// One idea is to just always assume external modules fulfill their specialization obligations
|
||||
// and save lambda set resolution for mono.
|
||||
@ -4466,7 +4472,11 @@ fn run_solve_solve(
|
||||
// ability.
|
||||
let exposed_vars_by_symbol: Vec<_> = solved_env
|
||||
.vars_by_symbol()
|
||||
.filter(|(k, _)| exposed_symbols.contains(k) || is_specialization_symbol(*k))
|
||||
.filter(|(k, _)| {
|
||||
exposed_symbols.contains(k)
|
||||
|| is_specialization_symbol(*k)
|
||||
|| k.is_exposed_for_builtin_derivers()
|
||||
})
|
||||
.collect();
|
||||
|
||||
(
|
||||
@ -4499,7 +4509,7 @@ fn run_solve<'a>(
|
||||
var_store: VarStore,
|
||||
decls: Declarations,
|
||||
dep_idents: IdentIdsByModule,
|
||||
cached_subs: CachedSubs,
|
||||
cached_types: CachedTypeState,
|
||||
derived_module: SharedDerivedModule,
|
||||
) -> Msg<'a> {
|
||||
let solve_start = Instant::now();
|
||||
@ -4515,7 +4525,7 @@ fn run_solve<'a>(
|
||||
|
||||
let (solved_subs, solved_implementations, exposed_vars_by_symbol, problems, abilities_store) = {
|
||||
if module_id.is_builtin() {
|
||||
match cached_subs.lock().remove(&module_id) {
|
||||
match cached_types.lock().remove(&module_id) {
|
||||
None => run_solve_solve(
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
@ -4525,17 +4535,18 @@ fn run_solve<'a>(
|
||||
module,
|
||||
derived_module,
|
||||
),
|
||||
Some((subs, exposed_vars_by_symbol)) => {
|
||||
(
|
||||
Solved(subs),
|
||||
// TODO(abilities) cache abilities for builtins
|
||||
VecMap::default(),
|
||||
exposed_vars_by_symbol.to_vec(),
|
||||
vec![],
|
||||
// TODO(abilities) cache abilities for builtins
|
||||
AbilitiesStore::default(),
|
||||
)
|
||||
}
|
||||
Some(TypeState {
|
||||
subs,
|
||||
exposed_vars_by_symbol,
|
||||
abilities,
|
||||
solved_implementations,
|
||||
}) => (
|
||||
Solved(subs),
|
||||
solved_implementations,
|
||||
exposed_vars_by_symbol,
|
||||
vec![],
|
||||
abilities,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
run_solve_solve(
|
||||
|
@ -1,9 +1,26 @@
|
||||
#![warn(clippy::dbg_macro)]
|
||||
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
use roc_module::symbol::ModuleId;
|
||||
pub mod docs;
|
||||
pub mod file;
|
||||
mod work;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
mod wasm_instant;
|
||||
|
||||
pub const BUILTIN_MODULES: &[(ModuleId, &str)] = &[
|
||||
(ModuleId::BOOL, "Bool"),
|
||||
(ModuleId::RESULT, "Result"),
|
||||
(ModuleId::NUM, "Num"),
|
||||
(ModuleId::LIST, "List"),
|
||||
(ModuleId::STR, "Str"),
|
||||
(ModuleId::DICT, "Dict"),
|
||||
(ModuleId::SET, "Set"),
|
||||
(ModuleId::BOX, "Box"),
|
||||
(ModuleId::ENCODE, "Encode"),
|
||||
(ModuleId::DECODE, "Decode"),
|
||||
(ModuleId::HASH, "Hash"),
|
||||
(ModuleId::JSON, "Json"),
|
||||
];
|
||||
|
@ -22,7 +22,7 @@ initialModel = \start ->
|
||||
}
|
||||
|
||||
|
||||
cheapestOpen : (position -> F64), Model position -> Result position [KeyNotFound]*
|
||||
cheapestOpen : (position -> F64), Model position -> Result position [KeyNotFound]* | position has Eq
|
||||
cheapestOpen = \costFunction, model ->
|
||||
|
||||
folder = \resSmallestSoFar, position ->
|
||||
@ -47,7 +47,7 @@ cheapestOpen = \costFunction, model ->
|
||||
|
||||
|
||||
|
||||
reconstructPath : Dict position position, position -> List position
|
||||
reconstructPath : Dict position position, position -> List position | position has Eq
|
||||
reconstructPath = \cameFrom, goal ->
|
||||
when Dict.get cameFrom goal is
|
||||
Err KeyNotFound ->
|
||||
@ -56,7 +56,7 @@ reconstructPath = \cameFrom, goal ->
|
||||
Ok next ->
|
||||
List.append (reconstructPath cameFrom next) goal
|
||||
|
||||
updateCost : position, position, Model position -> Model position
|
||||
updateCost : position, position, Model position -> Model position | position has Eq
|
||||
updateCost = \current, neighbour, model ->
|
||||
newCameFrom = Dict.insert model.cameFrom neighbour current
|
||||
|
||||
@ -80,12 +80,12 @@ updateCost = \current, neighbour, model ->
|
||||
model
|
||||
|
||||
|
||||
findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [KeyNotFound]*
|
||||
findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [KeyNotFound]* | position has Eq
|
||||
findPath = \{ costFunction, moveFunction, start, end } ->
|
||||
astar costFunction moveFunction end (initialModel start)
|
||||
|
||||
|
||||
astar : (position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]*
|
||||
astar : (position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]* | position has Eq
|
||||
astar = \costFn, moveFn, goal, model ->
|
||||
when cheapestOpen (\position -> costFn goal position) model is
|
||||
Err _ ->
|
||||
|
@ -481,12 +481,12 @@ fn load_astar() {
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"findPath" => "{ costFunction : position, position -> F64, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [KeyNotFound]*",
|
||||
"findPath" => "{ costFunction : position, position -> F64, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [KeyNotFound]* | position has Eq",
|
||||
"initialModel" => "position -> Model position",
|
||||
"reconstructPath" => "Dict position position, position -> List position",
|
||||
"updateCost" => "position, position, Model position -> Model position",
|
||||
"cheapestOpen" => "(position -> F64), Model position -> Result position [KeyNotFound]*",
|
||||
"astar" => "(position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]*",
|
||||
"reconstructPath" => "Dict position position, position -> List position | position has Eq",
|
||||
"updateCost" => "position, position, Model position -> Model position | position has Eq",
|
||||
"cheapestOpen" => "(position -> F64), Model position -> Result position [KeyNotFound]* | position has Eq",
|
||||
"astar" => "(position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]* | position has Eq",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -217,7 +217,6 @@ macro_rules! map_symbol_to_lowlevel {
|
||||
LowLevel::StrFromInt => unimplemented!(),
|
||||
LowLevel::StrFromFloat => unimplemented!(),
|
||||
LowLevel::NumIsFinite => unimplemented!(),
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -310,8 +309,8 @@ map_symbol_to_lowlevel! {
|
||||
NumShiftRightBy <= NUM_SHIFT_RIGHT,
|
||||
NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL,
|
||||
NumToStr <= NUM_TO_STR,
|
||||
Eq <= BOOL_EQ,
|
||||
NotEq <= BOOL_NEQ,
|
||||
Eq <= BOOL_STRUCTURAL_EQ,
|
||||
NotEq <= BOOL_STRUCTURAL_NOT_EQ,
|
||||
And <= BOOL_AND,
|
||||
Or <= BOOL_OR,
|
||||
Not <= BOOL_NOT,
|
||||
|
@ -51,6 +51,7 @@ pub const DERIVABLE_ABILITIES: &[(Symbol, &[Symbol])] = &[
|
||||
(Symbol::ENCODE_ENCODING, &[Symbol::ENCODE_TO_ENCODER]),
|
||||
(Symbol::DECODE_DECODING, &[Symbol::DECODE_DECODER]),
|
||||
(Symbol::HASH_HASH_ABILITY, &[Symbol::HASH_HASH]),
|
||||
(Symbol::BOOL_EQ, &[Symbol::BOOL_IS_EQ]),
|
||||
];
|
||||
|
||||
/// In Debug builds only, Symbol has a name() method that lets
|
||||
@ -98,6 +99,17 @@ impl Symbol {
|
||||
DERIVABLE_ABILITIES.iter().find(|(name, _)| *name == self)
|
||||
}
|
||||
|
||||
/// A symbol that should never be exposed to userspace, but needs to be exposed
|
||||
/// to compiled modules for deriving abilities for structural types.
|
||||
pub fn is_exposed_for_builtin_derivers(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
// The `structuralEq` call used deriving structural equality, which will wrap the `Eq`
|
||||
// low-level implementation.
|
||||
&Self::BOOL_STRUCTURAL_EQ
|
||||
)
|
||||
}
|
||||
|
||||
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName {
|
||||
interns
|
||||
.module_ids
|
||||
@ -796,6 +808,7 @@ macro_rules! define_builtins {
|
||||
$(exposed_type=$exposed_type:literal)?
|
||||
$(in_scope_for_hints=$in_scope_for_hints:literal)?
|
||||
)*
|
||||
$(unexposed $u_ident_id:literal $u_ident_const:ident: $u_ident_name:literal)*
|
||||
}
|
||||
)+
|
||||
num_modules: $total:literal
|
||||
@ -943,6 +956,9 @@ macro_rules! define_builtins {
|
||||
$(
|
||||
pub const $ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId($ident_id));
|
||||
)*
|
||||
$(
|
||||
pub const $u_ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId($u_ident_id));
|
||||
)*
|
||||
)+
|
||||
|
||||
/// The default `Apply` types that should be in scope,
|
||||
@ -1239,8 +1255,12 @@ define_builtins! {
|
||||
4 BOOL_OR: "or"
|
||||
5 BOOL_NOT: "not"
|
||||
6 BOOL_XOR: "xor"
|
||||
7 BOOL_EQ: "isEq"
|
||||
8 BOOL_NEQ: "isNotEq"
|
||||
7 BOOL_NEQ: "isNotEq"
|
||||
8 BOOL_EQ: "Eq" exposed_type=true
|
||||
9 BOOL_IS_EQ: "isEq"
|
||||
10 BOOL_IS_EQ_IMPL: "boolIsEq"
|
||||
unexposed 11 BOOL_STRUCTURAL_EQ: "structuralEq"
|
||||
unexposed 12 BOOL_STRUCTURAL_NOT_EQ: "structuralNotEq"
|
||||
}
|
||||
5 STR: "Str" => {
|
||||
0 STR_STR: "Str" exposed_apply_type=true // the Str.Str type alias
|
||||
@ -1377,6 +1397,9 @@ define_builtins! {
|
||||
74 LIST_MAP_TRY: "mapTry"
|
||||
75 LIST_WALK_TRY: "walkTry"
|
||||
76 LIST_WALK_BACKWARDS_UNTIL: "walkBackwardsUntil"
|
||||
77 LIST_COUNT_IF: "countIf"
|
||||
78 LIST_WALK_FROM: "walkFrom"
|
||||
79 LIST_WALK_FROM_UNTIL: "walkFromUntil"
|
||||
}
|
||||
7 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias
|
||||
@ -1412,6 +1435,8 @@ define_builtins! {
|
||||
15 DICT_WITH_CAPACITY: "withCapacity"
|
||||
16 DICT_CAPACITY: "capacity"
|
||||
17 DICT_UPDATE: "update"
|
||||
|
||||
18 DICT_LIST_GET_UNSAFE: "listGetUnsafe"
|
||||
}
|
||||
9 SET: "Set" => {
|
||||
0 SET_SET: "Set" exposed_type=true // the Set.Set type alias
|
||||
@ -1511,6 +1536,7 @@ define_builtins! {
|
||||
14 HASH_COMPLETE: "complete"
|
||||
15 HASH_HASH_STR_BYTES: "hashStrBytes"
|
||||
16 HASH_HASH_LIST: "hashList"
|
||||
17 HASH_HASH_UNORDERED: "hashUnordered"
|
||||
}
|
||||
14 JSON: "Json" => {
|
||||
0 JSON_JSON: "Json"
|
||||
|
@ -2604,22 +2604,25 @@ fn from_can_let<'a>(
|
||||
);
|
||||
}
|
||||
|
||||
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
|
||||
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
|
||||
} else {
|
||||
let outer_symbol = env.unique_symbol();
|
||||
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
|
||||
match def.loc_expr.value {
|
||||
roc_can::expr::Expr::Var(outer_symbol) if !procs.is_module_thunk(outer_symbol) => {
|
||||
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
|
||||
}
|
||||
_ => {
|
||||
let outer_symbol = env.unique_symbol();
|
||||
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
|
||||
|
||||
// convert the def body, store in outer_symbol
|
||||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
outer_symbol,
|
||||
env.arena.alloc(stmt),
|
||||
)
|
||||
// convert the def body, store in outer_symbol
|
||||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
outer_symbol,
|
||||
env.arena.alloc(stmt),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3902,7 +3905,7 @@ fn specialize_naked_symbol<'a>(
|
||||
std::vec::Vec::new(),
|
||||
layout_cache,
|
||||
assigned,
|
||||
env.arena.alloc(Stmt::Ret(assigned)),
|
||||
hole,
|
||||
);
|
||||
|
||||
return result;
|
||||
|
@ -879,15 +879,8 @@ impl<'a> UnionLayout<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tag_id_layout(&self) -> Layout<'a> {
|
||||
// TODO is it beneficial to return a more specific layout?
|
||||
// e.g. Layout::bool() and Layout::VOID
|
||||
match self.discriminant() {
|
||||
Discriminant::U0 => Layout::u8(),
|
||||
Discriminant::U1 => Layout::u8(),
|
||||
Discriminant::U8 => Layout::u8(),
|
||||
Discriminant::U16 => Layout::u16(),
|
||||
}
|
||||
pub fn tag_id_layout(&self) -> Layout<'static> {
|
||||
self.discriminant().layout()
|
||||
}
|
||||
|
||||
fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], target_info: TargetInfo) -> bool {
|
||||
@ -1138,6 +1131,17 @@ impl Discriminant {
|
||||
pub const fn alignment_bytes(&self) -> u32 {
|
||||
self.stack_size()
|
||||
}
|
||||
|
||||
pub const fn layout(&self) -> Layout<'static> {
|
||||
// TODO is it beneficial to return a more specific layout?
|
||||
// e.g. Layout::bool() and Layout::VOID
|
||||
match self {
|
||||
Discriminant::U0 => Layout::u8(),
|
||||
Discriminant::U1 => Layout::u8(),
|
||||
Discriminant::U8 => Layout::u8(),
|
||||
Discriminant::U16 => Layout::u16(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom type so we can get the numeric representation of a symbol in tests (so `#UserApp.3`
|
||||
@ -2210,7 +2214,16 @@ impl<'a> Layout<'a> {
|
||||
// completely, but for now we represent it with the empty tag union
|
||||
cacheable(Ok(Layout::VOID))
|
||||
}
|
||||
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
|
||||
FlexAbleVar(_, _) | RigidAbleVar(_, _) => {
|
||||
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_NO_UNBOUND_LAYOUT, {
|
||||
todo_abilities!("Able var is unbound!");
|
||||
});
|
||||
|
||||
// If we encounter an unbound type var (e.g. `*` or `a`)
|
||||
// then it's zero-sized; In the future we may drop this argument
|
||||
// completely, but for now we represent it with the empty tag union
|
||||
cacheable(Ok(Layout::VOID))
|
||||
}
|
||||
RecursionVar { structure, .. } => {
|
||||
let structure_content = env.subs.get_content_without_compacting(structure);
|
||||
Self::new_help(env, structure, *structure_content)
|
||||
@ -2713,7 +2726,7 @@ impl<'a> Layout<'a> {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8))
|
||||
}
|
||||
|
||||
pub fn u16() -> Layout<'a> {
|
||||
pub const fn u16() -> Layout<'a> {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U16))
|
||||
}
|
||||
|
||||
|
9
crates/compiler/serialize/Cargo.toml
Normal file
9
crates/compiler/serialize/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "roc_serialize"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
385
crates/compiler/serialize/src/bytes.rs
Normal file
385
crates/compiler/serialize/src/bytes.rs
Normal file
@ -0,0 +1,385 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
io::{self, Write},
|
||||
};
|
||||
|
||||
use roc_collections::{MutMap, VecMap};
|
||||
|
||||
pub fn serialize_slice<T: Copy>(
|
||||
slice: &[T],
|
||||
writer: &mut impl Write,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
let alignment = std::mem::align_of::<T>();
|
||||
let padding_bytes = next_multiple_of(written, alignment) - written;
|
||||
|
||||
for _ in 0..padding_bytes {
|
||||
writer.write_all(&[0])?;
|
||||
}
|
||||
|
||||
let bytes_slice = unsafe { slice_as_bytes(slice) };
|
||||
writer.write_all(bytes_slice)?;
|
||||
|
||||
Ok(written + padding_bytes + bytes_slice.len())
|
||||
}
|
||||
|
||||
pub fn deserialize_slice<T: Copy>(bytes: &[u8], length: usize, mut offset: usize) -> (&[T], usize) {
|
||||
let alignment = std::mem::align_of::<T>();
|
||||
let size = std::mem::size_of::<T>();
|
||||
|
||||
offset = next_multiple_of(offset, alignment);
|
||||
|
||||
let byte_length = length * size;
|
||||
let byte_slice = &bytes[offset..][..byte_length];
|
||||
|
||||
let slice = unsafe { std::slice::from_raw_parts(byte_slice.as_ptr() as *const T, length) };
|
||||
|
||||
(slice, offset + byte_length)
|
||||
}
|
||||
|
||||
pub fn deserialize_vec<T: Clone + Copy>(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (Vec<T>, usize) {
|
||||
let (slice, offset) = deserialize_slice(bytes, length, offset);
|
||||
(slice.to_vec(), offset)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct VecSlice<T> {
|
||||
pub start: u32,
|
||||
pub length: u16,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> VecSlice<T> {
|
||||
const fn len(&self) -> usize {
|
||||
self.length as usize
|
||||
}
|
||||
|
||||
fn indices(&self) -> std::ops::Range<usize> {
|
||||
self.start as usize..(self.start as usize + self.length as usize)
|
||||
}
|
||||
|
||||
fn extend_new(vec: &mut Vec<T>, it: impl IntoIterator<Item = T>) -> Self {
|
||||
let start = vec.len();
|
||||
|
||||
vec.extend(it);
|
||||
|
||||
let end = vec.len();
|
||||
|
||||
Self {
|
||||
start: start as u32,
|
||||
length: (end - start) as u16,
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_slice_of_slices<'a, T, U>(
|
||||
slice_of_slices: &[U],
|
||||
writer: &mut impl Write,
|
||||
written: usize,
|
||||
) -> io::Result<usize>
|
||||
where
|
||||
T: 'a + Copy,
|
||||
U: 'a + Borrow<[T]> + Sized,
|
||||
{
|
||||
let mut item_buf: Vec<T> = Vec::new();
|
||||
let mut serialized_slices: Vec<VecSlice<T>> = Vec::new();
|
||||
|
||||
for slice in slice_of_slices {
|
||||
let slice = VecSlice::extend_new(&mut item_buf, slice.borrow().iter().copied());
|
||||
serialized_slices.push(slice);
|
||||
}
|
||||
|
||||
let written = serialize_slice(&serialized_slices, writer, written)?;
|
||||
serialize_slice(&item_buf, writer, written)
|
||||
}
|
||||
|
||||
pub fn deserialize_slice_of_slices<T, Container>(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (Vec<Container>, usize)
|
||||
where
|
||||
T: Copy,
|
||||
Container: From<Vec<T>>,
|
||||
{
|
||||
let (serialized_slices, offset) = deserialize_slice::<VecSlice<T>>(bytes, length, offset);
|
||||
|
||||
let (vars_slice, offset) = {
|
||||
let total_items = serialized_slices.iter().map(|s| s.len()).sum();
|
||||
deserialize_slice::<T>(bytes, total_items, offset)
|
||||
};
|
||||
|
||||
let mut slice_of_slices = Vec::with_capacity(length);
|
||||
for slice in serialized_slices {
|
||||
let deserialized_slice = &vars_slice[slice.indices()];
|
||||
slice_of_slices.push(deserialized_slice.to_vec().into())
|
||||
}
|
||||
|
||||
(slice_of_slices, offset)
|
||||
}
|
||||
|
||||
pub fn serialize_map<K: Clone, V: Clone, W: Write>(
|
||||
map: &MutMap<K, V>,
|
||||
ser_keys: fn(&[K], &mut W, usize) -> io::Result<usize>,
|
||||
ser_values: fn(&[V], &mut W, usize) -> io::Result<usize>,
|
||||
writer: &mut W,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
let keys = map.keys().cloned().collect::<Vec<_>>();
|
||||
let values = map.values().cloned().collect::<Vec<_>>();
|
||||
|
||||
let written = ser_keys(keys.as_slice(), writer, written)?;
|
||||
let written = ser_values(values.as_slice(), writer, written)?;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn deserialize_map<K, V>(
|
||||
bytes: &[u8],
|
||||
deser_keys: fn(&[u8], usize, usize) -> (Vec<K>, usize),
|
||||
deser_values: fn(&[u8], usize, usize) -> (Vec<V>, usize),
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (MutMap<K, V>, usize)
|
||||
where
|
||||
K: Clone + std::hash::Hash + Eq,
|
||||
V: Clone,
|
||||
{
|
||||
let (keys, offset) = deser_keys(bytes, length, offset);
|
||||
let (values, offset) = deser_values(bytes, length, offset);
|
||||
|
||||
(
|
||||
MutMap::from_iter((keys.iter().cloned()).zip(values.iter().cloned())),
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn serialize_vec_map<K, V, W: Write>(
|
||||
map: &VecMap<K, V>,
|
||||
ser_keys: fn(&[K], &mut W, usize) -> io::Result<usize>,
|
||||
ser_values: fn(&[V], &mut W, usize) -> io::Result<usize>,
|
||||
writer: &mut W,
|
||||
written: usize,
|
||||
) -> io::Result<usize> {
|
||||
let (keys, values) = map.unzip_slices();
|
||||
|
||||
let written = ser_keys(keys, writer, written)?;
|
||||
let written = ser_values(values, writer, written)?;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn deserialize_vec_map<K, V>(
|
||||
bytes: &[u8],
|
||||
deser_keys: fn(&[u8], usize, usize) -> (Vec<K>, usize),
|
||||
deser_values: fn(&[u8], usize, usize) -> (Vec<V>, usize),
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (VecMap<K, V>, usize)
|
||||
where
|
||||
K: PartialEq,
|
||||
{
|
||||
let (keys, offset) = deser_keys(bytes, length, offset);
|
||||
let (values, offset) = deser_values(bytes, length, offset);
|
||||
|
||||
(unsafe { VecMap::zip(keys, values) }, offset)
|
||||
}
|
||||
|
||||
unsafe fn slice_as_bytes<T>(slice: &[T]) -> &[u8] {
|
||||
let ptr = slice.as_ptr();
|
||||
let byte_length = std::mem::size_of::<T>() * slice.len();
|
||||
|
||||
std::slice::from_raw_parts(ptr as *const u8, byte_length)
|
||||
}
|
||||
|
||||
// TODO check on https://github.com/rust-lang/rust/issues/88581 some time in the future
|
||||
pub const fn next_multiple_of(lhs: usize, rhs: usize) -> usize {
|
||||
match lhs % rhs {
|
||||
0 => lhs,
|
||||
r => lhs + (rhs - r),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use roc_collections::{MutMap, VecMap, VecSet};
|
||||
|
||||
use super::{
|
||||
deserialize_map, deserialize_slice, deserialize_slice_of_slices, deserialize_vec,
|
||||
deserialize_vec_map, serialize_map, serialize_slice, serialize_slice_of_slices,
|
||||
serialize_vec_map,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn serde_empty_slice() {
|
||||
let mut buf = vec![];
|
||||
serialize_slice(&[] as &[u8], &mut buf, 0).unwrap();
|
||||
assert!(buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_slice::<u8>(&buf, 0, 0);
|
||||
assert!(out.is_empty());
|
||||
assert_eq!(size, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_slice() {
|
||||
let input: &[u64] = &[15u64, 23, 37, 89];
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_slice(input, &mut buf, 0).unwrap();
|
||||
assert!(!buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_slice::<u64>(&buf, 4, 0);
|
||||
assert_eq!(out, input);
|
||||
assert_eq!(size, 4 * 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_vec() {
|
||||
let input: &[u64] = &[15u64, 23, 37, 89];
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_slice(input, &mut buf, 0).unwrap();
|
||||
assert!(!buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_vec::<u64>(&buf, 4, 0);
|
||||
assert_eq!(out, input);
|
||||
assert_eq!(size, buf.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_empty_slice_of_slices() {
|
||||
let input: &[&[u64]] = &[];
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_slice_of_slices(input, &mut buf, 0).unwrap();
|
||||
assert!(buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_slice_of_slices::<u64, Vec<_>>(&buf, 0, 0);
|
||||
assert!(out.is_empty());
|
||||
assert_eq!(size, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_slice_of_slices() {
|
||||
let input: &[&[u64]] = &[&[15, 23, 47], &[61, 72], &[85, 91]];
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_slice_of_slices(input, &mut buf, 0).unwrap();
|
||||
assert!(!buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_slice_of_slices::<u64, Vec<_>>(&buf, 3, 0);
|
||||
assert_eq!(out, input);
|
||||
assert_eq!(size, buf.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_slice_of_slices_into_vec_set() {
|
||||
let input: &[&[u64]] = &[&[15, 23, 47], &[61, 72], &[85, 91]];
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_slice_of_slices(input, &mut buf, 0).unwrap();
|
||||
assert!(!buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_slice_of_slices::<u64, VecSet<_>>(&buf, 3, 0);
|
||||
assert_eq!(size, buf.len());
|
||||
|
||||
let mut out = out.into_iter();
|
||||
assert_eq!(out.next().unwrap().into_vec(), &[15, 23, 47]);
|
||||
assert_eq!(out.next().unwrap().into_vec(), &[61, 72]);
|
||||
assert_eq!(out.next().unwrap().into_vec(), &[85, 91]);
|
||||
assert!(out.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_empty_map() {
|
||||
let input: MutMap<u64, u64> = Default::default();
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_map(&input, serialize_slice, serialize_slice, &mut buf, 0).unwrap();
|
||||
assert!(buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_map::<u64, u64>(&buf, deserialize_vec, deserialize_vec, 0, 0);
|
||||
assert!(out.is_empty());
|
||||
assert_eq!(size, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_map() {
|
||||
let mut input: MutMap<u64, Vec<u64>> = Default::default();
|
||||
input.insert(51, vec![15, 23, 37]);
|
||||
input.insert(39, vec![17, 91, 43]);
|
||||
input.insert(82, vec![90, 35, 76]);
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_map(
|
||||
&input,
|
||||
serialize_slice,
|
||||
serialize_slice_of_slices,
|
||||
&mut buf,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_map::<u64, Vec<u64>>(
|
||||
&buf,
|
||||
deserialize_vec,
|
||||
deserialize_slice_of_slices,
|
||||
3,
|
||||
0,
|
||||
);
|
||||
assert_eq!(out, input);
|
||||
assert_eq!(size, buf.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_empty_vec_map() {
|
||||
let input: VecMap<u64, u64> = Default::default();
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_vec_map(&input, serialize_slice, serialize_slice, &mut buf, 0).unwrap();
|
||||
assert!(buf.is_empty());
|
||||
|
||||
let (out, size) =
|
||||
deserialize_vec_map::<u64, u64>(&buf, deserialize_vec, deserialize_vec, 0, 0);
|
||||
assert!(out.is_empty());
|
||||
assert_eq!(size, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_vec_map() {
|
||||
let mut input: VecMap<u64, Vec<u64>> = Default::default();
|
||||
input.insert(51, vec![15, 23, 37]);
|
||||
input.insert(39, vec![17, 91, 43]);
|
||||
input.insert(82, vec![90, 35, 76]);
|
||||
|
||||
let mut buf = vec![];
|
||||
serialize_vec_map(
|
||||
&input,
|
||||
serialize_slice,
|
||||
serialize_slice_of_slices,
|
||||
&mut buf,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!buf.is_empty());
|
||||
|
||||
let (out, size) = deserialize_vec_map::<u64, Vec<u64>>(
|
||||
&buf,
|
||||
deserialize_vec,
|
||||
deserialize_slice_of_slices,
|
||||
3,
|
||||
0,
|
||||
);
|
||||
assert_eq!(out.unzip_slices(), input.unzip_slices());
|
||||
assert_eq!(size, buf.len());
|
||||
}
|
||||
}
|
1
crates/compiler/serialize/src/lib.rs
Normal file
1
crates/compiler/serialize/src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod bytes;
|
@ -5,7 +5,8 @@ use roc_error_macros::{internal_error, todo_abilities};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_solve_problem::{
|
||||
NotDerivableContext, NotDerivableDecode, TypeError, UnderivableReason, Unfulfilled,
|
||||
NotDerivableContext, NotDerivableDecode, NotDerivableEq, TypeError, UnderivableReason,
|
||||
Unfulfilled,
|
||||
};
|
||||
use roc_types::num::NumericRange;
|
||||
use roc_types::subs::{
|
||||
@ -49,7 +50,14 @@ pub struct PendingDerivesTable(
|
||||
);
|
||||
|
||||
impl PendingDerivesTable {
|
||||
pub fn new(subs: &mut Subs, aliases: &mut Aliases, pending_derives: PendingDerives) -> Self {
|
||||
pub fn new(
|
||||
subs: &mut Subs,
|
||||
aliases: &mut Aliases,
|
||||
pending_derives: PendingDerives,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
) -> Self {
|
||||
let mut table = VecMap::with_capacity(pending_derives.len());
|
||||
|
||||
for (opaque, (typ, derives)) in pending_derives.into_iter() {
|
||||
@ -65,8 +73,16 @@ impl PendingDerivesTable {
|
||||
let derive_key = RequestedDeriveKey { opaque, ability };
|
||||
|
||||
// Neither rank nor pools should matter here.
|
||||
let opaque_var =
|
||||
type_to_var(subs, Rank::toplevel(), &mut Pools::default(), aliases, &typ);
|
||||
let opaque_var = type_to_var(
|
||||
subs,
|
||||
Rank::toplevel(),
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
&mut Pools::default(),
|
||||
aliases,
|
||||
&typ,
|
||||
);
|
||||
let real_var = match subs.get_content_without_compacting(opaque_var) {
|
||||
Content::Alias(_, _, real_var, AliasKind::Opaque) => real_var,
|
||||
_ => internal_error!("Non-opaque in derives table"),
|
||||
@ -276,6 +292,8 @@ impl ObligationCache {
|
||||
Some(DeriveHash::is_derivable(self, abilities_store, subs, var))
|
||||
}
|
||||
|
||||
Symbol::BOOL_EQ => Some(DeriveEq::is_derivable(self, abilities_store, subs, var)),
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@ -420,7 +438,7 @@ impl ObligationCache {
|
||||
|
||||
#[inline(always)]
|
||||
#[rustfmt::skip]
|
||||
fn is_builtin_number_alias(symbol: Symbol) -> bool {
|
||||
fn is_builtin_int_alias(symbol: Symbol) -> bool {
|
||||
matches!(symbol,
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8
|
||||
| Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16
|
||||
@ -433,12 +451,32 @@ fn is_builtin_number_alias(symbol: Symbol) -> bool {
|
||||
| Symbol::NUM_I64 | Symbol::NUM_SIGNED64
|
||||
| Symbol::NUM_I128 | Symbol::NUM_SIGNED128
|
||||
| Symbol::NUM_NAT | Symbol::NUM_NATURAL
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[rustfmt::skip]
|
||||
fn is_builtin_float_alias(symbol: Symbol) -> bool {
|
||||
matches!(symbol,
|
||||
| Symbol::NUM_F32 | Symbol::NUM_BINARY32
|
||||
| Symbol::NUM_F64 | Symbol::NUM_BINARY64
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[rustfmt::skip]
|
||||
fn is_builtin_dec_alias(symbol: Symbol) -> bool {
|
||||
matches!(symbol,
|
||||
| Symbol::NUM_DEC | Symbol::NUM_DECIMAL,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[rustfmt::skip]
|
||||
fn is_builtin_number_alias(symbol: Symbol) -> bool {
|
||||
is_builtin_int_alias(symbol) || is_builtin_float_alias(symbol) || is_builtin_dec_alias(symbol)
|
||||
}
|
||||
|
||||
struct NotDerivable {
|
||||
var: Variable,
|
||||
context: NotDerivableContext,
|
||||
@ -986,6 +1024,106 @@ impl DerivableVisitor for DeriveHash {
|
||||
}
|
||||
}
|
||||
|
||||
struct DeriveEq;
|
||||
impl DerivableVisitor for DeriveEq {
|
||||
const ABILITY: Symbol = Symbol::BOOL_EQ;
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
|
||||
is_builtin_int_alias(symbol) || is_builtin_dec_alias(symbol)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursion(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
|
||||
if matches!(
|
||||
symbol,
|
||||
Symbol::LIST_LIST
|
||||
| Symbol::SET_SET
|
||||
| Symbol::DICT_DICT
|
||||
| Symbol::STR_STR
|
||||
| Symbol::BOX_BOX_TYPE,
|
||||
) {
|
||||
Ok(Descend(true))
|
||||
} else {
|
||||
Err(NotDerivable {
|
||||
var,
|
||||
context: NotDerivableContext::NoContext,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_record(
|
||||
subs: &Subs,
|
||||
var: Variable,
|
||||
fields: RecordFields,
|
||||
) -> Result<Descend, NotDerivable> {
|
||||
for (field_name, _, field) in fields.iter_all() {
|
||||
if subs[field].is_optional() {
|
||||
return Err(NotDerivable {
|
||||
var,
|
||||
context: NotDerivableContext::Decode(NotDerivableDecode::OptionalRecordField(
|
||||
subs[field_name].clone(),
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_record(_var: Variable) -> Result<(), NotDerivable> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_alias(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
|
||||
if is_builtin_float_alias(symbol) {
|
||||
Err(NotDerivable {
|
||||
var,
|
||||
context: NotDerivableContext::Eq(NotDerivableEq::FloatingPoint),
|
||||
})
|
||||
} else if is_builtin_number_alias(symbol) {
|
||||
Ok(Descend(false))
|
||||
} else {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
|
||||
// Ranged numbers are allowed, because they are always possibly ints - floats can not have
|
||||
// `isEq` derived, but if something were to be a float, we'd see it exactly as a float.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines what type implements an ability member of a specialized signature, given the
|
||||
/// [MustImplementAbility] constraints of the signature.
|
||||
pub fn type_implementing_specialization(
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use crate::ability::{
|
||||
resolve_ability_specialization, type_implementing_specialization, AbilityImplError,
|
||||
CheckedDerives, ObligationCache, PendingDerivesTable, Resolved,
|
||||
@ -274,6 +276,9 @@ impl Aliases {
|
||||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
arena: &bumpalo::Bump,
|
||||
symbol: Symbol,
|
||||
alias_variables: AliasVariables,
|
||||
@ -375,7 +380,18 @@ impl Aliases {
|
||||
if !can_reuse_old_definition {
|
||||
let mut typ = typ.clone();
|
||||
typ.substitute_variables(&substitutions);
|
||||
let alias_variable = type_to_variable(subs, rank, pools, arena, self, &typ, false);
|
||||
let alias_variable = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
self,
|
||||
&typ,
|
||||
false,
|
||||
);
|
||||
(alias_variable, kind)
|
||||
} else {
|
||||
if !substitutions.is_empty() {
|
||||
@ -389,7 +405,18 @@ impl Aliases {
|
||||
// assumption: an alias does not (transitively) syntactically contain itself
|
||||
// (if it did it would have to be a recursive tag union, which we should have fixed up
|
||||
// during canonicalization)
|
||||
let alias_variable = type_to_variable(subs, rank, pools, arena, self, &t, false);
|
||||
let alias_variable = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
self,
|
||||
&t,
|
||||
false,
|
||||
);
|
||||
|
||||
{
|
||||
match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) {
|
||||
@ -562,7 +589,14 @@ fn run_in_place(
|
||||
let mut obligation_cache = ObligationCache::default();
|
||||
let mut awaiting_specializations = AwaitingSpecializations::default();
|
||||
|
||||
let pending_derives = PendingDerivesTable::new(subs, aliases, pending_derives);
|
||||
let pending_derives = PendingDerivesTable::new(
|
||||
subs,
|
||||
aliases,
|
||||
pending_derives,
|
||||
problems,
|
||||
abilities_store,
|
||||
&mut obligation_cache,
|
||||
);
|
||||
let CheckedDerives {
|
||||
legal_derives: _,
|
||||
problems: derives_problems,
|
||||
@ -687,6 +721,9 @@ fn solve(
|
||||
constraints,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
subs,
|
||||
let_con.def_types,
|
||||
@ -747,6 +784,9 @@ fn solve(
|
||||
constraints,
|
||||
next_rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
subs,
|
||||
let_con.def_types,
|
||||
@ -858,11 +898,29 @@ fn solve(
|
||||
Eq(roc_can::constraint::Eq(type_index, expectation_index, category_index, region)) => {
|
||||
let category = &constraints.categories[category_index.index()];
|
||||
|
||||
let actual =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index);
|
||||
let actual = either_type_index_to_var(
|
||||
constraints,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
*type_index,
|
||||
);
|
||||
|
||||
let expectation = &constraints.expectations[expectation_index.index()];
|
||||
let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
|
||||
let expected = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
match unify(&mut UEnv::new(subs), actual, expected, Mode::EQ) {
|
||||
Success {
|
||||
@ -927,6 +985,9 @@ fn solve(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
&mut vec![], // don't report any extra errors
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
*source_index,
|
||||
);
|
||||
@ -962,8 +1023,16 @@ fn solve(
|
||||
let actual = deep_copy_var_in(subs, rank, pools, var, arena);
|
||||
let expectation = &constraints.expectations[expectation_index.index()];
|
||||
|
||||
let expected =
|
||||
type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
|
||||
let expected = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
match unify(&mut UEnv::new(subs), actual, expected, Mode::EQ) {
|
||||
Success {
|
||||
@ -1048,11 +1117,29 @@ fn solve(
|
||||
| PatternPresence(type_index, expectation_index, category_index, region) => {
|
||||
let category = &constraints.pattern_categories[category_index.index()];
|
||||
|
||||
let actual =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index);
|
||||
let actual = either_type_index_to_var(
|
||||
constraints,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
*type_index,
|
||||
);
|
||||
|
||||
let expectation = &constraints.pattern_expectations[expectation_index.index()];
|
||||
let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
|
||||
let expected = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
let mode = match constraint {
|
||||
PatternPresence(..) => Mode::PRESENT,
|
||||
@ -1209,8 +1296,17 @@ fn solve(
|
||||
}
|
||||
}
|
||||
IsOpenType(type_index) => {
|
||||
let actual =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, *type_index);
|
||||
let actual = either_type_index_to_var(
|
||||
constraints,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
*type_index,
|
||||
);
|
||||
|
||||
open_tag_union(subs, actual);
|
||||
|
||||
@ -1231,12 +1327,30 @@ fn solve(
|
||||
let tys = &constraints.types[types.indices()];
|
||||
let pattern_category = &constraints.pattern_categories[pattern_category.index()];
|
||||
|
||||
let actual = type_to_var(subs, rank, pools, aliases, typ);
|
||||
let actual = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
typ,
|
||||
);
|
||||
let tag_ty = Type::TagUnion(
|
||||
vec![(tag_name.clone(), tys.to_vec())],
|
||||
TypeExtension::Closed,
|
||||
);
|
||||
let includes = type_to_var(subs, rank, pools, aliases, &tag_ty);
|
||||
let includes = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
&tag_ty,
|
||||
);
|
||||
|
||||
match unify(&mut UEnv::new(subs), actual, includes, Mode::PRESENT) {
|
||||
Success {
|
||||
@ -1337,10 +1451,28 @@ fn solve(
|
||||
}
|
||||
};
|
||||
|
||||
let real_var =
|
||||
either_type_index_to_var(constraints, subs, rank, pools, aliases, real_var);
|
||||
let real_var = either_type_index_to_var(
|
||||
constraints,
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
aliases,
|
||||
real_var,
|
||||
);
|
||||
|
||||
let branches_var = type_to_var(subs, rank, pools, aliases, expected_type);
|
||||
let branches_var = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
expected_type,
|
||||
);
|
||||
|
||||
let real_content = subs.get_content_without_compacting(real_var);
|
||||
let branches_content = subs.get_content_without_compacting(branches_var);
|
||||
@ -1889,6 +2021,9 @@ impl LocalDefVarsVec<(Symbol, Loc<Variable>)> {
|
||||
constraints: &Constraints,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
aliases: &mut Aliases,
|
||||
subs: &mut Subs,
|
||||
def_types_slice: roc_can::constraint::DefTypes,
|
||||
@ -1899,7 +2034,16 @@ impl LocalDefVarsVec<(Symbol, Loc<Variable>)> {
|
||||
let mut local_def_vars = Self::with_length(types_slice.len());
|
||||
|
||||
for (&(symbol, region), typ) in (loc_symbols_slice.iter()).zip(types_slice) {
|
||||
let var = type_to_var(subs, rank, pools, aliases, typ);
|
||||
let var = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
typ,
|
||||
);
|
||||
|
||||
local_def_vars.push((symbol, Loc { value: var, region }));
|
||||
}
|
||||
@ -1930,6 +2074,9 @@ fn either_type_index_to_var(
|
||||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
aliases: &mut Aliases,
|
||||
either_type_index: roc_collections::soa::EitherIndex<Type, Variable>,
|
||||
) -> Variable {
|
||||
@ -1937,7 +2084,16 @@ fn either_type_index_to_var(
|
||||
Ok(type_index) => {
|
||||
let typ = &constraints.types[type_index.index()];
|
||||
|
||||
type_to_var(subs, rank, pools, aliases, typ)
|
||||
type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
pools,
|
||||
aliases,
|
||||
typ,
|
||||
)
|
||||
}
|
||||
Err(var_index) => {
|
||||
// we cheat, and store the variable directly in the index
|
||||
@ -1949,6 +2105,9 @@ fn either_type_index_to_var(
|
||||
pub(crate) fn type_to_var(
|
||||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
pools: &mut Pools,
|
||||
aliases: &mut Aliases,
|
||||
typ: &Type,
|
||||
@ -1958,7 +2117,18 @@ pub(crate) fn type_to_var(
|
||||
} else {
|
||||
let mut arena = take_scratchpad();
|
||||
|
||||
let var = type_to_variable(subs, rank, pools, &arena, aliases, typ, false);
|
||||
let var = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
&arena,
|
||||
aliases,
|
||||
typ,
|
||||
false,
|
||||
);
|
||||
|
||||
arena.reset();
|
||||
put_scratchpad(arena);
|
||||
@ -2109,6 +2279,9 @@ fn type_to_variable<'a>(
|
||||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
abilities_store: &AbilitiesStore,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
arena: &'a bumpalo::Bump,
|
||||
aliases: &mut Aliases,
|
||||
typ: &Type,
|
||||
@ -2118,6 +2291,7 @@ fn type_to_variable<'a>(
|
||||
use bumpalo::collections::Vec;
|
||||
|
||||
let mut stack = Vec::with_capacity_in(8, arena);
|
||||
let mut bind_to_ability = Vec::new_in(arena);
|
||||
|
||||
macro_rules! helper {
|
||||
($typ:expr, $ambient_function_policy:expr) => {{
|
||||
@ -2165,7 +2339,7 @@ fn type_to_variable<'a>(
|
||||
Apply(symbol, arguments, _) => {
|
||||
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
||||
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
|
||||
let var = helper!(var_index);
|
||||
let var = helper!(&var_index.value);
|
||||
subs.variables[target_index] = var;
|
||||
}
|
||||
|
||||
@ -2356,8 +2530,11 @@ fn type_to_variable<'a>(
|
||||
let new_variables = VariableSubsSlice::reserve_into_subs(subs, length);
|
||||
|
||||
for (target_index, arg_type) in (new_variables.indices()).zip(type_arguments) {
|
||||
let copy_var = helper!(arg_type);
|
||||
let copy_var = helper!(&arg_type.value.typ);
|
||||
subs.variables[target_index] = copy_var;
|
||||
if let Some(ability) = arg_type.value.opt_ability {
|
||||
bind_to_ability.push((Loc::at(arg_type.region, copy_var), ability));
|
||||
}
|
||||
}
|
||||
|
||||
let it = (new_variables.indices().skip(type_arguments.len()))
|
||||
@ -2365,8 +2542,18 @@ fn type_to_variable<'a>(
|
||||
for (target_index, ls) in it {
|
||||
// We MUST do this now, otherwise when linking the ambient function during
|
||||
// instantiation of the real var, there will be nothing to link against.
|
||||
let copy_var =
|
||||
type_to_variable(subs, rank, pools, arena, aliases, &ls.0, true);
|
||||
let copy_var = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
aliases,
|
||||
&ls.0,
|
||||
true,
|
||||
);
|
||||
subs.variables[target_index] = copy_var;
|
||||
}
|
||||
|
||||
@ -2381,6 +2568,9 @@ fn type_to_variable<'a>(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
*symbol,
|
||||
alias_variables,
|
||||
@ -2488,8 +2678,18 @@ fn type_to_variable<'a>(
|
||||
for (target_index, ls) in it {
|
||||
// We MUST do this now, otherwise when linking the ambient function during
|
||||
// instantiation of the real var, there will be nothing to link against.
|
||||
let copy_var =
|
||||
type_to_variable(subs, rank, pools, arena, aliases, &ls.0, true);
|
||||
let copy_var = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
aliases,
|
||||
&ls.0,
|
||||
true,
|
||||
);
|
||||
subs.variables[target_index] = copy_var;
|
||||
}
|
||||
|
||||
@ -2501,8 +2701,18 @@ fn type_to_variable<'a>(
|
||||
};
|
||||
|
||||
// cannot use helper! here because this variable may be involved in unification below
|
||||
let alias_variable =
|
||||
type_to_variable(subs, rank, pools, arena, aliases, alias_type, false);
|
||||
let alias_variable = type_to_variable(
|
||||
subs,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
abilities_store,
|
||||
obligation_cache,
|
||||
arena,
|
||||
aliases,
|
||||
alias_type,
|
||||
false,
|
||||
);
|
||||
// TODO(opaques): I think host-exposed aliases should always be structural
|
||||
// (when does it make sense to give a host an opaque type?)
|
||||
let content = Content::Alias(
|
||||
@ -2533,6 +2743,67 @@ fn type_to_variable<'a>(
|
||||
};
|
||||
}
|
||||
|
||||
for (Loc { value: var, region }, ability) in bind_to_ability {
|
||||
match *subs.get_content_unchecked(var) {
|
||||
Content::RigidVar(a) => {
|
||||
subs.set_content(var, Content::RigidAbleVar(a, ability));
|
||||
}
|
||||
Content::RigidAbleVar(_, ab) if ab == ability => {
|
||||
// pass, already bound
|
||||
}
|
||||
_ => {
|
||||
let flex_ability = subs.fresh(Descriptor {
|
||||
content: Content::FlexAbleVar(None, ability),
|
||||
rank,
|
||||
mark: Mark::NONE,
|
||||
copy: OptVariable::NONE,
|
||||
});
|
||||
|
||||
let category = Category::OpaqueArg;
|
||||
match unify(&mut UEnv::new(subs), var, flex_ability, Mode::EQ) {
|
||||
Success {
|
||||
vars: _,
|
||||
must_implement_ability,
|
||||
lambda_sets_to_specialize,
|
||||
extra_metadata: _,
|
||||
} => {
|
||||
// No introduction needed
|
||||
|
||||
if !must_implement_ability.is_empty() {
|
||||
let new_problems = obligation_cache.check_obligations(
|
||||
subs,
|
||||
abilities_store,
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(region, category, flex_ability),
|
||||
);
|
||||
problems.extend(new_problems);
|
||||
}
|
||||
debug_assert!(lambda_sets_to_specialize
|
||||
.drain()
|
||||
.all(|(_, vals)| vals.is_empty()));
|
||||
}
|
||||
Failure(_vars, actual_type, expected_type, _bad_impls) => {
|
||||
// No introduction needed
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
region,
|
||||
category,
|
||||
actual_type,
|
||||
Expected::NoExpectation(expected_type),
|
||||
);
|
||||
|
||||
problems.push(problem);
|
||||
}
|
||||
BadType(_vars, problem) => {
|
||||
// No introduction needed
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
@ -655,7 +655,7 @@ fn make_specialization_decision<P: Phase>(
|
||||
})
|
||||
}
|
||||
}
|
||||
Structure(_) | Alias(_, _, _, _) => {
|
||||
Structure(_) | Alias(_, _, _, _) | RecursionVar { .. } => {
|
||||
let builtin = match ability_member.try_into() {
|
||||
Ok(builtin) => builtin,
|
||||
Err(_) => return SpecializeDecision::Drop,
|
||||
@ -691,7 +691,6 @@ fn make_specialization_decision<P: Phase>(
|
||||
| RigidAbleVar(..)
|
||||
| FlexVar(..)
|
||||
| RigidVar(..)
|
||||
| RecursionVar { .. }
|
||||
| LambdaSet(..)
|
||||
| RangedNumber(..) => {
|
||||
internal_error!("unexpected")
|
||||
|
@ -378,6 +378,7 @@ mod solve_expr {
|
||||
let known_specializations = abilities_store.iter_declared_implementations().filter_map(
|
||||
|(impl_key, member_impl)| match member_impl {
|
||||
MemberImpl::Impl(impl_symbol) => {
|
||||
dbg!(impl_symbol);
|
||||
let specialization = abilities_store.specialization_info(*impl_symbol).expect(
|
||||
"declared implementations should be resolved conclusively after solving",
|
||||
);
|
||||
@ -3469,7 +3470,7 @@ mod solve_expr {
|
||||
Dict.insert
|
||||
"#
|
||||
),
|
||||
"Dict k v, k, v -> Dict k v",
|
||||
"Dict k v, k, v -> Dict k v | k has Eq",
|
||||
);
|
||||
}
|
||||
|
||||
@ -3730,7 +3731,7 @@ mod solve_expr {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
reconstructPath : Dict position position, position -> List position
|
||||
reconstructPath : Dict position position, position -> List position | position has Eq
|
||||
reconstructPath = \cameFrom, goal ->
|
||||
when Dict.get cameFrom goal is
|
||||
Err KeyNotFound ->
|
||||
@ -3742,7 +3743,7 @@ mod solve_expr {
|
||||
reconstructPath
|
||||
"#
|
||||
),
|
||||
"Dict position position, position -> List position",
|
||||
"Dict position position, position -> List position | position has Eq",
|
||||
);
|
||||
}
|
||||
|
||||
@ -3777,7 +3778,7 @@ mod solve_expr {
|
||||
|
||||
Model position : { openSet : Set position }
|
||||
|
||||
cheapestOpen : Model position -> Result position [KeyNotFound]*
|
||||
cheapestOpen : Model position -> Result position [KeyNotFound]* | position has Eq
|
||||
cheapestOpen = \model ->
|
||||
|
||||
folder = \resSmallestSoFar, position ->
|
||||
@ -3792,14 +3793,14 @@ mod solve_expr {
|
||||
Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder
|
||||
|> Result.map (\x -> x.position)
|
||||
|
||||
astar : Model position -> Result position [KeyNotFound]*
|
||||
astar : Model position -> Result position [KeyNotFound]* | position has Eq
|
||||
astar = \model -> cheapestOpen model
|
||||
|
||||
main =
|
||||
astar
|
||||
"#
|
||||
),
|
||||
"Model position -> Result position [KeyNotFound]*",
|
||||
"Model position -> Result position [KeyNotFound]* | position has Eq",
|
||||
);
|
||||
}
|
||||
|
||||
@ -4441,7 +4442,7 @@ mod solve_expr {
|
||||
|
||||
Key k : Num k
|
||||
|
||||
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v
|
||||
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Eq
|
||||
removeHelpEQGT = \targetKey, dict ->
|
||||
when dict is
|
||||
Node color key value left right ->
|
||||
@ -4555,7 +4556,7 @@ mod solve_expr {
|
||||
_ ->
|
||||
Empty
|
||||
|
||||
removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v
|
||||
removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Eq
|
||||
removeHelp = \targetKey, dict ->
|
||||
when dict is
|
||||
Empty ->
|
||||
@ -4585,7 +4586,7 @@ mod solve_expr {
|
||||
|
||||
main : RBTree I64 I64
|
||||
main =
|
||||
removeHelp 1 Empty
|
||||
removeHelp 1i64 Empty
|
||||
"#
|
||||
),
|
||||
"RBTree I64 I64",
|
||||
@ -4643,7 +4644,7 @@ mod solve_expr {
|
||||
|
||||
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
|
||||
|
||||
removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v
|
||||
removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v | k has Eq
|
||||
removeHelp = \targetKey, dict ->
|
||||
when dict is
|
||||
Empty ->
|
||||
@ -4678,7 +4679,7 @@ mod solve_expr {
|
||||
|
||||
removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v
|
||||
|
||||
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v
|
||||
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Eq
|
||||
removeHelpEQGT = \targetKey, dict ->
|
||||
when dict is
|
||||
Node color key value left right ->
|
||||
@ -4701,7 +4702,7 @@ mod solve_expr {
|
||||
|
||||
main : RBTree I64 I64
|
||||
main =
|
||||
removeHelp 1 Empty
|
||||
removeHelp 1i64 Empty
|
||||
"#
|
||||
),
|
||||
"RBTree I64 I64",
|
||||
@ -7972,4 +7973,40 @@ mod solve_expr {
|
||||
"O",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_implement_eq() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Trivial := {} has [Eq {isEq}]
|
||||
|
||||
isEq = \@Trivial {}, @Trivial {} -> Bool.true
|
||||
|
||||
main = Bool.isEq (@Trivial {}) (@Trivial {})
|
||||
"#
|
||||
),
|
||||
"Bool",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_able_variables_in_type_alias() {
|
||||
infer_queries!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
F a : a | a has Hash
|
||||
|
||||
main : F a -> F a
|
||||
#^^^^{-1}
|
||||
"#
|
||||
),
|
||||
@"main : a -[[main(0)]]-> a | a has Hash"
|
||||
print_only_under_alias: true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -67,9 +67,15 @@ pub enum NotDerivableContext {
|
||||
UnboundVar,
|
||||
Opaque(Symbol),
|
||||
Decode(NotDerivableDecode),
|
||||
Eq(NotDerivableEq),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum NotDerivableDecode {
|
||||
OptionalRecordField(Lowercase),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum NotDerivableEq {
|
||||
FloatingPoint,
|
||||
}
|
||||
|
@ -294,7 +294,8 @@ fn tag_one_label_zero_args() {
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(when #Derived.tag is A -> Encode.tag "A" [])
|
||||
(when #Derived.tag is
|
||||
A -> Encode.tag "A" [])
|
||||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
|
58
crates/compiler/test_derive/src/eq.rs
Normal file
58
crates/compiler/test_derive/src/eq.rs
Normal file
@ -0,0 +1,58 @@
|
||||
#![cfg(test)]
|
||||
// Even with #[allow(non_snake_case)] on individual idents, rust-analyzer issues diagnostics.
|
||||
// See https://github.com/rust-lang/rust-analyzer/issues/6541.
|
||||
// For the `v!` macro we use uppercase variables when constructing tag unions.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{util::check_single_lset_immediate, v};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use roc_derive_key::DeriveBuiltin::IsEq;
|
||||
|
||||
#[test]
|
||||
fn immediates() {
|
||||
// Everything is an immediate for `Eq`.
|
||||
check_single_lset_immediate(IsEq, v!(U8), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(U16), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(U32), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(U64), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(U128), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(I8), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(I16), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(I32), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(I64), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(I128), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(STR), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(
|
||||
IsEq,
|
||||
v!(Symbol::LIST_LIST v!(U8)),
|
||||
Symbol::BOOL_STRUCTURAL_EQ,
|
||||
);
|
||||
check_single_lset_immediate(
|
||||
IsEq,
|
||||
v!(Symbol::LIST_LIST v!(STR)),
|
||||
Symbol::BOOL_STRUCTURAL_EQ,
|
||||
);
|
||||
check_single_lset_immediate(IsEq, v!({ a: v!(U8), }), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(IsEq, v!(EMPTY_RECORD), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
check_single_lset_immediate(
|
||||
IsEq,
|
||||
v!([ A v!(U8) v!(STR), B v!(STR) ]),
|
||||
Symbol::BOOL_STRUCTURAL_EQ,
|
||||
);
|
||||
check_single_lset_immediate(
|
||||
IsEq,
|
||||
v!([ A v!(U8) v!(STR), B v!(STR) ]),
|
||||
Symbol::BOOL_STRUCTURAL_EQ,
|
||||
);
|
||||
check_single_lset_immediate(
|
||||
IsEq,
|
||||
v!([ Nil, Cons v!(^lst)] as lst),
|
||||
Symbol::BOOL_STRUCTURAL_EQ,
|
||||
);
|
||||
|
||||
// NOTE: despite this reaching an immediate, `F64`s will never actually be allowed to be
|
||||
// compared, because obligation checking will rule them out from `isEq`!
|
||||
check_single_lset_immediate(IsEq, v!(F64), Symbol::BOOL_STRUCTURAL_EQ);
|
||||
}
|
@ -27,6 +27,20 @@ test_key_eq! {
|
||||
v!({ c: v!(U8), a: v!(U8), b: v!(U8), })
|
||||
explicit_empty_record_and_implicit_empty_record:
|
||||
v!(EMPTY_RECORD), v!({})
|
||||
|
||||
same_tag_union:
|
||||
v!([ A v!(U8) v!(STR), B v!(STR) ]), v!([ A v!(U8) v!(STR), B v!(STR) ])
|
||||
same_tag_union_tags_diff_types:
|
||||
v!([ A v!(U8) v!(U8), B v!(U8) ]), v!([ A v!(STR) v!(STR), B v!(STR) ])
|
||||
same_tag_union_tags_any_order:
|
||||
v!([ A v!(U8) v!(U8), B v!(U8), C ]), v!([ C, B v!(STR), A v!(STR) v!(STR) ])
|
||||
explicit_empty_tag_union_and_implicit_empty_tag_union:
|
||||
v!(EMPTY_TAG_UNION), v!([])
|
||||
|
||||
same_recursive_tag_union:
|
||||
v!([ Nil, Cons v!(^lst)] as lst), v!([ Nil, Cons v!(^lst)] as lst)
|
||||
same_tag_union_and_recursive_tag_union_fields:
|
||||
v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(^lst)] as lst)
|
||||
}
|
||||
|
||||
test_key_neq! {
|
||||
@ -36,6 +50,13 @@ test_key_neq! {
|
||||
v!({ a: v!(U8), }), v!({ b: v!(U8), })
|
||||
record_empty_vs_nonempty:
|
||||
v!(EMPTY_RECORD), v!({ a: v!(U8), })
|
||||
|
||||
different_tag_union_tags:
|
||||
v!([ A v!(U8) ]), v!([ B v!(U8) ])
|
||||
tag_union_empty_vs_nonempty:
|
||||
v!(EMPTY_TAG_UNION), v!([ B v!(U8) ])
|
||||
different_recursive_tag_union_tags:
|
||||
v!([ Nil, Cons v!(^lst) ] as lst), v!([ Nil, Next v!(^lst) ] as lst)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -87,6 +108,36 @@ fn derivable_record_with_record_ext() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_tag_ext_flex_var() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!([ A v!(STR) ]* ),
|
||||
DeriveKey::Hash(FlatHashKey::TagUnion(vec![("A".into(), 1)])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_tag_ext_flex_able_var() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!([ A v!(STR) ]a has Symbol::ENCODE_TO_ENCODER),
|
||||
DeriveKey::Hash(FlatHashKey::TagUnion(vec![("A".into(), 1)])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_tag_with_tag_ext() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!([ B v!(STR) v!(U8) ][ A v!(STR) ]),
|
||||
DeriveKey::Hash(FlatHashKey::TagUnion(vec![
|
||||
("A".into(), 1),
|
||||
("B".into(), 2),
|
||||
])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
derive_test(Hash, v!(EMPTY_RECORD), |golden| {
|
||||
@ -149,3 +200,100 @@ fn two_field_record() {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_one_label_no_payloads() {
|
||||
derive_test(Hash, v!([A]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A]
|
||||
# hasher, [A] -[[hash_[A 0](0)]]-> hasher | hasher has Hasher
|
||||
# hasher, [A] -[[hash_[A 0](0)]]-> hasher | hasher has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[A 0](0)]]
|
||||
#Derived.hash_[A 0] = \#Derived.hasher, A -> #Derived.hasher
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_one_label_newtype() {
|
||||
derive_test(Hash, v!([A v!(U8) v!(STR)]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A U8 Str]
|
||||
# hasher, [A a a1] -[[hash_[A 2](0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher
|
||||
# hasher, [A a a1] -[[hash_[A 2](0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[A 2](0)]]
|
||||
#Derived.hash_[A 2] =
|
||||
\#Derived.hasher, A #Derived.2 #Derived.3 ->
|
||||
Hash.hash (Hash.hash #Derived.hasher #Derived.2) #Derived.3
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_two_labels() {
|
||||
derive_test(Hash, v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A U8 Str U16, B Str]
|
||||
# a, [A a1 a2 a3, B a3] -[[hash_[A 3,B 1](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash, a3 has Hash
|
||||
# a, [A a1 a2 a3, B a3] -[[hash_[A 3,B 1](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash, a3 has Hash
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[A 3,B 1](0)]]
|
||||
#Derived.hash_[A 3,B 1] =
|
||||
\#Derived.hasher, #Derived.union ->
|
||||
when #Derived.union is
|
||||
A #Derived.3 #Derived.4 #Derived.5 ->
|
||||
Hash.hash
|
||||
(Hash.hash
|
||||
(Hash.hash (Hash.addU8 #Derived.hasher 0) #Derived.3)
|
||||
#Derived.4)
|
||||
#Derived.5
|
||||
B #Derived.6 -> Hash.hash (Hash.addU8 #Derived.hasher 1) #Derived.6
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_two_labels_no_payloads() {
|
||||
derive_test(Hash, v!([A, B]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A, B]
|
||||
# a, [A, B] -[[hash_[A 0,B 0](0)]]-> a | a has Hasher
|
||||
# a, [A, B] -[[hash_[A 0,B 0](0)]]-> a | a has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[A 0,B 0](0)]]
|
||||
#Derived.hash_[A 0,B 0] =
|
||||
\#Derived.hasher, #Derived.union ->
|
||||
when #Derived.union is
|
||||
A -> Hash.addU8 #Derived.hasher 0
|
||||
B -> Hash.addU8 #Derived.hasher 1
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_tag_union() {
|
||||
derive_test(Hash, v!([Nil, Cons v!(U8) v!(^lst) ] as lst), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [Cons U8 $rec, Nil] as $rec
|
||||
# a, [Cons a1 a2, Nil] -[[hash_[Cons 2,Nil 0](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash
|
||||
# a, [Cons a1 a2, Nil] -[[hash_[Cons 2,Nil 0](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[Cons 2,Nil 0](0)]]
|
||||
#Derived.hash_[Cons 2,Nil 0] =
|
||||
\#Derived.hasher, #Derived.union ->
|
||||
when #Derived.union is
|
||||
Cons #Derived.3 #Derived.4 ->
|
||||
Hash.hash
|
||||
(Hash.hash (Hash.addU8 #Derived.hasher 0) #Derived.3)
|
||||
#Derived.4
|
||||
Nil -> Hash.addU8 #Derived.hasher 1
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use roc_can::expr::{ClosureData, OpaqueWrapFunctionData, WhenBranch};
|
||||
use roc_can::pattern::{Pattern, RecordDestruct};
|
||||
|
||||
use roc_module::symbol::Interns;
|
||||
|
||||
use ven_pretty::{Arena, DocAllocator, DocBuilder};
|
||||
|
||||
pub struct Ctx<'a> {
|
||||
@ -100,8 +101,12 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||
.append(expr(c, Free, f, &loc_cond.value))
|
||||
.append(f.text(" is"))
|
||||
.append(
|
||||
f.concat(branches.iter().map(|b| f.line().append(branch(c, f, b))))
|
||||
.group(),
|
||||
f.concat(
|
||||
branches
|
||||
.iter()
|
||||
.map(|b| f.hardline().append(branch(c, f, b)))
|
||||
)
|
||||
.group(),
|
||||
)
|
||||
.nest(2)
|
||||
.group()
|
||||
@ -134,7 +139,10 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||
)
|
||||
.group(),
|
||||
LetRec(_, _, _) => todo!(),
|
||||
LetNonRec(_, _) => todo!(),
|
||||
LetNonRec(loc_def, body) => def(c, f, loc_def)
|
||||
.append(f.hardline())
|
||||
.append(expr(c, Free, f, &body.value))
|
||||
.group(),
|
||||
Call(fun, args, _) => {
|
||||
let (_, fun, _, _) = &**fun;
|
||||
maybe_paren!(
|
||||
@ -154,7 +162,24 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||
.nest(2)
|
||||
)
|
||||
}
|
||||
RunLowLevel { .. } => todo!(),
|
||||
RunLowLevel { args, .. } => {
|
||||
let op = "LowLevel";
|
||||
|
||||
maybe_paren!(
|
||||
Free,
|
||||
p,
|
||||
f.reflow(op)
|
||||
.append(
|
||||
f.concat(
|
||||
args.iter()
|
||||
.map(|le| f.line().append(expr(c, AppArg, f, &le.1)))
|
||||
)
|
||||
.group()
|
||||
)
|
||||
.group()
|
||||
.nest(2)
|
||||
)
|
||||
}
|
||||
ForeignCall { .. } => todo!(),
|
||||
Closure(ClosureData {
|
||||
arguments,
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
mod decoding;
|
||||
mod encoding;
|
||||
mod eq;
|
||||
mod hash;
|
||||
|
||||
mod pretty_print;
|
||||
|
@ -55,6 +55,11 @@ fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, Pa
|
||||
module_source(ModuleId::HASH),
|
||||
builtins_path.join("Hash.roc"),
|
||||
),
|
||||
DeriveBuiltin::IsEq => (
|
||||
ModuleId::BOOL,
|
||||
module_source(ModuleId::BOOL),
|
||||
builtins_path.join("Bool.roc"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ roc_error_macros = { path = "../../error_macros" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_debug_flags = {path="../debug_flags"}
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
libc = "0.2.133"
|
||||
libc = "0.2.135"
|
||||
inkwell = { path = "../../vendor/inkwell" }
|
||||
target-lexicon = "0.12.3"
|
||||
libloading = "0.7.1"
|
||||
|
@ -1332,7 +1332,7 @@ mod hash {
|
||||
}
|
||||
|
||||
mod derived {
|
||||
use super::{assert_evals_to, build_test};
|
||||
use super::{assert_evals_to, build_test, indoc, TEST_HASHER};
|
||||
use roc_std::RocList;
|
||||
|
||||
#[test]
|
||||
@ -1372,5 +1372,286 @@ mod hash {
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_singleton_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : [A]
|
||||
a = A
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
// hash nothing because this is a newtype of a unit layout.
|
||||
] as &[u8]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_bool_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : [A, B]
|
||||
a = A
|
||||
|
||||
b : [A, B]
|
||||
b = B
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> Hash.hash b
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
0, // A
|
||||
1, // B
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_byte_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
l : List [A, B, C, D, E, F, G, H]
|
||||
l = [A, B, C, D, E, F, G, H]
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash l
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
0, // A
|
||||
1, // B
|
||||
2, // C
|
||||
3, // D
|
||||
4, // E
|
||||
5, // F
|
||||
6, // G
|
||||
7, // H
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_newtype_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : [A U8 U8 U8]
|
||||
a = A 15 23 47
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
// discriminant is skipped because it's a newtype
|
||||
15, 23, 47
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_newtype_by_void_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : Result [A U8 U8 U8] []
|
||||
a = Ok (A 15 23 47)
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
1, // Ok
|
||||
// A is skipped because it is a newtype
|
||||
15, 23, 47
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_heterogenous_tags() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : [A U8 U8, B {{ a: U8 }}, C Str]
|
||||
a = A 15 23
|
||||
|
||||
b : [A U8 U8, B {{ a: U8 }}, C Str]
|
||||
b = B {{ a: 37 }}
|
||||
|
||||
c : [A U8 U8, B {{ a: U8 }}, C Str]
|
||||
c = C "abc"
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> Hash.hash b
|
||||
|> Hash.hash c
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
0, // dicsr A
|
||||
15, 23, // payloads A
|
||||
1, // discr B
|
||||
37, // payloads B
|
||||
2, // discr C
|
||||
97, 98, 99 // payloads C
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_recursive_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
ConsList : [Cons U8 ConsList, Nil]
|
||||
|
||||
c : ConsList
|
||||
c = Cons 1 (Cons 2 Nil)
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash c
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
0, 1, // Cons 1
|
||||
0, 2, // Cons 2
|
||||
1, // Nil
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
|
||||
mod eq {
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_evals_to;
|
||||
|
||||
#[cfg(feature = "gen-wasm")]
|
||||
use crate::helpers::wasm::assert_evals_to;
|
||||
|
||||
use indoc::indoc;
|
||||
use roc_std::RocStr;
|
||||
|
||||
#[test]
|
||||
fn custom_eq_impl() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
LyingEq := U8 has [Eq {isEq}]
|
||||
|
||||
isEq = \@LyingEq m, @LyingEq n -> m != n
|
||||
|
||||
main =
|
||||
a = @LyingEq 10
|
||||
b = @LyingEq 5
|
||||
c = @LyingEq 5
|
||||
if Bool.isEq a b && !(Bool.isEq b c) then
|
||||
"okay"
|
||||
else
|
||||
"fail"
|
||||
"#
|
||||
),
|
||||
RocStr::from("okay"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_structural_eq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
main = Bool.isEq 10u8 10u8
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -77,40 +77,6 @@ fn neq_u64() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn eq_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
i : F64
|
||||
i = 1
|
||||
|
||||
i == i
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn neq_f64() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
i : F64
|
||||
i = 1
|
||||
|
||||
i != i
|
||||
"#
|
||||
),
|
||||
false,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn eq_bool_tag() {
|
||||
@ -673,8 +639,8 @@ fn compare_nullable_recursive_union_same_content() {
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn boxed_eq_int() {
|
||||
assert_evals_to!("Box.box 1 == Box.box 1", true, bool);
|
||||
assert_evals_to!("Box.box 2 == Box.box 1", false, bool);
|
||||
assert_evals_to!("Box.box 1i64 == Box.box 1", true, bool);
|
||||
assert_evals_to!("Box.box 2i64 == Box.box 1", false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -917,7 +917,7 @@ fn list_walk_implements_position() {
|
||||
r#"
|
||||
Option a : [Some a, None]
|
||||
|
||||
find : List a, a -> Option Nat
|
||||
find : List a, a -> Option Nat | a has Eq
|
||||
find = \list, needle ->
|
||||
findHelp list needle
|
||||
|> .v
|
||||
@ -956,6 +956,12 @@ fn list_walk_until_even_prefix_sum() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_walk_from_sum() {
|
||||
assert_evals_to!(r#"List.walkFrom [1, 2, 3] 1 0 Num.add"#, 5, i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_keep_if_empty_list_of_int() {
|
||||
@ -1065,6 +1071,92 @@ fn list_keep_if_str_is_hello() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_count_if_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
List.countIf [] \_ -> Bool.true
|
||||
"#
|
||||
),
|
||||
0,
|
||||
usize
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_count_if_always_true_for_non_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
alwaysTrue : I64 -> Bool
|
||||
alwaysTrue = \_ ->
|
||||
Bool.true
|
||||
|
||||
oneThroughEight : List I64
|
||||
oneThroughEight =
|
||||
[1,2,3,4,5,6,7,8]
|
||||
|
||||
List.countIf oneThroughEight alwaysTrue
|
||||
"#
|
||||
),
|
||||
8,
|
||||
usize
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_count_if_always_false_for_non_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
alwaysFalse : I64 -> Bool
|
||||
alwaysFalse = \_ ->
|
||||
Bool.false
|
||||
|
||||
List.countIf [1,2,3,4,5,6,7,8] alwaysFalse
|
||||
"#
|
||||
),
|
||||
0,
|
||||
usize
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_count_if_condition() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
intIsLessThanThree : I64 -> Bool
|
||||
intIsLessThanThree = \i ->
|
||||
i < 3
|
||||
|
||||
List.countIf [1,2,3,4,5,6,7,8] intIsLessThanThree
|
||||
"#
|
||||
),
|
||||
2,
|
||||
usize
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_count_if_str() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
List.countIf ["x", "y", "x"] (\x -> x == "x")
|
||||
"#
|
||||
),
|
||||
2,
|
||||
usize
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_map_on_empty_list_with_int_layout() {
|
||||
@ -3396,16 +3488,6 @@ fn list_let_generalization() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_walk_backwards_until_sum() {
|
||||
assert_evals_to!(
|
||||
r#"List.walkBackwardsUntil [1, 2] 0 \a,b -> Continue (a + b)"#,
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_walk_backwards_implements_position() {
|
||||
@ -3413,7 +3495,7 @@ fn list_walk_backwards_implements_position() {
|
||||
r#"
|
||||
Option a : [Some a, None]
|
||||
|
||||
find : List a, a -> Option Nat
|
||||
find : List a, a -> Option Nat | a has Eq
|
||||
find = \list, needle ->
|
||||
findHelp list needle
|
||||
|> .v
|
||||
@ -3434,6 +3516,16 @@ fn list_walk_backwards_implements_position() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_walk_backwards_until_sum() {
|
||||
assert_evals_to!(
|
||||
r#"List.walkBackwardsUntil [1, 2] 0 \a,b -> Continue (a + b)"#,
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_walk_backwards_until_even_prefix_sum() {
|
||||
@ -3451,3 +3543,31 @@ fn list_walk_backwards_until_even_prefix_sum() {
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_walk_from_until_sum() {
|
||||
assert_evals_to!(
|
||||
r#"List.walkFromUntil [1, 2, 3, 4] 2 0 \a,b -> Continue (a + b)"#,
|
||||
7,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_walk_from_even_prefix_sum() {
|
||||
assert_evals_to!(
|
||||
r#"
|
||||
helper = \a, b ->
|
||||
if Num.isEven b then
|
||||
Continue (a + b)
|
||||
|
||||
else
|
||||
Break a
|
||||
|
||||
List.walkFromUntil [2, 4, 8, 9] 1 0 helper"#,
|
||||
4 + 8,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
@ -875,7 +875,7 @@ fn gen_int_eq() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn gen_int_neq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
@ -948,7 +948,7 @@ fn gen_wrap_int_neq() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
wrappedNotEq : a, a -> Bool
|
||||
wrappedNotEq : a, a -> Bool | a has Eq
|
||||
wrappedNotEq = \num1, num2 ->
|
||||
num1 != num2
|
||||
|
||||
|
@ -2595,7 +2595,7 @@ fn pass_through_unresolved_type_variable() {
|
||||
|
||||
main : Str
|
||||
main =
|
||||
(accept Bool.isEq) "B"
|
||||
(accept \x -> x) "B"
|
||||
|
||||
|
||||
accept : * -> (b -> b)
|
||||
@ -2878,10 +2878,10 @@ fn unresolved_tvar_when_capture_is_unused() {
|
||||
|
||||
main : I64
|
||||
main =
|
||||
r : Bool
|
||||
r = Bool.false
|
||||
r : U8
|
||||
r = 1
|
||||
|
||||
p1 = (\_ -> r == (1 == 1))
|
||||
p1 = (\_ -> r == 1)
|
||||
oneOfResult = List.map [p1] (\p -> p Green)
|
||||
|
||||
when oneOfResult is
|
||||
|
@ -1876,9 +1876,9 @@ fn llvm_wasm_str_layout() {
|
||||
|> Str.reserve 42
|
||||
"#
|
||||
),
|
||||
[0, 5, 42],
|
||||
[0, 5, 1],
|
||||
[u32; 3],
|
||||
|[_ptr, len, cap]: [u32; 3]| [0, len, cap]
|
||||
|[_ptr, len, cap]: [u32; 3]| [0, len, if cap >= 42 { 1 } else { 0 }]
|
||||
)
|
||||
}
|
||||
|
||||
@ -1982,3 +1982,46 @@ fn str_with_prefix() {
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn destructure_pattern_assigned_from_thunk_opaque() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
MyCustomType := Str
|
||||
myMsg = @MyCustomType "Hello"
|
||||
|
||||
main =
|
||||
@MyCustomType msg = myMsg
|
||||
|
||||
msg
|
||||
"#
|
||||
),
|
||||
RocStr::from("Hello"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn destructure_pattern_assigned_from_thunk_tag() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
myMsg = A "hello " "world"
|
||||
|
||||
main =
|
||||
A m1 m2 = myMsg
|
||||
|
||||
Str.concat m1 m2
|
||||
"#
|
||||
),
|
||||
RocStr::from("hello world"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -11,5 +11,6 @@ roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_error_macros = {path="../../error_macros"}
|
||||
roc_debug_flags = {path="../debug_flags"}
|
||||
roc_serialize = {path="../serialize"}
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
static_assertions = "1.1.0"
|
||||
|
@ -77,6 +77,7 @@ struct SubsHeader {
|
||||
record_fields: u64,
|
||||
variable_slices: u64,
|
||||
unspecialized_lambda_sets: u64,
|
||||
uls_of_var: u64,
|
||||
exposed_vars_by_symbol: u64,
|
||||
}
|
||||
|
||||
@ -95,6 +96,7 @@ impl SubsHeader {
|
||||
record_fields: subs.record_fields.len() as u64,
|
||||
variable_slices: subs.variable_slices.len() as u64,
|
||||
unspecialized_lambda_sets: subs.unspecialized_lambda_sets.len() as u64,
|
||||
uls_of_var: subs.uls_of_var.len() as u64,
|
||||
exposed_vars_by_symbol: exposed_vars_by_symbol as u64,
|
||||
}
|
||||
}
|
||||
@ -110,19 +112,11 @@ impl SubsHeader {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn slice_as_bytes<T>(slice: &[T]) -> &[u8] {
|
||||
let ptr = slice.as_ptr();
|
||||
let byte_length = std::mem::size_of::<T>() * slice.len();
|
||||
|
||||
unsafe { std::slice::from_raw_parts(ptr as *const u8, byte_length) }
|
||||
}
|
||||
|
||||
fn round_to_multiple_of(value: usize, base: usize) -> usize {
|
||||
(value + (base - 1)) / base * base
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct SerializedTagName(SubsSlice<u8>);
|
||||
|
||||
use roc_serialize::bytes;
|
||||
|
||||
impl Subs {
|
||||
pub fn serialize(
|
||||
&self,
|
||||
@ -137,14 +131,15 @@ impl Subs {
|
||||
|
||||
written = self.utable.serialize(writer, written)?;
|
||||
|
||||
written = Self::serialize_slice(&self.variables, writer, written)?;
|
||||
written = bytes::serialize_slice(&self.variables, writer, written)?;
|
||||
written = Self::serialize_tag_names(&self.tag_names, writer, written)?;
|
||||
written = Self::serialize_slice(&self.closure_names, writer, written)?;
|
||||
written = bytes::serialize_slice(&self.closure_names, writer, written)?;
|
||||
written = Self::serialize_field_names(&self.field_names, writer, written)?;
|
||||
written = Self::serialize_slice(&self.record_fields, writer, written)?;
|
||||
written = Self::serialize_slice(&self.variable_slices, writer, written)?;
|
||||
written = Self::serialize_slice(&self.unspecialized_lambda_sets, writer, written)?;
|
||||
written = Self::serialize_slice(exposed_vars_by_symbol, writer, written)?;
|
||||
written = bytes::serialize_slice(&self.record_fields, writer, written)?;
|
||||
written = bytes::serialize_slice(&self.variable_slices, writer, written)?;
|
||||
written = bytes::serialize_slice(&self.unspecialized_lambda_sets, writer, written)?;
|
||||
written = Self::serialize_uls_of_var(&self.uls_of_var, writer, written)?;
|
||||
written = bytes::serialize_slice(exposed_vars_by_symbol, writer, written)?;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
@ -164,9 +159,9 @@ impl Subs {
|
||||
slices.push(slice);
|
||||
}
|
||||
|
||||
let written = Self::serialize_slice(&slices, writer, written)?;
|
||||
let written = bytes::serialize_slice(&slices, writer, written)?;
|
||||
|
||||
Self::serialize_slice(&buf, writer, written)
|
||||
bytes::serialize_slice(&buf, writer, written)
|
||||
}
|
||||
|
||||
/// Global tag names can be heap-allocated
|
||||
@ -185,30 +180,39 @@ impl Subs {
|
||||
slices.push(serialized);
|
||||
}
|
||||
|
||||
let written = Self::serialize_slice(&slices, writer, written)?;
|
||||
let written = bytes::serialize_slice(&slices, writer, written)?;
|
||||
|
||||
Self::serialize_slice(&buf, writer, written)
|
||||
bytes::serialize_slice(&buf, writer, written)
|
||||
}
|
||||
|
||||
pub(crate) fn serialize_slice<T>(
|
||||
slice: &[T],
|
||||
fn serialize_uls_of_var(
|
||||
uls_of_vars: &UlsOfVar,
|
||||
writer: &mut impl std::io::Write,
|
||||
written: usize,
|
||||
) -> std::io::Result<usize> {
|
||||
let alignment = std::mem::align_of::<T>();
|
||||
let padding_bytes = round_to_multiple_of(written, alignment) - written;
|
||||
|
||||
for _ in 0..padding_bytes {
|
||||
writer.write_all(&[0])?;
|
||||
}
|
||||
|
||||
let bytes_slice = unsafe { slice_as_bytes(slice) };
|
||||
writer.write_all(bytes_slice)?;
|
||||
|
||||
Ok(written + padding_bytes + bytes_slice.len())
|
||||
bytes::serialize_vec_map(
|
||||
&uls_of_vars.0,
|
||||
bytes::serialize_slice,
|
||||
bytes::serialize_slice_of_slices,
|
||||
writer,
|
||||
written,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: &[u8]) -> (Self, &[(Symbol, Variable)]) {
|
||||
fn deserialize_uls_of_var(bytes: &[u8], length: usize, offset: usize) -> (UlsOfVar, usize) {
|
||||
let (vec_map, offset) = bytes::deserialize_vec_map(
|
||||
bytes,
|
||||
bytes::deserialize_vec,
|
||||
bytes::deserialize_slice_of_slices,
|
||||
length,
|
||||
offset,
|
||||
);
|
||||
|
||||
(UlsOfVar(vec_map), offset)
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn deserialize(bytes: &[u8]) -> ((Self, &[(Symbol, Variable)]), usize) {
|
||||
let mut offset = 0;
|
||||
let header_slice = &bytes[..std::mem::size_of::<SubsHeader>()];
|
||||
offset += header_slice.len();
|
||||
@ -216,37 +220,43 @@ impl Subs {
|
||||
|
||||
let (utable, offset) = UnificationTable::deserialize(bytes, header.utable as usize, offset);
|
||||
|
||||
let (variables, offset) = Self::deserialize_slice(bytes, header.variables as usize, offset);
|
||||
let (variables, offset) =
|
||||
bytes::deserialize_slice(bytes, header.variables as usize, offset);
|
||||
let (tag_names, offset) =
|
||||
Self::deserialize_tag_names(bytes, header.tag_names as usize, offset);
|
||||
let (closure_names, offset) =
|
||||
Self::deserialize_slice(bytes, header.closure_names as usize, offset);
|
||||
bytes::deserialize_slice(bytes, header.closure_names as usize, offset);
|
||||
let (field_names, offset) =
|
||||
Self::deserialize_field_names(bytes, header.field_names as usize, offset);
|
||||
let (record_fields, offset) =
|
||||
Self::deserialize_slice(bytes, header.record_fields as usize, offset);
|
||||
bytes::deserialize_slice(bytes, header.record_fields as usize, offset);
|
||||
let (variable_slices, offset) =
|
||||
Self::deserialize_slice(bytes, header.variable_slices as usize, offset);
|
||||
bytes::deserialize_slice(bytes, header.variable_slices as usize, offset);
|
||||
let (unspecialized_lambda_sets, offset) =
|
||||
Self::deserialize_slice(bytes, header.unspecialized_lambda_sets as usize, offset);
|
||||
let (exposed_vars_by_symbol, _) =
|
||||
Self::deserialize_slice(bytes, header.exposed_vars_by_symbol as usize, offset);
|
||||
bytes::deserialize_slice(bytes, header.unspecialized_lambda_sets as usize, offset);
|
||||
let (uls_of_var, offset) =
|
||||
Self::deserialize_uls_of_var(bytes, header.uls_of_var as usize, offset);
|
||||
let (exposed_vars_by_symbol, offset) =
|
||||
bytes::deserialize_slice(bytes, header.exposed_vars_by_symbol as usize, offset);
|
||||
|
||||
(
|
||||
Self {
|
||||
utable,
|
||||
variables: variables.to_vec(),
|
||||
tag_names: tag_names.to_vec(),
|
||||
closure_names: closure_names.to_vec(),
|
||||
field_names,
|
||||
record_fields: record_fields.to_vec(),
|
||||
variable_slices: variable_slices.to_vec(),
|
||||
unspecialized_lambda_sets: unspecialized_lambda_sets.to_vec(),
|
||||
tag_name_cache: Default::default(),
|
||||
problems: Default::default(),
|
||||
uls_of_var: Default::default(),
|
||||
},
|
||||
exposed_vars_by_symbol,
|
||||
(
|
||||
Self {
|
||||
utable,
|
||||
variables: variables.to_vec(),
|
||||
tag_names: tag_names.to_vec(),
|
||||
closure_names: closure_names.to_vec(),
|
||||
field_names,
|
||||
record_fields: record_fields.to_vec(),
|
||||
variable_slices: variable_slices.to_vec(),
|
||||
unspecialized_lambda_sets: unspecialized_lambda_sets.to_vec(),
|
||||
tag_name_cache: Default::default(),
|
||||
problems: Default::default(),
|
||||
uls_of_var,
|
||||
},
|
||||
exposed_vars_by_symbol,
|
||||
),
|
||||
offset,
|
||||
)
|
||||
}
|
||||
|
||||
@ -255,7 +265,7 @@ impl Subs {
|
||||
length: usize,
|
||||
offset: usize,
|
||||
) -> (Vec<Lowercase>, usize) {
|
||||
let (slices, mut offset) = Self::deserialize_slice::<SubsSlice<u8>>(bytes, length, offset);
|
||||
let (slices, mut offset) = bytes::deserialize_slice::<SubsSlice<u8>>(bytes, length, offset);
|
||||
|
||||
let string_slice = &bytes[offset..];
|
||||
|
||||
@ -273,7 +283,7 @@ impl Subs {
|
||||
|
||||
fn deserialize_tag_names(bytes: &[u8], length: usize, offset: usize) -> (Vec<TagName>, usize) {
|
||||
let (slices, mut offset) =
|
||||
Self::deserialize_slice::<SerializedTagName>(bytes, length, offset);
|
||||
bytes::deserialize_slice::<SerializedTagName>(bytes, length, offset);
|
||||
|
||||
let string_slice = &bytes[offset..];
|
||||
|
||||
@ -290,24 +300,6 @@ impl Subs {
|
||||
|
||||
(tag_names, offset)
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_slice<T>(
|
||||
bytes: &[u8],
|
||||
length: usize,
|
||||
mut offset: usize,
|
||||
) -> (&[T], usize) {
|
||||
let alignment = std::mem::align_of::<T>();
|
||||
let size = std::mem::size_of::<T>();
|
||||
|
||||
offset = round_to_multiple_of(offset, alignment);
|
||||
|
||||
let byte_length = length * size;
|
||||
let byte_slice = &bytes[offset..][..byte_length];
|
||||
|
||||
let slice = unsafe { std::slice::from_raw_parts(byte_slice.as_ptr() as *const T, length) };
|
||||
|
||||
(slice, offset + byte_length)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mapping of variables to [Content::LambdaSet]s containing unspecialized lambda sets depending on
|
||||
|
@ -209,7 +209,7 @@ impl LambdaSet {
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
pub struct AliasCommon {
|
||||
pub symbol: Symbol,
|
||||
pub type_arguments: Vec<Type>,
|
||||
pub type_arguments: Vec<Loc<OptAbleType>>,
|
||||
pub lambda_set_variables: Vec<LambdaSet>,
|
||||
}
|
||||
|
||||
@ -282,7 +282,7 @@ pub enum Type {
|
||||
},
|
||||
RecursiveTagUnion(Variable, Vec<(TagName, Vec<Type>)>, TypeExtension),
|
||||
/// Applying a type to some arguments (e.g. Dict.Dict String Int)
|
||||
Apply(Symbol, Vec<Type>, Region),
|
||||
Apply(Symbol, Vec<Loc<Type>>, Region),
|
||||
Variable(Variable),
|
||||
RangedNumber(NumericRange),
|
||||
/// A type error, which will code gen to a runtime error
|
||||
@ -793,7 +793,7 @@ impl Type {
|
||||
..
|
||||
}) => {
|
||||
for value in type_arguments.iter_mut() {
|
||||
stack.push(value);
|
||||
stack.push(&mut value.value.typ);
|
||||
}
|
||||
|
||||
for lambda_set in lambda_set_variables.iter_mut() {
|
||||
@ -833,7 +833,7 @@ impl Type {
|
||||
stack.push(actual_type);
|
||||
}
|
||||
Apply(_, args, _) => {
|
||||
stack.extend(args);
|
||||
stack.extend(args.iter_mut().map(|t| &mut t.value));
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
UnspecializedLambdaSet {
|
||||
@ -915,7 +915,7 @@ impl Type {
|
||||
..
|
||||
}) => {
|
||||
for value in type_arguments.iter_mut() {
|
||||
stack.push(value);
|
||||
stack.push(&mut value.value.typ);
|
||||
}
|
||||
|
||||
for lambda_set in lambda_set_variables.iter_mut() {
|
||||
@ -954,7 +954,7 @@ impl Type {
|
||||
stack.push(actual_type);
|
||||
}
|
||||
Apply(_, args, _) => {
|
||||
stack.extend(args);
|
||||
stack.extend(args.iter_mut().map(|t| &mut t.value));
|
||||
}
|
||||
RangedNumber(_) => {}
|
||||
UnspecializedLambdaSet {
|
||||
@ -1021,7 +1021,9 @@ impl Type {
|
||||
..
|
||||
}) => {
|
||||
for ta in type_arguments {
|
||||
ta.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
ta.value
|
||||
.typ
|
||||
.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -1042,13 +1044,16 @@ impl Type {
|
||||
} => actual_type.substitute_alias(rep_symbol, rep_args, actual),
|
||||
Apply(symbol, args, region) if *symbol == rep_symbol => {
|
||||
if args.len() == rep_args.len()
|
||||
&& args.iter().zip(rep_args.iter()).all(|(t1, t2)| t1 == t2)
|
||||
&& args
|
||||
.iter()
|
||||
.zip(rep_args.iter())
|
||||
.all(|(t1, t2)| &t1.value == t2)
|
||||
{
|
||||
*self = actual.clone();
|
||||
|
||||
if let Apply(_, args, _) = self {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
arg.value.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
@ -1057,7 +1062,7 @@ impl Type {
|
||||
}
|
||||
Apply(_, args, _) => {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
arg.value.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -1103,7 +1108,9 @@ impl Type {
|
||||
..
|
||||
}) => {
|
||||
symbol == &rep_symbol
|
||||
|| type_arguments.iter().any(|v| v.contains_symbol(rep_symbol))
|
||||
|| type_arguments
|
||||
.iter()
|
||||
.any(|v| v.value.typ.contains_symbol(rep_symbol))
|
||||
|| lambda_set_variables
|
||||
.iter()
|
||||
.any(|v| v.0.contains_symbol(rep_symbol))
|
||||
@ -1117,7 +1124,7 @@ impl Type {
|
||||
name == &rep_symbol || actual.contains_symbol(rep_symbol)
|
||||
}
|
||||
Apply(symbol, _, _) if *symbol == rep_symbol => true,
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.value.contains_symbol(rep_symbol)),
|
||||
RangedNumber(_) => false,
|
||||
UnspecializedLambdaSet {
|
||||
unspecialized: Uls(_, sym, _),
|
||||
@ -1174,7 +1181,9 @@ impl Type {
|
||||
..
|
||||
} => actual_type.contains_variable(rep_variable),
|
||||
HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable),
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
|
||||
Apply(_, args, _) => args
|
||||
.iter()
|
||||
.any(|arg| arg.value.contains_variable(rep_variable)),
|
||||
RangedNumber(_) => false,
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) => false,
|
||||
}
|
||||
@ -1259,7 +1268,12 @@ impl Type {
|
||||
.iter()
|
||||
.all(|lambda_set| matches!(lambda_set.0, Type::Variable(..))));
|
||||
type_arguments.iter_mut().for_each(|t| {
|
||||
t.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables)
|
||||
t.value.typ.instantiate_aliases(
|
||||
region,
|
||||
aliases,
|
||||
var_store,
|
||||
new_lambda_set_variables,
|
||||
)
|
||||
});
|
||||
}
|
||||
HostExposedAlias {
|
||||
@ -1316,8 +1330,14 @@ impl Type {
|
||||
if false {
|
||||
let mut type_var_to_arg = Vec::new();
|
||||
|
||||
for (_, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
type_var_to_arg.push(arg_ann.clone());
|
||||
for (alias_var, arg_ann) in alias.type_variables.iter().zip(args) {
|
||||
type_var_to_arg.push(Loc::at(
|
||||
arg_ann.region,
|
||||
OptAbleType {
|
||||
typ: arg_ann.value.clone(),
|
||||
opt_ability: alias_var.value.opt_bound_ability,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let mut lambda_set_variables =
|
||||
@ -1370,17 +1390,17 @@ impl Type {
|
||||
) in alias.type_variables.iter().zip(args.iter())
|
||||
{
|
||||
let mut filler = filler.clone();
|
||||
filler.instantiate_aliases(
|
||||
filler.value.instantiate_aliases(
|
||||
region,
|
||||
aliases,
|
||||
var_store,
|
||||
new_lambda_set_variables,
|
||||
);
|
||||
named_args.push(OptAbleType {
|
||||
typ: filler.clone(),
|
||||
typ: filler.value.clone(),
|
||||
opt_ability: *opt_bound_ability,
|
||||
});
|
||||
substitution.insert(*placeholder, filler);
|
||||
substitution.insert(*placeholder, filler.value);
|
||||
}
|
||||
|
||||
// make sure hidden variables are freshly instantiated
|
||||
@ -1435,7 +1455,12 @@ impl Type {
|
||||
} else {
|
||||
// one of the special-cased Apply types.
|
||||
for x in args {
|
||||
x.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables);
|
||||
x.value.instantiate_aliases(
|
||||
region,
|
||||
aliases,
|
||||
var_store,
|
||||
new_lambda_set_variables,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1552,7 +1577,7 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
|
||||
..
|
||||
}) => {
|
||||
output.push(*symbol);
|
||||
stack.extend(type_arguments);
|
||||
stack.extend(type_arguments.iter().map(|ta| &ta.value.typ));
|
||||
}
|
||||
Alias {
|
||||
symbol: alias_symbol,
|
||||
@ -1572,7 +1597,7 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
|
||||
}
|
||||
Apply(symbol, args, _) => {
|
||||
output.push(*symbol);
|
||||
stack.extend(args);
|
||||
stack.extend(args.iter().map(|t| &t.value));
|
||||
}
|
||||
Erroneous(Problem::CyclicAlias(alias, _, _)) => {
|
||||
output.push(*alias);
|
||||
@ -1679,7 +1704,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
||||
..
|
||||
}) => {
|
||||
for arg in type_arguments {
|
||||
variables_help(arg, accum);
|
||||
variables_help(&arg.value.typ, accum);
|
||||
}
|
||||
|
||||
for lambda_set in lambda_set_variables {
|
||||
@ -1709,7 +1734,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
||||
RangedNumber(_) => {}
|
||||
Apply(_, args, _) => {
|
||||
for x in args {
|
||||
variables_help(x, accum);
|
||||
variables_help(&x.value, accum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1823,7 +1848,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
|
||||
..
|
||||
}) => {
|
||||
for arg in type_arguments {
|
||||
variables_help_detailed(arg, accum);
|
||||
variables_help_detailed(&arg.value.typ, accum);
|
||||
}
|
||||
|
||||
for lambda_set in lambda_set_variables {
|
||||
@ -1857,7 +1882,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
|
||||
RangedNumber(_) => {}
|
||||
Apply(_, args, _) => {
|
||||
for x in args {
|
||||
variables_help_detailed(x, accum);
|
||||
variables_help_detailed(&x.value, accum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2928,7 +2953,7 @@ fn instantiate_lambda_sets_as_unspecialized(
|
||||
debug_assert!(matches!(lambda_set.0, Type::Variable(_)));
|
||||
lambda_set.0 = new_uls();
|
||||
}
|
||||
stack.extend(type_arguments.iter_mut().rev());
|
||||
stack.extend(type_arguments.iter_mut().rev().map(|ta| &mut ta.value.typ));
|
||||
}
|
||||
Type::Alias {
|
||||
symbol: _,
|
||||
@ -2959,7 +2984,7 @@ fn instantiate_lambda_sets_as_unspecialized(
|
||||
stack.extend(type_arguments.iter_mut().rev());
|
||||
}
|
||||
Type::Apply(_sym, args, _region) => {
|
||||
stack.extend(args.iter_mut().rev());
|
||||
stack.extend(args.iter_mut().rev().map(|t| &mut t.value));
|
||||
}
|
||||
Type::Variable(_) => {}
|
||||
Type::RangedNumber(_) => {}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::hint::unreachable_unchecked;
|
||||
|
||||
use crate::subs::{Content, Descriptor, Mark, OptVariable, Rank, Variable, VariableSubsSlice};
|
||||
use roc_serialize::bytes;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct UnificationTable {
|
||||
@ -376,9 +377,7 @@ impl UnificationTable {
|
||||
writer: &mut impl std::io::Write,
|
||||
mut written: usize,
|
||||
) -> std::io::Result<usize> {
|
||||
use crate::subs::Subs;
|
||||
|
||||
written = Subs::serialize_slice(&self.contents, writer, written)?;
|
||||
written = bytes::serialize_slice(&self.contents, writer, written)?;
|
||||
|
||||
let mut ranks = Vec::new();
|
||||
let mut marks = Vec::new();
|
||||
@ -406,22 +405,20 @@ impl UnificationTable {
|
||||
}
|
||||
}
|
||||
|
||||
written = Subs::serialize_slice(&ranks, writer, written)?;
|
||||
written = Subs::serialize_slice(&marks, writer, written)?;
|
||||
written = Subs::serialize_slice(&copies, writer, written)?;
|
||||
written = Subs::serialize_slice(&redirects, writer, written)?;
|
||||
written = bytes::serialize_slice(&ranks, writer, written)?;
|
||||
written = bytes::serialize_slice(&marks, writer, written)?;
|
||||
written = bytes::serialize_slice(&copies, writer, written)?;
|
||||
written = bytes::serialize_slice(&redirects, writer, written)?;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize(bytes: &[u8], length: usize, offset: usize) -> (Self, usize) {
|
||||
use crate::subs::Subs;
|
||||
|
||||
let (contents, offset) = Subs::deserialize_slice::<Content>(bytes, length, offset);
|
||||
let (ranks, offset) = Subs::deserialize_slice::<Rank>(bytes, length, offset);
|
||||
let (marks, offset) = Subs::deserialize_slice::<Mark>(bytes, length, offset);
|
||||
let (copies, offset) = Subs::deserialize_slice::<OptVariable>(bytes, length, offset);
|
||||
let (redirects, offset) = Subs::deserialize_slice::<OptVariable>(bytes, length, offset);
|
||||
let (contents, offset) = bytes::deserialize_slice::<Content>(bytes, length, offset);
|
||||
let (ranks, offset) = bytes::deserialize_slice::<Rank>(bytes, length, offset);
|
||||
let (marks, offset) = bytes::deserialize_slice::<Mark>(bytes, length, offset);
|
||||
let (copies, offset) = bytes::deserialize_slice::<OptVariable>(bytes, length, offset);
|
||||
let (redirects, offset) = bytes::deserialize_slice::<OptVariable>(bytes, length, offset);
|
||||
|
||||
let mut metadata = Vec::with_capacity(ranks.len());
|
||||
|
||||
|
@ -340,6 +340,7 @@ pub fn unify(env: &mut Env, var1: Variable, var2: Variable, mode: Mode) -> Unifi
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn unify_introduced_ability_specialization(
|
||||
env: &mut Env,
|
||||
ability_member_signature: Variable,
|
||||
@ -350,6 +351,7 @@ pub fn unify_introduced_ability_specialization(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn unify_with_collector<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
var1: Variable,
|
||||
@ -360,6 +362,7 @@ pub fn unify_with_collector<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_help<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
var1: Variable,
|
||||
@ -416,6 +419,7 @@ fn unify_help<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn unify_pool<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -496,6 +500,7 @@ fn debug_print_unified_types<M: MetaCollector>(
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn unify_context<M: MetaCollector>(env: &mut Env, pool: &mut Pool, ctx: Context) -> Outcome<M> {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_print_unified_types::<M>(env, &ctx, None);
|
||||
@ -555,6 +560,7 @@ fn not_in_range_mismatch<M: MetaCollector>() -> Outcome<M> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_ranged_number<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -714,6 +720,7 @@ fn wrap_range_var(
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[must_use]
|
||||
fn unify_two_aliases<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -812,6 +819,7 @@ fn unify_two_aliases<M: MetaCollector>(
|
||||
|
||||
// Unifies a structural alias
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_alias<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -870,6 +878,7 @@ fn opaque_obligation(opaque: Symbol, opaque_var: Variable) -> Obligated {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_opaque<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -934,6 +943,7 @@ fn unify_opaque<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_structure<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -1032,6 +1042,7 @@ fn unify_structure<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_lambda_set<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -1358,6 +1369,7 @@ fn is_sorted_unspecialized_lamba_set_list(subs: &Subs, uls: &[Uls]) -> bool {
|
||||
uls == sort_unspecialized_lambda_sets(subs, uls.to_vec())
|
||||
}
|
||||
|
||||
#[must_use = "must use outcomes!"]
|
||||
fn unify_unspecialized_lambdas<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -1602,6 +1614,7 @@ fn unify_unspecialized_lambdas<M: MetaCollector>(
|
||||
))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn unify_lambda_set_help<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -1695,6 +1708,7 @@ fn unify_lambda_set_help<M: MetaCollector>(
|
||||
whole_outcome
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn unify_record<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -1801,6 +1815,7 @@ enum OtherFields {
|
||||
|
||||
type SharedFields = Vec<(Lowercase, (RecordField<Variable>, RecordField<Variable>))>;
|
||||
|
||||
#[must_use]
|
||||
fn unify_shared_fields<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -2109,6 +2124,7 @@ fn should_extend_ext_with_uninhabited_type(
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[must_use]
|
||||
fn unify_tag_unions<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -2409,6 +2425,7 @@ fn choose_merged_var(subs: &Subs, var1: Variable, var2: Variable) -> Variable {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn unify_shared_tags_new<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -2539,6 +2556,7 @@ fn unify_shared_tags_new<M: MetaCollector>(
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn unify_shared_tags_merge_new<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
ctx: &Context,
|
||||
@ -2558,6 +2576,7 @@ fn unify_shared_tags_merge_new<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_flat_type<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -2757,6 +2776,7 @@ fn unify_flat_type<M: MetaCollector>(
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn unify_zip_slices<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -2778,6 +2798,7 @@ fn unify_zip_slices<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_rigid<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
ctx: &Context,
|
||||
@ -2821,6 +2842,7 @@ fn unify_rigid<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_rigid_able<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
ctx: &Context,
|
||||
@ -2870,6 +2892,7 @@ fn unify_rigid_able<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_flex<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
ctx: &Context,
|
||||
@ -2906,6 +2929,7 @@ fn unify_flex<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_flex_able<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
ctx: &Context,
|
||||
@ -2948,7 +2972,6 @@ fn unify_flex_able<M: MetaCollector>(
|
||||
}
|
||||
|
||||
RigidVar(_) => mismatch!("FlexAble can never unify with non-able Rigid"),
|
||||
RecursionVar { .. } => mismatch!("FlexAble with RecursionVar"),
|
||||
LambdaSet(..) => mismatch!("FlexAble with LambdaSet"),
|
||||
|
||||
Alias(name, _args, _real_var, AliasKind::Opaque) => {
|
||||
@ -2963,7 +2986,10 @@ fn unify_flex_able<M: MetaCollector>(
|
||||
)
|
||||
}
|
||||
|
||||
Structure(_) | Alias(_, _, _, AliasKind::Structural) | RangedNumber(..) => {
|
||||
RecursionVar { .. }
|
||||
| Structure(_)
|
||||
| Alias(_, _, _, AliasKind::Structural)
|
||||
| RangedNumber(..) => {
|
||||
// Structural type wins.
|
||||
merge_flex_able_with_concrete(
|
||||
env,
|
||||
@ -2979,6 +3005,7 @@ fn unify_flex_able<M: MetaCollector>(
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn merge_flex_able_with_concrete<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
ctx: &Context,
|
||||
@ -3012,6 +3039,7 @@ fn merge_flex_able_with_concrete<M: MetaCollector>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
fn unify_recursion<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
@ -3046,10 +3074,22 @@ fn unify_recursion<M: MetaCollector>(
|
||||
mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
FlexAbleVar(..) | RigidAbleVar(..) => {
|
||||
RigidAbleVar(..) => {
|
||||
mismatch!("RecursionVar {:?} with able var {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
FlexAbleVar(_, ability) => merge_flex_able_with_concrete(
|
||||
env,
|
||||
ctx,
|
||||
ctx.second,
|
||||
*ability,
|
||||
RecursionVar {
|
||||
structure,
|
||||
opt_name: *opt_name,
|
||||
},
|
||||
Obligated::Adhoc(ctx.first),
|
||||
),
|
||||
|
||||
FlexVar(_) => merge(
|
||||
env,
|
||||
ctx,
|
||||
@ -3086,6 +3126,7 @@ fn unify_recursion<M: MetaCollector>(
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn merge<M: MetaCollector>(env: &mut Env, ctx: &Context, content: Content) -> Outcome<M> {
|
||||
let mut outcome: Outcome<M> = Outcome::default();
|
||||
|
||||
@ -3140,6 +3181,7 @@ fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[must_use]
|
||||
fn unify_function_or_tag_union_and_func<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
|
@ -9,7 +9,7 @@ use roc_code_markup::slow_pool::SlowPool;
|
||||
use roc_highlight::highlight_parser::{highlight_defs, highlight_expr};
|
||||
use roc_load::docs::DocEntry::DocDef;
|
||||
use roc_load::docs::{DocEntry, TypeAnnotation};
|
||||
use roc_load::docs::{ModuleDocumentation, RecordField};
|
||||
use roc_load::docs::{Documentation, ModuleDocumentation, RecordField};
|
||||
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
|
||||
use roc_module::symbol::{IdentIdsByModule, Interns, ModuleId};
|
||||
use roc_parse::ident::{parse_ident, Ident};
|
||||
@ -28,7 +28,7 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
||||
let loaded_modules = load_modules_for_files(filenames);
|
||||
|
||||
// TODO: get info from a package module; this is all hardcoded for now.
|
||||
let mut package = roc_load::docs::Documentation {
|
||||
let package = Documentation {
|
||||
name: "documentation".to_string(),
|
||||
version: "".to_string(),
|
||||
docs: "Package introduction or README.".to_string(),
|
||||
@ -104,7 +104,7 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
||||
);
|
||||
|
||||
// Write each package's module docs html file
|
||||
for loaded_module in package.modules.iter_mut() {
|
||||
for loaded_module in package.modules.iter() {
|
||||
for (module_id, module_docs) in loaded_module.documentation.iter() {
|
||||
if *module_id == loaded_module.module_id {
|
||||
let module_dir = build_dir.join(module_docs.name.replace('.', "/").as_str());
|
||||
@ -113,6 +113,10 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
||||
.expect("TODO gracefully handle not being able to create the module dir");
|
||||
|
||||
let rendered_module = template_html
|
||||
.replace(
|
||||
"<!-- Page title -->",
|
||||
page_title(&package, module_docs).as_str(),
|
||||
)
|
||||
.replace(
|
||||
"<!-- Package Name and Version -->",
|
||||
render_name_and_version(package.name.as_str(), package.version.as_str())
|
||||
@ -134,10 +138,15 @@ pub fn generate_docs_html(filenames: Vec<PathBuf>) {
|
||||
}
|
||||
|
||||
fn sidebar_link_url(module: &ModuleDocumentation) -> String {
|
||||
let mut href_buf = base_url();
|
||||
href_buf.push_str(module.name.as_str());
|
||||
let url = format!("{}{}/", base_url(), module.name.as_str());
|
||||
url
|
||||
}
|
||||
|
||||
href_buf
|
||||
fn page_title(package: &Documentation, module: &ModuleDocumentation) -> String {
|
||||
let package_name = &package.name;
|
||||
let module_name = &module.name;
|
||||
let title = format!("<title>{module_name} - {package_name}</title>");
|
||||
title
|
||||
}
|
||||
|
||||
// converts plain-text code to highlighted html
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!-- <title>TODO populate this based on the module's name, e.g. "Parser - roc/parser"</title> -->
|
||||
<!-- Page title -->
|
||||
<!-- <meta name="description" content="TODO populate this based on the module's description"> -->
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<script type="text/javascript" src="<!-- search.js -->" defer></script>
|
||||
@ -15,7 +15,7 @@
|
||||
<body>
|
||||
<nav id="sidebar-nav">
|
||||
<input id="module-search" aria-labelledby="search-link" type="text" placeholder="Search" />
|
||||
<label for="module-search" id="search-link">Search</label>
|
||||
<label for="module-search" id="search-link"><span id="search-link-text">Search</span> <span id="search-link-hint">(shortcut: S)</span></label>
|
||||
<div class="module-links">
|
||||
<!-- Module links -->
|
||||
</div>
|
||||
|
@ -45,20 +45,24 @@
|
||||
|
||||
search();
|
||||
|
||||
// Capture '/' keypress for quick search
|
||||
// Capture '/' keypress for quick search
|
||||
window.addEventListener("keyup", (e) => {
|
||||
|
||||
if (e.code === "Slash") {
|
||||
if (e.key === "s" && document.activeElement !== searchBox) {
|
||||
e.preventDefault;
|
||||
searchBox.focus();
|
||||
searchBox.value = "";
|
||||
}
|
||||
|
||||
if (e.code === "Escape" && document.activeElement === searchBox) {
|
||||
if (e.key === "Escape" && document.activeElement === searchBox) {
|
||||
e.preventDefault;
|
||||
searchBox.blur();
|
||||
}
|
||||
|
||||
// De-focus input box
|
||||
searchBox.blur();
|
||||
|
||||
// Reset sidebar state
|
||||
search();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
|
@ -446,10 +446,16 @@ pre {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#search-link:hover {
|
||||
#search-link:hover #search-link-text {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#search-link-hint {
|
||||
font-style: italic;
|
||||
margin-left: 1em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--body-bg-color: var(--purple-8);
|
||||
|
@ -21,4 +21,4 @@ roc_docs = { path = "../docs" }
|
||||
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions", "derive"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
libc = "0.2.133"
|
||||
libc = "0.2.135"
|
||||
|
@ -35,7 +35,7 @@ roc_solve = { path = "../compiler/solve" }
|
||||
ven_graph = { path = "../vendor/pathfinding" }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
arrayvec = "0.7.2"
|
||||
libc = "0.2.133"
|
||||
libc = "0.2.135"
|
||||
page_size = "0.4.2"
|
||||
# once winit 0.26 is out, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time.
|
||||
winit = "0.26.0"
|
||||
|
@ -6,7 +6,7 @@ Unlike most editors, we use projectional or structural editing to edit the [Abst
|
||||
|
||||
## Getting started
|
||||
|
||||
- Install the compiler, see [here](../BUILDING_FROM_SOURCE.md).
|
||||
- Install the compiler, see [here](../../BUILDING_FROM_SOURCE.md).
|
||||
- Run the following from the roc folder:
|
||||
|
||||
```sh
|
||||
@ -75,4 +75,4 @@ Important folders/files outside the editor folder:
|
||||
## Contributing
|
||||
|
||||
We welcome new contributors :heart: and are happy to help you get started.
|
||||
Check [CONTRIBUTING.md](../CONTRIBUTING.md) for more info.
|
||||
Check [CONTRIBUTING.md](../../CONTRIBUTING.md) for more info.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user