Merge branch 'trunk' into update_zig_09

This commit is contained in:
rvcas 2022-02-09 18:42:24 -05:00
commit 0124e4d4b1
No known key found for this signature in database
GPG key ID: C09B64E263F7D68C
486 changed files with 25920 additions and 11808 deletions

View file

@ -1,4 +1,4 @@
[alias] [alias]
test-gen-llvm = "test -p test_gen" test-gen-llvm = "test -p test_gen"
test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev" test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev"
test-gen-wasm = "test -p test_gen --no-default-features --features gen-wasm" test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm"

View file

@ -61,3 +61,5 @@ Shahn Hogan <shahnhogan@hotmail.com>
Tankor Smash <tankorsmash+github@gmail.com> Tankor Smash <tankorsmash+github@gmail.com>
Matthias Devlamynck <matthias.devlamynck@mailoo.org> Matthias Devlamynck <matthias.devlamynck@mailoo.org>
Jan Van Bruggen <JanCVanB@users.noreply.github.com> Jan Van Bruggen <JanCVanB@users.noreply.github.com>
Mats Sigge <<mats.sigge@gmail.com>>
Drew Lazzeri <dlazzeri1@gmail.com>

View file

@ -79,6 +79,12 @@ There are also alternative installation options at http://releases.llvm.org/down
[Troubleshooting](#troubleshooting) [Troubleshooting](#troubleshooting)
### Building
Use `cargo build` to build the whole project.
Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`.
## Using Nix ## Using Nix
### Install ### Install
@ -105,11 +111,10 @@ Now with nix installed, you just need to run one command:
> Also, if you're on NixOS you'll need to enable opengl at the system-wide level. You can do this in configuration.nix with `hardware.opengl.enable = true;`. If you don't do this, nix-shell will fail! > Also, if you're on NixOS you'll need to enable opengl at the system-wide level. You can do this in configuration.nix with `hardware.opengl.enable = true;`. If you don't do this, nix-shell will fail!
You should be in a shell with everything needed to build already installed. Next run: You should be in a shell with everything needed to build already installed.
Use `cargo run help` to see all subcommands.
`cargo run repl` To use the `repl` subcommand, execute `cargo run repl`.
Use `cargo build` to build the whole project.
You should be in a repl now. Have fun!
### Extra tips ### Extra tips

View file

@ -22,6 +22,15 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is
- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation. - Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
- It's a good idea to open a work-in-progress 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. Be sure to include "WIP" in the title of the PR as long as it's not ready for review! - It's a good idea to open a work-in-progress 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. Be sure to include "WIP" in the title of the PR as long as it's not ready for review!
- Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security. - Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security.
- All your commits need to be signed to prevent impersonation:
1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below.
2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key).
3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
4. Make git sign your commits automatically:
```
git config --global commit.gpgsign true
```
- You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). - You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
## Can we do better? ## Can we do better?

183
Cargo.lock generated
View file

@ -1306,12 +1306,6 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.22" version = "1.0.22"
@ -1865,9 +1859,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.55" version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2676,14 +2670,11 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [ dependencies = [
"backtrace",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"instant", "instant",
"libc", "libc",
"petgraph",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"thread-id",
"winapi", "winapi",
] ]
@ -2742,16 +2733,6 @@ dependencies = [
"sha-1", "sha-1",
] ]
[[package]]
name = "petgraph"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.9.0" version = "0.9.0"
@ -3204,6 +3185,17 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
[[package]]
name = "repl_test"
version = "0.1.0"
dependencies = [
"indoc",
"roc_cli",
"roc_repl_cli",
"roc_test_utils",
"strip-ansi-escapes",
]
[[package]] [[package]]
name = "rkyv" name = "rkyv"
version = "0.6.7" version = "0.6.7"
@ -3248,12 +3240,13 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting", "roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"snafu", "snafu",
@ -3283,6 +3276,7 @@ dependencies = [
"roc_reporting", "roc_reporting",
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"serde_json", "serde_json",
@ -3297,6 +3291,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_region", "roc_region",
"roc_target",
"roc_types", "roc_types",
] ]
@ -3327,32 +3322,26 @@ dependencies = [
"const_format", "const_format",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
"indoc", "indoc",
"inkwell 0.1.0",
"libloading 0.7.1",
"mimalloc", "mimalloc",
"pretty_assertions", "pretty_assertions",
"roc_build", "roc_build",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_docs", "roc_docs",
"roc_editor", "roc_editor",
"roc_error_macros",
"roc_fmt", "roc_fmt",
"roc_gen_llvm",
"roc_linker", "roc_linker",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
"roc_problem",
"roc_region", "roc_region",
"roc_repl_cli",
"roc_reporting", "roc_reporting",
"roc_solve", "roc_target",
"roc_types", "roc_test_utils",
"roc_unify",
"rustyline",
"rustyline-derive",
"serial_test", "serial_test",
"target-lexicon", "target-lexicon",
"tempfile", "tempfile",
@ -3414,6 +3403,7 @@ dependencies = [
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_region", "roc_region",
"roc_target",
"roc_types", "roc_types",
"snafu", "snafu",
"tempfile", "tempfile",
@ -3469,6 +3459,10 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "roc_error_macros"
version = "0.1.0"
[[package]] [[package]]
name = "roc_fmt" name = "roc_fmt"
version = "0.1.0" version = "0.1.0"
@ -3494,14 +3488,15 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting",
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"target-lexicon", "target-lexicon",
@ -3516,10 +3511,11 @@ dependencies = [
"morphic_lib", "morphic_lib",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_std", "roc_std",
"roc_target",
"target-lexicon", "target-lexicon",
] ]
@ -3530,10 +3526,11 @@ dependencies = [
"bumpalo", "bumpalo",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_std", "roc_std",
"roc_target",
] ]
[[package]] [[package]]
@ -3581,6 +3578,7 @@ dependencies = [
"roc_region", "roc_region",
"roc_reporting", "roc_reporting",
"roc_solve", "roc_solve",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"tempfile", "tempfile",
@ -3594,6 +3592,7 @@ dependencies = [
"bumpalo", "bumpalo",
"lazy_static", "lazy_static",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_ident", "roc_ident",
"roc_region", "roc_region",
"snafu", "snafu",
@ -3610,11 +3609,13 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"static_assertions", "static_assertions",
@ -3652,6 +3653,69 @@ dependencies = [
[[package]] [[package]]
name = "roc_region" name = "roc_region"
version = "0.1.0" version = "0.1.0"
dependencies = [
"static_assertions",
]
[[package]]
name = "roc_repl_cli"
version = "0.1.0"
dependencies = [
"bumpalo",
"const_format",
"inkwell 0.1.0",
"libloading 0.7.1",
"roc_build",
"roc_builtins",
"roc_collections",
"roc_gen_llvm",
"roc_load",
"roc_mono",
"roc_parse",
"roc_repl_eval",
"roc_target",
"roc_types",
"rustyline",
"rustyline-derive",
"target-lexicon",
]
[[package]]
name = "roc_repl_eval"
version = "0.1.0"
dependencies = [
"bumpalo",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_fmt",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
"roc_region",
"roc_reporting",
"roc_target",
"roc_types",
]
[[package]]
name = "roc_repl_wasm"
version = "0.1.0"
dependencies = [
"bumpalo",
"js-sys",
"roc_builtins",
"roc_collections",
"roc_gen_wasm",
"roc_load",
"roc_parse",
"roc_repl_eval",
"roc_target",
"roc_types",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]] [[package]]
name = "roc_reporting" name = "roc_reporting"
@ -3671,6 +3735,8 @@ dependencies = [
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_solve", "roc_solve",
"roc_target",
"roc_test_utils",
"roc_types", "roc_types",
"ven_pretty", "ven_pretty",
] ]
@ -3692,6 +3758,7 @@ dependencies = [
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_solve", "roc_solve",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"tempfile", "tempfile",
@ -3707,6 +3774,13 @@ dependencies = [
"quickcheck_macros", "quickcheck_macros",
] ]
[[package]]
name = "roc_target"
version = "0.1.0"
dependencies = [
"target-lexicon",
]
[[package]] [[package]]
name = "roc_test_utils" name = "roc_test_utils"
version = "0.1.0" version = "0.1.0"
@ -3720,6 +3794,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_region", "roc_region",
"static_assertions", "static_assertions",
@ -3730,6 +3805,7 @@ dependencies = [
name = "roc_unify" name = "roc_unify"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitflags",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_types", "roc_types",
@ -4253,6 +4329,7 @@ dependencies = [
"roc_reporting", "roc_reporting",
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"target-lexicon", "target-lexicon",
@ -4274,6 +4351,7 @@ dependencies = [
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_target",
"test_mono_macros", "test_mono_macros",
] ]
@ -4321,17 +4399,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread-id"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f"
dependencies = [
"libc",
"redox_syscall",
"winapi",
]
[[package]] [[package]]
name = "threadpool" name = "threadpool"
version = "1.8.1" version = "1.8.1"
@ -4600,9 +4667,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.78" version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -4610,9 +4677,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.78" version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"lazy_static", "lazy_static",
@ -4625,9 +4692,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.28" version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"js-sys", "js-sys",
@ -4637,9 +4704,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.78" version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -4647,9 +4714,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.78" version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4660,9 +4727,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.78" version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
[[package]] [[package]]
name = "wasmer" name = "wasmer"

View file

@ -22,6 +22,7 @@ members = [
"compiler/build", "compiler/build",
"compiler/arena_pool", "compiler/arena_pool",
"compiler/test_gen", "compiler/test_gen",
"compiler/roc_target",
"vendor/ena", "vendor/ena",
"vendor/inkwell", "vendor/inkwell",
"vendor/pathfinding", "vendor/pathfinding",
@ -30,7 +31,12 @@ members = [
"ast", "ast",
"cli", "cli",
"code_markup", "code_markup",
"error_macros",
"reporting", "reporting",
"repl_cli",
"repl_eval",
"repl_test",
"repl_wasm",
"roc_std", "roc_std",
"test_utils", "test_utils",
"utils", "utils",

View file

@ -1,4 +1,4 @@
FROM rust:1.57.0-slim-bullseye FROM rust:1.57.0-slim-bullseye # make sure to update nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages
WORKDIR /earthbuild WORKDIR /earthbuild
prep-debian: prep-debian:
@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
test-zig: test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt

View file

@ -71,8 +71,6 @@ The core Roc language and standard library include no I/O operations, which give
* A VR or [Arduino](https://www.arduino.cc/) platform can expose uncommon I/O operations supported by that hardware, while omitting common I/O operations that are unsupported (such as reading keyboard input from a terminal that doesn't exist). * A VR or [Arduino](https://www.arduino.cc/) platform can expose uncommon I/O operations supported by that hardware, while omitting common I/O operations that are unsupported (such as reading keyboard input from a terminal that doesn't exist).
* A high-performance Web server written in Rust can be a Roc platform where all I/O operations are implemented in terms of Streams or Observables rather than a more traditional asynchronous abstraction like Futures or Promises. This would mean all code in that platform's ecosystem would be necessarily built on a common streaming abstraction. * A high-performance Web server written in Rust can be a Roc platform where all I/O operations are implemented in terms of Streams or Observables rather than a more traditional asynchronous abstraction like Futures or Promises. This would mean all code in that platform's ecosystem would be necessarily built on a common streaming abstraction.
Each Roc platform gets its own separate package repository, with packages built on top of the API that platform exposes. This means each platform has its own ecosystem where everything is built on top of the same shared set of platform-specific primitives.
## Project Goals ## Project Goals
Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/docs) is in even earlier stages than the compiler itself. Roc is in relatively early stages of development. It's currently possible to build both platforms and applications (see the [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) folder for some examples that aren't particularly organized at the moment), although [documentation](https://github.com/rtfeldman/roc/tree/trunk/compiler/builtins/docs) is in even earlier stages than the compiler itself.

View file

@ -291,7 +291,8 @@ Records are not objects; they don't have methods or inheritance, they just store
We create the record when we write `{ birds: 5, iguanas: 7 }`. This defines We create the record when we write `{ birds: 5, iguanas: 7 }`. This defines
a record with two *fields* - namely, the `birds` field and the `iguanas` field - a record with two *fields* - namely, the `birds` field and the `iguanas` field -
and then assigns the number `5` to the `birds` field and the number `7` to the and then assigns the number `5` to the `birds` field and the number `7` to the
`iguanas` field. `iguanas` field. Order doesn't matter with record fields; we could have also specified
`iguanas` first and `birds` second, and Roc would consider it the exact same record.
When we write `counts.birds`, it accesses the `birds` field of the `counts` record, When we write `counts.birds`, it accesses the `birds` field of the `counts` record,
and when we write `counts.iguanas` it accesses the `iguanas` field. When we use `==` and when we write `counts.iguanas` it accesses the `iguanas` field. When we use `==`
@ -328,6 +329,21 @@ values - including other records, or even functions!
{ birds: 4, nestedRecord: { someFunction: (\arg -> arg + 1), name: "Sam" } } { birds: 4, nestedRecord: { someFunction: (\arg -> arg + 1), name: "Sam" } }
``` ```
### Record shorthands
Roc has a couple of shorthands you can use to express some record-related operations more concisely.
Instead of writing `\record -> record.x` we can write `.x` and it will evaluate to the same thing:
a function that takes a record and returns its `x` field. You can do this with any field you want.
For example:
```elm
returnFoo = .foo
returnFoo { foo: "hi!", bar: "blah" }
# returns "hi!"
```
Whenever we're setting a field to be a def that has the same name as the field - Whenever we're setting a field to be a def that has the same name as the field -
for example, `{ x: x }` - we can shorten it to just writing the name of the def alone - for example, `{ x: x }` - we can shorten it to just writing the name of the def alone -
for example, `{ x }`. We can do this with as many fields as we like, e.g. for example, `{ x }`. We can do this with as many fields as we like, e.g.
@ -545,6 +561,15 @@ This makes two changes to our earlier `stoplightColor` / `stoplightStr` example.
Any tag can be given a payload like this. A payload doesn't have to be a string; we could also have said (for example) `Custom { r: 40, g: 60, b: 80 }` to specify an RGB color instead of a string. Then in our `when` we could have written `Custom record ->` and then after the `->` used `record.r`, `record.g`, and `record.b` to access the `40`, `60`, `80` values. We could also have written `Custom { r, g, b } ->` to *destructure* the record, and then Any tag can be given a payload like this. A payload doesn't have to be a string; we could also have said (for example) `Custom { r: 40, g: 60, b: 80 }` to specify an RGB color instead of a string. Then in our `when` we could have written `Custom record ->` and then after the `->` used `record.r`, `record.g`, and `record.b` to access the `40`, `60`, `80` values. We could also have written `Custom { r, g, b } ->` to *destructure* the record, and then
accessed these `r`, `g`, and `b` defs after the `->` instead. accessed these `r`, `g`, and `b` defs after the `->` instead.
A tag can also have a payload with more than one value. Instead of `Custom { r: 40, g: 60, b: 80 }` we could
write `Custom 40 60 80`. If we did that, then instead of destructuring a record with `Custom { r, g, b } ->`
inside a `when`, we would write `Custom r g b ->` to destructure the values directly out of the payload.
We refer to whatever comes before a `->` in a `when` expression as a *pattern* - so for example, in the
`Custom description -> description` branch, `Custom description` would be a pattern. In programming, using
patterns in branching conditionals like `when` is known as [pattern matching](https://en.wikipedia.org/wiki/Pattern_matching). You may hear people say things like "let's pattern match on `Custom` here" as a way to
suggest making a `when` branch that begins with something like `Custom description ->`.
## Lists ## Lists
Another thing we can do in Roc is to make a *list* of values. Here's an example: Another thing we can do in Roc is to make a *list* of values. Here's an example:
@ -654,6 +679,24 @@ Instead, we're using a `when` to tell when we've got a string or a number, and t
We could take this as far as we like, adding more different tags (e.g. `BoolElem True`) and then adding We could take this as far as we like, adding more different tags (e.g. `BoolElem True`) and then adding
more branches to the `when` to handle them appropriately. more branches to the `when` to handle them appropriately.
### Using tags as functions
Let's say I want to apply a tag to a bunch of elements in a list. For example:
```elm
List.map [ "a", "b", "c", ] \str -> Foo str
```
This is a perfectly reasonable way to write it, but I can also write it like this:
```elm
List.map [ "a", "b", "c", ] Foo
```
These two versions compile to the same thing. As a convenience, Roc lets you specify
a tag name where a function is expected; when you do this, the compiler infers that you
want a function which uses all of its arguments as the payload to the given tag.
### `List.any` and `List.all` ### `List.any` and `List.all`
There are several functions that work like `List.map` - they walk through each element of a list and do There are several functions that work like `List.map` - they walk through each element of a list and do
@ -1007,17 +1050,153 @@ of the type annotation, or even the function's implementation! The only way to h
Similarly, the only way to have a function whose type is `a -> a` is if the function's implementation returns Similarly, the only way to have a function whose type is `a -> a` is if the function's implementation returns
its argument without modifying it in any way. This is known as [the identity function](https://en.wikipedia.org/wiki/Identity_function). its argument without modifying it in any way. This is known as [the identity function](https://en.wikipedia.org/wiki/Identity_function).
### Numeric types ## Numeric types
[ This part of the tutorial has not been written yet. Coming soon! ] Roc has different numeric types that each have different tradeoffs.
They can all be broken down into two categories: [fractions](https://en.wikipedia.org/wiki/Fraction),
and [integers](https://en.wikipedia.org/wiki/Integer). In Roc we call these `Frac` and `Int` for short.
### Open and closed records ### Integers
[ This part of the tutorial has not been written yet. Coming soon! ] Roc's integer types have two important characteristics: their *size* and their [*signedness*](https://en.wikipedia.org/wiki/Signedness).
Together, these two characteristics determine the range of numbers the integer type can represent.
### Open and closed tag unions For example, the Roc type `U8` can represent the numbers 0 through 255, whereas the `I16` type can represent
the numbers -32768 through 32767. You can actually infer these ranges from their names (`U8` and `I16`) alone!
[ This part of the tutorial has not been written yet. Coming soon! ] The `U` in `U8` indicates that it's *unsigned*, meaning that it can't have a minus [sign](https://en.wikipedia.org/wiki/Sign_(mathematics)), and therefore can't be negative. The fact that it's unsigned tells us immediately that
its lowest value is zero. The 8 in `U8` means it is 8 [bits](https://en.wikipedia.org/wiki/Bit) in size, which
means it has room to represent 2⁸ (which is equal to 256) different numbers. Since one of those 256 different numbers
is 0, we can look at `U8` and know that it goes from `0` (since it's unsigned) to `255` (2⁸ - 1, since it's 8 bits).
If we change `U8` to `I8`, making it a *signed* 8-bit integer, the range changes. Because it's still 8 bits, it still
has room to represent 2⁸ (that is, 256) different numbers. However, now in addition to one of those 256 numbers
being zero, about half of rest will be negative, and the others positive. So instead of ranging from, say -255
to 255 (which, counting zero, would represent 511 different numbers; too many to fit in 8 bits!) an `I8` value
ranges from -128 to 127.
Notice that the negative extreme is `-128` versus `127` (not `128`) on the positive side. That's because of
needing room for zero; the slot for zero is taken from the positive range because zero doesn't have a minus sign.
So in general, you can find the lowest signed number by taking its total range (256 different numbers in the case
of an 8-bit integer) and dividing it in half (half of 256 is 128, so -128 is `I8`'s lowest number). To find the
highest number, take the positive version of the lowest number (so, convert `-128` to `128`) and then subtract 1
to make room for zero (so, `128` becomes `127`; `I8` ranges from -128 to 127).
Following this pattern, the 16 in `I16` means that it's a signed 16 bit integer.
That tells us it has room to represent 2¹⁶ (which is equal to 65536) different numbers. Half of 65536 is 32768,
so the lowest `I16` would be -32768, and the highest would be 32767. Knowing that, we can also quickly tell that
the lowest `U16` would be zero (since it always is for unsigned integers), and the higeest `U16` would be 65536.
Choosing a size depends on your performance needs and the range of numbers you want to represent. Consider:
* Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them!
* Smaller integer sizes take up less memory. These savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can easily be a performance bottleneck.
* Certain processors work faster on some numeric sizes than others. There isn't even a general rule like "larger numeric sizes run slower" (or the reverse, for that matter) that applies to all processors. In fact, if the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly!
Here are the different fixed-size integer types that Roc supports:
| Range | Type | Size |
| -----------------------------------------------------------: | :---- | :------- |
| `-128`<br/>`127` | `I8` | 1 Byte |
| `0`<br/>`255` | `U8` | 1 Byte |
| `-32_768`<br/>`32_767` | `I16` | 2 Bytes |
| `0`<br/>`65_535` | `U16` | 2 Bytes |
| `-2_147_483_648`<br/>`2_147_483_647` | `I32` | 4 Bytes |
| `0`<br/>(over 4 billion) `4_294_967_295` | `U32` | 4 Bytes |
| `-9_223_372_036_854_775_808`<br/>`9_223_372_036_854_775_807` | `I64` | 8 Bytes |
| `0`<br/>(over 18 quintillion) `18_446_744_073_709_551_615` | `U64` | 8 Bytes |
| `-170_141_183_460_469_231_731_687_303_715_884_105_728`<br/>`170_141_183_460_469_231_731_687_303_715_884_105_727` | `I128` | 16 Bytes |
| `0`<br/>(over 340 undecillion) `340_282_366_920_938_463_463_374_607_431_768_211_455` | `U128` | 16 Bytes |
Roc also has one variable-size integer type: `Nat` (short for "natural number").
The size of `Nat` is equal to the size of a memory address, which varies by system.
For example, when compiling for a 64-bit system, `Nat` works the same way as `U64`.
When compiling for a 32-bit system, it works the same way as `U32`. Most popular
computing devices today are 64-bit, so `Nat` is usually the same as `U64`, but
Web Assembly is typically 32-bit - so when running a Roc program built for Web Assembly,
`Nat` will work like a `U32` in that program.
A common use for `Nat` is to store the length of a collection like a `List`;
there's a function `List.len : List * -> Nat` which returns the length of the given list.
64-bit systems can represent longer lists in memory than 32-bit systems can,
which is why the length of a list is represented as a `Nat`.
If any operation would result in an integer that is either too big
or too small to fit in that range (e.g. calling `Int.maxI32 + 1`, which adds 1 to
the highest possible 32-bit integer), then the operation will *overflow*.
When an overflow occurs, the program will crash.
As such, it's very important to design your integer operations not to exceed these bounds!
### Fractions
Roc has three fractional types:
* `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
* `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
* `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
These are different from integers in that they can represent numbers with fractional components,
such as 1.5 and -0.123.
`Dec` is the best default choice for representing base-10 decimal numbers
like currency, because it is base-10 under the hood. In contrast,
`F64` and `F32` are base-2 under the hood, which can lead to decimal
precision loss even when doing addition and subtraction. For example, when
using `F64`, running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125,
whereas when using `Dec`, 0.1 + 0.2 returns 0.3.
`F32` and `F64` have direct hardware support on common processors today. There is no hardware support
for fixed-point decimals, so under the hood, a `Dec` is an `I128`; operations on it perform
[base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
with 18 decimal places of precision.
This means a `Dec` can represent whole numbers up to slightly over 170
quintillion, along with 18 decimal places. (To be precise, it can store
numbers betwween `-170_141_183_460_469_231_731.687303715884105728`
and `170_141_183_460_469_231_731.687303715884105727`.) Why 18
decimal places? It's the highest number of decimal places where you can still
convert any `U64] to a `Dec` without losing information.
While the fixed-point `Dec` has a fixed range, the floating-point `F32` and `F64` do not.
Instead, outside of a certain range they start to lose precision instead of immediately overflowing
the way integers and `Dec` do. `F64` can represent [between 15 and 17 significant digits](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) before losing precision, whereas `F32` can only represent [between 6 and 9](https://en.wikipedia.org/wiki/Single-precision_floating-point_format#IEEE_754_single-precision_binary_floating-point_format:_binary32).
There are some use cases where `F64` and `F32` can be better choices than `Dec`
despite their precision drawbacks. For example, in graphical applications they
can be a better choice for representing coordinates because they take up less memory,
various relevant calculations run faster, and decimal precision loss isn't as big a concern
when dealing with screen coordinates as it is when dealing with something like currency.
### Num, Int, and Frac
Some operations work on specific numeric types - such as `I64` or `Dec` - but operations support
multiple numeric types. For example, the `Num.abs` function works on any number, since you can
take the [absolute value](https://en.wikipedia.org/wiki/Absolute_value) of integers and fractions alike.
Its type is:
```elm
abs : Num a -> Num a
```
This type says `abs` takes a number and then returns a number of the same type. That's because the
`Num` type is compatible with both integers and fractions.
There's also an `Int` type which is only compatible with integers, and a `Frac` type which is only
compatible with fractions. For example:
```elm
Num.xor : Int a, Int a -> Int a
```
```elm
Num.cos : Frac a -> Frac a
```
When you write a number literal in Roc, it has the type `Num *`. So you could call `Num.xor 1 1`
and also `Num.cos 1` and have them all work as expected; the number literal `1` has the type
`Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason,
you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`.
## Interface modules ## Interface modules
@ -1330,6 +1509,426 @@ Some important things to note about backpassing and `await`:
* Backpassing syntax does not need to be used with `await` in particular. It can be used with any function. * Backpassing syntax does not need to be used with `await` in particular. It can be used with any function.
* Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you! * Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you!
# Appendix: Advanced Concepts
Here are some concepts you likely won't need as a beginner, but may want to know about eventually.
This is listed as an appendix rather than the main tutorial, to emphasize that it's totally fine
to stop reading here and go build things!
## Open Records and Closed Records
Let's say I write a function which takes a record with a `firstName`
and `lastName` field, and puts them together with a space in between:
```swift
fullName = \user ->
"\(user.firstName) \(user.lastName)"
```
I can pass this function a record that has more fields than just
`firstName` and `lastName`, as long as it has *at least* both of those fields
(and both of them are strings). So any of these calls would work:
* `fullName { firstName: "Sam", lastName: "Sample" }`
* `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }`
* `fullName { age: 5, firstName: "Sam", things: 3, lastName: "Sample", role: Admin }`
This `user` argument is an *open record* - that is, a description of a minimum set of fields
on a record, and their types. When a function takes an open record as an argument,
it's okay if you pass it a record with more fields than just the ones specified.
In contrast, a *closed record* is one that requires an exact set of fields (and their types),
with no additional fields accepted.
If we add a type annotation to this `fullName` function, we can choose to have it accept either
an open record or a closed record:
```coffee
# Closed record
fullName : { firstName : Str, lastName : Str } -> Str
fullName = \user - >
"\(user.firstName) \(user.lastName)"
```
```coffee
# Open record (because of the `*`)
fullName : { firstName : Str, lastName : Str }* -> Str
fullName = \user - >
"\(user.firstName) \(user.lastName)"
```
The `*` in the type `{ firstName : Str, lastName : Str }*` is what makes it an open record type.
This `*` is the *wildcard type* we saw earlier with empty lists. (An empty list has the type `List *`,
in contrast to something like `List Str` which is a list of strings.)
This is because record types can optionally end in a type variable. Just like how we can have `List *`
or `List a -> List a`, we can also have `{ first : Str, last : Str }*` or
`{ first : Str, last : Str }a -> { first: Str, last : Str }a`. The differences are that in `List a`,
the type variable is required and appears with a space after `List`; in a record, the type variable
is optional, and appears (with no space) immediately after `}`.
If the type variable in a record type is a `*` (such as in `{ first : Str, last : Str }*`), then
it's an open record. If the type variable is missing, then it's a closed record. You can also specify
a closed record by putting a `{}` as the type variable (so for example, `{ email : Str }{}` is another way to write
`{ email : Str }`). In practice, closed records are basically always written without the `{}` on the end,
but later on we'll see a situation where putting types other than `*` in that spot can be useful.
## Constrained Records
The type variable can also be a named type variable, like so:
```coffee
addHttps : { url : Str }a -> { url : Str }a
addHttps = \record ->
{ record & url: "https://\(record.url)" }
```
This function uses *constrained records* in its type. The annotation is saying:
* This function takes a record which has at least a `url` field, and possibly others
* That `url` field has the type `Str`
* It returns a record of exactly the same type as the one it was given
So if we give this function a record with five fields, it will return a record with those
same five fields. The only requirement is that one of those fields must be `url : Str`.
In practice, constrained records appear in type annotations much less often than open or closed records do.
Here's when you can typically expect to encounter these three flavors of type variables in records:
- *Open records* are what the compiler infers when you use a record as an argument, or when destructuring it (for example, `{ x, y } =`).
- *Closed records* are what the compiler infers when you create a new record (for example, `{ x: 5, y: 6 }`)
- *Constrained records* are what the compiler infers when you do a record update (for example, `{ user & email: newEmail }`)
Of note, you can pass a closed record to a function that accepts a smaller open record, but not the reverse.
So a function `{ a : Str, b : Bool }* -> Str` can accept an `{ a : Str, b : Bool, c : Bool }` record,
but a function `{ a : Str, b : Bool, c : Bool } -> Str` would not accept an `{ a : Str, b : Bool }*` record.
This is because if a function accepts `{ a : Str, b : Bool, c : Bool }`, that means it might access the `c`
field of that record. So if you passed it a record that was not guaranteed to have all three of those fields
present (such as an `{ a : Str, b : Bool }*` record, which only guarantees that the fields `a` and `b` are present),
the function might try to access a `c` field at runtime that did not exist!
## Type Variables in Record Annotations
You can add type annotations to make record types less flexible than what the compiler infers, but not more
flexible. For example, you can use an annotation to tell the compiler to treat a record as closed when it would
be inferred as open (or constrained), but you can't use an annotation to make a record open when it would be
inferred as closed.
If you like, you can always annotate your functions as accepting open records. However, in practice this may not
always be the nicest choice. For example, let's say you have a `User` type alias, like so:
```coffee
User :
{
email : Str,
firstName : Str,
lastName : Str,
}
```
This defines `User` to be a closed record, which in practice is the most common way records named `User`
tend to be defined.
If you want to have a function take a `User`, you might write its type like so:
```elm
isValid : User -> Bool
```
If you want to have a function return a `User`, you might write its type like so:
```elm
userFromEmail : Str -> User
```
A function which takes a user and returns a user might look like this:
```elm
capitalizeNames : User -> User
```
This is a perfectly reasonable way to write all of these functions. However, I
might decide that I really want the `isValid` function to take an open record -
that is, a record with *at least* the fields of this `User` record, but possibly others as well.
Since open records have a type variable (like `*` in `{ email : Str }*` or `a` in
`{ email : Str }a -> { email : Str }a`), in order to do this I'd need to add a
type variable to the `User` type alias:
```coffee
User a :
{
email : Str,
firstName : Str,
lastName : Str,
}a
```
Notice that the `a` type variable appears not only in `User a` but also in `}a` at the end of the
record type!
Using `User a` type alias, I can still write the same three functions, but now their types need to look different.
This is what the first one would look like:
```elm
isValid : User * -> Bool
```
Here, the `User *` type alias substitutes `*` for the type variable `a` in the type alias,
which takes it from `{ email : Str, … }a` to `{ email : Str, … }*`. Now I can pass it any
record that has at least the fields in `User`, and possibly others as well, which was my goal.
```elm
userFromEmail : Str -> User {}
```
Here, the `User {}` type alias substitutes `{}` for the type variable `a` in the type alias,
which takes it from `{ email : Str, … }a` to `{ email : Str, … }{}`. As noted earlier,
this is another way to specify a closed record: putting a `{}` after it, in the same place that
you'd find a `*` in an open record.
> **Aside:** This works because you can form new record types by replacing the type variable with
> other record types. For example, `{ a : Str, b : Str }` can also be written `{ a : Str }{ b : Str }`.
> You can chain these more than once, e.g. `{ a : Str }{ b : Str }{ c : Str, d : Str }`.
> This is more useful when used with type annotations; for example, `{ a : Str, b : Str }User` describes
> a closed record consisting of all the fields in the closed record `User`, plus `a : Str` and `b : Str`.
This function still returns the same record as it always did, it just needs to be annotated as
`User {}` now instead of just `User`, because the `User` type alias has a variable in it that must be
specified.
The third function might need to use a named type variable:
```elm
capitalizeNames : User a -> User a
```
If this function does a record update on the given user, and returns that - for example, if its
definition were `capitalizeNames = \user -> { user & email: "blah" }` - then it needs to use the
same named type variable for both the argument and return value.
However, if returns a new `User` that it created from scratch, then its type could instead be:
```elm
capitalizeNames : User * -> User {}
```
This says that it takes a record with at least the fields specified in the `User` type alias,
and possibly others...and then returns a record with exactly the fields specified in the `User`
type alias, and no others.
These three examples illustrate why it's relatively uncommon to use open records for type aliases:
it makes a lot of types need to incorporate a type variable that otherwise they could omit,
all so that `isValid` can be given something that has not only the fields `User` has, but
some others as well. (In the case of a `User` record in particular, it may be that the extra
fields were included due to a mistake rather than on purpose, and accepting an open record could
prevent the compiler from raising an error that would have revealed the mistake.)
That said, this is a useful technique to know about if you want to (for example) make a record
type that accumulates more and more fields as it progresses through a series of operations.
## Open and Closed Tag Unions
Just like how Roc has open records and closed records, it also has open and closed tag unions.
The *open tag union* (or *open union* for short) `[ Foo Str, Bar Bool ]*` represents a tag that might
be `Foo Str` and might be `Bar Bool`, but might also be some other tag whose type isn't known at compile time.
Because an open union represents possibilities that are impossible to know ahead of time, any `when` I use on a
`[ Foo Str, Bar Bool ]*` value must include a catch-all `_ ->` branch. Otherwise, if one of those
unknown tags were to come up, the `when` would not know what to do with it! For example:
```coffee
example : [ Foo Str, Bar Bool ]* -> Bool
example = \tag ->
when tag is
Foo str -> Str.isEmpty str
Bar bool -> bool
_ -> False
```
In contrast, a *closed tag union* (or *closed union*) like `[ Foo Str, Bar Bool ]` (without the `*`)
represents an exhaustive set of possible tags. If I use a `when` on one of these, I can match on `Foo`
only and then on `Bar` only, with no need for a catch-all branch. For example:
```coffee
example : [ Foo Str, Bar Bool ] -> Bool
example = \tag ->
when tag is
Foo str -> Str.isEmpty str
Bar bool -> bool
```
If we were to remove the type annotations from the previous two code examples, Roc would infer the same
types for them anyway.
It would infer `tag : [ Foo Str, Bar Bool ]` for the latter example because the `when tag is` expression
only includes a `Foo Str` branch and a `Bar Bool` branch, and nothing else. Since the `when` doesn't handle
any other possibilities, these two tags must be the only possible ones the `tag` argument could be.
It would infer `tag : [ Foo Str, Bar Bool ]*` for the former example because the `when tag is` expression
includes a `Foo Str` branch and a `Bar Bool` branch - meaning we know about at least those two specific
possibilities - but also a `_ ->` branch, indicating that there may be other tags we don't know about. Since
the `when` is flexible enough to handle all possible tags, `tag` gets inferred as an open union.
Putting these together, whether a tag union is inferred to be open or closed depends on which possibilities
the implementation actually handles.
> **Aside:** As with open and closed records, we can use type annotations to make tag union types less flexible
> than what would be inferred. If we added a `_ ->` branch to the second example above, the compiler would still
> accept `example : [ Foo Str, Bar Bool ] -> Bool` as the type annotation, even though the catch-all branch
> would permit the more flexible `example : [ Foo Str, Bar Bool ]* -> Bool` annotation instead.
## Combining Open Unions
When we make a new record, it's inferred to be a closed record. For example, in `foo { a: "hi" }`,
the type of `{ a: "hi" }` is inferred to be `{ a : Str }`. In contrast, when we make a new tag, it's inferred
to be an open union. So in `foo (Bar "hi")`, the type of `Bar "hi"` is inferred to be `[ Bar Str ]*`.
This is because open unions can accumulate additional tags based on how they're used in the program,
whereas closed unions cannot. For example, let's look at this conditional:
```elm
if x > 5 then
"foo"
else
7
```
This will be a type mismatch because the two branches have incompatible types. Strings and numbers are not
type-compatible! Now let's look at another example:
```elm
if x > 5 then
Ok "foo"
else
Err "bar"
```
This shouldn't be a type mismatch, because we can see that the two branches are compatible; they are both
tags that could easily coexist in the same tag union. But if the compiler inferred the type of `Ok "foo"` to be
the closed union `[ Ok Str ]`, and likewise for `Err "bar"` and `[ Err Str ]`, then this would have to be
a type mismatch - because those two closed unions are incompatible.
Instead, the compiler infers `Ok "foo"` to be the open union `[ Ok Str ]*`, and `Err "bar"` to be the open
union `[ Err Str ]*`. Then, when using them together in this conditional, the inferred type of the conditional
becomes `[ Ok Str, Err Str ]*` - that is, the combination of the unions in each of its branches. (Branches in
a `when` work the same way with open unions.)
Earlier we saw how a function which accepts an open union must account for more possibilities, by including
catch-all `_ ->` patterns in its `when` expressions. So *accepting* an open union means you have more requirements.
In contrast, when you already *have* a value which is an open union, you have fewer requirements. A value
which is an open union (like `Ok "foo"`, which has the type `[ Ok Str ]*`) can be provided to anything that's
expecting a tag union (no matter whether it's open or closed), as long as the expected tag union includes at least
the tags in the open union you're providing.
So if I have an `[ Ok Str ]*` value, I can pass it functions with any of these types (among others):
* `[ Ok Str ]* -> Bool`
* `[ Ok Str ] -> Bool`
* `[ Ok Str, Err Bool ]* -> Bool`
* `[ Ok Str, Err Bool ] -> Bool`
* `[ Ok Str, Err Bool, Whatever ]* -> Bool`
* `[ Ok Str, Err Bool, Whatever ] -> Bool`
* `Result Str Bool -> Bool`
* `[ Err Bool, Whatever ]* -> Bool`
That last one works because a function accepting an open union can accept any unrecognized tag, including
`Ok Str` - even though it is not mentioned as one of the tags in `[ Err Bool, Whatever ]*`! Remember, when
a function accepts an open tag union, any `when` branches on that union must include a catch-all `_ ->` branch,
which is the branch that will end up handling the `Ok Str` value we pass in.
However, I could not pass an `[ Ok Str ]*` to a function with a *closed* tag union argument that did not
mention `Ok Str` as one of its tags. So if I tried to pass `[ Ok Str ]*` to a function with the type
`[ Err Bool, Whatever ] -> Str`, I would get a type mismatch - because a `when` in that function could
be handling the `Err Bool` possibility and the `Whatever` possibility, and since it would not necessarily have
a catch-all `_ ->` branch, it might not know what to do with an `Ok Str` if it received one.
> **Note:** It wouldn't be accurate to say that a function which accepts an open union handles
> "all possible tags." For example, if I have a function `[ Ok Str ]* -> Bool` and I pass it
> `Ok 5`, that will still be a type mismatch. If you think about it, a `when` in that function might
> have the branch `Ok str ->` which assumes there's a string inside that `Ok`, and if `Ok 5` type-checked,
> then that assumption would be false and things would break!
>
> So `[ Ok Str ]*` is more restrictive than `[]*`. It's basically saying "this may or may not be an `Ok` tag,
> but if it is an `Ok` tag, then it's guaranteed to have a payload of exactly `Str`."
In summary, here's a way to think about the difference between open unions in a value you have, compared to a value you're accepting:
* If you *have* a closed union, that means it has all the tags it ever will, and can't accumulate more.
* If you *have* an open union, that means it can accumulate more tags through conditional branches.
* If you *accept* a closed union, that means you only have to handle the possibilities listed in the union.
* If you *accept* an open union, that means you have to handle the possibility that it has a tag you can't know about.
## Type Variables in Tag Unions
Earlier we saw these two examples, one with an open tag union and the other with a closed one:
```coffee
example : [ Foo Str, Bar Bool ]* -> Bool
example = \tag ->
when tag is
Foo str -> Str.isEmpty str
Bar bool -> bool
_ -> False
```
```coffee
example : [ Foo Str, Bar Bool ] -> Bool
example = \tag ->
when tag is
Foo str -> Str.isEmpty str
Bar bool -> bool
```
Similarly to how there are open records with a `*`, closed records with nothing,
and constrained records with a named type variable, we can also have *constrained tag unions*
with a named type variable. Here's an example:
```coffee
example : [ Foo Str, Bar Bool ]a -> [ Foo Str, Bar Bool ]a
example = \tag ->
when tag is
Foo str -> Bar (Str.isEmpty str)
Bar _ -> Bar False
other -> other
```
This type says that the `example` function will take either a `Foo Str` tag, or a `Bar Bool` tag,
or possibly another tag we don't know about at compile time - and it also says that the function's
return type is the same as the type of its argument.
So if we give this function a `[ Foo Str, Bar Bool, Baz (List Str) ]` argument, then it will be guaranteed
to return a `[ Foo Str, Bar Bool, Baz (List Str) ]` value. This is more constrained than a function that
returned `[ Foo Str, Bar Bool ]*` because that would say it could return *any* other tag (in addition to
the `Foo Str` and `Bar Bool` we already know about).
If we removed the type annotation from `example` above, Roc's compiler would infer the same type anyway.
This may be surprising if you look closely at the body of the function, because:
* The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[ Bar Bool ]a` instead?
* The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it?
The reason it has this type is the `other -> other` branch. Take a look at that branch, and ask this question:
"What is the type of `other`?" There has to be exactly one answer! It can't be the case that `other` has one
type before the `->` and another type after it; whenever you see a named value in Roc, it is guaranteed to have
the same type everywhere it appears in that scope.
For this reason, any time you see a function that only runs a `when` on its only argument, and that `when`
includes a branch like `x -> x` or `other -> other`, the function's argument type and return type must necessarily
be equivalent.
> **Note:** Just like with records, you can also replace the type variable in tag union types with a concrete type.
> For example, `[ Foo Str ][ Bar Bool ][ Baz (List Str) ]` is equivalent to `[ Foo Str, Bar Bool, Baz (List Str) ]`.
>
> Also just like with records, you can use this to compose tag union type aliases. For example, you can write
> `NetworkError : [ Timeout, Disconnected ]` and then `Problem : [ InvalidInput, UnknownFormat ]NetworkError`
## Phantom Types
[ This part of the tutorial has not been written yet. Coming soon! ]
## Operator Desugaring Table ## Operator Desugaring Table
Here are various Roc expressions involving operators, and what they desugar to. Here are various Roc expressions involving operators, and what they desugar to.

View file

@ -17,7 +17,8 @@ roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" } roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify"} roc_unify = { path = "../compiler/unify"}
roc_load = { path = "../compiler/load" } roc_load = { path = "../compiler/load" }
roc_reporting = { path = "../reporting" } roc_target = { path = "../compiler/roc_target" }
roc_error_macros = { path = "../error_macros" }
arrayvec = "0.7.2" arrayvec = "0.7.2"
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
libc = "0.2.106" libc = "0.2.106"

View file

@ -1985,7 +1985,7 @@ pub mod test_constrain {
ident::Lowercase, ident::Lowercase,
symbol::{IdentIds, Interns, ModuleIds, Symbol}, symbol::{IdentIds, Interns, ModuleIds, Symbol},
}; };
use roc_parse::parser::SyntaxError; use roc_parse::parser::{SourceError, SyntaxError};
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::{ use roc_types::{
pretty_print::{content_to_string, name_all_type_vars}, pretty_print::{content_to_string, name_all_type_vars},
@ -2128,7 +2128,7 @@ pub mod test_constrain {
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope, scope: &mut Scope,
region: Region, region: Region,
) -> Result<(Expr2, Output), SyntaxError<'a>> { ) -> Result<(Expr2, Output), SourceError<'a, SyntaxError<'a>>> {
match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) {
Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)),
Err(fail) => Err(fail), Err(fail) => Err(fail),

View file

@ -1,6 +1,8 @@
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expr::Recursive; use roc_can::expr::{IntValue, Recursive};
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
};
use roc_can::operator::desugar_expr; use roc_can::operator::desugar_expr;
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -52,7 +54,7 @@ pub fn expr_to_expr2<'a>(
match parse_expr { match parse_expr {
Float(string) => { Float(string) => {
match finish_parsing_float(string) { match finish_parsing_float(string) {
Ok(float) => { Ok((float, _bound)) => {
let expr = Expr2::Float { let expr = Expr2::Float {
number: FloatVal::F64(float), number: FloatVal::F64(float),
var: env.var_store.fresh(), var: env.var_store.fresh(),
@ -73,10 +75,13 @@ pub fn expr_to_expr2<'a>(
} }
} }
Num(string) => { Num(string) => {
match finish_parsing_int(string) { match finish_parsing_num(string) {
Ok(int) => { Ok(ParsedNumResult::UnknownNum(int, _) | ParsedNumResult::Int(int, _)) => {
let expr = Expr2::SmallInt { let expr = Expr2::SmallInt {
number: IntVal::I64(int), number: IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}),
var: env.var_store.fresh(), var: env.var_store.fresh(),
// TODO non-hardcode // TODO non-hardcode
style: IntStyle::Decimal, style: IntStyle::Decimal,
@ -85,6 +90,15 @@ pub fn expr_to_expr2<'a>(
(expr, Output::default()) (expr, Output::default())
} }
Ok(ParsedNumResult::Float(float, _)) => {
let expr = Expr2::Float {
number: FloatVal::F64(float),
var: env.var_store.fresh(),
text: PoolStr::new(string, env.pool),
};
(expr, Output::default())
}
Err((raw, error)) => { Err((raw, error)) => {
// emit runtime error // emit runtime error
let runtime_error = RuntimeError::InvalidInt( let runtime_error = RuntimeError::InvalidInt(
@ -107,9 +121,12 @@ pub fn expr_to_expr2<'a>(
is_negative, is_negative,
} => { } => {
match finish_parsing_base(string, *base, *is_negative) { match finish_parsing_base(string, *base, *is_negative) {
Ok(int) => { Ok((int, _bound)) => {
let expr = Expr2::SmallInt { let expr = Expr2::SmallInt {
number: IntVal::I64(int), number: IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}),
var: env.var_store.fresh(), var: env.var_store.fresh(),
// TODO non-hardcode // TODO non-hardcode
style: IntStyle::from_base(*base), style: IntStyle::from_base(*base),

View file

@ -3,8 +3,10 @@
#![allow(unused_imports)] #![allow(unused_imports)]
use bumpalo::collections::Vec as BumpVec; use bumpalo::collections::Vec as BumpVec;
use roc_can::expr::unescape_char; use roc_can::expr::{unescape_char, IntValue};
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
};
use roc_collections::all::BumpMap; use roc_collections::all::BumpMap;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::ast::{StrLiteral, StrSegment};
@ -183,18 +185,35 @@ pub fn to_pattern2<'a>(
let problem = MalformedPatternProblem::MalformedFloat; let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)), Ok((float, _bound)) => Pattern2::FloatLiteral(FloatVal::F64(float)),
}, },
ptype => unsupported_pattern(env, ptype, region), ptype => unsupported_pattern(env, ptype, region),
}, },
NumLiteral(string) => match pattern_type { NumLiteral(string) => match pattern_type {
WhenBranch => match finish_parsing_int(string) { WhenBranch => match finish_parsing_num(string) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedInt; let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), Ok(ParsedNumResult::UnknownNum(int, _bound)) => {
Pattern2::NumLiteral(
env.var_store.fresh(),
match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
},
)
}
Ok(ParsedNumResult::Int(int, _bound)) => {
Pattern2::IntLiteral(IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}))
}
Ok(ParsedNumResult::Float(int, _bound)) => {
Pattern2::FloatLiteral(FloatVal::F64(int))
}
}, },
ptype => unsupported_pattern(env, ptype, region), ptype => unsupported_pattern(env, ptype, region),
}, },
@ -209,7 +228,11 @@ pub fn to_pattern2<'a>(
let problem = MalformedPatternProblem::MalformedBase(*base); let problem = MalformedPatternProblem::MalformedBase(*base);
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(int) => { Ok((int, _bound)) => {
let int = match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
};
if *is_negative { if *is_negative {
Pattern2::IntLiteral(IntVal::I64(-int)) Pattern2::IntLiteral(IntVal::I64(-int))
} else { } else {

View file

@ -1,6 +1,6 @@
use roc_error_macros::internal_error;
use roc_module::{called_via::CalledVia, symbol::Symbol}; use roc_module::{called_via::CalledVia, symbol::Symbol};
use roc_parse::ast::StrLiteral; use roc_parse::ast::StrLiteral;
use roc_reporting::internal_error;
use crate::{ use crate::{
ast_error::{ASTResult, UnexpectedASTNode}, ast_error::{ASTResult, UnexpectedASTNode},

View file

@ -12,6 +12,7 @@ use roc_types::subs::Variable;
#[derive(Debug)] #[derive(Debug)]
pub struct Rigids { pub struct Rigids {
// Rigid type variable = type variable where type is specified by the programmer
pub names: PoolVec<(Option<PoolStr>, Variable)>, // 8B pub names: PoolVec<(Option<PoolStr>, Variable)>, // 8B
padding: [u8; 1], padding: [u8; 1],
} }

View file

@ -3,6 +3,7 @@ use std::path::Path;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_load::file::LoadedModule; use roc_load::file::LoadedModule;
use roc_target::TargetInfo;
pub fn load_module(src_file: &Path) -> LoadedModule { pub fn load_module(src_file: &Path) -> LoadedModule {
let subs_by_module = MutMap::default(); let subs_by_module = MutMap::default();
@ -19,7 +20,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
) )
}), }),
subs_by_module, subs_by_module,
8, TargetInfo::default_x86_64(),
roc_can::builtins::builtin_defs_map, roc_can::builtins::builtin_defs_map,
); );

View file

@ -225,7 +225,7 @@ fn solve<'a>(
expectation.get_type_ref(), expectation.get_type_ref(),
); );
match unify(subs, actual, expected, Mode::Eq) { match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => { Success(vars) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
@ -318,7 +318,7 @@ fn solve<'a>(
expectation.get_type_ref(), expectation.get_type_ref(),
); );
match unify(subs, actual, expected, Mode::Eq) { match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => { Success(vars) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
@ -389,7 +389,7 @@ fn solve<'a>(
); );
// TODO(ayazhafiz): presence constraints for Expr2/Type2 // TODO(ayazhafiz): presence constraints for Expr2/Type2
match unify(subs, actual, expected, Mode::Eq) { match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => { Success(vars) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
@ -697,7 +697,7 @@ fn solve<'a>(
); );
let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty); let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty);
match unify(subs, actual, includes, Mode::Present) { match unify(subs, actual, includes, Mode::PRESENT) {
Success(vars) => { Success(vars) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
@ -1400,6 +1400,8 @@ fn adjust_rank_content(
rank rank
} }
RangedNumber(typ, _vars) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ),
} }
} }
@ -1550,6 +1552,10 @@ fn instantiate_rigids_help(
instantiate_rigids_help(subs, max_rank, pools, real_type_var); instantiate_rigids_help(subs, max_rank, pools, real_type_var);
} }
RangedNumber(typ, _vars) => {
instantiate_rigids_help(subs, max_rank, pools, typ);
}
} }
var var
@ -1806,6 +1812,25 @@ fn deep_copy_var_help(
copy copy
} }
RangedNumber(typ, vars) => {
let mut new_vars = Vec::with_capacity(vars.len());
for var_index in vars {
let var = subs[var_index];
let new_var = deep_copy_var_help(subs, max_rank, pools, var);
new_vars.push(new_var);
}
let new_slice = VariableSubsSlice::insert_into_subs(subs, new_vars.drain(..));
let new_real_type = deep_copy_var_help(subs, max_rank, pools, typ);
let new_content = RangedNumber(new_real_type, new_slice);
subs.set(copy, make_descriptor(new_content));
copy
}
} }
} }

View file

@ -15,14 +15,11 @@ test = false
bench = false bench = false
[features] [features]
default = ["target-aarch64", "target-x86_64", "target-wasm32", "llvm", "editor"] default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"]
wasm32-cli-run = ["target-wasm32", "run-wasm32"] wasm32-cli-run = ["target-wasm32", "run-wasm32"]
i386-cli-run = ["target-x86"] i386-cli-run = ["target-x86"]
# This is a separate feature because when we generate docs on Netlify,
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
llvm = ["inkwell", "roc_gen_llvm", "roc_build/llvm"]
editor = ["roc_editor"] editor = ["roc_editor"]
run-wasm32 = ["wasmer", "wasmer-wasi"] run-wasm32 = ["wasmer", "wasmer-wasi"]
@ -50,29 +47,22 @@ roc_docs = { path = "../docs" }
roc_parse = { path = "../compiler/parse" } roc_parse = { path = "../compiler/parse" }
roc_region = { path = "../compiler/region" } roc_region = { path = "../compiler/region" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }
roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" }
roc_builtins = { path = "../compiler/builtins" } roc_builtins = { path = "../compiler/builtins" }
roc_constrain = { path = "../compiler/constrain" }
roc_unify = { path = "../compiler/unify" }
roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" } roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" } roc_load = { path = "../compiler/load" }
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
roc_build = { path = "../compiler/build", default-features = false } roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" } roc_fmt = { path = "../compiler/fmt" }
roc_target = { path = "../compiler/roc_target" }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../reporting" }
roc_error_macros = { path = "../error_macros" }
roc_editor = { path = "../editor", optional = true } roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" } roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli" }
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
const_format = "0.2.22" const_format = "0.2.22"
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"
mimalloc = { version = "0.1.26", default-features = false } mimalloc = { version = "0.1.26", default-features = false }
inkwell = { path = "../vendor/inkwell", optional = true }
target-lexicon = "0.12.2" target-lexicon = "0.12.2"
tempfile = "3.2.0" tempfile = "3.2.0"
@ -83,9 +73,9 @@ wasmer-wasi = { version = "2.0.0", optional = true }
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
wasmer-wasi = "2.0.0" wasmer-wasi = "2.0.0"
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
roc_test_utils = { path = "../test_utils" }
indoc = "1.0.3" indoc = "1.0.3"
serial_test = "0.5.1" serial_test = "0.5.1"
tempfile = "3.2.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"} criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" } cli_utils = { path = "../cli_utils" }

View file

@ -8,6 +8,7 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_target::TargetInfo;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use target_lexicon::Triple; use target_lexicon::Triple;
@ -58,7 +59,7 @@ pub fn build_file<'a>(
target_valgrind: bool, target_valgrind: bool,
) -> Result<BuiltFile, LoadingProblem<'a>> { ) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let target_info = TargetInfo::from(target);
// Step 1: compile the app and generate the .o file // Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default(); let subs_by_module = MutMap::default();
@ -72,7 +73,7 @@ pub fn build_file<'a>(
stdlib, stdlib,
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
ptr_bytes, target_info,
builtin_defs_map, builtin_defs_map,
)?; )?;
@ -105,6 +106,21 @@ pub fn build_file<'a>(
// To do this we will need to preprocess files just for their exported symbols. // To do this we will need to preprocess files just for their exported symbols.
// Also, we should no longer need to do this once we have platforms on // Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there. // a package repository, as we can then get precompiled hosts from there.
let exposed_values = loaded
.exposed_to_host
.values
.keys()
.map(|x| x.as_str(&loaded.interns).to_string())
.collect();
let exposed_closure_types = loaded
.exposed_to_host
.closure_types
.iter()
.map(|x| x.as_str(&loaded.interns).to_string())
.collect();
let rebuild_thread = spawn_rebuild_thread( let rebuild_thread = spawn_rebuild_thread(
opt_level, opt_level,
surgically_link, surgically_link,
@ -112,11 +128,8 @@ pub fn build_file<'a>(
host_input_path.clone(), host_input_path.clone(),
binary_path.clone(), binary_path.clone(),
target, target,
loaded exposed_values,
.exposed_to_host exposed_closure_types,
.keys()
.map(|x| x.as_str(&loaded.interns).to_string())
.collect(),
target_valgrind, target_valgrind,
); );
@ -291,6 +304,7 @@ fn spawn_rebuild_thread(
binary_path: PathBuf, binary_path: PathBuf,
target: &Triple, target: &Triple,
exported_symbols: Vec<String>, exported_symbols: Vec<String>,
exported_closure_types: Vec<String>,
target_valgrind: bool, target_valgrind: bool,
) -> std::thread::JoinHandle<u128> { ) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone(); let thread_local_target = target.clone();
@ -305,6 +319,7 @@ fn spawn_rebuild_thread(
&thread_local_target, &thread_local_target,
host_input_path.as_path(), host_input_path.as_path(),
exported_symbols, exported_symbols,
exported_closure_types,
target_valgrind, target_valgrind,
) )
.unwrap(); .unwrap();
@ -342,7 +357,7 @@ pub fn check_file(
// only used for generating errors. We don't do code generation, so hardcoding should be fine // only used for generating errors. We don't do code generation, so hardcoding should be fine
// we need monomorphization for when exhaustiveness checking // we need monomorphization for when exhaustiveness checking
let ptr_bytes = 8; let target_info = TargetInfo::default_x86_64();
// Step 1: compile the app and generate the .o file // Step 1: compile the app and generate the .o file
let subs_by_module = MutMap::default(); let subs_by_module = MutMap::default();
@ -356,7 +371,7 @@ pub fn check_file(
stdlib, stdlib,
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
ptr_bytes, target_info,
builtin_defs_map, builtin_defs_map,
)?; )?;

View file

@ -2,6 +2,7 @@ use std::path::PathBuf;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_def; use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module; use roc_fmt::module::fmt_module;
use roc_fmt::Buf; use roc_fmt::Buf;
@ -11,17 +12,17 @@ use roc_parse::ast::{
TypeAnnotation, WhenBranch, TypeAnnotation, WhenBranch,
}; };
use roc_parse::header::{ use roc_parse::header::{
AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
}; };
use roc_parse::{ use roc_parse::{
ast::{Def, Module}, ast::{Def, Module},
ident::UppercaseIdent,
module::{self, module_defs}, module::{self, module_defs},
parser::{Parser, SyntaxError}, parser::{Parser, SyntaxError},
state::State, state::State,
}; };
use roc_region::all::Loc; use roc_region::all::{Loc, Region};
use roc_reporting::{internal_error, user_error};
pub fn format(files: std::vec::Vec<PathBuf>) { pub fn format(files: std::vec::Vec<PathBuf>) {
for file in files { for file in files {
@ -110,8 +111,8 @@ struct Ast<'a> {
} }
fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'a>> { fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'a>> {
let (module, state) = let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
module::parse_header(arena, State::new(src.as_bytes())).map_err(SyntaxError::Header)?; .map_err(|e| SyntaxError::Header(e.problem))?;
let (_, defs, _) = module_defs().parse(arena, state).map_err(|(_, e, _)| e)?; let (_, defs, _) = module_defs().parse(arena, state).map_err(|(_, e, _)| e)?;
@ -176,6 +177,7 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
packages: header.packages.remove_spaces(arena), packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena), imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena), provides: header.provides.remove_spaces(arena),
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
to: header.to.remove_spaces(arena), to: header.to.remove_spaces(arena),
before_header: &[], before_header: &[],
after_app_keyword: &[], after_app_keyword: &[],
@ -197,14 +199,6 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
packages: header.packages.remove_spaces(arena), packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena), imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena), provides: header.provides.remove_spaces(arena),
effects: Effects {
spaces_before_effects_keyword: &[],
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
effect_shortname: header.effects.effect_shortname.remove_spaces(arena),
effect_type_name: header.effects.effect_type_name.remove_spaces(arena),
entries: header.effects.entries.remove_spaces(arena),
},
before_header: &[], before_header: &[],
after_platform_keyword: &[], after_platform_keyword: &[],
before_requires: &[], before_requires: &[],
@ -219,6 +213,25 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
after_provides: &[], after_provides: &[],
}, },
}, },
Module::Hosted { header } => Module::Hosted {
header: HostedHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
generates: header.generates.remove_spaces(arena),
generates_with: header.generates_with.remove_spaces(arena),
before_header: &[],
after_hosted_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
before_generates: &[],
after_generates: &[],
before_with: &[],
after_with: &[],
},
},
} }
} }
} }
@ -285,7 +298,7 @@ impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
} }
} }
impl<'a> RemoveSpaces<'a> for PlatformRigid<'a> { impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self { fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self *self
} }
@ -319,7 +332,7 @@ impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> { impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self { fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena); let res = self.value.remove_spaces(arena);
Loc::new(0, 0, 0, 0, res) Loc::at(Region::zero(), res)
} }
} }

View file

@ -18,7 +18,6 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture};
pub mod build; pub mod build;
mod format; mod format;
pub mod repl;
pub use format::format; pub use format::format;
pub const CMD_BUILD: &str = "build"; pub const CMD_BUILD: &str = "build";

View file

@ -1,7 +1,7 @@
use roc_cli::build::check_file; use roc_cli::build::check_file;
use roc_cli::{ use roc_cli::{
build_app, docs, format, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, build_app, docs, format, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_FORMAT,
CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
}; };
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
use std::fs::{self, FileType}; use std::fs::{self, FileType};
@ -63,7 +63,7 @@ fn main() -> io::Result<()> {
} }
} }
Some((CMD_REPL, _)) => { Some((CMD_REPL, _)) => {
repl::main()?; roc_repl_cli::main()?;
// Exit 0 if the repl exited normally // Exit 0 if the repl exited normally
Ok(0) Ok(0)

View file

@ -1,109 +0,0 @@
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Store(tipe, var, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
used.insert(*var);
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,273 +0,0 @@
use crate::repl::eval;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::module::Linkage;
use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens};
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_load::file::LoadingProblem;
use roc_mono::ir::OptLevel;
use roc_parse::parser::SyntaxError;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked;
use target_lexicon::Triple;
pub enum ReplOutput {
Problems(Vec<String>),
NoProblems { expr: String, expr_type: String },
}
pub fn gen_and_eval<'a>(
src: &[u8],
target: Triple,
opt_level: OptLevel,
) -> Result<ReplOutput, SyntaxError<'a>> {
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
let arena = Bump::new();
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
let src_str: &str = unsafe { from_utf8_unchecked(src) };
let stdlib = roc_builtins::std::standard_stdlib();
let filename = PathBuf::from("REPL.roc");
let src_dir = Path::new("fake/test/path");
let module_src = promote_expr_to_module(src_str);
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
&arena,
filename,
&module_src,
&stdlib,
src_dir,
exposed_types,
ptr_bytes,
builtin_defs_map,
);
let mut loaded = match loaded {
Ok(v) => v,
Err(LoadingProblem::FormattedReport(report)) => {
return Ok(ReplOutput::Problems(vec![report]));
}
Err(e) => {
panic!("error while loading module: {:?}", e)
}
};
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
procedures,
entry_point,
interns,
exposed_to_host,
mut subs,
module_id: home,
sources,
..
} = loaded;
let mut lines = Vec::new();
for (home, (module_path, src)) in sources {
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
if error_count == 0 {
continue;
}
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
for problem in can_problems.into_iter() {
let report = can_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
for problem in type_problems {
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) {
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
}
for problem in mono_problems {
let report = mono_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
}
if !lines.is_empty() {
Ok(ReplOutput::Problems(lines))
} else {
let context = Context::create();
let builder = context.create_builder();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
&target, &context, "",
));
// mark our zig-defined builtins as internal
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal);
}
}
debug_assert_eq!(exposed_to_host.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var;
// pretty-print the expr type string for later.
name_all_type_vars(main_fn_var, &mut subs);
let content = subs.get_content_without_compacting(main_fn_var);
let expr_type_str = content_to_string(content, &subs, home, &interns);
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(layout) => *layout,
None => {
return Ok(ReplOutput::NoProblems {
expr: "<function>".to_string(),
expr_type: expr_type_str,
});
}
};
let module = arena.alloc(module);
let (module_pass, function_pass) =
roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
// Compile and add all the Procs before adding main
let env = roc_gen_llvm::llvm::build::Env {
arena: &arena,
builder: &builder,
dibuilder: &dibuilder,
compile_unit: &compile_unit,
context: &context,
interns,
module,
ptr_bytes,
is_gen_test: true, // so roc_panic is generated
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(),
};
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
// platform to provide them.
add_default_roc_externs(&env);
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
&env,
opt_level,
procedures,
entry_point,
);
env.dibuilder.finalize();
// we don't use the debug info, and it causes weird errors.
module.strip_debug_info();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
function_pass.run_on(&main_fn);
} else {
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
}
module_pass.run_on(env.module);
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
// Verify the module
if let Err(errors) = env.module.verify() {
panic!(
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
errors.to_string()
);
}
let lib = module_to_dylib(env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
let res_answer = unsafe {
eval::jit_to_ast(
&arena,
lib,
main_fn_name,
main_fn_layout,
content,
&env.interns,
home,
&subs,
ptr_bytes,
)
};
let mut expr = roc_fmt::Buf::new_in(&arena);
use eval::ToAstProblem::*;
match res_answer {
Ok(answer) => {
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
}
Err(FunctionLayout) => {
expr.indent(0);
expr.push_str("<function>");
}
}
Ok(ReplOutput::NoProblems {
expr: expr.into_bump_str().to_string(),
expr_type: expr_type_str,
})
}
}
fn promote_expr_to_module(src: &str) -> String {
let mut buffer =
String::from("app \"app\" provides [ replOutput ] to \"./platform\"\n\nreplOutput =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}

View file

@ -6,12 +6,16 @@ extern crate roc_collections;
extern crate roc_load; extern crate roc_load;
extern crate roc_module; extern crate roc_module;
#[macro_use]
extern crate indoc;
#[cfg(test)] #[cfg(test)]
mod cli_run { mod cli_run {
use cli_utils::helpers::{ use cli_utils::helpers::{
example_file, examples_dir, extract_valgrind_errors, fixture_file, run_cmd, run_roc, example_file, examples_dir, extract_valgrind_errors, fixture_file, known_bad_file, run_cmd,
run_with_valgrind, ValgrindError, ValgrindErrorXWhat, run_roc, run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
}; };
use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial; use serial_test::serial;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -44,6 +48,27 @@ mod cli_run {
use_valgrind: bool, use_valgrind: bool,
} }
fn strip_colors(str: &str) -> String {
use roc_reporting::report::*;
str.replace(RED_CODE, "")
.replace(WHITE_CODE, "")
.replace(BLUE_CODE, "")
.replace(YELLOW_CODE, "")
.replace(GREEN_CODE, "")
.replace(CYAN_CODE, "")
.replace(MAGENTA_CODE, "")
.replace(RESET_CODE, "")
.replace(BOLD_CODE, "")
.replace(UNDERLINE_CODE, "")
}
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], &flags[..]].concat());
let err = compile_out.stdout.trim();
let err = strip_colors(&err);
assert_multiline_str_eq!(err, expected.into());
}
fn check_output_with_stdin( fn check_output_with_stdin(
file: &Path, file: &Path,
stdin: &[&str], stdin: &[&str],
@ -62,7 +87,7 @@ mod cli_run {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat()); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat());
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr); panic!("roc build had stderr: {}", compile_out.stderr);
} }
assert!(compile_out.status.success(), "bad status {:?}", compile_out); assert!(compile_out.status.success(), "bad status {:?}", compile_out);
@ -351,11 +376,19 @@ mod cli_run {
// use_valgrind: true, // use_valgrind: true,
// }, // },
cli:"cli" => Example { cli:"cli" => Example {
filename: "Echo.roc", filename: "form.roc",
executable_filename: "echo", executable_filename: "form",
stdin: &["Giovanni\n", "Giorgio\n"], stdin: &["Giovanni\n", "Giorgio\n"],
input_file: None, input_file: None,
expected_ending: "Hi, Giovanni Giorgio!\n", expected_ending: "Hi, Giovanni Giorgio! 👋\n",
use_valgrind: true,
},
tui:"tui" => Example {
filename: "Main.roc",
executable_filename: "tui",
stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks
input_file: None,
expected_ending: "Hello Worldfoo!\n",
use_valgrind: true, use_valgrind: true,
}, },
// custom_malloc:"custom-malloc" => Example { // custom_malloc:"custom-malloc" => Example {
@ -388,9 +421,11 @@ mod cli_run {
macro_rules! benchmarks { macro_rules! benchmarks {
($($test_name:ident => $benchmark:expr,)+) => { ($($test_name:ident => $benchmark:expr,)+) => {
$( $(
#[test] #[test]
#[cfg_attr(not(debug_assertions), serial(benchmark))] #[cfg_attr(not(debug_assertions), serial(benchmark))]
#[cfg(all(not(feature = "wasm32-cli-run"), not(feature = "i386-cli-run")))]
fn $test_name() { fn $test_name() {
let benchmark = $benchmark; let benchmark = $benchmark;
let file_name = examples_dir("benchmarks").join(benchmark.filename); let file_name = examples_dir("benchmarks").join(benchmark.filename);
@ -601,6 +636,14 @@ mod cli_run {
expected_ending: "", expected_ending: "",
use_valgrind: true, use_valgrind: true,
}, },
issue2279 => Example {
filename: "Issue2279.roc",
executable_filename: "issue2279",
stdin: &[],
input_file: None,
expected_ending: "Hello, world!\n",
use_valgrind: true,
},
quicksort_app => Example { quicksort_app => Example {
filename: "QuicksortApp.roc", filename: "QuicksortApp.roc",
executable_filename: "quicksortapp", executable_filename: "quicksortapp",
@ -732,6 +775,94 @@ mod cli_run {
true, true,
); );
} }
#[test]
fn known_type_error() {
check_compile_error(
&known_bad_file("TypeError.roc"),
&[],
indoc!(
r#"
UNRECOGNIZED NAME
I cannot find a `d` value
10 _ <- await (line d)
^
Did you mean one of these?
U8
Ok
I8
F64
"#
),
);
}
#[test]
fn exposed_not_defined() {
check_compile_error(
&known_bad_file("ExposedNotDefined.roc"),
&[],
indoc!(
r#"
MISSING DEFINITION
bar is listed as exposed, but it isn't defined in this module.
You can fix this by adding a definition for bar, or by removing it
from exposes.
"#
),
);
}
#[test]
fn unused_import() {
check_compile_error(
&known_bad_file("UnusedImport.roc"),
&[],
indoc!(
r#"
UNUSED IMPORT
Nothing from Symbol is used in this module.
3 imports [ Symbol.{ Ident } ]
^^^^^^^^^^^^^^^^
Since Symbol isn't used, you don't need to import it.
"#
),
);
}
#[test]
fn unknown_generates_with() {
check_compile_error(
&known_bad_file("UnknownGeneratesWith.roc"),
&[],
indoc!(
r#"
UNKNOWN GENERATES FUNCTION
I don't know how to generate the foobar function.
4 generates Effect with [ after, map, always, foobar ]
^^^^^^
Only specific functions like `after` and `map` can be generated.Learn
more about hosted modules at TODO.
"#
),
);
}
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -4,7 +4,6 @@ platform "examples/multi-module"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/multi-dep-thunk"
packages {} packages {}
imports [] imports []
provides [ mainForHost ] provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str mainForHost : Str
mainForHost = main mainForHost = main

View file

@ -0,0 +1,3 @@
interface Foo
exposes [ bar ]
imports []

View file

@ -0,0 +1,6 @@
interface Symbol
exposes [ Ident ]
imports []
# NOTE: this module is fine, but used by UnusedImport.roc to uselessly import
Ident : Str

View file

@ -0,0 +1,11 @@
app "type-error"
packages { pf: "platform" }
imports [ pf.Stdout.{ line }, pf.Task.{ await } ]
provides [ main ] to pf
main =
_ <- await (line "a")
_ <- await (line "b")
_ <- await (line "c")
_ <- await (line d)
line "e"

View file

@ -0,0 +1,4 @@
hosted UnknownGeneratesWith
exposes [ Effect, after, map, always ]
imports []
generates Effect with [ after, map, always, foobar ]

View file

@ -0,0 +1,7 @@
interface UnusedImport
exposes [ plainText, emText ]
imports [ Symbol.{ Ident } ]
plainText = \str -> PlainText str
emText = \str -> EmText str

View file

@ -0,0 +1 @@
../../../examples/cli/platform

View file

@ -1,596 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
#[cfg(test)]
mod repl_eval {
use cli_utils::helpers;
const ERROR_MESSAGE_START: char = '─';
fn expect_success(input: &str, expected: &str) {
let out = helpers::repl_eval(input);
assert_eq!(&out.stderr, "");
assert_eq!(&out.stdout, expected);
assert!(out.status.success());
}
fn expect_failure(input: &str, expected: &str) {
let out = helpers::repl_eval(input);
// there may be some other stuff printed (e.g. unification errors)
// so skip till the header of the first error
match out.stdout.find(ERROR_MESSAGE_START) {
Some(index) => {
assert_eq!(&out.stderr, "");
assert_eq!(&out.stdout[index..], expected);
assert!(out.status.success());
}
None => {
assert_eq!(&out.stderr, "");
assert!(out.status.success());
panic!(
"I expected a failure, but there is no error message in stdout:\n\n{}",
&out.stdout
);
}
}
}
#[test]
fn literal_0() {
expect_success("0", "0 : Num *");
}
#[test]
fn literal_42() {
expect_success("42", "42 : Num *");
}
#[test]
fn literal_0x0() {
expect_success("0x0", "0 : Int *");
}
#[test]
fn literal_0x42() {
expect_success("0x42", "66 : Int *");
}
#[test]
fn literal_0point0() {
expect_success("0.0", "0 : Float *");
}
#[test]
fn literal_4point2() {
expect_success("4.2", "4.2 : Float *");
}
#[test]
fn num_addition() {
expect_success("1 + 2", "3 : Num *");
}
#[test]
fn int_addition() {
expect_success("0x1 + 2", "3 : I64");
}
#[test]
fn float_addition() {
expect_success("1.1 + 2", "3.1 : F64");
}
#[test]
fn num_rem() {
expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*");
}
#[test]
fn num_floor_division_success() {
expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*");
}
#[test]
fn num_floor_division_divby_zero() {
expect_success(
"Num.divFloor 4 0",
"Err DivByZero : Result (Int *) [ DivByZero ]*",
);
}
#[test]
fn num_ceil_division_success() {
expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*")
}
#[test]
fn bool_in_record() {
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
expect_success(
"{ z: { y: { x: 1 == 1 } } }",
"{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }",
);
expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }");
expect_success(
"{ x: 1 == 1, y: 1 != 1 }",
"{ x: True, y: False } : { x : Bool, y : Bool }",
);
}
#[test]
fn bool_basic_equality() {
expect_success("1 == 1", "True : Bool");
expect_success("1 != 1", "False : Bool");
}
#[test]
fn arbitrary_tag_unions() {
expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*");
expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*");
}
#[test]
fn tag_without_arguments() {
expect_success("True", "True : [ True ]*");
expect_success("False", "False : [ False ]*");
}
#[test]
fn byte_tag_union() {
expect_success(
"if 1 == 1 then Red else if 1 == 1 then Green else Blue",
"Red : [ Blue, Green, Red ]*",
);
expect_success(
"{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }",
"{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }",
);
}
#[test]
fn tag_in_record() {
expect_success(
"{ x: Foo 1 2 3, y : 4 }",
"{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }",
);
expect_success(
"{ x: Foo 1 2 3 }",
"{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }",
);
expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }");
}
#[test]
fn single_element_tag_union() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*");
}
#[test]
fn newtype_of_unit() {
expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*");
}
#[test]
fn tag_with_arguments() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success(
"if 1 == 1 then True 3 else False 3.14",
"True 3 : [ False (Float *), True (Num *) ]*",
)
}
#[test]
fn literal_empty_str() {
expect_success("\"\"", "\"\" : Str");
}
#[test]
fn literal_ascii_str() {
expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str");
}
#[test]
fn literal_utf8_str() {
expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str");
}
#[test]
fn str_concat() {
expect_success(
"Str.concat \"Hello, \" \"World!\"",
"\"Hello, World!\" : Str",
);
}
#[test]
fn str_count_graphemes() {
expect_success("Str.countGraphemes \"å🤔\"", "2 : Nat");
}
#[test]
fn literal_empty_list() {
expect_success("[]", "[] : List *");
}
#[test]
fn literal_empty_list_empty_record() {
expect_success("[ {} ]", "[ {} ] : List {}");
}
#[test]
fn literal_num_list() {
expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)");
}
#[test]
fn literal_int_list() {
expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List (Int *)");
}
#[test]
fn literal_float_list() {
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)");
}
#[test]
fn literal_string_list() {
expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#);
}
#[test]
fn nested_string_list() {
expect_success(
r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ]"#,
r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ] : List (List (List Str))"#,
);
}
#[test]
fn nested_num_list() {
expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ]"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Num *)))"#,
);
}
#[test]
fn nested_int_list() {
expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List I64))"#,
);
}
#[test]
fn nested_float_list() {
expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#,
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#,
);
}
#[test]
fn num_bitwise_and() {
expect_success("Num.bitwiseAnd 20 20", "20 : Int *");
expect_success("Num.bitwiseAnd 25 10", "8 : Int *");
expect_success("Num.bitwiseAnd 200 0", "0 : Int *")
}
#[test]
fn num_bitwise_xor() {
expect_success("Num.bitwiseXor 20 20", "0 : Int *");
expect_success("Num.bitwiseXor 15 14", "1 : Int *");
expect_success("Num.bitwiseXor 7 15", "8 : Int *");
expect_success("Num.bitwiseXor 200 0", "200 : Int *")
}
#[test]
fn num_add_wrap() {
expect_success("Num.addWrap Num.maxInt 1", "-9223372036854775808 : Int *");
}
#[test]
fn num_sub_wrap() {
expect_success("Num.subWrap Num.minInt 1", "9223372036854775807 : Int *");
}
#[test]
fn num_mul_wrap() {
expect_success("Num.mulWrap Num.maxInt 2", "-2 : Int *");
}
#[test]
fn num_add_checked() {
expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*");
expect_success(
"Num.addChecked Num.maxInt 1",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
#[test]
fn num_sub_checked() {
expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*");
expect_success(
"Num.subChecked Num.minInt 1",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
#[test]
fn num_mul_checked() {
expect_success(
"Num.mulChecked 20 2",
"Ok 40 : Result (Num *) [ Overflow ]*",
);
expect_success(
"Num.mulChecked Num.maxInt 2",
"Err Overflow : Result I64 [ Overflow ]*",
);
}
#[test]
fn list_concat() {
expect_success(
"List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]",
"[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List (Float *)",
);
}
#[test]
fn list_contains() {
expect_success("List.contains [] 0", "False : Bool");
expect_success("List.contains [ 1, 2, 3 ] 2", "True : Bool");
expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool");
}
#[test]
fn list_sum() {
expect_success("List.sum []", "0 : Num *");
expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *");
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64");
}
#[test]
fn list_first() {
expect_success(
"List.first [ 12, 9, 6, 3 ]",
"Ok 12 : Result (Num *) [ ListWasEmpty ]*",
);
expect_success(
"List.first []",
"Err ListWasEmpty : Result * [ ListWasEmpty ]*",
);
}
#[test]
fn list_last() {
expect_success(
"List.last [ 12, 9, 6, 3 ]",
"Ok 3 : Result (Num *) [ ListWasEmpty ]*",
);
expect_success(
"List.last []",
"Err ListWasEmpty : Result * [ ListWasEmpty ]*",
);
}
#[test]
fn empty_record() {
expect_success("{}", "{} : {}");
}
#[test]
fn basic_1_field_i64_record() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success("{ foo: 42 }", "{ foo: 42 } : { foo : Num * }");
}
#[test]
fn basic_1_field_f64_record() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float * }");
}
#[test]
fn nested_1_field_i64_record() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success(
"{ foo: { bar: { baz: 42 } } }",
"{ foo: { bar: { baz: 42 } } } : { foo : { bar : { baz : Num * } } }",
);
}
#[test]
fn nested_1_field_f64_record() {
// Even though this gets unwrapped at runtime, the repl should still
// report it as a record
expect_success(
"{ foo: { bar: { baz: 4.2 } } }",
"{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float * } } }",
);
}
#[test]
fn basic_2_field_i64_record() {
expect_success(
"{ foo: 0x4, bar: 0x2 }",
"{ bar: 2, foo: 4 } : { bar : Int *, foo : Int * }",
);
}
#[test]
fn basic_2_field_f64_record() {
expect_success(
"{ foo: 4.1, bar: 2.3 }",
"{ bar: 2.3, foo: 4.1 } : { bar : Float *, foo : Float * }",
);
}
#[test]
fn basic_2_field_mixed_record() {
expect_success(
"{ foo: 4.1, bar: 2 }",
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float * }",
);
}
#[test]
fn basic_3_field_record() {
expect_success(
"{ foo: 4.1, bar: 2, baz: 0x5 }",
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int *, foo : Float * }",
);
}
#[test]
fn list_of_1_field_records() {
// Even though these get unwrapped at runtime, the repl should still
// report them as records
expect_success("[ { foo: 42 } ]", "[ { foo: 42 } ] : List { foo : Num * }");
}
#[test]
fn list_of_2_field_records() {
expect_success(
"[ { foo: 4.1, bar: 2 } ]",
"[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float * }",
);
}
#[test]
fn three_element_record() {
// if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help
expect_success(
"{ a: 1, b: 2, c: 3 }",
"{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : Num * }",
);
}
#[test]
fn four_element_record() {
// if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help
expect_success(
"{ a: 1, b: 2, c: 3, d: 4 }",
"{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }",
);
}
// #[test]
// fn multiline_string() {
// // If a string contains newlines, format it as a multiline string in the output
// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\"");
// }
#[test]
fn list_of_3_field_records() {
expect_success(
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int *, foo : Float * }",
);
}
#[test]
fn identity_lambda() {
expect_success("\\x -> x", "<function> : a -> a");
}
#[test]
fn sum_lambda() {
expect_success("\\x, y -> x + y", "<function> : Num a, Num a -> Num a");
}
#[test]
fn stdlib_function() {
expect_success("Num.abs", "<function> : Num a -> Num a");
}
#[test]
fn too_few_args() {
expect_failure(
"Num.add 2",
indoc!(
r#"
TOO FEW ARGS
The add function expects 2 arguments, but it got only 1:
4 Num.add 2
^^^^^^^
Roc does not allow functions to be partially applied. Use a closure to
make partial application explicit.
"#
),
);
}
#[test]
fn type_problem() {
expect_failure(
"1 + \"\"",
indoc!(
r#"
TYPE MISMATCH
The 2nd argument to add is not what I expect:
4 1 + ""
^^
This argument is a string of type:
Str
But add needs the 2nd argument to be:
Num a
"#
),
);
}
#[test]
fn issue_2149() {
expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [ InvalidNumStr ]*");
expect_success(
r#"Str.toI8 "128""#,
"Err InvalidNumStr : Result I8 [ InvalidNumStr ]*",
);
expect_success(
r#"Str.toI16 "32767""#,
"Ok 32767 : Result I16 [ InvalidNumStr ]*",
);
expect_success(
r#"Str.toI16 "32768""#,
"Err InvalidNumStr : Result I16 [ InvalidNumStr ]*",
);
}
// #[test]
// fn parse_problem() {
// // can't find something that won't parse currently
// }
//
// #[test]
// fn mono_problem() {
// // can't produce a mono error (non-exhaustive pattern) yet
// }
}

236
cli_utils/Cargo.lock generated
View file

@ -386,6 +386,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "clipboard-win"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.24.0" version = "0.24.0"
@ -480,7 +491,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b" checksum = "4423d79fed83ebd9ab81ec21fa97144300a961782158287dc9bf7eddac37ff0b"
dependencies = [ dependencies = [
"clipboard-win", "clipboard-win 3.1.1",
"objc", "objc",
"objc-foundation", "objc-foundation",
"objc_id", "objc_id",
@ -808,9 +819,9 @@ dependencies = [
[[package]] [[package]]
name = "dirs-next" name = "dirs-next"
version = "1.0.2" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"dirs-sys-next", "dirs-sys-next",
@ -898,6 +909,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.9.0" version = "0.9.0"
@ -911,12 +928,33 @@ dependencies = [
"termcolor", "termcolor",
] ]
[[package]]
name = "error-code"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff"
dependencies = [
"libc",
"str-buf",
]
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fd-lock"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a16910e685088843d53132b04e0f10a571fdb193224fc589685b3ba1ce4cb03d"
dependencies = [
"cfg-if 1.0.0",
"libc",
"windows-sys",
]
[[package]] [[package]]
name = "find-crate" name = "find-crate"
version = "0.6.3" version = "0.6.3"
@ -1691,16 +1729,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
[[package]] [[package]]
name = "nix" name = "nibble_vec"
version = "0.17.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [ dependencies = [
"bitflags", "smallvec",
"cc",
"cfg-if 0.1.10",
"libc",
"void",
] ]
[[package]] [[package]]
@ -1740,6 +1774,19 @@ dependencies = [
"memoffset", "memoffset",
] ]
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.0" version = "7.1.0"
@ -2246,6 +2293,16 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.4" version = "0.8.4"
@ -2418,12 +2475,13 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting", "roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"snafu", "snafu",
@ -2453,6 +2511,7 @@ dependencies = [
"roc_reporting", "roc_reporting",
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"serde_json", "serde_json",
@ -2467,6 +2526,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_region", "roc_region",
"roc_target",
"roc_types", "roc_types",
] ]
@ -2492,31 +2552,24 @@ dependencies = [
"bumpalo", "bumpalo",
"clap 3.0.0-beta.5", "clap 3.0.0-beta.5",
"const_format", "const_format",
"inkwell 0.1.0",
"libloading 0.7.1",
"mimalloc", "mimalloc",
"roc_build", "roc_build",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_docs", "roc_docs",
"roc_editor", "roc_editor",
"roc_error_macros",
"roc_fmt", "roc_fmt",
"roc_gen_llvm",
"roc_linker", "roc_linker",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
"roc_problem",
"roc_region", "roc_region",
"roc_repl_cli",
"roc_reporting", "roc_reporting",
"roc_solve", "roc_target",
"roc_types",
"roc_unify",
"rustyline",
"rustyline-derive",
"target-lexicon", "target-lexicon",
"tempfile", "tempfile",
] ]
@ -2574,6 +2627,7 @@ dependencies = [
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_region", "roc_region",
"roc_target",
"roc_types", "roc_types",
"snafu", "snafu",
] ]
@ -2623,6 +2677,10 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "roc_error_macros"
version = "0.1.0"
[[package]] [[package]]
name = "roc_fmt" name = "roc_fmt"
version = "0.1.0" version = "0.1.0"
@ -2643,12 +2701,13 @@ dependencies = [
"packed_struct", "packed_struct",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting",
"roc_solve", "roc_solve",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"target-lexicon", "target-lexicon",
@ -2663,10 +2722,11 @@ dependencies = [
"morphic_lib", "morphic_lib",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_std", "roc_std",
"roc_target",
"target-lexicon", "target-lexicon",
] ]
@ -2677,10 +2737,11 @@ dependencies = [
"bumpalo", "bumpalo",
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_std", "roc_std",
"roc_target",
] ]
[[package]] [[package]]
@ -2725,6 +2786,7 @@ dependencies = [
"roc_region", "roc_region",
"roc_reporting", "roc_reporting",
"roc_solve", "roc_solve",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"ven_pretty", "ven_pretty",
@ -2758,6 +2820,7 @@ dependencies = [
"roc_region", "roc_region",
"roc_solve", "roc_solve",
"roc_std", "roc_std",
"roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"static_assertions", "static_assertions",
@ -2789,6 +2852,50 @@ dependencies = [
[[package]] [[package]]
name = "roc_region" name = "roc_region"
version = "0.1.0" version = "0.1.0"
dependencies = [
"static_assertions",
]
[[package]]
name = "roc_repl_cli"
version = "0.1.0"
dependencies = [
"bumpalo",
"const_format",
"inkwell 0.1.0",
"libloading 0.7.1",
"roc_build",
"roc_collections",
"roc_gen_llvm",
"roc_load",
"roc_mono",
"roc_parse",
"roc_repl_eval",
"roc_target",
"roc_types",
"rustyline",
"rustyline-derive",
"target-lexicon",
]
[[package]]
name = "roc_repl_eval"
version = "0.1.0"
dependencies = [
"bumpalo",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_fmt",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
"roc_region",
"roc_reporting",
"roc_target",
"roc_types",
]
[[package]] [[package]]
name = "roc_reporting" name = "roc_reporting"
@ -2826,6 +2933,13 @@ dependencies = [
name = "roc_std" name = "roc_std"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "roc_target"
version = "0.1.0"
dependencies = [
"target-lexicon",
]
[[package]] [[package]]
name = "roc_types" name = "roc_types"
version = "0.1.0" version = "0.1.0"
@ -2887,16 +3001,21 @@ dependencies = [
[[package]] [[package]]
name = "rustyline" name = "rustyline"
version = "6.2.0" version = "9.1.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99" source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "bitflags",
"cfg-if 1.0.0",
"clipboard-win 4.2.2",
"dirs-next", "dirs-next",
"fd-lock",
"libc", "libc",
"log", "log",
"memchr", "memchr",
"nix 0.17.0", "nix 0.23.1",
"radix_trie",
"scopeguard", "scopeguard",
"smallvec",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
"utf8parse", "utf8parse",
@ -2905,8 +3024,8 @@ dependencies = [
[[package]] [[package]]
name = "rustyline-derive" name = "rustyline-derive"
version = "0.3.1" version = "0.6.0"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99" source = "git+https://github.com/rtfeldman/rustyline?tag=v9.1.1#7053ae0fe0ee710d38ed5845dd979113382994dc"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@ -3175,6 +3294,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "str-buf"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
[[package]] [[package]]
name = "strip-ansi-escapes" name = "strip-ansi-escapes"
version = "0.1.1" version = "0.1.1"
@ -3418,12 +3543,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]] [[package]]
name = "vte" name = "vte"
version = "0.10.1" version = "0.10.1"
@ -3816,6 +3935,49 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6"
dependencies = [
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2"
[[package]]
name = "windows_i686_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a"
[[package]]
name = "windows_i686_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64"
[[package]]
name = "windows_x86_64_gnu"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954"
[[package]]
name = "windows_x86_64_msvc"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.25.0" version = "0.25.0"

View file

@ -4,7 +4,6 @@ extern crate roc_load;
extern crate roc_module; extern crate roc_module;
extern crate tempfile; extern crate tempfile;
use roc_cli::repl::{INSTRUCTIONS, WELCOME_MESSAGE};
use serde::Deserialize; use serde::Deserialize;
use serde_xml_rs::from_str; use serde_xml_rs::from_str;
use std::env; use std::env;
@ -32,7 +31,8 @@ pub fn path_to_roc_binary() -> PathBuf {
.or_else(|| { .or_else(|| {
env::current_exe().ok().map(|mut path| { env::current_exe().ok().map(|mut path| {
path.pop(); path.pop();
if path.ends_with("deps") { path.pop(); if path.ends_with("deps") {
path.pop();
} }
path path
}) })
@ -294,86 +294,14 @@ pub fn fixture_file(dir_name: &str, file_name: &str) -> PathBuf {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn repl_eval(input: &str) -> Out { pub fn known_bad_file(file_name: &str) -> PathBuf {
let mut cmd = Command::new(path_to_roc_binary()); let mut path = root_dir();
cmd.arg("repl"); // Descend into cli/tests/known_bad/{file_name}
path.push("cli");
path.push("tests");
path.push("known_bad");
path.push(file_name);
let mut child = cmd path
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute compiled `roc` binary in CLI test");
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
// Send the input expression
stdin
.write_all(input.as_bytes())
.expect("Failed to write input to stdin");
// Evaluate the expression
stdin
.write_all(b"\n")
.expect("Failed to write newline to stdin");
// Gracefully exit the repl
stdin
.write_all(b":exit\n")
.expect("Failed to write :exit to stdin");
}
let output = child
.wait_with_output()
.expect("Error waiting for REPL child process to exit.");
// Remove the initial instructions from the output.
let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.starts_with(&expected_instructions),
"Unexpected repl output: {}",
stdout
);
let (_, answer) = stdout.split_at(expected_instructions.len());
let answer = if answer.is_empty() {
// The repl crashed before completing the evaluation.
// This is most likely due to a segfault.
if output.status.to_string() == "signal: 11" {
panic!(
"repl segfaulted during the test. Stderr was {:?}",
String::from_utf8(output.stderr).unwrap()
);
} else {
panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap());
}
} else {
let expected_after_answer = "\n".to_string();
assert!(
answer.ends_with(&expected_after_answer),
"Unexpected repl output after answer: {}",
answer
);
// Use [1..] to trim the leading '\n'
// and (len - 1) to trim the trailing '\n'
let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1);
// Remove ANSI escape codes from the answer - for example:
//
// Before: "42 \u{1b}[35m:\u{1b}[0m Num *"
// After: "42 : Num *"
strip_ansi_escapes::strip(answer).unwrap()
};
Out {
stdout: String::from_utf8(answer).unwrap(),
stderr: String::from_utf8(output.stderr).unwrap(),
status: output.status,
}
} }

View file

@ -164,3 +164,24 @@ The compiler is invoked from the CLI via `build_file` in cli/src/build.rs
| Code gen (unoptimized but fast, Wasm) | gen_wasm/src/lib.rs: build_module | | Code gen (unoptimized but fast, Wasm) | gen_wasm/src/lib.rs: build_module |
For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`. For a more detailed understanding of the compilation phases, see the `Phase`, `BuildTask`, and `Msg` enums in `load/src/file.rs`.
## Debugging intermediate representations
### The mono IR
If you observe a miscomplication, you may first want to check the generated mono
IR for your code - maybe there was a problem during specialization or layout
generation. One way to do this is to add a test to `test_mono/src/tests.rs`
and run the tests with `cargo test -p test_mono`; this will write the mono
IR to a file.
You may also want to set some or all of the following environment variables:
- `PRINT_IR_AFTER_SPECIALIZATION=1` prints the mono IR after function
specialization to stdout
- `PRINT_IR_AFTER_RESET_REUSE=1` prints the mono IR after insertion of
reset/reuse isntructions to stdout
- `PRINT_IR_AFTER_REFCOUNT=1` prints the mono IR after insertion of reference
counting instructions to stdout
- `PRETTY_PRINT_IR_SYMBOLS=1` instructs the pretty printer to dump all the
information it knows about the mono IR whenever it is printed

View file

@ -19,6 +19,7 @@ roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_load = { path = "../load" } roc_load = { path = "../load" }
roc_target = { path = "../roc_target" }
roc_gen_llvm = { path = "../gen_llvm", optional = true } roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm", optional = true } roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false } roc_gen_dev = { path = "../gen_dev", default-features = false }

View file

@ -753,8 +753,10 @@ fn link_linux(
// NOTE: order of arguments to `ld` matters here! // NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments // The `-l` flags should go after the `.o` arguments
Ok((
Command::new("ld") let mut command = Command::new("ld");
command
// Don't allow LD_ env vars to affect this // Don't allow LD_ env vars to affect this
.env_clear() .env_clear()
.env("PATH", &env_path) .env("PATH", &env_path)
@ -792,10 +794,11 @@ fn link_linux(
// Output // Output
"-o", "-o",
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.) output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
]) ]);
.spawn()?,
output_path, let output = command.spawn()?;
))
Ok((output, output_path))
} }
fn link_macos( fn link_macos(
@ -853,11 +856,29 @@ fn link_macos(
"-lSystem", "-lSystem",
"-lresolv", "-lresolv",
"-lpthread", "-lpthread",
// This `-F PATH` flag is needed for `-framework` flags to work
"-F",
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/",
// These frameworks are needed for GUI examples to work
"-framework",
"AudioUnit",
"-framework",
"Cocoa",
"-framework",
"CoreAudio",
"-framework",
"CoreVideo",
"-framework",
"IOKit",
"-framework",
"Metal",
"-framework",
"QuartzCore",
// "-lrt", // TODO shouldn't we need this? // "-lrt", // TODO shouldn't we need this?
// "-lc_nonshared", // TODO shouldn't we need this? // "-lc_nonshared", // TODO shouldn't we need this?
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
// "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli "-framework",
// "Security", "Security",
// Output // Output
"-o", "-o",
output_path.to_str().unwrap(), // app output_path.to_str().unwrap(), // app

View file

@ -5,6 +5,7 @@ pub use roc_gen_llvm::llvm::build::FunctionIterator;
use roc_load::file::{LoadedModule, MonomorphizedModule}; use roc_load::file::{LoadedModule, MonomorphizedModule};
use roc_module::symbol::{Interns, ModuleId}; use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_region::all::LineInfo;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
@ -30,7 +31,6 @@ const LLVM_VERSION: &str = "12";
pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize { pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize {
report_problems_help( report_problems_help(
loaded.total_problems(), loaded.total_problems(),
&loaded.header_sources,
&loaded.sources, &loaded.sources,
&loaded.interns, &loaded.interns,
&mut loaded.can_problems, &mut loaded.can_problems,
@ -42,7 +42,6 @@ pub fn report_problems_monomorphized(loaded: &mut MonomorphizedModule) -> usize
pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize { pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize {
report_problems_help( report_problems_help(
loaded.total_problems(), loaded.total_problems(),
&loaded.header_sources,
&loaded.sources, &loaded.sources,
&loaded.interns, &loaded.interns,
&mut loaded.can_problems, &mut loaded.can_problems,
@ -53,7 +52,6 @@ pub fn report_problems_typechecked(loaded: &mut LoadedModule) -> usize {
fn report_problems_help( fn report_problems_help(
total_problems: usize, total_problems: usize,
header_sources: &MutMap<ModuleId, (PathBuf, Box<str>)>,
sources: &MutMap<ModuleId, (PathBuf, Box<str>)>, sources: &MutMap<ModuleId, (PathBuf, Box<str>)>,
interns: &Interns, interns: &Interns,
can_problems: &mut MutMap<ModuleId, Vec<roc_problem::can::Problem>>, can_problems: &mut MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
@ -74,12 +72,9 @@ fn report_problems_help(
for (home, (module_path, src)) in sources.iter() { for (home, (module_path, src)) in sources.iter() {
let mut src_lines: Vec<&str> = Vec::new(); let mut src_lines: Vec<&str> = Vec::new();
if let Some((_, header_src)) = header_sources.get(home) {
src_lines.extend(header_src.split('\n'));
src_lines.extend(src.split('\n').skip(1));
} else {
src_lines.extend(src.split('\n')); src_lines.extend(src.split('\n'));
}
let lines = LineInfo::new(&src_lines.join("\n"));
// Report parsing and canonicalization problems // Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, *home, interns); let alloc = RocDocAllocator::new(&src_lines, *home, interns);
@ -87,7 +82,7 @@ fn report_problems_help(
let problems = can_problems.remove(home).unwrap_or_default(); let problems = can_problems.remove(home).unwrap_or_default();
for problem in problems.into_iter() { for problem in problems.into_iter() {
let report = can_problem(&alloc, module_path.clone(), problem); let report = can_problem(&alloc, &lines, module_path.clone(), problem);
let severity = report.severity; let severity = report.severity;
let mut buf = String::new(); let mut buf = String::new();
@ -106,7 +101,7 @@ fn report_problems_help(
let problems = type_problems.remove(home).unwrap_or_default(); let problems = type_problems.remove(home).unwrap_or_default();
for problem in problems { for problem in problems {
if let Some(report) = type_problem(&alloc, module_path.clone(), problem) { if let Some(report) = type_problem(&alloc, &lines, module_path.clone(), problem) {
let severity = report.severity; let severity = report.severity;
let mut buf = String::new(); let mut buf = String::new();
@ -126,7 +121,7 @@ fn report_problems_help(
let problems = mono_problems.remove(home).unwrap_or_default(); let problems = mono_problems.remove(home).unwrap_or_default();
for problem in problems { for problem in problems {
let report = mono_problem(&alloc, module_path.clone(), problem); let report = mono_problem(&alloc, &lines, module_path.clone(), problem);
let severity = report.severity; let severity = report.severity;
let mut buf = String::new(); let mut buf = String::new();
@ -240,7 +235,7 @@ pub fn gen_from_mono_module_llvm(
let code_gen_start = SystemTime::now(); let code_gen_start = SystemTime::now();
// Generate the binary // Generate the binary
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let target_info = roc_target::TargetInfo::from(target);
let context = Context::create(); let context = Context::create();
let module = arena.alloc(module_from_builtins(target, &context, "app")); let module = arena.alloc(module_from_builtins(target, &context, "app"));
@ -291,11 +286,11 @@ pub fn gen_from_mono_module_llvm(
context: &context, context: &context,
interns: loaded.interns, interns: loaded.interns,
module, module,
ptr_bytes, target_info,
// in gen_tests, the compiler provides roc_panic // in gen_tests, the compiler provides roc_panic
// and sets up the setjump/longjump exception handling // and sets up the setjump/longjump exception handling
is_gen_test: false, is_gen_test: false,
exposed_to_host: loaded.exposed_to_host.keys().copied().collect(), exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(),
}; };
roc_gen_llvm::llvm::build::build_procedures( roc_gen_llvm::llvm::build::build_procedures(
@ -500,6 +495,7 @@ fn gen_from_mono_module_dev_wasm32(
let exposed_to_host = loaded let exposed_to_host = loaded
.exposed_to_host .exposed_to_host
.values
.keys() .keys()
.copied() .copied()
.collect::<MutSet<_>>(); .collect::<MutSet<_>>();
@ -510,7 +506,18 @@ fn gen_from_mono_module_dev_wasm32(
exposed_to_host, exposed_to_host,
}; };
let bytes = roc_gen_wasm::build_module(&env, &mut interns, procedures).unwrap(); let platform_and_builtins_object_file_bytes: &[u8] = if true {
todo!("The WebAssembly dev backend is a work in progress. Coming soon!")
} else {
&[] // This `if` gets rid of "unreachable code" warnings. When we're ready to use it, we'll notice!
};
let bytes = roc_gen_wasm::build_module(
&env,
&mut interns,
platform_and_builtins_object_file_bytes,
procedures,
);
std::fs::write(&app_o_file, &bytes).expect("failed to write object to file"); std::fs::write(&app_o_file, &bytes).expect("failed to write object to file");
@ -537,7 +544,7 @@ fn gen_from_mono_module_dev_assembly(
let env = roc_gen_dev::Env { let env = roc_gen_dev::Env {
arena, arena,
module_id, module_id,
exposed_to_host: exposed_to_host.keys().copied().collect(), exposed_to_host: exposed_to_host.values.keys().copied().collect(),
lazy_literals, lazy_literals,
generate_allocators, generate_allocators,
}; };

View file

@ -10,3 +10,4 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_target = { path = "../roc_target" }

View file

@ -80,7 +80,7 @@ fn atan() {
``` ```
But replace `Num.atan` and the type signature with the new builtin. But replace `Num.atan` and the type signature with the new builtin.
### gen/test/*.rs ### test_gen/test/*.rs
In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like: In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like:
``` ```
#[test] #[test]

View file

@ -26,7 +26,7 @@ pub fn build(b: *Builder) void {
.default_target = CrossTarget{ .default_target = CrossTarget{
.cpu_model = .baseline, .cpu_model = .baseline,
// TODO allow for native target for maximum speed // TODO allow for native target for maximum speed
} },
}); });
const i386_target = makeI386Target(); const i386_target = makeI386Target();
const wasm32_target = makeWasm32Target(); const wasm32_target = makeWasm32Target();

View file

@ -750,7 +750,8 @@ pub fn dictWalk(
const alignment_u32 = alignment.toU32(); const alignment_u32 = alignment.toU32();
// allocate space to write the result of the stepper into // allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea // experimentally aliasing the accum and output pointers is not a good idea
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32); // TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32) orelse unreachable;
var b1 = output orelse unreachable; var b1 = output orelse unreachable;
var b2 = bytes_ptr; var b2 = bytes_ptr;

View file

@ -0,0 +1,162 @@
const std = @import("std");
const utils = @import("utils.zig");
const CSlice = utils.CSlice;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const Failure = struct {
start_line: u32,
end_line: u32,
start_col: u16,
end_col: u16,
};
// BEGIN FAILURES GLOBALS ///////////////////
var failures_mutex = std.Thread.Mutex{};
var failures: [*]Failure = undefined;
var failure_length: usize = 0;
var failure_capacity: usize = 0;
// END FAILURES GLOBALS /////////////////////
pub fn expectFailed(
start_line: u32,
end_line: u32,
start_col: u16,
end_col: u16,
) void {
const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col };
// Lock the failures mutex before reading from any of the failures globals,
// and then release the lock once we're done modifying things.
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
// If we don't have enough capacity to add a failure, allocate a new failures pointer.
if (failure_length >= failure_capacity) {
if (failure_capacity > 0) {
// We already had previous failures allocated, so try to realloc in order
// to grow the size in-place without having to memcpy bytes over.
const old_pointer = failures;
const old_bytes = failure_capacity * @sizeOf(Failure);
failure_capacity *= 2;
const new_bytes = failure_capacity * @sizeOf(Failure);
const failures_u8 = @ptrCast([*]u8, @alignCast(@alignOf(Failure), failures));
const raw_pointer = utils.realloc(failures_u8, new_bytes, old_bytes, @alignOf(Failure));
failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer));
// If realloc wasn't able to expand in-place (that is, it returned a different pointer),
// then copy the data into the new pointer and dealloc the old one.
if (failures != old_pointer) {
const old_pointer_u8 = @ptrCast([*]u8, old_pointer);
utils.memcpy(@ptrCast([*]u8, failures), old_pointer_u8, old_bytes);
utils.dealloc(old_pointer_u8, @alignOf(Failure));
}
} else {
// We've never had any failures before, so allocate the failures for the first time.
failure_capacity = 10;
const raw_pointer = utils.alloc(failure_capacity * @sizeOf(Failure), @alignOf(Failure));
failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer));
}
}
failures[failure_length] = new_failure;
failure_length += 1;
}
pub fn expectFailedC(
start_line: u32,
end_line: u32,
start_col: u16,
end_col: u16,
) callconv(.C) void {
return @call(.{ .modifier = always_inline }, expectFailed, .{ start_line, end_line, start_col, end_col });
}
pub fn getExpectFailures() []Failure {
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
if (failure_length > 0) {
// defensively clone failures, in case someone modifies the originals after the mutex has been released.
const num_bytes = failure_length * @sizeOf(Failure);
// TODO handle the possibility of alloc failing
const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)) orelse unreachable;
utils.memcpy(raw_clones, @ptrCast([*]u8, failures), num_bytes);
const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones));
return clones[0..failure_length];
} else {
return failures[0..0];
}
}
pub fn getExpectFailuresC() callconv(.C) CSlice {
var bytes = @ptrCast(*c_void, failures);
return .{ .pointer = bytes, .len = failure_length };
}
pub fn deinitFailures() void {
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
utils.dealloc(@ptrCast([*]u8, failures), @alignOf(Failure));
failure_length = 0;
}
pub fn deinitFailuresC() callconv(.C) void {
return @call(.{ .modifier = always_inline }, deinitFailures, .{});
}
test "expectFailure does something" {
defer deinitFailures();
var fails = getExpectFailures();
try std.testing.expectEqual(fails.len, 0);
expectFailed(1, 2, 3, 4);
fails = getExpectFailures();
try std.testing.expectEqual(fails.len, 1);
utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure));
const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 };
fails = getExpectFailures();
try std.testing.expectEqual(fails[0], what_it_should_look_like);
utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure));
}

View file

@ -550,7 +550,8 @@ pub fn listKeepResult(
var output = RocList.allocate(alignment, list.len(), list.len() * after_width); var output = RocList.allocate(alignment, list.len(), list.len() * after_width);
const target_ptr = output.bytes orelse unreachable; const target_ptr = output.bytes orelse unreachable;
var temporary = @ptrCast([*]u8, utils.alloc(result_width, alignment)); // TODO handle alloc failing!
var temporary = utils.alloc(result_width, alignment) orelse unreachable;
if (data_is_owned) { if (data_is_owned) {
inc_n_data(data, size); inc_n_data(data, size);
@ -614,7 +615,8 @@ pub fn listWalk(
inc_n_data(data, list.len()); inc_n_data(data, list.len());
} }
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment); // TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable;
var b1 = output orelse unreachable; var b1 = output orelse unreachable;
var b2 = bytes_ptr; var b2 = bytes_ptr;
@ -660,7 +662,8 @@ pub fn listWalkBackwards(
inc_n_data(data, list.len()); inc_n_data(data, list.len());
} }
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment); // TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable;
var b1 = output orelse unreachable; var b1 = output orelse unreachable;
var b2 = bytes_ptr; var b2 = bytes_ptr;
@ -708,7 +711,8 @@ pub fn listWalkUntil(
return; return;
} }
const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment); // TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment) orelse unreachable;
// NOTE: assumes data bytes are the first bytes in a tag // NOTE: assumes data bytes are the first bytes in a tag
@memcpy(bytes_ptr, accum orelse unreachable, accum_width); @memcpy(bytes_ptr, accum orelse unreachable, accum_width);

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const math = std.math; const math = std.math;
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const expect = @import("expect.zig");
const ROC_BUILTINS = "roc_builtins"; const ROC_BUILTINS = "roc_builtins";
const NUM = "num"; const NUM = "num";
@ -141,12 +142,14 @@ comptime {
} }
// Utils // Utils
comptime { comptime {
exportUtilsFn(utils.test_panic, "test_panic"); exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.increfC, "incref"); exportUtilsFn(utils.increfC, "incref");
exportUtilsFn(utils.decrefC, "decref"); exportUtilsFn(utils.decrefC, "decref");
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null"); exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
exportExpectFn(expect.expectFailedC, "expect_failed");
exportExpectFn(expect.getExpectFailuresC, "get_expect_failures");
exportExpectFn(expect.deinitFailuresC, "deinit_failures");
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak }); @export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
} }
@ -175,6 +178,10 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "utils." ++ func_name); exportBuiltinFn(func, "utils." ++ func_name);
} }
fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "expect." ++ func_name);
}
// Custom panic function, as builtin Zig version errors during LLVM verification // Custom panic function, as builtin Zig version errors during LLVM verification
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn { pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
const builtin = @import("builtin"); const builtin = @import("builtin");

View file

@ -18,14 +18,18 @@ extern fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void;
// Signals to the host that the program has panicked // Signals to the host that the program has panicked
extern fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void; extern fn roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void;
// should work just like libc memcpy (we can't assume libc is present)
extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
comptime { comptime {
const builtin = @import("builtin"); const builtin = @import("builtin");
// During tetsts, use the testing allocators to satisfy these functions. // During tests, use the testing allocators to satisfy these functions.
if (builtin.is_test) { if (builtin.is_test) {
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong }); @export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong }); @export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong }); @export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong }); @export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
@export(testing_roc_memcpy, .{ .name = "roc_memcpy", .linkage = .Strong });
} }
} }
@ -53,8 +57,16 @@ fn testing_roc_panic(c_ptr: *anyopaque, tag_id: u32) callconv(.C) void {
@panic("Roc panicked"); @panic("Roc panicked");
} }
pub fn alloc(size: usize, alignment: u32) [*]u8 { fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?*c_void {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment })); const zig_dest = @ptrCast([*]u8, dest);
const zig_src = @ptrCast([*]u8, src);
@memcpy(zig_dest, zig_src, bytes);
return dest;
}
pub fn alloc(size: usize, alignment: u32) ?[*]u8 {
return @ptrCast(?[*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment }));
} }
pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 { pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 {
@ -70,6 +82,10 @@ pub fn panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment }); return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
} }
pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void {
@call(.{ .modifier = always_inline }, roc_memcpy, .{ dst, src, size });
}
// indirection because otherwise zig creates an alias to the panic function which our LLVM code // indirection because otherwise zig creates an alias to the panic function which our LLVM code
// does not know how to deal with // does not know how to deal with
pub fn test_panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { pub fn test_panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
@ -173,7 +189,8 @@ pub fn allocateWithRefcount(
switch (alignment) { switch (alignment) {
16 => { 16 => {
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment)); // TODO handle alloc failing!
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment) orelse unreachable);
var as_usize_array = @ptrCast([*]usize, new_bytes); var as_usize_array = @ptrCast([*]usize, new_bytes);
as_usize_array[0] = 0; as_usize_array[0] = 0;
@ -185,7 +202,8 @@ pub fn allocateWithRefcount(
return first_slot; return first_slot;
}, },
8 => { 8 => {
var raw = alloc(length, alignment); // TODO handle alloc failing!
var raw = alloc(length, alignment) orelse unreachable;
var new_bytes: [*]align(8) u8 = @alignCast(8, raw); var new_bytes: [*]align(8) u8 = @alignCast(8, raw);
var as_isize_array = @ptrCast([*]isize, new_bytes); var as_isize_array = @ptrCast([*]isize, new_bytes);
@ -197,7 +215,8 @@ pub fn allocateWithRefcount(
return first_slot; return first_slot;
}, },
4 => { 4 => {
var raw = alloc(length, alignment); // TODO handle alloc failing!
var raw = alloc(length, alignment) orelse unreachable;
var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw); var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw);
var as_isize_array = @ptrCast([*]isize, new_bytes); var as_isize_array = @ptrCast([*]isize, new_bytes);
@ -217,6 +236,11 @@ pub fn allocateWithRefcount(
} }
} }
pub const CSlice = extern struct {
pointer: *c_void,
len: usize,
};
pub fn unsafeReallocate( pub fn unsafeReallocate(
source_ptr: [*]u8, source_ptr: [*]u8,
alignment: u32, alignment: u32,

View file

@ -44,9 +44,9 @@ and : Bool, Bool -> Bool
## `a || b` is shorthand for `Bool.or a b`. ## `a || b` is shorthand for `Bool.or a b`.
## ##
## >>> True || True ## >>> True || True
# ##
## >>> True || False ## >>> True || False
# ##
## >>> False || True ## >>> False || True
## ##
## >>> False || False ## >>> False || False

View file

@ -32,6 +32,8 @@ interface List
set, set,
single, single,
sortWith, sortWith,
split,
sublist,
sum, sum,
swap, swap,
walk, walk,
@ -205,7 +207,7 @@ empty : List *
## Returns a list with the given length, where every element is the given value. ## Returns a list with the given length, where every element is the given value.
## ##
## ##
repeat : elem, Nat -> List elem repeat : Nat, elem -> List elem
## Returns a list of all the integers between one and another, ## Returns a list of all the integers between one and another,
## including both of the given numbers. ## including both of the given numbers.
@ -277,7 +279,7 @@ map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index ## This works like [List.map], except it also passes the index
## of the element to the conversion function. ## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after mapWithIndex : List before, (Nat, before -> after) -> List after
## This works like [List.map], except at any time you can return `Err` to ## This works like [List.map], except at any time you can return `Err` to
## cancel the entire operation immediately, and return that #Err. ## cancel the entire operation immediately, and return that #Err.

View file

@ -62,10 +62,25 @@ interface Num
isZero, isZero,
log, log,
maxFloat, maxFloat,
maxI8,
maxU8,
maxI16,
maxU16,
maxI32,
maxU32,
maxI64,
maxU64,
maxI128, maxI128,
maxInt,
minFloat, minFloat,
minInt, minI8,
minU8,
minI16,
minU16,
minI32,
minU32,
minI64,
minU64,
minI128,
modInt, modInt,
modFloat, modFloat,
mul, mul,
@ -102,7 +117,7 @@ interface Num
## ##
## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass ## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass
## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`. ## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`.
# ##
## Similarly, the number 0x1 (that is, the integer 1 in hexadecimal notation) ## Similarly, the number 0x1 (that is, the integer 1 in hexadecimal notation)
## technically has the type `Num (Integer *)`, so when you pass two of them to ## technically has the type `Num (Integer *)`, so when you pass two of them to
## [Num.add], the answer you get is `2 : Num (Integer *)`. ## [Num.add], the answer you get is `2 : Num (Integer *)`.
@ -359,47 +374,47 @@ Nat : Int [ @Natural ]
## ##
## | Range | Type | Size | ## | Range | Type | Size |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` -128` | #I8 | 1 Byte | ## | ` -128` | [I8] | 1 Byte |
## | ` 127` | | | ## | ` 127` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` 0` | #U8 | 1 Byte | ## | ` 0` | [U8] | 1 Byte |
## | ` 255` | | | ## | ` 255` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` -32_768` | #I16 | 2 Bytes | ## | ` -32_768` | [I16] | 2 Bytes |
## | ` 32_767` | | | ## | ` 32_767` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` 0` | #U16 | 2 Bytes | ## | ` 0` | [U16] | 2 Bytes |
## | ` 65_535` | | | ## | ` 65_535` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` -2_147_483_648` | #I32 | 4 Bytes | ## | ` -2_147_483_648` | [I32] | 4 Bytes |
## | ` 2_147_483_647` | | | ## | ` 2_147_483_647` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` 0` | #U32 | 4 Bytes | ## | ` 0` | [U32] | 4 Bytes |
## | ` (over 4 billion) 4_294_967_295` | | | ## | ` (over 4 billion) 4_294_967_295` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` -9_223_372_036_854_775_808` | #I64 | 8 Bytes | ## | ` -9_223_372_036_854_775_808` | [I64] | 8 Bytes |
## | ` 9_223_372_036_854_775_807` | | | ## | ` 9_223_372_036_854_775_807` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` 0` | #U64 | 8 Bytes | ## | ` 0` | [U64] | 8 Bytes |
## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | ## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | #I128 | 16 Bytes | ## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | [I128]| 16 Bytes |
## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | ## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | |
## |--------------------------------------------------------|-------|----------| ## |--------------------------------------------------------|-------|----------|
## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes | ## | ` (over 340 undecillion) 0` | [U128]| 16 Bytes |
## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | ## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | |
## ##
## Roc also has one variable-size integer type: #Nat. The size of #Nat is equal ## Roc also has one variable-size integer type: [Nat]. The size of [Nat] is equal
## to the size of a memory address, which varies by system. For example, when ## to the size of a memory address, which varies by system. For example, when
## compiling for a 64-bit system, #Nat is the same as #U64. When compiling for a ## compiling for a 64-bit system, [Nat] is the same as [U64]. When compiling for a
## 32-bit system, it's the same as #U32. ## 32-bit system, it's the same as [U32].
## ##
## A common use for #Nat is to store the length ("len" for short) of a ## A common use for [Nat] is to store the length ("len" for short) of a
## collection like #List, #Set, or #Map. 64-bit systems can represent longer ## collection like a [List]. 64-bit systems can represent longer
## lists in memory than 32-bit systems can, which is why the length of a list ## lists in memory than 32-bit systems can, which is why the length of a list
## is represented as a #Nat in Roc. ## is represented as a [Nat] in Roc.
## ##
## If any operation would result in an #Int that is either too big ## If any operation would result in an [Int] that is either too big
## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`), ## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`),
## then the operation will *overflow*. When an overflow occurs, the program will crash. ## then the operation will *overflow*. When an overflow occurs, the program will crash.
## ##
@ -501,7 +516,16 @@ add : Num a, Num a -> Num a
## ##
## This is the same as [Num.add] except if the operation overflows, instead of ## This is the same as [Num.add] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`. ## panicking or returning ∞ or -∞, it will return `Err Overflow`.
addCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* addChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
## Add two numbers, clamping on the maximum representable number rather than
## overflowing.
##
## This is the same as [Num.add] except for the saturating behavior if the
## addition is to overflow.
## For example, if `x : U8` is 200 and `y : U8` is 100, `addSaturated x y` will
## yield 255, the maximum value of a `U8`.
addSaturated : Num a, Num a -> Num a
## Subtract two numbers of the same type. ## Subtract two numbers of the same type.
## ##
@ -528,7 +552,16 @@ sub : Num a, Num a -> Num a
## ##
## This is the same as [Num.sub] except if the operation overflows, instead of ## This is the same as [Num.sub] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`. ## panicking or returning ∞ or -∞, it will return `Err Overflow`.
subCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]* subChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
## Subtract two numbers, clamping on the minimum representable number rather
## than overflowing.
##
## This is the same as [Num.sub] except for the saturating behavior if the
## subtraction is to overflow.
## For example, if `x : U8` is 10 and `y : U8` is 20, `subSaturated x y` will
## yield 0, the minimum value of a `U8`.
subSaturated : Num a, Num a -> Num a
## Multiply two numbers of the same type. ## Multiply two numbers of the same type.
## ##
@ -749,6 +782,15 @@ not : Int a -> Int a
## Limits ## Limits
## The lowest number that can be stored in a #Nat without underflowing its
## available memory and crashing.
##
## For reference, this is the number zero, because #Nat is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number.
## Unsigned numbers cannot be negative.
minNat : Nat
## The highest number that can be stored in a #Nat without overflowing its ## The highest number that can be stored in a #Nat without overflowing its
## available memory and crashing. ## available memory and crashing.
## ##
@ -757,83 +799,192 @@ not : Int a -> Int a
## 32-bit system, this will be equal to #Num.maxU32. ## 32-bit system, this will be equal to #Num.maxU32.
maxNat : Nat maxNat : Nat
## The number zero. ## The lowest number that can be stored in an #I8 without underflowing its
## available memory and crashing.
## ##
## #Num.minNat is the lowest number that can be stored in a #Nat, which is zero ## For reference, this number is `-128`.
## because #Nat is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), ##
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. ## Note that the positive version of this number is larger than #Int.maxI8,
minNat : Nat ## which means if you call #Num.abs on #Int.minI8, it will overflow and crash!
minI8 : I8
## The highest number that can be stored in an #I8 without overflowing its
## available memory and crashing.
##
## For reference, this number is `127`.
##
## Note that this is smaller than the positive version of #Int.minI8,
## which means if you call #Num.abs on #Int.minI8, it will overflow and crash!
maxI8 : I8
## The lowest number that can be stored in a #U8 without underflowing its
## available memory and crashing.
##
## For reference, this number is zero, because #U8 is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number.
## Unsigned numbers cannot be negative.
minU8 : U8
## The highest number that can be stored in a #U8 without overflowing its
## available memory and crashing.
##
## For reference, this number is `255`.
maxU8 : U8
## The lowest number that can be stored in an #I16 without underflowing its
## available memory and crashing.
##
## For reference, this number is `-32_768`.
##
## Note that the positive version of this number is larger than #Int.maxI16,
## which means if you call #Num.abs on #Int.minI16, it will overflow and crash!
minI16 : I16
## The highest number that can be stored in an #I16 without overflowing its
## available memory and crashing.
##
## For reference, this number is `32_767`.
##
## Note that this is smaller than the positive version of #Int.minI16,
## which means if you call #Num.abs on #Int.minI16, it will overflow and crash!
maxI16 : I16
## The lowest number that can be stored in a #U16 without underflowing its
## available memory and crashing.
##
## For reference, this number is zero, because #U16 is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number.
## Unsigned numbers cannot be negative.
minU16 : U16
## The highest number that can be stored in a #U16 without overflowing its
## available memory and crashing.
##
## For reference, this number is `65_535`.
maxU16 : U16
## The lowest number that can be stored in an #I32 without underflowing its
## available memory and crashing.
##
## For reference, this number is `-2_147_483_648`.
##
## Note that the positive version of this number is larger than #Int.maxI32,
## which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
minI32 : I32
## The highest number that can be stored in an #I32 without overflowing its ## The highest number that can be stored in an #I32 without overflowing its
## available memory and crashing. ## available memory and crashing.
## ##
## Note that this is smaller than the positive version of #Int.minI32 ## For reference, this number is `2_147_483_647`,
## which is over 2 million.
##
## Note that this is smaller than the positive version of #Int.minI32,
## which means if you call #Num.abs on #Int.minI32, it will overflow and crash! ## which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
maxI32 : I32 maxI32 : I32
## The min number that can be stored in an #I32 without overflowing its ## The lowest number that can be stored in a #U32 without underflowing its
## available memory and crashing. ## available memory and crashing.
## ##
## Note that the positive version of this number is this is larger than ## For reference, this number is zero, because #U32 is
## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash! ## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
minI32 : I32 ## and zero is the lowest unsigned number.
## Unsigned numbers cannot be negative.
## The highest number that can be stored in a #U64 without overflowing its minU32 : U32
## available memory and crashing.
##
## For reference, that number is `18_446_744_073_709_551_615`, which is over 18 quintillion.
maxU64 : U64
## The number zero.
##
## #Num.minU64 is the lowest number that can be stored in a #U64, which is zero
## because #U64 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minU64 : U64
## The highest number that can be stored in a #U32 without overflowing its ## The highest number that can be stored in a #U32 without overflowing its
## available memory and crashing. ## available memory and crashing.
## ##
## For reference, that number is `4_294_967_295`, which is over 4 million. ## For reference, this number is `4_294_967_295`,
## which is over 4 million.
maxU32 : U32 maxU32 : U32
## The number zero. ## The min number that can be stored in an #I64 without underflowing its
## available memory and crashing.
## ##
## #Num.minU32 is the lowest number that can be stored in a #U32, which is zero ## For reference, this number is `-`.
## because #U32 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations), ##
## Note that the positive version of this number is larger than #Int.maxI64,
## which means if you call #Num.abs on #Int.minI64, it will overflow and crash!
minI64 : I64
## The highest number that can be stored in an #I64 without overflowing its
## available memory and crashing.
##
## For reference, this number is ``,
## which is over 2 million.
##
## Note that this is smaller than the positive version of #Int.minI64,
## which means if you call #Num.abs on #Int.minI64, it will overflow and crash!
maxI64 : I64
## The lowest number that can be stored in a #U64 without underflowing its
## available memory and crashing.
##
## For reference, this number is zero because #U64 is
## [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative. ## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minU32 : U32 minU64 : U64
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308. ## The highest number that can be stored in a #U64 without overflowing its
## available memory and crashing.
## ##
## If you go higher than this, your running Roc code will crash - so be careful not to! ## For reference, this number is `18_446_744_073_709_551_615`,
maxF64 : F64 ## which is over 18 quintillion.
maxU64 : U64
## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308. ## The lowest number that can be stored in an #I128 without underflowing its
## available memory and crashing.
## ##
## If you go lower than this, your running Roc code will crash - so be careful not to! ## For reference, this number is `-170_141_183_460_469_231_731_687_303_715_884_105_728`.
minF64 : F64 ##
## Note that the positive version of this number is larger than #Int.maxI128,
## which means if you call #Num.abs on #Int.minI128, it will overflow and crash!
minI128 : I128
## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308. ## The highest number that can be stored in an #I128 without overflowing its
## available memory and crashing.
## ##
## If you go higher than this, your running Roc code will crash - so be careful not to! ## For reference, this number is `170_141_183_460_469_231_731_687_303_715_884_105_727`,
maxF32 : F32 ## which is over 2 million.
##
## Note that this is smaller than the positive version of #Int.minI128,
## which means if you call #Num.abs on #Int.minI128, it will overflow and crash!
maxI128 : I128
## The lowest supported #F32 value you can have, which is approximately -1.8 × 10^308. ## The lowest supported #F32 value you can have, which is approximately -1.8 × 10^308.
## ##
## If you go lower than this, your running Roc code will crash - so be careful not to! ## If you go lower than this, your running Roc code will crash - so be careful not to!
minF32 : F32 minF32 : F32
## The highest supported #Dec value you can have, which is precisely 170_141_183_460_469_231_731.687303715884105727. ## The highest supported #F32 value you can have, which is approximately 1.8 × 10^308.
## ##
## If you go higher than this, your running Roc code will crash - so be careful not to! ## If you go higher than this, your running Roc code will crash - so be careful not to!
maxDec : Dec maxF32 : F32
## The lowest supported #Dec value you can have, which is precisely -170_141_183_460_469_231_731.687303715884105728. ## The lowest supported #F64 value you can have, which is approximately -1.8 × 10^308.
##
## If you go lower than this, your running Roc code will crash - so be careful not to!
minF64 : F64
## The highest supported #F64 value you can have, which is approximately 1.8 × 10^308.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxF64 : F64
## The lowest supported #Dec value you can have,
## which is precisely -170_141_183_460_469_231_731.687303715884105728.
## ##
## If you go lower than this, your running Roc code will crash - so be careful not to! ## If you go lower than this, your running Roc code will crash - so be careful not to!
minDec : Dec minDec : Dec
## The highest supported #Dec value you can have,
## which is precisely 170_141_183_460_469_231_731.687303715884105727.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!
maxDec : Dec
## Constants ## Constants
## An approximation of e, specifically 2.718281828459045. ## An approximation of e, specifically 2.718281828459045.
@ -992,7 +1143,7 @@ shr : Int a, Int a -> Int a
## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right. ## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right.
## ##
## This is called `shlWrap` because any bits shifted ## This is called `shrWrap` because any bits shifted
## off the end of the number will be wrapped around to ## off the end of the number will be wrapped around to
## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.) ## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.)
shrWrap : Int a, Int a -> Int a shrWrap : Int a, Int a -> Int a

View file

@ -1,4 +1,5 @@
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_target::TargetInfo;
use std::ops::Index; use std::ops::Index;
pub const BUILTINS_HOST_OBJ_PATH: &str = env!( pub const BUILTINS_HOST_OBJ_PATH: &str = env!(
@ -46,14 +47,21 @@ impl FloatWidth {
} }
} }
pub const fn alignment_bytes(&self) -> u32 { pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
use roc_target::Architecture;
use std::mem::align_of; use std::mem::align_of;
use FloatWidth::*; use FloatWidth::*;
// TODO actually alignment is architecture-specific // TODO actually alignment is architecture-specific
match self { match self {
F32 => align_of::<f32>() as u32, F32 => align_of::<f32>() as u32,
F64 => align_of::<f64>() as u32, F64 => match target_info.architecture {
Architecture::X86_64
| Architecture::Aarch64
| Architecture::Arm
| Architecture::Wasm32 => 8,
Architecture::X86_32 => 4,
},
F128 => align_of::<i128>() as u32, F128 => align_of::<i128>() as u32,
} }
} }
@ -106,16 +114,22 @@ impl IntWidth {
} }
} }
pub const fn alignment_bytes(&self) -> u32 { pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
use roc_target::Architecture;
use std::mem::align_of; use std::mem::align_of;
use IntWidth::*; use IntWidth::*;
// TODO actually alignment is architecture-specific
match self { match self {
U8 | I8 => align_of::<i8>() as u32, U8 | I8 => align_of::<i8>() as u32,
U16 | I16 => align_of::<i16>() as u32, U16 | I16 => align_of::<i16>() as u32,
U32 | I32 => align_of::<i32>() as u32, U32 | I32 => align_of::<i32>() as u32,
U64 | I64 => align_of::<i64>() as u32, U64 | I64 => match target_info.architecture {
Architecture::X86_64
| Architecture::Aarch64
| Architecture::Arm
| Architecture::Wasm32 => 8,
Architecture::X86_32 => 4,
},
U128 | I128 => align_of::<i128>() as u32, U128 | I128 => align_of::<i128>() as u32,
} }
} }
@ -201,22 +215,28 @@ macro_rules! float_intrinsic {
#[macro_export] #[macro_export]
macro_rules! int_intrinsic { macro_rules! int_intrinsic {
($name:literal) => {{ ($signed_name:literal, $unsigned_name:literal) => {{
let mut output = IntrinsicName::default(); let mut output = IntrinsicName::default();
output.options[4] = concat!($name, ".i8"); // The indeces align with the `Index` impl for `IntrinsicName`.
output.options[5] = concat!($name, ".i16"); output.options[4] = concat!($unsigned_name, ".i8");
output.options[6] = concat!($name, ".i32"); output.options[5] = concat!($unsigned_name, ".i16");
output.options[7] = concat!($name, ".i64"); output.options[6] = concat!($unsigned_name, ".i32");
output.options[8] = concat!($name, ".i128"); output.options[7] = concat!($unsigned_name, ".i64");
output.options[9] = concat!($name, ".i8"); output.options[8] = concat!($unsigned_name, ".i128");
output.options[10] = concat!($name, ".i16");
output.options[11] = concat!($name, ".i32"); output.options[9] = concat!($signed_name, ".i8");
output.options[12] = concat!($name, ".i64"); output.options[10] = concat!($signed_name, ".i16");
output.options[13] = concat!($name, ".i128"); output.options[11] = concat!($signed_name, ".i32");
output.options[12] = concat!($signed_name, ".i64");
output.options[13] = concat!($signed_name, ".i128");
output output
}}; }};
($name:literal) => {
int_intrinsic!($name, $name)
};
} }
pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin"); pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
@ -316,3 +336,6 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
pub const UTILS_INCREF: &str = "roc_builtins.utils.incref"; pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref"; pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null"; pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed";
pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures";
pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures";

View file

@ -141,6 +141,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(int_type(flex(TVAR1))), Box::new(int_type(flex(TVAR1))),
); );
// addSaturated : Num a, Num a -> Num a
add_top_level_function_type!(
Symbol::NUM_ADD_SATURATED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
);
// sub or (-) : Num a, Num a -> Num a // sub or (-) : Num a, Num a -> Num a
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_SUB, Symbol::NUM_SUB,
@ -162,6 +169,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(result_type(num_type(flex(TVAR1)), overflow())), Box::new(result_type(num_type(flex(TVAR1)), overflow())),
); );
// subSaturated : Num a, Num a -> Num a
add_top_level_function_type!(
Symbol::NUM_SUB_SATURATED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
);
// mul or (*) : Num a, Num a -> Num a // mul or (*) : Num a, Num a -> Num a
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_MUL, Symbol::NUM_MUL,
@ -288,12 +302,6 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(bool_type()) Box::new(bool_type())
); );
// maxInt : Int range
add_type!(Symbol::NUM_MAX_INT, int_type(flex(TVAR1)));
// minInt : Int range
add_type!(Symbol::NUM_MIN_INT, int_type(flex(TVAR1)));
let div_by_zero = SolvedType::TagUnion( let div_by_zero = SolvedType::TagUnion(
vec![(TagName::Global("DivByZero".into()), vec![])], vec![(TagName::Global("DivByZero".into()), vec![])],
Box::new(SolvedType::Wildcard), Box::new(SolvedType::Wildcard),
@ -383,6 +391,57 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(bool_type()), Box::new(bool_type()),
); );
// minI8 : I8
add_type!(Symbol::NUM_MIN_I8, i8_type());
// maxI8 : I8
add_type!(Symbol::NUM_MAX_I8, i8_type());
// minU8 : U8
add_type!(Symbol::NUM_MIN_U8, u8_type());
// maxU8 : U8
add_type!(Symbol::NUM_MAX_U8, u8_type());
// minI16 : I16
add_type!(Symbol::NUM_MIN_I16, i16_type());
// maxI16 : I16
add_type!(Symbol::NUM_MAX_I16, i16_type());
// minU16 : U16
add_type!(Symbol::NUM_MIN_U16, u16_type());
// maxU16 : U16
add_type!(Symbol::NUM_MAX_U16, u16_type());
// minI32 : I32
add_type!(Symbol::NUM_MIN_I32, i32_type());
// maxI32 : I32
add_type!(Symbol::NUM_MAX_I32, i32_type());
// minU32 : U32
add_type!(Symbol::NUM_MIN_U32, u32_type());
// maxU32 : U32
add_type!(Symbol::NUM_MAX_U32, u32_type());
// minI64 : I64
add_type!(Symbol::NUM_MIN_I64, i64_type());
// maxI64 : I64
add_type!(Symbol::NUM_MAX_I64, i64_type());
// minU64 : U64
add_type!(Symbol::NUM_MIN_U64, u64_type());
// maxU64 : U64
add_type!(Symbol::NUM_MAX_U64, u64_type());
// minI128 : I128
add_type!(Symbol::NUM_MIN_I128, i128_type());
// maxI128 : I128 // maxI128 : I128
add_type!(Symbol::NUM_MAX_I128, i128_type()); add_type!(Symbol::NUM_MAX_I128, i128_type());
@ -1267,6 +1326,20 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))), Box::new(list_type(flex(TVAR1))),
); );
// sortAsc : List (Num a) -> List (Num a)
add_top_level_function_type!(
Symbol::LIST_SORT_ASC,
vec![list_type(num_type(flex(TVAR1)))],
Box::new(list_type(num_type(flex(TVAR1))))
);
// sortDesc : List (Num a) -> List (Num a)
add_top_level_function_type!(
Symbol::LIST_SORT_DESC,
vec![list_type(num_type(flex(TVAR1)))],
Box::new(list_type(num_type(flex(TVAR1))))
);
// find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* // find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
{ {
let not_found = SolvedType::TagUnion( let not_found = SolvedType::TagUnion(

View file

@ -357,7 +357,7 @@ fn can_annotation_help(
actual: Box::new(actual), actual: Box::new(actual),
} }
} }
None => Type::Apply(symbol, args), None => Type::Apply(symbol, args, region),
} }
} }
BoundVariable(v) => { BoundVariable(v) => {
@ -377,7 +377,8 @@ fn can_annotation_help(
As( As(
loc_inner, loc_inner,
_spaces, _spaces,
AliasHeader { alias_header
@ AliasHeader {
name, name,
vars: loc_vars, vars: loc_vars,
}, },
@ -390,7 +391,7 @@ fn can_annotation_help(
) { ) {
Ok(symbol) => symbol, Ok(symbol) => symbol,
Err((original_region, shadow)) => { Err((original_region, shadow, _new_symbol)) => {
let problem = Problem::Shadowed(original_region, shadow.clone()); let problem = Problem::Shadowed(original_region, shadow.clone());
env.problem(roc_problem::can::Problem::ShadowingInAnnotation { env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
@ -439,20 +440,43 @@ fn can_annotation_help(
} }
} }
let alias_args = vars.iter().map(|(_, v)| v.clone()).collect::<Vec<_>>();
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type { let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
let rec_var = var_store.fresh(); let rec_var = var_store.fresh();
let mut new_tags = Vec::with_capacity(tags.len()); let mut new_tags = Vec::with_capacity(tags.len());
let mut is_nested_datatype = false;
for (tag_name, args) in tags { for (tag_name, args) in tags {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for arg in args { for arg in args {
let mut new_arg = arg.clone(); let mut new_arg = arg.clone();
new_arg.substitute_alias(symbol, &Type::Variable(rec_var)); let substitution_result =
new_arg.substitute_alias(symbol, &alias_args, &Type::Variable(rec_var));
if let Err(differing_recursion_region) = substitution_result {
env.problems
.push(roc_problem::can::Problem::NestedDatatype {
alias: symbol,
def_region: alias_header.region(),
differing_recursion_region,
});
is_nested_datatype = true;
}
// Either way, add the argument; not doing so would only result in more
// confusing error messages later on.
new_args.push(new_arg); new_args.push(new_arg);
} }
new_tags.push((tag_name.clone(), new_args)); new_tags.push((tag_name.clone(), new_args));
} }
if is_nested_datatype {
// We don't have a way to represent nested data types; hence, we don't actually
// use the recursion var in them, and should avoid marking them as such.
Type::TagUnion(new_tags, ext)
} else {
Type::RecursiveTagUnion(rec_var, new_tags, ext) Type::RecursiveTagUnion(rec_var, new_tags, ext)
}
} else { } else {
inner_type inner_type
}; };

View file

@ -1,6 +1,7 @@
use crate::def::Def; use crate::def::Def;
use crate::expr::{self, ClosureData, Expr::*}; use crate::expr::{self, ClosureData, Expr::*, IntValue};
use crate::expr::{Expr, Field, Recursive}; use crate::expr::{Expr, Field, Recursive};
use crate::num::{FloatBound, IntBound, IntWidth, NumericBound};
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia; use roc_module::called_via::CalledVia;
@ -44,6 +45,23 @@ macro_rules! macro_magic {
/// delegates to the compiler-internal List.getUnsafe function to do the actual /// delegates to the compiler-internal List.getUnsafe function to do the actual
/// lookup (if the bounds check passed). That internal function is hardcoded in code gen, /// lookup (if the bounds check passed). That internal function is hardcoded in code gen,
/// which works fine because it doesn't involve any open tag unions. /// which works fine because it doesn't involve any open tag unions.
/// Does a builtin depend on any other builtins?
///
/// NOTE: you are supposed to give all symbols that are relied on,
/// even those that are relied on transitively!
pub fn builtin_dependencies(symbol: Symbol) -> &'static [Symbol] {
match symbol {
Symbol::LIST_SORT_ASC => &[Symbol::LIST_SORT_WITH, Symbol::NUM_COMPARE],
Symbol::LIST_SORT_DESC => &[Symbol::LIST_SORT_WITH],
Symbol::LIST_PRODUCT => &[Symbol::LIST_WALK, Symbol::NUM_MUL],
Symbol::LIST_SUM => &[Symbol::LIST_WALK, Symbol::NUM_ADD],
Symbol::LIST_JOIN_MAP => &[Symbol::LIST_WALK, Symbol::LIST_CONCAT],
_ => &[],
}
}
/// Implementation for a builtin
pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> { pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def> {
debug_assert!(symbol.is_builtin()); debug_assert!(symbol.is_builtin());
@ -125,6 +143,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_WALK_BACKWARDS => list_walk_backwards, LIST_WALK_BACKWARDS => list_walk_backwards,
LIST_WALK_UNTIL => list_walk_until, LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with, LIST_SORT_WITH => list_sort_with,
LIST_SORT_ASC => list_sort_asc,
LIST_SORT_DESC => list_sort_desc,
LIST_ANY => list_any, LIST_ANY => list_any,
LIST_ALL => list_all, LIST_ALL => list_all,
LIST_FIND => list_find, LIST_FIND => list_find,
@ -156,9 +176,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ADD => num_add, NUM_ADD => num_add,
NUM_ADD_CHECKED => num_add_checked, NUM_ADD_CHECKED => num_add_checked,
NUM_ADD_WRAP => num_add_wrap, NUM_ADD_WRAP => num_add_wrap,
NUM_ADD_SATURATED => num_add_saturated,
NUM_SUB => num_sub, NUM_SUB => num_sub,
NUM_SUB_WRAP => num_sub_wrap, NUM_SUB_WRAP => num_sub_wrap,
NUM_SUB_CHECKED => num_sub_checked, NUM_SUB_CHECKED => num_sub_checked,
NUM_SUB_SATURATED => num_sub_saturated,
NUM_MUL => num_mul, NUM_MUL => num_mul,
NUM_MUL_WRAP => num_mul_wrap, NUM_MUL_WRAP => num_mul_wrap,
NUM_MUL_CHECKED => num_mul_checked, NUM_MUL_CHECKED => num_mul_checked,
@ -195,8 +217,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ASIN => num_asin, NUM_ASIN => num_asin,
NUM_BYTES_TO_U16 => num_bytes_to_u16, NUM_BYTES_TO_U16 => num_bytes_to_u16,
NUM_BYTES_TO_U32 => num_bytes_to_u32, NUM_BYTES_TO_U32 => num_bytes_to_u32,
NUM_MAX_INT => num_max_int,
NUM_MIN_INT => num_min_int,
NUM_BITWISE_AND => num_bitwise_and, NUM_BITWISE_AND => num_bitwise_and,
NUM_BITWISE_XOR => num_bitwise_xor, NUM_BITWISE_XOR => num_bitwise_xor,
NUM_BITWISE_OR => num_bitwise_or, NUM_BITWISE_OR => num_bitwise_or,
@ -204,6 +224,23 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_SHIFT_RIGHT => num_shift_right_by, NUM_SHIFT_RIGHT => num_shift_right_by,
NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by, NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by,
NUM_INT_CAST=> num_int_cast, NUM_INT_CAST=> num_int_cast,
NUM_MIN_I8=> num_min_i8,
NUM_MAX_I8=> num_max_i8,
NUM_MIN_U8=> num_min_u8,
NUM_MAX_U8=> num_max_u8,
NUM_MIN_I16=> num_min_i16,
NUM_MAX_I16=> num_max_i16,
NUM_MIN_U16=> num_min_u16,
NUM_MAX_U16=> num_max_u16,
NUM_MIN_I32=> num_min_i32,
NUM_MAX_I32=> num_max_i32,
NUM_MIN_U32=> num_min_u32,
NUM_MAX_U32=> num_max_u32,
NUM_MIN_I64=> num_min_i64,
NUM_MAX_I64=> num_max_i64,
NUM_MIN_U64=> num_min_u64,
NUM_MAX_U64=> num_max_u64,
NUM_MIN_I128=> num_min_i128,
NUM_MAX_I128=> num_max_i128, NUM_MAX_I128=> num_max_i128,
NUM_TO_STR => num_to_str, NUM_TO_STR => num_to_str,
RESULT_MAP => result_map, RESULT_MAP => result_map,
@ -353,36 +390,6 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
) )
} }
/// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int(int_var, int_precision_var, i64::MAX.into());
Def {
annotation: None,
expr_var: int_var,
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
/// Num.minInt : Int
fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int(int_var, int_precision_var, i64::MIN.into());
Def {
annotation: None,
expr_var: int_var,
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
// Num.toStr : Num a -> Str // Num.toStr : Num a -> Str
fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh(); let num_var = var_store.fresh();
@ -641,6 +648,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_overflow_checked(symbol, var_store, LowLevel::NumAddChecked) num_overflow_checked(symbol, var_store, LowLevel::NumAddChecked)
} }
/// Num.addSaturated : Int a, Int a -> Int a
fn num_add_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumAddSaturated)
}
/// Num.sub : Num a, Num a -> Num a /// Num.sub : Num a, Num a -> Num a
fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSub) num_binop(symbol, var_store, LowLevel::NumSub)
@ -656,6 +668,11 @@ fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked) num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked)
} }
/// Num.subSaturated : Int a, Int a -> Int a
fn num_sub_saturated(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSubSaturated)
}
/// Num.mul : Num a, Num a -> Num a /// Num.mul : Num a, Num a -> Num a
fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumMul) num_binop(symbol, var_store, LowLevel::NumMul)
@ -777,7 +794,7 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
(arg_var, num(unbound_zero_var, 0)), (arg_var, num(unbound_zero_var, 0, num_no_bound())),
], ],
ret_var: bool_var, ret_var: bool_var,
}; };
@ -800,7 +817,7 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ args: vec![
(arg_var, num(unbound_zero_var, 0)), (arg_var, num(unbound_zero_var, 0, num_no_bound())),
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
], ],
ret_var: bool_var, ret_var: bool_var,
@ -825,7 +842,7 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
(arg_var, num(unbound_zero_var, 0)), (arg_var, num(unbound_zero_var, 0, num_no_bound())),
], ],
ret_var: bool_var, ret_var: bool_var,
}; };
@ -848,14 +865,17 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, int(var_store.fresh(), var_store.fresh(), 1)), (
arg_var,
int::<i128>(var_store.fresh(), var_store.fresh(), 1, int_no_bound()),
),
( (
arg_var, arg_var,
RunLowLevel { RunLowLevel {
op: LowLevel::NumRemUnchecked, op: LowLevel::NumRemUnchecked,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
(arg_var, num(unbound_two_var, 2)), (arg_var, num(unbound_two_var, 2, num_no_bound())),
], ],
ret_var: arg_var, ret_var: arg_var,
}, },
@ -882,14 +902,14 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(arg_var, num(arg_num_var, 0)), (arg_var, num(arg_num_var, 0, num_no_bound())),
( (
arg_var, arg_var,
RunLowLevel { RunLowLevel {
op: LowLevel::NumRemUnchecked, op: LowLevel::NumRemUnchecked,
args: vec![ args: vec![
(arg_var, Var(Symbol::ARG_1)), (arg_var, Var(Symbol::ARG_1)),
(arg_var, num(arg_num_var, 2)), (arg_var, num(arg_num_var, 2, num_no_bound())),
], ],
ret_var: arg_var, ret_var: arg_var,
}, },
@ -943,7 +963,10 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGte, op: LowLevel::NumGte,
args: vec![ args: vec![
(float_var, Var(Symbol::ARG_1)), (float_var, Var(Symbol::ARG_1)),
(float_var, float(unbound_zero_var, precision_var, 0.0)), (
float_var,
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
),
], ],
ret_var: bool_var, ret_var: bool_var,
}), }),
@ -989,7 +1012,10 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt, op: LowLevel::NumGt,
args: vec![ args: vec![
(float_var, Var(Symbol::ARG_1)), (float_var, Var(Symbol::ARG_1)),
(float_var, float(unbound_zero_var, precision_var, 0.0)), (
float_var,
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
),
], ],
ret_var: bool_var, ret_var: bool_var,
}), }),
@ -1225,31 +1251,104 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::NumIntCast, var_store) lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
} }
/// Num.minI8: I8
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i8>(symbol, var_store, i8::MIN, IntBound::Exact(IntWidth::I8))
}
/// Num.maxI8: I8
fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i8>(symbol, var_store, i8::MAX, IntBound::Exact(IntWidth::I8))
}
/// Num.minU8: U8
fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u8>(symbol, var_store, u8::MIN, IntBound::Exact(IntWidth::U8))
}
/// Num.maxU8: U8
fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u8>(symbol, var_store, u8::MAX, IntBound::Exact(IntWidth::U8))
}
/// Num.minI16: I16
fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i16>(symbol, var_store, i16::MIN, IntBound::Exact(IntWidth::I16))
}
/// Num.maxI16: I16
fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i16>(symbol, var_store, i16::MAX, IntBound::Exact(IntWidth::I16))
}
/// Num.minU16: U16
fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u16>(symbol, var_store, u16::MIN, IntBound::Exact(IntWidth::U16))
}
/// Num.maxU16: U16
fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u16>(symbol, var_store, u16::MAX, IntBound::Exact(IntWidth::U16))
}
/// Num.minI32: I32
fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i32>(symbol, var_store, i32::MIN, IntBound::Exact(IntWidth::I32))
}
/// Num.maxI32: I32
fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i32>(symbol, var_store, i32::MAX, IntBound::Exact(IntWidth::I32))
}
/// Num.minU32: U32
fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u32>(symbol, var_store, u32::MIN, IntBound::Exact(IntWidth::U32))
}
/// Num.maxU32: U32
fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u32>(symbol, var_store, u32::MAX, IntBound::Exact(IntWidth::U32))
}
/// Num.minI64: I64
fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i64>(symbol, var_store, i64::MIN, IntBound::Exact(IntWidth::I64))
}
/// Num.maxI64: I64
fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i64>(symbol, var_store, i64::MAX, IntBound::Exact(IntWidth::I64))
}
/// Num.minU64: U64
fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u64>(symbol, var_store, u64::MIN, IntBound::Exact(IntWidth::U64))
}
/// Num.maxU64: U64
fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u64>(symbol, var_store, u64::MAX, IntBound::Exact(IntWidth::U64))
}
/// Num.minI128: I128
fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i128>(
symbol,
var_store,
i128::MIN,
IntBound::Exact(IntWidth::I128),
)
}
/// Num.maxI128: I128 /// Num.maxI128: I128
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh(); int_min_or_max::<i128>(
let int_precision_var = var_store.fresh(); symbol,
let body = int(int_var, int_precision_var, i128::MAX); var_store,
i128::MAX,
let std = roc_builtins::std::types(); IntBound::Exact(IntWidth::I128),
let solved = std.get(&symbol).unwrap(); )
let mut free_vars = roc_types::solved_types::FreeVars::default();
let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store);
let annotation = crate::def::Annotation {
signature,
introduced_variables: Default::default(),
region: Region::zero(),
aliases: Default::default(),
};
Def {
annotation: Some(annotation),
expr_var: int_var,
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
} }
/// List.isEmpty : List * -> Bool /// List.isEmpty : List * -> Bool
@ -1262,7 +1361,7 @@ fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::Eq, op: LowLevel::Eq,
args: vec![ args: vec![
(len_var, num(unbound_zero_var, 0)), (len_var, num(unbound_zero_var, 0, num_no_bound())),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -1374,7 +1473,15 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))), loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
}, },
), ),
(errorcode_var, int(errorcode_var, Variable::UNSIGNED8, 0)), (
errorcode_var,
int::<i128>(
errorcode_var,
Variable::UNSIGNED8,
0,
IntBound::Exact(IntWidth::U8),
),
),
], ],
ret_var: bool_var, ret_var: bool_var,
}), }),
@ -2116,7 +2223,12 @@ fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let zero = int(len_var, Variable::NATURAL, 0); let zero = int::<i128>(
len_var,
Variable::NATURAL,
0,
IntBound::Exact(IntWidth::Nat),
);
let body = RunLowLevel { let body = RunLowLevel {
op: LowLevel::ListSublist, op: LowLevel::ListSublist,
@ -2142,7 +2254,12 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let len_var = var_store.fresh(); let len_var = var_store.fresh();
let zero = int(len_var, Variable::NATURAL, 0); let zero = int::<i128>(
len_var,
Variable::NATURAL,
0,
IntBound::Exact(IntWidth::Nat),
);
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let get_list_len = RunLowLevel { let get_list_len = RunLowLevel {
@ -2252,7 +2369,12 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
let clos_elem_sym = Symbol::ARG_4; let clos_elem_sym = Symbol::ARG_4;
let int_var = var_store.fresh(); let int_var = var_store.fresh();
let zero = int(int_var, Variable::NATURAL, 0); let zero = int::<i128>(
int_var,
Variable::NATURAL,
0,
IntBound::Exact(IntWidth::Nat),
);
// \acc, elem -> acc |> List.append sep |> List.append elem // \acc, elem -> acc |> List.append sep |> List.append elem
let clos = Closure(ClosureData { let clos = Closure(ClosureData {
@ -2332,7 +2454,12 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
let clos_ret_var = var_store.fresh(); let clos_ret_var = var_store.fresh();
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let zero = int(index_var, Variable::NATURAL, 0); let zero = int::<i128>(
index_var,
Variable::NATURAL,
0,
IntBound::Exact(IntWidth::Nat),
);
let clos = Closure(ClosureData { let clos = Closure(ClosureData {
function_type: clos_fun_var, function_type: clos_fun_var,
@ -2494,7 +2621,10 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListDropAt, op: LowLevel::ListDropAt,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(index_var, int(num_var, num_precision_var, 0)), (
index_var,
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
),
], ],
ret_var: list_var, ret_var: list_var,
}; };
@ -2591,7 +2721,10 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var, ret_var: len_var,
}, },
), ),
(arg_var, int(num_var, num_precision_var, 1)), (
arg_var,
int::<i128>(num_var, num_precision_var, 1, int_no_bound()),
),
], ],
ret_var: len_var, ret_var: len_var,
}, },
@ -2789,7 +2922,10 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, int(num_var, num_precision_var, 0)), (
len_var,
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2820,7 +2956,15 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(arg_var, int(num_var, num_precision_var, 0)), (
arg_var,
int::<i128>(
num_var,
num_precision_var,
0,
int_no_bound(),
),
),
], ],
ret_var: list_elem_var, ret_var: list_elem_var,
}, },
@ -2919,7 +3063,10 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, int(num_var, num_precision_var, 0)), (
len_var,
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -2950,7 +3097,15 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(arg_var, int(num_var, num_precision_var, 0)), (
arg_var,
int::<i128>(
num_var,
num_precision_var,
0,
int_no_bound(),
),
),
], ],
ret_var: list_elem_var, ret_var: list_elem_var,
}, },
@ -3033,15 +3188,25 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
let body = RunLowLevel { let function = (
op: LowLevel::ListWalk, var_store.fresh(),
args: vec![ Loc::at_zero(Expr::Var(Symbol::LIST_WALK)),
(list_var, Var(Symbol::ARG_1)), var_store.fresh(),
(num_var, num(var_store.fresh(), 0)),
(closure_var, list_sum_add(num_var, var_store)),
],
ret_var, ret_var,
}; );
let body = Expr::Call(
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(
num_var,
Loc::at_zero(num(var_store.fresh(), 0, num_no_bound())),
),
(closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))),
],
CalledVia::Space,
);
defn( defn(
symbol, symbol,
@ -3052,60 +3217,37 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
fn list_sum_add(num_var: Variable, var_store: &mut VarStore) -> Expr {
let body = RunLowLevel {
op: LowLevel::NumAdd,
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
ret_var: num_var,
};
defn_help(
Symbol::LIST_SUM_ADD,
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
var_store,
body,
num_var,
)
}
/// List.product : List (Num a) -> Num a /// List.product : List (Num a) -> Num a
fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let ret_var = num_var;
let list_var = var_store.fresh(); let list_var = var_store.fresh();
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
let body = RunLowLevel { let function = (
op: LowLevel::ListWalk, var_store.fresh(),
args: vec![ Loc::at_zero(Expr::Var(Symbol::LIST_WALK)),
(list_var, Var(Symbol::ARG_1)), var_store.fresh(),
(num_var, num(var_store.fresh(), 1)), num_var,
(closure_var, list_product_mul(num_var, var_store)), );
let body = Expr::Call(
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(
num_var,
Loc::at_zero(num(var_store.fresh(), 1, num_no_bound())),
),
(closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))),
], ],
ret_var, CalledVia::Space,
}; );
defn( defn(
symbol, symbol,
vec![(list_var, Symbol::ARG_1)], vec![(list_var, Symbol::ARG_1)],
var_store, var_store,
body, body,
ret_var,
)
}
fn list_product_mul(num_var: Variable, var_store: &mut VarStore) -> Expr {
let body = RunLowLevel {
op: LowLevel::NumMul,
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_4))],
ret_var: num_var,
};
defn_help(
Symbol::LIST_PRODUCT_MUL,
vec![(num_var, Symbol::ARG_3), (num_var, Symbol::ARG_4)],
var_store,
body,
num_var, num_var,
) )
} }
@ -3183,6 +3325,91 @@ fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListSortWith, var_store) lowlevel_2(symbol, LowLevel::ListSortWith, var_store)
} }
/// List.sortAsc : List (Num a) -> List (Num a)
fn list_sort_asc(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let closure_var = var_store.fresh();
let ret_var = list_var;
let function = (
var_store.fresh(),
Loc::at_zero(Expr::Var(Symbol::LIST_SORT_WITH)),
var_store.fresh(),
ret_var,
);
let body = Expr::Call(
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(closure_var, Loc::at_zero(Var(Symbol::NUM_COMPARE))),
],
CalledVia::Space,
);
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
/// List.sortDesc : List (Num a) -> List (Num a)
fn list_sort_desc(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let num_var = var_store.fresh();
let closure_var = var_store.fresh();
let compare_ret_var = var_store.fresh();
let ret_var = list_var;
let closure = Closure(ClosureData {
function_type: closure_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: compare_ret_var,
name: Symbol::LIST_SORT_DESC_COMPARE,
recursive: Recursive::NotRecursive,
captured_symbols: vec![],
arguments: vec![
(num_var, no_region(Pattern::Identifier(Symbol::ARG_2))),
(num_var, no_region(Pattern::Identifier(Symbol::ARG_3))),
],
loc_body: {
Box::new(no_region(RunLowLevel {
op: LowLevel::NumCompare,
args: vec![(num_var, Var(Symbol::ARG_3)), (num_var, Var(Symbol::ARG_2))],
ret_var: compare_ret_var,
}))
},
});
let function = (
var_store.fresh(),
Loc::at_zero(Expr::Var(Symbol::LIST_SORT_WITH)),
var_store.fresh(),
ret_var,
);
let body = Expr::Call(
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(closure_var, Loc::at_zero(closure)),
],
CalledVia::Space,
);
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
/// List.any: List elem, (elem -> Bool) -> Bool /// List.any: List elem, (elem -> Bool) -> Bool
fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListAny, var_store) lowlevel_2(symbol, LowLevel::ListAny, var_store)
@ -3664,7 +3891,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
(num_var, num(unbound_zero_var, 0)), (num_var, num(unbound_zero_var, 0, num_no_bound())),
], ],
ret_var: bool_var, ret_var: bool_var,
}, },
@ -3767,7 +3994,10 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
(num_var, float(unbound_zero_var, precision_var, 0.0)), (
num_var,
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
),
], ],
ret_var: bool_var, ret_var: bool_var,
}, },
@ -3832,7 +4062,12 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
( (
num_var, num_var,
int(unbound_zero_var, unbound_zero_precision_var, 0), int::<i128>(
unbound_zero_var,
unbound_zero_precision_var,
0,
int_no_bound(),
),
), ),
], ],
ret_var: bool_var, ret_var: bool_var,
@ -3898,7 +4133,12 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)), (num_var, Var(Symbol::ARG_2)),
( (
num_var, num_var,
int(unbound_zero_var, unbound_zero_precision_var, 0), int::<i128>(
unbound_zero_var,
unbound_zero_precision_var,
0,
int_no_bound(),
),
), ),
], ],
ret_var: bool_var, ret_var: bool_var,
@ -3968,7 +4208,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, int(zero_var, zero_precision_var, 0)), (
len_var,
int::<i128>(zero_var, zero_precision_var, 0, int_no_bound()),
),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -3992,7 +4235,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe, op: LowLevel::ListGetUnsafe,
args: vec![ args: vec![
(list_var, Var(Symbol::ARG_1)), (list_var, Var(Symbol::ARG_1)),
(len_var, int(zero_var, zero_precision_var, 0)), (
len_var,
int::<i128>(zero_var, zero_precision_var, 0, int_no_bound()),
),
], ],
ret_var: list_elem_var, ret_var: list_elem_var,
}, },
@ -4049,7 +4295,10 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel { RunLowLevel {
op: LowLevel::NotEq, op: LowLevel::NotEq,
args: vec![ args: vec![
(len_var, int(num_var, num_precision_var, 0)), (
len_var,
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
),
( (
len_var, len_var,
RunLowLevel { RunLowLevel {
@ -4088,7 +4337,15 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var, ret_var: len_var,
}, },
), ),
(arg_var, int(num_var, num_precision_var, 1)), (
arg_var,
int::<i128>(
num_var,
num_precision_var,
1,
int_no_bound(),
),
),
], ],
ret_var: len_var, ret_var: len_var,
}, },
@ -4716,7 +4973,10 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level
add_var, add_var,
RunLowLevel { RunLowLevel {
ret_var: cast_var, ret_var: cast_var,
args: vec![(cast_var, num(var_store.fresh(), offset))], args: vec![(
cast_var,
num(var_store.fresh(), offset, num_no_bound()),
)],
op: LowLevel::NumIntCast, op: LowLevel::NumIntCast,
}, },
), ),
@ -4804,16 +5064,80 @@ fn defn_help(
} }
#[inline(always)] #[inline(always)]
fn int(num_var: Variable, precision_var: Variable, i: i128) -> Expr { fn int_min_or_max<I128>(symbol: Symbol, var_store: &mut VarStore, i: I128, bound: IntBound) -> Def
Int(num_var, precision_var, i.to_string().into_boxed_str(), i) where
I128: Into<i128>,
{
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int::<I128>(int_var, int_precision_var, i, bound);
let std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap();
let mut free_vars = roc_types::solved_types::FreeVars::default();
let signature = roc_types::solved_types::to_type(&solved.0, &mut free_vars, var_store);
let annotation = crate::def::Annotation {
signature,
introduced_variables: Default::default(),
region: Region::zero(),
aliases: Default::default(),
};
Def {
annotation: Some(annotation),
expr_var: int_var,
loc_expr: Loc::at_zero(body),
loc_pattern: Loc::at_zero(Pattern::Identifier(symbol)),
pattern_vars: SendMap::default(),
}
}
fn num_no_bound() -> NumericBound {
NumericBound::None
}
fn int_no_bound() -> IntBound {
IntBound::None
}
fn float_no_bound() -> FloatBound {
FloatBound::None
} }
#[inline(always)] #[inline(always)]
fn float(num_var: Variable, precision_var: Variable, f: f64) -> Expr { fn int<I128>(num_var: Variable, precision_var: Variable, i: I128, bound: IntBound) -> Expr
Float(num_var, precision_var, f.to_string().into_boxed_str(), f) where
I128: Into<i128>,
{
let ii = i.into();
Int(
num_var,
precision_var,
ii.to_string().into_boxed_str(),
IntValue::I128(ii),
bound,
)
} }
#[inline(always)] #[inline(always)]
fn num(num_var: Variable, i: i64) -> Expr { fn float(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -> Expr {
Num(num_var, i.to_string().into_boxed_str(), i) Float(
num_var,
precision_var,
f.to_string().into_boxed_str(),
f,
bound,
)
}
#[inline(always)]
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound) -> Expr {
let i = i.into();
Num(
num_var,
i.to_string().into_boxed_str(),
IntValue::I128(i),
bound,
)
} }

View file

@ -277,7 +277,7 @@ pub fn canonicalize_defs<'a>(
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len()); let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false; let mut is_phantom = false;
for loc_lowercase in vars { for loc_lowercase in vars.iter() {
if let Some(var) = can_ann if let Some(var) = can_ann
.introduced_variables .introduced_variables
.var_by_name(&loc_lowercase.value) .var_by_name(&loc_lowercase.value)
@ -303,23 +303,42 @@ pub fn canonicalize_defs<'a>(
continue; continue;
} }
let mut is_nested_datatype = false;
if can_ann.typ.contains_symbol(symbol) { if can_ann.typ.contains_symbol(symbol) {
make_tag_union_recursive( let alias_args = can_vars
.iter()
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
.collect::<Vec<_>>();
let alias_region =
Region::across_all([name.region].iter().chain(vars.iter().map(|l| &l.region)));
let made_recursive = make_tag_union_recursive(
env, env,
symbol, Loc::at(alias_region, (symbol, &alias_args)),
name.region, name.region,
vec![], vec![],
&mut can_ann.typ, &mut can_ann.typ,
var_store, var_store,
// Don't report any errors yet. We'll take care of self and mutual
// recursion errors after the sorted introductions are complete.
&mut false, &mut false,
); );
is_nested_datatype = made_recursive.is_err();
} }
scope.add_alias(symbol, ann.region, can_vars.clone(), can_ann.typ.clone()); if is_nested_datatype {
// Bail out
continue;
}
scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
let alias = scope.lookup_alias(symbol).expect("alias is added to scope"); let alias = scope.lookup_alias(symbol).expect("alias is added to scope");
aliases.insert(symbol, alias.clone()); aliases.insert(symbol, alias.clone());
} }
// Now that we know the alias dependency graph, we can try to insert recursion variables
// where aliases are recursive tag unions, or detect illegal recursions.
correct_mutual_recursive_type_alias(env, &mut aliases, var_store); correct_mutual_recursive_type_alias(env, &mut aliases, var_store);
// Now that we have the scope completely assembled, and shadowing resolved, // Now that we have the scope completely assembled, and shadowing resolved,
@ -805,7 +824,7 @@ fn pattern_to_vars_by_symbol(
) { ) {
use Pattern::*; use Pattern::*;
match pattern { match pattern {
Identifier(symbol) => { Identifier(symbol) | Shadowed(_, _, symbol) => {
vars_by_symbol.insert(*symbol, expr_var); vars_by_symbol.insert(*symbol, expr_var);
} }
@ -821,15 +840,13 @@ fn pattern_to_vars_by_symbol(
} }
} }
NumLiteral(_, _, _) NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) => {} | UnsupportedPattern(_) => {}
Shadowed(_, _) => {}
} }
} }
@ -880,7 +897,7 @@ fn canonicalize_pending_def<'a>(
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed { Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
def_symbol: *symbol, def_symbol: *symbol,
}, },
Pattern::Shadowed(region, loc_ident) => RuntimeError::Shadowing { Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
original_region: *region, original_region: *region,
shadow: loc_ident.clone(), shadow: loc_ident.clone(),
}, },
@ -962,66 +979,7 @@ fn canonicalize_pending_def<'a>(
} }
} }
Alias { Alias { .. } => unreachable!("Aliases are handled in a separate pass"),
name, ann, vars, ..
} => {
let symbol = name.value;
let can_ann = canonicalize_annotation(env, scope, &ann.value, ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
for loc_lowercase in vars {
if let Some(var) = can_ann
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
});
} else {
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
if can_ann.typ.contains_symbol(symbol) {
// the alias is recursive. If it's a tag union, we attempt to fix this
if let Type::TagUnion(tags, ext) = can_ann.typ {
// re-canonicalize the alias with the alias already in scope
let rec_var = var_store.fresh();
let mut rec_type_union = Type::RecursiveTagUnion(rec_var, tags, ext);
rec_type_union.substitute_alias(symbol, &Type::Variable(rec_var));
scope.add_alias(symbol, name.region, can_vars, rec_type_union);
} else {
env.problems
.push(Problem::CyclicAlias(symbol, name.region, vec![]));
return output;
}
}
let alias = scope.lookup_alias(symbol).expect("alias was not added");
aliases.insert(symbol, alias.clone());
output
.introduced_variables
.union(&can_ann.introduced_variables);
}
InvalidAlias => { InvalidAlias => {
// invalid aliases (shadowed, incorrect patterns) get ignored // invalid aliases (shadowed, incorrect patterns) get ignored
} }
@ -1557,7 +1515,7 @@ fn to_pending_def<'a>(
)) ))
} }
Err((original_region, loc_shadowed_symbol)) => { Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
env.problem(Problem::ShadowingInAnnotation { env.problem(Problem::ShadowingInAnnotation {
original_region, original_region,
shadow: loc_shadowed_symbol, shadow: loc_shadowed_symbol,
@ -1681,9 +1639,16 @@ fn correct_mutual_recursive_type_alias<'a>(
var_store, var_store,
&mut ImSet::default(), &mut ImSet::default(),
); );
make_tag_union_recursive(
let alias_args = &alias
.type_variables
.iter()
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
.collect::<Vec<_>>();
let _made_recursive = make_tag_union_recursive(
env, env,
*rec, Loc::at(alias.header_region(), (*rec, alias_args)),
alias.region, alias.region,
others, others,
&mut alias.typ, &mut alias.typ,
@ -1697,25 +1662,71 @@ fn correct_mutual_recursive_type_alias<'a>(
} }
} }
/// Attempt to make a tag union recursive at the position of `recursive_alias`; for example,
///
/// ```roc
/// [ Cons a (ConsList a), Nil ] as ConsList a
/// ```
///
/// can be made recursive at the position "ConsList a" with a fresh recursive variable, say r1:
///
/// ```roc
/// [ Cons a r1, Nil ] as r1
/// ```
///
/// Returns `Err` if the tag union is recursive, but there is no structure-preserving recursion
/// variable for it. This can happen when the type is a nested datatype, for example in either of
///
/// ```roc
/// Nested a : [ Chain a (Nested (List a)), Term ]
/// DuoList a b : [ Cons a (DuoList b a), Nil ]
/// ```
///
/// When `Err` is returned, a problem will be added to `env`.
fn make_tag_union_recursive<'a>( fn make_tag_union_recursive<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
symbol: Symbol, recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>,
region: Region, region: Region,
others: Vec<Symbol>, others: Vec<Symbol>,
typ: &mut Type, typ: &mut Type,
var_store: &mut VarStore, var_store: &mut VarStore,
can_report_error: &mut bool, can_report_error: &mut bool,
) { ) -> Result<(), ()> {
let Loc {
value: (symbol, args),
region: alias_region,
} = recursive_alias;
let vars = args.iter().map(|(_, t)| t.clone()).collect::<Vec<_>>();
match typ { match typ {
Type::TagUnion(tags, ext) => { Type::TagUnion(tags, ext) => {
let rec_var = var_store.fresh(); let rec_var = var_store.fresh();
*typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); let mut pending_typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
typ.substitute_alias(symbol, &Type::Variable(rec_var)); let substitution_result =
pending_typ.substitute_alias(symbol, &vars, &Type::Variable(rec_var));
match substitution_result {
Ok(()) => {
// We can substitute the alias presence for the variable exactly.
*typ = pending_typ;
Ok(())
} }
Type::RecursiveTagUnion(_, _, _) => {} Err(differing_recursion_region) => {
Type::Alias { actual, .. } => make_tag_union_recursive( env.problems.push(Problem::NestedDatatype {
alias: symbol,
def_region: alias_region,
differing_recursion_region,
});
Err(())
}
}
}
Type::RecursiveTagUnion(_, _, _) => Ok(()),
Type::Alias {
actual,
type_arguments,
..
} => make_tag_union_recursive(
env, env,
symbol, Loc::at_zero((symbol, type_arguments)),
region, region,
others, others,
actual, actual,
@ -1733,6 +1744,7 @@ fn make_tag_union_recursive<'a>(
let problem = Problem::CyclicAlias(symbol, region, others); let problem = Problem::CyclicAlias(symbol, region, others);
env.problems.push(problem); env.problems.push(problem);
} }
Ok(())
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@ use crate::builtins::builtin_defs_map;
use crate::def::{can_defs_with_return, Def}; use crate::def::{can_defs_with_return, Def};
use crate::env::Env; use crate::env::Env;
use crate::num::{ use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result, finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
int_expr_from_result, num_expr_from_result, int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound,
}; };
use crate::pattern::{canonicalize_pattern, Pattern}; use crate::pattern::{canonicalize_pattern, Pattern};
use crate::procedure::References; use crate::procedure::References;
@ -20,7 +20,7 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias; use roc_types::types::Alias;
use std::fmt::Debug; use std::fmt::{Debug, Display};
use std::{char, u32}; use std::{char, u32};
#[derive(Clone, Default, Debug, PartialEq)] #[derive(Clone, Default, Debug, PartialEq)]
@ -46,17 +46,32 @@ impl Output {
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum IntValue {
I128(i128),
U128(u128),
}
impl Display for IntValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IntValue::I128(n) => Display::fmt(&n, f),
IntValue::U128(n) => Display::fmt(&n, f),
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Expr { pub enum Expr {
// Literals // Literals
// Num stores the `a` variable in `Num a`. Not the same as the variable // Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages // stored in Int and Float below, which is strictly for better error messages
Num(Variable, Box<str>, i64), Num(Variable, Box<str>, IntValue, NumericBound),
// Int and Float store a variable to generate better error messages // Int and Float store a variable to generate better error messages
Int(Variable, Variable, Box<str>, i128), Int(Variable, Variable, Box<str>, IntValue, IntBound),
Float(Variable, Variable, Box<str>, f64), Float(Variable, Variable, Box<str>, f64, FloatBound),
Str(Box<str>), Str(Box<str>),
List { List {
elem_var: Variable, elem_var: Variable,
@ -208,20 +223,20 @@ pub fn canonicalize_expr<'a>(
use Expr::*; use Expr::*;
let (expr, output) = match expr { let (expr, output) = match expr {
ast::Expr::Num(str) => { &ast::Expr::Num(str) => {
let answer = num_expr_from_result( let answer = num_expr_from_result(
var_store, var_store,
finish_parsing_int(*str).map(|int| (*str, int)), finish_parsing_num(str).map(|result| (str, result)),
region, region,
env, env,
); );
(answer, Output::default()) (answer, Output::default())
} }
ast::Expr::Float(str) => { &ast::Expr::Float(str) => {
let answer = float_expr_from_result( let answer = float_expr_from_result(
var_store, var_store,
finish_parsing_float(str).map(|f| (*str, f)), finish_parsing_float(str).map(|(f, bound)| (str, f, bound)),
region, region,
env, env,
); );
@ -758,13 +773,13 @@ pub fn canonicalize_expr<'a>(
let region1 = Region::new( let region1 = Region::new(
*binop1_position, *binop1_position,
binop1_position.bump_column(binop1.width()), binop1_position.bump_column(binop1.width() as u32),
); );
let loc_binop1 = Loc::at(region1, *binop1); let loc_binop1 = Loc::at(region1, *binop1);
let region2 = Region::new( let region2 = Region::new(
*binop2_position, *binop2_position,
binop2_position.bump_column(binop2.width()), binop2_position.bump_column(binop2.width() as u32),
); );
let loc_binop2 = Loc::at(region2, *binop2); let loc_binop2 = Loc::at(region2, *binop2);
@ -790,21 +805,21 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default()) (RuntimeError(problem), Output::default())
} }
ast::Expr::NonBase10Int { &ast::Expr::NonBase10Int {
string, string,
base, base,
is_negative, is_negative,
} => { } => {
// the minus sign is added before parsing, to get correct overflow/underflow behavior // the minus sign is added before parsing, to get correct overflow/underflow behavior
let answer = match finish_parsing_base(string, *base, *is_negative) { let answer = match finish_parsing_base(string, base, is_negative) {
Ok(int) => { Ok((int, bound)) => {
// Done in this kinda round about way with intermediate variables // Done in this kinda round about way with intermediate variables
// to keep borrowed values around and make this compile // to keep borrowed values around and make this compile
let int_string = int.to_string(); let int_string = int.to_string();
let int_str = int_string.as_str(); let int_str = int_string.as_str();
int_expr_from_result(var_store, Ok((int_str, int as i128)), region, *base, env) int_expr_from_result(var_store, Ok((int_str, int, bound)), region, base, env)
} }
Err(e) => int_expr_from_result(var_store, Err(e), region, *base, env), Err(e) => int_expr_from_result(var_store, Err(e), region, base, env),
}; };
(answer, Output::default()) (answer, Output::default())
@ -1226,9 +1241,9 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
match expr { match expr {
// Num stores the `a` variable in `Num a`. Not the same as the variable // Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages // stored in Int and Float below, which is strictly for better error messages
other @ Num(_, _, _) other @ Num(..)
| other @ Int(_, _, _, _) | other @ Int(..)
| other @ Float(_, _, _, _) | other @ Float(..)
| other @ Str { .. } | other @ Str { .. }
| other @ RuntimeError(_) | other @ RuntimeError(_)
| other @ EmptyRecord | other @ EmptyRecord
@ -1680,22 +1695,22 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
let mut iter = segments.into_iter().rev(); let mut iter = segments.into_iter().rev();
let mut loc_expr = match iter.next() { let mut loc_expr = match iter.next() {
Some(Plaintext(string)) => Loc::new(0, 0, 0, 0, Expr::Str(string)), Some(Plaintext(string)) => Loc::at(Region::zero(), Expr::Str(string)),
Some(Interpolation(loc_expr)) => loc_expr, Some(Interpolation(loc_expr)) => loc_expr,
None => { None => {
// No segments? Empty string! // No segments? Empty string!
Loc::new(0, 0, 0, 0, Expr::Str("".into())) Loc::at(Region::zero(), Expr::Str("".into()))
} }
}; };
for seg in iter { for seg in iter {
let loc_new_expr = match seg { let loc_new_expr = match seg {
Plaintext(string) => Loc::new(0, 0, 0, 0, Expr::Str(string)), Plaintext(string) => Loc::at(Region::zero(), Expr::Str(string)),
Interpolation(loc_interpolated_expr) => loc_interpolated_expr, Interpolation(loc_interpolated_expr) => loc_interpolated_expr,
}; };
let fn_expr = Loc::new(0, 0, 0, 0, Expr::Var(Symbol::STR_CONCAT)); let fn_expr = Loc::at(Region::zero(), Expr::Var(Symbol::STR_CONCAT));
let expr = Expr::Call( let expr = Expr::Call(
Box::new(( Box::new((
var_store.fresh(), var_store.fresh(),
@ -1710,7 +1725,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
CalledVia::StringInterpolation, CalledVia::StringInterpolation,
); );
loc_expr = Loc::new(0, 0, 0, 0, expr); loc_expr = Loc::at(Region::zero(), expr);
} }
loc_expr.value loc_expr.value

View file

@ -5,6 +5,7 @@ pub mod annotation;
pub mod builtins; pub mod builtins;
pub mod constraint; pub mod constraint;
pub mod def; pub mod def;
pub mod effect_module;
pub mod env; pub mod env;
pub mod expected; pub mod expected;
pub mod expr; pub mod expr;

View file

@ -1,4 +1,5 @@
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env; use crate::env::Env;
use crate::expr::{ClosureData, Expr, Output}; use crate::expr::{ClosureData, Expr, Output};
use crate::operator::desugar_def; use crate::operator::desugar_def;
@ -6,15 +7,16 @@ use crate::pattern::Pattern;
use crate::scope::Scope; use crate::scope::Scope;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::ident::Ident;
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::ident::{Ident, TagName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast; use roc_parse::ast;
use roc_parse::header::HeaderFor;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias; use roc_types::types::{Alias, Type};
#[derive(Debug)] #[derive(Debug)]
pub struct Module { pub struct Module {
@ -39,11 +41,36 @@ pub struct ModuleOutput {
pub scope: Scope, pub scope: Scope,
} }
fn validate_generate_with<'a>(
generate_with: &'a [Loc<roc_parse::header::ExposedName<'a>>],
) -> (HostedGeneratedFunctions, Vec<Loc<Ident>>) {
let mut functions = HostedGeneratedFunctions::default();
let mut unknown = Vec::new();
for generated in generate_with {
match generated.value.as_str() {
"after" => functions.after = true,
"map" => functions.map = true,
"always" => functions.always = true,
"loop" => functions.loop_ = true,
"forever" => functions.forever = true,
other => {
// we don't know how to generate this function
let ident = Ident::from(other);
unknown.push(Loc::at(generated.region, ident));
}
}
}
(functions, unknown)
}
// TODO trim these down // TODO trim these down
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a, F>( pub fn canonicalize_module_defs<'a, F>(
arena: &Bump, arena: &Bump,
loc_defs: &'a [Loc<ast::Def<'a>>], loc_defs: &'a [Loc<ast::Def<'a>>],
header_for: &roc_parse::header::HeaderFor,
home: ModuleId, home: ModuleId,
module_ids: &ModuleIds, module_ids: &ModuleIds,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
@ -59,12 +86,66 @@ where
{ {
let mut can_exposed_imports = MutMap::default(); let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home, var_store); let mut scope = Scope::new(home, var_store);
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
let num_deps = dep_idents.len(); let num_deps = dep_idents.len();
for (name, alias) in aliases.into_iter() { for (name, alias) in aliases.into_iter() {
scope.add_alias(name, alias.region, alias.type_variables, alias.typ); scope.add_alias(name, alias.region, alias.type_variables, alias.typ);
} }
struct Hosted {
effect_symbol: Symbol,
generated_functions: HostedGeneratedFunctions,
}
let hosted_info = if let HeaderFor::Hosted {
generates,
generates_with,
} = header_for
{
let name: &str = generates.into();
let (generated_functions, unknown_generated) = validate_generate_with(generates_with);
for unknown in unknown_generated {
env.problem(Problem::UnknownGeneratesWith(unknown));
}
let effect_symbol = scope
.introduce(
name.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap();
let effect_tag_name = TagName::Private(effect_symbol);
{
let a_var = var_store.fresh();
let actual = crate::effect_module::build_effect_actual(
effect_tag_name,
Type::Variable(a_var),
var_store,
);
scope.add_alias(
effect_symbol,
Region::zero(),
vec![Loc::at_zero(("a".into(), a_var))],
actual,
);
}
Some(Hosted {
effect_symbol,
generated_functions,
})
} else {
None
};
// Desugar operators (convert them to Apply calls, taking into account // Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization. // operator precedence and associativity rules), before doing other canonicalization.
// //
@ -82,7 +163,6 @@ where
})); }));
} }
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
let mut lookups = Vec::with_capacity(num_deps); let mut lookups = Vec::with_capacity(num_deps);
let mut rigid_variables = MutMap::default(); let mut rigid_variables = MutMap::default();
@ -124,7 +204,11 @@ where
// This is a type alias // This is a type alias
// the symbol should already be added to the scope when this module is canonicalized // the symbol should already be added to the scope when this module is canonicalized
debug_assert!(scope.contains_alias(symbol)); debug_assert!(
scope.contains_alias(symbol),
"apparently, {:?} is not actually a type alias",
symbol
);
// but now we know this symbol by a different identifier, so we still need to add it to // but now we know this symbol by a different identifier, so we still need to add it to
// the scope // the scope
@ -139,7 +223,7 @@ where
} }
} }
let (defs, scope, output, symbols_introduced) = canonicalize_defs( let (defs, mut scope, output, symbols_introduced) = canonicalize_defs(
&mut env, &mut env,
Output::default(), Output::default(),
var_store, var_store,
@ -181,6 +265,17 @@ where
references.insert(*symbol); references.insert(*symbol);
} }
// add any builtins used by other builtins
let transitive_builtins: Vec<Symbol> = references
.iter()
.filter(|s| s.is_builtin())
.map(|s| crate::builtins::builtin_dependencies(*s))
.flatten()
.copied()
.collect();
references.extend(transitive_builtins);
// NOTE previously we inserted builtin defs into the list of defs here // NOTE previously we inserted builtin defs into the list of defs here
// this is now done later, in file.rs. // this is now done later, in file.rs.
@ -193,7 +288,26 @@ where
(Ok(mut declarations), output) => { (Ok(mut declarations), output) => {
use crate::def::Declaration::*; use crate::def::Declaration::*;
for decl in declarations.iter() { if let Some(Hosted {
effect_symbol,
generated_functions,
}) = hosted_info
{
let mut exposed_symbols = MutSet::default();
// NOTE this currently builds all functions, not just the ones that the user requested
crate::effect_module::build_effect_builtins(
&mut env,
&mut scope,
effect_symbol,
var_store,
&mut exposed_symbols,
&mut declarations,
generated_functions,
);
}
for decl in declarations.iter_mut() {
match decl { match decl {
Declare(def) => { Declare(def) => {
for (symbol, _) in def.pattern_vars.iter() { for (symbol, _) in def.pattern_vars.iter() {
@ -206,6 +320,59 @@ where
exposed_but_not_defined.remove(symbol); exposed_but_not_defined.remove(symbol);
} }
} }
// Temporary hack: we don't know exactly what symbols are hosted symbols,
// and which are meant to be normal definitions without a body. So for now
// we just assume they are hosted functions (meant to be provided by the platform)
if let Some(Hosted { effect_symbol, .. }) = hosted_info {
macro_rules! make_hosted_def {
() => {
let symbol = def.pattern_vars.iter().next().unwrap().0;
let ident_id = symbol.ident_id();
let ident =
env.ident_ids.get_name(ident_id).unwrap().to_string();
let def_annotation = def.annotation.clone().unwrap();
let annotation = crate::annotation::Annotation {
typ: def_annotation.signature,
introduced_variables: def_annotation.introduced_variables,
references: Default::default(),
aliases: Default::default(),
};
let hosted_def = crate::effect_module::build_host_exposed_def(
&mut env,
&mut scope,
*symbol,
&ident,
TagName::Private(effect_symbol),
var_store,
annotation,
);
*def = hosted_def;
};
}
match &def.loc_expr.value {
Expr::RuntimeError(RuntimeError::NoImplementationNamed {
..
}) => {
make_hosted_def!();
}
Expr::Closure(closure_data)
if matches!(
closure_data.loc_body.value,
Expr::RuntimeError(
RuntimeError::NoImplementationNamed { .. }
)
) =>
{
make_hosted_def!();
}
_ => {}
}
}
} }
DeclareRec(defs) => { DeclareRec(defs) => {
for def in defs { for def in defs {
@ -238,6 +405,18 @@ where
let mut aliases = MutMap::default(); let mut aliases = MutMap::default();
if let Some(Hosted { effect_symbol, .. }) = hosted_info {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_but_not_defined.remove(&effect_symbol);
let hosted_alias = scope.lookup_alias(effect_symbol).unwrap().clone();
aliases.insert(effect_symbol, hosted_alias);
}
for (symbol, alias) in output.aliases { for (symbol, alias) in output.aliases {
// Remove this from exposed_symbols, // Remove this from exposed_symbols,
// so that at the end of the process, // so that at the end of the process,
@ -263,8 +442,8 @@ where
let runtime_error = RuntimeError::ExposedButNotDefined(symbol); let runtime_error = RuntimeError::ExposedButNotDefined(symbol);
let def = Def { let def = Def {
loc_pattern: Loc::new(0, 0, 0, 0, Pattern::Identifier(symbol)), loc_pattern: Loc::at(Region::zero(), Pattern::Identifier(symbol)),
loc_expr: Loc::new(0, 0, 0, 0, Expr::RuntimeError(runtime_error)), loc_expr: Loc::at(Region::zero(), Expr::RuntimeError(runtime_error)),
expr_var: var_store.fresh(), expr_var: var_store.fresh(),
pattern_vars, pattern_vars,
annotation: None, annotation: None,
@ -382,12 +561,12 @@ fn fix_values_captured_in_closure_pattern(
} }
} }
Identifier(_) Identifier(_)
| NumLiteral(_, _, _) | NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| Shadowed(_, _) | Shadowed(..)
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) => (), | UnsupportedPattern(_) => (),
} }
@ -438,9 +617,9 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols);
} }
Num(_, _, _) Num(..)
| Int(_, _, _, _) | Int(..)
| Float(_, _, _, _) | Float(..)
| Str(_) | Str(_)
| Var(_) | Var(_)
| EmptyRecord | EmptyRecord

View file

@ -1,5 +1,5 @@
use crate::env::Env; use crate::env::Env;
use crate::expr::Expr; use crate::expr::{Expr, IntValue};
use roc_parse::ast::Base; use roc_parse::ast::Base;
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_problem::can::RuntimeError::*; use roc_problem::can::RuntimeError::*;
@ -7,21 +7,33 @@ use roc_problem::can::{FloatErrorKind, IntErrorKind};
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use std::i64; use std::i64;
use std::str;
// TODO use rust's integer parsing again
//
// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639
// There is a nightly API for exposing the parse error.
#[inline(always)] #[inline(always)]
pub fn num_expr_from_result( pub fn num_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<(&str, i64), (&str, IntErrorKind)>, result: Result<(&str, ParsedNumResult), (&str, IntErrorKind)>,
region: Region, region: Region,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
match result { match result {
Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num), Ok((str, ParsedNumResult::UnknownNum(num, bound))) => {
Expr::Num(var_store.fresh(), (*str).into(), num, bound)
}
Ok((str, ParsedNumResult::Int(num, bound))) => Expr::Int(
var_store.fresh(),
var_store.fresh(),
(*str).into(),
num,
bound,
),
Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float(
var_store.fresh(),
var_store.fresh(),
(*str).into(),
num,
bound,
),
Err((raw, error)) => { Err((raw, error)) => {
// (Num *) compiles to Int if it doesn't // (Num *) compiles to Int if it doesn't
// get specialized to something else first, // get specialized to something else first,
@ -38,14 +50,20 @@ pub fn num_expr_from_result(
#[inline(always)] #[inline(always)]
pub fn int_expr_from_result( pub fn int_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<(&str, i128), (&str, IntErrorKind)>, result: Result<(&str, IntValue, IntBound), (&str, IntErrorKind)>,
region: Region, region: Region,
base: Base, base: Base,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
// Int stores a variable to generate better error messages // Int stores a variable to generate better error messages
match result { match result {
Ok((str, int)) => Expr::Int(var_store.fresh(), var_store.fresh(), (*str).into(), int), Ok((str, int, bound)) => Expr::Int(
var_store.fresh(),
var_store.fresh(),
(*str).into(),
int,
bound,
),
Err((raw, error)) => { Err((raw, error)) => {
let runtime_error = InvalidInt(error, base, region, raw.into()); let runtime_error = InvalidInt(error, base, region, raw.into());
@ -59,13 +77,19 @@ pub fn int_expr_from_result(
#[inline(always)] #[inline(always)]
pub fn float_expr_from_result( pub fn float_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<(&str, f64), (&str, FloatErrorKind)>, result: Result<(&str, f64, FloatBound), (&str, FloatErrorKind)>,
region: Region, region: Region,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
// Float stores a variable to generate better error messages // Float stores a variable to generate better error messages
match result { match result {
Ok((str, float)) => Expr::Float(var_store.fresh(), var_store.fresh(), (*str).into(), float), Ok((str, float, bound)) => Expr::Float(
var_store.fresh(),
var_store.fresh(),
(*str).into(),
float,
bound,
),
Err((raw, error)) => { Err((raw, error)) => {
let runtime_error = InvalidFloat(error, region, raw.into()); let runtime_error = InvalidFloat(error, region, raw.into());
@ -76,11 +100,17 @@ pub fn float_expr_from_result(
} }
} }
pub enum ParsedNumResult {
Int(IntValue, IntBound),
Float(f64, FloatBound),
UnknownNum(IntValue, NumericBound),
}
#[inline(always)] #[inline(always)]
pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, IntErrorKind)> { pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorKind)> {
// Ignore underscores. // Ignore underscores.
let radix = 10; let radix = 10;
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind)) from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))
} }
#[inline(always)] #[inline(always)]
@ -88,7 +118,7 @@ pub fn finish_parsing_base(
raw: &str, raw: &str,
base: Base, base: Base,
is_negative: bool, is_negative: bool,
) -> Result<i64, (&str, IntErrorKind)> { ) -> Result<(IntValue, IntBound), (&str, IntErrorKind)> {
let radix = match base { let radix = match base {
Base::Hex => 16, Base::Hex => 16,
Base::Decimal => 10, Base::Decimal => 10,
@ -98,18 +128,34 @@ pub fn finish_parsing_base(
// Ignore underscores, insert - when negative to get correct underflow/overflow behavior // Ignore underscores, insert - when negative to get correct underflow/overflow behavior
(if is_negative { (if is_negative {
from_str_radix::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix) from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix)
} else { } else {
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix) from_str_radix(raw.replace("_", "").as_str(), radix)
}) })
.map_err(|e| (raw, e.kind)) .and_then(|parsed| match parsed {
ParsedNumResult::Float(..) => Err(IntErrorKind::FloatSuffix),
ParsedNumResult::Int(val, bound) => Ok((val, bound)),
ParsedNumResult::UnknownNum(val, NumericBound::None) => Ok((val, IntBound::None)),
ParsedNumResult::UnknownNum(val, NumericBound::AtLeastIntOrFloat { sign, width }) => {
Ok((val, IntBound::AtLeast { sign, width }))
}
})
.map_err(|e| (raw, e))
} }
#[inline(always)] #[inline(always)]
pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> { pub fn finish_parsing_float(raw: &str) -> Result<(f64, FloatBound), (&str, FloatErrorKind)> {
let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw);
let bound = match opt_bound {
None => FloatBound::None,
Some(ParsedWidth::Float(fw)) => FloatBound::Exact(fw),
Some(ParsedWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)),
};
// Ignore underscores. // Ignore underscores.
match raw.replace("_", "").parse::<f64>() { match raw_without_suffix.replace("_", "").parse::<f64>() {
Ok(float) if float.is_finite() => Ok(float), Ok(float) if float.is_finite() => Ok((float, bound)),
Ok(float) => { Ok(float) => {
if float.is_sign_positive() { if float.is_sign_positive() {
Err((raw, FloatErrorKind::PositiveInfinity)) Err((raw, FloatErrorKind::PositiveInfinity))
@ -121,6 +167,41 @@ pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
} }
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ParsedWidth {
Int(IntWidth),
Float(FloatWidth),
}
fn parse_literal_suffix(num_str: &str) -> (Option<ParsedWidth>, &str) {
macro_rules! parse_num_suffix {
($($suffix:expr, $width:expr)*) => {$(
if num_str.ends_with($suffix) {
return (Some($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap());
}
)*}
}
parse_num_suffix! {
"u8", ParsedWidth::Int(IntWidth::U8)
"u16", ParsedWidth::Int(IntWidth::U16)
"u32", ParsedWidth::Int(IntWidth::U32)
"u64", ParsedWidth::Int(IntWidth::U64)
"u128", ParsedWidth::Int(IntWidth::U128)
"i8", ParsedWidth::Int(IntWidth::I8)
"i16", ParsedWidth::Int(IntWidth::I16)
"i32", ParsedWidth::Int(IntWidth::I32)
"i64", ParsedWidth::Int(IntWidth::I64)
"i128", ParsedWidth::Int(IntWidth::I128)
"nat", ParsedWidth::Int(IntWidth::Nat)
"dec", ParsedWidth::Float(FloatWidth::Dec)
"f32", ParsedWidth::Float(FloatWidth::F32)
"f64", ParsedWidth::Float(FloatWidth::F64)
}
(None, num_str)
}
/// Integer parsing code taken from the rust libcore, /// Integer parsing code taken from the rust libcore,
/// pulled in so we can give custom error messages /// pulled in so we can give custom error messages
/// ///
@ -129,44 +210,8 @@ pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
/// the LEGAL_DETAILS file in the root directory of this distribution. /// the LEGAL_DETAILS file in the root directory of this distribution.
/// ///
/// Thanks to the Rust project and its contributors! /// Thanks to the Rust project and its contributors!
trait FromStrRadixHelper: PartialOrd + Copy { fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind> {
fn min_value() -> Self;
fn max_value() -> Self;
fn from_u32(u: u32) -> Self;
fn checked_mul(&self, other: u32) -> Option<Self>;
fn checked_sub(&self, other: u32) -> Option<Self>;
fn checked_add(&self, other: u32) -> Option<Self>;
}
macro_rules! doit {
($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
#[inline]
fn min_value() -> Self { Self::min_value() }
#[inline]
fn max_value() -> Self { Self::max_value() }
#[inline]
fn from_u32(u: u32) -> Self { u as Self }
#[inline]
fn checked_mul(&self, other: u32) -> Option<Self> {
Self::checked_mul(*self, other as Self)
}
#[inline]
fn checked_sub(&self, other: u32) -> Option<Self> {
Self::checked_sub(*self, other as Self)
}
#[inline]
fn checked_add(&self, other: u32) -> Option<Self> {
Self::checked_add(*self, other as Self)
}
})*)
}
// We only need the i64 implementation, but libcore defines
// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
doit! { i64 }
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
use self::IntErrorKind::*; use self::IntErrorKind::*;
use self::ParseIntError as PIE;
assert!( assert!(
(2..=36).contains(&radix), (2..=36).contains(&radix),
@ -174,86 +219,294 @@ fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, Par
radix radix
); );
if src.is_empty() { let (opt_exact_bound, src) = parse_literal_suffix(src);
return Err(PIE { kind: Empty });
use std::num::IntErrorKind as StdIEK;
let result = match i128::from_str_radix(src, radix) {
Ok(result) => IntValue::I128(result),
Err(pie) => match pie.kind() {
StdIEK::Empty => return Err(IntErrorKind::Empty),
StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
StdIEK::NegOverflow => return Err(IntErrorKind::Underflow),
StdIEK::PosOverflow => {
// try a u128
match u128::from_str_radix(src, radix) {
Ok(result) => IntValue::U128(result),
Err(pie) => match pie.kind() {
StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
StdIEK::PosOverflow => return Err(IntErrorKind::Overflow),
StdIEK::Empty | StdIEK::Zero | StdIEK::NegOverflow => unreachable!(),
_ => unreachable!("I thought all possibilities were exhausted, but std::num added a new one")
},
} }
let is_signed_ty = T::from_u32(0) > T::min_value();
// all valid digits are ascii, so we will just iterate over the utf8 bytes
// and cast them to chars. .to_digit() will safely return None for anything
// other than a valid ascii digit for the given radix, including the first-byte
// of multi-byte sequences
let src = src.as_bytes();
let (is_positive, digits) = match src[0] {
b'+' => (true, &src[1..]),
b'-' if is_signed_ty => (false, &src[1..]),
_ => (true, src),
};
if digits.is_empty() {
return Err(PIE { kind: Empty });
} }
StdIEK::Zero => unreachable!("Parsed a i128"),
_ => unreachable!(
"I thought all possibilities were exhausted, but std::num added a new one"
),
},
};
let mut result = T::from_u32(0); let (lower_bound, is_negative) = match result {
if is_positive { IntValue::I128(num) => (lower_bound_of_int(num), num < 0),
// The number is positive IntValue::U128(_) => (IntWidth::U128, false),
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
}; };
result = match result.checked_mul(radix) {
Some(result) => result, match opt_exact_bound {
None => return Err(PIE { kind: Overflow }), None => {
}; // There's no exact bound, but we do have a lower bound.
result = match result.checked_add(x) { let sign_demand = if is_negative {
Some(result) => result, SignDemand::Signed
None => return Err(PIE { kind: Overflow }), } else {
SignDemand::NoDemand
}; };
Ok(ParsedNumResult::UnknownNum(
result,
NumericBound::AtLeastIntOrFloat {
sign: sign_demand,
width: lower_bound,
},
))
}
Some(ParsedWidth::Float(fw)) => {
// For now, assume floats can represent all integers
// TODO: this is somewhat incorrect, revisit
Ok(ParsedNumResult::Float(
match result {
IntValue::I128(n) => n as f64,
IntValue::U128(n) => n as f64,
},
FloatBound::Exact(fw),
))
}
Some(ParsedWidth::Int(exact_width)) => {
// We need to check if the exact bound >= lower bound.
if exact_width.is_superset(&lower_bound, is_negative) {
// Great! Use the exact bound.
Ok(ParsedNumResult::Int(result, IntBound::Exact(exact_width)))
} else {
// This is something like 200i8; the lower bound is u8, which holds strictly more
// ints on the positive side than i8 does. Report an error depending on which side
// of the integers we checked.
let err = if is_negative {
UnderflowsSuffix {
suffix_type: exact_width.type_str(),
min_value: exact_width.min_value(),
} }
} else { } else {
// The number is negative OverflowsSuffix {
for &c in digits { suffix_type: exact_width.type_str(),
let x = match (c as char).to_digit(radix) { max_value: exact_width.max_value(),
Some(x) => x, }
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
result = match result.checked_sub(x) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
}; };
Err(err)
}
} }
} }
Ok(result)
} }
/// An error which can be returned when parsing an integer. fn lower_bound_of_int(result: i128) -> IntWidth {
/// use IntWidth::*;
/// This error is used as the error type for the `from_str_radix()` functions if result >= 0 {
/// on the primitive integer types, such as [`i8::from_str_radix`]. // Positive
/// let result = result as u128;
/// # Potential causes if result > U64.max_value() {
/// I128
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace } else if result > I64.max_value() {
/// in the string e.g., when it is obtained from the standard input. U64
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing. } else if result > U32.max_value() {
/// I64
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim } else if result > I32.max_value() {
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix U32
#[derive(Debug, Clone, PartialEq, Eq)] } else if result > U16.max_value() {
pub struct ParseIntError { I32
kind: IntErrorKind, } else if result > I16.max_value() {
} U16
} else if result > U8.max_value() {
impl ParseIntError { I16
/// Outputs the detailed cause of parsing an integer failing. } else if result > I8.max_value() {
pub fn kind(&self) -> &IntErrorKind { U8
&self.kind } else {
I8
}
} else {
// Negative
if result < I64.min_value() {
I128
} else if result < I32.min_value() {
I64
} else if result < I16.min_value() {
I32
} else if result < I8.min_value() {
I16
} else {
I8
}
} }
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum IntSign {
Unsigned,
Signed,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntWidth {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
Nat,
}
impl IntWidth {
/// Returns the `IntSign` and bit width of a variant.
fn sign_and_width(&self) -> (IntSign, u32) {
use IntSign::*;
use IntWidth::*;
match self {
U8 => (Unsigned, 8),
U16 => (Unsigned, 16),
U32 => (Unsigned, 32),
U64 => (Unsigned, 64),
U128 => (Unsigned, 128),
I8 => (Signed, 8),
I16 => (Signed, 16),
I32 => (Signed, 32),
I64 => (Signed, 64),
I128 => (Signed, 128),
// TODO: this is platform specific!
Nat => (Unsigned, 64),
}
}
fn type_str(&self) -> &'static str {
use IntWidth::*;
match self {
U8 => "U8",
U16 => "U16",
U32 => "U32",
U64 => "U64",
U128 => "U128",
I8 => "I8",
I16 => "I16",
I32 => "I32",
I64 => "I64",
I128 => "I128",
Nat => "Nat",
}
}
fn max_value(&self) -> u128 {
use IntWidth::*;
match self {
U8 => u8::MAX as u128,
U16 => u16::MAX as u128,
U32 => u32::MAX as u128,
U64 => u64::MAX as u128,
U128 => u128::MAX,
I8 => i8::MAX as u128,
I16 => i16::MAX as u128,
I32 => i32::MAX as u128,
I64 => i64::MAX as u128,
I128 => i128::MAX as u128,
// TODO: this is platform specific!
Nat => u64::MAX as u128,
}
}
fn min_value(&self) -> i128 {
use IntWidth::*;
match self {
U8 | U16 | U32 | U64 | U128 | Nat => 0,
I8 => i8::MIN as i128,
I16 => i16::MIN as i128,
I32 => i32::MIN as i128,
I64 => i64::MIN as i128,
I128 => i128::MIN,
}
}
/// Checks if `self` represents superset of integers that `lower_bound` represents, on a particular
/// side of the integers relative to 0.
///
/// If `is_negative` is true, the negative side is checked; otherwise the positive side is checked.
pub fn is_superset(&self, lower_bound: &Self, is_negative: bool) -> bool {
use IntSign::*;
if is_negative {
match (self.sign_and_width(), lower_bound.sign_and_width()) {
((Signed, us), (Signed, lower_bound)) => us >= lower_bound,
// Unsigned ints can never represent negative numbers; signed (non-zero width)
// ints always can.
((Unsigned, _), (Signed, _)) => false,
((Signed, _), (Unsigned, _)) => true,
// Trivially true; both can only express 0.
((Unsigned, _), (Unsigned, _)) => true,
}
} else {
match (self.sign_and_width(), lower_bound.sign_and_width()) {
((Signed, us), (Signed, lower_bound))
| ((Unsigned, us), (Unsigned, lower_bound)) => us >= lower_bound,
// Unsigned ints with the same bit width as their unsigned counterparts can always
// express 2x more integers on the positive side as unsigned ints.
((Unsigned, us), (Signed, lower_bound)) => us >= lower_bound,
// ...but that means signed int widths can represent less than their unsigned
// counterparts, so the below is true iff the bit width is strictly greater. E.g.
// i16 is a superset of u8, but i16 is not a superset of u16.
((Signed, us), (Unsigned, lower_bound)) => us > lower_bound,
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatWidth {
Dec,
F32,
F64,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SignDemand {
/// Can be signed or unsigned.
NoDemand,
/// Must be signed.
Signed,
}
/// Describes a bound on the width of an integer.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntBound {
/// There is no bound on the width.
None,
/// Must have an exact width.
Exact(IntWidth),
/// Must have a certain sign and a minimum width.
AtLeast { sign: SignDemand, width: IntWidth },
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatBound {
None,
Exact(FloatWidth),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NumericBound {
None,
/// Must be an integer of a certain size, or any float.
AtLeastIntOrFloat {
sign: SignDemand,
width: IntWidth,
},
}

View file

@ -121,8 +121,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
/// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes. /// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes.
pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc<Expr<'a>> { pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc<Expr<'a>> {
match &loc_expr.value { match &loc_expr.value {
Float(_) Float(..)
| Num(_) | Num(..)
| NonBase10Int { .. } | NonBase10Int { .. }
| Str(_) | Str(_)
| AccessorFunction(_) | AccessorFunction(_)

View file

@ -1,6 +1,9 @@
use crate::env::Env; use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, Output}; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound,
NumericBound, ParsedNumResult,
};
use crate::scope::Scope; use crate::scope::Scope;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -9,6 +12,7 @@ use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
/// A pattern, including possible problems (e.g. shadowing) so that /// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached. /// codegen can generate a runtime error if this pattern is reached.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -25,14 +29,14 @@ pub enum Pattern {
ext_var: Variable, ext_var: Variable,
destructs: Vec<Loc<RecordDestruct>>, destructs: Vec<Loc<RecordDestruct>>,
}, },
IntLiteral(Variable, Box<str>, i64), NumLiteral(Variable, Box<str>, IntValue, NumericBound),
NumLiteral(Variable, Box<str>, i64), IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
FloatLiteral(Variable, Box<str>, f64), FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
StrLiteral(Box<str>), StrLiteral(Box<str>),
Underscore, Underscore,
// Runtime Exceptions // Runtime Exceptions
Shadowed(Region, Loc<Ident>), Shadowed(Region, Loc<Ident>, Symbol),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region), UnsupportedPattern(Region),
// parse error patterns // parse error patterns
@ -65,7 +69,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
use Pattern::*; use Pattern::*;
match pattern { match pattern {
Identifier(symbol) => { Identifier(symbol) | Shadowed(_, _, symbol) => {
symbols.push(*symbol); symbols.push(*symbol);
} }
@ -85,15 +89,13 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
} }
} }
NumLiteral(_, _, _) NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) => {} | UnsupportedPattern(_) => {}
Shadowed(_, _) => {}
} }
} }
@ -121,13 +123,14 @@ pub fn canonicalize_pattern<'a>(
Pattern::Identifier(symbol) Pattern::Identifier(symbol)
} }
Err((original_region, shadow)) => { Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
})); }));
output.references.bound_symbols.insert(new_symbol);
Pattern::Shadowed(original_region, shadow) Pattern::Shadowed(original_region, shadow, new_symbol)
} }
}, },
GlobalTag(name) => { GlobalTag(name) => {
@ -185,13 +188,19 @@ pub fn canonicalize_pattern<'a>(
} }
} }
FloatLiteral(str) => match pattern_type { &FloatLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_float(str) { WhenBranch => match finish_parsing_float(str) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedFloat; let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (*str).into(), float), Ok((float, bound)) => Pattern::FloatLiteral(
var_store.fresh(),
var_store.fresh(),
(str).into(),
float,
bound,
),
}, },
ptype => unsupported_pattern(env, ptype, region), ptype => unsupported_pattern(env, ptype, region),
}, },
@ -201,32 +210,58 @@ pub fn canonicalize_pattern<'a>(
TopLevelDef | DefExpr => bad_underscore(env, region), TopLevelDef | DefExpr => bad_underscore(env, region),
}, },
NumLiteral(str) => match pattern_type { &NumLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_int(str) { WhenBranch => match finish_parsing_num(str) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedInt; let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(int) => Pattern::NumLiteral(var_store.fresh(), (*str).into(), int), Ok(ParsedNumResult::UnknownNum(int, bound)) => {
Pattern::NumLiteral(var_store.fresh(), (str).into(), int, bound)
}
Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral(
var_store.fresh(),
var_store.fresh(),
(str).into(),
int,
bound,
),
Ok(ParsedNumResult::Float(float, bound)) => Pattern::FloatLiteral(
var_store.fresh(),
var_store.fresh(),
(str).into(),
float,
bound,
),
}, },
ptype => unsupported_pattern(env, ptype, region), ptype => unsupported_pattern(env, ptype, region),
}, },
NonBase10Literal { &NonBase10Literal {
string, string,
base, base,
is_negative, is_negative,
} => match pattern_type { } => match pattern_type {
WhenBranch => match finish_parsing_base(string, *base, *is_negative) { WhenBranch => match finish_parsing_base(string, base, is_negative) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedBase(*base); let problem = MalformedPatternProblem::MalformedBase(base);
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(int) => { Ok((IntValue::U128(_), _)) if is_negative => {
let sign_str = if *is_negative { "-" } else { "" }; // Can't negate a u128; that doesn't fit in any integer literal type we support.
let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
}
Ok((int, bound)) => {
let sign_str = if is_negative { "-" } else { "" };
let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str(); let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
let i = if *is_negative { -int } else { int }; let i = match int {
Pattern::IntLiteral(var_store.fresh(), int_str, i) // Safety: this is fine because I128::MAX = |I128::MIN| - 1
IntValue::I128(n) if is_negative => IntValue::I128(-n),
IntValue::I128(n) => IntValue::I128(n),
IntValue::U128(_) => unreachable!(),
};
Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound)
} }
}, },
ptype => unsupported_pattern(env, ptype, region), ptype => unsupported_pattern(env, ptype, region),
@ -268,7 +303,7 @@ pub fn canonicalize_pattern<'a>(
}, },
}); });
} }
Err((original_region, shadow)) => { Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
@ -278,7 +313,8 @@ pub fn canonicalize_pattern<'a>(
// are, we're definitely shadowed and will // are, we're definitely shadowed and will
// get a runtime exception as soon as we // get a runtime exception as soon as we
// encounter the first bad pattern. // encounter the first bad pattern.
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); opt_erroneous =
Some(Pattern::Shadowed(original_region, shadow, new_symbol));
} }
}; };
} }
@ -339,7 +375,7 @@ pub fn canonicalize_pattern<'a>(
}, },
}); });
} }
Err((original_region, shadow)) => { Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
@ -349,7 +385,8 @@ pub fn canonicalize_pattern<'a>(
// are, we're definitely shadowed and will // are, we're definitely shadowed and will
// get a runtime exception as soon as we // get a runtime exception as soon as we
// encounter the first bad pattern. // encounter the first bad pattern.
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); opt_erroneous =
Some(Pattern::Shadowed(original_region, shadow, new_symbol));
} }
}; };
} }
@ -452,7 +489,7 @@ fn add_bindings_from_patterns(
use Pattern::*; use Pattern::*;
match pattern { match pattern {
Identifier(symbol) => { Identifier(symbol) | Shadowed(_, _, symbol) => {
answer.push((*symbol, *region)); answer.push((*symbol, *region));
} }
AppliedTag { AppliedTag {
@ -472,12 +509,11 @@ fn add_bindings_from_patterns(
answer.push((*symbol, *region)); answer.push((*symbol, *region));
} }
} }
NumLiteral(_, _, _) NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| Shadowed(_, _)
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) => (), | UnsupportedPattern(_) => (),
} }

View file

@ -110,15 +110,21 @@ impl Scope {
exposed_ident_ids: &IdentIds, exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds, all_ident_ids: &mut IdentIds,
region: Region, region: Region,
) -> Result<Symbol, (Region, Loc<Ident>)> { ) -> Result<Symbol, (Region, Loc<Ident>, Symbol)> {
match self.idents.get(&ident) { match self.idents.get(&ident) {
Some((_, original_region)) => { Some(&(_, original_region)) => {
let shadow = Loc { let shadow = Loc {
value: ident, value: ident.clone(),
region, region,
}; };
Err((*original_region, shadow)) let ident_id = all_ident_ids.add(ident.clone());
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Err((original_region, shadow, symbol))
} }
None => { None => {
// If this IdentId was already added previously // If this IdentId was already added previously

View file

@ -15,7 +15,7 @@ mod test_can {
use crate::helpers::{can_expr_with, test_home, CanExprOut}; use crate::helpers::{can_expr_with, test_home, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expr::Expr::{self, *}; use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Recursive}; use roc_can::expr::{ClosureData, IntValue, Recursive};
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::{Position, Region}; use roc_region::all::{Position, Region};
use std::{f64, i64}; use std::{f64, i64};
@ -32,7 +32,7 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Float(_, _, _, actual) => { Expr::Float(_, _, _, actual, _) => {
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
actual => { actual => {
@ -46,8 +46,8 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Int(_, _, _, actual) => { Expr::Int(_, _, _, actual, _) => {
assert_eq!(expected, actual); assert_eq!(IntValue::I128(expected), actual);
} }
actual => { actual => {
panic!("Expected an Int *, but got: {:?}", actual); panic!("Expected an Int *, but got: {:?}", actual);
@ -55,13 +55,13 @@ mod test_can {
} }
} }
fn assert_can_num(input: &str, expected: i64) { fn assert_can_num(input: &str, expected: i128) {
let arena = Bump::new(); let arena = Bump::new();
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Num(_, _, actual) => { Expr::Num(_, _, actual, _) => {
assert_eq!(expected, actual); assert_eq!(IntValue::I128(expected), actual);
} }
actual => { actual => {
panic!("Expected a Num, but got: {:?}", actual); panic!("Expected a Num, but got: {:?}", actual);
@ -79,7 +79,7 @@ mod test_can {
fn int_too_large() { fn int_too_large() {
use roc_parse::ast::Base; use roc_parse::ast::Base;
let string = (i64::MAX as i128 + 1).to_string(); let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string();
assert_can( assert_can(
&string.clone(), &string.clone(),
@ -96,7 +96,7 @@ mod test_can {
fn int_too_small() { fn int_too_small() {
use roc_parse::ast::Base; use roc_parse::ast::Base;
let string = (i64::MIN as i128 - 1).to_string(); let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string();
assert_can( assert_can(
&string.clone(), &string.clone(),
@ -186,12 +186,12 @@ mod test_can {
#[test] #[test]
fn num_max() { fn num_max() {
assert_can_num(&(i64::MAX.to_string()), i64::MAX); assert_can_num(&(i64::MAX.to_string()), i64::MAX.into());
} }
#[test] #[test]
fn num_min() { fn num_min() {
assert_can_num(&(i64::MIN.to_string()), i64::MIN); assert_can_num(&(i64::MIN.to_string()), i64::MIN.into());
} }
#[test] #[test]
@ -368,9 +368,11 @@ mod test_can {
let arena = Bump::new(); let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1); assert_eq!(problems.len(), 2);
assert!(problems.iter().all(|problem| match problem { assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
// Due to one of the shadows
Problem::UnusedDef(..) => true,
_ => false, _ => false,
})); }));
} }
@ -389,9 +391,11 @@ mod test_can {
let arena = Bump::new(); let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1); assert_eq!(problems.len(), 2);
assert!(problems.iter().all(|problem| match problem { assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
// Due to one of the shadows
Problem::UnusedDef(..) => true,
_ => false, _ => false,
})); }));
} }
@ -410,10 +414,12 @@ mod test_can {
let arena = Bump::new(); let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1); assert_eq!(problems.len(), 2);
println!("{:#?}", problems); println!("{:#?}", problems);
assert!(problems.iter().all(|problem| match problem { assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true, Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
// Due to one of the shadows
Problem::UnusedDef(..) => true,
_ => false, _ => false,
})); }));
} }
@ -943,14 +949,8 @@ mod test_can {
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry { let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![CycleEntry {
symbol: interns.symbol(home, "x".into()), symbol: interns.symbol(home, "x".into()),
symbol_region: Region::new( symbol_region: Region::new(Position::new(0), Position::new(1)),
Position { line: 0, column: 0 }, expr_region: Region::new(Position::new(4), Position::new(5)),
Position { line: 0, column: 1 },
),
expr_region: Region::new(
Position { line: 0, column: 4 },
Position { line: 0, column: 5 },
),
}])); }]));
assert_eq!(is_circular_def, true); assert_eq!(is_circular_def, true);
@ -980,36 +980,18 @@ mod test_can {
let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![ let problem = Problem::RuntimeError(RuntimeError::CircularDef(vec![
CycleEntry { CycleEntry {
symbol: interns.symbol(home, "x".into()), symbol: interns.symbol(home, "x".into()),
symbol_region: Region::new( symbol_region: Region::new(Position::new(0), Position::new(1)),
Position { line: 0, column: 0 }, expr_region: Region::new(Position::new(4), Position::new(5)),
Position { line: 0, column: 1 },
),
expr_region: Region::new(
Position { line: 0, column: 4 },
Position { line: 0, column: 5 },
),
}, },
CycleEntry { CycleEntry {
symbol: interns.symbol(home, "y".into()), symbol: interns.symbol(home, "y".into()),
symbol_region: Region::new( symbol_region: Region::new(Position::new(6), Position::new(7)),
Position { line: 1, column: 0 }, expr_region: Region::new(Position::new(10), Position::new(11)),
Position { line: 1, column: 1 },
),
expr_region: Region::new(
Position { line: 1, column: 4 },
Position { line: 1, column: 5 },
),
}, },
CycleEntry { CycleEntry {
symbol: interns.symbol(home, "z".into()), symbol: interns.symbol(home, "z".into()),
symbol_region: Region::new( symbol_region: Region::new(Position::new(12), Position::new(13)),
Position { line: 2, column: 0 }, expr_region: Region::new(Position::new(16), Position::new(17)),
Position { line: 2, column: 1 },
),
expr_region: Region::new(
Position { line: 2, column: 4 },
Position { line: 2, column: 5 },
),
}, },
])); ]));

View file

@ -1,6 +1,7 @@
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint; use roc_can::constraint::LetConstraint;
use roc_can::expected::Expected::{self, *}; use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand};
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -10,19 +11,55 @@ use roc_types::types::Category;
use roc_types::types::Reason; use roc_types::types::Reason;
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
#[must_use]
pub fn add_numeric_bound_constr(
constrs: &mut Vec<Constraint>,
num_type: Type,
bound: impl TypedNumericBound,
region: Region,
category: Category,
) -> Type {
let range = bound.bounded_range();
let total_num_type = num_type;
match range.len() {
0 => total_num_type,
1 => {
let actual_type = Variable(range[0]);
constrs.push(Eq(
total_num_type.clone(),
Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region),
category,
region,
));
total_num_type
}
_ => RangedNumber(Box::new(total_num_type), range),
}
}
#[inline(always)] #[inline(always)]
pub fn int_literal( pub fn int_literal(
num_var: Variable, num_var: Variable,
precision_var: Variable, precision_var: Variable,
expected: Expected<Type>, expected: Expected<Type>,
region: Region, region: Region,
bound: IntBound,
) -> Constraint { ) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
exists( let mut constrs = Vec::with_capacity(3);
vec![num_var], // Always add the bound first; this improves the resolved type quality in case it's an alias
And(vec![ // like "U8".
let num_type = add_numeric_bound_constr(
&mut constrs,
Variable(num_var),
bound,
region,
Category::Num,
);
constrs.extend(vec![
Eq( Eq(
num_type.clone(), num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region), ForReason(reason, num_int(Type::Variable(precision_var)), region),
@ -30,8 +67,9 @@ pub fn int_literal(
region, region,
), ),
Eq(num_type, expected, Category::Int, region), Eq(num_type, expected, Category::Int, region),
]), ]);
)
exists(vec![num_var], And(constrs))
} }
#[inline(always)] #[inline(always)]
@ -40,13 +78,19 @@ pub fn float_literal(
precision_var: Variable, precision_var: Variable,
expected: Expected<Type>, expected: Expected<Type>,
region: Region, region: Region,
bound: FloatBound,
) -> Constraint { ) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
exists( let mut constrs = Vec::with_capacity(3);
vec![num_var, precision_var], let num_type = add_numeric_bound_constr(
And(vec![ &mut constrs,
Variable(num_var),
bound,
region,
Category::Float,
);
constrs.extend(vec![
Eq( Eq(
num_type.clone(), num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region), ForReason(reason, num_float(Type::Variable(precision_var)), region),
@ -54,8 +98,26 @@ pub fn float_literal(
region, region,
), ),
Eq(num_type, expected, Category::Float, region), Eq(num_type, expected, Category::Float, region),
]), ]);
)
exists(vec![num_var, precision_var], And(constrs))
}
#[inline(always)]
pub fn num_literal(
num_var: Variable,
expected: Expected<Type>,
region: Region,
bound: NumericBound,
) -> Constraint {
let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
let mut constrs = Vec::with_capacity(3);
let num_type =
add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num);
constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]);
exists(vec![num_var], And(constrs))
} }
#[inline(always)] #[inline(always)]
@ -71,7 +133,7 @@ pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
#[inline(always)] #[inline(always)]
pub fn builtin_type(symbol: Symbol, args: Vec<Type>) -> Type { pub fn builtin_type(symbol: Symbol, args: Vec<Type>) -> Type {
Type::Apply(symbol, args) Type::Apply(symbol, args, Region::zero())
} }
#[inline(always)] #[inline(always)]
@ -188,3 +250,85 @@ pub fn num_num(typ: Type) -> Type {
Box::new(alias_content), Box::new(alias_content),
) )
} }
pub trait TypedNumericBound {
fn bounded_range(&self) -> Vec<Variable>;
}
impl TypedNumericBound for IntBound {
fn bounded_range(&self) -> Vec<Variable> {
match self {
IntBound::None => vec![],
IntBound::Exact(w) => vec![match w {
IntWidth::U8 => Variable::U8,
IntWidth::U16 => Variable::U16,
IntWidth::U32 => Variable::U32,
IntWidth::U64 => Variable::U64,
IntWidth::U128 => Variable::U128,
IntWidth::I8 => Variable::I8,
IntWidth::I16 => Variable::I16,
IntWidth::I32 => Variable::I32,
IntWidth::I64 => Variable::I64,
IntWidth::I128 => Variable::I128,
IntWidth::Nat => Variable::NAT,
}],
IntBound::AtLeast { sign, width } => {
let whole_range: &[(IntWidth, Variable)] = match sign {
SignDemand::NoDemand => {
&[
(IntWidth::I8, Variable::I8),
(IntWidth::U8, Variable::U8),
(IntWidth::I16, Variable::I16),
(IntWidth::U16, Variable::U16),
(IntWidth::I32, Variable::I32),
(IntWidth::U32, Variable::U32),
(IntWidth::I64, Variable::I64),
(IntWidth::Nat, Variable::NAT), // FIXME: Nat's order here depends on the platform!
(IntWidth::U64, Variable::U64),
(IntWidth::I128, Variable::I128),
(IntWidth::U128, Variable::U128),
]
}
SignDemand::Signed => &[
(IntWidth::I8, Variable::I8),
(IntWidth::I16, Variable::I16),
(IntWidth::I32, Variable::I32),
(IntWidth::I64, Variable::I64),
(IntWidth::I128, Variable::I128),
],
};
whole_range
.iter()
.skip_while(|(lower_bound, _)| *lower_bound != *width)
.map(|(_, var)| *var)
.collect()
}
}
}
}
impl TypedNumericBound for FloatBound {
fn bounded_range(&self) -> Vec<Variable> {
match self {
FloatBound::None => vec![],
FloatBound::Exact(w) => vec![match w {
FloatWidth::Dec => Variable::DEC,
FloatWidth::F32 => Variable::F32,
FloatWidth::F64 => Variable::F64,
}],
}
}
}
impl TypedNumericBound for NumericBound {
fn bounded_range(&self) -> Vec<Variable> {
match self {
NumericBound::None => vec![],
&NumericBound::AtLeastIntOrFloat { sign, width } => {
let mut range = IntBound::AtLeast { sign, width }.bounded_range();
range.extend_from_slice(&[Variable::F32, Variable::F64, Variable::DEC]);
range
}
}
}
}

View file

@ -1,4 +1,6 @@
use crate::builtins::{empty_list_type, float_literal, int_literal, list_type, str_type}; use crate::builtins::{
empty_list_type, float_literal, int_literal, list_type, num_literal, str_type,
};
use crate::pattern::{constrain_pattern, PatternState}; use crate::pattern::{constrain_pattern, PatternState};
use roc_can::annotation::IntroducedVariables; use roc_can::annotation::IntroducedVariables;
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
@ -77,7 +79,7 @@ fn constrain_untyped_args(
loc_pattern.region, loc_pattern.region,
pattern_expected, pattern_expected,
&mut pattern_state, &mut pattern_state,
false, true,
); );
vars.push(*pattern_var); vars.push(*pattern_var);
@ -96,17 +98,11 @@ pub fn constrain_expr(
expected: Expected<Type>, expected: Expected<Type>,
) -> Constraint { ) -> Constraint {
match expr { match expr {
Int(var, precision, _, _) => int_literal(*var, *precision, expected, region), &Int(var, precision, _, _, bound) => int_literal(var, precision, expected, region, bound),
Num(var, _, _) => exists( &Num(var, _, _, bound) => num_literal(var, expected, region, bound),
vec![*var], &Float(var, precision, _, _, bound) => {
Eq( float_literal(var, precision, expected, region, bound)
crate::builtins::num_num(Type::Variable(*var)), }
expected,
Category::Num,
region,
),
),
Float(var, precision, _, _) => float_literal(*var, *precision, expected, region),
EmptyRecord => constrain_empty_record(region, expected), EmptyRecord => constrain_empty_record(region, expected),
Expr::Record { record_var, fields } => { Expr::Record { record_var, fields } => {
if fields.is_empty() { if fields.is_empty() {
@ -1144,7 +1140,7 @@ fn constrain_def_pattern(env: &Env, loc_pattern: &Loc<Pattern>, expr_type: Type)
loc_pattern.region, loc_pattern.region,
pattern_expected, pattern_expected,
&mut state, &mut state,
false, true,
); );
state state

View file

@ -48,17 +48,16 @@ fn headers_from_annotation_help(
headers: &mut SendMap<Symbol, Loc<Type>>, headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool { ) -> bool {
match pattern { match pattern {
Identifier(symbol) => { Identifier(symbol) | Shadowed(_, _, symbol) => {
headers.insert(*symbol, annotation.clone()); headers.insert(*symbol, annotation.clone());
true true
} }
Underscore Underscore
| Shadowed(_, _)
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)
| NumLiteral(_, _, _) | NumLiteral(..)
| IntLiteral(_, _, _) | IntLiteral(..)
| FloatLiteral(_, _, _) | FloatLiteral(..)
| StrLiteral(_) => true, | StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -159,11 +158,11 @@ pub fn constrain_pattern(
PresenceConstraint::IsOpen, PresenceConstraint::IsOpen,
)); ));
} }
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => { Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) => {
// Neither the _ pattern nor erroneous ones add any constraints. // Neither the _ pattern nor erroneous ones add any constraints.
} }
Identifier(symbol) => { Identifier(symbol) | Shadowed(_, _, symbol) => {
if destruct_position { if destruct_position {
state.constraints.push(Constraint::Present( state.constraints.push(Constraint::Present(
expected.get_type_ref().clone(), expected.get_type_ref().clone(),
@ -179,31 +178,83 @@ pub fn constrain_pattern(
); );
} }
NumLiteral(var, _, _) => { &NumLiteral(var, _, _, bound) => {
state.vars.push(*var); state.vars.push(var);
let num_type = builtins::num_num(Type::Variable(var));
let num_type = builtins::add_numeric_bound_constr(
&mut state.constraints,
num_type,
bound,
region,
Category::Num,
);
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Num, PatternCategory::Num,
builtins::num_num(Type::Variable(*var)), num_type,
expected, expected,
)); ));
} }
IntLiteral(precision_var, _, _) => { &IntLiteral(num_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr(
&mut state.constraints,
Type::Variable(num_var),
bound,
region,
Category::Int,
);
// Link the free num var with the int var and our expectation.
let int_type = builtins::num_int(Type::Variable(precision_var));
state.constraints.push(Constraint::Eq(
num_type, // TODO check me if something breaks!
Expected::NoExpectation(int_type),
Category::Int,
region,
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Int, PatternCategory::Int,
builtins::num_int(Type::Variable(*precision_var)), Type::Variable(num_var),
expected, expected,
)); ));
} }
FloatLiteral(precision_var, _, _) => { &FloatLiteral(num_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr(
&mut state.constraints,
Type::Variable(num_var),
bound,
region,
Category::Float,
);
// Link the free num var with the float var and our expectation.
let float_type = builtins::num_float(Type::Variable(precision_var));
state.constraints.push(Constraint::Eq(
num_type.clone(), // TODO check me if something breaks!
Expected::NoExpectation(float_type),
Category::Float,
region,
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Float, PatternCategory::Float,
builtins::num_float(Type::Variable(*precision_var)), num_type, // TODO check me if something breaks!
expected, expected,
)); ));
} }

View file

@ -3,7 +3,8 @@ use crate::{
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT}, spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
Buf, Buf,
}; };
use roc_parse::ast::{AliasHeader, AssignedField, Expr, Tag, TypeAnnotation}; use roc_parse::ast::{AliasHeader, AssignedField, Collection, Expr, Tag, TypeAnnotation};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc; use roc_region::all::Loc;
/// Does an AST node need parens around it? /// Does an AST node need parens around it?
@ -36,8 +37,8 @@ pub enum Parens {
/// newlines are taken into account. /// newlines are taken into account.
#[derive(PartialEq, Eq, Clone, Copy)] #[derive(PartialEq, Eq, Clone, Copy)]
pub enum Newlines { pub enum Newlines {
Yes,
No, No,
Yes,
} }
pub trait Formattable { pub trait Formattable {
@ -82,6 +83,20 @@ where
} }
} }
impl<'a, T> Formattable for Collection<'a, T>
where
T: Formattable,
{
fn is_multiline(&self) -> bool {
// if there are any comments, they must go on their own line
// because otherwise they'd comment out the closing delimiter
!self.final_comments().is_empty() ||
// if any of the items in the collection are multiline,
// then the whole collection must be multiline
self.items.iter().any(Formattable::is_multiline)
}
}
/// A Located formattable value is also formattable /// A Located formattable value is also formattable
impl<T> Formattable for Loc<T> impl<T> Formattable for Loc<T>
where where
@ -107,6 +122,22 @@ where
} }
} }
impl<'a> Formattable for UppercaseIdent<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
_indent: u16,
) {
buf.push_str((*self).into())
}
}
impl<'a> Formattable for TypeAnnotation<'a> { impl<'a> Formattable for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*; use roc_parse::ast::TypeAnnotation::*;
@ -246,8 +277,9 @@ impl<'a> Formattable for TypeAnnotation<'a> {
} }
As(lhs, _spaces, AliasHeader { name, vars }) => { As(lhs, _spaces, AliasHeader { name, vars }) => {
// TODO use spaces? // TODO use _spaces?
lhs.value.format(buf, indent); lhs.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
buf.spaces(1); buf.spaces(1);
buf.push_str("as"); buf.push_str("as");
buf.spaces(1); buf.spaces(1);

View file

@ -16,11 +16,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
) where ) where
<T as ExtractSpaces<'a>>::Item: Formattable, <T as ExtractSpaces<'a>>::Item: Formattable,
{ {
buf.indent(indent); if items.is_multiline() {
let is_multiline =
items.iter().any(|item| item.is_multiline()) || !items.final_comments().is_empty();
if is_multiline {
let braces_indent = indent; let braces_indent = indent;
let item_indent = braces_indent + INDENT; let item_indent = braces_indent + INDENT;
if newline == Newlines::Yes { if newline == Newlines::Yes {
@ -52,9 +48,12 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
item_indent, item_indent,
); );
buf.newline(); buf.newline();
buf.indent(braces_indent);
buf.push(end);
} else { } else {
// is_multiline == false // is_multiline == false
// there is no comment to add // there is no comment to add
buf.indent(indent);
buf.push(start); buf.push(start);
let mut iter = items.iter().peekable(); let mut iter = items.iter().peekable();
while let Some(item) = iter.next() { while let Some(item) = iter.next() {
@ -68,7 +67,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
if !items.is_empty() { if !items.is_empty() {
buf.spaces(1); buf.spaces(1);
} }
}
buf.indent(indent);
buf.push(end); buf.push(end);
}
} }

View file

@ -46,7 +46,9 @@ impl<'a> Formattable for Def<'a> {
indent + INDENT, indent + INDENT,
); );
} else { } else {
buf.push_str(" : "); buf.spaces(1);
buf.push_str(":");
buf.spaces(1);
loc_annotation.format_with_options( loc_annotation.format_with_options(
buf, buf,
Parens::NotNeeded, Parens::NotNeeded,

View file

@ -6,7 +6,7 @@ use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
use crate::Buf; use crate::Buf;
use roc_module::called_via::{self, BinOp}; use roc_module::called_via::{self, BinOp};
use roc_parse::ast::{ use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch, AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch,
}; };
use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::ast::{StrLiteral, StrSegment};
use roc_region::all::Loc; use roc_region::all::Loc;
@ -27,8 +27,8 @@ impl<'a> Formattable for Expr<'a> {
} }
// These expressions never have newlines // These expressions never have newlines
Float(_) Float(..)
| Num(_) | Num(..)
| NonBase10Int { .. } | NonBase10Int { .. }
| Access(_, _) | Access(_, _)
| AccessorFunction(_) | AccessorFunction(_)
@ -196,17 +196,25 @@ impl<'a> Formattable for Expr<'a> {
buf.push(')'); buf.push(')');
} }
} }
Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { &Num(string) => {
buf.indent(indent);
buf.push_str(string);
}
&Float(string) => {
buf.indent(indent);
buf.push_str(string);
}
GlobalTag(string) | PrivateTag(string) => {
buf.indent(indent); buf.indent(indent);
buf.push_str(string) buf.push_str(string)
} }
NonBase10Int { &NonBase10Int {
base, base,
string, string,
is_negative, is_negative,
} => { } => {
buf.indent(indent); buf.indent(indent);
if *is_negative { if is_negative {
buf.push('-'); buf.push('-');
} }
@ -514,11 +522,15 @@ fn fmt_when<'a, 'buf>(
let patterns = &branch.patterns; let patterns = &branch.patterns;
let expr = &branch.value; let expr = &branch.value;
let (first_pattern, rest) = patterns.split_first().unwrap(); let (first_pattern, rest) = patterns.split_first().unwrap();
let is_multiline = match rest.last() { let is_multiline = if let Some((last_pattern, inner_patterns)) = rest.split_last() {
None => false, !first_pattern.value.extract_spaces().after.is_empty()
Some(last_pattern) => { || !last_pattern.value.extract_spaces().before.is_empty()
first_pattern.region.start().line != last_pattern.region.end().line || inner_patterns.iter().any(|p| {
} let spaces = p.value.extract_spaces();
!spaces.before.is_empty() || !spaces.after.is_empty()
})
} else {
false
}; };
fmt_pattern( fmt_pattern(

View file

@ -11,6 +11,7 @@ pub mod spaces;
use bumpalo::{collections::String, Bump}; use bumpalo::{collections::String, Bump};
#[derive(Debug)]
pub struct Buf<'a> { pub struct Buf<'a> {
text: String<'a>, text: String<'a>,
spaces_to_flush: usize, spaces_to_flush: usize,

View file

@ -5,9 +5,10 @@ use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf; use crate::Buf;
use roc_parse::ast::{Collection, Module, Spaced}; use roc_parse::ast::{Collection, Module, Spaced};
use roc_parse::header::{ use roc_parse::header::{
AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
}; };
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc; use roc_region::all::Loc;
pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) { pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) {
@ -21,6 +22,9 @@ pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) {
Module::Platform { header } => { Module::Platform { header } => {
fmt_platform_header(buf, header); fmt_platform_header(buf, header);
} }
Module::Hosted { header } => {
fmt_hosted_header(buf, header);
}
} }
} }
@ -49,6 +53,45 @@ pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a Interface
fmt_imports(buf, header.imports, indent); fmt_imports(buf, header.imports, indent);
} }
pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("hosted");
// module name
fmt_default_spaces(buf, header.after_hosted_keyword, indent);
buf.push_str(header.name.value.as_str());
// exposes
fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent);
buf.push_str("exposes");
fmt_default_spaces(buf, header.after_exposes, indent);
fmt_exposes(buf, header.exposes, indent);
// imports
fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent);
buf.push_str("imports");
fmt_default_spaces(buf, header.after_imports, indent);
fmt_imports(buf, header.imports, indent);
// generates
fmt_default_spaces(buf, header.before_generates, indent);
buf.indent(indent);
buf.push_str("generates");
fmt_default_spaces(buf, header.after_generates, indent);
buf.push_str(header.generates.into());
// with
fmt_default_spaces(buf, header.before_with, indent);
buf.indent(indent);
buf.push_str("with");
fmt_default_spaces(buf, header.after_with, indent);
fmt_exposes(buf, header.generates_with, indent);
}
pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) { pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) {
let indent = INDENT; let indent = INDENT;
buf.indent(0); buf.indent(0);
@ -76,7 +119,7 @@ pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>)
buf.indent(indent); buf.indent(indent);
buf.push_str("provides"); buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, indent); fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, indent); fmt_provides(buf, header.provides, header.provides_types, indent);
fmt_default_spaces(buf, header.before_to, indent); fmt_default_spaces(buf, header.before_to, indent);
buf.indent(indent); buf.indent(indent);
buf.push_str("to"); buf.push_str("to");
@ -126,9 +169,7 @@ pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHe
buf.indent(indent); buf.indent(indent);
buf.push_str("provides"); buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, indent); fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, indent); fmt_provides(buf, header.provides, None, indent);
fmt_effects(buf, &header.effects, indent);
} }
fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) { fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) {
@ -140,22 +181,6 @@ fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>,
buf.push_str(" }"); buf.push_str(" }");
} }
fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16) {
fmt_default_spaces(buf, effects.spaces_before_effects_keyword, indent);
buf.indent(indent);
buf.push_str("effects");
fmt_default_spaces(buf, effects.spaces_after_effects_keyword, indent);
buf.indent(indent);
buf.push_str(effects.effect_shortname);
buf.push('.');
buf.push_str(effects.effect_type_name);
fmt_default_spaces(buf, effects.spaces_after_type_name, indent);
fmt_collection(buf, indent, '{', '}', effects.entries, Newlines::No)
}
impl<'a> Formattable for TypedIdent<'a> { impl<'a> Formattable for TypedIdent<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
false false
@ -179,8 +204,14 @@ fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName, _indent: u16)
impl<'a, T: Formattable> Formattable for Spaced<'a, T> { impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
// TODO use Spaced::*;
false
match self {
Item(formattable) => formattable.is_multiline(),
SpaceBefore(formattable, spaces) | SpaceAfter(formattable, spaces) => {
!spaces.is_empty() || formattable.is_multiline()
}
}
} }
fn format_with_options<'buf>( fn format_with_options<'buf>(
@ -206,32 +237,25 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
} }
} }
impl<'a> Formattable for PlatformRigid<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
buf.push_str(self.rigid);
buf.push_str("=>");
buf.push_str(self.alias);
}
}
fn fmt_imports<'a, 'buf>( fn fmt_imports<'a, 'buf>(
buf: &mut Buf<'buf>, buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>, loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
indent: u16, indent: u16,
) { ) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) fmt_collection(buf, indent + INDENT, '[', ']', loc_entries, Newlines::No)
} }
fn fmt_provides<'a, 'buf>( fn fmt_provides<'a, 'buf>(
buf: &mut Buf<'buf>, buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, loc_exposed_names: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
loc_provided_types: Option<Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>>,
indent: u16, indent: u16,
) { ) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) fmt_collection(buf, indent, '[', ']', loc_exposed_names, Newlines::No);
if let Some(loc_provided) = loc_provided_types {
fmt_default_spaces(buf, &[], indent);
fmt_collection(buf, indent + INDENT, '{', '}', loc_provided, Newlines::No);
}
} }
fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) { fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) {
@ -248,7 +272,7 @@ fn fmt_exposes<'buf, N: Formattable + Copy>(
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>, loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
indent: u16, indent: u16,
) { ) {
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No) fmt_collection(buf, indent + INDENT, '[', ']', loc_entries, Newlines::No)
} }
pub trait FormatName { pub trait FormatName {
@ -282,7 +306,8 @@ impl<'a> Formattable for ExposedName<'a> {
false false
} }
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) { fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
buf.indent(indent);
buf.push_str(self.as_str()); buf.push_str(self.as_str());
} }
} }
@ -330,6 +355,8 @@ fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, i
fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) { fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) {
use roc_parse::header::ImportsEntry::*; use roc_parse::header::ImportsEntry::*;
buf.indent(indent);
match entry { match entry {
Module(module, loc_exposes_entries) => { Module(module, loc_exposes_entries) => {
buf.push_str(module.as_str()); buf.push_str(module.as_str());

View file

@ -31,9 +31,9 @@ impl<'a> Formattable for Pattern<'a> {
| Pattern::GlobalTag(_) | Pattern::GlobalTag(_)
| Pattern::PrivateTag(_) | Pattern::PrivateTag(_)
| Pattern::Apply(_, _) | Pattern::Apply(_, _)
| Pattern::NumLiteral(_) | Pattern::NumLiteral(..)
| Pattern::NonBase10Literal { .. } | Pattern::NonBase10Literal { .. }
| Pattern::FloatLiteral(_) | Pattern::FloatLiteral(..)
| Pattern::StrLiteral(_) | Pattern::StrLiteral(_)
| Pattern::Underscore(_) | Pattern::Underscore(_)
| Pattern::Malformed(_) | Pattern::Malformed(_)
@ -116,17 +116,17 @@ impl<'a> Formattable for Pattern<'a> {
loc_pattern.format(buf, indent); loc_pattern.format(buf, indent);
} }
NumLiteral(string) => { &NumLiteral(string) => {
buf.indent(indent); buf.indent(indent);
buf.push_str(string); buf.push_str(string);
} }
NonBase10Literal { &NonBase10Literal {
base, base,
string, string,
is_negative, is_negative,
} => { } => {
buf.indent(indent); buf.indent(indent);
if *is_negative { if is_negative {
buf.push('-'); buf.push('-');
} }
@ -139,7 +139,7 @@ impl<'a> Formattable for Pattern<'a> {
buf.push_str(string); buf.push_str(string);
} }
FloatLiteral(string) => { &FloatLiteral(string) => {
buf.indent(indent); buf.indent(indent);
buf.push_str(string); buf.push_str(string);
} }

View file

@ -2640,6 +2640,25 @@ mod test_fmt {
)); ));
} }
#[test]
fn multi_line_interface() {
module_formats_same(indoc!(
r#"
interface Foo
exposes
[
Stuff,
Things,
somethingElse,
]
imports
[
Blah,
Baz.{ stuff, things },
]"#
));
}
#[test] #[test]
fn single_line_app() { fn single_line_app() {
module_formats_same(indoc!( module_formats_same(indoc!(
@ -2652,20 +2671,47 @@ mod test_fmt {
fn single_line_platform() { fn single_line_platform() {
module_formats_same( module_formats_same(
"platform \"folkertdev/foo\" \ "platform \"folkertdev/foo\" \
requires { model=>Model, msg=>Msg } { main : Effect {} } \ requires { Model, Msg } { main : Effect {} } \
exposes [] \ exposes [] \
packages {} \ packages {} \
imports [ Task.{ Task } ] \ imports [ Task.{ Task } ] \
provides [ mainForHost ] \ provides [ mainForHost ]",
effects fx.Effect \
{ \
putLine : Str -> Effect {}, \
putInt : I64 -> Effect {}, \
getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } \
}",
); );
} }
#[test]
fn single_line_hosted() {
module_formats_same(indoc!(
r#"
hosted Foo exposes [] imports [] generates Bar with []"#
));
}
#[test]
fn multi_line_hosted() {
module_formats_same(indoc!(
r#"
hosted Foo
exposes
[
Stuff,
Things,
somethingElse,
]
imports
[
Blah,
Baz.{ stuff, things },
]
generates Bar with
[
map,
after,
loop,
]"#
));
}
/// Annotations and aliases /// Annotations and aliases
#[test] #[test]

View file

@ -16,7 +16,8 @@ roc_builtins = { path = "../builtins" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_reporting = { path = "../../reporting" } roc_target = { path = "../roc_target" }
roc_error_macros = { path = "../../error_macros" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
target-lexicon = "0.12.2" target-lexicon = "0.12.2"
# TODO: Deal with the update of object to 0.27. # TODO: Deal with the update of object to 0.27.

View file

@ -3,9 +3,9 @@ use crate::Relocation;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use packed_struct::prelude::*; use packed_struct::prelude::*;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::Layout; use roc_mono::layout::Layout;
use roc_reporting::internal_error;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -2,17 +2,18 @@ use crate::{Backend, Env, Relocation};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use roc_reporting::internal_error; use roc_target::TargetInfo;
use std::marker::PhantomData; use std::marker::PhantomData;
pub mod aarch64; pub mod aarch64;
pub mod x86_64; pub mod x86_64;
const PTR_SIZE: u32 = 8; const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64();
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> { pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
const BASE_PTR_REG: GeneralReg; const BASE_PTR_REG: GeneralReg;
@ -308,7 +309,7 @@ pub fn new_backend_64bit<
phantom_cc: PhantomData, phantom_cc: PhantomData,
env, env,
interns, interns,
helper_proc_gen: CodeGenHelp::new(env.arena, IntWidth::I64, env.module_id), helper_proc_gen: CodeGenHelp::new(env.arena, TARGET_INFO, env.module_id),
helper_proc_symbols: bumpalo::vec![in env.arena], helper_proc_symbols: bumpalo::vec![in env.arena],
proc_name: None, proc_name: None,
is_self_recursive: None, is_self_recursive: None,
@ -974,7 +975,7 @@ impl<
} }
fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) {
let struct_size = layout.stack_size(PTR_SIZE); let struct_size = layout.stack_size(TARGET_INFO);
if let Layout::Struct(field_layouts) = layout { if let Layout::Struct(field_layouts) = layout {
if struct_size > 0 { if struct_size > 0 {
@ -991,7 +992,7 @@ impl<
let mut current_offset = offset; let mut current_offset = offset;
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) { for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
self.copy_symbol_to_stack_offset(current_offset, field, field_layout); self.copy_symbol_to_stack_offset(current_offset, field, field_layout);
let field_size = field_layout.stack_size(PTR_SIZE); let field_size = field_layout.stack_size(TARGET_INFO);
current_offset += field_size as i32; current_offset += field_size as i32;
} }
} else { } else {
@ -1029,14 +1030,14 @@ impl<
if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) { if let Some(SymbolStorage::Base { offset, .. }) = self.symbol_storage_map.get(structure) {
let mut data_offset = *offset; let mut data_offset = *offset;
for i in 0..index { for i in 0..index {
let field_size = field_layouts[i as usize].stack_size(PTR_SIZE); let field_size = field_layouts[i as usize].stack_size(TARGET_INFO);
data_offset += field_size as i32; data_offset += field_size as i32;
} }
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
*sym, *sym,
SymbolStorage::Base { SymbolStorage::Base {
offset: data_offset, offset: data_offset,
size: field_layouts[index as usize].stack_size(PTR_SIZE), size: field_layouts[index as usize].stack_size(TARGET_INFO),
owned: false, owned: false,
}, },
); );
@ -1569,10 +1570,10 @@ impl<
{ {
debug_assert_eq!( debug_assert_eq!(
*size, *size,
layout.stack_size(PTR_SIZE), layout.stack_size(TARGET_INFO),
"expected struct to have same size as data being stored in it" "expected struct to have same size as data being stored in it"
); );
for i in 0..layout.stack_size(PTR_SIZE) as i32 { for i in 0..layout.stack_size(TARGET_INFO) as i32 {
ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i); ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i);
ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg); ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg);
} }

View file

@ -1,13 +1,13 @@
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE}; use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, TARGET_INFO};
use crate::{ use crate::{
single_register_builtins, single_register_floats, single_register_integers, Relocation, single_register_builtins, single_register_floats, single_register_integers, Relocation,
}; };
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use roc_reporting::internal_error;
// Not sure exactly how I want to represent registers. // Not sure exactly how I want to represent registers.
// If we want max speed, we would likely make them structs that impl the same trait to avoid ifs. // If we want max speed, we would likely make them structs that impl the same trait to avoid ifs.
@ -451,7 +451,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
// TODO: This may need to be more complex/extended to fully support the calling convention. // TODO: This may need to be more complex/extended to fully support the calling convention.
// details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf // details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
ret_layout.stack_size(PTR_SIZE) > 16 ret_layout.stack_size(TARGET_INFO) > 16
} }
} }
@ -775,7 +775,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool { fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
// TODO: This is not fully correct there are some exceptions for "vector" types. // TODO: This is not fully correct there are some exceptions for "vector" types.
// details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values // details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values
ret_layout.stack_size(PTR_SIZE) > 8 ret_layout.stack_size(TARGET_INFO) > 8
} }
} }

View file

@ -5,8 +5,9 @@
use bumpalo::{collections::Vec, Bump}; use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::ident::{ModuleName, TagName}; use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{ use roc_mono::ir::{
@ -14,7 +15,6 @@ use roc_mono::ir::{
SelfRecursive, Stmt, SelfRecursive, Stmt,
}; };
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds};
use roc_reporting::internal_error;
mod generic64; mod generic64;
mod object_builder; mod object_builder;
@ -260,8 +260,9 @@ trait Backend<'a> {
ret_layout, ret_layout,
.. ..
} => { } => {
// If this function is just a lowlevel wrapper, then inline it if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) =
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { LowLevelWrapperType::from_symbol(*func_sym)
{
self.build_run_low_level( self.build_run_low_level(
sym, sym,
&lowlevel, &lowlevel,

View file

@ -8,11 +8,11 @@ use object::{
SymbolFlags, SymbolKind, SymbolScope, SymbolFlags, SymbolKind, SymbolScope,
}; };
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::symbol; use roc_module::symbol;
use roc_module::symbol::Interns; use roc_module::symbol::Interns;
use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
use roc_reporting::internal_error;
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple}; use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
// This is used by some code below which is currently commented out. // This is used by some code below which is currently commented out.

View file

@ -10,8 +10,9 @@ edition = "2018"
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_reporting = { path = "../../reporting" } roc_error_macros = { path = "../../error_macros" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }

File diff suppressed because it is too large Load diff

View file

@ -17,14 +17,15 @@ use inkwell::AddressSpace;
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutIds};
use roc_target::TargetInfo;
#[repr(transparent)] #[repr(transparent)]
struct Alignment(u8); struct Alignment(u8);
impl Alignment { impl Alignment {
fn from_key_value_layout(key: &Layout, value: &Layout, ptr_bytes: u32) -> Alignment { fn from_key_value_layout(key: &Layout, value: &Layout, target_info: TargetInfo) -> Alignment {
let key_align = key.alignment_bytes(ptr_bytes); let key_align = key.alignment_bytes(target_info);
let value_align = value.alignment_bytes(ptr_bytes); let value_align = value.alignment_bytes(target_info);
let mut bits = key_align.max(value_align) as u8; let mut bits = key_align.max(value_align) as u8;
@ -105,15 +106,15 @@ pub fn dict_insert<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr"); let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr");
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
@ -162,15 +163,15 @@ pub fn dict_remove<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr"); let result_ptr = builder.build_alloca(zig_dict_type(env), "result_ptr");
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
@ -218,13 +219,13 @@ pub fn dict_contains<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
@ -264,13 +265,13 @@ pub fn dict_get<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
@ -366,13 +367,13 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
) { ) {
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let (key_fn, value_fn) = match rc_operation { let (key_fn, value_fn) = match rc_operation {
@ -412,13 +413,13 @@ pub fn dict_keys<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout); let inc_key_fn = build_inc_wrapper(env, layout_ids, key_layout);
@ -454,19 +455,18 @@ fn pass_dict_c_abi<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
dict: BasicValueEnum<'ctx>, dict: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
match env.ptr_bytes { match env.target_info.ptr_width() {
4 => { roc_target::PtrWidth::Bytes4 => {
let target_type = env.context.custom_width_int_type(96).into(); let target_type = env.context.custom_width_int_type(96).into();
complex_bitcast(env.builder, dict, target_type, "to_i96") complex_bitcast(env.builder, dict, target_type, "to_i96")
} }
8 => { roc_target::PtrWidth::Bytes8 => {
let dict_ptr = env.builder.build_alloca(zig_dict_type(env), "dict_ptr"); let dict_ptr = env.builder.build_alloca(zig_dict_type(env), "dict_ptr");
env.builder.build_store(dict_ptr, dict); env.builder.build_store(dict_ptr, dict);
dict_ptr.into() dict_ptr.into()
} }
_ => unreachable!(),
} }
} }
@ -483,13 +483,13 @@ pub fn dict_union<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
@ -576,13 +576,13 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
@ -631,7 +631,7 @@ pub fn dict_walk<'a, 'ctx, 'env>(
let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr"); let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr");
env.builder.build_store(accum_ptr, accum); env.builder.build_store(accum_ptr, accum);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let output_ptr = builder.build_alloca(accum_bt, "output_ptr"); let output_ptr = builder.build_alloca(accum_bt, "output_ptr");
@ -671,13 +671,13 @@ pub fn dict_values<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env let value_width = env
.ptr_int() .ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(value_layout.stack_size(env.target_info) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes); let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout); let inc_value_fn = build_inc_wrapper(env, layout_ids, value_layout);
@ -729,14 +729,14 @@ pub fn set_from_list<'a, 'ctx, 'env>(
let key_width = env let key_width = env
.ptr_int() .ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false); .const_int(key_layout.stack_size(env.target_info) as u64, false);
let value_width = env.ptr_int().const_zero(); let value_width = env.ptr_int().const_zero();
let result_alloca = builder.build_alloca(zig_dict_type(env), "result_alloca"); let result_alloca = builder.build_alloca(zig_dict_type(env), "result_alloca");
let alignment = let alignment =
Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.ptr_bytes); Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.target_info);
let alignment_iv = alignment.as_int_value(env.context); let alignment_iv = alignment.as_int_value(env.context);
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout); let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);

View file

@ -120,7 +120,7 @@ fn hash_builtin<'a, 'ctx, 'env>(
builtin: &Builtin<'a>, builtin: &Builtin<'a>,
when_recursive: WhenRecursive<'a>, when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let ptr_bytes = env.ptr_bytes; let ptr_bytes = env.target_info;
match builtin { match builtin {
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => { Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => {
@ -246,7 +246,7 @@ fn hash_struct<'a, 'ctx, 'env>(
when_recursive: WhenRecursive<'a>, when_recursive: WhenRecursive<'a>,
field_layouts: &[Layout<'a>], field_layouts: &[Layout<'a>],
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let ptr_bytes = env.ptr_bytes; let ptr_bytes = env.target_info;
let layout = Layout::Struct(field_layouts); let layout = Layout::Struct(field_layouts);
@ -423,7 +423,7 @@ fn hash_tag<'a, 'ctx, 'env>(
env, env,
seed, seed,
hash_bytes, hash_bytes,
tag_id_layout.stack_size(env.ptr_bytes), tag_id_layout.stack_size(env.target_info),
); );
// hash the tag data // hash the tag data
@ -474,7 +474,7 @@ fn hash_tag<'a, 'ctx, 'env>(
env, env,
seed, seed,
hash_bytes, hash_bytes,
tag_id_layout.stack_size(env.ptr_bytes), tag_id_layout.stack_size(env.target_info),
); );
// hash the tag data // hash the tag data
@ -574,7 +574,7 @@ fn hash_tag<'a, 'ctx, 'env>(
env, env,
seed, seed,
hash_bytes, hash_bytes,
tag_id_layout.stack_size(env.ptr_bytes), tag_id_layout.stack_size(env.target_info),
); );
// hash tag data // hash tag data

View file

@ -87,7 +87,7 @@ pub fn layout_width<'a, 'ctx, 'env>(
layout: &Layout<'a>, layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
env.ptr_int() env.ptr_int()
.const_int(layout.stack_size(env.ptr_bytes) as u64, false) .const_int(layout.stack_size(env.target_info) as u64, false)
.into() .into()
} }
@ -1253,17 +1253,17 @@ pub fn allocate_list<'a, 'ctx, 'env>(
let ctx = env.context; let ctx = env.context;
let len_type = env.ptr_int(); let len_type = env.ptr_int();
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; let elem_bytes = elem_layout.stack_size(env.target_info) as u64;
let bytes_per_element = len_type.const_int(elem_bytes, false); let bytes_per_element = len_type.const_int(elem_bytes, false);
let number_of_data_bytes = let number_of_data_bytes =
builder.build_int_mul(bytes_per_element, number_of_elements, "data_length"); builder.build_int_mul(bytes_per_element, number_of_elements, "data_length");
// the refcount of a new list is initially 1 // the refcount of a new list is initially 1
// we assume that the list is indeed used (dead variables are eliminated) // we assume that the list is indeed used (dead variables are eliminated)
let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.ptr_bytes); let rc1 = crate::llvm::refcounting::refcount_1(ctx, env.target_info);
let basic_type = basic_type_from_layout(env, elem_layout); let basic_type = basic_type_from_layout(env, elem_layout);
let alignment_bytes = elem_layout.alignment_bytes(env.ptr_bytes); let alignment_bytes = elem_layout.alignment_bytes(env.target_info);
allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes, rc1) allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes, rc1)
} }

View file

@ -10,6 +10,7 @@ use morphic_lib::UpdateMode;
use roc_builtins::bitcode::{self, IntWidth}; use roc_builtins::bitcode::{self, IntWidth};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use roc_target::PtrWidth;
use super::build::load_symbol; use super::build::load_symbol;
@ -79,10 +80,9 @@ fn str_symbol_to_c_abi<'a, 'ctx, 'env>(
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let string = load_symbol(scope, &symbol); let string = load_symbol(scope, &symbol);
let target_type = match env.ptr_bytes { let target_type = match env.target_info.ptr_width() {
8 => env.context.i128_type().into(), PtrWidth::Bytes8 => env.context.i128_type().into(),
4 => env.context.i64_type().into(), PtrWidth::Bytes4 => env.context.i64_type().into(),
_ => unreachable!(),
}; };
complex_bitcast(env.builder, string, target_type, "str_to_c_abi").into_int_value() complex_bitcast(env.builder, string, target_type, "str_to_c_abi").into_int_value()
@ -96,10 +96,9 @@ pub fn str_to_c_abi<'a, 'ctx, 'env>(
env.builder.build_store(cell, value); env.builder.build_store(cell, value);
let target_type = match env.ptr_bytes { let target_type = match env.target_info.ptr_width() {
8 => env.context.i128_type(), PtrWidth::Bytes8 => env.context.i128_type(),
4 => env.context.i64_type(), PtrWidth::Bytes4 => env.context.i64_type(),
_ => unreachable!(),
}; };
let target_type_ptr = env let target_type_ptr = env
@ -310,20 +309,19 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>(
let builder = env.builder; let builder = env.builder;
let ctx = env.context; let ctx = env.context;
let fields = match env.ptr_bytes { let fields = match env.target_info.ptr_width() {
8 | 4 => [ PtrWidth::Bytes4 | PtrWidth::Bytes8 => [
env.ptr_int().into(), env.ptr_int().into(),
super::convert::zig_str_type(env).into(), super::convert::zig_str_type(env).into(),
env.context.bool_type().into(), env.context.bool_type().into(),
ctx.i8_type().into(), ctx.i8_type().into(),
], ],
_ => unreachable!(),
}; };
let record_type = env.context.struct_type(&fields, false); let record_type = env.context.struct_type(&fields, false);
match env.ptr_bytes { match env.target_info.ptr_width() {
8 | 4 => { PtrWidth::Bytes4 | PtrWidth::Bytes8 => {
let result_ptr_cast = env let result_ptr_cast = env
.builder .builder
.build_bitcast( .build_bitcast(
@ -337,7 +335,6 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>(
.build_load(result_ptr_cast, "load_utf8_validate_bytes_result") .build_load(result_ptr_cast, "load_utf8_validate_bytes_result")
.into_struct_value() .into_struct_value()
} }
_ => unreachable!(),
} }
} }

View file

@ -4,6 +4,7 @@ use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
use inkwell::AddressSpace; use inkwell::AddressSpace;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::layout::{Builtin, Layout, UnionLayout};
use roc_target::TargetInfo;
fn basic_type_from_record<'a, 'ctx, 'env>( fn basic_type_from_record<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>, env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
@ -36,7 +37,7 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
match union_layout { match union_layout {
NonRecursive(tags) => { NonRecursive(tags) => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); let data = block_of_memory_slices(env.context, tags, env.target_info);
env.context.struct_type(&[data, tag_id_type], false).into() env.context.struct_type(&[data, tag_id_type], false).into()
} }
@ -44,9 +45,9 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
| NullableWrapped { | NullableWrapped {
other_tags: tags, .. other_tags: tags, ..
} => { } => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); let data = block_of_memory_slices(env.context, tags, env.target_info);
if union_layout.stores_tag_id_as_data(env.ptr_bytes) { if union_layout.stores_tag_id_as_data(env.target_info) {
env.context env.context
.struct_type(&[data, tag_id_type], false) .struct_type(&[data, tag_id_type], false)
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)
@ -56,11 +57,12 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
} }
} }
NullableUnwrapped { other_fields, .. } => { NullableUnwrapped { other_fields, .. } => {
let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); let block =
block_of_memory_slices(env.context, &[other_fields], env.target_info);
block.ptr_type(AddressSpace::Generic).into() block.ptr_type(AddressSpace::Generic).into()
} }
NonNullableUnwrapped(fields) => { NonNullableUnwrapped(fields) => {
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); let block = block_of_memory_slices(env.context, &[fields], env.target_info);
block.ptr_type(AddressSpace::Generic).into() block.ptr_type(AddressSpace::Generic).into()
} }
} }
@ -95,7 +97,7 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
match union_layout { match union_layout {
NonRecursive(tags) => { NonRecursive(tags) => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); let data = block_of_memory_slices(env.context, tags, env.target_info);
let struct_type = env.context.struct_type(&[data, tag_id_type], false); let struct_type = env.context.struct_type(&[data, tag_id_type], false);
struct_type.ptr_type(AddressSpace::Generic).into() struct_type.ptr_type(AddressSpace::Generic).into()
@ -104,9 +106,9 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
| NullableWrapped { | NullableWrapped {
other_tags: tags, .. other_tags: tags, ..
} => { } => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes); let data = block_of_memory_slices(env.context, tags, env.target_info);
if union_layout.stores_tag_id_as_data(env.ptr_bytes) { if union_layout.stores_tag_id_as_data(env.target_info) {
env.context env.context
.struct_type(&[data, tag_id_type], false) .struct_type(&[data, tag_id_type], false)
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)
@ -116,11 +118,12 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
} }
} }
NullableUnwrapped { other_fields, .. } => { NullableUnwrapped { other_fields, .. } => {
let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); let block =
block_of_memory_slices(env.context, &[other_fields], env.target_info);
block.ptr_type(AddressSpace::Generic).into() block.ptr_type(AddressSpace::Generic).into()
} }
NonNullableUnwrapped(fields) => { NonNullableUnwrapped(fields) => {
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes); let block = block_of_memory_slices(env.context, &[fields], env.target_info);
block.ptr_type(AddressSpace::Generic).into() block.ptr_type(AddressSpace::Generic).into()
} }
} }
@ -188,13 +191,13 @@ pub fn float_type_from_float_width<'a, 'ctx, 'env>(
pub fn block_of_memory_slices<'ctx>( pub fn block_of_memory_slices<'ctx>(
context: &'ctx Context, context: &'ctx Context,
layouts: &[&[Layout<'_>]], layouts: &[&[Layout<'_>]],
ptr_bytes: u32, target_info: TargetInfo,
) -> BasicTypeEnum<'ctx> { ) -> BasicTypeEnum<'ctx> {
let mut union_size = 0; let mut union_size = 0;
for tag in layouts { for tag in layouts {
let mut total = 0; let mut total = 0;
for layout in tag.iter() { for layout in tag.iter() {
total += layout.stack_size(ptr_bytes as u32); total += layout.stack_size(target_info);
} }
union_size = union_size.max(total); union_size = union_size.max(total);
@ -203,32 +206,16 @@ pub fn block_of_memory_slices<'ctx>(
block_of_memory_help(context, union_size) block_of_memory_help(context, union_size)
} }
pub fn union_data_is_struct<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
layouts: &[Layout<'_>],
) -> StructType<'ctx> {
let data_type = basic_type_from_record(env, layouts);
union_data_is_struct_type(env.context, data_type.into_struct_type())
}
pub fn union_data_is_struct_type<'ctx>(
context: &'ctx Context,
struct_type: StructType<'ctx>,
) -> StructType<'ctx> {
let tag_id_type = context.i64_type();
context.struct_type(&[struct_type.into(), tag_id_type.into()], false)
}
pub fn block_of_memory<'ctx>( pub fn block_of_memory<'ctx>(
context: &'ctx Context, context: &'ctx Context,
layout: &Layout<'_>, layout: &Layout<'_>,
ptr_bytes: u32, target_info: TargetInfo,
) -> BasicTypeEnum<'ctx> { ) -> BasicTypeEnum<'ctx> {
// TODO make this dynamic // TODO make this dynamic
let mut union_size = layout.stack_size(ptr_bytes as u32); let mut union_size = layout.stack_size(target_info);
if let Layout::Union(UnionLayout::NonRecursive { .. }) = layout { if let Layout::Union(UnionLayout::NonRecursive { .. }) = layout {
union_size -= ptr_bytes; union_size -= target_info.ptr_width() as u32;
} }
block_of_memory_help(context, union_size) block_of_memory_help(context, union_size)
@ -267,16 +254,10 @@ fn block_of_memory_help(context: &Context, union_size: u32) -> BasicTypeEnum<'_>
} }
/// The int type that the C ABI turns our RocList/RocStr into /// The int type that the C ABI turns our RocList/RocStr into
pub fn str_list_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> { pub fn str_list_int(ctx: &Context, target_info: TargetInfo) -> IntType<'_> {
match ptr_bytes { match target_info.ptr_width() {
1 => ctx.i16_type(), roc_target::PtrWidth::Bytes4 => ctx.i64_type(),
2 => ctx.i32_type(), roc_target::PtrWidth::Bytes8 => ctx.i128_type(),
4 => ctx.i64_type(),
8 => ctx.i128_type(),
_ => panic!(
"Invalid target: Roc does't support compiling to {}-bit systems.",
ptr_bytes * 8
),
} }
} }

View file

@ -42,6 +42,43 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
} }
} }
// roc_memcpy
{
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_memcpy").unwrap();
let mut params = fn_val.get_param_iter();
let dest_arg = params.next().unwrap();
let dest_alignment = 1;
let src_arg = params.next().unwrap();
let src_alignment = 1;
let bytes_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
// Add a basic block for the entry point
let entry = ctx.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
// Call libc memcpy()
let _retval = builder
.build_memcpy(
dest_arg.into_pointer_value(),
dest_alignment,
src_arg.into_pointer_value(),
src_alignment,
bytes_arg.into_int_value(),
)
.unwrap();
builder.build_return(None);
if cfg!(debug_assertions) {
crate::llvm::build::verify_fn(fn_val);
}
}
// roc_realloc // roc_realloc
{ {
let libc_realloc_val = { let libc_realloc_val = {
@ -175,7 +212,9 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
let buffer = crate::llvm::build::get_sjlj_buffer(env); let buffer = crate::llvm::build::get_sjlj_buffer(env);
// write our error message pointer // write our error message pointer
let index = env.ptr_int().const_int(3 * env.ptr_bytes as u64, false); let index = env
.ptr_int()
.const_int(3 * env.target_info.ptr_width() as u64, false);
let message_buffer_raw = let message_buffer_raw =
unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") }; unsafe { builder.build_gep(buffer, &[index], "raw_msg_buffer_ptr") };
let message_buffer = builder.build_bitcast( let message_buffer = builder.build_bitcast(

View file

@ -18,21 +18,16 @@ use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns; use roc_module::symbol::Interns;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
use roc_target::TargetInfo;
/// "Infinite" reference count, for static values /// "Infinite" reference count, for static values
/// Ref counts are encoded as negative numbers where isize::MIN represents 1 /// Ref counts are encoded as negative numbers where isize::MIN represents 1
pub const REFCOUNT_MAX: usize = 0_usize; pub const REFCOUNT_MAX: usize = 0_usize;
pub fn refcount_1(ctx: &Context, ptr_bytes: u32) -> IntValue<'_> { pub fn refcount_1(ctx: &Context, target_info: TargetInfo) -> IntValue<'_> {
match ptr_bytes { match target_info.ptr_width() {
1 => ctx.i8_type().const_int(i8::MIN as u64, false), roc_target::PtrWidth::Bytes4 => ctx.i32_type().const_int(i32::MIN as u64, false),
2 => ctx.i16_type().const_int(i16::MIN as u64, false), roc_target::PtrWidth::Bytes8 => ctx.i64_type().const_int(i64::MIN as u64, false),
4 => ctx.i32_type().const_int(i32::MIN as u64, false),
8 => ctx.i64_type().const_int(i64::MIN as u64, false),
_ => panic!(
"Invalid target: Roc does't support compiling to {}-bit systems.",
ptr_bytes * 8
),
} }
} }
@ -98,7 +93,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> { pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
let current = self.get_refcount(env); let current = self.get_refcount(env);
let one = refcount_1(env.context, env.ptr_bytes); let one = refcount_1(env.context, env.target_info);
env.builder env.builder
.build_int_compare(IntPredicate::EQ, current, one, "is_one") .build_int_compare(IntPredicate::EQ, current, one, "is_one")
@ -163,8 +158,8 @@ impl<'ctx> PointerToRefcount<'ctx> {
pub fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) { pub fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) {
let alignment = layout let alignment = layout
.allocation_alignment_bytes(env.ptr_bytes) .allocation_alignment_bytes(env.target_info)
.max(env.ptr_bytes); .max(env.target_info.ptr_width() as u32);
let context = env.context; let context = env.context;
let block = env.builder.get_insert_block().expect("to be in a function"); let block = env.builder.get_insert_block().expect("to be in a function");
@ -1192,7 +1187,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
debug_assert!(arg_val.is_pointer_value()); debug_assert!(arg_val.is_pointer_value());
let current_tag_id = get_tag_id(env, fn_val, &union_layout, arg_val); let current_tag_id = get_tag_id(env, fn_val, &union_layout, arg_val);
let value_ptr = if union_layout.stores_tag_id_in_pointer(env.ptr_bytes) { let value_ptr = if union_layout.stores_tag_id_in_pointer(env.target_info) {
tag_pointer_clear_tag_id(env, arg_val.into_pointer_value()) tag_pointer_clear_tag_id(env, arg_val.into_pointer_value())
} else { } else {
arg_val.into_pointer_value() arg_val.into_pointer_value()

View file

@ -38,10 +38,23 @@ macro_rules! run_jit_function {
}}; }};
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{ ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
run_jit_function!($lib, $main_fn_name, $ty, $transform, $errors, &[])
}};
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr, $expect_failures:expr) => {{
use inkwell::context::Context; use inkwell::context::Context;
use roc_builtins::bitcode;
use roc_gen_llvm::run_roc::RocCallResult; use roc_gen_llvm::run_roc::RocCallResult;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
#[derive(Debug, Copy, Clone)]
#[repr(C)]
struct Failure {
start_line: u32,
end_line: u32,
start_col: u16,
end_col: u16,
}
unsafe { unsafe {
let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<$ty>) -> ()> = let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<$ty>) -> ()> =
$lib.get($main_fn_name.as_bytes()) $lib.get($main_fn_name.as_bytes())
@ -49,11 +62,50 @@ macro_rules! run_jit_function {
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored"); .expect("errored");
let mut result = MaybeUninit::uninit(); #[repr(C)]
struct Failures {
failures: *const Failure,
count: usize,
}
main(result.as_mut_ptr()); impl Drop for Failures {
fn drop(&mut self) {
use std::alloc::{dealloc, Layout};
use std::mem;
match result.assume_init().into() { unsafe {
let layout = Layout::from_size_align_unchecked(
mem::size_of::<Failure>(),
mem::align_of::<Failure>(),
);
dealloc(self.failures as *mut u8, layout);
}
}
}
let get_expect_failures: libloading::Symbol<unsafe extern "C" fn() -> Failures> = $lib
.get(bitcode::UTILS_GET_EXPECT_FAILURES.as_bytes())
.ok()
.ok_or(format!(
"Unable to JIT compile `{}`",
bitcode::UTILS_GET_EXPECT_FAILURES
))
.expect("errored");
let mut main_result = MaybeUninit::uninit();
main(main_result.as_mut_ptr());
let failures = get_expect_failures();
if failures.count > 0 {
// TODO tell the user about the failures!
let failures =
unsafe { core::slice::from_raw_parts(failures.failures, failures.count) };
panic!("Failed with {} failures. Failures: ", failures.len());
}
match main_result.assume_init().into() {
Ok(success) => { Ok(success) => {
// only if there are no exceptions thrown, check for errors // only if there are no exceptions thrown, check for errors
assert!($errors.is_empty(), "Encountered errors:\n{}", $errors); assert!($errors.is_empty(), "Encountered errors:\n{}", $errors);
@ -95,7 +147,7 @@ macro_rules! run_jit_function_dynamic_type {
let flag = *result; let flag = *result;
if flag == 0 { if flag == 0 {
$transform(result.add(std::mem::size_of::<RocCallResult<()>>()) as *const u8) $transform(result.add(std::mem::size_of::<RocCallResult<()>>()) as usize)
} else { } else {
use std::ffi::CString; use std::ffi::CString;
use std::os::raw::c_char; use std::os::raw::c_char;

View file

@ -11,5 +11,6 @@ roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
roc_reporting = { path = "../../reporting" } roc_error_macros = { path = "../../error_macros" }

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{Layout, UnionLayout}; use roc_mono::layout::{Layout, UnionLayout};
use crate::wasm_module::ValueType; use crate::wasm_module::ValueType;
use crate::{PTR_SIZE, PTR_TYPE}; use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO};
/// Manually keep up to date with the Zig version we are using for builtins /// Manually keep up to date with the Zig version we are using for builtins
pub const BUILTINS_ZIG_VERSION: ZigVersion = ZigVersion::Zig8; pub const BUILTINS_ZIG_VERSION: ZigVersion = ZigVersion::Zig8;
@ -47,8 +47,8 @@ impl WasmLayout {
use UnionLayout::*; use UnionLayout::*;
use ValueType::*; use ValueType::*;
let size = layout.stack_size(PTR_SIZE); let size = layout.stack_size(TARGET_INFO);
let alignment_bytes = layout.alignment_bytes(PTR_SIZE); let alignment_bytes = layout.alignment_bytes(TARGET_INFO);
match layout { match layout {
Layout::Builtin(Int(int_width)) => { Layout::Builtin(Int(int_width)) => {

View file

@ -4,23 +4,35 @@ mod low_level;
mod storage; mod storage;
pub mod wasm_module; pub mod wasm_module;
// Helpers for interfacing to a Wasm module from outside
pub mod wasm32_result;
pub mod wasm32_sized;
use bumpalo::{self, collections::Vec, Bump}; use bumpalo::{self, collections::Vec, Bump};
use roc_builtins::bitcode::IntWidth;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevelWrapperType;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
use roc_reporting::internal_error; use roc_target::TargetInfo;
use crate::backend::WasmBackend; use crate::backend::WasmBackend;
use crate::wasm_module::{ use crate::wasm_module::{
Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule, Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule,
}; };
const PTR_SIZE: u32 = 4; const TARGET_INFO: TargetInfo = TargetInfo::default_wasm32();
const PTR_SIZE: u32 = {
let value = TARGET_INFO.ptr_width() as u32;
// const assert that our pointer width is actually 4
// the code relies on the pointer width being exactly 4
assert!(value == 4);
value
};
const PTR_TYPE: ValueType = ValueType::I32; const PTR_TYPE: ValueType = ValueType::I32;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0; pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
@ -35,34 +47,45 @@ pub struct Env<'a> {
pub exposed_to_host: MutSet<Symbol>, pub exposed_to_host: MutSet<Symbol>,
} }
/// Entry point for Roc CLI
pub fn build_module<'a>( pub fn build_module<'a>(
env: &'a Env<'a>, env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
preload_bytes: &[u8],
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<std::vec::Vec<u8>, String> { ) -> std::vec::Vec<u8> {
let (mut wasm_module, _) = build_module_help(env, interns, procedures)?; let (mut wasm_module, called_preload_fns, _) =
let mut buffer = std::vec::Vec::with_capacity(4096); build_module_without_wrapper(env, interns, preload_bytes, procedures);
wasm_module.serialize_mut(&mut buffer);
Ok(buffer) wasm_module.remove_dead_preloads(env.arena, called_preload_fns);
let mut buffer = std::vec::Vec::with_capacity(wasm_module.size());
wasm_module.serialize(&mut buffer);
buffer
} }
pub fn build_module_help<'a>( /// Entry point for REPL (repl_wasm) and integration tests (test_gen)
pub fn build_module_without_wrapper<'a>(
env: &'a Env<'a>, env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
preload_bytes: &[u8],
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<(WasmModule<'a>, u32), String> { ) -> (WasmModule<'a>, Vec<'a, u32>, u32) {
let mut layout_ids = LayoutIds::default(); let mut layout_ids = LayoutIds::default();
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
let mut exports = Vec::with_capacity_in(4, env.arena); let mut exports = Vec::with_capacity_in(4, env.arena);
let mut main_fn_index = None; let mut maybe_main_fn_index = None;
// Collect the symbols & names for the procedures, // Collect the symbols & names for the procedures,
// and filter out procs we're going to inline // and filter out procs we're going to inline
let mut fn_index: u32 = 0; let mut fn_index: u32 = 0;
for ((sym, layout), proc) in procedures.into_iter() { for ((sym, layout), proc) in procedures.into_iter() {
if LowLevel::from_inlined_wrapper(sym).is_some() { if matches!(
LowLevelWrapperType::from_symbol(sym),
LowLevelWrapperType::CanBeReplacedBy(_)
) {
continue; continue;
} }
procs.push(proc); procs.push(proc);
@ -72,9 +95,9 @@ pub fn build_module_help<'a>(
.to_symbol_string(sym, interns); .to_symbol_string(sym, interns);
if env.exposed_to_host.contains(&sym) { if env.exposed_to_host.contains(&sym) {
main_fn_index = Some(fn_index); maybe_main_fn_index = Some(fn_index);
exports.push(Export { exports.push(Export {
name: fn_name.clone(), name: env.arena.alloc_slice_copy(fn_name.as_bytes()),
ty: ExportType::Func, ty: ExportType::Func,
index: fn_index, index: fn_index,
}); });
@ -87,14 +110,21 @@ pub fn build_module_help<'a>(
fn_index += 1; fn_index += 1;
} }
// Pre-load the WasmModule with data from the platform & builtins object file
let initial_module = WasmModule::preload(env.arena, preload_bytes);
// Adjust Wasm function indices to account for functions from the object file
let fn_index_offset: u32 =
initial_module.import.function_count + initial_module.code.preloaded_count;
let mut backend = WasmBackend::new( let mut backend = WasmBackend::new(
env, env,
interns, interns,
layout_ids, layout_ids,
proc_symbols, proc_symbols,
linker_symbols, initial_module,
exports, fn_index_offset,
CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id), CodeGenHelp::new(env.arena, TargetInfo::default_wasm32(), env.module_id),
); );
if DEBUG_LOG_SETTINGS.user_procs_ir { if DEBUG_LOG_SETTINGS.user_procs_ir {
@ -128,9 +158,10 @@ pub fn build_module_help<'a>(
backend.build_proc(proc); backend.build_proc(proc);
} }
let module = backend.finalize_module(); let (module, called_preload_fns) = backend.finalize();
let main_function_index = maybe_main_fn_index.unwrap() + fn_index_offset;
Ok((module, main_fn_index.unwrap())) (module, called_preload_fns, main_function_index)
} }
pub struct CopyMemoryConfig { pub struct CopyMemoryConfig {
@ -195,10 +226,6 @@ macro_rules! round_up_to_alignment {
}; };
} }
pub fn debug_panic<E: std::fmt::Debug>(error: E) {
internal_error!("{:?}", error);
}
pub struct WasmDebugLogSettings { pub struct WasmDebugLogSettings {
proc_start_end: bool, proc_start_end: bool,
user_procs_ir: bool, user_procs_ir: bool,

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,9 @@ use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::Layout; use roc_mono::layout::Layout;
use roc_reporting::internal_error;
use crate::layout::{ use crate::layout::{
CallConv, ReturnMethod, StackMemoryFormat, WasmLayout, ZigVersion, BUILTINS_ZIG_VERSION, CallConv, ReturnMethod, StackMemoryFormat, WasmLayout, ZigVersion, BUILTINS_ZIG_VERSION,

View file

@ -0,0 +1,234 @@
/*
Generate a wrapper function to expose a generic interface from a Wasm module for any result type.
The wrapper function ensures the value is written to memory and returns its address as i32.
The user needs to analyse the Wasm module's memory to decode the result.
*/
use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{Builtin, Layout};
use roc_target::TargetInfo;
use crate::wasm32_sized::Wasm32Sized;
use crate::wasm_module::{
linking::SymInfo, linking::WasmObjectSymbol, Align, CodeBuilder, Export, ExportType, LocalId,
Signature, ValueType, WasmModule,
};
use roc_std::{RocDec, RocList, RocOrder, RocStr};
/// Type-driven wrapper generation
pub trait Wasm32Result {
fn insert_wrapper<'a>(
arena: &'a Bump,
module: &mut WasmModule<'a>,
wrapper_name: &str,
main_function_index: u32,
) {
insert_wrapper_metadata(arena, module, wrapper_name);
let mut code_builder = CodeBuilder::new(arena);
Self::build_wrapper_body(&mut code_builder, main_function_index);
module.code.code_builders.push(code_builder);
}
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32);
}
/// Layout-driven wrapper generation
pub fn insert_wrapper_for_layout<'a>(
arena: &'a Bump,
module: &mut WasmModule<'a>,
wrapper_name: &str,
main_fn_index: u32,
layout: &Layout<'a>,
) {
match layout {
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
i8::insert_wrapper(arena, module, wrapper_name, main_fn_index);
}
Layout::Builtin(Builtin::Int(IntWidth::U16 | IntWidth::I16)) => {
i16::insert_wrapper(arena, module, wrapper_name, main_fn_index);
}
Layout::Builtin(Builtin::Int(IntWidth::U32 | IntWidth::I32)) => {
i32::insert_wrapper(arena, module, wrapper_name, main_fn_index);
}
Layout::Builtin(Builtin::Int(IntWidth::U64 | IntWidth::I64)) => {
i64::insert_wrapper(arena, module, wrapper_name, main_fn_index);
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
f32::insert_wrapper(arena, module, wrapper_name, main_fn_index);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
f64::insert_wrapper(arena, module, wrapper_name, main_fn_index);
}
_ => {
// The result is not a Wasm primitive, it's an array of bytes in stack memory.
let size = layout.stack_size(TargetInfo::default_wasm32());
insert_wrapper_metadata(arena, module, wrapper_name);
let mut code_builder = CodeBuilder::new(arena);
build_wrapper_body_stack_memory(&mut code_builder, main_fn_index, size as usize);
module.code.code_builders.push(code_builder);
}
}
}
fn insert_wrapper_metadata<'a>(arena: &'a Bump, module: &mut WasmModule<'a>, wrapper_name: &str) {
let index = module.import.function_count
+ module.code.preloaded_count
+ module.code.code_builders.len() as u32;
module.add_function_signature(Signature {
param_types: Vec::with_capacity_in(0, arena),
ret_type: Some(ValueType::I32),
});
module.export.append(Export {
name: arena.alloc_slice_copy(wrapper_name.as_bytes()),
ty: ExportType::Func,
index,
});
let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined {
flags: 0,
index,
name: wrapper_name.to_string(),
});
module.linking.symbol_table.push(linker_symbol);
}
macro_rules! build_wrapper_body_primitive {
($store_instruction: ident, $align: expr) => {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
let frame_pointer_id = LocalId(0);
let frame_pointer = Some(frame_pointer_id);
let local_types = &[ValueType::I32];
let frame_size = 8;
// Main's symbol index is the same as its function index, since the first symbols we created were for procs
let main_symbol_index = main_function_index;
code_builder.get_local(frame_pointer_id);
code_builder.call(main_function_index, main_symbol_index, 0, true);
code_builder.$store_instruction($align, 0);
code_builder.get_local(frame_pointer_id);
code_builder.build_fn_header_and_footer(local_types, frame_size, frame_pointer);
}
};
}
macro_rules! wasm_result_primitive {
($type_name: ident, $store_instruction: ident, $align: expr) => {
impl Wasm32Result for $type_name {
build_wrapper_body_primitive!($store_instruction, $align);
}
};
}
fn build_wrapper_body_stack_memory(
code_builder: &mut CodeBuilder,
main_function_index: u32,
size: usize,
) {
let local_id = LocalId(0);
let local_types = &[ValueType::I32];
let frame_pointer = Some(local_id);
// Main's symbol index is the same as its function index, since the first symbols we created were for procs
let main_symbol_index = main_function_index;
code_builder.get_local(local_id);
code_builder.call(main_function_index, main_symbol_index, 0, true);
code_builder.get_local(local_id);
code_builder.build_fn_header_and_footer(local_types, size as i32, frame_pointer);
}
macro_rules! wasm_result_stack_memory {
($type_name: ident) => {
impl Wasm32Result for $type_name {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(
code_builder,
main_function_index,
$type_name::ACTUAL_WIDTH,
)
}
}
};
}
wasm_result_primitive!(bool, i32_store8, Align::Bytes1);
wasm_result_primitive!(RocOrder, i32_store8, Align::Bytes1);
wasm_result_primitive!(u8, i32_store8, Align::Bytes1);
wasm_result_primitive!(i8, i32_store8, Align::Bytes1);
wasm_result_primitive!(u16, i32_store16, Align::Bytes2);
wasm_result_primitive!(i16, i32_store16, Align::Bytes2);
wasm_result_primitive!(u32, i32_store, Align::Bytes4);
wasm_result_primitive!(i32, i32_store, Align::Bytes4);
wasm_result_primitive!(u64, i64_store, Align::Bytes8);
wasm_result_primitive!(i64, i64_store, Align::Bytes8);
wasm_result_primitive!(usize, i32_store, Align::Bytes4);
wasm_result_primitive!(f32, f32_store, Align::Bytes4);
wasm_result_primitive!(f64, f64_store, Align::Bytes8);
wasm_result_stack_memory!(u128);
wasm_result_stack_memory!(i128);
wasm_result_stack_memory!(RocDec);
wasm_result_stack_memory!(RocStr);
impl<T: Wasm32Result> Wasm32Result for RocList<T> {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(code_builder, main_function_index, 12)
}
}
impl<T: Wasm32Result> Wasm32Result for &'_ T {
build_wrapper_body_primitive!(i32_store, Align::Bytes4);
}
impl<T, const N: usize> Wasm32Result for [T; N]
where
T: Wasm32Result + Wasm32Sized,
{
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(code_builder, main_function_index, N * T::ACTUAL_WIDTH)
}
}
impl Wasm32Result for () {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
// Main's symbol index is the same as its function index, since the first symbols we created were for procs
let main_symbol_index = main_function_index;
code_builder.call(main_function_index, main_symbol_index, 0, false);
code_builder.get_global(0);
code_builder.build_fn_header_and_footer(&[], 0, None);
}
}
impl<T, U> Wasm32Result for (T, U)
where
T: Wasm32Result + Wasm32Sized,
U: Wasm32Result + Wasm32Sized,
{
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(
code_builder,
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH,
)
}
}
impl<T, U, V> Wasm32Result for (T, U, V)
where
T: Wasm32Result + Wasm32Sized,
U: Wasm32Result + Wasm32Sized,
V: Wasm32Result + Wasm32Sized,
{
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(
code_builder,
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH,
)
}
}

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