mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Merge remote-tracking branch 'origin/trunk' into gui-example
This commit is contained in:
commit
655373dbe7
281 changed files with 11678 additions and 5539 deletions
8
.reuse/dep5
Normal file
8
.reuse/dep5
Normal file
|
@ -0,0 +1,8 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: Roc
|
||||
Upstream-Contact: Richard Feldman <oss@rtfeldman.com>
|
||||
Source: https://github.com/rtfeldman/roc
|
||||
|
||||
Files: *
|
||||
Copyright: © The Roc Contributors
|
||||
License: UPL-1.0
|
2
AUTHORS
2
AUTHORS
|
@ -63,3 +63,5 @@ Matthias Devlamynck <matthias.devlamynck@mailoo.org>
|
|||
Jan Van Bruggen <JanCVanB@users.noreply.github.com>
|
||||
Mats Sigge <<mats.sigge@gmail.com>>
|
||||
Drew Lazzeri <dlazzeri1@gmail.com>
|
||||
Tom Dohrmann <erbse.13@gmx.de>
|
||||
Elijah Schow <elijah.schow@gmail.com>
|
||||
|
|
|
@ -79,6 +79,12 @@ There are also alternative installation options at http://releases.llvm.org/down
|
|||
|
||||
[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
|
||||
|
||||
### 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!
|
||||
|
||||
You should be in a shell with everything needed to build already installed. Next run:
|
||||
|
||||
`cargo run repl`
|
||||
|
||||
You should be in a repl now. Have fun!
|
||||
You should be in a shell with everything needed to build already installed.
|
||||
Use `cargo run help` to see all subcommands.
|
||||
To use the `repl` subcommand, execute `cargo run repl`.
|
||||
Use `cargo build` to build the whole project.
|
||||
|
||||
### Extra tips
|
||||
|
||||
|
|
|
@ -10,7 +10,15 @@ Check [Build from source](BUILDING_FROM_SOURCE.md) for instructions.
|
|||
|
||||
## Running Tests
|
||||
|
||||
To run all tests and checks as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
|
||||
Most contributors execute the following commands befor pushing their code:
|
||||
```
|
||||
cargo test
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy -- -D warnings
|
||||
```
|
||||
Execute `cargo fmt --all` to fix the formatting.
|
||||
|
||||
If you want to run all tests and checks as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
|
||||
```
|
||||
earthly +test-all
|
||||
```
|
||||
|
@ -19,6 +27,8 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is
|
|||
|
||||
## Contribution Tips
|
||||
|
||||
- Create an issue if the purpose of a struct/field/type/function/... is not immediately clear from its name or nearby comments.
|
||||
- You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||
- 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!
|
||||
- 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.
|
||||
|
@ -31,8 +41,6 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is
|
|||
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).
|
||||
|
||||
## Can we do better?
|
||||
|
||||
Feel free to open an issue if you think this document can be improved or is unclear in any way.
|
||||
|
|
225
Cargo.lock
generated
225
Cargo.lock
generated
|
@ -24,7 +24,7 @@ version = "0.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||
dependencies = [
|
||||
"gimli 0.26.1",
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -739,66 +739,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-bforest"
|
||||
version = "0.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8ca3560686e7c9c7ed7e0fe77469f2410ba5d7781b1acaa9adc8d8deea28e3e"
|
||||
dependencies = [
|
||||
"cranelift-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen"
|
||||
version = "0.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf9bf1ffffb6ce3d2e5ebc83549bd2436426c99b31cc550d521364cbe35d276"
|
||||
dependencies = [
|
||||
"cranelift-bforest",
|
||||
"cranelift-codegen-meta",
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-entity",
|
||||
"gimli 0.24.0",
|
||||
"log",
|
||||
"regalloc",
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-meta"
|
||||
version = "0.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cc21936a5a6d07e23849ffe83e5c1f6f50305c074f4b2970ca50c13bf55b821"
|
||||
dependencies = [
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-shared"
|
||||
version = "0.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca5b6ffaa87560bebe69a5446449da18090b126037920b0c1c6d5945f72faf6b"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-entity"
|
||||
version = "0.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d6b4a8bef04f82e4296782646f733c641d09497df2fabf791323fefaa44c64c"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-frontend"
|
||||
version = "0.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b783b351f966fce33e3c03498cb116d16d97a8f9978164a60920bd0d3a99c"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"log",
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
|
@ -1193,6 +1133,38 @@ version = "0.4.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
|
||||
|
||||
[[package]]
|
||||
name = "dynasm"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dynasmrt"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"dynasm",
|
||||
"memmap2 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
|
@ -1280,12 +1252,6 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
version = "3.0.2"
|
||||
|
@ -1306,12 +1272,6 @@ dependencies = [
|
|||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.22"
|
||||
|
@ -1511,17 +1471,6 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"indexmap",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.26.1"
|
||||
|
@ -1865,9 +1814,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.55"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
|
||||
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -2676,14 +2625,11 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"petgraph",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"thread-id",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -2742,16 +2688,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "phf"
|
||||
version = "0.9.0"
|
||||
|
@ -3143,17 +3079,6 @@ dependencies = [
|
|||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regalloc"
|
||||
version = "0.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5"
|
||||
dependencies = [
|
||||
"log",
|
||||
"rustc-hash",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
|
@ -3213,6 +3138,8 @@ dependencies = [
|
|||
"roc_repl_cli",
|
||||
"roc_test_utils",
|
||||
"strip-ansi-escapes",
|
||||
"wasmer",
|
||||
"wasmer-wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3307,6 +3234,7 @@ dependencies = [
|
|||
name = "roc_builtins"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
|
@ -3323,6 +3251,7 @@ dependencies = [
|
|||
"pretty_assertions",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
|
@ -3400,6 +3329,7 @@ dependencies = [
|
|||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
|
@ -3411,6 +3341,7 @@ name = "roc_docs"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark",
|
||||
"roc_ast",
|
||||
|
@ -3590,6 +3521,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
|
@ -3600,6 +3532,7 @@ dependencies = [
|
|||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"strip-ansi-escapes",
|
||||
"tempfile",
|
||||
"ven_pretty",
|
||||
]
|
||||
|
@ -3611,6 +3544,7 @@ dependencies = [
|
|||
"bumpalo",
|
||||
"lazy_static",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_ident",
|
||||
"roc_region",
|
||||
"snafu",
|
||||
|
@ -3627,6 +3561,7 @@ dependencies = [
|
|||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
|
@ -3720,8 +3655,19 @@ dependencies = [
|
|||
name = "roc_repl_wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"futures",
|
||||
"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]]
|
||||
|
@ -3776,6 +3722,7 @@ name = "roc_std"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"libc",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
|
@ -3801,6 +3748,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
"static_assertions",
|
||||
|
@ -3811,6 +3759,7 @@ dependencies = [
|
|||
name = "roc_unify"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_types",
|
||||
|
@ -4216,12 +4165,6 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -4404,17 +4347,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "threadpool"
|
||||
version = "1.8.1"
|
||||
|
@ -4683,9 +4615,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -4693,9 +4625,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
|
@ -4708,9 +4640,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.28"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
|
||||
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
|
@ -4720,9 +4652,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -4730,9 +4662,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -4743,9 +4675,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
|
||||
[[package]]
|
||||
name = "wasmer"
|
||||
|
@ -4760,7 +4692,7 @@ dependencies = [
|
|||
"target-lexicon",
|
||||
"thiserror",
|
||||
"wasmer-compiler",
|
||||
"wasmer-compiler-cranelift",
|
||||
"wasmer-compiler-singlepass",
|
||||
"wasmer-derive",
|
||||
"wasmer-engine",
|
||||
"wasmer-engine-dylib",
|
||||
|
@ -4790,20 +4722,19 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-compiler-cranelift"
|
||||
name = "wasmer-compiler-singlepass"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a570746cbec434179e2d53357973a34dfdb208043104e8fac3b7b0023015cf6"
|
||||
checksum = "9429b9f7708c582d855b1787f09c7029ff23fb692550d4a1cc351c8ea84c3014"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
"cranelift-frontend",
|
||||
"gimli 0.24.0",
|
||||
"byteorder",
|
||||
"dynasm",
|
||||
"dynasmrt",
|
||||
"lazy_static",
|
||||
"loupe",
|
||||
"more-asserts",
|
||||
"rayon",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"wasmer-compiler",
|
||||
"wasmer-types",
|
||||
"wasmer-vm",
|
||||
|
|
314
FAQ.md
Normal file
314
FAQ.md
Normal file
|
@ -0,0 +1,314 @@
|
|||
# Frequently Asked Questions
|
||||
|
||||
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
|
||||
|
||||
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious
|
||||
effort to focus on the Roc Editor *instead of* adding Roc support to other editors - specifically in order to give the Roc
|
||||
Editor the best possible chance at kickstarting a virtuous cycle of plugin authorship.
|
||||
|
||||
This is an unusual approach, but there are more details in [this 2021 interview](https://youtu.be/ITrDd6-PbvY?t=212).
|
||||
|
||||
In the meantime, using CoffeeScript syntax highlighting for .roc files turns out to work surprisingly well!
|
||||
|
||||
## Why is there no way to specify "import everything this module exposes" in `imports`?
|
||||
|
||||
In [Elm](https://elm-lang.org), it's possible to import a module in a way that brings everything that module
|
||||
exposes into scope. It can be convenient, but like all programming language features, it has downsides.
|
||||
|
||||
A minor reason Roc doesn't have this feature is that exposing everything can make it more difficult
|
||||
outside the editor (e.g. on a website) to tell where something comes from, especially if multiple imports are
|
||||
using this. ("I don't see `blah` defined in this module, so it must be coming from an import...but which of
|
||||
these several import-exposing-everything modules could it be? I'll have to check all of them, or
|
||||
download this code base and open it up in the editor so I can jump to definition!")
|
||||
|
||||
The main reason for this design, though, is compiler performance.
|
||||
|
||||
Currently, the name resolution step in compilation can be parallelized across modules, because it's possible to
|
||||
tell if there's a naming error within a module using only the contents of that module. If "expose everything" is
|
||||
allowed, then it's no longer clear whether anything is a naming error or not, until all the "expose everything"
|
||||
modules have been processed, so we know exactly which names they expose. Because that feature doesn't exist in Roc,
|
||||
all modules can do name resolution in parallel.
|
||||
|
||||
Of note, allowing this feature would only slow down modules that used it; modules that didn't use it would still be
|
||||
parallelizable. However, when people find out ways to speed up their builds (in any language), advice starts to
|
||||
circulate about how to unlock those speed boosts. If Roc had this feature, it's predictable that a commonly-accepted
|
||||
piece of advice would eventually circulate: "don't use this feature because it slows down your builds."
|
||||
|
||||
If a feature exists in a language, but the common recommendation is never to use it, that's cause for reconsidering
|
||||
whether the feature should be in the language at all. In the case of this feature, I think it's simpler if the
|
||||
language doesn't have it; that way nobody has to learn (or spend time spreading the word) about the
|
||||
performance-boosting advice not to use it.
|
||||
|
||||
## Why doesn't Roc have higher-kinded polymorphism or arbitrary-rank types?
|
||||
|
||||
_Since this is a FAQ answer, I'm going to assume familiarity with higher-kinded types and higher-rank types instead of including a primer on them._
|
||||
|
||||
A valuable aspect of Roc's type system is that it has decidable [principal](https://en.wikipedia.org/wiki/Principal_type)
|
||||
type inference. This means that:
|
||||
|
||||
* At compile time, Roc can correctly infer the types for every expression in a program, even if you don't annotate any of the types.
|
||||
* This inference always infers the most general type possible; you couldn't possibly add a valid type annotation that would make the type more flexible than the one that Roc would infer if you deleted the annotation.
|
||||
|
||||
It's been proven that any type system which supports either [higher-kinded polymorphism](https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-kinded-polymorphism.pdf) or [arbitrary-rank types](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/putting.pdf) cannot have decidable
|
||||
principal type inference. With either of those features in the language, there will be situations where the compiler
|
||||
would be unable to infer a type—and you'd have to write a type annotation. This also means there would be
|
||||
situations where the editor would not be able to reliably tell you the type of part of your program, unlike today
|
||||
where it can accurately tell you the type of anything, even if you have no type annotations in your entire code base.
|
||||
|
||||
### Arbitrary-rank types
|
||||
|
||||
Unlike arbitrary-rank (aka "Rank-N") types, both Rank-1 and Rank-2 type systems are compatible with principal
|
||||
type inference. Roc currently uses Rank-1 types, and the benefits of Rank-N over Rank-2 don't seem worth
|
||||
sacrificing principal type inference to attain, so let's focus on the trade-offs between Rank-1 and Rank-2.
|
||||
|
||||
Supporting Rank-2 types in Roc has been discussed before, but it has several important downsides:
|
||||
|
||||
* It would increase the complexity of the language.
|
||||
* It would make some compiler error messages more confusing (e.g. they might mention `forall` because that was the most general type that could be inferred, even if that wasn't helpful or related to the actual problem).
|
||||
* It would substantially increase the complexity of the type checker, which would necessarily slow it down.
|
||||
|
||||
No implementation of Rank-2 types can remove any of these downsides. Thus far, we've been able to come up
|
||||
with sufficiently nice APIs that only require Rank-1 types, and we haven't seen a really compelling use case
|
||||
where the gap between the Rank-2 and Rank-1 designs was big enough to justify switching to Rank-2.
|
||||
|
||||
Since I prefer Roc being simpler and having a faster compiler with nicer error messages, my hope is that Roc
|
||||
will never get Rank-2 types. However, it may turn out that in the future we learn about currently-unknown
|
||||
upsides that somehow outweigh these downsides, so I'm open to considering the possibility - while rooting against it.
|
||||
|
||||
### Higher-kinded polymorphism
|
||||
|
||||
I want to be really clear about this one: the explicit plan is that Roc will never support higher-kinded polymorphism.
|
||||
|
||||
On the technical side, the reasons for this are ordinary: I understand the practical benefits and
|
||||
drawbacks of HKP, and I think the drawbacks outweigh the benefits when it comes to Roc. (Those who come to a
|
||||
different conclusion may think HKP's drawbacks would be less of a big a deal in Roc than I do. That's reasonable;
|
||||
we programmers often weigh the same trade-offs differently.) To be clear, I think this in the specific context of
|
||||
Roc; there are plenty of other languages where HKP seems like a great fit. For example, it's hard to imagine Haskell
|
||||
without it. Similarly, I think lifetime annotations are a great fit for Rust, but don't think they'd be right
|
||||
for Roc either.
|
||||
|
||||
I also think it's important to consider the cultural implications of deciding whether or not to support HKP.
|
||||
To illustrate what I mean, imagine this conversation:
|
||||
|
||||
**Programmer 1:** "How do you feel about higher-kinded polymorphism?"
|
||||
|
||||
**Programmer 2:** "I have no idea what that is."
|
||||
|
||||
**Programmer 1:** "Okay, how do you feel about monads?"
|
||||
|
||||
**Programmer 2:** "OH NO."
|
||||
|
||||
I've had several variations of this conversation: I'm talking about higher-kinded types,
|
||||
another programmer asks what that means, I give monads as an example, and their reaction is strongly negative.
|
||||
I've also had plenty of conversations with programmers who love HKP and vigorously advocate for its addition
|
||||
to languages they use which don't have it. Feelings about HKP seem strongly divided, maybe more so
|
||||
than any other type system feature besides static and dynamic types.
|
||||
|
||||
It's impossible for a programming language to be neutral on this. If the language doesn't support HKP, nobody can
|
||||
implement a Monad typeclass (or equivalent) in any way that can be expected to catch on. Advocacy to add HKP to the
|
||||
language will inevitably follow. If the language does support HKP, one or more alternate standard libraries built
|
||||
around monads will inevitably follow, along with corresponding cultural changes. (See Scala for example.)
|
||||
Culturally, to support HKP is to take a side, and to decline to support it is also to take a side.
|
||||
|
||||
Given this, language designers have three options:
|
||||
|
||||
* Have HKP and have Monad in the standard library. Embrace them and build a culture and ecosystem around them.
|
||||
* Have HKP and don't have Monad in the standard library. An alternate standard lbirary built around monads will inevitably emerge, and both the community and ecosystem will divide themselves along pro-monad and anti-monad lines.
|
||||
* Don't have HKP; build a culture and ecosystem around other things.
|
||||
|
||||
Considering that these are the only three options, I think the best choice for Roc—not only on a technical
|
||||
level, but on a cultural level as well—is to make it clear that the plan is for Roc never to support HKP.
|
||||
I hope this clarity can save a lot of community members' time that would otherwise be spent on advocacy or
|
||||
arguing between the two sides of the divide. Again, I think it's completely reasonable for anyone to have a
|
||||
different preference, but given that language designers can only choose one of these options, I'm confident
|
||||
I've made the right choice for Roc by designing it never to have higher-kinded polymorphism.
|
||||
|
||||
## Why do Roc's syntax and standard library differ from Elm's?
|
||||
|
||||
Roc is a direct descendant of [Elm](https://elm-lang.org/). However, there are some differences between the two languages.
|
||||
|
||||
Syntactic differences are among these. This is a feature, not a bug; if Roc had identical syntax to Elm, then it's
|
||||
predictable that people would write code that was designed to work in both languages - and would then rely on
|
||||
that being true, for example by making a package which advertised "Works in both Elm and Roc!" This in turn
|
||||
would mean that later if either language were to change its syntax in a way that didn't make sense for the other,
|
||||
the result would be broken code and sadness.
|
||||
|
||||
So why does Roc have the specific syntax changes it does? Here are some brief explanations:
|
||||
|
||||
* `#` instead of `--` for comments - this allows [hashbang](https://senthilnayagan.medium.com/shebang-hashbang-10966b8f28a8)s to work without needing special syntax. That isn't a use case Elm supports, but it is one Roc is designed to support.
|
||||
* `{}` instead of `()` for the unit type - Elm has both, and they can both be used as a unit type. Since `{}` has other uses in the type system, but `()` doesn't, I consider it redundant and took it out.
|
||||
* No tuples - I wanted to try simplifying the language and seeing how much we'd miss them. Anything that could be represented as a tuple can be represented with either a record or a single-tag union instead (e.g. `Pair x y = ...`), so is it really necessary to have a third syntax for representing a group of fields with potentially different types?
|
||||
* `when`...`is` instead of `case`...`of` - I predict it will be easier for beginners to pick up, because usually the way I explain `case`...`of` to beginners is by saying the words "when" and "is" out loud - e.g. "when `color` is `Red`, it runs this first branch; when `color` is `Blue`, it runs this other branch..."
|
||||
* `:` instead of `=` for record field definitions (e.g. `{ foo: bar }` where Elm syntax would be `{ foo = bar }`): I like `=` being reserved for definitions, and `:` is the most popular alternative.
|
||||
* Backpassing syntax - since Roc is designed to be used for use cases like command-line apps, shell scripts, and servers, I expect chained effects to come up a lot more often than they do in Elm. I think backpassing is nice for those use cases, similarly to how `do` notation is nice for them in Haskell.
|
||||
* Tag unions instead of Elm's custom types (aka algebraic data types). This isn't just a syntactic change; tag unions are mainly in Roc because they can facilitate errors being accumulated across chained effects, which (as noted a moment ago) I expect to be a lot more common in Roc than in Elm. If you have tag unions, you don't really need a separate language feature for algebraic data types, since closed tag unions essentially work the same way - aside from not giving you a way to selectively expose variants or define phantom types. Roc's opaque types language feature covers those use cases instead.
|
||||
* No `::` operator, or `::` pattern matching for lists. Both of these are for the same reason: an Elm `List` is a linked list, so both prepending to it and removing an element from the front are very cheap operations. In contrast, a Roc `List` is a flat array, so both prepending to it and removing an element from the front are among the most expensive operations you can possibly do with it! To get good performance, this usage pattern should be encouraged in Elm and discouraged in Roc. Since having special syntax would encourage it, it would not be good for Roc to have that syntax!
|
||||
* No `<|` operator. In Elm, I almost exclusively found myself wanting to use this in conjunction with anonymous functions (e.g. `foo <| \bar -> ...`) or conditionals (e.g. `foo <| if bar then ...`). In Roc you can do both of these without the `<|`. That means the main remaining use for `<|` is to reduce parentheses, but I tend to think `|>` is better at that (or else the parens are fine), so after the other syntactic changes, I considered `<|` an unnecessary stylistic alternative to `|>` or parens.
|
||||
* The `|>` operator passes the expression before the `|>` as the *first* argument to the function after the `|>` instead of as the last argument. See the section on currying for details on why this works this way.
|
||||
* `:` instead of `type alias` - I like to avoid reserved keywords for terms that are desirable in userspace, so that people don't have to name things `typ` because `type` is a reserved keyword, or `clazz` because `class` is reserved. (I couldn't think of satisfactory alternatives for `as`, `when`, `is`, or `if` other than different reserved keywords. I could see an argument for `then`—and maybe even `is`—being replaced with a `->` or `=>` or something, but I don't anticipate missing either of those words much in userspace. `then` is used in JavaScript promises, but I think there are several better names for that function.)
|
||||
* No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.)
|
||||
* Trailing commas - I've seen people walk away (in some cases physically!) from Elm as soon as they saw the leading commas in collection literals. While I think they've made a mistake by not pushing past this aesthetic preference to give the language a chance, I also would prefer not put them in a position to make such a mistake in the first place. Secondarily, while I'm personally fine with either style, between the two I prefer the look of trailing commas.
|
||||
* The `!` unary prefix operator. I didn't want to have a `Basics` module (more on that in a moment), and without `Basics`, this would either need to be called fully-qualified (`Bool.not`) or else a module import of `Bool.{ not }` would be necessary. Both seemed less nice than supporting the `!` prefix that's common to so many widely-used languages, especially when we already have a unary prefix operator of `-` for negation (e.g. `-x`).
|
||||
* `!=` for the inequality operator (instead of Elm's `/=`) - this one pairs more naturally with the `!` prefix operator and is also very common in other languages.
|
||||
|
||||
Roc also has a different standard library from Elm. Some of the differences come down to platforms and applications (e.g. having `Task` in Roc's standard library wouldn't make sense), but others do not. Here are some brief explanations:
|
||||
|
||||
* No `Basics` module. I wanted to have a simple rule of "all modules in the standard library are imported by default, and so are their exposed types," and that's it. Given that I wanted the comparison operators (e.g. `<`) to work only on numbers, it ended up that having `Num` and `Bool` modules meant that almost nothing would be left for a `Basics` equivalent in Roc except `identity` and `Never`. The Roc type `[]` (empty tag union) is equivalent to `Never`, so that wasn't necessary, and I generally think that `identity` is a good concept but a sign of an incomplete API whenever its use comes up in practice. For example, instead of calling `|> List.filterMap identity` I'd rather have access to a more self-descriptive function like `|> List.dropNothings`. With `Num` and `Bool`, and without `identity` and `Never`, there was nothing left in `Basics`.
|
||||
* `Str` instead of `String` - after using the `str` type in Rust, I realized I had no issue whatsoever with the more concise name, especially since it was used in so many places (similar to `Msg` and `Cmd` in Elm) - so I decided to save a couple of letters.
|
||||
* No function composition operators - I stopped using these in Elm so long ago, at one point I forgot they were in the language! See the FAQ entry on currying for details about why.
|
||||
* No `Char`. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value).
|
||||
* No `Debug.log` - the editor can do a better job at this, or you can write `expect x != x` to see what `x` is when the expectation fails. Using the editor means your code doesn't change, and using `expect` gives a natural reminder to remove the debugging code before shipping: the build will fail.
|
||||
* No `Debug.todo` - instead you can write a type annotation with no implementation below it; the type checker will treat it normally, but attempting to use the value will cause a runtime exception. This is a feature I've often wanted in Elm, because I like prototyping APIs by writing out the types only, but then when I want the compiler to type-check them for me, I end up having to add `Debug.todo` in various places.
|
||||
* No `Maybe`. There are several reasons for this:
|
||||
* If a function returns a potential error, I prefer `Result` with an error type that uses a no-payload tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) This is not only more self-descriptive, it also composes better with operations that have multiple ways to fail.
|
||||
* Optional record fields can be handled using the explicit Optional Record Field language feature.
|
||||
* To describe something that's neither an operation that can fail nor an optional field, I prefer using a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, making a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]`.
|
||||
* It's surprisingly easy to misuse - especially by overusing it when a different language feature (especially a custom tag union) would lead to nicer code. Joël's legendary [talk about Maybe](https://youtu.be/43eM4kNbb6c) is great, but the fact that a whole talk about such a simple type can be so useful speaks to how easy the type is to misuse. Imagine a 20-minute talk about `Result` - could it be anywhere near as hepful?
|
||||
* On a historical note, it's conceivable that the creation of `Maybe` predated `Result`, and `Maybe` might have been thought of as a substitute for null pointers—as opposed to something that emerged organically based on specific motivating use cases after `Result` already existed.
|
||||
|
||||
## Why aren't Roc functions curried by default?
|
||||
|
||||
Although technically any language with first-class functions makes it possible to curry
|
||||
any function (e.g. I can manually curry a Roc function `\x, y, z ->` by writing `\x -> \y -> \z ->` instead),
|
||||
typically what people mean when they say Roc isn't a curried language is that Roc functions aren't curried
|
||||
by default. For the rest of this section, I'll use "currying" as a shorthand for "functions that are curried
|
||||
by default" for the sake of brevity.
|
||||
|
||||
As I see it, currying has one major upside and several major downsides. The upside:
|
||||
|
||||
* It makes function calls more concise in some cases.
|
||||
|
||||
The downsides:
|
||||
|
||||
* It lowers error message quality, because there can no longer be an error for "function called with too few arguments." (Calling a function with fewer arguments is always valid in curried functions; the error you get instead will unavoidably be some other sort of type mismatch, and it will be up to you to figure out that the real problem was that you forgot an argument.)
|
||||
* It makes the `|>` operator more error-prone in some cases.
|
||||
* It makes higher-order function calls need more parentheses in some cases.
|
||||
* It significantly increases the language's learning curve. (More on this later.)
|
||||
* It facilitates pointfree function composition. (More on why this is listed as a downside later.)
|
||||
|
||||
There's also a downside that it would make runtime performance of compiled programs worse by default,
|
||||
but I assume it would be possible to optimize that away at the cost of slightly longer compile times.
|
||||
|
||||
I consider the one upside (conciseness in some places) extremely minor, and have almost never missed it in Roc.
|
||||
Here are some more details about the downsides as I see them.
|
||||
|
||||
### Currying and the `|>` operator
|
||||
|
||||
In Roc, this code produces `"Hello, World!"`
|
||||
|
||||
```elm
|
||||
"Hello, World"
|
||||
|> Str.concat "!"
|
||||
```
|
||||
|
||||
This is because Roc's `|>` operator uses the expression before the `|>` as the *first* argument to the function
|
||||
after it. For functions where both arguments have the same type, but it's obvious which argument goes where (e.g.
|
||||
`Str.concat "Hello, " "World!"`, `List.concat [ 1, 2 ] [ 3, 4 ]`), this works out well. Another example would
|
||||
be `|> Num.sub 1`, which subtracts 1 from whatever came before the `|>`.
|
||||
|
||||
For this reason, "pipeline-friendliness" in Roc means that the first argument to each function is typically
|
||||
the one that's most likely to be built up using a pipeline. For example, `List.map`:
|
||||
|
||||
```elm
|
||||
numbers
|
||||
|> List.map Num.abs
|
||||
```
|
||||
|
||||
This argument ordering convention also often makes it possible to pass anonymous functions to higher-order
|
||||
functions without needing parentheses, like so:
|
||||
|
||||
```elm
|
||||
List.map numbers \num -> Num.abs (num - 1)
|
||||
```
|
||||
|
||||
(If the arguments were reversed, this would be `List.map (\num -> Num.abs (num - 1)) numbers` and the
|
||||
extra parentheses would be required.)
|
||||
|
||||
Neither of these benefits is compatible with the argument ordering currying encourages. Currying encourages
|
||||
`List.map` to take the `List` as its second argument instead of the first, so that you can partially apply it
|
||||
like `(List.map Num.abs)`; if Roc introduced currying but kept the order of `List.map` the same way it is today,
|
||||
then partially applying `List.map` (e.g. `(List.map numbers)`) would be much less useful than if the arguments
|
||||
were swapped - but that in turn would make it less useful with `|>` and would require parentheses when passing
|
||||
it an anonymous function.
|
||||
|
||||
This is a fundamental design tension. One argument order works well with `|>` (at least the way it works in Roc
|
||||
today) and with passing anonymous functions to higher-order functions, and the other works well with currying.
|
||||
It's impossible to have both.
|
||||
|
||||
Of note, one possible design is to have currying while also having `|>` pass the *last* argument instead of the first.
|
||||
This is what Elm does, and it makes pipeline-friendliness and curry-friendliness the same thing. However, it also
|
||||
means that either `|> Str.concat "!"` would add the `"!"` to the front of the string, or else `Str.concat`'s
|
||||
arguments would have to be flipped - meaning that `Str.concat "Hello, World" "!"` would evaluate to `"!Hello, World"`.
|
||||
|
||||
The only way to have `Str.concat` work the way it does in Roc today (where both pipelines and non-pipeline calling
|
||||
do what you'd want them to) is to order function arguments in a way that is not conducive to currying. This design
|
||||
tension only exists if there's currying in the language; without it, you can order arguments for pipeline-friendliness
|
||||
without concern.
|
||||
|
||||
### Currying and learning curve
|
||||
|
||||
Prior to designing Roc, I taught a lot of beginner [Elm](https://elm-lang.org/) workshops. Sometimes at
|
||||
conferences, sometimes for [Frontend Masters](https://frontendmasters.com/courses/intro-elm/),
|
||||
sometimes for free at local coding bootcamps or meetup groups.
|
||||
In total I've spent well over 100 hours standing in front of a class, introducing the students to their
|
||||
first pure functional programming language.
|
||||
|
||||
Here was my experience teaching currying:
|
||||
|
||||
* The only way to avoid teaching it is to refuse to explain why multi-argument functions have multiple `->`s in them. (If you don't explain it, at least one student will ask about it - and many if not all of the others will wonder.)
|
||||
* Teaching currying properly takes a solid chunk of time, because it requires explaining partial application, explaining how curried functions facilitate partial application, how function signatures accurately reflect that they're curried, and going through examples for all of these.
|
||||
* Even after doing all this, and iterating on my approach each time to try to explain it more effectively than I had the time before, I'd estimate that under 50% of the class ended up actually understanding currying. I consistently heard that in practice it only "clicked" for most people after spending significantly more time writing code with it.
|
||||
|
||||
This is not the end of the world, especially because it's easy enough to think "okay, I still don't totally get this
|
||||
even after that explanation, but I can remember that function arguments are separated by `->` in this language
|
||||
and maybe I'll understand the rest later." (Which they almost always do, if they stick with the language.)
|
||||
Clearly currying doesn't preclude a language from being easy to learn, because Elm has currying, and Elm's learning
|
||||
curve is famously gentle.
|
||||
|
||||
That said, beginners who feel confused while learning the language are less likely to continue with it.
|
||||
And however easy Roc would be to learn if it had currying, the language is certainly easier to learn without it.
|
||||
|
||||
### Pointfree function composition
|
||||
|
||||
[Pointfree function composition](https://en.wikipedia.org/wiki/Tacit_programming) is where you define
|
||||
a new function by composing together two existing functions without naming intermediate arguments.
|
||||
Here's an example:
|
||||
|
||||
```elm
|
||||
reverseSort : List elem -> List elem
|
||||
reverseSort = compose List.reverse List.sort
|
||||
|
||||
compose : (a -> b), (c -> a) -> (c -> b)
|
||||
compose = \f, g, x -> f (g x)
|
||||
```
|
||||
|
||||
Here's how I would instead write this:
|
||||
|
||||
```elm
|
||||
reverseSort : List elem -> List elem
|
||||
reverseSort = \list -> List.reverse (List.sort list)
|
||||
```
|
||||
|
||||
I've consistently found that I can more quickly and accurately understand function definitions that use
|
||||
named arguments, even though the code is longer. I suspect this is because I'm faster at reading than I am at
|
||||
desugaring, and whenever I read the top version I end up needing to mentally desugar it into the bottom version.
|
||||
In more complex examples (this is among the tamest pointfree function composition examples I've seen), I make
|
||||
a mistake in my mental desugaring, and misunderstand what the function is doing - which can cause bugs.
|
||||
|
||||
I assumed I would get faster and more accurate at this over time. However, by now it's been about a decade
|
||||
since I first learned about the technique, and I'm still slower and less accurate at reading code that uses
|
||||
pointfree function composition (including if I wrote it - but even moreso if I didn't) than code written with
|
||||
with named arguments. I've asked a lot of other programmers about their experiences with pointfree function
|
||||
composition over the years, and the overwhelming majority of responses have been consistent with my experience.
|
||||
|
||||
As such, my opinion about pointfree function composition has gotten less and less nuanced over time. I've now moved
|
||||
past "it's the right tool for the job, sometimes" to concluding it's best thought of as an antipattern. This is
|
||||
because I realized how much time I was spending evaluating on a case-by-case basis whether it might be the
|
||||
right fit for a given situation. The time spent on this analysis alone vastly outweighed the sum of all the
|
||||
benefits I got in the rare cases where I concluded it was a fit. So I've found the way to get the most out of
|
||||
pointfree function composition is to never even think about using it; every other strategy leads to a worse outcome.
|
||||
|
||||
Currying facilitates the antipattern of pointfree function composition, which I view as a downside of currying.
|
||||
|
||||
Stacking up all these downsides of currying against the one upside of making certain function calls more concise,
|
||||
I concluded that it would be a mistake to have it in Roc.
|
|
@ -6,7 +6,7 @@ The [tutorial](TUTORIAL.md) is the best place to learn about how to use the lang
|
|||
|
||||
There's also a folder of [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) - the [CLI example](https://github.com/rtfeldman/roc/tree/trunk/examples/cli) in particular is a reasonable starting point to build on.
|
||||
|
||||
[Roc Zulip chat](https://roc.zulipchat.com) is the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects.
|
||||
If you have a specific question, the [FAQ](FAQ.md) might have an answer, although [Roc Zulip chat](https://roc.zulipchat.com) is overall the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects.
|
||||
|
||||
## State of Roc
|
||||
|
||||
|
@ -32,6 +32,12 @@ For NQueens, input 10 in the terminal and press enter.
|
|||
|
||||
**Tip:** when programming in roc, we recommend to execute `./roc check myproject/Foo.roc` before `./roc myproject/Foo.roc` or `./roc build myproject/Foo.roc`. `./roc check` can produce clear error messages in cases where building/running may panic.
|
||||
|
||||
## Sponsor
|
||||
|
||||
We are very grateful for our sponsor [NoRedInk](https://www.noredink.com/).
|
||||
|
||||
<img src="https://www.noredink.com/assets/logo-red-black-f6989d7567cf90b349409137595e99c52d036d755b4403d25528e0fd83a3b084.svg" height="60" alt="NoRedInk logo"/>
|
||||
|
||||
## Applications and Platforms
|
||||
|
||||
Applications are often built on a *framework.* Typically, both application and framework are written in the same language.
|
||||
|
|
|
@ -6,6 +6,10 @@ and more!
|
|||
|
||||
Enjoy!
|
||||
|
||||
## Getting started
|
||||
|
||||
Learn how to install roc on your machine [here](https://github.com/rtfeldman/roc#getting-started).
|
||||
|
||||
## Strings and Numbers
|
||||
|
||||
Let’s start by getting acquainted with Roc’s Read Eval Print Loop, or REPL for
|
||||
|
|
|
@ -13,9 +13,10 @@
|
|||
// use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
|
||||
// use crate::procedure::References;
|
||||
use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap};
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{self, AliasHeader};
|
||||
use roc_parse::ast::{self, TypeHeader};
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
@ -199,7 +200,7 @@ fn to_pending_def<'a>(
|
|||
}
|
||||
|
||||
roc_parse::ast::Def::Alias {
|
||||
header: AliasHeader { name, vars },
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => {
|
||||
let region = Region::span_across(&name.region, &ann.region);
|
||||
|
@ -260,6 +261,8 @@ fn to_pending_def<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
Opaque { .. } => todo_opaques!(),
|
||||
|
||||
Expect(_) => todo!(),
|
||||
|
||||
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => {
|
||||
|
@ -321,7 +324,7 @@ fn from_pending_alias<'a>(
|
|||
for loc_lowercase in vars {
|
||||
if !named_rigids.contains_key(&loc_lowercase.value) {
|
||||
env.problem(Problem::PhantomTypeArgument {
|
||||
alias: symbol,
|
||||
typ: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
|
|
|
@ -14,6 +14,14 @@ pub enum Def2 {
|
|||
expr_id: NodeId<Expr2>,
|
||||
},
|
||||
Blank,
|
||||
CommentsBefore {
|
||||
comments: String,
|
||||
def_id: DefId,
|
||||
},
|
||||
CommentsAfter {
|
||||
comments: String,
|
||||
def_id: DefId,
|
||||
},
|
||||
}
|
||||
|
||||
pub type DefId = NodeId<Def2>;
|
||||
|
@ -36,6 +44,14 @@ pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String {
|
|||
Def2::Blank => {
|
||||
full_string.push_str("Def2::Blank");
|
||||
}
|
||||
Def2::CommentsBefore {
|
||||
comments,
|
||||
def_id: _,
|
||||
} => full_string.push_str(comments),
|
||||
Def2::CommentsAfter {
|
||||
comments,
|
||||
def_id: _,
|
||||
} => full_string.push_str(comments),
|
||||
}
|
||||
|
||||
full_string
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use bumpalo::collections::Vec as BumpVec;
|
||||
use bumpalo::Bump;
|
||||
use roc_module::ident::{Ident, IdentStr};
|
||||
use roc_parse::parser::SyntaxError;
|
||||
use roc_parse::{ast::CommentOrNewline, parser::SyntaxError};
|
||||
use roc_region::all::Region;
|
||||
|
||||
use crate::lang::{core::expr::expr_to_expr2::loc_expr_to_expr2, env::Env, scope::Scope};
|
||||
|
@ -29,10 +29,49 @@ pub fn def_to_def2<'a>(
|
|||
region: Region,
|
||||
) -> Def2 {
|
||||
use roc_parse::ast::Def::*;
|
||||
//dbg!(parsed_def);
|
||||
|
||||
match parsed_def {
|
||||
SpaceBefore(inner_def, _) => def_to_def2(arena, env, scope, inner_def, region),
|
||||
SpaceAfter(inner_def, _) => def_to_def2(arena, env, scope, inner_def, region),
|
||||
SpaceBefore(inner_def, comments) => {
|
||||
// filter comments
|
||||
if !comments.is_empty() && !all_newlines(comments) {
|
||||
let inner_def = def_to_def2(arena, env, scope, inner_def, region);
|
||||
|
||||
let inner_def_id = env.pool.add(inner_def);
|
||||
let mut all_comments_str = String::new();
|
||||
|
||||
for comment in comments.iter().filter(|c_or_nl| !c_or_nl.is_newline()) {
|
||||
all_comments_str.push_str(&comment.to_string_repr());
|
||||
}
|
||||
|
||||
Def2::CommentsBefore {
|
||||
comments: all_comments_str,
|
||||
def_id: inner_def_id,
|
||||
}
|
||||
} else {
|
||||
def_to_def2(arena, env, scope, inner_def, region)
|
||||
}
|
||||
}
|
||||
SpaceAfter(inner_def, comments) => {
|
||||
// filter comments
|
||||
if !comments.is_empty() && !all_newlines(comments) {
|
||||
let inner_def = def_to_def2(arena, env, scope, inner_def, region);
|
||||
|
||||
let inner_def_id = env.pool.add(inner_def);
|
||||
let mut all_comments_str = String::new();
|
||||
|
||||
for comment in comments.iter().filter(|c_or_nl| !c_or_nl.is_newline()) {
|
||||
all_comments_str.push_str(&comment.to_string_repr());
|
||||
}
|
||||
|
||||
Def2::CommentsAfter {
|
||||
def_id: inner_def_id,
|
||||
comments: all_comments_str,
|
||||
}
|
||||
} else {
|
||||
def_to_def2(arena, env, scope, inner_def, region)
|
||||
}
|
||||
}
|
||||
Body(&loc_pattern, &loc_expr) => {
|
||||
let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0;
|
||||
let expr_id = env.pool.add(expr2);
|
||||
|
@ -66,6 +105,12 @@ pub fn def_to_def2<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
fn all_newlines(comments: &[CommentOrNewline]) -> bool {
|
||||
comments
|
||||
.iter()
|
||||
.all(|com_or_newline| com_or_newline.is_newline())
|
||||
}
|
||||
|
||||
pub fn str_to_def2<'a>(
|
||||
arena: &'a Bump,
|
||||
input: &'a str,
|
||||
|
|
|
@ -76,7 +76,7 @@ pub fn expr_to_expr2<'a>(
|
|||
}
|
||||
Num(string) => {
|
||||
match finish_parsing_num(string) {
|
||||
Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => {
|
||||
Ok(ParsedNumResult::UnknownNum(int, _) | ParsedNumResult::Int(int, _)) => {
|
||||
let expr = Expr2::SmallInt {
|
||||
number: IntVal::I64(match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
|
|
|
@ -8,6 +8,7 @@ use roc_can::num::{
|
|||
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
|
||||
};
|
||||
use roc_collections::all::BumpMap;
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_parse::ast::{StrLiteral, StrSegment};
|
||||
use roc_parse::pattern::PatternType;
|
||||
|
@ -196,7 +197,7 @@ pub fn to_pattern2<'a>(
|
|||
let problem = MalformedPatternProblem::MalformedInt;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok(ParsedNumResult::UnknownNum(int)) => {
|
||||
Ok(ParsedNumResult::UnknownNum(int, _bound)) => {
|
||||
Pattern2::NumLiteral(
|
||||
env.var_store.fresh(),
|
||||
match int {
|
||||
|
@ -269,6 +270,8 @@ pub fn to_pattern2<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
OpaqueRef(..) => todo_opaques!(),
|
||||
|
||||
Apply(tag, patterns) => {
|
||||
let can_patterns = PoolVec::with_capacity(patterns.len() as u32, env.pool);
|
||||
for (loc_pattern, node_id) in (*patterns).iter().zip(can_patterns.iter_node_ids()) {
|
||||
|
|
|
@ -329,9 +329,9 @@ pub fn to_type2<'a>(
|
|||
annotation: &roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
) -> Type2 {
|
||||
use roc_parse::ast::AliasHeader;
|
||||
use roc_parse::ast::Pattern;
|
||||
use roc_parse::ast::TypeAnnotation::*;
|
||||
use roc_parse::ast::TypeHeader;
|
||||
|
||||
match annotation {
|
||||
Apply(module_name, ident, targs) => {
|
||||
|
@ -455,7 +455,7 @@ pub fn to_type2<'a>(
|
|||
As(
|
||||
loc_inner,
|
||||
_spaces,
|
||||
AliasHeader {
|
||||
TypeHeader {
|
||||
name,
|
||||
vars: loc_vars,
|
||||
},
|
||||
|
|
|
@ -21,7 +21,6 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
|
|||
}),
|
||||
subs_by_module,
|
||||
TargetInfo::default_x86_64(),
|
||||
roc_can::builtins::builtin_defs_map,
|
||||
);
|
||||
|
||||
match loaded {
|
||||
|
|
|
@ -225,7 +225,7 @@ fn solve<'a>(
|
|||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
match unify(subs, actual, expected, Mode::Eq) {
|
||||
match unify(subs, actual, expected, Mode::EQ) {
|
||||
Success(vars) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
|
@ -318,7 +318,7 @@ fn solve<'a>(
|
|||
expectation.get_type_ref(),
|
||||
);
|
||||
|
||||
match unify(subs, actual, expected, Mode::Eq) {
|
||||
match unify(subs, actual, expected, Mode::EQ) {
|
||||
Success(vars) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
|
@ -389,7 +389,7 @@ fn solve<'a>(
|
|||
);
|
||||
|
||||
// TODO(ayazhafiz): presence constraints for Expr2/Type2
|
||||
match unify(subs, actual, expected, Mode::Eq) {
|
||||
match unify(subs, actual, expected, Mode::EQ) {
|
||||
Success(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);
|
||||
|
||||
match unify(subs, actual, includes, Mode::Present) {
|
||||
match unify(subs, actual, includes, Mode::PRESENT) {
|
||||
Success(vars) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
|
@ -1400,6 +1400,8 @@ fn adjust_rank_content(
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
RangedNumber(typ, _vars) => {
|
||||
instantiate_rigids_help(subs, max_rank, pools, typ);
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
|
@ -1806,6 +1812,25 @@ fn deep_copy_var_help(
|
|||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,11 +66,11 @@ mimalloc = { version = "0.1.26", default-features = false }
|
|||
target-lexicon = "0.12.2"
|
||||
tempfile = "3.2.0"
|
||||
|
||||
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] }
|
||||
wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-singlepass", "default-universal"] }
|
||||
wasmer-wasi = { version = "2.0.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] }
|
||||
wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] }
|
||||
wasmer-wasi = "2.0.0"
|
||||
pretty_assertions = "1.0.0"
|
||||
roc_test_utils = { path = "../test_utils" }
|
||||
|
|
|
@ -4,7 +4,6 @@ use roc_build::{
|
|||
program,
|
||||
};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_load::file::LoadingProblem;
|
||||
use roc_mono::ir::OptLevel;
|
||||
|
@ -74,7 +73,6 @@ pub fn build_file<'a>(
|
|||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
target_info,
|
||||
builtin_defs_map,
|
||||
)?;
|
||||
|
||||
use target_lexicon::Architecture;
|
||||
|
@ -206,7 +204,11 @@ pub fn build_file<'a>(
|
|||
buf.push_str("Code Generation");
|
||||
buf.push('\n');
|
||||
|
||||
report_timing(buf, "Generate LLVM IR", code_gen_timing.code_gen);
|
||||
report_timing(
|
||||
buf,
|
||||
"Generate Assembly from Mono IR",
|
||||
code_gen_timing.code_gen,
|
||||
);
|
||||
report_timing(buf, "Emit .o file", code_gen_timing.emit_o_file);
|
||||
|
||||
let compilation_end = compilation_start.elapsed().unwrap();
|
||||
|
@ -309,7 +311,9 @@ fn spawn_rebuild_thread(
|
|||
) -> std::thread::JoinHandle<u128> {
|
||||
let thread_local_target = target.clone();
|
||||
std::thread::spawn(move || {
|
||||
if !precompiled {
|
||||
print!("🔨 Rebuilding host... ");
|
||||
}
|
||||
|
||||
let rebuild_host_start = SystemTime::now();
|
||||
if !precompiled {
|
||||
|
@ -340,7 +344,9 @@ fn spawn_rebuild_thread(
|
|||
}
|
||||
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
|
||||
|
||||
if !precompiled {
|
||||
println!("Done!");
|
||||
}
|
||||
|
||||
rebuild_host_end.as_millis()
|
||||
})
|
||||
|
@ -372,7 +378,6 @@ pub fn check_file(
|
|||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
target_info,
|
||||
builtin_defs_map,
|
||||
)?;
|
||||
|
||||
let buf = &mut String::with_capacity(1024);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::FormatMode;
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_error_macros::{internal_error, user_error};
|
||||
|
@ -8,8 +9,8 @@ use roc_fmt::module::fmt_module;
|
|||
use roc_fmt::Buf;
|
||||
use roc_module::called_via::{BinOp, UnaryOp};
|
||||
use roc_parse::ast::{
|
||||
AliasHeader, AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag,
|
||||
TypeAnnotation, WhenBranch,
|
||||
AssignedField, Collection, Expr, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation,
|
||||
TypeHeader, WhenBranch,
|
||||
};
|
||||
use roc_parse::header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
|
||||
|
@ -24,7 +25,7 @@ use roc_parse::{
|
|||
};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
pub fn format(files: std::vec::Vec<PathBuf>) {
|
||||
pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), String> {
|
||||
for file in files {
|
||||
let arena = Bump::new();
|
||||
|
||||
|
@ -99,10 +100,23 @@ pub fn format(files: std::vec::Vec<PathBuf>) {
|
|||
unstable_2_file.display());
|
||||
}
|
||||
|
||||
match mode {
|
||||
FormatMode::CheckOnly => {
|
||||
// If we notice that this file needs to be formatted, return early
|
||||
if buf.as_str() != src {
|
||||
return Err("One or more files need to be reformatted.".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
FormatMode::Format => {
|
||||
// If all the checks above passed, actually write out the new file.
|
||||
std::fs::write(&file, buf.as_str()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Ast<'a> {
|
||||
|
@ -388,15 +402,25 @@ impl<'a> RemoveSpaces<'a> for Def<'a> {
|
|||
Def::Annotation(a.remove_spaces(arena), b.remove_spaces(arena))
|
||||
}
|
||||
Def::Alias {
|
||||
header: AliasHeader { name, vars },
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => Def::Alias {
|
||||
header: AliasHeader {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
ann: ann.remove_spaces(arena),
|
||||
},
|
||||
Def::Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ,
|
||||
} => Def::Opaque {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
typ: typ.remove_spaces(arena),
|
||||
},
|
||||
Def::Body(a, b) => Def::Body(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
|
@ -500,6 +524,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
|||
Expr::Underscore(a) => Expr::Underscore(a),
|
||||
Expr::GlobalTag(a) => Expr::GlobalTag(a),
|
||||
Expr::PrivateTag(a) => Expr::PrivateTag(a),
|
||||
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
|
||||
Expr::Closure(a, b) => Expr::Closure(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
|
@ -550,6 +575,7 @@ impl<'a> RemoveSpaces<'a> for Pattern<'a> {
|
|||
Pattern::Identifier(a) => Pattern::Identifier(a),
|
||||
Pattern::GlobalTag(a) => Pattern::GlobalTag(a),
|
||||
Pattern::PrivateTag(a) => Pattern::PrivateTag(a),
|
||||
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
|
||||
Pattern::Apply(a, b) => Pattern::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
|
|
|
@ -37,6 +37,7 @@ pub const FLAG_TIME: &str = "time";
|
|||
pub const FLAG_LINK: &str = "roc-linker";
|
||||
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
|
||||
pub const FLAG_VALGRIND: &str = "valgrind";
|
||||
pub const FLAG_CHECK: &str = "check";
|
||||
pub const ROC_FILE: &str = "ROC_FILE";
|
||||
pub const ROC_DIR: &str = "ROC_DIR";
|
||||
pub const BACKEND: &str = "BACKEND";
|
||||
|
@ -122,6 +123,12 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.index(1)
|
||||
.multiple_values(true)
|
||||
.required(false))
|
||||
.arg(
|
||||
Arg::new(FLAG_CHECK)
|
||||
.long(FLAG_CHECK)
|
||||
.about("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
|
||||
.required(false),
|
||||
)
|
||||
)
|
||||
.subcommand(App::new(CMD_VERSION)
|
||||
.about("Print version information")
|
||||
|
@ -242,6 +249,11 @@ pub enum BuildConfig {
|
|||
BuildAndRun { roc_file_arg_index: usize },
|
||||
}
|
||||
|
||||
pub enum FormatMode {
|
||||
Format,
|
||||
CheckOnly,
|
||||
}
|
||||
|
||||
pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
||||
use build::build_file;
|
||||
use std::str::FromStr;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use roc_cli::build::check_file;
|
||||
use roc_cli::{
|
||||
build_app, docs, format, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_FORMAT,
|
||||
CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
|
||||
build_app, docs, format, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT,
|
||||
CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_TIME, ROC_FILE,
|
||||
};
|
||||
use roc_load::file::LoadingProblem;
|
||||
use std::fs::{self, FileType};
|
||||
|
@ -150,9 +150,20 @@ fn main() -> io::Result<()> {
|
|||
roc_files_recursive(os_str.as_os_str(), metadata.file_type(), &mut roc_files)?;
|
||||
}
|
||||
|
||||
format(roc_files);
|
||||
let format_mode = match matches.is_present(FLAG_CHECK) {
|
||||
true => FormatMode::CheckOnly,
|
||||
false => FormatMode::Format,
|
||||
};
|
||||
|
||||
Ok(0)
|
||||
let format_exit_code = match format(roc_files, format_mode) {
|
||||
Ok(_) => 0,
|
||||
Err(message) => {
|
||||
eprintln!("{}", message);
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
Ok(format_exit_code)
|
||||
}
|
||||
Some((CMD_VERSION, _)) => {
|
||||
println!("roc {}", concatcp!(include_str!("../../version.txt"), "\n"));
|
||||
|
|
|
@ -69,6 +69,17 @@ mod cli_run {
|
|||
assert_multiline_str_eq!(err, expected.into());
|
||||
}
|
||||
|
||||
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
|
||||
let flags = &["--check"];
|
||||
let out = run_roc(&[&["format", &file.to_str().unwrap()], &flags[..]].concat());
|
||||
|
||||
if expects_success_exit_code {
|
||||
assert!(out.status.success());
|
||||
} else {
|
||||
assert!(!out.status.success());
|
||||
}
|
||||
}
|
||||
|
||||
fn check_output_with_stdin(
|
||||
file: &Path,
|
||||
stdin: &[&str],
|
||||
|
@ -801,6 +812,78 @@ mod cli_run {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[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.
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_check_good() {
|
||||
check_format_check_as_expected(&fixture_file("format", "Formatted.roc"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_check_reformatting_needed() {
|
||||
check_format_check_as_expected(&fixture_file("format", "NotFormatted.roc"), false);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
6
cli/tests/fixtures/format/Formatted.roc
vendored
Normal file
6
cli/tests/fixtures/format/Formatted.roc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
app "formatted"
|
||||
packages { pf: "platform" } imports []
|
||||
provides [ main ] to pf
|
||||
|
||||
main : Str
|
||||
main = Dep1.value1 {}
|
6
cli/tests/fixtures/format/NotFormatted.roc
vendored
Normal file
6
cli/tests/fixtures/format/NotFormatted.roc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
app "formatted"
|
||||
packages { pf: "platform" }
|
||||
provides [ main ] to pf
|
||||
|
||||
main : Str
|
||||
main = Dep1.value1 {}
|
3
cli/tests/known_bad/ExposedNotDefined.roc
Normal file
3
cli/tests/known_bad/ExposedNotDefined.roc
Normal file
|
@ -0,0 +1,3 @@
|
|||
interface Foo
|
||||
exposes [ bar ]
|
||||
imports []
|
6
cli/tests/known_bad/Symbol.roc
Normal file
6
cli/tests/known_bad/Symbol.roc
Normal 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
|
4
cli/tests/known_bad/UnknownGeneratesWith.roc
Normal file
4
cli/tests/known_bad/UnknownGeneratesWith.roc
Normal file
|
@ -0,0 +1,4 @@
|
|||
hosted UnknownGeneratesWith
|
||||
exposes [ Effect, after, map, always ]
|
||||
imports []
|
||||
generates Effect with [ after, map, always, foobar ]
|
7
cli/tests/known_bad/UnusedImport.roc
Normal file
7
cli/tests/known_bad/UnusedImport.roc
Normal file
|
@ -0,0 +1,7 @@
|
|||
interface UnusedImport
|
||||
exposes [ plainText, emText ]
|
||||
imports [ Symbol.{ Ident } ]
|
||||
|
||||
plainText = \str -> PlainText str
|
||||
|
||||
emText = \str -> EmText str
|
54
cli_utils/Cargo.lock
generated
54
cli_utils/Cargo.lock
generated
|
@ -964,12 +964,6 @@ dependencies = [
|
|||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.22"
|
||||
|
@ -2047,14 +2041,11 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"petgraph",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"thread-id",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -2107,16 +2098,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "phf"
|
||||
version = "0.9.0"
|
||||
|
@ -2799,6 +2780,7 @@ dependencies = [
|
|||
"bumpalo",
|
||||
"lazy_static",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_ident",
|
||||
"roc_region",
|
||||
"snafu",
|
||||
|
@ -2815,6 +2797,7 @@ dependencies = [
|
|||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
|
@ -2947,6 +2930,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
"static_assertions",
|
||||
|
@ -2957,6 +2941,7 @@ dependencies = [
|
|||
name = "roc_unify"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_types",
|
||||
|
@ -3403,17 +3388,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "threadpool"
|
||||
version = "1.8.1"
|
||||
|
@ -3584,9 +3558,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -3594,9 +3568,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
|
@ -3621,9 +3595,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -3631,9 +3605,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -3644,9 +3618,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.78"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
|
|
|
@ -147,3 +147,18 @@ pub fn new_arrow_mn(ast_node_id: ASTNodeId, newlines_at_end: usize) -> MarkupNod
|
|||
newlines_at_end,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_comments_mn(
|
||||
comments: String,
|
||||
ast_node_id: ASTNodeId,
|
||||
newlines_at_end: usize,
|
||||
) -> MarkupNode {
|
||||
MarkupNode::Text {
|
||||
content: comments,
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Comment,
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt: None,
|
||||
newlines_at_end,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ pub fn ast_to_mark_nodes<'a>(
|
|||
let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)];
|
||||
|
||||
for &def_id in ast.def_ids.iter() {
|
||||
// for debugging
|
||||
//println!("{}", def2_to_string(def_id, env.pool));
|
||||
|
||||
let def2 = env.pool.get(def_id);
|
||||
|
||||
let expr2_markup_id = def2_to_markup(env, def2, def_id, mark_node_pool, interns)?;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use crate::{
|
||||
markup::{common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node},
|
||||
markup::{
|
||||
common_nodes::new_blank_mn_w_nls,
|
||||
top_level_def::{tld_mark_node, tld_w_comments_mark_node},
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
};
|
||||
|
||||
|
@ -46,6 +49,36 @@ pub fn def2_to_markup<'a>(
|
|||
mark_node_pool.add(tld_mn)
|
||||
}
|
||||
Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)),
|
||||
Def2::CommentsBefore { comments, def_id } => {
|
||||
let inner_def = env.pool.get(*def_id);
|
||||
let inner_def_mark_node_id =
|
||||
def2_to_markup(env, inner_def, *def_id, mark_node_pool, interns)?;
|
||||
|
||||
let full_mark_node = tld_w_comments_mark_node(
|
||||
comments.clone(),
|
||||
inner_def_mark_node_id,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
true,
|
||||
)?;
|
||||
|
||||
mark_node_pool.add(full_mark_node)
|
||||
}
|
||||
Def2::CommentsAfter { def_id, comments } => {
|
||||
let inner_def = env.pool.get(*def_id);
|
||||
let inner_def_mark_node_id =
|
||||
def2_to_markup(env, inner_def, *def_id, mark_node_pool, interns)?;
|
||||
|
||||
let full_mark_node = tld_w_comments_mark_node(
|
||||
comments.clone(),
|
||||
inner_def_mark_node_id,
|
||||
ast_node_id,
|
||||
mark_node_pool,
|
||||
false,
|
||||
)?;
|
||||
|
||||
mark_node_pool.add(full_mark_node)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(mark_node_id)
|
||||
|
|
|
@ -5,11 +5,16 @@ use roc_ast::{
|
|||
use roc_module::symbol::IdentId;
|
||||
|
||||
use crate::{
|
||||
markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode},
|
||||
markup::{
|
||||
attribute::Attributes,
|
||||
common_nodes::{new_comments_mn, new_equals_mn},
|
||||
nodes::MarkupNode,
|
||||
},
|
||||
slow_pool::{MarkNodeId, SlowPool},
|
||||
syntax_highlight::HighlightStyle,
|
||||
};
|
||||
|
||||
// Top Level Defined Value. example: `main = "Hello, World!"`
|
||||
pub fn tld_mark_node<'a>(
|
||||
identifier_id: IdentId,
|
||||
expr_mark_node_id: MarkNodeId,
|
||||
|
@ -36,8 +41,33 @@ pub fn tld_mark_node<'a>(
|
|||
ast_node_id,
|
||||
children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id],
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 2,
|
||||
newlines_at_end: 3,
|
||||
};
|
||||
|
||||
Ok(full_let_node)
|
||||
}
|
||||
|
||||
pub fn tld_w_comments_mark_node(
|
||||
comments: String,
|
||||
def_mark_node_id: MarkNodeId,
|
||||
ast_node_id: ASTNodeId,
|
||||
mark_node_pool: &mut SlowPool,
|
||||
comments_before: bool,
|
||||
) -> ASTResult<MarkupNode> {
|
||||
let comment_mn_id = mark_node_pool.add(new_comments_mn(comments, ast_node_id, 1));
|
||||
|
||||
let children_ids = if comments_before {
|
||||
vec![comment_mn_id, def_mark_node_id]
|
||||
} else {
|
||||
vec![def_mark_node_id, comment_mn_id]
|
||||
};
|
||||
|
||||
let tld_w_comment_node = MarkupNode::Nested {
|
||||
ast_node_id,
|
||||
children_ids,
|
||||
parent_id_opt: None,
|
||||
newlines_at_end: 2,
|
||||
};
|
||||
|
||||
Ok(tld_w_comment_node)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ pub enum HighlightStyle {
|
|||
Import,
|
||||
Provides,
|
||||
Blank,
|
||||
Comment,
|
||||
DocsComment,
|
||||
}
|
||||
|
||||
pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
|
||||
|
@ -42,7 +44,8 @@ pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
|
|||
(Import, from_hsb(225, 50, 100)),
|
||||
(Provides, from_hsb(225, 50, 100)),
|
||||
(Blank, from_hsb(258, 50, 90)),
|
||||
// comment from_hsb(285, 6, 47) or 186, 35, 40
|
||||
(Comment, from_hsb(258, 50, 90)), // TODO check color
|
||||
(DocsComment, from_hsb(258, 50, 90)), // TODO check color
|
||||
]
|
||||
.iter()
|
||||
.for_each(|tup| {
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::collections::HashMap;
|
|||
use std::env;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Output};
|
||||
use std::process::{self, Child, Command, Output};
|
||||
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
||||
|
||||
fn zig_executable() -> String {
|
||||
|
@ -636,6 +636,33 @@ fn library_path<const N: usize>(segments: [&str; N]) -> Option<PathBuf> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Given a list of library directories and the name of a library, find the 1st match
|
||||
///
|
||||
/// The provided list of library directories should be in the form of a list of
|
||||
/// directories, where each directory is represented by a series of path segments, like
|
||||
///
|
||||
/// ["/usr", "lib"]
|
||||
///
|
||||
/// Each directory will be checked for a file with the provided filename, and the first
|
||||
/// match will be returned.
|
||||
///
|
||||
/// If there are no matches, [`None`] will be returned.
|
||||
fn look_for_library(lib_dirs: &[&[&str]], lib_filename: &str) -> Option<PathBuf> {
|
||||
lib_dirs
|
||||
.iter()
|
||||
.map(|lib_dir| {
|
||||
lib_dir.iter().fold(PathBuf::new(), |mut path, segment| {
|
||||
path.push(segment);
|
||||
path
|
||||
})
|
||||
})
|
||||
.map(|mut path| {
|
||||
path.push(lib_filename);
|
||||
path
|
||||
})
|
||||
.find(|path| path.exists())
|
||||
}
|
||||
|
||||
fn link_linux(
|
||||
target: &Triple,
|
||||
output_path: PathBuf,
|
||||
|
@ -670,28 +697,75 @@ fn link_linux(
|
|||
));
|
||||
}
|
||||
|
||||
let libcrt_path =
|
||||
// Some things we'll need to build a list of dirs to check for libraries
|
||||
let maybe_nix_path = nix_path_opt();
|
||||
let usr_lib_arch = ["/usr", "lib", &architecture];
|
||||
let lib_arch = ["/lib", &architecture];
|
||||
let nix_path_segments;
|
||||
let lib_dirs_if_nix: [&[&str]; 5];
|
||||
let lib_dirs_if_nonix: [&[&str]; 4];
|
||||
|
||||
// Build the aformentioned list
|
||||
let lib_dirs: &[&[&str]] =
|
||||
// give preference to nix_path if it's defined, this prevents bugs
|
||||
if let Some(nix_path) = nix_path_opt() {
|
||||
library_path([&nix_path])
|
||||
.unwrap()
|
||||
if let Some(nix_path) = &maybe_nix_path {
|
||||
nix_path_segments = [nix_path.as_str()];
|
||||
lib_dirs_if_nix = [
|
||||
&nix_path_segments,
|
||||
&usr_lib_arch,
|
||||
&lib_arch,
|
||||
&["/usr", "lib"],
|
||||
&["/usr", "lib64"],
|
||||
];
|
||||
&lib_dirs_if_nix
|
||||
} else {
|
||||
library_path(["/usr", "lib", &architecture])
|
||||
.or_else(|| library_path(["/usr", "lib"]))
|
||||
.unwrap()
|
||||
lib_dirs_if_nonix = [
|
||||
&usr_lib_arch,
|
||||
&lib_arch,
|
||||
&["/usr", "lib"],
|
||||
&["/usr", "lib64"],
|
||||
];
|
||||
&lib_dirs_if_nonix
|
||||
};
|
||||
|
||||
// Look for the libraries we'll need
|
||||
|
||||
let libgcc_name = "libgcc_s.so.1";
|
||||
let libgcc_path =
|
||||
// give preference to nix_path if it's defined, this prevents bugs
|
||||
if let Some(nix_path) = nix_path_opt() {
|
||||
library_path([&nix_path, libgcc_name])
|
||||
.unwrap()
|
||||
} else {
|
||||
library_path(["/lib", &architecture, libgcc_name])
|
||||
.or_else(|| library_path(["/usr", "lib", &architecture, libgcc_name]))
|
||||
.or_else(|| library_path(["/usr", "lib", libgcc_name]))
|
||||
.unwrap()
|
||||
let libgcc_path = look_for_library(lib_dirs, libgcc_name);
|
||||
|
||||
let crti_name = "crti.o";
|
||||
let crti_path = look_for_library(lib_dirs, crti_name);
|
||||
|
||||
let crtn_name = "crtn.o";
|
||||
let crtn_path = look_for_library(lib_dirs, crtn_name);
|
||||
|
||||
let scrt1_name = "Scrt1.o";
|
||||
let scrt1_path = look_for_library(lib_dirs, scrt1_name);
|
||||
|
||||
// Unwrap all the paths at once so we can inform the user of all missing libs at once
|
||||
let (libgcc_path, crti_path, crtn_path, scrt1_path) =
|
||||
match (libgcc_path, crti_path, crtn_path, scrt1_path) {
|
||||
(Some(libgcc), Some(crti), Some(crtn), Some(scrt1)) => (libgcc, crti, crtn, scrt1),
|
||||
(maybe_gcc, maybe_crti, maybe_crtn, maybe_scrt1) => {
|
||||
if maybe_gcc.is_none() {
|
||||
eprintln!("Couldn't find libgcc_s.so.1!");
|
||||
eprintln!("You may need to install libgcc\n");
|
||||
}
|
||||
if maybe_crti.is_none() | maybe_crtn.is_none() | maybe_scrt1.is_none() {
|
||||
eprintln!("Couldn't find the glibc development files!");
|
||||
eprintln!("We need the objects crti.o, crtn.o, and Scrt1.o");
|
||||
eprintln!("You may need to install the glibc development package");
|
||||
eprintln!("(probably called glibc-dev or glibc-devel)\n");
|
||||
}
|
||||
|
||||
let dirs = lib_dirs
|
||||
.iter()
|
||||
.map(|segments| segments.join("/"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
eprintln!("We looked in the following directories:\n{}", dirs);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let ld_linux = match target.architecture {
|
||||
|
@ -717,7 +791,7 @@ fn link_linux(
|
|||
LinkType::Executable => (
|
||||
// Presumably this S stands for Static, since if we include Scrt1.o
|
||||
// in the linking for dynamic builds, linking fails.
|
||||
vec![libcrt_path.join("Scrt1.o").to_str().unwrap().to_string()],
|
||||
vec![scrt1_path.to_string_lossy().into_owned()],
|
||||
output_path,
|
||||
),
|
||||
LinkType::Dylib => {
|
||||
|
@ -772,8 +846,8 @@ fn link_linux(
|
|||
"-A",
|
||||
arch_str(target),
|
||||
"-pie",
|
||||
libcrt_path.join("crti.o").to_str().unwrap(),
|
||||
libcrt_path.join("crtn.o").to_str().unwrap(),
|
||||
&*crti_path.to_string_lossy(),
|
||||
&*crtn_path.to_string_lossy(),
|
||||
])
|
||||
.args(&base_args)
|
||||
.args(&["-dynamic-linker", ld_linux])
|
||||
|
@ -850,6 +924,18 @@ fn link_macos(
|
|||
ld_command.arg(format!("-L{}/swift", sdk_path));
|
||||
};
|
||||
|
||||
let roc_link_flags = match env::var("ROC_LINK_FLAGS") {
|
||||
Ok(flags) => {
|
||||
println!("⚠️ CAUTION: The ROC_LINK_FLAGS environment variable is a temporary workaround, and will no longer do anything once surgical linking lands! If you're concerned about what this means for your use case, please ask about it on Zulip.");
|
||||
|
||||
flags
|
||||
}
|
||||
Err(_) => "".to_string(),
|
||||
};
|
||||
for roc_link_flag in roc_link_flags.split_whitespace() {
|
||||
ld_command.arg(roc_link_flag.to_string());
|
||||
}
|
||||
|
||||
ld_command.args(&[
|
||||
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
|
||||
// for discussion and further references
|
||||
|
|
|
@ -7,7 +7,7 @@ use roc_module::symbol::{Interns, ModuleId};
|
|||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use roc_collections::all::MutMap;
|
||||
#[cfg(feature = "target-wasm32")]
|
||||
|
@ -230,7 +230,6 @@ pub fn gen_from_mono_module_llvm(
|
|||
use inkwell::context::Context;
|
||||
use inkwell::module::Linkage;
|
||||
use inkwell::targets::{CodeModel, FileType, RelocMode};
|
||||
use std::time::SystemTime;
|
||||
|
||||
let code_gen_start = SystemTime::now();
|
||||
|
||||
|
@ -486,6 +485,7 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
loaded: MonomorphizedModule,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
let code_gen_start = SystemTime::now();
|
||||
let MonomorphizedModule {
|
||||
module_id,
|
||||
procedures,
|
||||
|
@ -517,12 +517,19 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
&mut interns,
|
||||
platform_and_builtins_object_file_bytes,
|
||||
procedures,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let code_gen = code_gen_start.elapsed().unwrap();
|
||||
let emit_o_file_start = SystemTime::now();
|
||||
|
||||
std::fs::write(&app_o_file, &bytes).expect("failed to write object to file");
|
||||
|
||||
CodeGenTiming::default()
|
||||
let emit_o_file = emit_o_file_start.elapsed().unwrap();
|
||||
|
||||
CodeGenTiming {
|
||||
code_gen,
|
||||
emit_o_file,
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_mono_module_dev_assembly(
|
||||
|
@ -531,6 +538,8 @@ fn gen_from_mono_module_dev_assembly(
|
|||
target: &target_lexicon::Triple,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
let code_gen_start = SystemTime::now();
|
||||
|
||||
let lazy_literals = true;
|
||||
let generate_allocators = false; // provided by the platform
|
||||
|
||||
|
@ -552,10 +561,18 @@ fn gen_from_mono_module_dev_assembly(
|
|||
|
||||
let module_object = roc_gen_dev::build_module(&env, &mut interns, target, procedures);
|
||||
|
||||
let code_gen = code_gen_start.elapsed().unwrap();
|
||||
let emit_o_file_start = SystemTime::now();
|
||||
|
||||
let module_out = module_object
|
||||
.write()
|
||||
.expect("failed to build output object");
|
||||
std::fs::write(&app_o_file, module_out).expect("failed to write object to file");
|
||||
|
||||
CodeGenTiming::default()
|
||||
let emit_o_file = emit_o_file_start.elapsed().unwrap();
|
||||
|
||||
CodeGenTiming {
|
||||
code_gen,
|
||||
emit_o_file,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,3 +11,7 @@ roc_region = { path = "../region" }
|
|||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
|
||||
[build-dependencies]
|
||||
# dunce can be removed once ziglang/zig#5109 is fixed
|
||||
dunce = "1.0.2"
|
||||
|
|
|
@ -28,7 +28,7 @@ There will be two directories like `roc_builtins-[some random characters]`, look
|
|||
|
||||
> The bitcode is a bunch of bytes that aren't particularly human-readable.
|
||||
> If you want to take a look at the human-readable LLVM IR, look at
|
||||
> `target/debug/build/roc_builtins-[some random characters]/out/builtins.ll`
|
||||
> `compiler/builtins/bitcode/builtins.ll`
|
||||
|
||||
## Calling bitcode functions
|
||||
|
||||
|
|
|
@ -210,6 +210,7 @@ pub fn listMap(
|
|||
}
|
||||
}
|
||||
|
||||
// List.mapWithIndex : List before, (before, Nat -> after) -> List after
|
||||
pub fn listMapWithIndex(
|
||||
list: RocList,
|
||||
caller: Caller2,
|
||||
|
@ -231,7 +232,8 @@ pub fn listMapWithIndex(
|
|||
}
|
||||
|
||||
while (i < size) : (i += 1) {
|
||||
caller(data, @ptrCast(?[*]u8, &i), source_ptr + (i * old_element_width), target_ptr + (i * new_element_width));
|
||||
// before, Nat -> after
|
||||
caller(data, source_ptr + (i * old_element_width), @ptrCast(?[*]u8, &i), target_ptr + (i * new_element_width));
|
||||
}
|
||||
|
||||
return output;
|
||||
|
|
|
@ -98,6 +98,14 @@ comptime {
|
|||
num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil.");
|
||||
}
|
||||
|
||||
inline for (INTEGERS) |FROM| {
|
||||
inline for (INTEGERS) |TO| {
|
||||
// We're exporting more than we need here, but that's okay.
|
||||
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
|
||||
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
|
||||
}
|
||||
}
|
||||
|
||||
inline for (FLOATS) |T| {
|
||||
num.exportAsin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".asin.");
|
||||
num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos.");
|
||||
|
|
|
@ -108,6 +108,39 @@ pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
|
|||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn ToIntCheckedResult(comptime T: type) type {
|
||||
// On the Roc side we sort by alignment; putting the errorcode last
|
||||
// always works out (no number with smaller alignment than 1).
|
||||
return extern struct {
|
||||
value: T,
|
||||
out_of_bounds: bool,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn exportToIntCheckingMax(comptime From: type, comptime To: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
|
||||
if (input > std.math.maxInt(To)) {
|
||||
return .{ .out_of_bounds = true, .value = 0 };
|
||||
}
|
||||
return .{ .out_of_bounds = false, .value = @intCast(To, input) };
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportToIntCheckingMaxAndMin(comptime From: type, comptime To: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: From) callconv(.C) ToIntCheckedResult(To) {
|
||||
if (input > std.math.maxInt(To) or input < std.math.minInt(To)) {
|
||||
return .{ .out_of_bounds = true, .value = 0 };
|
||||
}
|
||||
return .{ .out_of_bounds = false, .value = @intCast(To, input) };
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(From), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn bytesToU16C(arg: RocList, position: usize) callconv(.C) u16 {
|
||||
return @call(.{ .modifier = always_inline }, bytesToU16, .{ arg, position });
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ fn main() {
|
|||
}
|
||||
|
||||
// "." is relative to where "build.rs" is
|
||||
let build_script_dir_path = fs::canonicalize(Path::new(".")).unwrap();
|
||||
// dunce can be removed once ziglang/zig#5109 is fixed
|
||||
let build_script_dir_path = dunce::canonicalize(Path::new(".")).unwrap();
|
||||
let bitcode_path = build_script_dir_path.join("bitcode");
|
||||
|
||||
// LLVM .bc FILES
|
||||
|
|
|
@ -207,7 +207,7 @@ empty : List *
|
|||
## Returns a list with the given length, where every element is the given value.
|
||||
##
|
||||
##
|
||||
repeat : Nat, elem -> List elem
|
||||
repeat : elem, Nat -> List elem
|
||||
|
||||
## Returns a list of all the integers between one and another,
|
||||
## including both of the given numbers.
|
||||
|
@ -279,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
|
||||
## of the element to the conversion function.
|
||||
mapWithIndex : List before, (Nat, before -> after) -> List after
|
||||
mapWithIndex : List before, (before, Nat -> after) -> List after
|
||||
|
||||
## This works like [List.map], except at any time you can return `Err` to
|
||||
## cancel the entire operation immediately, and return that #Err.
|
||||
|
|
|
@ -100,6 +100,26 @@ interface Num
|
|||
subWrap,
|
||||
sqrt,
|
||||
tan,
|
||||
toI8,
|
||||
toI8Checked,
|
||||
toI16,
|
||||
toI16Checked,
|
||||
toI32,
|
||||
toI32Checked,
|
||||
toI64,
|
||||
toI64Checked,
|
||||
toI128,
|
||||
toI128Checked,
|
||||
toU8,
|
||||
toU8Checked,
|
||||
toU16,
|
||||
toU16Checked,
|
||||
toU32,
|
||||
toU32Checked,
|
||||
toU64,
|
||||
toU64Checked,
|
||||
toU128,
|
||||
toU128Checked,
|
||||
toFloat,
|
||||
toStr
|
||||
]
|
||||
|
@ -592,6 +612,35 @@ mulCheckOverflow : Num a, Num a -> Result (Num a) [ Overflow ]*
|
|||
|
||||
## Convert
|
||||
|
||||
## Convert any [Int] to a specifically-sized [Int], without checking validity.
|
||||
## These are unchecked bitwise operations,
|
||||
## so if the source number is outside the target range, then these will
|
||||
## effectively modulo-wrap around the target range to reach a valid value.
|
||||
toI8 : Int * -> I8
|
||||
toI16 : Int * -> I16
|
||||
toI32 : Int * -> I32
|
||||
toI64 : Int * -> I64
|
||||
toI128 : Int * -> I128
|
||||
toU8 : Int * -> U8
|
||||
toU16 : Int * -> U16
|
||||
toU32 : Int * -> U32
|
||||
toU64 : Int * -> U64
|
||||
toU128 : Int * -> U128
|
||||
## Convert any [Int] to a specifically-sized [Int], after checking validity.
|
||||
## These are checked bitwise operations,
|
||||
## so if the source number is outside the target range, then these will
|
||||
## return `Err OutOfBounds`.
|
||||
toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
|
||||
toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
|
||||
toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
|
||||
toI64Checked : Int * -> Result I64 [ OutOfBounds ]*
|
||||
toI128Checked : Int * -> Result I128 [ OutOfBounds ]*
|
||||
toU8Checked : Int * -> Result U8 [ OutOfBounds ]*
|
||||
toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
|
||||
toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
|
||||
toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
|
||||
toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
|
||||
|
||||
## Convert a number to a [Str].
|
||||
##
|
||||
## This is the same as calling `Num.format {}` - so for more details on
|
||||
|
|
|
@ -12,7 +12,7 @@ pub const BUILTINS_WASM32_OBJ_PATH: &str = env!(
|
|||
"Env var BUILTINS_WASM32_O not found. Is there a problem with the build script?"
|
||||
);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct IntrinsicName {
|
||||
pub options: [&'static str; 14],
|
||||
}
|
||||
|
@ -159,6 +159,21 @@ impl IntWidth {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn type_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::I8 => "i8",
|
||||
Self::I16 => "i16",
|
||||
Self::I32 => "i32",
|
||||
Self::I64 => "i64",
|
||||
Self::I128 => "i128",
|
||||
Self::U8 => "u8",
|
||||
Self::U16 => "u16",
|
||||
Self::U32 => "u32",
|
||||
Self::U64 => "u64",
|
||||
Self::U128 => "u128",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<DecWidth> for IntrinsicName {
|
||||
|
@ -214,11 +229,12 @@ macro_rules! float_intrinsic {
|
|||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! int_intrinsic {
|
||||
macro_rules! llvm_int_intrinsic {
|
||||
($signed_name:literal, $unsigned_name:literal) => {{
|
||||
let mut output = IntrinsicName::default();
|
||||
|
||||
// The indeces align with the `Index` impl for `IntrinsicName`.
|
||||
// LLVM uses the same types for both signed and unsigned integers.
|
||||
output.options[4] = concat!($unsigned_name, ".i8");
|
||||
output.options[5] = concat!($unsigned_name, ".i16");
|
||||
output.options[6] = concat!($unsigned_name, ".i32");
|
||||
|
@ -239,6 +255,28 @@ macro_rules! int_intrinsic {
|
|||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! int_intrinsic {
|
||||
($name:expr) => {{
|
||||
let mut output = IntrinsicName::default();
|
||||
|
||||
// The indices align with the `Index` impl for `IntrinsicName`.
|
||||
output.options[4] = concat!($name, ".u8");
|
||||
output.options[5] = concat!($name, ".u16");
|
||||
output.options[6] = concat!($name, ".u32");
|
||||
output.options[7] = concat!($name, ".u64");
|
||||
output.options[8] = concat!($name, ".u128");
|
||||
|
||||
output.options[9] = concat!($name, ".i8");
|
||||
output.options[10] = concat!($name, ".i16");
|
||||
output.options[11] = concat!($name, ".i32");
|
||||
output.options[12] = concat!($name, ".i64");
|
||||
output.options[13] = concat!($name, ".i128");
|
||||
|
||||
output
|
||||
}};
|
||||
}
|
||||
|
||||
pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin");
|
||||
pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos");
|
||||
pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
|
||||
|
@ -339,3 +377,50 @@ 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";
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IntToIntrinsicName {
|
||||
pub options: [IntrinsicName; 10],
|
||||
}
|
||||
|
||||
impl IntToIntrinsicName {
|
||||
pub const fn default() -> Self {
|
||||
Self {
|
||||
options: [IntrinsicName::default(); 10],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<IntWidth> for IntToIntrinsicName {
|
||||
type Output = IntrinsicName;
|
||||
|
||||
fn index(&self, index: IntWidth) -> &Self::Output {
|
||||
&self.options[index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! int_to_int_intrinsic {
|
||||
($name_prefix:literal, $name_suffix:literal) => {{
|
||||
let mut output = IntToIntrinsicName::default();
|
||||
|
||||
output.options[0] = int_intrinsic!(concat!($name_prefix, "u8", $name_suffix));
|
||||
output.options[1] = int_intrinsic!(concat!($name_prefix, "u16", $name_suffix));
|
||||
output.options[2] = int_intrinsic!(concat!($name_prefix, "u32", $name_suffix));
|
||||
output.options[3] = int_intrinsic!(concat!($name_prefix, "u64", $name_suffix));
|
||||
output.options[4] = int_intrinsic!(concat!($name_prefix, "u128", $name_suffix));
|
||||
|
||||
output.options[5] = int_intrinsic!(concat!($name_prefix, "i8", $name_suffix));
|
||||
output.options[6] = int_intrinsic!(concat!($name_prefix, "i16", $name_suffix));
|
||||
output.options[7] = int_intrinsic!(concat!($name_prefix, "i32", $name_suffix));
|
||||
output.options[8] = int_intrinsic!(concat!($name_prefix, "i64", $name_suffix));
|
||||
output.options[9] = int_intrinsic!(concat!($name_prefix, "i128", $name_suffix));
|
||||
|
||||
output
|
||||
}};
|
||||
}
|
||||
|
||||
pub const NUM_INT_TO_INT_CHECKING_MAX: IntToIntrinsicName =
|
||||
int_to_int_intrinsic!("roc_builtins.num.int_to_", "_checking_max");
|
||||
pub const NUM_INT_TO_INT_CHECKING_MAX_AND_MIN: IntToIntrinsicName =
|
||||
int_to_int_intrinsic!("roc_builtins.num.int_to_", "_checking_max_and_min");
|
||||
|
|
|
@ -445,6 +445,156 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
// maxI128 : I128
|
||||
add_type!(Symbol::NUM_MAX_I128, i128_type());
|
||||
|
||||
// toI8 : Int * -> I8
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I8,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(i8_type()),
|
||||
);
|
||||
|
||||
let out_of_bounds = SolvedType::TagUnion(
|
||||
vec![(TagName::Global("OutOfBounds".into()), vec![])],
|
||||
Box::new(SolvedType::Wildcard),
|
||||
);
|
||||
|
||||
// toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I8_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(i8_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toI16 : Int * -> I16
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I16,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(i16_type()),
|
||||
);
|
||||
|
||||
// toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I16_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(i16_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toI32 : Int * -> I32
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I32,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(i32_type()),
|
||||
);
|
||||
|
||||
// toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I32_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(i32_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toI64 : Int * -> I64
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I64,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(i64_type()),
|
||||
);
|
||||
|
||||
// toI64Checked : Int * -> Result I64 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I64_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(i64_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toI128 : Int * -> I128
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I128,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(i128_type()),
|
||||
);
|
||||
|
||||
// toI128Checked : Int * -> Result I128 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_I128_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(i128_type(), out_of_bounds)),
|
||||
);
|
||||
|
||||
// toU8 : Int * -> U8
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U8,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(u8_type()),
|
||||
);
|
||||
|
||||
let out_of_bounds = SolvedType::TagUnion(
|
||||
vec![(TagName::Global("OutOfBounds".into()), vec![])],
|
||||
Box::new(SolvedType::Wildcard),
|
||||
);
|
||||
|
||||
// toU8Checked : Int * -> Result U8 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U8_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(u8_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toU16 : Int * -> U16
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U16,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(u16_type()),
|
||||
);
|
||||
|
||||
// toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U16_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(u16_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toU32 : Int * -> U32
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U32,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(u32_type()),
|
||||
);
|
||||
|
||||
// toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U32_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(u32_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toU64 : Int * -> U64
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U64,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(u64_type()),
|
||||
);
|
||||
|
||||
// toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U64_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(u64_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toU128 : Int * -> U128
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U128,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(u128_type()),
|
||||
);
|
||||
|
||||
// toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_U128_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(u128_type(), out_of_bounds)),
|
||||
);
|
||||
|
||||
// toStr : Num a -> Str
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_STR,
|
||||
|
@ -1078,14 +1228,14 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
Box::new(list_type(flex(TVAR2))),
|
||||
);
|
||||
|
||||
// mapWithIndex : List before, (Nat, before -> after) -> List after
|
||||
// mapWithIndex : List before, (before, Nat -> after) -> List after
|
||||
{
|
||||
let_tvars! { cvar, before, after};
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_MAP_WITH_INDEX,
|
||||
vec![
|
||||
list_type(flex(before)),
|
||||
closure(vec![nat_type(), flex(before)], cvar, Box::new(flex(after))),
|
||||
closure(vec![flex(before), nat_type()], cvar, Box::new(flex(after))),
|
||||
],
|
||||
Box::new(list_type(flex(after))),
|
||||
)
|
||||
|
@ -1264,10 +1414,10 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
Box::new(list_type(flex(TVAR1)))
|
||||
);
|
||||
|
||||
// repeat : Nat, elem -> List elem
|
||||
// repeat : elem, Nat -> List elem
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_REPEAT,
|
||||
vec![nat_type(), flex(TVAR1)],
|
||||
vec![flex(TVAR1), nat_type()],
|
||||
Box::new(list_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
|
|
|
@ -3,10 +3,10 @@ use crate::scope::Scope;
|
|||
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_parse::ast::{AliasHeader, AssignedField, Pattern, Tag, TypeAnnotation};
|
||||
use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{Alias, LambdaSet, Problem, RecordField, Type};
|
||||
use roc_types::types::{Alias, AliasKind, LambdaSet, Problem, RecordField, Type};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Annotation {
|
||||
|
@ -136,7 +136,12 @@ fn make_apply_symbol(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn find_alias_symbols(
|
||||
/// Retrieves all symbols in an annotations that reference a type definition, that is either an
|
||||
/// alias or an opaque type.
|
||||
///
|
||||
/// For example, in `[ A Age U8, B Str {} ]`, there are three type definition references - `Age`,
|
||||
/// `U8`, and `Str`.
|
||||
pub fn find_type_def_symbols(
|
||||
module_id: ModuleId,
|
||||
ident_ids: &mut IdentIds,
|
||||
initial_annotation: &roc_parse::ast::TypeAnnotation,
|
||||
|
@ -355,6 +360,7 @@ fn can_annotation_help(
|
|||
type_arguments: vars,
|
||||
lambda_set_variables,
|
||||
actual: Box::new(actual),
|
||||
kind: alias.kind,
|
||||
}
|
||||
}
|
||||
None => Type::Apply(symbol, args, region),
|
||||
|
@ -378,7 +384,7 @@ fn can_annotation_help(
|
|||
loc_inner,
|
||||
_spaces,
|
||||
alias_header
|
||||
@ AliasHeader {
|
||||
@ TypeHeader {
|
||||
name,
|
||||
vars: loc_vars,
|
||||
},
|
||||
|
@ -488,7 +494,13 @@ fn can_annotation_help(
|
|||
hidden_variables.remove(&loc_var.value.1);
|
||||
}
|
||||
|
||||
scope.add_alias(symbol, region, lowercase_vars, alias_actual);
|
||||
scope.add_alias(
|
||||
symbol,
|
||||
region,
|
||||
lowercase_vars,
|
||||
alias_actual,
|
||||
AliasKind::Structural, // aliases in "as" are never opaque
|
||||
);
|
||||
|
||||
let alias = scope.lookup_alias(symbol).unwrap();
|
||||
local_aliases.insert(symbol, alias.clone());
|
||||
|
@ -511,6 +523,7 @@ fn can_annotation_help(
|
|||
type_arguments: vars,
|
||||
lambda_set_variables: alias.lambda_set_variables.clone(),
|
||||
actual: Box::new(alias.typ.clone()),
|
||||
kind: alias.kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::def::Def;
|
||||
use crate::expr::{self, ClosureData, Expr::*, IntValue};
|
||||
use crate::expr::{Expr, Field, Recursive};
|
||||
use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
|
||||
use crate::num::{FloatBound, IntBound, IntWidth, NumericBound};
|
||||
use crate::pattern::Pattern;
|
||||
use roc_collections::all::SendMap;
|
||||
use roc_module::called_via::CalledVia;
|
||||
|
@ -242,6 +242,26 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
NUM_MAX_U64=> num_max_u64,
|
||||
NUM_MIN_I128=> num_min_i128,
|
||||
NUM_MAX_I128=> num_max_i128,
|
||||
NUM_TO_I8 => num_to_i8,
|
||||
NUM_TO_I8_CHECKED => num_to_i8_checked,
|
||||
NUM_TO_I16 => num_to_i16,
|
||||
NUM_TO_I16_CHECKED => num_to_i16_checked,
|
||||
NUM_TO_I32 => num_to_i32,
|
||||
NUM_TO_I32_CHECKED => num_to_i32_checked,
|
||||
NUM_TO_I64 => num_to_i64,
|
||||
NUM_TO_I64_CHECKED => num_to_i64_checked,
|
||||
NUM_TO_I128 => num_to_i128,
|
||||
NUM_TO_I128_CHECKED => num_to_i128_checked,
|
||||
NUM_TO_U8 => num_to_u8,
|
||||
NUM_TO_U8_CHECKED => num_to_u8_checked,
|
||||
NUM_TO_U16 => num_to_u16,
|
||||
NUM_TO_U16_CHECKED => num_to_u16_checked,
|
||||
NUM_TO_U32 => num_to_u32,
|
||||
NUM_TO_U32_CHECKED => num_to_u32_checked,
|
||||
NUM_TO_U64 => num_to_u64,
|
||||
NUM_TO_U64_CHECKED => num_to_u64_checked,
|
||||
NUM_TO_U128 => num_to_u128,
|
||||
NUM_TO_U128_CHECKED => num_to_u128_checked,
|
||||
NUM_TO_STR => num_to_str,
|
||||
RESULT_MAP => result_map,
|
||||
RESULT_MAP_ERR => result_map_err,
|
||||
|
@ -390,6 +410,174 @@ fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
// Num.toI8 : Int * -> I8
|
||||
fn num_to_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toI16 : Int * -> I16
|
||||
fn num_to_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toI32 : Int * -> I32
|
||||
fn num_to_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toI64 : Int * -> I64
|
||||
fn num_to_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toI128 : Int * -> I128
|
||||
fn num_to_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toU8 : Int * -> U8
|
||||
fn num_to_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toU16 : Int * -> U16
|
||||
fn num_to_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toU32 : Int * -> U32
|
||||
fn num_to_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toU64 : Int * -> U64
|
||||
fn num_to_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toU128 : Int * -> U128
|
||||
fn num_to_u128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to IntCast
|
||||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_var_1 = var_store.fresh();
|
||||
let num_var_2 = var_store.fresh();
|
||||
let ret_var = var_store.fresh();
|
||||
let record_var = var_store.fresh();
|
||||
|
||||
// let arg_2 = RunLowLevel NumToXXXChecked arg_1
|
||||
// if arg_2.b then
|
||||
// Err OutOfBounds
|
||||
// else
|
||||
// Ok arg_2.a
|
||||
//
|
||||
// "a" and "b" because the lowlevel return value looks like { converted_val: XXX, out_of_bounds: bool },
|
||||
// and codegen will sort by alignment, so "a" will be the first key, etc.
|
||||
|
||||
let cont = If {
|
||||
branch_var: ret_var,
|
||||
cond_var: bool_var,
|
||||
branches: vec![(
|
||||
// if-condition
|
||||
no_region(
|
||||
// arg_2.b
|
||||
Access {
|
||||
record_var,
|
||||
ext_var: var_store.fresh(),
|
||||
field: "b".into(),
|
||||
field_var: var_store.fresh(),
|
||||
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
|
||||
},
|
||||
),
|
||||
// out of bounds!
|
||||
no_region(tag(
|
||||
"Err",
|
||||
vec![tag("OutOfBounds", Vec::new(), var_store)],
|
||||
var_store,
|
||||
)),
|
||||
)],
|
||||
final_else: Box::new(
|
||||
// all is well
|
||||
no_region(
|
||||
// Ok arg_2.a
|
||||
tag(
|
||||
"Ok",
|
||||
vec![
|
||||
// arg_2.a
|
||||
Access {
|
||||
record_var,
|
||||
ext_var: var_store.fresh(),
|
||||
field: "a".into(),
|
||||
field_var: num_var_2,
|
||||
loc_expr: Box::new(no_region(Var(Symbol::ARG_2))),
|
||||
},
|
||||
],
|
||||
var_store,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
// arg_2 = RunLowLevel NumToXXXChecked arg_1
|
||||
let def = crate::def::Def {
|
||||
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_2)),
|
||||
loc_expr: no_region(RunLowLevel {
|
||||
op: lowlevel,
|
||||
args: vec![(num_var_1, Var(Symbol::ARG_1))],
|
||||
ret_var: record_var,
|
||||
}),
|
||||
expr_var: record_var,
|
||||
pattern_vars: SendMap::default(),
|
||||
annotation: None,
|
||||
};
|
||||
|
||||
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(num_var_1, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
ret_var,
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! num_to_checked {
|
||||
($($fn:ident)*) => {$(
|
||||
// Num.toXXXChecked : Int * -> Result XXX [ OutOfBounds ]*
|
||||
fn $fn(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Use the generic `NumToIntChecked`; we'll figure out exactly what layout(s) we need
|
||||
// during code generation after types are resolved.
|
||||
to_num_checked(symbol, var_store, LowLevel::NumToIntChecked)
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
num_to_checked! {
|
||||
num_to_i8_checked
|
||||
num_to_i16_checked
|
||||
num_to_i32_checked
|
||||
num_to_i64_checked
|
||||
num_to_i128_checked
|
||||
num_to_u8_checked
|
||||
num_to_u16_checked
|
||||
num_to_u32_checked
|
||||
num_to_u64_checked
|
||||
num_to_u128_checked
|
||||
}
|
||||
|
||||
// Num.toStr : Num a -> Str
|
||||
fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let num_var = var_store.fresh();
|
||||
|
@ -867,7 +1055,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
args: vec![
|
||||
(
|
||||
arg_var,
|
||||
int::<i128>(var_store.fresh(), var_store.fresh(), 1, num_no_bound()),
|
||||
int::<i128>(var_store.fresh(), var_store.fresh(), 1, int_no_bound()),
|
||||
),
|
||||
(
|
||||
arg_var,
|
||||
|
@ -965,7 +1153,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
(float_var, Var(Symbol::ARG_1)),
|
||||
(
|
||||
float_var,
|
||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
||||
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
|
@ -1014,7 +1202,7 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
(float_var, Var(Symbol::ARG_1)),
|
||||
(
|
||||
float_var,
|
||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
||||
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
|
@ -1253,162 +1441,82 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
|
||||
/// Num.minI8: I8
|
||||
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i8>(
|
||||
symbol,
|
||||
var_store,
|
||||
i8::MIN,
|
||||
NumericBound::Exact(IntWidth::I8),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::I8),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::U8),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::U8),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::I16),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::I16),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::U16),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::U16),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::I32),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::I32),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::U32),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::U32),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::I64),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::I64),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::U64),
|
||||
)
|
||||
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,
|
||||
NumericBound::Exact(IntWidth::U64),
|
||||
)
|
||||
int_min_or_max::<u64>(symbol, var_store, u64::MAX, IntBound::Exact(IntWidth::U64))
|
||||
}
|
||||
|
||||
/// Num.minI128: I128
|
||||
|
@ -1417,7 +1525,7 @@ fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
symbol,
|
||||
var_store,
|
||||
i128::MIN,
|
||||
NumericBound::Exact(IntWidth::I128),
|
||||
IntBound::Exact(IntWidth::I128),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1427,7 +1535,7 @@ fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
symbol,
|
||||
var_store,
|
||||
i128::MAX,
|
||||
NumericBound::Exact(IntWidth::I128),
|
||||
IntBound::Exact(IntWidth::I128),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1559,7 +1667,7 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
errorcode_var,
|
||||
Variable::UNSIGNED8,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::U8),
|
||||
IntBound::Exact(IntWidth::U8),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -2307,7 +2415,7 @@ fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
len_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::Nat),
|
||||
IntBound::Exact(IntWidth::Nat),
|
||||
);
|
||||
|
||||
let body = RunLowLevel {
|
||||
|
@ -2338,7 +2446,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
len_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::Nat),
|
||||
IntBound::Exact(IntWidth::Nat),
|
||||
);
|
||||
let bool_var = var_store.fresh();
|
||||
|
||||
|
@ -2453,7 +2561,7 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
int_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::Nat),
|
||||
IntBound::Exact(IntWidth::Nat),
|
||||
);
|
||||
|
||||
// \acc, elem -> acc |> List.append sep |> List.append elem
|
||||
|
@ -2538,7 +2646,7 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
index_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::Nat),
|
||||
IntBound::Exact(IntWidth::Nat),
|
||||
);
|
||||
|
||||
let clos = Closure(ClosureData {
|
||||
|
@ -2703,7 +2811,7 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
(list_var, Var(Symbol::ARG_1)),
|
||||
(
|
||||
index_var,
|
||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
||||
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: list_var,
|
||||
|
@ -2803,7 +2911,7 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
),
|
||||
(
|
||||
arg_var,
|
||||
int::<i128>(num_var, num_precision_var, 1, num_no_bound()),
|
||||
int::<i128>(num_var, num_precision_var, 1, int_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: len_var,
|
||||
|
@ -3004,7 +3112,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
args: vec![
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
||||
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
|
||||
),
|
||||
(
|
||||
len_var,
|
||||
|
@ -3042,7 +3150,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
num_var,
|
||||
num_precision_var,
|
||||
0,
|
||||
num_no_bound(),
|
||||
int_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -3145,7 +3253,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
args: vec![
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
||||
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
|
||||
),
|
||||
(
|
||||
len_var,
|
||||
|
@ -3183,7 +3291,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
num_var,
|
||||
num_precision_var,
|
||||
0,
|
||||
num_no_bound(),
|
||||
int_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -3380,7 +3488,7 @@ fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
lowlevel_2(symbol, LowLevel::ListMap, var_store)
|
||||
}
|
||||
|
||||
/// List.mapWithIndex : List before, (Nat, before -> after) -> List after
|
||||
/// List.mapWithIndex : List before, (before, Nat -> after) -> List after
|
||||
fn list_map_with_index(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::ListMapWithIndex, var_store)
|
||||
}
|
||||
|
@ -4076,7 +4184,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
(num_var, Var(Symbol::ARG_2)),
|
||||
(
|
||||
num_var,
|
||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
||||
float(unbound_zero_var, precision_var, 0.0, float_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
|
@ -4146,7 +4254,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
unbound_zero_var,
|
||||
unbound_zero_precision_var,
|
||||
0,
|
||||
num_no_bound(),
|
||||
int_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -4217,7 +4325,7 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
unbound_zero_var,
|
||||
unbound_zero_precision_var,
|
||||
0,
|
||||
num_no_bound(),
|
||||
int_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -4290,7 +4398,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
args: vec![
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
|
||||
int::<i128>(zero_var, zero_precision_var, 0, int_no_bound()),
|
||||
),
|
||||
(
|
||||
len_var,
|
||||
|
@ -4317,7 +4425,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
(list_var, Var(Symbol::ARG_1)),
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
|
||||
int::<i128>(zero_var, zero_precision_var, 0, int_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: list_elem_var,
|
||||
|
@ -4377,7 +4485,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
args: vec![
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
||||
int::<i128>(num_var, num_precision_var, 0, int_no_bound()),
|
||||
),
|
||||
(
|
||||
len_var,
|
||||
|
@ -4423,7 +4531,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
num_var,
|
||||
num_precision_var,
|
||||
1,
|
||||
num_no_bound(),
|
||||
int_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -5144,12 +5252,7 @@ fn defn_help(
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn int_min_or_max<I128>(
|
||||
symbol: Symbol,
|
||||
var_store: &mut VarStore,
|
||||
i: I128,
|
||||
bound: NumericBound<IntWidth>,
|
||||
) -> Def
|
||||
fn int_min_or_max<I128>(symbol: Symbol, var_store: &mut VarStore, i: I128, bound: IntBound) -> Def
|
||||
where
|
||||
I128: Into<i128>,
|
||||
{
|
||||
|
@ -5178,17 +5281,20 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn num_no_bound<W: Copy>() -> NumericBound<W> {
|
||||
fn num_no_bound() -> NumericBound {
|
||||
NumericBound::None
|
||||
}
|
||||
|
||||
fn int_no_bound() -> IntBound {
|
||||
IntBound::None
|
||||
}
|
||||
|
||||
fn float_no_bound() -> FloatBound {
|
||||
FloatBound::None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn int<I128>(
|
||||
num_var: Variable,
|
||||
precision_var: Variable,
|
||||
i: I128,
|
||||
bound: NumericBound<IntWidth>,
|
||||
) -> Expr
|
||||
fn int<I128>(num_var: Variable, precision_var: Variable, i: I128, bound: IntBound) -> Expr
|
||||
where
|
||||
I128: Into<i128>,
|
||||
{
|
||||
|
@ -5203,12 +5309,7 @@ where
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn float(
|
||||
num_var: Variable,
|
||||
precision_var: Variable,
|
||||
f: f64,
|
||||
bound: NumericBound<FloatWidth>,
|
||||
) -> Expr {
|
||||
fn float(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -> Expr {
|
||||
Float(
|
||||
num_var,
|
||||
precision_var,
|
||||
|
@ -5219,7 +5320,7 @@ fn float(
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound<NumWidth>) -> Expr {
|
||||
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound) -> Expr {
|
||||
let i = i.into();
|
||||
Num(
|
||||
num_var,
|
||||
|
|
|
@ -9,20 +9,22 @@ use crate::expr::{
|
|||
};
|
||||
use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
|
||||
use crate::procedure::References;
|
||||
use crate::scope::create_alias;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast;
|
||||
use roc_parse::ast::AliasHeader;
|
||||
use roc_parse::ast::TypeHeader;
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::AliasKind;
|
||||
use roc_types::types::{Alias, Type};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use ven_graph::{strongly_connected_components, topological_sort, topological_sort_into_groups};
|
||||
use ven_graph::{strongly_connected_components, topological_sort};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Def {
|
||||
|
@ -72,17 +74,18 @@ enum PendingDef<'a> {
|
|||
&'a Loc<ast::Expr<'a>>,
|
||||
),
|
||||
|
||||
/// A type alias, e.g. `Ints : List Int`
|
||||
/// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively.
|
||||
Alias {
|
||||
name: Loc<Symbol>,
|
||||
vars: Vec<Loc<Lowercase>>,
|
||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||
kind: AliasKind,
|
||||
},
|
||||
|
||||
/// An invalid alias, that is ignored in the rest of the pipeline
|
||||
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
|
||||
/// with an incorrect pattern
|
||||
InvalidAlias,
|
||||
InvalidAlias { kind: AliasKind },
|
||||
}
|
||||
|
||||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
|
@ -107,18 +110,20 @@ impl Declaration {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a topologically sorted sequence of alias names
|
||||
fn sort_aliases_before_introduction(mut alias_symbols: MutMap<Symbol, Vec<Symbol>>) -> Vec<Symbol> {
|
||||
let defined_symbols: Vec<Symbol> = alias_symbols.keys().copied().collect();
|
||||
/// Returns a topologically sorted sequence of alias/opaque names
|
||||
fn sort_type_defs_before_introduction(
|
||||
mut referenced_symbols: MutMap<Symbol, Vec<Symbol>>,
|
||||
) -> Vec<Symbol> {
|
||||
let defined_symbols: Vec<Symbol> = referenced_symbols.keys().copied().collect();
|
||||
|
||||
// find the strongly connected components and their relations
|
||||
let sccs = {
|
||||
// only retain symbols from the current alias_defs
|
||||
for v in alias_symbols.iter_mut() {
|
||||
// only retain symbols from the current set of defined symbols; the rest come from other modules
|
||||
for v in referenced_symbols.iter_mut() {
|
||||
v.1.retain(|x| defined_symbols.iter().any(|s| s == x));
|
||||
}
|
||||
|
||||
let all_successors_with_self = |symbol: &Symbol| alias_symbols[symbol].iter().copied();
|
||||
let all_successors_with_self = |symbol: &Symbol| referenced_symbols[symbol].iter().copied();
|
||||
|
||||
strongly_connected_components(&defined_symbols, all_successors_with_self)
|
||||
};
|
||||
|
@ -138,7 +143,7 @@ fn sort_aliases_before_introduction(mut alias_symbols: MutMap<Symbol, Vec<Symbol
|
|||
|
||||
for (index, group) in sccs.iter().enumerate() {
|
||||
for s in group {
|
||||
let reachable = &alias_symbols[s];
|
||||
let reachable = &referenced_symbols[s];
|
||||
for r in reachable {
|
||||
let new_index = symbol_to_group_index[r];
|
||||
|
||||
|
@ -223,7 +228,10 @@ pub fn canonicalize_defs<'a>(
|
|||
.map(|t| t.0),
|
||||
)
|
||||
}
|
||||
PendingDef::Alias { .. } | PendingDef::InvalidAlias => {}
|
||||
|
||||
// Type definitions aren't value definitions, so we don't need to do
|
||||
// anything for them here.
|
||||
PendingDef::Alias { .. } | PendingDef::InvalidAlias { .. } => {}
|
||||
}
|
||||
}
|
||||
// Record the ast::Expr for later. We'll do another pass through these
|
||||
|
@ -244,34 +252,42 @@ pub fn canonicalize_defs<'a>(
|
|||
let mut value_defs = Vec::new();
|
||||
|
||||
let mut alias_defs = MutMap::default();
|
||||
let mut alias_symbols = MutMap::default();
|
||||
let mut referenced_type_symbols = MutMap::default();
|
||||
|
||||
for pending_def in pending.into_iter() {
|
||||
match pending_def {
|
||||
PendingDef::Alias { name, vars, ann } => {
|
||||
let symbols =
|
||||
crate::annotation::find_alias_symbols(env.home, &mut env.ident_ids, &ann.value);
|
||||
PendingDef::Alias {
|
||||
name,
|
||||
vars,
|
||||
ann,
|
||||
kind,
|
||||
} => {
|
||||
let referenced_symbols = crate::annotation::find_type_def_symbols(
|
||||
env.home,
|
||||
&mut env.ident_ids,
|
||||
&ann.value,
|
||||
);
|
||||
|
||||
alias_symbols.insert(name.value, symbols);
|
||||
alias_defs.insert(name.value, (name, vars, ann));
|
||||
referenced_type_symbols.insert(name.value, referenced_symbols);
|
||||
|
||||
alias_defs.insert(name.value, (name, vars, ann, kind));
|
||||
}
|
||||
other => value_defs.push(other),
|
||||
}
|
||||
}
|
||||
|
||||
let sorted = sort_aliases_before_introduction(alias_symbols);
|
||||
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
|
||||
|
||||
for alias_name in sorted {
|
||||
let (name, vars, ann) = alias_defs.remove(&alias_name).unwrap();
|
||||
for type_name in sorted {
|
||||
let (name, vars, ann, kind) = alias_defs.remove(&type_name).unwrap();
|
||||
|
||||
let symbol = name.value;
|
||||
let mut can_ann =
|
||||
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
|
||||
let can_ann = canonicalize_annotation(env, &mut 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);
|
||||
output.references.referenced_type_defs.insert(symbol);
|
||||
}
|
||||
|
||||
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
|
||||
|
@ -282,7 +298,7 @@ pub fn canonicalize_defs<'a>(
|
|||
.introduced_variables
|
||||
.var_by_name(&loc_lowercase.value)
|
||||
{
|
||||
// This is a valid lowercase rigid var for the alias.
|
||||
// This is a valid lowercase rigid var for the type def.
|
||||
can_vars.push(Loc {
|
||||
value: (loc_lowercase.value.clone(), *var),
|
||||
region: loc_lowercase.region,
|
||||
|
@ -291,7 +307,7 @@ pub fn canonicalize_defs<'a>(
|
|||
is_phantom = true;
|
||||
|
||||
env.problems.push(Problem::PhantomTypeArgument {
|
||||
alias: symbol,
|
||||
typ: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
variable_name: loc_lowercase.value.clone(),
|
||||
});
|
||||
|
@ -303,43 +319,28 @@ pub fn canonicalize_defs<'a>(
|
|||
continue;
|
||||
}
|
||||
|
||||
let mut is_nested_datatype = false;
|
||||
if can_ann.typ.contains_symbol(symbol) {
|
||||
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,
|
||||
Loc::at(alias_region, (symbol, &alias_args)),
|
||||
let alias = create_alias(
|
||||
symbol,
|
||||
name.region,
|
||||
vec![],
|
||||
&mut can_ann.typ,
|
||||
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,
|
||||
can_vars.clone(),
|
||||
can_ann.typ.clone(),
|
||||
kind,
|
||||
);
|
||||
|
||||
is_nested_datatype = made_recursive.is_err();
|
||||
}
|
||||
|
||||
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");
|
||||
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);
|
||||
let mut aliases = correct_mutual_recursive_type_alias(env, &aliases, var_store);
|
||||
for (symbol, alias) in aliases.iter() {
|
||||
scope.add_alias(
|
||||
*symbol,
|
||||
alias.region,
|
||||
alias.type_variables.clone(),
|
||||
alias.typ.clone(),
|
||||
alias.kind,
|
||||
);
|
||||
}
|
||||
|
||||
// Now that we have the scope completely assembled, and shadowing resolved,
|
||||
// we're ready to canonicalize any body exprs.
|
||||
|
@ -445,7 +446,7 @@ pub fn sort_can_defs(
|
|||
let mut defined_symbols: Vec<Symbol> = Vec::new();
|
||||
let mut defined_symbols_set: ImSet<Symbol> = ImSet::default();
|
||||
|
||||
for symbol in can_defs_by_symbol.keys().into_iter() {
|
||||
for symbol in can_defs_by_symbol.keys() {
|
||||
defined_symbols.push(*symbol);
|
||||
defined_symbols_set.insert(*symbol);
|
||||
}
|
||||
|
@ -834,6 +835,15 @@ fn pattern_to_vars_by_symbol(
|
|||
}
|
||||
}
|
||||
|
||||
UnwrappedOpaque {
|
||||
arguments, opaque, ..
|
||||
} => {
|
||||
for (var, nested) in arguments {
|
||||
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
|
||||
}
|
||||
vars_by_symbol.insert(*opaque, expr_var);
|
||||
}
|
||||
|
||||
RecordDestructure { destructs, .. } => {
|
||||
for destruct in destructs {
|
||||
vars_by_symbol.insert(destruct.value.symbol, destruct.value.var);
|
||||
|
@ -846,7 +856,8 @@ fn pattern_to_vars_by_symbol(
|
|||
| StrLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_) => {}
|
||||
| UnsupportedPattern(_)
|
||||
| OpaqueNotInScope(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -880,7 +891,7 @@ fn canonicalize_pending_def<'a>(
|
|||
|
||||
for symbol in ann.references {
|
||||
output.references.lookups.insert(symbol);
|
||||
output.references.referenced_aliases.insert(symbol);
|
||||
output.references.referenced_type_defs.insert(symbol);
|
||||
}
|
||||
|
||||
aliases.extend(ann.aliases.clone());
|
||||
|
@ -980,8 +991,9 @@ fn canonicalize_pending_def<'a>(
|
|||
}
|
||||
|
||||
Alias { .. } => unreachable!("Aliases are handled in a separate pass"),
|
||||
InvalidAlias => {
|
||||
// invalid aliases (shadowed, incorrect patterns) get ignored
|
||||
|
||||
InvalidAlias { .. } => {
|
||||
// invalid aliases and opaques (shadowed, incorrect patterns) get ignored
|
||||
}
|
||||
TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
|
||||
let ann =
|
||||
|
@ -990,7 +1002,7 @@ fn canonicalize_pending_def<'a>(
|
|||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in ann.references {
|
||||
output.references.lookups.insert(symbol);
|
||||
output.references.referenced_aliases.insert(symbol);
|
||||
output.references.referenced_type_defs.insert(symbol);
|
||||
}
|
||||
|
||||
let typ = ann.typ;
|
||||
|
@ -1464,10 +1476,19 @@ fn to_pending_def<'a>(
|
|||
}
|
||||
|
||||
Alias {
|
||||
header: AliasHeader { name, vars },
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
..
|
||||
}
|
||||
| Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ: ann,
|
||||
} => {
|
||||
let kind = if matches!(def, Alias { .. }) {
|
||||
AliasKind::Structural
|
||||
} else {
|
||||
AliasKind::Opaque
|
||||
};
|
||||
|
||||
let region = Region::span_across(&name.region, &ann.region);
|
||||
|
||||
match scope.introduce(
|
||||
|
@ -1492,27 +1513,33 @@ fn to_pending_def<'a>(
|
|||
}
|
||||
_ => {
|
||||
// any other pattern in this position is a syntax error.
|
||||
env.problems.push(Problem::InvalidAliasRigid {
|
||||
let problem = Problem::InvalidAliasRigid {
|
||||
alias_name: symbol,
|
||||
region: loc_var.region,
|
||||
});
|
||||
};
|
||||
env.problems.push(problem);
|
||||
|
||||
return Some((Output::default(), PendingDef::InvalidAlias));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some((
|
||||
return Some((
|
||||
Output::default(),
|
||||
PendingDef::Alias {
|
||||
name: Loc {
|
||||
PendingDef::InvalidAlias { kind },
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let name = Loc {
|
||||
region: name.region,
|
||||
value: symbol,
|
||||
},
|
||||
};
|
||||
|
||||
let pending_def = PendingDef::Alias {
|
||||
name,
|
||||
vars: can_rigids,
|
||||
ann,
|
||||
},
|
||||
))
|
||||
kind,
|
||||
};
|
||||
|
||||
Some((Output::default(), pending_def))
|
||||
}
|
||||
|
||||
Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
|
||||
|
@ -1521,7 +1548,7 @@ fn to_pending_def<'a>(
|
|||
shadow: loc_shadowed_symbol,
|
||||
});
|
||||
|
||||
Some((Output::default(), PendingDef::InvalidAlias))
|
||||
Some((Output::default(), PendingDef::InvalidAlias { kind }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1564,17 +1591,17 @@ fn pending_typed_body<'a>(
|
|||
/// Make aliases recursive
|
||||
fn correct_mutual_recursive_type_alias<'a>(
|
||||
env: &mut Env<'a>,
|
||||
aliases: &mut SendMap<Symbol, Alias>,
|
||||
original_aliases: &SendMap<Symbol, Alias>,
|
||||
var_store: &mut VarStore,
|
||||
) {
|
||||
) -> SendMap<Symbol, Alias> {
|
||||
let mut symbols_introduced = ImSet::default();
|
||||
|
||||
for (key, _) in aliases.iter() {
|
||||
for (key, _) in original_aliases.iter() {
|
||||
symbols_introduced.insert(*key);
|
||||
}
|
||||
|
||||
let all_successors_with_self = |symbol: &Symbol| -> ImSet<Symbol> {
|
||||
match aliases.get(symbol) {
|
||||
match original_aliases.get(symbol) {
|
||||
Some(alias) => {
|
||||
let mut loc_succ = alias.typ.symbols();
|
||||
// remove anything that is not defined in the current block
|
||||
|
@ -1586,53 +1613,42 @@ fn correct_mutual_recursive_type_alias<'a>(
|
|||
}
|
||||
};
|
||||
|
||||
let all_successors_without_self = |symbol: &Symbol| -> ImSet<Symbol> {
|
||||
match aliases.get(symbol) {
|
||||
Some(alias) => {
|
||||
let mut loc_succ = alias.typ.symbols();
|
||||
// remove anything that is not defined in the current block
|
||||
loc_succ.retain(|key| symbols_introduced.contains(key));
|
||||
loc_succ.remove(symbol);
|
||||
|
||||
loc_succ
|
||||
}
|
||||
None => ImSet::default(),
|
||||
}
|
||||
};
|
||||
|
||||
let originals = aliases.clone();
|
||||
|
||||
// TODO investigate should this be in a loop?
|
||||
let defined_symbols: Vec<Symbol> = aliases.keys().copied().collect();
|
||||
let defined_symbols: Vec<Symbol> = original_aliases.keys().copied().collect();
|
||||
|
||||
// split into self-recursive and mutually recursive
|
||||
match topological_sort_into_groups(&defined_symbols, all_successors_with_self) {
|
||||
Ok(_) => {
|
||||
// no mutual recursion in any alias
|
||||
}
|
||||
Err((_, mutually_recursive_symbols)) => {
|
||||
for cycle in strongly_connected_components(
|
||||
&mutually_recursive_symbols,
|
||||
all_successors_without_self,
|
||||
) {
|
||||
// make sure we report only one error for the cycle, not an error for every
|
||||
let cycles = strongly_connected_components(&defined_symbols, all_successors_with_self);
|
||||
let mut solved_aliases = SendMap::default();
|
||||
|
||||
for cycle in cycles {
|
||||
debug_assert!(!cycle.is_empty());
|
||||
|
||||
let mut pending_aliases: SendMap<_, _> = cycle
|
||||
.iter()
|
||||
.map(|&sym| (sym, original_aliases.get(&sym).unwrap().clone()))
|
||||
.collect();
|
||||
|
||||
// Make sure we report only one error for the cycle, not an error for every
|
||||
// alias in the cycle.
|
||||
let mut can_still_report_error = true;
|
||||
|
||||
// TODO use itertools to be more efficient here
|
||||
for rec in &cycle {
|
||||
let mut to_instantiate = ImMap::default();
|
||||
let mut others = Vec::with_capacity(cycle.len() - 1);
|
||||
for other in &cycle {
|
||||
for &rec in cycle.iter() {
|
||||
// First, we need to instantiate the alias with any symbols in the currrent module it
|
||||
// depends on.
|
||||
// We only need to worry about symbols in this SCC or any prior one, since the SCCs
|
||||
// were sorted topologically, and we've already instantiated aliases coming from other
|
||||
// modules.
|
||||
let mut to_instantiate: ImMap<_, _> = solved_aliases.clone().into_iter().collect();
|
||||
let mut others_in_scc = Vec::with_capacity(cycle.len() - 1);
|
||||
for &other in cycle.iter() {
|
||||
if rec != other {
|
||||
others.push(*other);
|
||||
if let Some(alias) = originals.get(other) {
|
||||
to_instantiate.insert(*other, alias.clone());
|
||||
others_in_scc.push(other);
|
||||
if let Some(alias) = original_aliases.get(&other) {
|
||||
to_instantiate.insert(other, alias.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(alias) = aliases.get_mut(rec) {
|
||||
let alias = pending_aliases.get_mut(&rec).unwrap();
|
||||
alias.typ.instantiate_aliases(
|
||||
alias.region,
|
||||
&to_instantiate,
|
||||
|
@ -1640,26 +1656,77 @@ fn correct_mutual_recursive_type_alias<'a>(
|
|||
&mut ImSet::default(),
|
||||
);
|
||||
|
||||
let alias_args = &alias
|
||||
.type_variables
|
||||
.iter()
|
||||
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
|
||||
.collect::<Vec<_>>();
|
||||
// Now mark the alias recursive, if it needs to be.
|
||||
let is_self_recursive = alias.typ.contains_symbol(rec);
|
||||
let is_mutually_recursive = cycle.len() > 1;
|
||||
|
||||
let _made_recursive = make_tag_union_recursive(
|
||||
if is_self_recursive || is_mutually_recursive {
|
||||
let _made_recursive = make_tag_union_of_alias_recursive(
|
||||
env,
|
||||
Loc::at(alias.header_region(), (*rec, alias_args)),
|
||||
alias.region,
|
||||
others,
|
||||
&mut alias.typ,
|
||||
rec,
|
||||
alias,
|
||||
vec![],
|
||||
var_store,
|
||||
&mut can_still_report_error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The cycle we just instantiated and marked recursive may still be an illegal cycle, if
|
||||
// all the types in the cycle are narrow newtypes. We can't figure this out until now,
|
||||
// because we need all the types to be deeply instantiated.
|
||||
let all_are_narrow = cycle.iter().all(|sym| {
|
||||
let typ = &pending_aliases.get(sym).unwrap().typ;
|
||||
matches!(typ, Type::RecursiveTagUnion(..)) && typ.is_narrow()
|
||||
});
|
||||
|
||||
if all_are_narrow {
|
||||
// This cycle is illegal!
|
||||
let mut rest = cycle;
|
||||
let alias_name = rest.pop().unwrap();
|
||||
|
||||
let alias = pending_aliases.get_mut(&alias_name).unwrap();
|
||||
|
||||
mark_cyclic_alias(
|
||||
env,
|
||||
&mut alias.typ,
|
||||
alias_name,
|
||||
alias.region,
|
||||
rest,
|
||||
can_still_report_error,
|
||||
)
|
||||
}
|
||||
|
||||
// Now, promote all resolved aliases in this cycle as solved.
|
||||
solved_aliases.extend(pending_aliases);
|
||||
}
|
||||
|
||||
solved_aliases
|
||||
}
|
||||
|
||||
fn make_tag_union_of_alias_recursive<'a>(
|
||||
env: &mut Env<'a>,
|
||||
alias_name: Symbol,
|
||||
alias: &mut Alias,
|
||||
others: Vec<Symbol>,
|
||||
var_store: &mut VarStore,
|
||||
can_report_error: &mut bool,
|
||||
) -> Result<(), ()> {
|
||||
let alias_args = alias
|
||||
.type_variables
|
||||
.iter()
|
||||
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
make_tag_union_recursive_help(
|
||||
env,
|
||||
Loc::at(alias.header_region(), (alias_name, &alias_args)),
|
||||
alias.region,
|
||||
others,
|
||||
&mut alias.typ,
|
||||
var_store,
|
||||
can_report_error,
|
||||
)
|
||||
}
|
||||
|
||||
/// Attempt to make a tag union recursive at the position of `recursive_alias`; for example,
|
||||
|
@ -1683,7 +1750,7 @@ fn correct_mutual_recursive_type_alias<'a>(
|
|||
/// ```
|
||||
///
|
||||
/// When `Err` is returned, a problem will be added to `env`.
|
||||
fn make_tag_union_recursive<'a>(
|
||||
fn make_tag_union_recursive_help<'a>(
|
||||
env: &mut Env<'a>,
|
||||
recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>,
|
||||
region: Region,
|
||||
|
@ -1724,7 +1791,7 @@ fn make_tag_union_recursive<'a>(
|
|||
actual,
|
||||
type_arguments,
|
||||
..
|
||||
} => make_tag_union_recursive(
|
||||
} => make_tag_union_recursive_help(
|
||||
env,
|
||||
Loc::at_zero((symbol, type_arguments)),
|
||||
region,
|
||||
|
@ -1734,17 +1801,27 @@ fn make_tag_union_recursive<'a>(
|
|||
can_report_error,
|
||||
),
|
||||
_ => {
|
||||
let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone());
|
||||
*typ = Type::Erroneous(problem);
|
||||
|
||||
// ensure cyclic error is only reported for one element of the cycle
|
||||
if *can_report_error {
|
||||
mark_cyclic_alias(env, typ, symbol, region, others, *can_report_error);
|
||||
*can_report_error = false;
|
||||
|
||||
let problem = Problem::CyclicAlias(symbol, region, others);
|
||||
env.problems.push(problem);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_cyclic_alias<'a>(
|
||||
env: &mut Env<'a>,
|
||||
typ: &mut Type,
|
||||
symbol: Symbol,
|
||||
region: Region,
|
||||
others: Vec<Symbol>,
|
||||
report: bool,
|
||||
) {
|
||||
let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone());
|
||||
*typ = Type::Erroneous(problem);
|
||||
|
||||
if report {
|
||||
let problem = Problem::CyclicAlias(symbol, region, others);
|
||||
env.problems.push(problem);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,56 +10,42 @@ use roc_module::ident::TagName;
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::Type;
|
||||
use roc_types::types::{AliasKind, Type};
|
||||
|
||||
/// Functions that are always implemented for Effect
|
||||
type Builder = for<'r, 's, 't0, 't1> fn(
|
||||
&'r mut Env<'s>,
|
||||
&'t0 mut Scope,
|
||||
Symbol,
|
||||
TagName,
|
||||
&'t1 mut VarStore,
|
||||
) -> (Symbol, Def);
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub(crate) struct HostedGeneratedFunctions {
|
||||
pub(crate) after: bool,
|
||||
pub(crate) map: bool,
|
||||
pub(crate) always: bool,
|
||||
pub(crate) loop_: bool,
|
||||
pub(crate) forever: bool,
|
||||
}
|
||||
|
||||
pub const BUILTIN_EFFECT_FUNCTIONS: &[(&str, Builder)] = &[
|
||||
// Effect.after : Effect a, (a -> Effect b) -> Effect b
|
||||
("after", build_effect_after),
|
||||
// Effect.map : Effect a, (a -> b) -> Effect b
|
||||
("map", build_effect_map),
|
||||
// Effect.always : a -> Effect a
|
||||
("always", build_effect_always),
|
||||
// Effect.forever : Effect a -> Effect b
|
||||
("forever", build_effect_forever),
|
||||
// Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b
|
||||
("loop", build_effect_loop),
|
||||
];
|
||||
|
||||
const RECURSIVE_BUILTIN_EFFECT_FUNCTIONS: &[&str] = &["forever", "loop"];
|
||||
|
||||
// the Effects alias & associated functions
|
||||
//
|
||||
// A platform can define an Effect type in its header. It can have an arbitrary name
|
||||
// (e.g. Task, IO), but we'll call it an Effect in general.
|
||||
//
|
||||
// From that name, we generate an effect module, an effect alias, and some functions.
|
||||
//
|
||||
// The effect alias is implemented as
|
||||
//
|
||||
// Effect a : [ @Effect ({} -> a) ]
|
||||
//
|
||||
// For this alias we implement the functions defined in BUILTIN_EFFECT_FUNCTIONS with the
|
||||
// standard implementation.
|
||||
|
||||
pub fn build_effect_builtins(
|
||||
/// the Effects alias & associated functions
|
||||
///
|
||||
/// A platform can define an Effect type in its header. It can have an arbitrary name
|
||||
/// (e.g. Task, IO), but we'll call it an Effect in general.
|
||||
///
|
||||
/// From that name, we generate an effect module, an effect alias, and some functions.
|
||||
///
|
||||
/// The effect alias is implemented as
|
||||
///
|
||||
/// Effect a : [ @Effect ({} -> a) ]
|
||||
///
|
||||
/// For this alias we implement the functions specified in HostedGeneratedFunctions with the
|
||||
/// standard implementation.
|
||||
pub(crate) fn build_effect_builtins(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
effect_symbol: Symbol,
|
||||
var_store: &mut VarStore,
|
||||
exposed_symbols: &mut MutSet<Symbol>,
|
||||
declarations: &mut Vec<Declaration>,
|
||||
generated_functions: HostedGeneratedFunctions,
|
||||
) {
|
||||
for (name, f) in BUILTIN_EFFECT_FUNCTIONS.iter() {
|
||||
let (symbol, def) = f(
|
||||
macro_rules! helper {
|
||||
($f:expr) => {{
|
||||
let (symbol, def) = $f(
|
||||
env,
|
||||
scope,
|
||||
effect_symbol,
|
||||
|
@ -67,14 +53,40 @@ pub fn build_effect_builtins(
|
|||
var_store,
|
||||
);
|
||||
|
||||
// make the outside world know this symbol exists
|
||||
exposed_symbols.insert(symbol);
|
||||
|
||||
let is_recursive = RECURSIVE_BUILTIN_EFFECT_FUNCTIONS.iter().any(|n| n == name);
|
||||
if is_recursive {
|
||||
declarations.push(Declaration::DeclareRec(vec![def]));
|
||||
} else {
|
||||
def
|
||||
}};
|
||||
}
|
||||
|
||||
if generated_functions.after {
|
||||
let def = helper!(build_effect_after);
|
||||
declarations.push(Declaration::Declare(def));
|
||||
}
|
||||
|
||||
// Effect.map : Effect a, (a -> b) -> Effect b
|
||||
if generated_functions.map {
|
||||
let def = helper!(build_effect_map);
|
||||
declarations.push(Declaration::Declare(def));
|
||||
}
|
||||
|
||||
// Effect.always : a -> Effect a
|
||||
if generated_functions.always {
|
||||
let def = helper!(build_effect_always);
|
||||
declarations.push(Declaration::Declare(def));
|
||||
}
|
||||
|
||||
// Effect.forever : Effect a -> Effect b
|
||||
if generated_functions.forever {
|
||||
let def = helper!(build_effect_forever);
|
||||
declarations.push(Declaration::DeclareRec(vec![def]));
|
||||
}
|
||||
|
||||
// Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b
|
||||
if generated_functions.loop_ {
|
||||
let def = helper!(build_effect_loop);
|
||||
declarations.push(Declaration::DeclareRec(vec![def]));
|
||||
}
|
||||
|
||||
// Useful when working on functions in this module. By default symbols that we named do now
|
||||
|
@ -1128,6 +1140,7 @@ fn build_effect_loop(
|
|||
closure_var,
|
||||
))],
|
||||
actual: Box::new(actual),
|
||||
kind: AliasKind::Structural,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1567,6 +1580,7 @@ fn build_effect_alias(
|
|||
type_arguments: vec![(a_name.into(), Type::Variable(a_var))],
|
||||
lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(closure_var))],
|
||||
actual: Box::new(actual),
|
||||
kind: AliasKind::Structural,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::def::{can_defs_with_return, Def};
|
|||
use crate::env::Env;
|
||||
use crate::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
|
||||
int_expr_from_result, num_expr_from_result, FloatWidth, IntWidth, NumWidth, NumericBound,
|
||||
int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound,
|
||||
};
|
||||
use crate::pattern::{canonicalize_pattern, Pattern};
|
||||
use crate::procedure::References;
|
||||
|
@ -67,17 +67,11 @@ pub enum Expr {
|
|||
|
||||
// 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
|
||||
Num(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
|
||||
Num(Variable, Box<str>, IntValue, NumericBound),
|
||||
|
||||
// Int and Float store a variable to generate better error messages
|
||||
Int(
|
||||
Variable,
|
||||
Variable,
|
||||
Box<str>,
|
||||
IntValue,
|
||||
NumericBound<IntWidth>,
|
||||
),
|
||||
Float(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
|
||||
Int(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||
Float(Variable, Variable, Box<str>, f64, FloatBound),
|
||||
Str(Box<str>),
|
||||
List {
|
||||
elem_var: Variable,
|
||||
|
@ -178,6 +172,11 @@ pub enum Expr {
|
|||
arguments: Vec<(Variable, Loc<Expr>)>,
|
||||
},
|
||||
|
||||
OpaqueRef {
|
||||
name: Symbol,
|
||||
arguments: Vec<(Variable, Loc<Expr>)>,
|
||||
},
|
||||
|
||||
// Test
|
||||
Expect(Box<Loc<Expr>>, Box<Loc<Expr>>),
|
||||
|
||||
|
@ -425,6 +424,10 @@ pub fn canonicalize_expr<'a>(
|
|||
name,
|
||||
arguments: args,
|
||||
},
|
||||
OpaqueRef { name, .. } => OpaqueRef {
|
||||
name,
|
||||
arguments: args,
|
||||
},
|
||||
ZeroArgumentTag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
|
@ -551,7 +554,7 @@ pub fn canonicalize_expr<'a>(
|
|||
output.union(new_output);
|
||||
|
||||
// filter out aliases
|
||||
captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s));
|
||||
captured_symbols.retain(|s| !output.references.referenced_type_defs.contains(s));
|
||||
|
||||
// filter out functions that don't close over anything
|
||||
captured_symbols.retain(|s| !output.non_closures.contains(s));
|
||||
|
@ -702,6 +705,19 @@ pub fn canonicalize_expr<'a>(
|
|||
Output::default(),
|
||||
)
|
||||
}
|
||||
ast::Expr::OpaqueRef(opaque_ref) => match scope.lookup_opaque_ref(opaque_ref, region) {
|
||||
Ok(name) => (
|
||||
OpaqueRef {
|
||||
name,
|
||||
arguments: vec![],
|
||||
},
|
||||
Output::default(),
|
||||
),
|
||||
Err(runtime_error) => {
|
||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||
(RuntimeError(runtime_error), Output::default())
|
||||
}
|
||||
},
|
||||
ast::Expr::Expect(condition, continuation) => {
|
||||
let mut output = Output::default();
|
||||
|
||||
|
@ -1479,6 +1495,20 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
);
|
||||
}
|
||||
|
||||
OpaqueRef { name, arguments } => {
|
||||
let arguments = arguments
|
||||
.into_iter()
|
||||
.map(|(var, loc_expr)| {
|
||||
(
|
||||
var,
|
||||
loc_expr.map_owned(|expr| inline_calls(var_store, scope, expr)),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
OpaqueRef { name, arguments }
|
||||
}
|
||||
|
||||
ZeroArgumentTag {
|
||||
closure_name,
|
||||
variant_var,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
|
||||
use crate::effect_module::HostedGeneratedFunctions;
|
||||
use crate::env::Env;
|
||||
use crate::expr::{ClosureData, Expr, Output};
|
||||
use crate::operator::desugar_def;
|
||||
|
@ -15,7 +16,7 @@ use roc_parse::pattern::PatternType;
|
|||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{Alias, Type};
|
||||
use roc_types::types::{Alias, AliasKind, Type};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Module {
|
||||
|
@ -40,9 +41,33 @@ pub struct ModuleOutput {
|
|||
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
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn canonicalize_module_defs<'a, F>(
|
||||
pub fn canonicalize_module_defs<'a>(
|
||||
arena: &Bump,
|
||||
loc_defs: &'a [Loc<ast::Def<'a>>],
|
||||
header_for: &roc_parse::header::HeaderFor,
|
||||
|
@ -54,23 +79,39 @@ pub fn canonicalize_module_defs<'a, F>(
|
|||
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||
exposed_symbols: &MutSet<Symbol>,
|
||||
var_store: &mut VarStore,
|
||||
look_up_builtin: F,
|
||||
) -> Result<ModuleOutput, RuntimeError>
|
||||
where
|
||||
F: Fn(Symbol, &mut VarStore) -> Option<Def> + 'static + Send + Copy,
|
||||
{
|
||||
) -> Result<ModuleOutput, RuntimeError> {
|
||||
let mut can_exposed_imports = MutMap::default();
|
||||
let mut scope = Scope::new(home, var_store);
|
||||
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
|
||||
let num_deps = dep_idents.len();
|
||||
|
||||
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,
|
||||
alias.kind,
|
||||
);
|
||||
}
|
||||
|
||||
let effect_symbol = if let HeaderFor::Hosted { generates, .. } = header_for {
|
||||
// TODO extract effect name from the header
|
||||
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(),
|
||||
|
@ -96,10 +137,14 @@ where
|
|||
Region::zero(),
|
||||
vec![Loc::at_zero(("a".into(), a_var))],
|
||||
actual,
|
||||
AliasKind::Structural,
|
||||
);
|
||||
}
|
||||
|
||||
Some(effect_symbol)
|
||||
Some(Hosted {
|
||||
effect_symbol,
|
||||
generated_functions,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -246,7 +291,11 @@ where
|
|||
(Ok(mut declarations), output) => {
|
||||
use crate::def::Declaration::*;
|
||||
|
||||
if let Some(effect_symbol) = effect_symbol {
|
||||
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
|
||||
|
@ -257,6 +306,7 @@ where
|
|||
var_store,
|
||||
&mut exposed_symbols,
|
||||
&mut declarations,
|
||||
generated_functions,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -277,7 +327,7 @@ where
|
|||
// 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(effect_symbol) = effect_symbol {
|
||||
if let Some(Hosted { effect_symbol, .. }) = hosted_info {
|
||||
macro_rules! make_hosted_def {
|
||||
() => {
|
||||
let symbol = def.pattern_vars.iter().next().unwrap().0;
|
||||
|
@ -358,7 +408,7 @@ where
|
|||
|
||||
let mut aliases = MutMap::default();
|
||||
|
||||
if let Some(effect_symbol) = effect_symbol {
|
||||
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
|
||||
|
@ -435,7 +485,7 @@ where
|
|||
for symbol in references.iter() {
|
||||
if symbol.is_builtin() {
|
||||
// this can fail when the symbol is for builtin types, or has no implementation yet
|
||||
if let Some(def) = look_up_builtin(*symbol, var_store) {
|
||||
if let Some(def) = crate::builtins::builtin_defs_map(*symbol, var_store) {
|
||||
declarations.push(Declaration::Builtin(def));
|
||||
}
|
||||
}
|
||||
|
@ -493,6 +543,10 @@ fn fix_values_captured_in_closure_pattern(
|
|||
AppliedTag {
|
||||
arguments: loc_args,
|
||||
..
|
||||
}
|
||||
| UnwrappedOpaque {
|
||||
arguments: loc_args,
|
||||
..
|
||||
} => {
|
||||
for (_, loc_arg) in loc_args.iter_mut() {
|
||||
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
|
||||
|
@ -521,7 +575,8 @@ fn fix_values_captured_in_closure_pattern(
|
|||
| Underscore
|
||||
| Shadowed(..)
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_) => (),
|
||||
| UnsupportedPattern(_)
|
||||
| OpaqueNotInScope(..) => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -643,7 +698,7 @@ fn fix_values_captured_in_closure_expr(
|
|||
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
|
||||
}
|
||||
|
||||
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => {
|
||||
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } | OpaqueRef { arguments, .. } => {
|
||||
for (_, loc_arg) in arguments.iter_mut() {
|
||||
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ pub fn num_expr_from_result(
|
|||
env: &mut Env,
|
||||
) -> Expr {
|
||||
match result {
|
||||
Ok((str, ParsedNumResult::UnknownNum(num))) => {
|
||||
Expr::Num(var_store.fresh(), (*str).into(), num, NumericBound::None)
|
||||
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(),
|
||||
|
@ -50,7 +50,7 @@ pub fn num_expr_from_result(
|
|||
#[inline(always)]
|
||||
pub fn int_expr_from_result(
|
||||
var_store: &mut VarStore,
|
||||
result: Result<(&str, IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)>,
|
||||
result: Result<(&str, IntValue, IntBound), (&str, IntErrorKind)>,
|
||||
region: Region,
|
||||
base: Base,
|
||||
env: &mut Env,
|
||||
|
@ -77,7 +77,7 @@ pub fn int_expr_from_result(
|
|||
#[inline(always)]
|
||||
pub fn float_expr_from_result(
|
||||
var_store: &mut VarStore,
|
||||
result: Result<(&str, f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)>,
|
||||
result: Result<(&str, f64, FloatBound), (&str, FloatErrorKind)>,
|
||||
region: Region,
|
||||
env: &mut Env,
|
||||
) -> Expr {
|
||||
|
@ -101,31 +101,16 @@ pub fn float_expr_from_result(
|
|||
}
|
||||
|
||||
pub enum ParsedNumResult {
|
||||
Int(IntValue, NumericBound<IntWidth>),
|
||||
Float(f64, NumericBound<FloatWidth>),
|
||||
UnknownNum(IntValue),
|
||||
Int(IntValue, IntBound),
|
||||
Float(f64, FloatBound),
|
||||
UnknownNum(IntValue, NumericBound),
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorKind)> {
|
||||
// Ignore underscores.
|
||||
let radix = 10;
|
||||
let (num, bound) =
|
||||
from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))?;
|
||||
// Let's try to specialize the number
|
||||
Ok(match bound {
|
||||
NumericBound::None => ParsedNumResult::UnknownNum(num),
|
||||
NumericBound::Exact(NumWidth::Int(iw)) => {
|
||||
ParsedNumResult::Int(num, NumericBound::Exact(iw))
|
||||
}
|
||||
NumericBound::Exact(NumWidth::Float(fw)) => {
|
||||
let num = match num {
|
||||
IntValue::I128(n) => n as f64,
|
||||
IntValue::U128(n) => n as f64,
|
||||
};
|
||||
ParsedNumResult::Float(num, NumericBound::Exact(fw))
|
||||
}
|
||||
})
|
||||
from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -133,7 +118,7 @@ pub fn finish_parsing_base(
|
|||
raw: &str,
|
||||
base: Base,
|
||||
is_negative: bool,
|
||||
) -> Result<(IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)> {
|
||||
) -> Result<(IntValue, IntBound), (&str, IntErrorKind)> {
|
||||
let radix = match base {
|
||||
Base::Hex => 16,
|
||||
Base::Decimal => 10,
|
||||
|
@ -147,27 +132,25 @@ pub fn finish_parsing_base(
|
|||
} else {
|
||||
from_str_radix(raw.replace("_", "").as_str(), radix)
|
||||
})
|
||||
.and_then(|(n, bound)| {
|
||||
let bound = match bound {
|
||||
NumericBound::None => NumericBound::None,
|
||||
NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw),
|
||||
NumericBound::Exact(NumWidth::Float(_)) => return Err(IntErrorKind::FloatSuffix),
|
||||
};
|
||||
Ok((n, bound))
|
||||
.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)]
|
||||
pub fn finish_parsing_float(
|
||||
raw: &str,
|
||||
) -> Result<(f64, NumericBound<FloatWidth>), (&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 => NumericBound::None,
|
||||
Some(NumWidth::Float(fw)) => NumericBound::Exact(fw),
|
||||
Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)),
|
||||
None => FloatBound::None,
|
||||
Some(ParsedWidth::Float(fw)) => FloatBound::Exact(fw),
|
||||
Some(ParsedWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)),
|
||||
};
|
||||
|
||||
// Ignore underscores.
|
||||
|
@ -184,7 +167,13 @@ pub fn finish_parsing_float(
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
|
||||
#[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) {
|
||||
|
@ -194,20 +183,20 @@ fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
|
|||
}
|
||||
|
||||
parse_num_suffix! {
|
||||
"u8", NumWidth::Int(IntWidth::U8)
|
||||
"u16", NumWidth::Int(IntWidth::U16)
|
||||
"u32", NumWidth::Int(IntWidth::U32)
|
||||
"u64", NumWidth::Int(IntWidth::U64)
|
||||
"u128", NumWidth::Int(IntWidth::U128)
|
||||
"i8", NumWidth::Int(IntWidth::I8)
|
||||
"i16", NumWidth::Int(IntWidth::I16)
|
||||
"i32", NumWidth::Int(IntWidth::I32)
|
||||
"i64", NumWidth::Int(IntWidth::I64)
|
||||
"i128", NumWidth::Int(IntWidth::I128)
|
||||
"nat", NumWidth::Int(IntWidth::Nat)
|
||||
"dec", NumWidth::Float(FloatWidth::Dec)
|
||||
"f32", NumWidth::Float(FloatWidth::F32)
|
||||
"f64", NumWidth::Float(FloatWidth::F64)
|
||||
"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)
|
||||
|
@ -221,10 +210,7 @@ fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
|
|||
/// the LEGAL_DETAILS file in the root directory of this distribution.
|
||||
///
|
||||
/// Thanks to the Rust project and its contributors!
|
||||
fn from_str_radix(
|
||||
src: &str,
|
||||
radix: u32,
|
||||
) -> Result<(IntValue, NumericBound<NumWidth>), IntErrorKind> {
|
||||
fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind> {
|
||||
use self::IntErrorKind::*;
|
||||
|
||||
assert!(
|
||||
|
@ -262,25 +248,42 @@ fn from_str_radix(
|
|||
};
|
||||
|
||||
let (lower_bound, is_negative) = match result {
|
||||
IntValue::I128(num) => (lower_bound_of_int(num), num <= 0),
|
||||
IntValue::I128(num) => (lower_bound_of_int(num), num < 0),
|
||||
IntValue::U128(_) => (IntWidth::U128, false),
|
||||
};
|
||||
|
||||
match opt_exact_bound {
|
||||
None => {
|
||||
// TODO: use the lower bound
|
||||
Ok((result, NumericBound::None))
|
||||
// There's no exact bound, but we do have a lower bound.
|
||||
let sign_demand = if is_negative {
|
||||
SignDemand::Signed
|
||||
} else {
|
||||
SignDemand::NoDemand
|
||||
};
|
||||
Ok(ParsedNumResult::UnknownNum(
|
||||
result,
|
||||
NumericBound::AtLeastIntOrFloat {
|
||||
sign: sign_demand,
|
||||
width: lower_bound,
|
||||
},
|
||||
))
|
||||
}
|
||||
Some(bound @ NumWidth::Float(_)) => {
|
||||
Some(ParsedWidth::Float(fw)) => {
|
||||
// For now, assume floats can represent all integers
|
||||
// TODO: this is somewhat incorrect, revisit
|
||||
Ok((result, NumericBound::Exact(bound)))
|
||||
Ok(ParsedNumResult::Float(
|
||||
match result {
|
||||
IntValue::I128(n) => n as f64,
|
||||
IntValue::U128(n) => n as f64,
|
||||
},
|
||||
FloatBound::Exact(fw),
|
||||
))
|
||||
}
|
||||
Some(NumWidth::Int(exact_width)) => {
|
||||
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((result, NumericBound::Exact(NumWidth::Int(exact_width))))
|
||||
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
|
||||
|
@ -474,19 +477,36 @@ pub enum FloatWidth {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum NumWidth {
|
||||
Int(IntWidth),
|
||||
Float(FloatWidth),
|
||||
pub enum SignDemand {
|
||||
/// Can be signed or unsigned.
|
||||
NoDemand,
|
||||
/// Must be signed.
|
||||
Signed,
|
||||
}
|
||||
|
||||
/// Describes a bound on the width of a numeric literal.
|
||||
/// Describes a bound on the width of an integer.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum NumericBound<W>
|
||||
where
|
||||
W: Copy,
|
||||
{
|
||||
pub enum IntBound {
|
||||
/// There is no bound on the width.
|
||||
None,
|
||||
/// Must have exactly the width `W`.
|
||||
Exact(W),
|
||||
/// 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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
|
|||
Body(loc_pattern, loc_expr) => Body(loc_pattern, desugar_expr(arena, loc_expr)),
|
||||
SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def),
|
||||
alias @ Alias { .. } => *alias,
|
||||
opaque @ Opaque { .. } => *opaque,
|
||||
ann @ Annotation(_, _) => *ann,
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
|
@ -132,7 +133,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
| MalformedClosure
|
||||
| PrecedenceConflict { .. }
|
||||
| GlobalTag(_)
|
||||
| PrivateTag(_) => loc_expr,
|
||||
| PrivateTag(_)
|
||||
| OpaqueRef(_) => loc_expr,
|
||||
|
||||
Access(sub_expr, paths) => {
|
||||
let region = loc_expr.region;
|
||||
|
@ -170,7 +172,10 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
}),
|
||||
|
||||
RecordUpdate { fields, update } => {
|
||||
// NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
|
||||
// NOTE the `update` field is always a `Var { .. }`, we only desugar it to get rid of
|
||||
// any spaces before/after
|
||||
let new_update = desugar_expr(arena, update);
|
||||
|
||||
let new_fields = fields.map_items(arena, |field| {
|
||||
let value = desugar_field(arena, &field.value);
|
||||
Loc {
|
||||
|
@ -182,7 +187,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
|
|||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: RecordUpdate {
|
||||
update: *update,
|
||||
update: new_update,
|
||||
fields: new_fields,
|
||||
},
|
||||
})
|
||||
|
@ -415,7 +420,8 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
|
|||
Or => (ModuleName::BOOL, "or"),
|
||||
Pizza => unreachable!("Cannot desugar the |> operator"),
|
||||
Assignment => unreachable!("Cannot desugar the = operator"),
|
||||
HasType => unreachable!("Cannot desugar the : operator"),
|
||||
IsAliasType => unreachable!("Cannot desugar the : operator"),
|
||||
IsOpaqueType => unreachable!("Cannot desugar the := operator"),
|
||||
Backpassing => unreachable!("Cannot desugar the <- operator"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::env::Env;
|
||||
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
|
||||
use crate::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth,
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound,
|
||||
NumericBound, ParsedNumResult,
|
||||
};
|
||||
use crate::scope::Scope;
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{self, StrLiteral, StrSegment};
|
||||
|
@ -24,25 +25,25 @@ pub enum Pattern {
|
|||
tag_name: TagName,
|
||||
arguments: Vec<(Variable, Loc<Pattern>)>,
|
||||
},
|
||||
UnwrappedOpaque {
|
||||
whole_var: Variable,
|
||||
opaque: Symbol,
|
||||
arguments: Vec<(Variable, Loc<Pattern>)>,
|
||||
},
|
||||
RecordDestructure {
|
||||
whole_var: Variable,
|
||||
ext_var: Variable,
|
||||
destructs: Vec<Loc<RecordDestruct>>,
|
||||
},
|
||||
NumLiteral(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
|
||||
IntLiteral(
|
||||
Variable,
|
||||
Variable,
|
||||
Box<str>,
|
||||
IntValue,
|
||||
NumericBound<IntWidth>,
|
||||
),
|
||||
FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
|
||||
NumLiteral(Variable, Box<str>, IntValue, NumericBound),
|
||||
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
|
||||
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
|
||||
StrLiteral(Box<str>),
|
||||
Underscore,
|
||||
|
||||
// Runtime Exceptions
|
||||
Shadowed(Region, Loc<Ident>, Symbol),
|
||||
OpaqueNotInScope(Loc<Ident>),
|
||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
UnsupportedPattern(Region),
|
||||
// parse error patterns
|
||||
|
@ -84,6 +85,14 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
|||
symbols_from_pattern_help(&nested.value, symbols);
|
||||
}
|
||||
}
|
||||
UnwrappedOpaque {
|
||||
opaque, arguments, ..
|
||||
} => {
|
||||
symbols.push(*opaque);
|
||||
for (_, nested) in arguments {
|
||||
symbols_from_pattern_help(&nested.value, symbols);
|
||||
}
|
||||
}
|
||||
RecordDestructure { destructs, .. } => {
|
||||
for destruct in destructs {
|
||||
// when a record field has a pattern guard, only symbols in the guard are introduced
|
||||
|
@ -101,7 +110,8 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
|||
| StrLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_) => {}
|
||||
| UnsupportedPattern(_)
|
||||
| OpaqueNotInScope(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,17 +169,8 @@ pub fn canonicalize_pattern<'a>(
|
|||
arguments: vec![],
|
||||
}
|
||||
}
|
||||
OpaqueRef(..) => todo_opaques!(),
|
||||
Apply(tag, patterns) => {
|
||||
let tag_name = match tag.value {
|
||||
GlobalTag(name) => TagName::Global(name.into()),
|
||||
PrivateTag(name) => {
|
||||
let ident_id = env.ident_ids.get_or_insert(&name.into());
|
||||
|
||||
TagName::Private(Symbol::new(env.home, ident_id))
|
||||
}
|
||||
_ => unreachable!("Other patterns cannot be applied"),
|
||||
};
|
||||
|
||||
let mut can_patterns = Vec::with_capacity(patterns.len());
|
||||
for loc_pattern in *patterns {
|
||||
let (new_output, can_pattern) = canonicalize_pattern(
|
||||
|
@ -186,6 +187,9 @@ pub fn canonicalize_pattern<'a>(
|
|||
can_patterns.push((var_store.fresh(), can_pattern));
|
||||
}
|
||||
|
||||
match tag.value {
|
||||
GlobalTag(name) => {
|
||||
let tag_name = TagName::Global(name.into());
|
||||
Pattern::AppliedTag {
|
||||
whole_var: var_store.fresh(),
|
||||
ext_var: var_store.fresh(),
|
||||
|
@ -193,6 +197,33 @@ pub fn canonicalize_pattern<'a>(
|
|||
arguments: can_patterns,
|
||||
}
|
||||
}
|
||||
PrivateTag(name) => {
|
||||
let ident_id = env.ident_ids.get_or_insert(&name.into());
|
||||
let tag_name = TagName::Private(Symbol::new(env.home, ident_id));
|
||||
|
||||
Pattern::AppliedTag {
|
||||
whole_var: var_store.fresh(),
|
||||
ext_var: var_store.fresh(),
|
||||
tag_name,
|
||||
arguments: can_patterns,
|
||||
}
|
||||
}
|
||||
|
||||
OpaqueRef(name) => match scope.lookup_opaque_ref(name, tag.region) {
|
||||
Ok(opaque) => Pattern::UnwrappedOpaque {
|
||||
whole_var: var_store.fresh(),
|
||||
opaque,
|
||||
arguments: can_patterns,
|
||||
},
|
||||
Err(runtime_error) => {
|
||||
env.problem(Problem::RuntimeError(runtime_error));
|
||||
|
||||
Pattern::OpaqueNotInScope(Loc::at(tag.region, name.into()))
|
||||
}
|
||||
},
|
||||
_ => unreachable!("Other patterns cannot be applied"),
|
||||
}
|
||||
}
|
||||
|
||||
&FloatLiteral(str) => match pattern_type {
|
||||
WhenBranch => match finish_parsing_float(str) {
|
||||
|
@ -222,8 +253,8 @@ pub fn canonicalize_pattern<'a>(
|
|||
let problem = MalformedPatternProblem::MalformedInt;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok(ParsedNumResult::UnknownNum(int)) => {
|
||||
Pattern::NumLiteral(var_store.fresh(), (str).into(), int, NumericBound::None)
|
||||
Ok(ParsedNumResult::UnknownNum(int, bound)) => {
|
||||
Pattern::NumLiteral(var_store.fresh(), (str).into(), int, bound)
|
||||
}
|
||||
Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral(
|
||||
var_store.fresh(),
|
||||
|
@ -260,7 +291,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
}
|
||||
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).into_boxed_str();
|
||||
let i = match int {
|
||||
// Safety: this is fine because I128::MAX = |I128::MIN| - 1
|
||||
IntValue::I128(n) if is_negative => IntValue::I128(-n),
|
||||
|
@ -506,6 +537,16 @@ fn add_bindings_from_patterns(
|
|||
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
|
||||
}
|
||||
}
|
||||
UnwrappedOpaque {
|
||||
arguments: loc_args,
|
||||
opaque,
|
||||
..
|
||||
} => {
|
||||
for (_, loc_arg) in loc_args {
|
||||
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
|
||||
}
|
||||
answer.push((*opaque, *region));
|
||||
}
|
||||
RecordDestructure { destructs, .. } => {
|
||||
for Loc {
|
||||
region,
|
||||
|
@ -521,7 +562,8 @@ fn add_bindings_from_patterns(
|
|||
| StrLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_) => (),
|
||||
| UnsupportedPattern(_)
|
||||
| OpaqueNotInScope(..) => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,8 @@ impl Procedure {
|
|||
pub struct References {
|
||||
pub bound_symbols: ImSet<Symbol>,
|
||||
pub lookups: ImSet<Symbol>,
|
||||
pub referenced_aliases: ImSet<Symbol>,
|
||||
/// Aliases or opaque types referenced
|
||||
pub referenced_type_defs: ImSet<Symbol>,
|
||||
pub calls: ImSet<Symbol>,
|
||||
}
|
||||
|
||||
|
@ -59,7 +60,7 @@ impl References {
|
|||
self.lookups = self.lookups.union(other.lookups);
|
||||
self.calls = self.calls.union(other.calls);
|
||||
self.bound_symbols = self.bound_symbols.union(other.bound_symbols);
|
||||
self.referenced_aliases = self.referenced_aliases.union(other.referenced_aliases);
|
||||
self.referenced_type_defs = self.referenced_type_defs.union(other.referenced_type_defs);
|
||||
|
||||
self
|
||||
}
|
||||
|
@ -68,7 +69,7 @@ impl References {
|
|||
self.lookups.extend(other.lookups);
|
||||
self.calls.extend(other.calls);
|
||||
self.bound_symbols.extend(other.bound_symbols);
|
||||
self.referenced_aliases.extend(other.referenced_aliases);
|
||||
self.referenced_type_defs.extend(other.referenced_type_defs);
|
||||
}
|
||||
|
||||
pub fn has_lookup(&self, symbol: Symbol) -> bool {
|
||||
|
|
|
@ -4,7 +4,7 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
|||
use roc_problem::can::RuntimeError;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{Alias, Type};
|
||||
use roc_types::types::{Alias, AliasKind, Type};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Scope {
|
||||
|
@ -50,6 +50,8 @@ impl Scope {
|
|||
lambda_set_variables: Vec::new(),
|
||||
recursion_variables: MutSet::default(),
|
||||
type_variables: variables,
|
||||
// TODO(opaques): replace when opaques are included in the stdlib
|
||||
kind: AliasKind::Structural,
|
||||
};
|
||||
|
||||
aliases.insert(symbol, alias);
|
||||
|
@ -100,6 +102,76 @@ impl Scope {
|
|||
self.aliases.get(&symbol)
|
||||
}
|
||||
|
||||
/// Check if there is an opaque type alias referenced by `opaque_ref` referenced in the
|
||||
/// current scope. E.g. `$Age` must reference an opaque `Age` declared in this module, not any
|
||||
/// other!
|
||||
// TODO(opaques): $->@ in the above comment
|
||||
pub fn lookup_opaque_ref(
|
||||
&self,
|
||||
opaque_ref: &str,
|
||||
lookup_region: Region,
|
||||
) -> Result<Symbol, RuntimeError> {
|
||||
debug_assert!(opaque_ref.starts_with('$'));
|
||||
let opaque = opaque_ref[1..].into();
|
||||
|
||||
match self.idents.get(&opaque) {
|
||||
// TODO: is it worth caching any of these results?
|
||||
Some((symbol, decl_region)) => {
|
||||
if symbol.module_id() != self.home {
|
||||
// The reference is to an opaque type declared in another module - this is
|
||||
// illegal, as opaque types can only be wrapped/unwrapped in the scope they're
|
||||
// declared.
|
||||
return Err(RuntimeError::OpaqueOutsideScope {
|
||||
opaque,
|
||||
referenced_region: lookup_region,
|
||||
imported_region: *decl_region,
|
||||
});
|
||||
}
|
||||
|
||||
match self.aliases.get(symbol) {
|
||||
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
|
||||
|
||||
Some(alias) => match alias.kind {
|
||||
// The reference is to a proper alias like `Age : U32`, not an opaque type!
|
||||
AliasKind::Structural => Err(self.opaque_not_defined_error(
|
||||
opaque,
|
||||
lookup_region,
|
||||
Some(alias.header_region()),
|
||||
)),
|
||||
// All is good
|
||||
AliasKind::Opaque => Ok(*symbol),
|
||||
},
|
||||
}
|
||||
}
|
||||
None => Err(self.opaque_not_defined_error(opaque, lookup_region, None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn opaque_not_defined_error(
|
||||
&self,
|
||||
opaque: Ident,
|
||||
lookup_region: Region,
|
||||
opt_defined_alias: Option<Region>,
|
||||
) -> RuntimeError {
|
||||
let opaques_in_scope = self
|
||||
.idents()
|
||||
.filter(|(_, (sym, _))| {
|
||||
self.aliases
|
||||
.get(sym)
|
||||
.map(|alias| alias.kind)
|
||||
.unwrap_or(AliasKind::Structural)
|
||||
== AliasKind::Opaque
|
||||
})
|
||||
.map(|(v, _)| v.as_ref().into())
|
||||
.collect();
|
||||
|
||||
RuntimeError::OpaqueNotDefined {
|
||||
usage: Loc::at(lookup_region, opaque),
|
||||
opaques_in_scope,
|
||||
opt_defined_alias,
|
||||
}
|
||||
}
|
||||
|
||||
/// Introduce a new ident to scope.
|
||||
///
|
||||
/// Returns Err if this would shadow an existing ident, including the
|
||||
|
@ -180,7 +252,24 @@ impl Scope {
|
|||
region: Region,
|
||||
vars: Vec<Loc<(Lowercase, Variable)>>,
|
||||
typ: Type,
|
||||
kind: AliasKind,
|
||||
) {
|
||||
let alias = create_alias(name, region, vars, typ, kind);
|
||||
self.aliases.insert(name, alias);
|
||||
}
|
||||
|
||||
pub fn contains_alias(&mut self, name: Symbol) -> bool {
|
||||
self.aliases.contains_key(&name)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_alias(
|
||||
name: Symbol,
|
||||
region: Region,
|
||||
vars: Vec<Loc<(Lowercase, Variable)>>,
|
||||
typ: Type,
|
||||
kind: AliasKind,
|
||||
) -> Alias {
|
||||
let roc_types::types::VariableDetail {
|
||||
type_variables,
|
||||
lambda_set_variables,
|
||||
|
@ -209,18 +298,12 @@ impl Scope {
|
|||
.map(|v| roc_types::types::LambdaSet(Type::Variable(v)))
|
||||
.collect();
|
||||
|
||||
let alias = Alias {
|
||||
Alias {
|
||||
region,
|
||||
type_variables: vars,
|
||||
lambda_set_variables,
|
||||
recursion_variables,
|
||||
typ,
|
||||
};
|
||||
|
||||
self.aliases.insert(name, alias);
|
||||
}
|
||||
|
||||
pub fn contains_alias(&mut self, name: Symbol) -> bool {
|
||||
self.aliases.contains_key(&name)
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1063,6 +1063,22 @@ mod test_can {
|
|||
assert_eq!(problems, Vec::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_2534() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
x = { a: 1 }
|
||||
{
|
||||
x & a: 2
|
||||
}
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
assert_eq!(problems, Vec::new());
|
||||
}
|
||||
|
||||
//#[test]
|
||||
//fn closing_over_locals() {
|
||||
// // "local" should be used, because the closure used it.
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
|
|
|
@ -1,30 +1,41 @@
|
|||
use roc_can::constraint::Constraint::{self, *};
|
||||
use roc_can::constraint::LetConstraint;
|
||||
use roc_can::expected::Expected::{self, *};
|
||||
use roc_can::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
|
||||
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand};
|
||||
use roc_collections::all::SendMap;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::Category;
|
||||
use roc_types::types::Reason;
|
||||
use roc_types::types::Type::{self, *};
|
||||
use roc_types::types::{AliasKind, Category};
|
||||
|
||||
#[must_use]
|
||||
pub fn add_numeric_bound_constr(
|
||||
constrs: &mut Vec<Constraint>,
|
||||
num_type: Type,
|
||||
bound: impl TypedNumericBound,
|
||||
region: Region,
|
||||
category: Category,
|
||||
) {
|
||||
if let Some(typ) = bound.concrete_num_type() {
|
||||
) -> 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(
|
||||
num_type,
|
||||
Expected::ForReason(Reason::NumericLiteralSuffix, typ, region),
|
||||
total_num_type.clone(),
|
||||
Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region),
|
||||
category,
|
||||
region,
|
||||
));
|
||||
total_num_type
|
||||
}
|
||||
_ => RangedNumber(Box::new(total_num_type), range),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,15 +45,20 @@ pub fn int_literal(
|
|||
precision_var: Variable,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
bound: NumericBound<IntWidth>,
|
||||
bound: IntBound,
|
||||
) -> Constraint {
|
||||
let num_type = Variable(num_var);
|
||||
let reason = Reason::IntLiteral;
|
||||
|
||||
let mut constrs = Vec::with_capacity(3);
|
||||
// Always add the bound first; this improves the resolved type quality in case it's an alias
|
||||
// like "U8".
|
||||
add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num);
|
||||
let num_type = add_numeric_bound_constr(
|
||||
&mut constrs,
|
||||
Variable(num_var),
|
||||
bound,
|
||||
region,
|
||||
Category::Num,
|
||||
);
|
||||
constrs.extend(vec![
|
||||
Eq(
|
||||
num_type.clone(),
|
||||
|
@ -62,15 +78,14 @@ pub fn float_literal(
|
|||
precision_var: Variable,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
bound: NumericBound<FloatWidth>,
|
||||
bound: FloatBound,
|
||||
) -> Constraint {
|
||||
let num_type = Variable(num_var);
|
||||
let reason = Reason::FloatLiteral;
|
||||
|
||||
let mut constrs = Vec::with_capacity(3);
|
||||
add_numeric_bound_constr(
|
||||
let num_type = add_numeric_bound_constr(
|
||||
&mut constrs,
|
||||
num_type.clone(),
|
||||
Variable(num_var),
|
||||
bound,
|
||||
region,
|
||||
Category::Float,
|
||||
|
@ -93,12 +108,13 @@ pub fn num_literal(
|
|||
num_var: Variable,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
bound: NumericBound<NumWidth>,
|
||||
bound: NumericBound,
|
||||
) -> Constraint {
|
||||
let num_type = crate::builtins::num_num(Type::Variable(num_var));
|
||||
let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
|
||||
|
||||
let mut constrs = Vec::with_capacity(3);
|
||||
add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num);
|
||||
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))
|
||||
|
@ -146,6 +162,8 @@ fn builtin_alias(
|
|||
type_arguments,
|
||||
actual,
|
||||
lambda_set_variables: vec![],
|
||||
// TODO(opaques): revisit later
|
||||
kind: AliasKind::Structural,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,57 +212,6 @@ pub fn num_int(range: Type) -> Type {
|
|||
)
|
||||
}
|
||||
|
||||
macro_rules! num_types {
|
||||
// Represent
|
||||
// num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8))
|
||||
// int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8)
|
||||
//
|
||||
// num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32))
|
||||
// float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32)
|
||||
// and so on, for all numeric types.
|
||||
($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => {
|
||||
$(
|
||||
#[inline(always)]
|
||||
fn $sub_fn() -> Type {
|
||||
builtin_alias(
|
||||
$inner_alias,
|
||||
vec![],
|
||||
Box::new(Type::TagUnion(
|
||||
vec![(TagName::Private($inner_private_tag), vec![])],
|
||||
Box::new(Type::EmptyTagUnion)
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn $num_fn() -> Type {
|
||||
builtin_alias(
|
||||
$alias,
|
||||
vec![],
|
||||
Box::new($num_type($sub_fn()))
|
||||
)
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
num_types! {
|
||||
num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8
|
||||
num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16
|
||||
num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32
|
||||
num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64
|
||||
num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128
|
||||
num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8
|
||||
num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16
|
||||
num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32
|
||||
num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64
|
||||
num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128
|
||||
num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL
|
||||
num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL
|
||||
num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32
|
||||
num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_signed64() -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
|
@ -287,52 +254,82 @@ pub fn num_num(typ: Type) -> Type {
|
|||
}
|
||||
|
||||
pub trait TypedNumericBound {
|
||||
/// Get a concrete type for this number, if one exists.
|
||||
/// Returns `None` e.g. if the bound is open, like `Int *`.
|
||||
fn concrete_num_type(&self) -> Option<Type>;
|
||||
fn bounded_range(&self) -> Vec<Variable>;
|
||||
}
|
||||
|
||||
impl TypedNumericBound for NumericBound<IntWidth> {
|
||||
fn concrete_num_type(&self) -> Option<Type> {
|
||||
impl TypedNumericBound for IntBound {
|
||||
fn bounded_range(&self) -> Vec<Variable> {
|
||||
match self {
|
||||
NumericBound::None => None,
|
||||
NumericBound::Exact(w) => Some(match w {
|
||||
IntWidth::U8 => num_u8(),
|
||||
IntWidth::U16 => num_u16(),
|
||||
IntWidth::U32 => num_u32(),
|
||||
IntWidth::U64 => num_u64(),
|
||||
IntWidth::U128 => num_u128(),
|
||||
IntWidth::I8 => num_i8(),
|
||||
IntWidth::I16 => num_i16(),
|
||||
IntWidth::I32 => num_i32(),
|
||||
IntWidth::I64 => num_i64(),
|
||||
IntWidth::I128 => num_i128(),
|
||||
IntWidth::Nat => num_nat(),
|
||||
}),
|
||||
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 NumericBound<FloatWidth> {
|
||||
fn concrete_num_type(&self) -> Option<Type> {
|
||||
impl TypedNumericBound for FloatBound {
|
||||
fn bounded_range(&self) -> Vec<Variable> {
|
||||
match self {
|
||||
NumericBound::None => None,
|
||||
NumericBound::Exact(w) => Some(match w {
|
||||
FloatWidth::Dec => num_dec(),
|
||||
FloatWidth::F32 => num_f32(),
|
||||
FloatWidth::F64 => num_f64(),
|
||||
}),
|
||||
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<NumWidth> {
|
||||
fn concrete_num_type(&self) -> Option<Type> {
|
||||
impl TypedNumericBound for NumericBound {
|
||||
fn bounded_range(&self) -> Vec<Variable> {
|
||||
match self {
|
||||
NumericBound::None => None,
|
||||
NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(*iw).concrete_num_type(),
|
||||
NumericBound::Exact(NumWidth::Float(fw)) => {
|
||||
NumericBound::Exact(*fw).concrete_num_type()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use roc_can::expr::Expr::{self, *};
|
|||
use roc_can::expr::{ClosureData, Field, WhenBranch};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_collections::all::{ImMap, Index, MutSet, SendMap};
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
@ -916,6 +917,8 @@ pub fn constrain_expr(
|
|||
exists(vars, And(arg_cons))
|
||||
}
|
||||
|
||||
OpaqueRef { .. } => todo_opaques!(),
|
||||
|
||||
RunLowLevel { args, ret_var, op } => {
|
||||
// This is a modified version of what we do for function calls.
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ use roc_can::expected::{Expected, PExpected};
|
|||
use roc_can::pattern::Pattern::{self, *};
|
||||
use roc_can::pattern::{DestructType, RecordDestruct};
|
||||
use roc_collections::all::{Index, SendMap};
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
@ -55,6 +56,7 @@ fn headers_from_annotation_help(
|
|||
Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
| OpaqueNotInScope(..)
|
||||
| NumLiteral(..)
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
|
@ -114,6 +116,8 @@ fn headers_from_annotation_help(
|
|||
}
|
||||
_ => false,
|
||||
},
|
||||
|
||||
UnwrappedOpaque { .. } => todo_opaques!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +162,7 @@ pub fn constrain_pattern(
|
|||
PresenceConstraint::IsOpen,
|
||||
));
|
||||
}
|
||||
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) => {
|
||||
Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | OpaqueNotInScope(..) => {
|
||||
// Neither the _ pattern nor erroneous ones add any constraints.
|
||||
}
|
||||
|
||||
|
@ -183,9 +187,9 @@ pub fn constrain_pattern(
|
|||
|
||||
let num_type = builtins::num_num(Type::Variable(var));
|
||||
|
||||
builtins::add_numeric_bound_constr(
|
||||
let num_type = builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
num_type.clone(),
|
||||
num_type,
|
||||
bound,
|
||||
region,
|
||||
Category::Num,
|
||||
|
@ -202,7 +206,7 @@ pub fn constrain_pattern(
|
|||
&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.
|
||||
builtins::add_numeric_bound_constr(
|
||||
let num_type = builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
Type::Variable(num_var),
|
||||
bound,
|
||||
|
@ -214,7 +218,7 @@ pub fn constrain_pattern(
|
|||
let int_type = builtins::num_int(Type::Variable(precision_var));
|
||||
|
||||
state.constraints.push(Constraint::Eq(
|
||||
Type::Variable(num_var),
|
||||
num_type, // TODO check me if something breaks!
|
||||
Expected::NoExpectation(int_type),
|
||||
Category::Int,
|
||||
region,
|
||||
|
@ -232,7 +236,7 @@ pub fn constrain_pattern(
|
|||
&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.
|
||||
builtins::add_numeric_bound_constr(
|
||||
let num_type = builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
Type::Variable(num_var),
|
||||
bound,
|
||||
|
@ -244,7 +248,7 @@ pub fn constrain_pattern(
|
|||
let float_type = builtins::num_float(Type::Variable(precision_var));
|
||||
|
||||
state.constraints.push(Constraint::Eq(
|
||||
Type::Variable(num_var),
|
||||
num_type.clone(), // TODO check me if something breaks!
|
||||
Expected::NoExpectation(float_type),
|
||||
Category::Float,
|
||||
region,
|
||||
|
@ -254,7 +258,7 @@ pub fn constrain_pattern(
|
|||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Float,
|
||||
Type::Variable(num_var),
|
||||
num_type, // TODO check me if something breaks!
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
@ -444,5 +448,7 @@ pub fn constrain_pattern(
|
|||
state.constraints.push(whole_con);
|
||||
state.constraints.push(tag_con);
|
||||
}
|
||||
|
||||
UnwrappedOpaque { .. } => todo_opaques!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
|
||||
Buf,
|
||||
};
|
||||
use roc_parse::ast::{AliasHeader, AssignedField, Collection, Expr, Tag, TypeAnnotation};
|
||||
use roc_parse::ast::{AssignedField, Collection, Expr, Tag, TypeAnnotation, TypeHeader};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
|
@ -276,7 +276,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
As(lhs, _spaces, AliasHeader { name, vars }) => {
|
||||
As(lhs, _spaces, TypeHeader { name, vars }) => {
|
||||
// TODO use _spaces?
|
||||
lhs.value
|
||||
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::annotation::{Formattable, Newlines, Parens};
|
|||
use crate::pattern::fmt_pattern;
|
||||
use crate::spaces::{fmt_spaces, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{AliasHeader, Def, Expr, Pattern};
|
||||
use roc_parse::ast::{Def, Expr, Pattern, TypeHeader};
|
||||
use roc_region::all::Loc;
|
||||
|
||||
/// A Located formattable value is also formattable
|
||||
|
@ -12,6 +12,7 @@ impl<'a> Formattable for Def<'a> {
|
|||
|
||||
match self {
|
||||
Alias { ann, .. } => ann.is_multiline(),
|
||||
Opaque { typ, .. } => typ.is_multiline(),
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
loc_pattern.is_multiline() || loc_annotation.is_multiline()
|
||||
}
|
||||
|
@ -58,8 +59,12 @@ impl<'a> Formattable for Def<'a> {
|
|||
}
|
||||
}
|
||||
Alias {
|
||||
header: AliasHeader { name, vars },
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
}
|
||||
| Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ: ann,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
@ -69,7 +74,11 @@ impl<'a> Formattable for Def<'a> {
|
|||
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
|
||||
}
|
||||
|
||||
buf.push_str(" :");
|
||||
buf.push_str(match self {
|
||||
Alias { .. } => " :",
|
||||
Opaque { .. } => " :=",
|
||||
_ => unreachable!(),
|
||||
});
|
||||
buf.spaces(1);
|
||||
|
||||
ann.format(buf, indent + INDENT)
|
||||
|
|
|
@ -37,7 +37,8 @@ impl<'a> Formattable for Expr<'a> {
|
|||
| MalformedIdent(_, _)
|
||||
| MalformedClosure
|
||||
| GlobalTag(_)
|
||||
| PrivateTag(_) => false,
|
||||
| PrivateTag(_)
|
||||
| OpaqueRef(_) => false,
|
||||
|
||||
// These expressions always have newlines
|
||||
Defs(_, _) | When(_, _) => true,
|
||||
|
@ -204,7 +205,7 @@ impl<'a> Formattable for Expr<'a> {
|
|||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
GlobalTag(string) | PrivateTag(string) => {
|
||||
GlobalTag(string) | PrivateTag(string) | OpaqueRef(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string)
|
||||
}
|
||||
|
@ -347,7 +348,8 @@ fn push_op(buf: &mut Buf, op: BinOp) {
|
|||
called_via::BinOp::Or => buf.push_str("||"),
|
||||
called_via::BinOp::Pizza => buf.push_str("|>"),
|
||||
called_via::BinOp::Assignment => unreachable!(),
|
||||
called_via::BinOp::HasType => unreachable!(),
|
||||
called_via::BinOp::IsAliasType => unreachable!(),
|
||||
called_via::BinOp::IsOpaqueType => unreachable!(),
|
||||
called_via::BinOp::Backpassing => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -1067,7 +1069,11 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
|||
| BinOp::GreaterThanOrEq
|
||||
| BinOp::And
|
||||
| BinOp::Or => true,
|
||||
BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false,
|
||||
BinOp::Pizza
|
||||
| BinOp::Assignment
|
||||
| BinOp::IsAliasType
|
||||
| BinOp::IsOpaqueType
|
||||
| BinOp::Backpassing => false,
|
||||
})
|
||||
}
|
||||
Expr::If(_, _) => true,
|
||||
|
|
|
@ -30,6 +30,7 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
Pattern::Identifier(_)
|
||||
| Pattern::GlobalTag(_)
|
||||
| Pattern::PrivateTag(_)
|
||||
| Pattern::OpaqueRef(_)
|
||||
| Pattern::Apply(_, _)
|
||||
| Pattern::NumLiteral(..)
|
||||
| Pattern::NonBase10Literal { .. }
|
||||
|
@ -56,7 +57,7 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
buf.indent(indent);
|
||||
buf.push_str(string)
|
||||
}
|
||||
GlobalTag(name) | PrivateTag(name) => {
|
||||
GlobalTag(name) | PrivateTag(name) | OpaqueRef(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage};
|
||||
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
|
||||
use crate::Relocation;
|
||||
use bumpalo::collections::Vec;
|
||||
use packed_struct::prelude::*;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::Layout;
|
||||
|
@ -75,7 +74,7 @@ pub struct AArch64Call {}
|
|||
|
||||
const STACK_ALIGNMENT: u8 = 16;
|
||||
|
||||
impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||
impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64Call {
|
||||
const BASE_PTR_REG: AArch64GeneralReg = AArch64GeneralReg::FP;
|
||||
const STACK_PTR_REG: AArch64GeneralReg = AArch64GeneralReg::ZRSP;
|
||||
|
||||
|
@ -160,13 +159,14 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
#[inline(always)]
|
||||
fn setup_stack(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
saved_regs: &[AArch64GeneralReg],
|
||||
saved_general_regs: &[AArch64GeneralReg],
|
||||
saved_float_regs: &[AArch64FloatReg],
|
||||
requested_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) -> i32 {
|
||||
// Full size is upcast to i64 to make sure we don't overflow here.
|
||||
let full_stack_size = match requested_stack_size
|
||||
.checked_add(8 * saved_regs.len() as i32 + 8) // The extra 8 is space to store the frame pointer.
|
||||
.checked_add(8 * (saved_general_regs.len() + saved_float_regs.len()) as i32 + 8) // The extra 8 is space to store the frame pointer.
|
||||
.and_then(|size| size.checked_add(fn_call_stack_size))
|
||||
{
|
||||
Some(size) => size,
|
||||
|
@ -204,10 +204,14 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::FP);
|
||||
|
||||
offset = aligned_stack_size - fn_call_stack_size;
|
||||
for reg in saved_regs {
|
||||
for reg in saved_general_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_base32_reg64(buf, offset, *reg);
|
||||
}
|
||||
for reg in saved_float_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_base32_freg64(buf, offset, *reg);
|
||||
}
|
||||
aligned_stack_size
|
||||
} else {
|
||||
0
|
||||
|
@ -220,7 +224,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
#[inline(always)]
|
||||
fn cleanup_stack(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
saved_regs: &[AArch64GeneralReg],
|
||||
saved_general_regs: &[AArch64GeneralReg],
|
||||
saved_float_regs: &[AArch64FloatReg],
|
||||
aligned_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) {
|
||||
|
@ -233,10 +238,14 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::FP, offset);
|
||||
|
||||
offset = aligned_stack_size - fn_call_stack_size;
|
||||
for reg in saved_regs {
|
||||
for reg in saved_general_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_reg64_base32(buf, *reg, offset);
|
||||
}
|
||||
for reg in saved_float_regs {
|
||||
offset -= 8;
|
||||
AArch64Assembler::mov_freg64_base32(buf, *reg, offset);
|
||||
}
|
||||
AArch64Assembler::add_reg64_reg64_imm32(
|
||||
buf,
|
||||
AArch64GeneralReg::ZRSP,
|
||||
|
@ -249,37 +258,64 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
|||
#[inline(always)]
|
||||
fn load_args<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
AArch64GeneralReg,
|
||||
AArch64FloatReg,
|
||||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_args: &'a [(Layout<'a>, Symbol)],
|
||||
_ret_layout: &Layout<'a>,
|
||||
mut _stack_size: u32,
|
||||
) -> u32 {
|
||||
) {
|
||||
todo!("Loading args for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn store_args<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_symbol_map: &MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
AArch64GeneralReg,
|
||||
AArch64FloatReg,
|
||||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_args: &'a [Symbol],
|
||||
_arg_layouts: &[Layout<'a>],
|
||||
_ret_layout: &Layout<'a>,
|
||||
) -> u32 {
|
||||
) {
|
||||
todo!("Storing args for AArch64");
|
||||
}
|
||||
|
||||
fn return_struct<'a>(
|
||||
fn return_complex_symbol<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_struct_offset: i32,
|
||||
_struct_size: u32,
|
||||
_field_layouts: &[Layout<'a>],
|
||||
_ret_reg: Option<AArch64GeneralReg>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
AArch64GeneralReg,
|
||||
AArch64FloatReg,
|
||||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Returning structs for AArch64");
|
||||
todo!("Returning complex symbols for AArch64");
|
||||
}
|
||||
|
||||
fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool {
|
||||
todo!("Returning via arg pointer for AArch64");
|
||||
fn load_returned_complex_symbol<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
AArch64GeneralReg,
|
||||
AArch64FloatReg,
|
||||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Loading returned complex symbols for AArch64");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
1084
compiler/gen_dev/src/generic64/storage.rs
Normal file
1084
compiler/gen_dev/src/generic64/storage.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,15 @@
|
|||
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, TARGET_INFO};
|
||||
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
|
||||
use crate::{
|
||||
single_register_builtins, single_register_floats, single_register_integers, Relocation,
|
||||
single_register_floats, single_register_integers, single_register_layouts, Relocation,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64();
|
||||
|
||||
// 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.
|
||||
|
@ -67,7 +69,7 @@ pub struct X86_64SystemV {}
|
|||
|
||||
const STACK_ALIGNMENT: u8 = 16;
|
||||
|
||||
impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||
impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64SystemV {
|
||||
const BASE_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RBP;
|
||||
const STACK_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RSP;
|
||||
|
||||
|
@ -161,13 +163,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
#[inline(always)]
|
||||
fn setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
general_saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
requested_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) -> i32 {
|
||||
x86_64_generic_setup_stack(
|
||||
buf,
|
||||
general_saved_regs,
|
||||
saved_general_regs,
|
||||
saved_float_regs,
|
||||
requested_stack_size,
|
||||
fn_call_stack_size,
|
||||
)
|
||||
|
@ -176,13 +180,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
#[inline(always)]
|
||||
fn cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
general_saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
aligned_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) {
|
||||
x86_64_generic_cleanup_stack(
|
||||
buf,
|
||||
general_saved_regs,
|
||||
saved_general_regs,
|
||||
saved_float_regs,
|
||||
aligned_stack_size,
|
||||
fn_call_stack_size,
|
||||
)
|
||||
|
@ -191,271 +197,230 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
|||
#[inline(always)]
|
||||
fn load_args<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
args: &'a [(Layout<'a>, Symbol)],
|
||||
ret_layout: &Layout<'a>,
|
||||
mut stack_size: u32,
|
||||
) -> u32 {
|
||||
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
|
||||
) {
|
||||
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer.
|
||||
let mut general_i = 0;
|
||||
let mut float_i = 0;
|
||||
if X86_64SystemV::returns_via_arg_pointer(ret_layout) {
|
||||
symbol_map.insert(
|
||||
Symbol::RET_POINTER,
|
||||
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
|
||||
);
|
||||
storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]);
|
||||
general_i += 1;
|
||||
}
|
||||
for (layout, sym) in args.iter() {
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
if general_i < Self::GENERAL_PARAM_REGS.len() {
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
|
||||
);
|
||||
storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[general_i]);
|
||||
general_i += 1;
|
||||
} else {
|
||||
storage_manager.primitive_stack_arg(sym, arg_offset);
|
||||
arg_offset += 8;
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: arg_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
single_register_floats!() => {
|
||||
if float_i < Self::FLOAT_PARAM_REGS.len() {
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[float_i]),
|
||||
);
|
||||
storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[float_i]);
|
||||
float_i += 1;
|
||||
} else {
|
||||
storage_manager.primitive_stack_arg(sym, arg_offset);
|
||||
arg_offset += 8;
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: arg_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
|
||||
// Load the value from the param reg into a useable base offset.
|
||||
let src1 = Self::GENERAL_PARAM_REGS[general_i];
|
||||
let src2 = Self::GENERAL_PARAM_REGS[general_i + 1];
|
||||
stack_size += 16;
|
||||
let offset = -(stack_size as i32);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset, src1);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset + 8, src2);
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset,
|
||||
size: 16,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
let base_offset = storage_manager.claim_stack_area(sym, 16);
|
||||
X86_64Assembler::mov_base32_reg64(buf, base_offset, src1);
|
||||
X86_64Assembler::mov_base32_reg64(buf, base_offset + 8, src2);
|
||||
general_i += 2;
|
||||
} else {
|
||||
todo!("loading strings args on the stack");
|
||||
todo!("loading lists and strings args on the stack");
|
||||
}
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("Loading args with layout {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack_size
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn store_args<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
symbol_map: &MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> u32 {
|
||||
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
) {
|
||||
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
if Self::returns_via_arg_pointer(ret_layout) {
|
||||
// Save space on the stack for the arg we will return.
|
||||
storage_manager
|
||||
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
|
||||
todo!("claim first parama reg for the address");
|
||||
}
|
||||
let mut general_i = 0;
|
||||
let mut float_i = 0;
|
||||
// For most return layouts we will do nothing.
|
||||
// In some cases, we need to put the return address as the first arg.
|
||||
match ret_layout {
|
||||
single_register_builtins!() | Layout::Builtin(Builtin::Str) | Layout::Struct([]) => {
|
||||
// Nothing needs to be done for any of these cases.
|
||||
}
|
||||
x => {
|
||||
todo!("receiving return type, {:?}", x);
|
||||
}
|
||||
}
|
||||
for (i, layout) in arg_layouts.iter().enumerate() {
|
||||
for (sym, layout) in args.iter().zip(arg_layouts.iter()) {
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
let storage = match symbol_map.get(&args[i]) {
|
||||
Some(storage) => storage,
|
||||
None => {
|
||||
internal_error!("function argument does not reference any symbol")
|
||||
}
|
||||
};
|
||||
if general_i < Self::GENERAL_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst = Self::GENERAL_PARAM_REGS[general_i];
|
||||
match storage {
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||
}
|
||||
}
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::GENERAL_PARAM_REGS[general_i],
|
||||
);
|
||||
general_i += 1;
|
||||
} else {
|
||||
// Load the value to the stack.
|
||||
match storage {
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use RAX as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
// Copy to stack using return reg as buffer.
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
X86_64GeneralReg::RAX,
|
||||
*offset,
|
||||
sym,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
X86_64Assembler::mov_stack32_reg64(
|
||||
buf,
|
||||
stack_offset,
|
||||
X86_64GeneralReg::RAX,
|
||||
tmp_stack_offset,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||
}
|
||||
}
|
||||
stack_offset += 8;
|
||||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
single_register_floats!() => {
|
||||
let storage = match symbol_map.get(&args[i]) {
|
||||
Some(storage) => storage,
|
||||
None => {
|
||||
internal_error!("function argument does not reference any symbol")
|
||||
}
|
||||
};
|
||||
if float_i < Self::FLOAT_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst = Self::FLOAT_PARAM_REGS[float_i];
|
||||
match storage {
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_freg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
internal_error!("Cannot load general symbol into FloatReg")
|
||||
}
|
||||
}
|
||||
storage_manager.load_to_specified_float_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::FLOAT_PARAM_REGS[float_i],
|
||||
);
|
||||
float_i += 1;
|
||||
} else {
|
||||
// Load the value to the stack.
|
||||
match storage {
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use XMM0 as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_freg64_base32(
|
||||
// Copy to stack using return reg as buffer.
|
||||
storage_manager.load_to_specified_float_reg(
|
||||
buf,
|
||||
X86_64FloatReg::XMM0,
|
||||
*offset,
|
||||
sym,
|
||||
Self::FLOAT_RETURN_REGS[0],
|
||||
);
|
||||
X86_64Assembler::mov_stack32_freg64(
|
||||
buf,
|
||||
stack_offset,
|
||||
X86_64FloatReg::XMM0,
|
||||
tmp_stack_offset,
|
||||
Self::FLOAT_RETURN_REGS[0],
|
||||
);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
internal_error!("Cannot load general symbol into FloatReg")
|
||||
}
|
||||
}
|
||||
stack_offset += 8;
|
||||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
let storage = match symbol_map.get(&args[i]) {
|
||||
Some(storage) => storage,
|
||||
None => {
|
||||
internal_error!("function argument does not reference any symbol")
|
||||
}
|
||||
};
|
||||
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst1 = Self::GENERAL_PARAM_REGS[general_i];
|
||||
let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1];
|
||||
match storage {
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst1, *offset);
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8);
|
||||
}
|
||||
_ => {
|
||||
internal_error!(
|
||||
"Strings only support being loaded from base offsets"
|
||||
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(base_offset % 8, 0);
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_PARAM_REGS[general_i],
|
||||
base_offset,
|
||||
);
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_PARAM_REGS[general_i + 1],
|
||||
base_offset + 8,
|
||||
);
|
||||
}
|
||||
}
|
||||
general_i += 2;
|
||||
} else {
|
||||
todo!("calling functions with strings on the stack");
|
||||
}
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("calling with arg type, {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack_offset as u32
|
||||
storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32);
|
||||
}
|
||||
|
||||
fn return_struct<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_struct_offset: i32,
|
||||
_struct_size: u32,
|
||||
_field_layouts: &[Layout<'a>],
|
||||
_ret_reg: Option<X86_64GeneralReg>,
|
||||
fn return_complex_symbol<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
sym: &Symbol,
|
||||
layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Returning structs for X86_64");
|
||||
match layout {
|
||||
single_register_layouts!() => {
|
||||
internal_error!("single register layouts are not complex symbols");
|
||||
}
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(base_offset % 8, 0);
|
||||
X86_64Assembler::mov_reg64_base32(buf, Self::GENERAL_RETURN_REGS[0], base_offset);
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_RETURN_REGS[1],
|
||||
base_offset + 8,
|
||||
);
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => todo!("returning complex type, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_returned_complex_symbol<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
sym: &Symbol,
|
||||
layout: &Layout<'a>,
|
||||
) {
|
||||
match layout {
|
||||
single_register_layouts!() => {
|
||||
internal_error!("single register layouts are not complex symbols");
|
||||
}
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
let offset = storage_manager.claim_stack_area(sym, 16);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]);
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => todo!("receiving complex return type, {:?}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl X86_64SystemV {
|
||||
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 will 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
|
||||
ret_layout.stack_size(TARGET_INFO) > 16
|
||||
}
|
||||
}
|
||||
|
||||
impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||
impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64WindowsFastcall {
|
||||
const BASE_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RBP;
|
||||
const STACK_PTR_REG: X86_64GeneralReg = X86_64GeneralReg::RSP;
|
||||
|
||||
|
@ -553,225 +518,202 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
#[inline(always)]
|
||||
fn setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
requested_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) -> i32 {
|
||||
x86_64_generic_setup_stack(buf, saved_regs, requested_stack_size, fn_call_stack_size)
|
||||
x86_64_generic_setup_stack(
|
||||
buf,
|
||||
saved_general_regs,
|
||||
saved_float_regs,
|
||||
requested_stack_size,
|
||||
fn_call_stack_size,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
aligned_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) {
|
||||
x86_64_generic_cleanup_stack(buf, saved_regs, aligned_stack_size, fn_call_stack_size)
|
||||
x86_64_generic_cleanup_stack(
|
||||
buf,
|
||||
saved_general_regs,
|
||||
saved_float_regs,
|
||||
aligned_stack_size,
|
||||
fn_call_stack_size,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn load_args<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
args: &'a [(Layout<'a>, Symbol)],
|
||||
ret_layout: &Layout<'a>,
|
||||
stack_size: u32,
|
||||
) -> u32 {
|
||||
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
|
||||
) {
|
||||
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer.
|
||||
let mut i = 0;
|
||||
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout) {
|
||||
symbol_map.insert(
|
||||
Symbol::RET_POINTER,
|
||||
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]),
|
||||
);
|
||||
storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[i]);
|
||||
i += 1;
|
||||
}
|
||||
for (layout, sym) in args.iter() {
|
||||
if i < Self::GENERAL_PARAM_REGS.len() {
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
symbol_map
|
||||
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]));
|
||||
storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[i]);
|
||||
i += 1;
|
||||
}
|
||||
single_register_floats!() => {
|
||||
symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i]));
|
||||
storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[i]);
|
||||
i += 1;
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
// I think this just needs to be passed on the stack, so not a huge deal.
|
||||
todo!("Passing str args with Windows fast call");
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("Loading args with layout {:?}", x);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
arg_offset += match layout {
|
||||
single_register_builtins!() => 8,
|
||||
match layout {
|
||||
single_register_layouts!() => {
|
||||
storage_manager.primitive_stack_arg(sym, arg_offset);
|
||||
arg_offset += 8;
|
||||
}
|
||||
x => {
|
||||
todo!("Loading args with layout {:?}", x);
|
||||
}
|
||||
};
|
||||
symbol_map.insert(
|
||||
*sym,
|
||||
SymbolStorage::Base {
|
||||
offset: arg_offset,
|
||||
size: 8,
|
||||
owned: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
stack_size
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn store_args<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
symbol_map: &MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
|
||||
storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> u32 {
|
||||
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
// For most return layouts we will do nothing.
|
||||
// In some cases, we need to put the return address as the first arg.
|
||||
match ret_layout {
|
||||
single_register_builtins!() | Layout::Struct([]) => {
|
||||
// Nothing needs to be done for any of these cases.
|
||||
) {
|
||||
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
if Self::returns_via_arg_pointer(ret_layout) {
|
||||
// Save space on the stack for the arg we will return.
|
||||
storage_manager
|
||||
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
|
||||
todo!("claim first parama reg for the address");
|
||||
}
|
||||
x => {
|
||||
todo!("receiving return type, {:?}", x);
|
||||
}
|
||||
}
|
||||
for (i, layout) in arg_layouts.iter().enumerate() {
|
||||
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() {
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
let storage = match symbol_map.get(&args[i]) {
|
||||
Some(storage) => storage,
|
||||
None => {
|
||||
internal_error!("function argument does not reference any symbol")
|
||||
}
|
||||
};
|
||||
if i < Self::GENERAL_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst = Self::GENERAL_PARAM_REGS[i];
|
||||
match storage {
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Load the value to the stack.
|
||||
match storage {
|
||||
SymbolStorage::GeneralReg(reg)
|
||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use RAX as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
X86_64GeneralReg::RAX,
|
||||
*offset,
|
||||
sym,
|
||||
Self::GENERAL_PARAM_REGS[i],
|
||||
);
|
||||
} else {
|
||||
// Copy to stack using return reg as buffer.
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
X86_64Assembler::mov_stack32_reg64(
|
||||
buf,
|
||||
stack_offset,
|
||||
X86_64GeneralReg::RAX,
|
||||
tmp_stack_offset,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
}
|
||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||
}
|
||||
}
|
||||
stack_offset += 8;
|
||||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
single_register_floats!() => {
|
||||
let storage = match symbol_map.get(&args[i]) {
|
||||
Some(storage) => storage,
|
||||
None => {
|
||||
internal_error!("function argument does not reference any symbol")
|
||||
}
|
||||
};
|
||||
if i < Self::FLOAT_PARAM_REGS.len() {
|
||||
// Load the value to the param reg.
|
||||
let dst = Self::FLOAT_PARAM_REGS[i];
|
||||
match storage {
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
X86_64Assembler::mov_freg64_base32(buf, dst, *offset);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
internal_error!("Cannot load general symbol into FloatReg")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Load the value to the stack.
|
||||
match storage {
|
||||
SymbolStorage::FloatReg(reg)
|
||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
||||
}
|
||||
SymbolStorage::Base { offset, .. } => {
|
||||
// Use XMM0 as a tmp reg because it will be free before function calls.
|
||||
X86_64Assembler::mov_freg64_base32(
|
||||
storage_manager.load_to_specified_float_reg(
|
||||
buf,
|
||||
X86_64FloatReg::XMM0,
|
||||
*offset,
|
||||
sym,
|
||||
Self::FLOAT_PARAM_REGS[i],
|
||||
);
|
||||
} else {
|
||||
// Copy to stack using return reg as buffer.
|
||||
storage_manager.load_to_specified_float_reg(
|
||||
buf,
|
||||
sym,
|
||||
Self::FLOAT_RETURN_REGS[0],
|
||||
);
|
||||
X86_64Assembler::mov_stack32_freg64(
|
||||
buf,
|
||||
stack_offset,
|
||||
X86_64FloatReg::XMM0,
|
||||
tmp_stack_offset,
|
||||
Self::FLOAT_RETURN_REGS[0],
|
||||
);
|
||||
}
|
||||
SymbolStorage::GeneralReg(_)
|
||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||
internal_error!("Cannot load general symbol into FloatReg")
|
||||
}
|
||||
}
|
||||
stack_offset += 8;
|
||||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
// I think this just needs to be passed on the stack, so not a huge deal.
|
||||
todo!("Passing str args with Windows fast call");
|
||||
}
|
||||
Layout::Struct(&[]) => {}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("calling with arg type, {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack_offset as u32
|
||||
storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32);
|
||||
}
|
||||
|
||||
fn return_struct<'a>(
|
||||
fn return_complex_symbol<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_struct_offset: i32,
|
||||
_struct_size: u32,
|
||||
_field_layouts: &[Layout<'a>],
|
||||
_ret_reg: Option<X86_64GeneralReg>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
_sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Returning structs for X86_64WindowsFastCall");
|
||||
todo!("Returning complex symbols for X86_64");
|
||||
}
|
||||
|
||||
fn load_returned_complex_symbol<'a>(
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
_storage_manager: &mut StorageManager<
|
||||
'a,
|
||||
X86_64GeneralReg,
|
||||
X86_64FloatReg,
|
||||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
_sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
todo!("Loading returned complex symbols for X86_64");
|
||||
}
|
||||
}
|
||||
|
||||
impl X86_64WindowsFastcall {
|
||||
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
|
||||
// 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
|
||||
|
@ -782,7 +724,8 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
|||
#[inline(always)]
|
||||
fn x86_64_generic_setup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
requested_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) -> i32 {
|
||||
|
@ -790,7 +733,7 @@ fn x86_64_generic_setup_stack<'a>(
|
|||
X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RBP, X86_64GeneralReg::RSP);
|
||||
|
||||
let full_stack_size = match requested_stack_size
|
||||
.checked_add(8 * saved_regs.len() as i32)
|
||||
.checked_add(8 * (saved_general_regs.len() + saved_float_regs.len()) as i32)
|
||||
.and_then(|size| size.checked_add(fn_call_stack_size))
|
||||
{
|
||||
Some(size) => size,
|
||||
|
@ -817,10 +760,14 @@ fn x86_64_generic_setup_stack<'a>(
|
|||
|
||||
// Put values at the top of the stack to avoid conflicts with previously saved variables.
|
||||
let mut offset = aligned_stack_size - fn_call_stack_size;
|
||||
for reg in saved_regs {
|
||||
for reg in saved_general_regs {
|
||||
X86_64Assembler::mov_base32_reg64(buf, -offset, *reg);
|
||||
offset -= 8;
|
||||
}
|
||||
for reg in saved_float_regs {
|
||||
X86_64Assembler::mov_base32_freg64(buf, -offset, *reg);
|
||||
offset -= 8;
|
||||
}
|
||||
aligned_stack_size
|
||||
} else {
|
||||
0
|
||||
|
@ -834,16 +781,21 @@ fn x86_64_generic_setup_stack<'a>(
|
|||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn x86_64_generic_cleanup_stack<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
saved_regs: &[X86_64GeneralReg],
|
||||
saved_general_regs: &[X86_64GeneralReg],
|
||||
saved_float_regs: &[X86_64FloatReg],
|
||||
aligned_stack_size: i32,
|
||||
fn_call_stack_size: i32,
|
||||
) {
|
||||
if aligned_stack_size > 0 {
|
||||
let mut offset = aligned_stack_size - fn_call_stack_size;
|
||||
for reg in saved_regs {
|
||||
for reg in saved_general_regs {
|
||||
X86_64Assembler::mov_reg64_base32(buf, *reg, -offset);
|
||||
offset -= 8;
|
||||
}
|
||||
for reg in saved_float_regs {
|
||||
X86_64Assembler::mov_freg64_base32(buf, *reg, -offset);
|
||||
offset -= 8;
|
||||
}
|
||||
X86_64Assembler::add_reg64_reg64_imm32(
|
||||
buf,
|
||||
X86_64GeneralReg::RSP,
|
||||
|
@ -1429,6 +1381,9 @@ fn mov_reg64_base64_offset32(
|
|||
/// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register.
|
||||
#[inline(always)]
|
||||
fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
if dst == src {
|
||||
return;
|
||||
}
|
||||
let dst_high = dst as u8 > 7;
|
||||
let dst_mod = dst as u8 % 8;
|
||||
let src_high = src as u8 > 7;
|
||||
|
@ -2161,10 +2116,7 @@ mod tests {
|
|||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
(
|
||||
(X86_64FloatReg::XMM0, X86_64FloatReg::XMM0),
|
||||
vec![0xF2, 0x0F, 0x10, 0xC0],
|
||||
),
|
||||
((X86_64FloatReg::XMM0, X86_64FloatReg::XMM0), vec![]),
|
||||
(
|
||||
(X86_64FloatReg::XMM0, X86_64FloatReg::XMM15),
|
||||
vec![0xF2, 0x41, 0x0F, 0x10, 0xC7],
|
||||
|
@ -2173,10 +2125,7 @@ mod tests {
|
|||
(X86_64FloatReg::XMM15, X86_64FloatReg::XMM0),
|
||||
vec![0xF2, 0x44, 0x0F, 0x10, 0xF8],
|
||||
),
|
||||
(
|
||||
(X86_64FloatReg::XMM15, X86_64FloatReg::XMM15),
|
||||
vec![0xF2, 0x45, 0x0F, 0x10, 0xFF],
|
||||
),
|
||||
((X86_64FloatReg::XMM15, X86_64FloatReg::XMM15), vec![]),
|
||||
] {
|
||||
buf.clear();
|
||||
movsd_freg64_freg64(&mut buf, *dst, *src);
|
||||
|
|
|
@ -694,16 +694,8 @@ trait Backend<'a> {
|
|||
fn free_symbol(&mut self, sym: &Symbol);
|
||||
|
||||
/// set_last_seen sets the statement a symbol was last seen in.
|
||||
fn set_last_seen(
|
||||
&mut self,
|
||||
sym: Symbol,
|
||||
stmt: &Stmt<'a>,
|
||||
owning_symbol: &MutMap<Symbol, Symbol>,
|
||||
) {
|
||||
fn set_last_seen(&mut self, sym: Symbol, stmt: &Stmt<'a>) {
|
||||
self.last_seen_map().insert(sym, stmt);
|
||||
if let Some(parent) = owning_symbol.get(&sym) {
|
||||
self.last_seen_map().insert(*parent, stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/// last_seen_map gets the map from symbol to when it is last seen in the function.
|
||||
|
@ -749,45 +741,39 @@ trait Backend<'a> {
|
|||
/// scan_ast runs through the ast and fill the last seen map.
|
||||
/// This must iterate through the ast in the same way that build_stmt does. i.e. then before else.
|
||||
fn scan_ast(&mut self, stmt: &Stmt<'a>) {
|
||||
// This keeps track of symbols that depend on other symbols.
|
||||
// The main case of this is data in structures and tagged unions.
|
||||
// This data must extend the lifetime of the original structure or tagged union.
|
||||
// For arrays the loading is always done through low levels and does not depend on the underlying array's lifetime.
|
||||
let mut owning_symbol: MutMap<Symbol, Symbol> = MutMap::default();
|
||||
// Join map keeps track of join point parameters so that we can keep them around while they still might be jumped to.
|
||||
let mut join_map: MutMap<JoinPointId, &'a [Param<'a>]> = MutMap::default();
|
||||
match stmt {
|
||||
Stmt::Let(sym, expr, _, following) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
match expr {
|
||||
Expr::Literal(_) => {}
|
||||
|
||||
Expr::Call(call) => self.scan_ast_call(call, stmt, &owning_symbol),
|
||||
Expr::Call(call) => self.scan_ast_call(call, stmt),
|
||||
|
||||
Expr::Tag { arguments, .. } => {
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Struct(syms) => {
|
||||
for sym in *syms {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::StructAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::GetTagId { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::UnionAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt, &owning_symbol);
|
||||
owning_symbol.insert(*sym, *structure);
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::Array { elems, .. } => {
|
||||
for elem in *elems {
|
||||
if let ListLiteralElement::Symbol(sym) = elem {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -797,22 +783,22 @@ trait Backend<'a> {
|
|||
tag_name,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*symbol, stmt, &owning_symbol);
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
match tag_name {
|
||||
TagName::Closure(sym) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
TagName::Private(sym) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
TagName::Global(_) => {}
|
||||
}
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Reset { symbol, .. } => {
|
||||
self.set_last_seen(*symbol, stmt, &owning_symbol);
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
}
|
||||
Expr::EmptyArray => {}
|
||||
Expr::RuntimeErrorFunction(_) => {}
|
||||
|
@ -826,56 +812,59 @@ trait Backend<'a> {
|
|||
default_branch,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*cond_symbol, stmt, &owning_symbol);
|
||||
self.set_last_seen(*cond_symbol, stmt);
|
||||
for (_, _, branch) in *branches {
|
||||
self.scan_ast(branch);
|
||||
}
|
||||
self.scan_ast(default_branch.1);
|
||||
}
|
||||
Stmt::Ret(sym) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
Stmt::Refcounting(modify, following) => {
|
||||
let sym = modify.get_symbol();
|
||||
|
||||
self.set_last_seen(sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(sym, stmt);
|
||||
self.scan_ast(following);
|
||||
}
|
||||
Stmt::Join {
|
||||
parameters,
|
||||
body: continuation,
|
||||
remainder,
|
||||
id,
|
||||
..
|
||||
} => {
|
||||
join_map.insert(*id, parameters);
|
||||
for param in *parameters {
|
||||
self.set_last_seen(param.symbol, stmt, &owning_symbol);
|
||||
self.set_last_seen(param.symbol, stmt);
|
||||
}
|
||||
self.scan_ast(continuation);
|
||||
self.scan_ast(remainder);
|
||||
}
|
||||
Stmt::Jump(JoinPointId(sym), symbols) => {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
if let Some(parameters) = join_map.get(&JoinPointId(*sym)) {
|
||||
// Keep the parameters around. They will be overwritten when jumping.
|
||||
for param in *parameters {
|
||||
self.set_last_seen(param.symbol, stmt);
|
||||
}
|
||||
}
|
||||
self.set_last_seen(*sym, stmt);
|
||||
for sym in *symbols {
|
||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Stmt::RuntimeError(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_ast_call(
|
||||
&mut self,
|
||||
call: &roc_mono::ir::Call,
|
||||
stmt: &roc_mono::ir::Stmt<'a>,
|
||||
owning_symbol: &MutMap<Symbol, Symbol>,
|
||||
) {
|
||||
fn scan_ast_call(&mut self, call: &roc_mono::ir::Call, stmt: &roc_mono::ir::Stmt<'a>) {
|
||||
let roc_mono::ir::Call {
|
||||
call_type,
|
||||
arguments,
|
||||
} = call;
|
||||
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt, owning_symbol);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
|
||||
match call_type {
|
||||
|
|
|
@ -13,6 +13,7 @@ use roc_module::symbol;
|
|||
use roc_module::symbol::Interns;
|
||||
use roc_mono::ir::{Proc, ProcLayout};
|
||||
use roc_mono::layout::LayoutIds;
|
||||
use roc_target::TargetInfo;
|
||||
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
|
||||
|
||||
// This is used by some code below which is currently commented out.
|
||||
|
@ -38,7 +39,7 @@ pub fn build_module<'a>(
|
|||
x86_64::X86_64FloatReg,
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64SystemV,
|
||||
>(env, interns);
|
||||
>(env, TargetInfo::default_x86_64(), interns);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
|
@ -55,7 +56,7 @@ pub fn build_module<'a>(
|
|||
x86_64::X86_64FloatReg,
|
||||
x86_64::X86_64Assembler,
|
||||
x86_64::X86_64SystemV,
|
||||
>(env, interns);
|
||||
>(env, TargetInfo::default_x86_64(), interns);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
|
@ -76,7 +77,7 @@ pub fn build_module<'a>(
|
|||
aarch64::AArch64FloatReg,
|
||||
aarch64::AArch64Assembler,
|
||||
aarch64::AArch64Call,
|
||||
>(env, interns);
|
||||
>(env, TargetInfo::default_aarch64(), interns);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
|
@ -93,7 +94,7 @@ pub fn build_module<'a>(
|
|||
aarch64::AArch64FloatReg,
|
||||
aarch64::AArch64Assembler,
|
||||
aarch64::AArch64Call,
|
||||
>(env, interns);
|
||||
>(env, TargetInfo::default_aarch64(), interns);
|
||||
build_object(
|
||||
procedures,
|
||||
backend,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/// Helpers for interacting with the zig that generates bitcode
|
||||
use crate::debug_info_init;
|
||||
use crate::llvm::build::{
|
||||
load_roc_value, struct_from_fields, Env, C_CALL_CONV, FAST_CALL_CONV, TAG_DATA_INDEX,
|
||||
complex_bitcast_check_size, load_roc_value, struct_from_fields, to_cc_return, CCReturn, Env,
|
||||
C_CALL_CONV, FAST_CALL_CONV, TAG_DATA_INDEX,
|
||||
};
|
||||
use crate::llvm::convert::basic_type_from_layout;
|
||||
use crate::llvm::refcounting::{
|
||||
|
@ -11,9 +12,12 @@ use inkwell::attributes::{Attribute, AttributeLoc};
|
|||
use inkwell::types::{BasicType, BasicTypeEnum};
|
||||
use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue};
|
||||
use inkwell::AddressSpace;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{LambdaSet, Layout, LayoutIds, UnionLayout};
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
|
@ -30,6 +34,38 @@ pub fn call_bitcode_fn<'a, 'ctx, 'env>(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn call_list_bitcode_fn<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
fn_name: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_help(env, args, fn_name)
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"LLVM error: Did not get return value from bitcode function {:?}",
|
||||
fn_name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn call_str_bitcode_fn<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
fn_name: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_help(env, args, fn_name)
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"LLVM error: Did not get return value from bitcode function {:?}",
|
||||
fn_name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn call_void_bitcode_fn<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
|
@ -60,6 +96,63 @@ fn call_bitcode_fn_help<'a, 'ctx, 'env>(
|
|||
call
|
||||
}
|
||||
|
||||
pub fn call_bitcode_fn_fixing_for_convention<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
return_layout: &Layout<'_>,
|
||||
fn_name: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
// Calling zig bitcode, so we must follow C calling conventions.
|
||||
let cc_return = to_cc_return(env, return_layout);
|
||||
match cc_return {
|
||||
CCReturn::Return => {
|
||||
// We'll get a return value
|
||||
call_bitcode_fn(env, args, fn_name)
|
||||
}
|
||||
CCReturn::ByPointer => {
|
||||
// We need to pass the return value by pointer.
|
||||
let roc_return_type = basic_type_from_layout(env, return_layout);
|
||||
|
||||
let cc_ptr_return_type = env
|
||||
.module
|
||||
.get_function(fn_name)
|
||||
.unwrap()
|
||||
.get_type()
|
||||
.get_param_types()[0]
|
||||
.into_pointer_type();
|
||||
let cc_return_type: BasicTypeEnum<'ctx> = cc_ptr_return_type
|
||||
.get_element_type()
|
||||
.try_into()
|
||||
.expect("Zig bitcode return type is not a basic type!");
|
||||
|
||||
let cc_return_value_ptr = env.builder.build_alloca(cc_return_type, "return_value");
|
||||
let fixed_args: Vec<BasicValueEnum<'ctx>> = [cc_return_value_ptr.into()]
|
||||
.iter()
|
||||
.chain(args)
|
||||
.copied()
|
||||
.collect();
|
||||
call_void_bitcode_fn(env, &fixed_args, fn_name);
|
||||
|
||||
let cc_return_value = env.builder.build_load(cc_return_value_ptr, "read_result");
|
||||
if roc_return_type.size_of() == cc_return_type.size_of() {
|
||||
cc_return_value
|
||||
} else {
|
||||
// We need to convert the C-callconv return type, which may be larger than the Roc
|
||||
// return type, into the Roc return type.
|
||||
complex_bitcast_check_size(
|
||||
env,
|
||||
cc_return_value,
|
||||
roc_return_type,
|
||||
"c_value_to_roc_value",
|
||||
)
|
||||
}
|
||||
}
|
||||
CCReturn::Void => {
|
||||
internal_error!("Tried to call valued bitcode function, but it has no return type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ARGUMENT_SYMBOLS: [Symbol; 8] = [
|
||||
Symbol::ARG_1,
|
||||
Symbol::ARG_2,
|
||||
|
@ -281,7 +374,9 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
|
|||
}
|
||||
|
||||
match closure_data_layout.runtime_representation() {
|
||||
Layout::Struct(&[]) => {
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
} => {
|
||||
// nothing to add
|
||||
}
|
||||
other => {
|
||||
|
@ -601,7 +696,9 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
|
|||
let default = [value1.into(), value2.into()];
|
||||
|
||||
let arguments_cast = match closure_data_layout.runtime_representation() {
|
||||
Layout::Struct(&[]) => {
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
} => {
|
||||
// nothing to add
|
||||
&default
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
|
||||
use crate::llvm::bitcode::{
|
||||
call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_void_bitcode_fn,
|
||||
};
|
||||
use crate::llvm::build_dict::{
|
||||
self, dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
|
||||
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
|
||||
|
@ -53,7 +55,7 @@ use morphic_lib::{
|
|||
CalleeSpecVar, FuncName, FuncSpec, FuncSpecSolutions, ModSolutions, UpdateMode, UpdateModeVar,
|
||||
};
|
||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName};
|
||||
use roc_builtins::{float_intrinsic, int_intrinsic};
|
||||
use roc_builtins::{float_intrinsic, llvm_int_intrinsic};
|
||||
use roc_collections::all::{ImMap, MutMap, MutSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::low_level::LowLevel;
|
||||
|
@ -609,14 +611,14 @@ static LLVM_SETJMP: &str = "llvm.eh.sjlj.setjmp";
|
|||
pub static LLVM_LONGJMP: &str = "llvm.eh.sjlj.longjmp";
|
||||
|
||||
const LLVM_ADD_WITH_OVERFLOW: IntrinsicName =
|
||||
int_intrinsic!("llvm.sadd.with.overflow", "llvm.uadd.with.overflow");
|
||||
llvm_int_intrinsic!("llvm.sadd.with.overflow", "llvm.uadd.with.overflow");
|
||||
const LLVM_SUB_WITH_OVERFLOW: IntrinsicName =
|
||||
int_intrinsic!("llvm.ssub.with.overflow", "llvm.usub.with.overflow");
|
||||
llvm_int_intrinsic!("llvm.ssub.with.overflow", "llvm.usub.with.overflow");
|
||||
const LLVM_MUL_WITH_OVERFLOW: IntrinsicName =
|
||||
int_intrinsic!("llvm.smul.with.overflow", "llvm.umul.with.overflow");
|
||||
llvm_int_intrinsic!("llvm.smul.with.overflow", "llvm.umul.with.overflow");
|
||||
|
||||
const LLVM_ADD_SATURATED: IntrinsicName = int_intrinsic!("llvm.sadd.sat", "llvm.uadd.sat");
|
||||
const LLVM_SUB_SATURATED: IntrinsicName = int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
|
||||
const LLVM_ADD_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.sadd.sat", "llvm.uadd.sat");
|
||||
const LLVM_SUB_SATURATED: IntrinsicName = llvm_int_intrinsic!("llvm.ssub.sat", "llvm.usub.sat");
|
||||
|
||||
fn add_intrinsic<'ctx>(
|
||||
module: &Module<'ctx>,
|
||||
|
@ -712,8 +714,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
|
|||
);
|
||||
|
||||
// NOTE fake layout; it is only used for debug prints
|
||||
let roc_main_fn =
|
||||
function_value_by_func_spec(env, *func_spec, symbol, &[], &Layout::Struct(&[]));
|
||||
let roc_main_fn = function_value_by_func_spec(env, *func_spec, symbol, &[], &Layout::UNIT);
|
||||
|
||||
let main_fn_name = "$Test.main";
|
||||
|
||||
|
@ -1186,8 +1187,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
|
||||
// extract field from a record
|
||||
match (value, layout) {
|
||||
(StructValue(argument), Layout::Struct(fields)) => {
|
||||
debug_assert!(!fields.is_empty());
|
||||
(StructValue(argument), Layout::Struct { field_layouts, .. }) => {
|
||||
debug_assert!(!field_layouts.is_empty());
|
||||
|
||||
let field_value = env
|
||||
.builder
|
||||
|
@ -1199,14 +1200,14 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let field_layout = fields[*index as usize];
|
||||
let field_layout = field_layouts[*index as usize];
|
||||
use_roc_value(env, field_layout, field_value, "struct_field_tag")
|
||||
}
|
||||
(
|
||||
PointerValue(argument),
|
||||
Layout::Union(UnionLayout::NonNullableUnwrapped(fields)),
|
||||
) => {
|
||||
let struct_layout = Layout::Struct(fields);
|
||||
let struct_layout = Layout::struct_no_name_order(fields);
|
||||
let struct_type = basic_type_from_layout(env, &struct_layout);
|
||||
|
||||
let cast_argument = env
|
||||
|
@ -1290,7 +1291,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
)
|
||||
}
|
||||
UnionLayout::NonNullableUnwrapped(field_layouts) => {
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
let struct_layout = Layout::struct_no_name_order(field_layouts);
|
||||
|
||||
let struct_type = basic_type_from_layout(env, &struct_layout);
|
||||
|
||||
|
@ -1339,7 +1340,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
debug_assert_ne!(*tag_id != 0, *nullable_id);
|
||||
|
||||
let field_layouts = other_fields;
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
let struct_layout = Layout::struct_no_name_order(field_layouts);
|
||||
|
||||
let struct_type = basic_type_from_layout(env, &struct_layout);
|
||||
|
||||
|
@ -2022,7 +2023,7 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
|
|||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
let struct_layout = Layout::struct_no_name_order(field_layouts);
|
||||
let struct_type = basic_type_from_layout(env, &struct_layout);
|
||||
|
||||
let wrapper_type = env
|
||||
|
@ -2921,7 +2922,7 @@ pub fn complex_bitcast<'ctx>(
|
|||
|
||||
/// Check the size of the input and output types. Pretending we have more bytes at a pointer than
|
||||
/// we actually do can lead to faulty optimizations and weird segfaults/crashes
|
||||
fn complex_bitcast_check_size<'a, 'ctx, 'env>(
|
||||
pub fn complex_bitcast_check_size<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
from_value: BasicValueEnum<'ctx>,
|
||||
to_type: BasicTypeEnum<'ctx>,
|
||||
|
@ -3520,7 +3521,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
|
|||
call_roc_function(
|
||||
env,
|
||||
roc_wrapper_function,
|
||||
&Layout::Struct(&[Layout::u64(), return_layout]),
|
||||
&Layout::struct_no_name_order(&[Layout::u64(), return_layout]),
|
||||
arguments_for_call,
|
||||
)
|
||||
};
|
||||
|
@ -3600,11 +3601,14 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
|
|||
|
||||
let param_types = Vec::from_iter_in(roc_function.get_type().get_param_types(), env.arena);
|
||||
|
||||
// drop the "return pointer" if it exists on the roc function
|
||||
let (params, param_types) = match (&roc_return, &cc_return) {
|
||||
// Drop the "return pointer" if it exists on the roc function
|
||||
// and the c function does not return via pointer
|
||||
let param_types = match (&roc_return, &cc_return) {
|
||||
(RocReturn::ByPointer, CCReturn::Return) => ¶m_types[1..],
|
||||
_ => ¶m_types,
|
||||
(RocReturn::ByPointer, CCReturn::Return) => (¶ms[..], ¶m_types[1..]),
|
||||
// Drop the return pointer the other way, if the C function returns by pointer but Roc
|
||||
// doesn't
|
||||
(RocReturn::Return, CCReturn::ByPointer) => (¶ms[1..], ¶m_types[..]),
|
||||
_ => (¶ms[..], ¶m_types[..]),
|
||||
};
|
||||
|
||||
debug_assert!(
|
||||
|
@ -3903,7 +3907,7 @@ fn roc_result_layout<'a>(
|
|||
) -> Layout<'a> {
|
||||
let elements = [Layout::u64(), Layout::usize(target_info), return_layout];
|
||||
|
||||
Layout::Struct(arena.alloc(elements))
|
||||
Layout::struct_no_name_order(arena.alloc(elements))
|
||||
}
|
||||
|
||||
fn roc_result_type<'a, 'ctx, 'env>(
|
||||
|
@ -4075,7 +4079,13 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
|
|||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
) -> (&'static str, FunctionValue<'ctx>) {
|
||||
let mod_solutions = build_procedures_help(env, opt_level, procedures, entry_point, None);
|
||||
let mod_solutions = build_procedures_help(
|
||||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
promote_to_main_function(env, mod_solutions, entry_point.symbol, entry_point.layout)
|
||||
}
|
||||
|
@ -5016,7 +5026,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
ListMapWithIndex { xs } => {
|
||||
// List.mapWithIndex : List before, (Nat, before -> after) -> List after
|
||||
// List.mapWithIndex : List before, (before, Nat -> after) -> List after
|
||||
let (list, list_layout) = load_symbol_and_layout(scope, xs);
|
||||
|
||||
let (function, closure, closure_layout) = function_details!();
|
||||
|
@ -5026,7 +5036,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
|||
Layout::Builtin(Builtin::List(element_layout)),
|
||||
Layout::Builtin(Builtin::List(result_layout)),
|
||||
) => {
|
||||
let argument_layouts = &[Layout::usize(env.target_info), **element_layout];
|
||||
let argument_layouts = &[**element_layout, Layout::usize(env.target_info)];
|
||||
|
||||
let roc_function_call = roc_function_call(
|
||||
env,
|
||||
|
@ -5357,7 +5367,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
let (string, _string_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let number_layout = match layout {
|
||||
Layout::Struct(fields) => fields[0], // TODO: why is it sometimes a struct?
|
||||
Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct?
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
|
@ -5486,13 +5496,13 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
list_single(env, arg, arg_layout)
|
||||
}
|
||||
ListRepeat => {
|
||||
// List.repeat : Int, elem -> List elem
|
||||
// List.repeat : elem, Nat -> List elem
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let list_len = load_symbol(scope, &args[0]).into_int_value();
|
||||
let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]);
|
||||
let (elem, elem_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
let list_len = load_symbol(scope, &args[1]).into_int_value();
|
||||
|
||||
list_repeat(env, layout_ids, list_len, elem, elem_layout)
|
||||
list_repeat(env, layout_ids, elem, elem_layout, list_len)
|
||||
}
|
||||
ListReverse => {
|
||||
// List.reverse : List elem -> List elem
|
||||
|
@ -5682,7 +5692,8 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
|
||||
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
|
||||
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin
|
||||
| NumToIntChecked => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
@ -5694,7 +5705,14 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
match arg_builtin {
|
||||
Int(int_width) => {
|
||||
let int_type = convert::int_type_from_int_width(env, *int_width);
|
||||
build_int_unary_op(env, arg.into_int_value(), int_type, op)
|
||||
build_int_unary_op(
|
||||
env,
|
||||
arg.into_int_value(),
|
||||
*int_width,
|
||||
int_type,
|
||||
op,
|
||||
layout,
|
||||
)
|
||||
}
|
||||
Float(float_width) => build_float_unary_op(
|
||||
env,
|
||||
|
@ -6188,7 +6206,7 @@ impl RocReturn {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CCReturn {
|
||||
pub enum CCReturn {
|
||||
/// Return as normal
|
||||
Return,
|
||||
/// require an extra argument, a pointer
|
||||
|
@ -6230,7 +6248,7 @@ impl CCReturn {
|
|||
}
|
||||
|
||||
/// According to the C ABI, how should we return a value with the given layout?
|
||||
fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
|
||||
pub fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
|
||||
let return_size = layout.stack_size(env.target_info);
|
||||
let pass_result_by_pointer = return_size > 2 * env.target_info.ptr_width() as u32;
|
||||
|
||||
|
@ -6924,8 +6942,10 @@ fn int_type_signed_min(int_type: IntType) -> IntValue {
|
|||
fn build_int_unary_op<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
arg: IntValue<'ctx>,
|
||||
int_type: IntType<'ctx>,
|
||||
arg_width: IntWidth,
|
||||
arg_int_type: IntType<'ctx>,
|
||||
op: LowLevel,
|
||||
return_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use roc_module::low_level::LowLevel::*;
|
||||
|
||||
|
@ -6934,22 +6954,98 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
|||
match op {
|
||||
NumNeg => {
|
||||
// integer abs overflows when applied to the minimum value of a signed type
|
||||
int_neg_raise_on_overflow(env, arg, int_type)
|
||||
int_neg_raise_on_overflow(env, arg, arg_int_type)
|
||||
}
|
||||
NumAbs => {
|
||||
// integer abs overflows when applied to the minimum value of a signed type
|
||||
int_abs_raise_on_overflow(env, arg, int_type)
|
||||
int_abs_raise_on_overflow(env, arg, arg_int_type)
|
||||
}
|
||||
NumToFloat => {
|
||||
// TODO: Handle different sized numbers
|
||||
// This is an Int, so we need to convert it.
|
||||
|
||||
let target_float_type = match return_layout {
|
||||
Layout::Builtin(Builtin::Float(float_width)) => {
|
||||
convert::float_type_from_float_width(env, *float_width)
|
||||
}
|
||||
_ => internal_error!("There can only be floats here!"),
|
||||
};
|
||||
|
||||
bd.build_cast(
|
||||
InstructionOpcode::SIToFP,
|
||||
arg,
|
||||
env.context.f64_type(),
|
||||
target_float_type,
|
||||
"i64_to_f64",
|
||||
)
|
||||
}
|
||||
NumToIntChecked => {
|
||||
// return_layout : Result N [ OutOfBounds ]* ~ { result: N, out_of_bounds: bool }
|
||||
|
||||
let target_int_width = match return_layout {
|
||||
Layout::Struct { field_layouts, .. } if field_layouts.len() == 2 => {
|
||||
debug_assert!(matches!(field_layouts[1], Layout::Builtin(Builtin::Bool)));
|
||||
match field_layouts[0] {
|
||||
Layout::Builtin(Builtin::Int(iw)) => iw,
|
||||
layout => internal_error!(
|
||||
"There can only be an int layout here, found {:?}!",
|
||||
layout
|
||||
),
|
||||
}
|
||||
}
|
||||
layout => internal_error!(
|
||||
"There can only be a result layout here, found {:?}!",
|
||||
layout
|
||||
),
|
||||
};
|
||||
|
||||
let arg_always_fits_in_target = (arg_width.stack_size() < target_int_width.stack_size()
|
||||
&& (
|
||||
// If the arg is unsigned, it will always fit in either a signed or unsigned
|
||||
// int of a larger width.
|
||||
!arg_width.is_signed()
|
||||
||
|
||||
// Otherwise if the arg is signed, it will always fit in a signed int of a
|
||||
// larger width.
|
||||
(target_int_width.is_signed() )
|
||||
) )
|
||||
|| // Or if the two types are the same, they trivially fit.
|
||||
arg_width == target_int_width;
|
||||
|
||||
if arg_always_fits_in_target {
|
||||
// This is guaranteed to succeed so we can just make it an int cast and let LLVM
|
||||
// optimize it away.
|
||||
let target_int_type = convert::int_type_from_int_width(env, target_int_width);
|
||||
let target_int_val: BasicValueEnum<'ctx> = env
|
||||
.builder
|
||||
.build_int_cast(arg, target_int_type, "int_cast")
|
||||
.into();
|
||||
|
||||
let return_type =
|
||||
convert::basic_type_from_layout(env, return_layout).into_struct_type();
|
||||
let r = return_type.const_zero();
|
||||
let r = bd
|
||||
.build_insert_value(r, target_int_val, 0, "converted_int")
|
||||
.unwrap();
|
||||
let r = bd
|
||||
.build_insert_value(r, env.context.bool_type().const_zero(), 1, "out_of_bounds")
|
||||
.unwrap();
|
||||
|
||||
r.into_struct_value().into()
|
||||
} else {
|
||||
let bitcode_fn = if !arg_width.is_signed() {
|
||||
// We are trying to convert from unsigned to signed/unsigned of same or lesser width, e.g.
|
||||
// u16 -> i16, u16 -> i8, or u16 -> u8. We only need to check that the argument
|
||||
// value fits in the MAX target type value.
|
||||
&bitcode::NUM_INT_TO_INT_CHECKING_MAX[target_int_width][arg_width]
|
||||
} else {
|
||||
// We are trying to convert from signed to signed/unsigned of same or lesser width, e.g.
|
||||
// i16 -> u16, i16 -> i8, or i16 -> u8. We need to check that the argument value fits in
|
||||
// the MAX and MIN target type.
|
||||
&bitcode::NUM_INT_TO_INT_CHECKING_MAX_AND_MIN[target_int_width][arg_width]
|
||||
};
|
||||
|
||||
call_bitcode_fn_fixing_for_convention(env, &[arg.into()], return_layout, bitcode_fn)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Unrecognized int unary operation: {:?}", op);
|
||||
}
|
||||
|
|
|
@ -735,8 +735,7 @@ pub fn set_from_list<'a, 'ctx, 'env>(
|
|||
|
||||
let result_alloca = builder.build_alloca(zig_dict_type(env), "result_alloca");
|
||||
|
||||
let alignment =
|
||||
Alignment::from_key_value_layout(key_layout, &Layout::Struct(&[]), env.target_info);
|
||||
let alignment = Alignment::from_key_value_layout(key_layout, &Layout::UNIT, env.target_info);
|
||||
let alignment_iv = alignment.as_int_value(env.context);
|
||||
|
||||
let hash_fn = build_hash_wrapper(env, layout_ids, key_layout);
|
||||
|
|
|
@ -50,10 +50,10 @@ fn build_hash_layout<'a, 'ctx, 'env>(
|
|||
hash_builtin(env, layout_ids, seed, val, layout, builtin, when_recursive)
|
||||
}
|
||||
|
||||
Layout::Struct(fields) => build_hash_struct(
|
||||
Layout::Struct { field_layouts, .. } => build_hash_struct(
|
||||
env,
|
||||
layout_ids,
|
||||
fields,
|
||||
field_layouts,
|
||||
when_recursive,
|
||||
seed,
|
||||
val.into_struct_value(),
|
||||
|
@ -166,7 +166,7 @@ fn build_hash_struct<'a, 'ctx, 'env>(
|
|||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
let struct_layout = Layout::struct_no_name_order(field_layouts);
|
||||
|
||||
let symbol = Symbol::GENERIC_HASH;
|
||||
let fn_name = layout_ids
|
||||
|
@ -248,7 +248,7 @@ fn hash_struct<'a, 'ctx, 'env>(
|
|||
) -> IntValue<'ctx> {
|
||||
let ptr_bytes = env.target_info;
|
||||
|
||||
let layout = Layout::Struct(field_layouts);
|
||||
let layout = Layout::struct_no_name_order(field_layouts);
|
||||
|
||||
// Optimization: if the bit representation of equal values is the same
|
||||
// just hash the bits. Caveat here is tags: e.g. `Nothing` in `Just a`
|
||||
|
@ -818,7 +818,7 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
|
|||
.build_struct_gep(wrapper_ptr, TAG_DATA_INDEX, "get_tag_data")
|
||||
.unwrap();
|
||||
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
let struct_layout = Layout::struct_no_name_order(field_layouts);
|
||||
let struct_type = basic_type_from_layout(env, &struct_layout);
|
||||
let struct_ptr = env
|
||||
.builder
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
use crate::llvm::bitcode::{
|
||||
build_dec_wrapper, build_eq_wrapper, build_has_tag_id, build_inc_n_wrapper, build_inc_wrapper,
|
||||
call_bitcode_fn, call_void_bitcode_fn,
|
||||
call_bitcode_fn, call_list_bitcode_fn, call_void_bitcode_fn,
|
||||
};
|
||||
use crate::llvm::build::{
|
||||
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, RocFunctionCall,
|
||||
|
@ -29,29 +29,6 @@ pub fn pass_update_mode<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
fn list_returned_from_zig<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
output: BasicValueEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
// per the C ABI, our list objects are passed between functions as an i128/i64
|
||||
complex_bitcast(
|
||||
env.builder,
|
||||
output,
|
||||
super::convert::zig_list_type(env).into(),
|
||||
"from_str_list_int",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn call_bitcode_fn_returns_list<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
args: &[BasicValueEnum<'ctx>],
|
||||
fn_name: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let value = call_bitcode_fn(env, args, fn_name);
|
||||
|
||||
list_returned_from_zig(env, value)
|
||||
}
|
||||
|
||||
fn pass_element_as_opaque<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
element: BasicValueEnum<'ctx>,
|
||||
|
@ -108,7 +85,7 @@ pub fn list_single<'a, 'ctx, 'env>(
|
|||
element: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
env.alignment_intvalue(element_layout),
|
||||
|
@ -119,17 +96,17 @@ pub fn list_single<'a, 'ctx, 'env>(
|
|||
)
|
||||
}
|
||||
|
||||
/// List.repeat : Int, elem -> List elem
|
||||
/// List.repeat : elem, Nat -> List elem
|
||||
pub fn list_repeat<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
list_len: IntValue<'ctx>,
|
||||
element: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
list_len: IntValue<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let inc_element_fn = build_inc_n_wrapper(env, layout_ids, element_layout);
|
||||
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
list_len.into(),
|
||||
|
@ -148,7 +125,7 @@ pub fn list_join<'a, 'ctx, 'env>(
|
|||
outer_list: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, outer_list),
|
||||
|
@ -166,7 +143,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
|
|||
element_layout: &Layout<'a>,
|
||||
update_mode: UpdateMode,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list),
|
||||
|
@ -213,7 +190,7 @@ pub fn list_append<'a, 'ctx, 'env>(
|
|||
element_layout: &Layout<'a>,
|
||||
update_mode: UpdateMode,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
|
@ -233,7 +210,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
|
|||
element: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
|
@ -254,7 +231,7 @@ pub fn list_swap<'a, 'ctx, 'env>(
|
|||
element_layout: &Layout<'a>,
|
||||
update_mode: UpdateMode,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
|
@ -278,7 +255,7 @@ pub fn list_sublist<'a, 'ctx, 'env>(
|
|||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
|
@ -301,7 +278,7 @@ pub fn list_drop_at<'a, 'ctx, 'env>(
|
|||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
|
@ -543,7 +520,7 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
|
|||
let inc_element_fn = build_inc_wrapper(env, layout_ids, element_layout);
|
||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list),
|
||||
|
@ -672,7 +649,7 @@ pub fn list_sort_with<'a, 'ctx, 'env>(
|
|||
list: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list),
|
||||
|
@ -687,7 +664,7 @@ pub fn list_sort_with<'a, 'ctx, 'env>(
|
|||
)
|
||||
}
|
||||
|
||||
/// List.mapWithIndex : List before, (Nat, before -> after) -> List after
|
||||
/// List.mapWithIndex : List before, (before, Nat -> after) -> List after
|
||||
pub fn list_map_with_index<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
roc_function_call: RocFunctionCall<'ctx>,
|
||||
|
@ -695,7 +672,7 @@ pub fn list_map_with_index<'a, 'ctx, 'env>(
|
|||
element_layout: &Layout<'a>,
|
||||
return_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list),
|
||||
|
@ -719,7 +696,7 @@ pub fn list_map<'a, 'ctx, 'env>(
|
|||
element_layout: &Layout<'a>,
|
||||
return_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list),
|
||||
|
@ -784,7 +761,7 @@ pub fn list_map3<'a, 'ctx, 'env>(
|
|||
let dec_b = build_dec_wrapper(env, layout_ids, element2_layout);
|
||||
let dec_c = build_dec_wrapper(env, layout_ids, element3_layout);
|
||||
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list1),
|
||||
|
@ -826,7 +803,7 @@ pub fn list_map4<'a, 'ctx, 'env>(
|
|||
let dec_c = build_dec_wrapper(env, layout_ids, element3_layout);
|
||||
let dec_d = build_dec_wrapper(env, layout_ids, element4_layout);
|
||||
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list1),
|
||||
|
@ -859,7 +836,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
|
|||
second_list: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
call_list_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, first_list),
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
|
||||
use crate::llvm::build::{complex_bitcast, Env, Scope};
|
||||
use crate::llvm::build_list::{
|
||||
allocate_list, call_bitcode_fn_returns_list, pass_update_mode, store_list,
|
||||
use crate::llvm::bitcode::{
|
||||
call_bitcode_fn, call_list_bitcode_fn, call_str_bitcode_fn, call_void_bitcode_fn,
|
||||
};
|
||||
use crate::llvm::build::{complex_bitcast, Env, Scope};
|
||||
use crate::llvm::build_list::{allocate_list, pass_update_mode, store_list};
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
|
||||
use inkwell::AddressSpace;
|
||||
|
@ -25,7 +25,7 @@ pub fn str_repeat<'a, 'ctx, 'env>(
|
|||
) -> BasicValueEnum<'ctx> {
|
||||
let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol);
|
||||
let count = load_symbol(scope, &count_symbol);
|
||||
call_bitcode_fn(env, &[str_c_abi.into(), count], bitcode::STR_REPEAT)
|
||||
call_str_bitcode_fn(env, &[str_c_abi.into(), count], bitcode::STR_REPEAT)
|
||||
}
|
||||
|
||||
/// Str.split : Str, Str -> List Str
|
||||
|
@ -40,7 +40,7 @@ pub fn str_split<'a, 'ctx, 'env>(
|
|||
let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol);
|
||||
let delim_c_abi = str_symbol_to_c_abi(env, scope, delimiter_symbol);
|
||||
|
||||
let segment_count = call_bitcode_fn(
|
||||
let segment_count = call_list_bitcode_fn(
|
||||
env,
|
||||
&[str_c_abi.into(), delim_c_abi.into()],
|
||||
bitcode::STR_COUNT_SEGMENTS,
|
||||
|
@ -140,7 +140,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
|
|||
let str1_c_abi = str_symbol_to_c_abi(env, scope, str1_symbol);
|
||||
let str2_c_abi = str_symbol_to_c_abi(env, scope, str2_symbol);
|
||||
|
||||
call_bitcode_fn(
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[str1_c_abi.into(), str2_c_abi.into()],
|
||||
bitcode::STR_CONCAT,
|
||||
|
@ -159,7 +159,7 @@ pub fn str_join_with<'a, 'ctx, 'env>(
|
|||
let list_i128 = str_symbol_to_c_abi(env, scope, list_symbol);
|
||||
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
|
||||
|
||||
call_bitcode_fn(
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[list_i128.into(), str_i128.into()],
|
||||
bitcode::STR_JOIN_WITH,
|
||||
|
@ -255,7 +255,7 @@ pub fn str_trim<'a, 'ctx, 'env>(
|
|||
str_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
|
||||
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM)
|
||||
call_str_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM)
|
||||
}
|
||||
|
||||
/// Str.trimLeft : Str -> Str
|
||||
|
@ -265,7 +265,7 @@ pub fn str_trim_left<'a, 'ctx, 'env>(
|
|||
str_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
|
||||
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_LEFT)
|
||||
call_str_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_LEFT)
|
||||
}
|
||||
|
||||
/// Str.trimRight : Str -> Str
|
||||
|
@ -275,7 +275,7 @@ pub fn str_trim_right<'a, 'ctx, 'env>(
|
|||
str_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
|
||||
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_RIGHT)
|
||||
call_str_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_RIGHT)
|
||||
}
|
||||
|
||||
/// Str.fromInt : Int -> Str
|
||||
|
@ -284,7 +284,7 @@ pub fn str_from_int<'a, 'ctx, 'env>(
|
|||
value: IntValue<'ctx>,
|
||||
int_width: IntWidth,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn(env, &[value.into()], &bitcode::STR_FROM_INT[int_width])
|
||||
call_str_bitcode_fn(env, &[value.into()], &bitcode::STR_FROM_INT[int_width])
|
||||
}
|
||||
|
||||
/// Str.toUtf8 : Str -> List U8
|
||||
|
@ -299,7 +299,7 @@ pub fn str_to_utf8<'a, 'ctx, 'env>(
|
|||
"to_utf8",
|
||||
);
|
||||
|
||||
call_bitcode_fn_returns_list(env, &[string], bitcode::STR_TO_UTF8)
|
||||
call_list_bitcode_fn(env, &[string], bitcode::STR_TO_UTF8)
|
||||
}
|
||||
|
||||
fn decode_from_utf8_result<'a, 'ctx, 'env>(
|
||||
|
@ -411,7 +411,7 @@ pub fn str_from_float<'a, 'ctx, 'env>(
|
|||
) -> BasicValueEnum<'ctx> {
|
||||
let float = load_symbol(scope, &int_symbol);
|
||||
|
||||
call_bitcode_fn(env, &[float], bitcode::STR_FROM_FLOAT)
|
||||
call_str_bitcode_fn(env, &[float], bitcode::STR_FROM_FLOAT)
|
||||
}
|
||||
|
||||
/// Str.equal : Str, Str -> Bool
|
||||
|
|
|
@ -161,10 +161,10 @@ fn build_eq<'a, 'ctx, 'env>(
|
|||
build_eq_builtin(env, layout_ids, lhs_val, rhs_val, builtin, when_recursive)
|
||||
}
|
||||
|
||||
Layout::Struct(fields) => build_struct_eq(
|
||||
Layout::Struct { field_layouts, .. } => build_struct_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
fields,
|
||||
field_layouts,
|
||||
when_recursive,
|
||||
lhs_val.into_struct_value(),
|
||||
rhs_val.into_struct_value(),
|
||||
|
@ -330,11 +330,11 @@ fn build_neq<'a, 'ctx, 'env>(
|
|||
build_neq_builtin(env, layout_ids, lhs_val, rhs_val, builtin, when_recursive)
|
||||
}
|
||||
|
||||
Layout::Struct(fields) => {
|
||||
Layout::Struct { field_layouts, .. } => {
|
||||
let is_equal = build_struct_eq(
|
||||
env,
|
||||
layout_ids,
|
||||
fields,
|
||||
field_layouts,
|
||||
when_recursive,
|
||||
lhs_val.into_struct_value(),
|
||||
rhs_val.into_struct_value(),
|
||||
|
@ -587,7 +587,7 @@ fn build_struct_eq<'a, 'ctx, 'env>(
|
|||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
let struct_layout = Layout::struct_no_name_order(field_layouts);
|
||||
|
||||
let symbol = Symbol::GENERIC_EQ;
|
||||
let fn_name = layout_ids
|
||||
|
@ -1208,7 +1208,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
|
|||
tag1: PointerValue<'ctx>,
|
||||
tag2: PointerValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
let struct_layout = Layout::struct_no_name_order(field_layouts);
|
||||
|
||||
let wrapper_type = basic_type_from_layout(env, &struct_layout);
|
||||
debug_assert!(wrapper_type.is_struct_type());
|
||||
|
|
|
@ -28,7 +28,10 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
|||
use Layout::*;
|
||||
|
||||
match layout {
|
||||
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
|
||||
Struct {
|
||||
field_layouts: sorted_fields,
|
||||
..
|
||||
} => basic_type_from_record(env, sorted_fields),
|
||||
LambdaSet(lambda_set) => basic_type_from_layout(env, &lambda_set.runtime_representation()),
|
||||
Union(union_layout) => {
|
||||
use UnionLayout::*;
|
||||
|
@ -86,7 +89,10 @@ pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
|
|||
use Layout::*;
|
||||
|
||||
match layout {
|
||||
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
|
||||
Struct {
|
||||
field_layouts: sorted_fields,
|
||||
..
|
||||
} => basic_type_from_record(env, sorted_fields),
|
||||
LambdaSet(lambda_set) => {
|
||||
basic_type_from_layout_1(env, &lambda_set.runtime_representation())
|
||||
}
|
||||
|
|
|
@ -280,7 +280,7 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
|
|||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let layout = Layout::Struct(layouts);
|
||||
let layout = Layout::struct_no_name_order(layouts);
|
||||
|
||||
let (_, fn_name) = function_name_from_mode(
|
||||
layout_ids,
|
||||
|
@ -440,7 +440,7 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
|
|||
}
|
||||
Set(element_layout) => {
|
||||
let key_layout = element_layout;
|
||||
let value_layout = &Layout::Struct(&[]);
|
||||
let value_layout = &Layout::UNIT;
|
||||
|
||||
let function = modify_refcount_dict(
|
||||
env,
|
||||
|
@ -619,8 +619,9 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
Struct(layouts) => {
|
||||
let function = modify_refcount_struct(env, layout_ids, layouts, mode, when_recursive);
|
||||
Struct { field_layouts, .. } => {
|
||||
let function =
|
||||
modify_refcount_struct(env, layout_ids, field_layouts, mode, when_recursive);
|
||||
|
||||
Some(function)
|
||||
}
|
||||
|
@ -1312,7 +1313,8 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
|||
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
|
||||
let wrapper_type =
|
||||
basic_type_from_layout(env, &Layout::struct_no_name_order(field_layouts));
|
||||
|
||||
// cast the opaque pointer to a pointer of the correct shape
|
||||
let struct_ptr = env
|
||||
|
@ -1720,7 +1722,8 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
|||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
|
||||
let wrapper_type =
|
||||
basic_type_from_layout(env, &Layout::struct_no_name_order(field_layouts));
|
||||
|
||||
debug_assert!(wrapper_type.is_struct_type());
|
||||
let opaque_tag_data_ptr = env
|
||||
|
|
|
@ -44,7 +44,7 @@ pub struct WasmBackend<'a> {
|
|||
next_constant_addr: u32,
|
||||
fn_index_offset: u32,
|
||||
called_preload_fns: Vec<'a, u32>,
|
||||
proc_symbols: Vec<'a, (Symbol, u32)>,
|
||||
proc_lookup: Vec<'a, (Symbol, ProcLayout<'a>, u32)>,
|
||||
helper_proc_gen: CodeGenHelp<'a>,
|
||||
|
||||
// Function-level data
|
||||
|
@ -62,7 +62,7 @@ impl<'a> WasmBackend<'a> {
|
|||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
layout_ids: LayoutIds<'a>,
|
||||
proc_symbols: Vec<'a, (Symbol, u32)>,
|
||||
proc_lookup: Vec<'a, (Symbol, ProcLayout<'a>, u32)>,
|
||||
mut module: WasmModule<'a>,
|
||||
fn_index_offset: u32,
|
||||
helper_proc_gen: CodeGenHelp<'a>,
|
||||
|
@ -89,7 +89,7 @@ impl<'a> WasmBackend<'a> {
|
|||
next_constant_addr: CONST_SEGMENT_BASE_ADDR,
|
||||
fn_index_offset,
|
||||
called_preload_fns: Vec::with_capacity_in(2, env.arena),
|
||||
proc_symbols,
|
||||
proc_lookup,
|
||||
helper_proc_gen,
|
||||
|
||||
// Function-level data
|
||||
|
@ -106,7 +106,7 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
fn register_helper_proc(&mut self, new_proc_info: (Symbol, ProcLayout<'a>)) {
|
||||
let (new_proc_sym, new_proc_layout) = new_proc_info;
|
||||
let wasm_fn_index = self.proc_symbols.len() as u32;
|
||||
let wasm_fn_index = self.proc_lookup.len() as u32;
|
||||
let linker_sym_index = self.module.linking.symbol_table.len() as u32;
|
||||
|
||||
let name = self
|
||||
|
@ -114,7 +114,8 @@ impl<'a> WasmBackend<'a> {
|
|||
.get_toplevel(new_proc_sym, &new_proc_layout)
|
||||
.to_symbol_string(new_proc_sym, self.interns);
|
||||
|
||||
self.proc_symbols.push((new_proc_sym, linker_sym_index));
|
||||
self.proc_lookup
|
||||
.push((new_proc_sym, new_proc_layout, linker_sym_index));
|
||||
let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined {
|
||||
flags: 0,
|
||||
index: wasm_fn_index,
|
||||
|
@ -742,8 +743,24 @@ impl<'a> WasmBackend<'a> {
|
|||
ret_storage: &StoredValue,
|
||||
) {
|
||||
match call_type {
|
||||
CallType::ByName { name: func_sym, .. } => {
|
||||
self.expr_call_by_name(*func_sym, arguments, ret_sym, ret_layout, ret_storage)
|
||||
CallType::ByName {
|
||||
name: func_sym,
|
||||
arg_layouts,
|
||||
ret_layout: result,
|
||||
..
|
||||
} => {
|
||||
let proc_layout = ProcLayout {
|
||||
arguments: arg_layouts,
|
||||
result: **result,
|
||||
};
|
||||
self.expr_call_by_name(
|
||||
*func_sym,
|
||||
&proc_layout,
|
||||
arguments,
|
||||
ret_sym,
|
||||
ret_layout,
|
||||
ret_storage,
|
||||
)
|
||||
}
|
||||
CallType::LowLevel { op: lowlevel, .. } => {
|
||||
self.expr_call_low_level(*lowlevel, arguments, ret_sym, ret_layout, ret_storage)
|
||||
|
@ -756,6 +773,7 @@ impl<'a> WasmBackend<'a> {
|
|||
fn expr_call_by_name(
|
||||
&mut self,
|
||||
func_sym: Symbol,
|
||||
proc_layout: &ProcLayout<'a>,
|
||||
arguments: &'a [Symbol],
|
||||
ret_sym: Symbol,
|
||||
ret_layout: &Layout<'a>,
|
||||
|
@ -779,9 +797,10 @@ impl<'a> WasmBackend<'a> {
|
|||
CallConv::C,
|
||||
);
|
||||
|
||||
for (roc_proc_index, (ir_sym, linker_sym_index)) in self.proc_symbols.iter().enumerate() {
|
||||
let iter = self.proc_lookup.iter().enumerate();
|
||||
for (roc_proc_index, (ir_sym, pl, linker_sym_index)) in iter {
|
||||
if *ir_sym == func_sym && pl == proc_layout {
|
||||
let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32;
|
||||
if *ir_sym == func_sym {
|
||||
let num_wasm_args = param_types.len();
|
||||
let has_return_val = ret_type.is_some();
|
||||
self.code_builder.call(
|
||||
|
@ -795,9 +814,10 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
|
||||
internal_error!(
|
||||
"Could not find procedure {:?}\nKnown procedures: {:?}",
|
||||
"Could not find procedure {:?} with proc_layout {:?}\nKnown procedures: {:#?}",
|
||||
func_sym,
|
||||
self.proc_symbols
|
||||
proc_layout,
|
||||
self.proc_lookup
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -884,7 +904,7 @@ impl<'a> WasmBackend<'a> {
|
|||
storage: &StoredValue,
|
||||
fields: &'a [Symbol],
|
||||
) {
|
||||
if matches!(layout, Layout::Struct(_)) {
|
||||
if matches!(layout, Layout::Struct { .. }) {
|
||||
match storage {
|
||||
StoredValue::StackMemory { location, size, .. } => {
|
||||
if *size > 0 {
|
||||
|
|
|
@ -88,7 +88,7 @@ impl WasmLayout {
|
|||
},
|
||||
|
||||
Layout::Builtin(Str | Dict(_, _) | Set(_) | List(_))
|
||||
| Layout::Struct(_)
|
||||
| Layout::Struct { .. }
|
||||
| Layout::LambdaSet(_)
|
||||
| Layout::Union(NonRecursive(_)) => Self::StackMemory {
|
||||
size,
|
||||
|
|
|
@ -4,6 +4,10 @@ mod low_level;
|
|||
mod storage;
|
||||
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 roc_collections::all::{MutMap, MutSet};
|
||||
|
@ -43,25 +47,25 @@ pub struct Env<'a> {
|
|||
pub exposed_to_host: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
/// Entry point for production
|
||||
/// Entry point for Roc CLI
|
||||
pub fn build_module<'a>(
|
||||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
preload_bytes: &[u8],
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<std::vec::Vec<u8>, String> {
|
||||
) -> std::vec::Vec<u8> {
|
||||
let (mut wasm_module, called_preload_fns, _) =
|
||||
build_module_without_test_wrapper(env, interns, preload_bytes, procedures);
|
||||
build_module_without_wrapper(env, interns, preload_bytes, procedures);
|
||||
|
||||
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);
|
||||
Ok(buffer)
|
||||
buffer
|
||||
}
|
||||
|
||||
/// Entry point for integration tests (test_gen)
|
||||
pub fn build_module_without_test_wrapper<'a>(
|
||||
/// Entry point for REPL (repl_wasm) and integration tests (test_gen)
|
||||
pub fn build_module_without_wrapper<'a>(
|
||||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
preload_bytes: &[u8],
|
||||
|
@ -69,7 +73,7 @@ pub fn build_module_without_test_wrapper<'a>(
|
|||
) -> (WasmModule<'a>, Vec<'a, u32>, u32) {
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
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_lookup = 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 maybe_main_fn_index = None;
|
||||
|
@ -77,7 +81,7 @@ pub fn build_module_without_test_wrapper<'a>(
|
|||
// Collect the symbols & names for the procedures,
|
||||
// and filter out procs we're going to inline
|
||||
let mut fn_index: u32 = 0;
|
||||
for ((sym, layout), proc) in procedures.into_iter() {
|
||||
for ((sym, proc_layout), proc) in procedures.into_iter() {
|
||||
if matches!(
|
||||
LowLevelWrapperType::from_symbol(sym),
|
||||
LowLevelWrapperType::CanBeReplacedBy(_)
|
||||
|
@ -87,7 +91,7 @@ pub fn build_module_without_test_wrapper<'a>(
|
|||
procs.push(proc);
|
||||
|
||||
let fn_name = layout_ids
|
||||
.get_toplevel(sym, &layout)
|
||||
.get_toplevel(sym, &proc_layout)
|
||||
.to_symbol_string(sym, interns);
|
||||
|
||||
if env.exposed_to_host.contains(&sym) {
|
||||
|
@ -100,7 +104,10 @@ pub fn build_module_without_test_wrapper<'a>(
|
|||
}
|
||||
|
||||
let linker_sym = SymInfo::for_function(fn_index, fn_name);
|
||||
proc_symbols.push((sym, linker_symbols.len() as u32));
|
||||
let linker_sym_index = linker_symbols.len() as u32;
|
||||
|
||||
// linker_sym_index is redundant for these procs from user code, but needed for generated helpers!
|
||||
proc_lookup.push((sym, proc_layout, linker_sym_index));
|
||||
linker_symbols.push(linker_sym);
|
||||
|
||||
fn_index += 1;
|
||||
|
@ -117,7 +124,7 @@ pub fn build_module_without_test_wrapper<'a>(
|
|||
env,
|
||||
interns,
|
||||
layout_ids,
|
||||
proc_symbols,
|
||||
proc_lookup,
|
||||
initial_module,
|
||||
fn_index_offset,
|
||||
CodeGenHelp::new(env.arena, TargetInfo::default_wasm32(), env.module_id),
|
||||
|
|
|
@ -212,7 +212,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
}
|
||||
StrToNum => {
|
||||
let number_layout = match self.ret_layout {
|
||||
Layout::Struct(fields) => fields[0],
|
||||
Layout::Struct { field_layouts, .. } => field_layouts[0],
|
||||
_ => {
|
||||
internal_error!("Unexpected mono layout {:?} for StrToNum", self.ret_layout)
|
||||
}
|
||||
|
@ -655,6 +655,9 @@ impl<'a> LowLevelCall<'a> {
|
|||
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
|
||||
}
|
||||
}
|
||||
NumToIntChecked => {
|
||||
todo!()
|
||||
}
|
||||
And => {
|
||||
self.load_args(backend);
|
||||
backend.code_builder.i32_and();
|
||||
|
@ -708,7 +711,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
|
||||
// Empty record is always equal to empty record.
|
||||
// There are no runtime arguments to check, so just emit true or false.
|
||||
Layout::Struct(fields) if fields.is_empty() => {
|
||||
Layout::Struct { field_layouts, .. } if field_layouts.is_empty() => {
|
||||
backend.code_builder.i32_const(!invert_result as i32);
|
||||
}
|
||||
|
||||
|
@ -719,7 +722,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
}
|
||||
|
||||
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_))
|
||||
| Layout::Struct(_)
|
||||
| Layout::Struct { .. }
|
||||
| Layout::Union(_)
|
||||
| Layout::LambdaSet(_) => {
|
||||
// Don't want Zig calling convention here, we're calling internal Roc functions
|
||||
|
|
234
compiler/gen_wasm/src/wasm32_result.rs
Normal file
234
compiler/gen_wasm/src/wasm32_result.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
78
compiler/gen_wasm/src/wasm32_sized.rs
Normal file
78
compiler/gen_wasm/src/wasm32_sized.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use roc_std::{RocDec, RocList, RocOrder, RocStr};
|
||||
|
||||
pub trait Wasm32Sized: Sized {
|
||||
const SIZE_OF_WASM: usize;
|
||||
const ALIGN_OF_WASM: usize;
|
||||
const ACTUAL_WIDTH: usize = if (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM) == 0 {
|
||||
Self::SIZE_OF_WASM
|
||||
} else {
|
||||
Self::SIZE_OF_WASM + (Self::ALIGN_OF_WASM - (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM))
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! wasm32_sized_primitive {
|
||||
($($type_name:ident ,)+) => {
|
||||
$(
|
||||
impl Wasm32Sized for $type_name {
|
||||
const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>();
|
||||
const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>();
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
wasm32_sized_primitive!(
|
||||
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder,
|
||||
);
|
||||
|
||||
impl Wasm32Sized for () {
|
||||
const SIZE_OF_WASM: usize = 0;
|
||||
const ALIGN_OF_WASM: usize = 0;
|
||||
}
|
||||
|
||||
impl Wasm32Sized for RocStr {
|
||||
const SIZE_OF_WASM: usize = 8;
|
||||
const ALIGN_OF_WASM: usize = 4;
|
||||
}
|
||||
|
||||
impl<T: Wasm32Sized> Wasm32Sized for RocList<T> {
|
||||
const SIZE_OF_WASM: usize = 8;
|
||||
const ALIGN_OF_WASM: usize = 4;
|
||||
}
|
||||
|
||||
impl<T: Wasm32Sized> Wasm32Sized for &'_ T {
|
||||
const SIZE_OF_WASM: usize = 4;
|
||||
const ALIGN_OF_WASM: usize = 4;
|
||||
}
|
||||
|
||||
impl<T: Wasm32Sized, const N: usize> Wasm32Sized for [T; N] {
|
||||
const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM;
|
||||
const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM;
|
||||
}
|
||||
|
||||
impl Wasm32Sized for usize {
|
||||
const SIZE_OF_WASM: usize = 4;
|
||||
const ALIGN_OF_WASM: usize = 4;
|
||||
}
|
||||
|
||||
impl<T: Wasm32Sized, U: Wasm32Sized> Wasm32Sized for (T, U) {
|
||||
const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM;
|
||||
const ALIGN_OF_WASM: usize = max2(T::SIZE_OF_WASM, U::SIZE_OF_WASM);
|
||||
}
|
||||
|
||||
impl<T: Wasm32Sized, U: Wasm32Sized, V: Wasm32Sized> Wasm32Sized for (T, U, V) {
|
||||
const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM;
|
||||
const ALIGN_OF_WASM: usize = max3(T::SIZE_OF_WASM, U::SIZE_OF_WASM, V::SIZE_OF_WASM);
|
||||
}
|
||||
|
||||
const fn max2(a: usize, b: usize) -> usize {
|
||||
if a > b {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
const fn max3(a: usize, b: usize, c: usize) -> usize {
|
||||
max2(max2(a, b), c)
|
||||
}
|
|
@ -311,7 +311,7 @@ impl Clone for IdentStr {
|
|||
|
||||
impl Drop for IdentStr {
|
||||
fn drop(&mut self) {
|
||||
if !self.is_small_str() {
|
||||
if !self.is_empty() && !self.is_small_str() {
|
||||
unsafe {
|
||||
let align = mem::align_of::<u8>();
|
||||
let layout = Layout::from_size_align_unchecked(self.length, align);
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
|
@ -23,7 +24,7 @@ roc_reporting = { path = "../../reporting" }
|
|||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
parking_lot = { version = "0.11.2", features = ["deadlock_detection"] }
|
||||
parking_lot = { version = "0.11.2" }
|
||||
crossbeam = "0.8.1"
|
||||
num_cpus = "1.13.0"
|
||||
|
||||
|
@ -32,3 +33,4 @@ tempfile = "3.2.0"
|
|||
pretty_assertions = "1.0.0"
|
||||
maplit = "1.0.2"
|
||||
indoc = "1.0.3"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
|
|
|
@ -7,7 +7,7 @@ use roc_can::scope::Scope;
|
|||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::IdentIds;
|
||||
use roc_parse::ast::CommentOrNewline;
|
||||
use roc_parse::ast::{self, AliasHeader};
|
||||
use roc_parse::ast::{self, TypeHeader};
|
||||
use roc_parse::ast::{AssignedField, Def};
|
||||
use roc_region::all::Loc;
|
||||
|
||||
|
@ -206,7 +206,7 @@ fn generate_entry_doc<'a>(
|
|||
},
|
||||
|
||||
Def::Alias {
|
||||
header: AliasHeader { name, vars },
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => {
|
||||
let mut type_vars = Vec::new();
|
||||
|
@ -228,6 +228,29 @@ fn generate_entry_doc<'a>(
|
|||
(acc, None)
|
||||
}
|
||||
|
||||
Def::Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
..
|
||||
} => {
|
||||
let mut type_vars = Vec::new();
|
||||
|
||||
for var in vars.iter() {
|
||||
if let Pattern::Identifier(ident_name) = var.value {
|
||||
type_vars.push(ident_name.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let doc_def = DocDef {
|
||||
name: name.value.to_string(),
|
||||
type_annotation: TypeAnnotation::NoTypeAnn,
|
||||
type_vars,
|
||||
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
|
||||
};
|
||||
acc.push(DocEntry::DocDef(doc_def));
|
||||
|
||||
(acc, None)
|
||||
}
|
||||
|
||||
Def::Body(_, _) => (acc, None),
|
||||
|
||||
Def::Expect(c) => todo!("documentation for tests {:?}", c),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,3 +3,7 @@
|
|||
#![allow(clippy::large_enum_variant)]
|
||||
pub mod docs;
|
||||
pub mod file;
|
||||
mod work;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
mod wasm_system_time;
|
||||
|
|
42
compiler/load/src/wasm_system_time.rs
Normal file
42
compiler/load/src/wasm_system_time.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
#![cfg(target_family = "wasm")]
|
||||
/*
|
||||
For the Web REPL (repl_www), we build the compiler as a Wasm module.
|
||||
SystemTime is the only thing in the compiler that would need a special implementation for this.
|
||||
There is a WASI implementation for it, but we are targeting the browser, not WASI!
|
||||
It's possible to write browser versions of WASI's low-level ABI but we'd rather avoid it.
|
||||
Instead we use these dummy implementations, which should just disappear at compile time.
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SystemTime;
|
||||
|
||||
impl SystemTime {
|
||||
pub fn now() -> Self {
|
||||
SystemTime
|
||||
}
|
||||
pub fn duration_since(&self, _: SystemTime) -> Result<Duration, String> {
|
||||
Ok(Duration)
|
||||
}
|
||||
pub fn elapsed(&self) -> Result<Duration, String> {
|
||||
Ok(Duration)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Duration;
|
||||
|
||||
impl Duration {
|
||||
pub fn checked_sub(&self, _: Duration) -> Option<Duration> {
|
||||
Some(Duration)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Duration {
|
||||
fn default() -> Self {
|
||||
Duration
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for Duration {
|
||||
fn add_assign(&mut self, _: Duration) {}
|
||||
}
|
290
compiler/load/src/work.rs
Normal file
290
compiler/load/src/work.rs
Normal file
|
@ -0,0 +1,290 @@
|
|||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::{ModuleId, PackageQualified};
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
/// NOTE the order of definition of the phases is used by the ord instance
|
||||
/// make sure they are ordered from first to last!
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)]
|
||||
pub enum Phase {
|
||||
LoadHeader,
|
||||
Parse,
|
||||
CanonicalizeAndConstrain,
|
||||
SolveTypes,
|
||||
FindSpecializations,
|
||||
MakeSpecializations,
|
||||
}
|
||||
|
||||
/// NOTE keep up to date manually, from ParseAndGenerateConstraints to the highest phase we support
|
||||
const PHASES: [Phase; 6] = [
|
||||
Phase::LoadHeader,
|
||||
Phase::Parse,
|
||||
Phase::CanonicalizeAndConstrain,
|
||||
Phase::SolveTypes,
|
||||
Phase::FindSpecializations,
|
||||
Phase::MakeSpecializations,
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Status {
|
||||
NotStarted,
|
||||
Pending,
|
||||
Done,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
enum Job<'a> {
|
||||
Step(ModuleId, Phase),
|
||||
ResolveShorthand(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Dependencies<'a> {
|
||||
waiting_for: MutMap<Job<'a>, MutSet<Job<'a>>>,
|
||||
notifies: MutMap<Job<'a>, MutSet<Job<'a>>>,
|
||||
status: MutMap<Job<'a>, Status>,
|
||||
}
|
||||
|
||||
impl<'a> Dependencies<'a> {
|
||||
/// Add all the dependencies for a module, return (module, phase) pairs that can make progress
|
||||
pub fn add_module(
|
||||
&mut self,
|
||||
module_id: ModuleId,
|
||||
dependencies: &MutSet<PackageQualified<'a, ModuleId>>,
|
||||
goal_phase: Phase,
|
||||
) -> MutSet<(ModuleId, Phase)> {
|
||||
use Phase::*;
|
||||
|
||||
let mut output = MutSet::default();
|
||||
|
||||
for dep in dependencies.iter() {
|
||||
let has_package_dependency = self.add_package_dependency(dep, Phase::LoadHeader);
|
||||
|
||||
let dep = *dep.as_inner();
|
||||
|
||||
if !has_package_dependency {
|
||||
// loading can start immediately on this dependency
|
||||
output.insert((dep, Phase::LoadHeader));
|
||||
}
|
||||
|
||||
// to parse and generate constraints, the headers of all dependencies must be loaded!
|
||||
// otherwise, we don't know whether an imported symbol is actually exposed
|
||||
self.add_dependency_help(module_id, dep, Phase::Parse, Phase::LoadHeader);
|
||||
|
||||
// to canonicalize a module, all its dependencies must be canonicalized
|
||||
self.add_dependency(module_id, dep, Phase::CanonicalizeAndConstrain);
|
||||
|
||||
// to typecheck a module, all its dependencies must be type checked already
|
||||
self.add_dependency(module_id, dep, Phase::SolveTypes);
|
||||
|
||||
if goal_phase >= FindSpecializations {
|
||||
self.add_dependency(module_id, dep, Phase::FindSpecializations);
|
||||
}
|
||||
|
||||
if goal_phase >= MakeSpecializations {
|
||||
self.add_dependency(dep, module_id, Phase::MakeSpecializations);
|
||||
}
|
||||
}
|
||||
|
||||
// add dependencies for self
|
||||
// phase i + 1 of a file always depends on phase i being completed
|
||||
{
|
||||
let mut i = 0;
|
||||
while PHASES[i] < goal_phase {
|
||||
self.add_dependency_help(module_id, module_id, PHASES[i + 1], PHASES[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.add_to_status(module_id, goal_phase);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn add_to_status(&mut self, module_id: ModuleId, goal_phase: Phase) {
|
||||
for phase in PHASES.iter() {
|
||||
if *phase > goal_phase {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Entry::Vacant(entry) = self.status.entry(Job::Step(module_id, *phase)) {
|
||||
entry.insert(Status::NotStarted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Propagate a notification, return (module, phase) pairs that can make progress
|
||||
pub fn notify(&mut self, module_id: ModuleId, phase: Phase) -> MutSet<(ModuleId, Phase)> {
|
||||
self.notify_help(Job::Step(module_id, phase))
|
||||
}
|
||||
|
||||
/// Propagate a notification, return (module, phase) pairs that can make progress
|
||||
pub fn notify_package(&mut self, shorthand: &'a str) -> MutSet<(ModuleId, Phase)> {
|
||||
self.notify_help(Job::ResolveShorthand(shorthand))
|
||||
}
|
||||
|
||||
fn notify_help(&mut self, key: Job<'a>) -> MutSet<(ModuleId, Phase)> {
|
||||
self.status.insert(key.clone(), Status::Done);
|
||||
|
||||
let mut output = MutSet::default();
|
||||
|
||||
if let Some(to_notify) = self.notifies.get(&key) {
|
||||
for notify_key in to_notify {
|
||||
let mut is_empty = false;
|
||||
if let Some(waiting_for_pairs) = self.waiting_for.get_mut(notify_key) {
|
||||
waiting_for_pairs.remove(&key);
|
||||
is_empty = waiting_for_pairs.is_empty();
|
||||
}
|
||||
|
||||
if is_empty {
|
||||
self.waiting_for.remove(notify_key);
|
||||
|
||||
if let Job::Step(module, phase) = *notify_key {
|
||||
output.insert((module, phase));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.notifies.remove(&key);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn add_package_dependency(
|
||||
&mut self,
|
||||
module: &PackageQualified<'a, ModuleId>,
|
||||
next_phase: Phase,
|
||||
) -> bool {
|
||||
match module {
|
||||
PackageQualified::Unqualified(_) => {
|
||||
// no dependency, we can just start loading the file
|
||||
false
|
||||
}
|
||||
PackageQualified::Qualified(shorthand, module_id) => {
|
||||
let job = Job::ResolveShorthand(shorthand);
|
||||
let next_step = Job::Step(*module_id, next_phase);
|
||||
match self.status.get(&job) {
|
||||
None | Some(Status::NotStarted) | Some(Status::Pending) => {
|
||||
// this shorthand is not resolved, add a dependency
|
||||
{
|
||||
let entry = self
|
||||
.waiting_for
|
||||
.entry(next_step.clone())
|
||||
.or_insert_with(Default::default);
|
||||
|
||||
entry.insert(job.clone());
|
||||
}
|
||||
|
||||
{
|
||||
let entry = self.notifies.entry(job).or_insert_with(Default::default);
|
||||
|
||||
entry.insert(next_step);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
Some(Status::Done) => {
|
||||
// shorthand is resolved; no dependency
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A waits for B, and B will notify A when it completes the phase
|
||||
fn add_dependency(&mut self, a: ModuleId, b: ModuleId, phase: Phase) {
|
||||
self.add_dependency_help(a, b, phase, phase);
|
||||
}
|
||||
|
||||
/// phase_a of module a is waiting for phase_b of module_b
|
||||
fn add_dependency_help(&mut self, a: ModuleId, b: ModuleId, phase_a: Phase, phase_b: Phase) {
|
||||
// no need to wait if the dependency is already done!
|
||||
if let Some(Status::Done) = self.status.get(&Job::Step(b, phase_b)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = Job::Step(a, phase_a);
|
||||
let value = Job::Step(b, phase_b);
|
||||
match self.waiting_for.get_mut(&key) {
|
||||
Some(existing) => {
|
||||
existing.insert(value);
|
||||
}
|
||||
None => {
|
||||
let mut set = MutSet::default();
|
||||
set.insert(value);
|
||||
self.waiting_for.insert(key, set);
|
||||
}
|
||||
}
|
||||
|
||||
let key = Job::Step(b, phase_b);
|
||||
let value = Job::Step(a, phase_a);
|
||||
match self.notifies.get_mut(&key) {
|
||||
Some(existing) => {
|
||||
existing.insert(value);
|
||||
}
|
||||
None => {
|
||||
let mut set = MutSet::default();
|
||||
set.insert(value);
|
||||
self.notifies.insert(key, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn solved_all(&self) -> bool {
|
||||
debug_assert_eq!(self.notifies.is_empty(), self.waiting_for.is_empty());
|
||||
|
||||
for status in self.status.values() {
|
||||
match status {
|
||||
Status::Done => {
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn prepare_start_phase(&mut self, module_id: ModuleId, phase: Phase) -> PrepareStartPhase {
|
||||
match self.status.get_mut(&Job::Step(module_id, phase)) {
|
||||
Some(current @ Status::NotStarted) => {
|
||||
// start this phase!
|
||||
*current = Status::Pending;
|
||||
PrepareStartPhase::Continue
|
||||
}
|
||||
Some(Status::Pending) => {
|
||||
// don't start this task again!
|
||||
PrepareStartPhase::Done
|
||||
}
|
||||
Some(Status::Done) => {
|
||||
// don't start this task again, but tell those waiting for it they can continue
|
||||
let new = self.notify(module_id, phase);
|
||||
|
||||
PrepareStartPhase::Recurse(new)
|
||||
}
|
||||
None => match phase {
|
||||
Phase::LoadHeader => {
|
||||
// this is fine, mark header loading as pending
|
||||
self.status
|
||||
.insert(Job::Step(module_id, Phase::LoadHeader), Status::Pending);
|
||||
|
||||
PrepareStartPhase::Continue
|
||||
}
|
||||
_ => unreachable!(
|
||||
"Pair {:?} is not in dependencies.status, that should never happen!",
|
||||
(module_id, phase)
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PrepareStartPhase {
|
||||
Continue,
|
||||
Done,
|
||||
Recurse(MutSet<(ModuleId, Phase)>),
|
||||
}
|
|
@ -16,7 +16,6 @@ mod helpers;
|
|||
mod test_load {
|
||||
use crate::helpers::fixtures_dir;
|
||||
use bumpalo::Bump;
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_can::def::Declaration::*;
|
||||
use roc_can::def::Def;
|
||||
use roc_collections::all::MutMap;
|
||||
|
@ -24,14 +23,45 @@ mod test_load {
|
|||
use roc_load::file::LoadedModule;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::can_problem;
|
||||
use roc_reporting::report::RocDocAllocator;
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
use roc_types::subs::Subs;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TARGET_INFO: roc_target::TargetInfo = roc_target::TargetInfo::default_x86_64();
|
||||
|
||||
// HELPERS
|
||||
|
||||
fn format_can_problems(
|
||||
problems: Vec<Problem>,
|
||||
home: ModuleId,
|
||||
interns: &Interns,
|
||||
filename: PathBuf,
|
||||
src: &str,
|
||||
) -> String {
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let lines = LineInfo::new(src);
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
let reports = problems
|
||||
.into_iter()
|
||||
.map(|problem| can_problem(&alloc, &lines, filename.clone(), problem).pretty(&alloc));
|
||||
|
||||
let mut buf = String::new();
|
||||
alloc
|
||||
.stack(reports)
|
||||
.append(alloc.line())
|
||||
.1
|
||||
.render_raw(70, &mut roc_reporting::report::CiWrite::new(&mut buf))
|
||||
.unwrap();
|
||||
buf
|
||||
}
|
||||
|
||||
fn multiple_modules(files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
|
||||
use roc_load::file::LoadingProblem;
|
||||
|
||||
|
@ -44,11 +74,19 @@ mod test_load {
|
|||
Ok(Err(loading_problem)) => Err(format!("{:?}", loading_problem)),
|
||||
Ok(Ok(mut loaded_module)) => {
|
||||
let home = loaded_module.module_id;
|
||||
let (filepath, src) = loaded_module.sources.get(&home).unwrap();
|
||||
|
||||
let can_problems = loaded_module.can_problems.remove(&home).unwrap_or_default();
|
||||
if !can_problems.is_empty() {
|
||||
return Err(format_can_problems(
|
||||
can_problems,
|
||||
home,
|
||||
&loaded_module.interns,
|
||||
filepath.clone(),
|
||||
src,
|
||||
));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
loaded_module.can_problems.remove(&home).unwrap_or_default(),
|
||||
Vec::new()
|
||||
);
|
||||
assert_eq!(
|
||||
loaded_module
|
||||
.type_problems
|
||||
|
@ -66,9 +104,8 @@ mod test_load {
|
|||
arena: &'a Bump,
|
||||
mut files: Vec<(&str, &str)>,
|
||||
) -> Result<Result<LoadedModule, roc_load::file::LoadingProblem<'a>>, std::io::Error> {
|
||||
use std::fs::File;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
let stdlib = roc_builtins::std::standard_stdlib();
|
||||
|
@ -80,12 +117,15 @@ mod test_load {
|
|||
let dir = tempdir()?;
|
||||
|
||||
let app_module = files.pop().unwrap();
|
||||
let interfaces = files;
|
||||
|
||||
for (name, source) in interfaces {
|
||||
for (name, source) in files {
|
||||
let mut filename = PathBuf::from(name);
|
||||
filename.set_extension("roc");
|
||||
let file_path = dir.path().join(filename.clone());
|
||||
|
||||
// Create any necessary intermediate directories (e.g. /platform)
|
||||
fs::create_dir_all(file_path.parent().unwrap())?;
|
||||
|
||||
let mut file = File::create(file_path)?;
|
||||
writeln!(file, "{}", source)?;
|
||||
file_handles.push(file);
|
||||
|
@ -108,7 +148,6 @@ mod test_load {
|
|||
dir.path(),
|
||||
exposed_types,
|
||||
TARGET_INFO,
|
||||
builtin_defs_map,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -132,7 +171,6 @@ mod test_load {
|
|||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
TARGET_INFO,
|
||||
builtin_defs_map,
|
||||
);
|
||||
let mut loaded_module = match loaded {
|
||||
Ok(x) => x,
|
||||
|
@ -298,7 +336,6 @@ mod test_load {
|
|||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
TARGET_INFO,
|
||||
builtin_defs_map,
|
||||
);
|
||||
|
||||
let mut loaded_module = loaded.expect("Test module failed to load");
|
||||
|
@ -602,4 +639,162 @@ mod test_load {
|
|||
Ok(_) => unreachable!("we expect failure here"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_parse_error() {
|
||||
let modules = vec![
|
||||
(
|
||||
"platform/Package-Config.roc",
|
||||
indoc!(
|
||||
r#"
|
||||
platform "examples/hello-world"
|
||||
requires {} { main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
blah 1 2 3 # causing a parse error on purpose
|
||||
|
||||
mainForHost : Str
|
||||
"#
|
||||
),
|
||||
),
|
||||
(
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
app "hello-world"
|
||||
packages { pf: "platform" }
|
||||
imports []
|
||||
provides [ main ] to pf
|
||||
|
||||
main = "Hello, World!\n"
|
||||
"#
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
match multiple_modules(modules) {
|
||||
Err(report) => {
|
||||
assert!(report.contains("NOT END OF FILE"));
|
||||
assert!(report.contains("blah 1 2 3 # causing a parse error on purpose"));
|
||||
}
|
||||
Ok(_) => unreachable!("we expect failure here"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// See https://github.com/rtfeldman/roc/issues/2413
|
||||
fn platform_exposes_main_return_by_pointer_issue() {
|
||||
let modules = vec![
|
||||
(
|
||||
"platform/Package-Config.roc",
|
||||
indoc!(
|
||||
r#"
|
||||
platform "examples/hello-world"
|
||||
requires {} { main : { content: Str, other: Str } }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
|
||||
mainForHost : { content: Str, other: Str }
|
||||
mainForHost = main
|
||||
"#
|
||||
),
|
||||
),
|
||||
(
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
app "hello-world"
|
||||
packages { pf: "platform" }
|
||||
imports []
|
||||
provides [ main ] to pf
|
||||
|
||||
main = { content: "Hello, World!\n", other: "" }
|
||||
"#
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
assert!(multiple_modules(modules).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opaque_wrapped_unwrapped_outside_defining_module() {
|
||||
let modules = vec![
|
||||
(
|
||||
"Age",
|
||||
indoc!(
|
||||
r#"
|
||||
interface Age exposes [ Age ] imports []
|
||||
|
||||
Age := U32
|
||||
"#
|
||||
),
|
||||
),
|
||||
(
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ]
|
||||
|
||||
twenty = $Age 20
|
||||
|
||||
readAge = \$Age n -> n
|
||||
"#
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
let err = multiple_modules(modules).unwrap_err();
|
||||
let err = strip_ansi_escapes::strip(err).unwrap();
|
||||
let err = String::from_utf8(err).unwrap();
|
||||
assert_eq!(
|
||||
err,
|
||||
indoc!(
|
||||
r#"
|
||||
── OPAQUE DECLARED OUTSIDE SCOPE ───────────────────────────────────────────────
|
||||
|
||||
The unwrapped opaque type Age referenced here:
|
||||
|
||||
3│ twenty = $Age 20
|
||||
^^^^
|
||||
|
||||
is imported from another module:
|
||||
|
||||
1│ interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ]
|
||||
^^^^^^^^^^^
|
||||
|
||||
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
|
||||
|
||||
── OPAQUE DECLARED OUTSIDE SCOPE ───────────────────────────────────────────────
|
||||
|
||||
The unwrapped opaque type Age referenced here:
|
||||
|
||||
5│ readAge = \$Age n -> n
|
||||
^^^^
|
||||
|
||||
is imported from another module:
|
||||
|
||||
1│ interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ]
|
||||
^^^^^^^^^^^
|
||||
|
||||
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
|
||||
|
||||
── UNUSED IMPORT ───────────────────────────────────────────────────────────────
|
||||
|
||||
Nothing from Age is used in this module.
|
||||
|
||||
1│ interface Main exposes [ twenty, readAge ] imports [ Age.{ Age } ]
|
||||
^^^^^^^^^^^
|
||||
|
||||
Since Age isn't used, you don't need to import it.
|
||||
"#
|
||||
),
|
||||
"\n{}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ license = "UPL-1.0"
|
|||
roc_region = { path = "../region" }
|
||||
roc_ident = { path = "../ident" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = {path = "../../error_macros"}
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
lazy_static = "1.4.0"
|
||||
static_assertions = "1.1.0"
|
||||
|
|
|
@ -47,7 +47,8 @@ pub enum BinOp {
|
|||
Or,
|
||||
Pizza,
|
||||
Assignment,
|
||||
HasType,
|
||||
IsAliasType,
|
||||
IsOpaqueType,
|
||||
Backpassing,
|
||||
// lowest precedence
|
||||
}
|
||||
|
@ -59,7 +60,7 @@ impl BinOp {
|
|||
Caret | Star | Slash | Percent | Plus | Minus | LessThan | GreaterThan => 1,
|
||||
DoubleSlash | DoublePercent | Equals | NotEquals | LessThanOrEq | GreaterThanOrEq
|
||||
| And | Or | Pizza => 2,
|
||||
Assignment | HasType | Backpassing => unreachable!(),
|
||||
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +104,7 @@ impl BinOp {
|
|||
Equals | NotEquals | LessThan | GreaterThan | LessThanOrEq | GreaterThanOrEq => {
|
||||
NonAssociative
|
||||
}
|
||||
Assignment | HasType | Backpassing => unreachable!(),
|
||||
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +117,7 @@ impl BinOp {
|
|||
And => 3,
|
||||
Or => 2,
|
||||
Pizza => 1,
|
||||
Assignment | HasType | Backpassing => unreachable!(),
|
||||
Assignment | IsAliasType | IsOpaqueType | Backpassing => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +155,8 @@ impl std::fmt::Display for BinOp {
|
|||
Or => "||",
|
||||
Pizza => "|>",
|
||||
Assignment => "=",
|
||||
HasType => ":",
|
||||
IsAliasType => ":",
|
||||
IsOpaqueType => ":=",
|
||||
Backpassing => "<-",
|
||||
};
|
||||
|
||||
|
|
|
@ -59,7 +59,9 @@ pub enum TagName {
|
|||
Closure(Symbol),
|
||||
}
|
||||
|
||||
static_assertions::assert_eq_size!([u8; 24], TagName);
|
||||
roc_error_macros::assert_sizeof_aarch64!(TagName, 24);
|
||||
roc_error_macros::assert_sizeof_wasm!(TagName, 16);
|
||||
roc_error_macros::assert_sizeof_default!(TagName, 24);
|
||||
|
||||
impl TagName {
|
||||
pub fn as_ident_str(&self, interns: &Interns, home: ModuleId) -> IdentStr {
|
||||
|
|
|
@ -111,6 +111,7 @@ pub enum LowLevel {
|
|||
NumShiftRightBy,
|
||||
NumShiftRightZfBy,
|
||||
NumIntCast,
|
||||
NumToIntChecked,
|
||||
NumToStr,
|
||||
Eq,
|
||||
NotEq,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue